mouse.rsannotatedmouse.rssource187 lines · 6.2 KB · raw
1use crate::input::Pins;
2use bincode::{Decode, Encode};
3use genesis_config::MegaMouseJoypadState;
4use std::cmp;
5
6const MAX_COUNTER: u8 = 9;
7
8const COUNTER_INCREMENT_WAIT_CYCLES: u32 = 23; // Roughly 3 μs, some games depend on counter changing well before TL changes
9const TL_CHANGE_WAIT_CYCLES: u32 = 76; // Roughly 10 μs
10
11#[derive(Debug, Clone, Encode, Decode)]
12pub struct MegaMouseState {
13    pub joypad: MegaMouseJoypadState,
14    latched: MegaMouseJoypadState,
15    latched_x: i16,
16    latched_y: i16,
17    sensitivity: f64,
18    counter: u8,
19    tl: bool,
20    clock_frozen: bool,
21    counter_increment_wait_cycles: u32,
22    tl_change_wait_cycles: u32,
23    prev_th: bool,
24    prev_tr: bool,
25}
26
27impl MegaMouseState {
28    pub fn new(joypad: MegaMouseJoypadState, sensitivity: f64) -> Self {
29        Self {
30            joypad,
31            latched: joypad,
32            latched_x: 0,
33            latched_y: 0,
34            sensitivity,
35            counter: 0,
36            tl: true,
37            clock_frozen: false,
38            counter_increment_wait_cycles: 0,
39            tl_change_wait_cycles: 0,
40            prev_th: true,
41            prev_tr: true,
42        }
43    }
44
45    pub fn update_pins(&mut self, pins: &mut Pins) {
46        let th = pins.th();
47        let tr = pins.tr();
48
49        let th_changed = th != self.prev_th;
50        let tr_changed = tr != self.prev_tr;
51
52        if tr_changed {
53            if tr != self.tl && !self.clock_frozen {
54                // TL changes to reflect TR, but only after a delay (which several games depend on)
55                self.tl_change_wait_cycles = TL_CHANGE_WAIT_CYCLES;
56
57                if !th {
58                    // TR changes while TH=0 clock the internal counter
59                    self.counter_increment_wait_cycles = COUNTER_INCREMENT_WAIT_CYCLES;
60                }
61            } else if tr == self.tl {
62                self.counter_increment_wait_cycles = 0;
63                self.tl_change_wait_cycles = 0;
64            }
65        }
66
67        if th {
68            // While TH=1, mouse is deselected and outputs constants 0s
69            self.counter = 0;
70            self.counter_increment_wait_cycles = 0;
71
72            // TH=1 + TR=1 resets the mouse
73            self.clock_frozen &= !tr;
74
75            // There appears to be additional delay after a reset, but not always, and it's not
76            // clear to me why that delay happens so I'm not emulating it
77        } else if th_changed {
78            // TH 1->0 transition begins a transfer
79            self.latch_inputs();
80            self.counter = 1;
81
82            // If previous state was TH=1 TR=0, the clock freezes until the mouse is reset (based on test ROM)
83            // TODO does this only happen if there was also a TR 0->1 transition?
84            self.clock_frozen = !self.prev_tr;
85        }
86
87        self.prev_th = th;
88        self.prev_tr = tr;
89
90        log::debug!(
91            "Mouse update, TH={} TR={}; TL={} counter={}",
92            u8::from(th),
93            u8::from(tr),
94            u8::from(self.tl),
95            self.counter
96        );
97
98        pins.input_tl(self.tl);
99
100        match self.counter {
101            0 => {
102                // No transfer in progress, output constant 0s
103                pins.input_data_nibble(0b0000);
104            }
105            1..=3 => {
106                // Mouse ID (3 nibble sequence)
107                pins.input_data_nibble([0xB, 0xF, 0xF][(self.counter - 1) as usize]);
108            }
109            4 => {
110                // Axis sign and overflow bits (overflow not emulated)
111                pins.input_d3(false); // Y overflow
112                pins.input_d2(false); // X overflow
113                pins.input_d1(self.latched_y < 0);
114                pins.input_d0(self.latched_x < 0);
115            }
116            5 => {
117                // Buttons
118                pins.input_d3(self.latched.start);
119                pins.input_d2(self.latched.middle);
120                pins.input_d1(self.latched.right);
121                pins.input_d0(self.latched.left);
122            }
123            6 => {
124                // X axis, high nibble
125                pins.input_data_nibble((self.latched_x as u8) >> 4);
126            }
127            7 => {
128                // X axis, low nibble
129                pins.input_data_nibble((self.latched_x as u8) & 0xF);
130            }
131            8 => {
132                // Y axis, high nibble
133                pins.input_data_nibble((self.latched_y as u8) >> 4);
134            }
135            9 => {
136                // Y axis, low nibble
137                pins.input_data_nibble((self.latched_y as u8) & 0xF);
138            }
139            _ => panic!(
140                "invalid Mega Mouse counter value {}, should be <= {MAX_COUNTER}",
141                self.counter
142            ),
143        }
144    }
145
146    fn latch_inputs(&mut self) {
147        fn f64_to_i9(value: f64) -> i16 {
148            // Mega Mouse axis values are signed 9-bit
149            // Arbitrarily multiply by 0.3 because using full pixel values is way too sensitive
150            (0.3 * value).round().clamp(-256.0, 255.0) as i16
151        }
152
153        let dx = self.sensitivity * (self.joypad.x_position.get() - self.latched.x_position.get());
154        let dy = self.sensitivity * (self.joypad.y_position.get() - self.latched.y_position.get());
155        self.latched = self.joypad;
156
157        self.latched_x = f64_to_i9(dx);
158        self.latched_y = f64_to_i9(-dy); // Mega Mouse Y axis is inverted (+ is up, - is down)
159    }
160
161    pub fn tick(&mut self, m68k_cycles: u32, pins: &mut Pins) {
162        if self.counter_increment_wait_cycles == 0 && self.tl_change_wait_cycles == 0 {
163            return;
164        }
165
166        if self.counter_increment_wait_cycles != 0 {
167            self.counter_increment_wait_cycles =
168                self.counter_increment_wait_cycles.saturating_sub(m68k_cycles);
169            if self.counter_increment_wait_cycles == 0 {
170                self.counter = cmp::min(MAX_COUNTER, self.counter + 1);
171            }
172        }
173
174        if self.tl_change_wait_cycles != 0 {
175            self.tl_change_wait_cycles = self.tl_change_wait_cycles.saturating_sub(m68k_cycles);
176            if self.tl_change_wait_cycles == 0 {
177                self.tl = self.prev_tr;
178            }
179        }
180
181        self.update_pins(pins);
182    }
183
184    pub fn set_sensitivity(&mut self, sensitivity: f64) {
185        self.sensitivity = sensitivity;
186    }
187}