Skip to main content

rustynes_core/
input_device.rs

1//! Optional non-standard input-device overlays for the `$4016`/`$4017`
2//! controller ports: the Arkanoid "Vaus" paddle and the NES Zapper light gun.
3//!
4//! These are **opt-in overlays**. The bus holds an `Option<InputDevice>` per
5//! port; when a port has no overlay device (the default) the standard
6//! controller / Four Score serial path runs completely unchanged, so the
7//! default + Four Score reads stay byte-identical and the determinism
8//! contract is preserved. A device is only consulted when explicitly attached
9//! via [`crate::Nes::set_paddle`] / [`crate::Nes::set_zapper`].
10//!
11//! ## Vaus paddle (Arkanoid controller)
12//!
13//! Per the `NESdev` "Arkanoid controller" page (NES 7-pin version), the device
14//! reports on the player-2 port (`$4017`):
15//!
16//! ```text
17//! 7  bit  0
18//! ---- ----
19//! xxxD Bxxx
20//!    | |
21//!    | +---- Fire button (1: pressed)        -> bit 3
22//!    +------ Serial control knob data        -> bit 4
23//!            (8/9-bit, inverted, MSb first)
24//! ```
25//!
26//! A write of `$4016` bit 0 = 1 -> 0 (the standard controller strobe) starts a
27//! "conversion": the 8-bit potentiometer value is latched MSb-first into the
28//! shift register. Each `$4017` read shifts out the next bit (on bit 4),
29//! **inverted** on the wire. After the register empties, reads repeat the
30//! serial-in bit (the 9th / `LSb`). The fire button (bit 3) is returned directly
31//! and is unaffected by the strobe.
32//!
33//! The in-tree `vaus-test` ROM (Damian Yerrick) documents the NES wiring as
34//! `$4017 D3: Button`, `$4017 D4: Position (8 bits, MSB first)` — matching the
35//! wiki layout above.
36//!
37//! ## Zapper light gun
38//!
39//! Per the `NESdev` "Zapper" page (NES variant), the device reports on its port:
40//!
41//! ```text
42//! 7  bit  0
43//! ---- ----
44//! xxxT Wxxx
45//!    | |
46//!    | +---- Light sensed (0: detected; 1: NOT detected)  -> bit 3
47//!    +------ Trigger (1: pulled/half-pulled; 0: released)  -> bit 4
48//! ```
49//!
50//! Note the inverted light polarity: bit 3 is **0** while light is detected and
51//! **1** otherwise. The light sensor stays active for roughly 19-26 scanlines
52//! after seeing a bright pixel (the photodiode capacitor drains exponentially);
53//! we use a simpler frame-granular model: a luminance threshold sampled at the
54//! aim point once per completed frame (sufficient because games re-sample every
55//! frame). The Zapper has no shift register — its byte is read in parallel and
56//! is independent of the strobe.
57
58/// The Arkanoid "Vaus" paddle overlay state.
59///
60/// Models the NES 7-pin variant on `$4017`: an 8-bit potentiometer value
61/// shifted out MSb-first (inverted on the wire) on bit 4, plus a fire button
62/// on bit 3.
63#[derive(Clone, Copy, Debug)]
64pub struct VausState {
65    /// The raw (pre-inversion) 8-bit potentiometer position. `$00` is the far
66    /// left, `$FF` the far right (per the wiki, turning right increases the
67    /// value).
68    pub(crate) position: u8,
69    /// Whether the fire button is currently held.
70    pub(crate) fire: bool,
71    /// 8-bit shift register, MSb-first readout. Reloaded from `position` on the
72    /// strobe falling edge (conversion latch). The serial-in bit (repeated
73    /// after the register empties) is the current `LSb`.
74    pub(crate) shift: u8,
75    /// Last strobe level written (bit 0 of `$4016`).
76    pub(crate) strobe: bool,
77}
78
79impl Default for VausState {
80    fn default() -> Self {
81        Self::new()
82    }
83}
84
85impl VausState {
86    /// New paddle centered, button released.
87    #[must_use]
88    pub const fn new() -> Self {
89        Self {
90            position: 0x80,
91            fire: false,
92            shift: 0x80,
93            strobe: false,
94        }
95    }
96
97    /// Update the live paddle position + fire state. Takes effect on the next
98    /// conversion (strobe falling edge), matching the standard controller's
99    /// latch-on-strobe semantics.
100    pub const fn set(&mut self, position: u8, fire: bool) {
101        self.position = position;
102        self.fire = fire;
103        if self.strobe {
104            self.shift = position;
105        }
106    }
107
108    /// Handle a `$4016` strobe write. On the rising edge the conversion latches
109    /// the current position into the shift register (we model the conversion as
110    /// instantaneous, which is the standard fixed-position emulation choice).
111    pub const fn write_strobe(&mut self, value: u8) {
112        let new_strobe = value & 1 != 0;
113        if new_strobe {
114            self.shift = self.position;
115        }
116        self.strobe = new_strobe;
117    }
118
119    /// Read the device byte for a `$4017` access, advancing the shift register.
120    /// Returns the full 8-bit value already positioned on bits 3 (fire) and 4
121    /// (knob data); the caller ORs in the open-bus upper bits.
122    ///
123    /// Bit 4 carries the **inverted** `MSb` of the shift register (knob data is
124    /// inverted on the wire per the wiki). Bit 3 carries the fire button (1 =
125    /// pressed). All other bits are 0.
126    pub const fn read(&mut self) -> u8 {
127        // Knob data bit: MSb of the shift register, inverted on the wire.
128        let data_bit = (self.shift >> 7) & 1;
129        let wire_data = data_bit ^ 1;
130        // Shift left, feeding the LSb back into the serial-in position so that
131        // post-empty reads repeat the 9th (serial-in) bit per the wiki.
132        let serial_in = self.shift & 1;
133        self.shift = (self.shift << 1) | serial_in;
134        let fire = self.fire as u8;
135        (wire_data << 4) | (fire << 3)
136    }
137
138    /// Side-effect-free sample of the next device byte (debugger peek).
139    #[must_use]
140    pub const fn peek(&self) -> u8 {
141        let data_bit = (self.shift >> 7) & 1;
142        let wire_data = data_bit ^ 1;
143        let fire = self.fire as u8;
144        (wire_data << 4) | (fire << 3)
145    }
146
147    /// Reconstruct from save-state parts.
148    #[must_use]
149    pub const fn from_parts(position: u8, fire: bool, shift: u8, strobe: bool) -> Self {
150        Self {
151            position,
152            fire,
153            shift,
154            strobe,
155        }
156    }
157
158    /// Raw potentiometer position (save-state).
159    #[must_use]
160    pub const fn position_raw(&self) -> u8 {
161        self.position
162    }
163    /// Raw fire state (save-state).
164    #[must_use]
165    pub const fn fire_raw(&self) -> bool {
166        self.fire
167    }
168    /// Raw shift register (save-state).
169    #[must_use]
170    pub const fn shift_raw(&self) -> u8 {
171        self.shift
172    }
173    /// Raw strobe state (save-state).
174    #[must_use]
175    pub const fn strobe_raw(&self) -> bool {
176        self.strobe
177    }
178}
179
180/// The NES Zapper light-gun overlay state.
181///
182/// Models the NES variant: bit 3 = light sensed (0 detected / 1 not), bit 4 =
183/// trigger (1 pulled). Light detection samples the PPU framebuffer luminance at
184/// the aim point once per frame (a frame-granular model — see `light_seen`).
185#[derive(Clone, Copy, Debug, Default)]
186pub struct ZapperState {
187    /// Aim point X (0..256), screen pixel. Out-of-range = aimed off-screen.
188    pub(crate) x: u16,
189    /// Aim point Y (0..240), screen scanline. Out-of-range = aimed off-screen.
190    pub(crate) y: u16,
191    /// Whether the trigger is currently pulled.
192    pub(crate) trigger: bool,
193    /// Whether the photodiode currently sees light. Set by [`Self::sample_light`]
194    /// each frame from the framebuffer luminance at the aim point; while `true`,
195    /// bit 3 reads 0 (light detected), else bit 3 reads 1 (no light). This is a
196    /// frame-granular model: games re-sample every frame, so per-frame
197    /// resolution is sufficient (the wiki's ~19-26-scanline photodiode hold
198    /// matters only for sub-frame timing tricks, which the supported games do
199    /// not require).
200    pub(crate) light_seen: bool,
201}
202
203/// Luminance threshold (0..255, Rec.601-ish) above which a sampled framebuffer
204/// pixel counts as "bright enough" to trigger the photodiode.
205pub(crate) const ZAPPER_LUMA_THRESHOLD: u16 = 0x80;
206
207impl ZapperState {
208    /// New zapper aimed off-screen, trigger released, no light.
209    #[must_use]
210    pub const fn new() -> Self {
211        Self {
212            x: u16::MAX,
213            y: u16::MAX,
214            trigger: false,
215            light_seen: false,
216        }
217    }
218
219    /// Update the live aim point + trigger state.
220    pub const fn set(&mut self, x: u16, y: u16, trigger: bool) {
221        self.x = x;
222        self.y = y;
223        self.trigger = trigger;
224    }
225
226    /// Sample the framebuffer luminance at the aim point, setting `light_seen`
227    /// if the pixel is bright enough. `framebuffer` is the PPU's RGBA8 256x240
228    /// buffer. Called once per frame by the bus after the frame completes.
229    pub fn sample_light(&mut self, framebuffer: &[u8]) {
230        const W: usize = 256;
231        const H: usize = 240;
232        if (self.x as usize) >= W || (self.y as usize) >= H {
233            // Aimed off-screen: never sees light.
234            self.light_seen = false;
235            return;
236        }
237        let idx = ((self.y as usize) * W + (self.x as usize)) * 4;
238        // Guard against a partial framebuffer.
239        if idx + 2 >= framebuffer.len() {
240            self.light_seen = false;
241            return;
242        }
243        let r = u16::from(framebuffer[idx]);
244        let g = u16::from(framebuffer[idx + 1]);
245        let b = u16::from(framebuffer[idx + 2]);
246        // Rec.601 luma approximation (integer): (77*R + 150*G + 29*B) >> 8.
247        let luma = (77 * r + 150 * g + 29 * b) >> 8;
248        self.light_seen = luma >= ZAPPER_LUMA_THRESHOLD;
249    }
250
251    /// The device byte for a `$4016`/`$4017` access. Bit 3 = light (0 detected /
252    /// 1 not), bit 4 = trigger (1 pulled). Independent of the strobe (the
253    /// Zapper has no shift register). The caller ORs in the open-bus upper bits.
254    #[must_use]
255    pub const fn read(&self) -> u8 {
256        let light_not_detected = (!self.light_seen) as u8;
257        let trigger = self.trigger as u8;
258        (trigger << 4) | (light_not_detected << 3)
259    }
260
261    /// Reconstruct from save-state parts.
262    #[must_use]
263    pub const fn from_parts(x: u16, y: u16, trigger: bool, light_seen: bool) -> Self {
264        Self {
265            x,
266            y,
267            trigger,
268            light_seen,
269        }
270    }
271
272    /// Raw aim X (save-state).
273    #[must_use]
274    pub const fn x_raw(&self) -> u16 {
275        self.x
276    }
277    /// Raw aim Y (save-state).
278    #[must_use]
279    pub const fn y_raw(&self) -> u16 {
280        self.y
281    }
282    /// Raw trigger state (save-state).
283    #[must_use]
284    pub const fn trigger_raw(&self) -> bool {
285        self.trigger
286    }
287    /// Raw light-seen state (save-state).
288    #[must_use]
289    pub const fn light_seen_raw(&self) -> bool {
290        self.light_seen
291    }
292}
293
294/// The NES Power Pad (a.k.a. Family Fun Fitness / Family Trainer mat) overlay.
295///
296/// A 12-button mat read on the player-2 port (`$4017`) through two 8-bit
297/// parallel-in/serial-out shift registers (a pair of 4021s), strobed by the
298/// standard `$4016` controller strobe. The 12 buttons are indexed 0..=11
299/// (matching the mat's "1".."12" labels); the frontend decides which physical
300/// keys map to which mat button (and any Side-A/Side-B row inversion).
301///
302/// Per the `NESdev` "Power Pad" page (and Mesen's implementation), the button
303/// bits load into two registers and shift out LSb-first on bits 3 and 4 of each
304/// `$4017` read:
305///
306/// - register L (bit 3 of the read): buttons 2, 1, 5, 9, 6, 10, 11, 7;
307/// - register H (bit 4 of the read): buttons 4, 3, 12, 8, then four `1` bits.
308///
309/// (Button numbers are 1-based here, matching the mat labels; the code uses
310/// 0-based indices.) Each read shifts both registers right and feeds `1`s in
311/// from the top, so post-shift reads settle to "no button".
312#[derive(Clone, Copy, Debug, Default)]
313pub struct PowerPadState {
314    /// Live pressed-button mask: bit `i` (0..=11) set = mat button `i+1` held.
315    pub(crate) buttons: u16,
316    /// Low shift register (read out on bit 3).
317    pub(crate) shift_l: u8,
318    /// High shift register (read out on bit 4).
319    pub(crate) shift_h: u8,
320    /// Last strobe level written (bit 0 of `$4016`).
321    pub(crate) strobe: bool,
322}
323
324impl PowerPadState {
325    /// New mat with no buttons pressed.
326    #[must_use]
327    pub const fn new() -> Self {
328        Self {
329            buttons: 0,
330            shift_l: 0,
331            shift_h: 0,
332            strobe: false,
333        }
334    }
335
336    /// Reload both shift registers from the live button mask (the parallel
337    /// latch). The bit order matches the `NESdev` / Mesen serial layout.
338    const fn reload(&mut self) {
339        // bit(i) = (buttons >> i) & 1, as u8. Inlined (no closures in const fn).
340        let p = self.buttons;
341        // L: buttons 2,1,5,9,6,10,11,7 (0-based 1,0,4,8,5,9,10,6).
342        self.shift_l = (((p >> 1) & 1) as u8)
343            | (((p & 1) as u8) << 1)
344            | ((((p >> 4) & 1) as u8) << 2)
345            | ((((p >> 8) & 1) as u8) << 3)
346            | ((((p >> 5) & 1) as u8) << 4)
347            | ((((p >> 9) & 1) as u8) << 5)
348            | ((((p >> 10) & 1) as u8) << 6)
349            | ((((p >> 6) & 1) as u8) << 7);
350        // H: buttons 4,3,12,8 (0-based 3,2,11,7), then four 1 bits (read as H=1).
351        self.shift_h = (((p >> 3) & 1) as u8)
352            | ((((p >> 2) & 1) as u8) << 1)
353            | ((((p >> 11) & 1) as u8) << 2)
354            | ((((p >> 7) & 1) as u8) << 3)
355            | 0xF0;
356    }
357
358    /// Update the live pressed-button mask (bit `i` = mat button `i+1`). While
359    /// the strobe is held high the registers track the live mask (parallel
360    /// load), matching the standard controller's latch-while-strobed semantics.
361    pub const fn set(&mut self, buttons: u16) {
362        self.buttons = buttons & 0x0FFF;
363        if self.strobe {
364            self.reload();
365        }
366    }
367
368    /// Handle a `$4016` strobe write. While bit 0 is high the registers are
369    /// (re)loaded from the live buttons; the falling edge leaves the latched
370    /// snapshot to shift out.
371    pub const fn write_strobe(&mut self, value: u8) {
372        let new_strobe = value & 1 != 0;
373        if new_strobe {
374            self.reload();
375        }
376        self.strobe = new_strobe;
377    }
378
379    /// Read the device byte for a `$4017` access, shifting both registers.
380    /// Bit 4 = the current serial-out (`LSb`) of register H, bit 3 = register L;
381    /// each read then shifts both right (feeding `1`s in from the top). The
382    /// caller ORs in the open-bus upper bits. While the strobe is high the
383    /// registers are continuously reloaded (reads return the first button).
384    pub const fn read(&mut self) -> u8 {
385        if self.strobe {
386            self.reload();
387        }
388        let out = ((self.shift_h & 1) << 4) | ((self.shift_l & 1) << 3);
389        self.shift_l = (self.shift_l >> 1) | 0x80;
390        self.shift_h = (self.shift_h >> 1) | 0x80;
391        out
392    }
393
394    /// Side-effect-free sample of the next device byte (debugger peek).
395    #[must_use]
396    pub const fn peek(&self) -> u8 {
397        ((self.shift_h & 1) << 4) | ((self.shift_l & 1) << 3)
398    }
399
400    /// Reconstruct from save-state parts. `buttons` is masked to the 12 mat
401    /// bits, matching [`Self::set`], so a malformed save-state cannot inject
402    /// out-of-range bits.
403    #[must_use]
404    pub const fn from_parts(buttons: u16, shift_l: u8, shift_h: u8, strobe: bool) -> Self {
405        Self {
406            buttons: buttons & 0x0FFF,
407            shift_l,
408            shift_h,
409            strobe,
410        }
411    }
412
413    /// Raw live button mask (save-state).
414    #[must_use]
415    pub const fn buttons_raw(&self) -> u16 {
416        self.buttons
417    }
418    /// Raw low shift register (save-state).
419    #[must_use]
420    pub const fn shift_l_raw(&self) -> u8 {
421        self.shift_l
422    }
423    /// Raw high shift register (save-state).
424    #[must_use]
425    pub const fn shift_h_raw(&self) -> u8 {
426        self.shift_h
427    }
428    /// Raw strobe state (save-state).
429    #[must_use]
430    pub const fn strobe_raw(&self) -> bool {
431        self.strobe
432    }
433}
434
435/// The (Hyperkin / Nintendo) mouse overlay state — the SNES-style serial mouse
436/// as wired to an NES `$4016`/`$4017` port (D0 serial-out).
437///
438/// Per the `NESdev` "Mouse" page (the SNES mouse, the canonical serial mouse
439/// reused on the NES), a strobe latches a fixed-format 32-bit report that is
440/// then shifted out **MSb-first on D0** (one bit per port read):
441///
442/// ```text
443/// bits 31..28 : signature 0b0001 (device id nibble)
444/// bits 27..26 : 00
445/// bits 25..24 : sensitivity (00 low / 01 medium / 10 high; cycled by pressing
446///               both buttons on real hardware — we expose it as a field)
447/// bit  23     : left button  (1 = pressed)
448/// bit  22     : right button (1 = pressed)
449/// bits 21..16 : 0
450/// bits 15..8  : Y movement — bit 15 = direction sign (1 = up/-), bits 14..8 =
451///               magnitude (0..127); 0 when not moving
452/// bits  7..0  : X movement — bit  7 = direction sign (1 = left/-), bits  6..0 =
453///               magnitude (0..127); 0 when not moving
454/// ```
455///
456/// After the 32 real bits are shifted out, further reads return `1` (the open
457/// serial line idles high), matching the standard controller's post-sequence
458/// behavior. Like the standard controller, while the strobe is held high the
459/// report is continuously re-latched, so reads return the first (signature) bit.
460#[derive(Clone, Copy, Debug, Default)]
461pub struct SnesMouseState {
462    /// Live delta-X this frame (signed; clamped into +/-127 on latch).
463    pub(crate) dx: i16,
464    /// Live delta-Y this frame (signed; clamped into +/-127 on latch).
465    pub(crate) dy: i16,
466    /// Left button held.
467    pub(crate) left: bool,
468    /// Right button held.
469    pub(crate) right: bool,
470    /// Sensitivity (0 low / 1 medium / 2 high). Reported in the latched word.
471    pub(crate) sensitivity: u8,
472    /// 32-bit shift register, MSb-first readout. Reloaded from the live state on
473    /// the strobe (the parallel latch).
474    pub(crate) shift: u32,
475    /// Count of real bits shifted out (0..=32); beyond 32, reads idle high (`1`).
476    pub(crate) read_count: u8,
477    /// Last strobe level written (bit 0 of `$4016`).
478    pub(crate) strobe: bool,
479}
480
481impl SnesMouseState {
482    /// New mouse at rest (no movement, buttons up, low sensitivity).
483    #[must_use]
484    pub const fn new() -> Self {
485        Self {
486            dx: 0,
487            dy: 0,
488            left: false,
489            right: false,
490            sensitivity: 0,
491            shift: 0,
492            read_count: 0,
493            strobe: false,
494        }
495    }
496
497    /// Encode one axis into the 8-bit serial field: bit 7 = direction sign
498    /// (1 = negative), bits 6..0 = magnitude clamped to 0..=127.
499    const fn enc_axis(v: i16) -> u32 {
500        // `v.unsigned_abs()` avoids the `-i16::MIN` overflow panic that `-v`
501        // would hit for `v == i16::MIN` (32768 is unrepresentable as `i16`).
502        let mag = v.unsigned_abs() as u32;
503        let mag = if mag > 127 { 127 } else { mag };
504        let sign = if v < 0 { 1u32 } else { 0 };
505        (sign << 7) | mag
506    }
507
508    /// Encode the current live state into the 32-bit report word (MSb-first
509    /// serial order; bit 31 is shifted out first).
510    const fn encode(&self) -> u32 {
511        let dx = Self::enc_axis(self.dx);
512        let dy = Self::enc_axis(self.dy);
513        let sig = 0b0001u32 << 28;
514        let sens = ((self.sensitivity & 0b11) as u32) << 24;
515        let left = (self.left as u32) << 23;
516        let right = (self.right as u32) << 22;
517        sig | sens | left | right | (dy << 8) | dx
518    }
519
520    /// Update the live movement + button + sensitivity state. Takes effect on
521    /// the next latch (strobe), matching the standard controller semantics.
522    pub const fn set(&mut self, dx: i16, dy: i16, left: bool, right: bool, sensitivity: u8) {
523        self.dx = dx;
524        self.dy = dy;
525        self.left = left;
526        self.right = right;
527        self.sensitivity = sensitivity & 0b11;
528        if self.strobe {
529            self.shift = self.encode();
530            self.read_count = 0;
531        }
532    }
533
534    /// Handle a `$4016` strobe write. On a high level the 32-bit report is
535    /// (re)latched from the live state; the read counter resets.
536    pub const fn write_strobe(&mut self, value: u8) {
537        let new_strobe = value & 1 != 0;
538        if new_strobe {
539            self.shift = self.encode();
540            self.read_count = 0;
541        }
542        self.strobe = new_strobe;
543    }
544
545    /// Read the device byte for a port access, shifting out one MSb-first bit on
546    /// D0. After 32 bits the line idles high (`1` on D0). While the strobe is
547    /// held high the report is continuously re-latched (reads return bit 31).
548    /// The caller ORs in the open-bus upper bits.
549    pub const fn read(&mut self) -> u8 {
550        if self.strobe {
551            self.shift = self.encode();
552            self.read_count = 0;
553        }
554        if self.read_count >= 32 {
555            return 1; // serial line idles high after the report
556        }
557        let bit = (self.shift >> 31) & 1;
558        self.shift <<= 1;
559        self.read_count += 1;
560        bit as u8
561    }
562
563    /// Side-effect-free sample of the next D0 bit (debugger peek).
564    #[must_use]
565    pub const fn peek(&self) -> u8 {
566        if self.read_count >= 32 {
567            return 1;
568        }
569        ((self.shift >> 31) & 1) as u8
570    }
571
572    /// Reconstruct from save-state parts.
573    #[must_use]
574    #[allow(clippy::too_many_arguments)] // one arg per persisted field
575    pub const fn from_parts(
576        dx: i16,
577        dy: i16,
578        left: bool,
579        right: bool,
580        sensitivity: u8,
581        shift: u32,
582        read_count: u8,
583        strobe: bool,
584    ) -> Self {
585        Self {
586            dx,
587            dy,
588            left,
589            right,
590            sensitivity: sensitivity & 0b11,
591            shift,
592            read_count,
593            strobe,
594        }
595    }
596
597    /// Raw delta-X (save-state).
598    #[must_use]
599    pub const fn dx_raw(&self) -> i16 {
600        self.dx
601    }
602    /// Raw delta-Y (save-state).
603    #[must_use]
604    pub const fn dy_raw(&self) -> i16 {
605        self.dy
606    }
607    /// Raw left button (save-state).
608    #[must_use]
609    pub const fn left_raw(&self) -> bool {
610        self.left
611    }
612    /// Raw right button (save-state).
613    #[must_use]
614    pub const fn right_raw(&self) -> bool {
615        self.right
616    }
617    /// Raw sensitivity (save-state).
618    #[must_use]
619    pub const fn sensitivity_raw(&self) -> u8 {
620        self.sensitivity
621    }
622    /// Raw shift register (save-state).
623    #[must_use]
624    pub const fn shift_raw(&self) -> u32 {
625        self.shift
626    }
627    /// Raw read counter (save-state).
628    #[must_use]
629    pub const fn read_count_raw(&self) -> u8 {
630        self.read_count
631    }
632    /// Raw strobe state (save-state).
633    #[must_use]
634    pub const fn strobe_raw(&self) -> bool {
635        self.strobe
636    }
637}
638
639/// Number of physical keys on the Famicom Family BASIC keyboard matrix
640/// (`9 rows x 8 columns / 2` halves; 72 keys, with a handful of unused matrix
641/// positions reported as `1` / not-pressed).
642pub const FAMILY_KEYBOARD_KEYS: usize = 72;
643
644/// Number of selectable rows in the Family BASIC keyboard matrix.
645const FAMILY_KEYBOARD_ROWS: usize = 9;
646
647/// The Famicom **Family BASIC keyboard** overlay state.
648///
649/// Per the `NESdev` "Family BASIC Keyboard" page, the keyboard is a `9 x 8`
650/// switch matrix (with the data-recorder lines unused here) read through the
651/// expansion port but software-visible on `$4017`. The protocol:
652///
653/// - **`$4016` write** — bit 0 (the "column" select; 0 selects the low 4 keys
654///   of the current row, 1 selects the high 4) and bit 1 (a clock; a 0->1
655///   transition advances to the next row). Bit 2 enables the keyboard matrix;
656///   when bit 2 is 0 the matrix is disabled and `$4017` reads `1`s. Writing
657///   bit 1 = 0 while bit 2 = 1 **resets** the row counter to 0.
658/// - **`$4017` read** — bits 4..1 carry the four key switches of the currently
659///   selected (row, column-half), **active-low** (0 = pressed). There are 9
660///   rows x 2 halves = 18 selectable groups of 4 keys = 72 key positions.
661///
662/// We model the live pressed state as a 72-bit key bitmap (`[u8; 9]`, one byte
663/// per row: low nibble = column-half 0, high nibble = column-half 1) and the
664/// row counter + column select per the write protocol. Determinism holds: it is
665/// a pure function of the writes + the live key bitmap.
666#[derive(Clone, Copy, Debug)]
667pub struct FamilyKeyboardState {
668    /// Per-row key bitmap. `keys[row]` bits 0..=3 = column-half 0 keys, bits
669    /// 4..=7 = column-half 1 keys. A set bit = that key is held.
670    pub(crate) keys: [u8; FAMILY_KEYBOARD_ROWS],
671    /// Current matrix row (0..=8); wraps/saturates at the last row.
672    pub(crate) row: u8,
673    /// Column-half select (bit 0 of the last `$4016` write): 0 = low nibble,
674    /// 1 = high nibble.
675    pub(crate) column: bool,
676    /// Whether the matrix is enabled (bit 2 of the last `$4016` write). When
677    /// disabled, `$4017` reads return all-`1` (no keys).
678    pub(crate) enabled: bool,
679    /// Last clock level (bit 1 of `$4016`); a 0->1 edge advances the row.
680    pub(crate) clock: bool,
681}
682
683impl Default for FamilyKeyboardState {
684    fn default() -> Self {
685        Self::new()
686    }
687}
688
689impl FamilyKeyboardState {
690    /// New keyboard with no keys held, matrix reset + disabled.
691    #[must_use]
692    pub const fn new() -> Self {
693        Self {
694            keys: [0; FAMILY_KEYBOARD_ROWS],
695            row: 0,
696            column: false,
697            enabled: false,
698            clock: false,
699        }
700    }
701
702    /// Set the full pressed-key bitmap (one byte per matrix row; low nibble =
703    /// column-half 0, high nibble = column-half 1). The frontend builds this
704    /// from host keys via its key map.
705    pub const fn set_keys(&mut self, keys: [u8; FAMILY_KEYBOARD_ROWS]) {
706        self.keys = keys;
707    }
708
709    /// Set one key by linear index (0..72) — `index = row * 8 + bit`, matching
710    /// the matrix layout (`bit` 0..=3 = column-half 0, 4..=7 = column-half 1).
711    /// Out-of-range indices are ignored.
712    pub const fn set_key(&mut self, index: usize, pressed: bool) {
713        if index >= FAMILY_KEYBOARD_KEYS {
714            return;
715        }
716        let row = index / 8;
717        #[allow(clippy::cast_possible_truncation)] // index % 8 is always 0..=7
718        let bit = (index % 8) as u8;
719        if pressed {
720            self.keys[row] |= 1 << bit;
721        } else {
722            self.keys[row] &= !(1 << bit);
723        }
724    }
725
726    /// Handle a `$4016` write: latch column select (bit 0), advance the row on a
727    /// clock (bit 1) rising edge, set the matrix-enable (bit 2). A clock low
728    /// while enabled resets the row counter to 0.
729    pub const fn write_strobe(&mut self, value: u8) {
730        let column = value & 0x01 != 0;
731        let clock = value & 0x02 != 0;
732        let enabled = value & 0x04 != 0;
733        if enabled {
734            if clock && !self.clock {
735                // Rising clock edge: advance to the next row (saturate at last).
736                if (self.row as usize) < FAMILY_KEYBOARD_ROWS - 1 {
737                    self.row += 1;
738                }
739            } else if !clock {
740                // Clock low (while enabled): reset to the first row.
741                self.row = 0;
742            }
743        }
744        self.column = column;
745        self.enabled = enabled;
746        self.clock = clock;
747    }
748
749    /// Read the device byte for a `$4017` access. The four selected key switches
750    /// are returned on bits 4..1, **active-low** (0 = pressed). When the matrix
751    /// is disabled, all four bits read `1` (no keys). The caller ORs in the
752    /// open-bus upper bits.
753    #[must_use]
754    pub const fn read(&self) -> u8 {
755        if !self.enabled {
756            // Disabled matrix: key switches all read high (not pressed).
757            return 0b0001_1110;
758        }
759        let row = self.row as usize;
760        let byte = self.keys[row];
761        let nibble = if self.column {
762            (byte >> 4) & 0x0F
763        } else {
764            byte & 0x0F
765        };
766        // Active-low: pressed key (1 in our bitmap) reads 0 on the wire.
767        let wire = (!nibble) & 0x0F;
768        wire << 1
769    }
770
771    /// Side-effect-free sample of the device byte (debugger peek) — identical to
772    /// [`Self::read`] (the keyboard read has no side effects).
773    #[must_use]
774    pub const fn peek(&self) -> u8 {
775        self.read()
776    }
777
778    /// Reconstruct from save-state parts.
779    #[must_use]
780    pub const fn from_parts(
781        keys: [u8; FAMILY_KEYBOARD_ROWS],
782        row: u8,
783        column: bool,
784        enabled: bool,
785        clock: bool,
786    ) -> Self {
787        // Clamp the restored row to the matrix bound: a corrupt/malicious
788        // save-state must not be able to drive `read()`'s `self.keys[row]`
789        // out of bounds. The live `write_strobe` path already saturates the
790        // row at `FAMILY_KEYBOARD_ROWS - 1`; mirror that on restore.
791        let row = if (row as usize) >= FAMILY_KEYBOARD_ROWS {
792            #[allow(clippy::cast_possible_truncation)] // ROWS is small (9)
793            {
794                (FAMILY_KEYBOARD_ROWS - 1) as u8
795            }
796        } else {
797            row
798        };
799        Self {
800            keys,
801            row,
802            column,
803            enabled,
804            clock,
805        }
806    }
807
808    /// Raw per-row key bitmap (save-state).
809    #[must_use]
810    pub const fn keys_raw(&self) -> [u8; FAMILY_KEYBOARD_ROWS] {
811        self.keys
812    }
813    /// Raw row counter (save-state).
814    #[must_use]
815    pub const fn row_raw(&self) -> u8 {
816        self.row
817    }
818    /// Raw column select (save-state).
819    #[must_use]
820    pub const fn column_raw(&self) -> bool {
821        self.column
822    }
823    /// Raw matrix-enable (save-state).
824    #[must_use]
825    pub const fn enabled_raw(&self) -> bool {
826        self.enabled
827    }
828    /// Raw clock level (save-state).
829    #[must_use]
830    pub const fn clock_raw(&self) -> bool {
831        self.clock
832    }
833}
834
835/// The **Konami Hyper Shot** overlay state (v1.3.0 Workstream F1).
836///
837/// A simple 4-button expansion controller (two players, each with a Run and a
838/// Jump button) used by _Hyper Olympic_ / _Hyper Sports_. Per the `NESdev`
839/// "Konami Hyper Shot" page it is read in **parallel** on `$4017` (no shift
840/// register), with `$4016` writes selecting which player's buttons are
841/// enabled:
842///
843/// ```text
844/// $4016 write:               $4017 read:
845/// 7  bit  0                  7  bit  0
846/// ---- ----                  ---- ----
847/// xxxx xEFx                  xxxD CBAx
848///       ||                      | |||
849///       |+- 0 = enable P1          | ||+-- P1 Run
850///       +-- 0 = enable P2          | |+--- P1 Jump
851///                                  | +---- P2 Run
852///                                  +------ P2 Jump
853/// ```
854///
855/// The Jump/Run bits for a player read `0` while that player's enable bit
856/// ($4016 bit 1 for P1, bit 2 for P2) is **set** (i.e. disabled). Determinism
857/// holds: the read is a pure function of the live button mask + the last write.
858#[derive(Clone, Copy, Debug, Default)]
859pub struct KonamiHyperShotState {
860    /// Live button mask: bit 0 = P1 Run, bit 1 = P1 Jump, bit 2 = P2 Run,
861    /// bit 3 = P2 Jump.
862    pub(crate) buttons: u8,
863    /// `true` if P1's buttons are enabled (`$4016` bit 1 == 0).
864    pub(crate) p1_enabled: bool,
865    /// `true` if P2's buttons are enabled (`$4016` bit 2 == 0).
866    pub(crate) p2_enabled: bool,
867}
868
869impl KonamiHyperShotState {
870    /// New controller with no buttons held and both players enabled (the
871    /// power-on `$4016` write has not happened yet; enable is active-low, so the
872    /// quiescent state matches a write of 0).
873    #[must_use]
874    pub const fn new() -> Self {
875        Self {
876            buttons: 0,
877            p1_enabled: true,
878            p2_enabled: true,
879        }
880    }
881
882    /// Set the live 4-button mask (bit 0 = P1 Run, 1 = P1 Jump, 2 = P2 Run,
883    /// 3 = P2 Jump). Bits above 3 are ignored.
884    pub const fn set(&mut self, buttons: u8) {
885        self.buttons = buttons & 0x0F;
886    }
887
888    /// Handle a `$4016` write: bit 1 = 0 enables P1, bit 2 = 0 enables P2
889    /// (active-low).
890    pub const fn write_strobe(&mut self, value: u8) {
891        self.p1_enabled = value & 0x02 == 0;
892        self.p2_enabled = value & 0x04 == 0;
893    }
894
895    /// Read the device byte for a `$4017` access. Bit 1 = P1 Run, bit 2 = P1
896    /// Jump, bit 3 = P2 Run, bit 4 = P2 Jump; a player's bits read `0` while
897    /// disabled. The caller ORs in the open-bus upper bits.
898    #[must_use]
899    pub const fn read(&self) -> u8 {
900        let p1_run = (self.buttons & 0x01 != 0) && self.p1_enabled;
901        let p1_jump = (self.buttons & 0x02 != 0) && self.p1_enabled;
902        let p2_run = (self.buttons & 0x04 != 0) && self.p2_enabled;
903        let p2_jump = (self.buttons & 0x08 != 0) && self.p2_enabled;
904        ((p1_run as u8) << 1)
905            | ((p1_jump as u8) << 2)
906            | ((p2_run as u8) << 3)
907            | ((p2_jump as u8) << 4)
908    }
909
910    /// Side-effect-free sample (debugger peek) — identical to [`Self::read`].
911    #[must_use]
912    pub const fn peek(&self) -> u8 {
913        self.read()
914    }
915
916    /// Reconstruct from save-state parts.
917    #[must_use]
918    pub const fn from_parts(buttons: u8, p1_enabled: bool, p2_enabled: bool) -> Self {
919        Self {
920            buttons: buttons & 0x0F,
921            p1_enabled,
922            p2_enabled,
923        }
924    }
925
926    /// Raw button mask (save-state).
927    #[must_use]
928    pub const fn buttons_raw(&self) -> u8 {
929        self.buttons
930    }
931    /// Raw P1-enable (save-state).
932    #[must_use]
933    pub const fn p1_enabled_raw(&self) -> bool {
934        self.p1_enabled
935    }
936    /// Raw P2-enable (save-state).
937    #[must_use]
938    pub const fn p2_enabled_raw(&self) -> bool {
939        self.p2_enabled
940    }
941}
942
943/// The **Bandai Hyper Shot** (Exciting Boxing punching bag) overlay state
944/// (v1.3.0 Workstream F1).
945///
946/// The punching bag has 8 sensors read on `$4017`, multiplexed by `$4016`
947/// bit 1 (the "A" select) into two groups of four returned on bits 4..1. Per
948/// the `NESdev` "Exciting Boxing Punching Bag" page:
949///
950/// ```text
951/// $4016 write:               $4017 read:
952/// 7  bit  0                  7  bit  0
953/// ---- ----                  ---- ----
954/// xxxx xxAx                  xxxE DCBx
955///        |                      | |||
956///        +- select group        | ||+-- Left Hook (A=0) / Left Jab  (A=1)
957///                               | |+--- Move Right (A=0) / Body      (A=1)
958///                               | +---- Move Left (A=0) / Right Jab  (A=1)
959///                               +------ Right Hook (A=0) / Straight   (A=1)
960/// ```
961///
962/// We model the 8 sensors as a live bitmask and the `A` select from the last
963/// `$4016` write; the read is a pure function of both (deterministic).
964#[derive(Clone, Copy, Debug, Default)]
965pub struct BandaiHyperShotState {
966    /// Live sensor mask. Group A=0 (bits 0..=3): Left Hook, Move Right, Move
967    /// Left, Right Hook. Group A=1 (bits 4..=7): Left Jab, Body, Right Jab,
968    /// Straight. A set bit = that sensor is active.
969    pub(crate) sensors: u8,
970    /// The `A` select latched from the last `$4016` write (bit 1). `false`
971    /// selects the A=0 group (bits 0..=3), `true` the A=1 group (bits 4..=7).
972    pub(crate) select: bool,
973}
974
975impl BandaiHyperShotState {
976    /// New punching bag with no sensor active, group A=0 selected.
977    #[must_use]
978    pub const fn new() -> Self {
979        Self {
980            sensors: 0,
981            select: false,
982        }
983    }
984
985    /// Set the live 8-sensor mask. Bits 0..=3 are the A=0 group (Left Hook,
986    /// Move Right, Move Left, Right Hook); bits 4..=7 are the A=1 group (Left
987    /// Jab, Body, Right Jab, Straight).
988    pub const fn set(&mut self, sensors: u8) {
989        self.sensors = sensors;
990    }
991
992    /// Handle a `$4016` write: bit 1 (`A`) selects which sensor group is
993    /// returned on the next reads.
994    pub const fn write_strobe(&mut self, value: u8) {
995        self.select = value & 0x02 != 0;
996    }
997
998    /// Read the device byte for a `$4017` access. The selected group's four
999    /// sensors appear on bits 4..1. The caller ORs in the open-bus upper bits.
1000    #[must_use]
1001    pub const fn read(&self) -> u8 {
1002        let nibble = if self.select {
1003            (self.sensors >> 4) & 0x0F
1004        } else {
1005            self.sensors & 0x0F
1006        };
1007        nibble << 1
1008    }
1009
1010    /// Side-effect-free sample (debugger peek) — identical to [`Self::read`].
1011    #[must_use]
1012    pub const fn peek(&self) -> u8 {
1013        self.read()
1014    }
1015
1016    /// Reconstruct from save-state parts.
1017    #[must_use]
1018    pub const fn from_parts(sensors: u8, select: bool) -> Self {
1019        Self { sensors, select }
1020    }
1021
1022    /// Raw sensor mask (save-state).
1023    #[must_use]
1024    pub const fn sensors_raw(&self) -> u8 {
1025        self.sensors
1026    }
1027    /// Raw `A`-select (save-state).
1028    #[must_use]
1029    pub const fn select_raw(&self) -> bool {
1030        self.select
1031    }
1032}
1033
1034/// An optional non-standard device overlaid on a controller port. When set,
1035/// the bus's `$4016`/`$4017` read path returns this device's byte instead of
1036/// the standard controller / Four Score serial byte.
1037#[derive(Clone, Copy, Debug)]
1038pub enum InputDevice {
1039    /// NES Zapper light gun.
1040    Zapper(ZapperState),
1041    /// Arkanoid "Vaus" paddle.
1042    Vaus(VausState),
1043    /// NES Power Pad / Family Fun Fitness mat (12 buttons).
1044    PowerPad(PowerPadState),
1045    /// SNES-style serial mouse (Hyperkin / Nintendo), D0 serial-out.
1046    SnesMouse(SnesMouseState),
1047    /// Famicom Family BASIC keyboard (72-key matrix on `$4017`).
1048    FamilyKeyboard(FamilyKeyboardState),
1049    /// Bandai **Family Trainer** mat (v1.3.0 Workstream F1). Layout-equivalent
1050    /// to the [`PowerPad`](Self::PowerPad): the Famicom mat reuses the exact
1051    /// 12-button parallel-in/serial-out scan (it differs only in the expansion-
1052    /// port wiring vs the NES controller-port Power Pad), so the same
1053    /// [`PowerPadState`] drives it.
1054    FamilyTrainer(PowerPadState),
1055    /// **Subor keyboard** (v1.3.0 Workstream F1). A Family BASIC keyboard
1056    /// work-alike (the Subor clone matrix), reusing the same
1057    /// [`FamilyKeyboardState`] `9 x 8` matrix scan.
1058    SuborKeyboard(FamilyKeyboardState),
1059    /// **Konami Hyper Shot** (v1.3.0 Workstream F1): a 4-button (2-player
1060    /// Run/Jump) parallel-read expansion controller.
1061    KonamiHyperShot(KonamiHyperShotState),
1062    /// **Bandai Hyper Shot** / Exciting Boxing punching bag (v1.3.0 Workstream
1063    /// F1): an 8-sensor expansion controller multiplexed into two groups.
1064    BandaiHyperShot(BandaiHyperShotState),
1065}
1066
1067impl InputDevice {
1068    /// Forward a `$4016` strobe write to the device (only the Vaus latches on
1069    /// it; the Zapper ignores it).
1070    pub const fn write_strobe(&mut self, value: u8) {
1071        match self {
1072            Self::Vaus(v) => v.write_strobe(value),
1073            Self::PowerPad(p) | Self::FamilyTrainer(p) => p.write_strobe(value),
1074            Self::SnesMouse(m) => m.write_strobe(value),
1075            Self::FamilyKeyboard(k) | Self::SuborKeyboard(k) => k.write_strobe(value),
1076            Self::KonamiHyperShot(h) => h.write_strobe(value),
1077            Self::BandaiHyperShot(b) => b.write_strobe(value),
1078            Self::Zapper(_) => {}
1079        }
1080    }
1081
1082    /// Read the device byte (already bit-positioned), advancing any internal
1083    /// shift register.
1084    pub const fn read(&mut self) -> u8 {
1085        match self {
1086            Self::Vaus(v) => v.read(),
1087            Self::Zapper(z) => z.read(),
1088            Self::PowerPad(p) | Self::FamilyTrainer(p) => p.read(),
1089            Self::SnesMouse(m) => m.read(),
1090            Self::FamilyKeyboard(k) | Self::SuborKeyboard(k) => k.read(),
1091            Self::KonamiHyperShot(h) => h.read(),
1092            Self::BandaiHyperShot(b) => b.read(),
1093        }
1094    }
1095
1096    /// Side-effect-free sample of the device byte (debugger peek).
1097    #[must_use]
1098    pub const fn peek(&self) -> u8 {
1099        match self {
1100            Self::Vaus(v) => v.peek(),
1101            Self::Zapper(z) => z.read(),
1102            Self::PowerPad(p) | Self::FamilyTrainer(p) => p.peek(),
1103            Self::SnesMouse(m) => m.peek(),
1104            Self::FamilyKeyboard(k) | Self::SuborKeyboard(k) => k.peek(),
1105            Self::KonamiHyperShot(h) => h.peek(),
1106            Self::BandaiHyperShot(b) => b.peek(),
1107        }
1108    }
1109}
1110
1111#[cfg(test)]
1112mod tests {
1113    use super::*;
1114
1115    #[test]
1116    fn vaus_fire_button_on_bit3_independent_of_strobe() {
1117        let mut v = VausState::new();
1118        v.set(0x80, true);
1119        // No strobe yet; fire is returned directly regardless.
1120        assert_eq!(v.read() & (1 << 3), 1 << 3, "fire = bit 3 set");
1121        v.set(0x80, false);
1122        assert_eq!(v.read() & (1 << 3), 0, "fire released = bit 3 clear");
1123    }
1124
1125    #[test]
1126    fn vaus_knob_shifts_out_msb_first_inverted_on_bit4() {
1127        let mut v = VausState::new();
1128        // position 0b1010_0000: MSb-first raw bits = 1,0,1,0,0,0,0,0
1129        v.set(0b1010_0000, false);
1130        v.write_strobe(1);
1131        v.write_strobe(0);
1132        // Wire is inverted, so expected wire bits (bit 4) = 0,1,0,1,1,1,1,1.
1133        let expect_raw = [1u8, 0, 1, 0, 0, 0, 0, 0];
1134        for (i, raw) in expect_raw.iter().enumerate() {
1135            let byte = v.read();
1136            let wire_bit = (byte >> 4) & 1;
1137            assert_eq!(wire_bit, raw ^ 1, "read {i}: wire bit must be inverted raw");
1138        }
1139    }
1140
1141    #[test]
1142    fn vaus_post_empty_repeats_serial_in_bit() {
1143        let mut v = VausState::new();
1144        // LSb (serial-in) = 1 -> after the 8 real bits, reads repeat inverted 1 = 0.
1145        v.set(0b0000_0001, false);
1146        v.write_strobe(1);
1147        v.write_strobe(0);
1148        for _ in 0..8 {
1149            let _ = v.read();
1150        }
1151        // Now the register is all serial-in (1); wire bit = inverted = 0.
1152        for _ in 0..4 {
1153            assert_eq!((v.read() >> 4) & 1, 0);
1154        }
1155    }
1156
1157    #[test]
1158    fn zapper_light_detected_for_bright_pixel() {
1159        let mut z = ZapperState::new();
1160        z.set(10, 10, false);
1161        let mut fb = alloc::vec![0u8; 256 * 240 * 4];
1162        // Bright white pixel at (10, 10).
1163        let idx = (10 * 256 + 10) * 4;
1164        fb[idx] = 0xFF;
1165        fb[idx + 1] = 0xFF;
1166        fb[idx + 2] = 0xFF;
1167        z.sample_light(&fb);
1168        // Light detected -> bit 3 = 0.
1169        assert_eq!(
1170            z.read() & (1 << 3),
1171            0,
1172            "bright pixel -> light detected (bit3=0)"
1173        );
1174    }
1175
1176    #[test]
1177    fn zapper_no_light_for_dark_pixel() {
1178        let mut z = ZapperState::new();
1179        z.set(10, 10, false);
1180        let fb = alloc::vec![0u8; 256 * 240 * 4]; // all black
1181        z.sample_light(&fb);
1182        assert_eq!(
1183            z.read() & (1 << 3),
1184            1 << 3,
1185            "dark pixel -> no light (bit3=1)"
1186        );
1187    }
1188
1189    #[test]
1190    fn zapper_off_screen_never_sees_light() {
1191        let mut z = ZapperState::new();
1192        z.set(1000, 1000, false);
1193        let mut fb = alloc::vec![0u8; 256 * 240 * 4];
1194        fb.fill(0xFF);
1195        z.sample_light(&fb);
1196        assert_eq!(z.read() & (1 << 3), 1 << 3, "off-screen aim -> no light");
1197    }
1198
1199    #[test]
1200    fn zapper_trigger_on_bit4() {
1201        let mut z = ZapperState::new();
1202        z.set(10, 10, true);
1203        assert_eq!(z.read() & (1 << 4), 1 << 4, "trigger pulled -> bit4 set");
1204        z.set(10, 10, false);
1205        assert_eq!(z.read() & (1 << 4), 0, "trigger released -> bit4 clear");
1206    }
1207
1208    /// Read 8 device bytes after a strobe, returning the bit-3 (L) and bit-4 (H)
1209    /// streams as bool arrays.
1210    fn powerpad_read8(p: &mut PowerPadState) -> ([bool; 8], [bool; 8]) {
1211        p.write_strobe(1);
1212        p.write_strobe(0);
1213        let mut l = [false; 8];
1214        let mut h = [false; 8];
1215        for i in 0..8 {
1216            let b = p.read();
1217            l[i] = b & (1 << 3) != 0;
1218            h[i] = b & (1 << 4) != 0;
1219        }
1220        (l, h)
1221    }
1222
1223    #[test]
1224    fn powerpad_no_buttons_reads_clear_then_h_ones() {
1225        // No buttons: L is all 0; H reads 0 for the first 4 (buttons 4,3,12,8),
1226        // then 1 for the trailing "read as H=1" bits.
1227        let mut p = PowerPadState::new();
1228        let (l, h) = powerpad_read8(&mut p);
1229        assert_eq!(l, [false; 8], "no L bits with nothing pressed");
1230        assert_eq!(h, [false, false, false, false, true, true, true, true]);
1231    }
1232
1233    #[test]
1234    fn powerpad_button_maps_to_expected_serial_position() {
1235        // Mat button "1" (index 0) is bit 1 of L -> appears on the 2nd read.
1236        let mut p = PowerPadState::new();
1237        p.set(1 << 0);
1238        let (l, _h) = powerpad_read8(&mut p);
1239        assert_eq!(l, [false, true, false, false, false, false, false, false]);
1240
1241        // Mat button "2" (index 1) is bit 0 of L -> appears on the 1st read.
1242        let mut p = PowerPadState::new();
1243        p.set(1 << 1);
1244        let (l, _h) = powerpad_read8(&mut p);
1245        assert_eq!(l, [true, false, false, false, false, false, false, false]);
1246
1247        // Mat button "4" (index 3) is bit 0 of H -> 1st read on bit 4.
1248        let mut p = PowerPadState::new();
1249        p.set(1 << 3);
1250        let (_l, h) = powerpad_read8(&mut p);
1251        assert_eq!(h, [true, false, false, false, true, true, true, true]);
1252    }
1253
1254    #[test]
1255    fn powerpad_strobe_high_reloads_each_read() {
1256        // While strobe is high, every read re-latches, so the first serial bit
1257        // is returned repeatedly (standard controller strobe semantics).
1258        let mut p = PowerPadState::new();
1259        p.set(1 << 1); // button "2" -> L bit 0 (1st-read position).
1260        p.write_strobe(1); // strobe held high
1261        for _ in 0..5 {
1262            assert_eq!(p.read() & (1 << 3), 1 << 3, "strobe-high repeats bit 0");
1263        }
1264    }
1265
1266    #[test]
1267    fn powerpad_save_state_round_trip() {
1268        let mut p = PowerPadState::new();
1269        p.set(0b1010_0101_0011);
1270        p.write_strobe(1);
1271        p.write_strobe(0);
1272        let _ = p.read(); // advance the registers
1273        let restored = PowerPadState::from_parts(
1274            p.buttons_raw(),
1275            p.shift_l_raw(),
1276            p.shift_h_raw(),
1277            p.strobe_raw(),
1278        );
1279        assert_eq!(restored.peek(), p.peek());
1280        assert_eq!(restored.buttons_raw(), 0b1010_0101_0011);
1281    }
1282
1283    #[test]
1284    fn powerpad_masks_to_12_bits() {
1285        // Bits above 11 are ignored (the mat has 12 buttons).
1286        let mut p = PowerPadState::new();
1287        p.set(0xFFFF);
1288        assert_eq!(p.buttons_raw(), 0x0FFF);
1289    }
1290
1291    #[test]
1292    fn input_device_enum_dispatch() {
1293        let mut d = InputDevice::Vaus(VausState::new());
1294        d.write_strobe(1);
1295        d.write_strobe(0);
1296        let _ = d.read();
1297        let mut z = InputDevice::Zapper(ZapperState::new());
1298        // Strobe is a no-op for the Zapper.
1299        z.write_strobe(1);
1300        assert_eq!(z.read() & (1 << 3), 1 << 3, "zapper: no light by default");
1301    }
1302
1303    /// Shift out `n` D0 bits (MSb-first), returning them packed into a u64 in
1304    /// read order (first bit = most significant of the returned `n`-bit value).
1305    fn mouse_read_bits(m: &mut SnesMouseState, n: usize) -> u64 {
1306        m.write_strobe(1);
1307        m.write_strobe(0);
1308        let mut acc = 0u64;
1309        for _ in 0..n {
1310            acc = (acc << 1) | u64::from(m.read() & 1);
1311        }
1312        acc
1313    }
1314
1315    #[test]
1316    fn snes_mouse_signature_nibble_is_0b0001() {
1317        let mut m = SnesMouseState::new();
1318        // First 4 bits are the device-id signature nibble 0b0001.
1319        let bits = mouse_read_bits(&mut m, 4);
1320        assert_eq!(bits, 0b0001, "signature nibble must be 0b0001");
1321    }
1322
1323    #[test]
1324    fn snes_mouse_full_report_encodes_buttons_and_movement() {
1325        let mut m = SnesMouseState::new();
1326        // dx = +5, dy = -3, left pressed, sensitivity = 2 (high).
1327        m.set(5, -3, true, false, 2);
1328        let word = mouse_read_bits(&mut m, 32);
1329        // Reconstruct the full 32-bit word and check each field.
1330        assert_eq!((word >> 28) & 0x0F, 0b0001, "signature");
1331        assert_eq!((word >> 24) & 0b11, 2, "sensitivity");
1332        assert_eq!((word >> 23) & 1, 1, "left button");
1333        assert_eq!((word >> 22) & 1, 0, "right button");
1334        // Y field (bits 15..8): sign=1 (negative), magnitude 3.
1335        let y = (word >> 8) & 0xFF;
1336        assert_eq!((y >> 7) & 1, 1, "Y sign negative");
1337        assert_eq!(y & 0x7F, 3, "Y magnitude");
1338        // X field (bits 7..0): sign=0 (positive), magnitude 5.
1339        let x = word & 0xFF;
1340        assert_eq!((x >> 7) & 1, 0, "X sign positive");
1341        assert_eq!(x & 0x7F, 5, "X magnitude");
1342    }
1343
1344    #[test]
1345    fn snes_mouse_idles_high_after_32_bits() {
1346        let mut m = SnesMouseState::new();
1347        m.write_strobe(1);
1348        m.write_strobe(0);
1349        for _ in 0..32 {
1350            let _ = m.read();
1351        }
1352        for _ in 0..4 {
1353            assert_eq!(m.read() & 1, 1, "serial line idles high after the report");
1354        }
1355    }
1356
1357    #[test]
1358    fn snes_mouse_clamps_movement_to_127() {
1359        let mut m = SnesMouseState::new();
1360        m.set(1000, -1000, false, false, 0);
1361        let word = mouse_read_bits(&mut m, 32);
1362        assert_eq!(word & 0x7F, 127, "X magnitude clamps to 127");
1363        assert_eq!((word >> 8) & 0x7F, 127, "Y magnitude clamps to 127");
1364    }
1365
1366    #[test]
1367    fn snes_mouse_enc_axis_handles_i16_extremes_without_panic() {
1368        // Regression: `enc_axis(i16::MIN)` must not panic on the `-v` overflow
1369        // (`-(-32768)` is unrepresentable as i16). Both extremes encode sanely:
1370        // sign bit set/clear and magnitude clamped to the 7-bit max of 127.
1371        let mut m = SnesMouseState::new();
1372        m.set(i16::MIN, i16::MAX, false, false, 0);
1373        let word = mouse_read_bits(&mut m, 32);
1374        // X = i16::MIN: negative -> sign bit (bit 7) set, magnitude clamped 127.
1375        assert_eq!(word & 0x80, 0x80, "i16::MIN encodes as negative");
1376        assert_eq!(word & 0x7F, 127, "i16::MIN magnitude clamps to 127");
1377        // Y = i16::MAX: positive -> sign bit clear, magnitude clamped 127.
1378        assert_eq!((word >> 8) & 0x80, 0, "i16::MAX encodes as positive");
1379        assert_eq!((word >> 8) & 0x7F, 127, "i16::MAX magnitude clamps to 127");
1380    }
1381
1382    #[test]
1383    fn snes_mouse_strobe_high_repeats_signature_bit() {
1384        let mut m = SnesMouseState::new();
1385        m.write_strobe(1); // held high
1386        for _ in 0..5 {
1387            // Signature MSb (bit 31 of 0b0001 << 28) is 0; repeats while strobed.
1388            assert_eq!(m.read() & 1, 0, "strobe-high repeats bit 31");
1389        }
1390    }
1391
1392    #[test]
1393    fn family_keyboard_disabled_reads_all_high() {
1394        let k = FamilyKeyboardState::new();
1395        // Not enabled (bit 2 unset): key switches all read high (bits 4..1 set).
1396        assert_eq!(
1397            k.read(),
1398            0b0001_1110,
1399            "disabled matrix -> no keys (bits 4..1=1)"
1400        );
1401    }
1402
1403    #[test]
1404    fn family_keyboard_pressed_key_reads_active_low() {
1405        let mut k = FamilyKeyboardState::new();
1406        // Press key at row 0, column-half 0, switch 0 (linear index 0).
1407        k.set_key(0, true);
1408        // Enable matrix (bit2), select column-half 0 (bit0=0), clock low resets row to 0.
1409        k.write_strobe(0b0000_0100);
1410        let r = k.read();
1411        // The pressed switch (bit 0 of the nibble) appears active-low on bit 1.
1412        assert_eq!(r & (1 << 1), 0, "pressed key reads 0 (active-low) on bit 1");
1413        // The other three switches are not pressed -> read 1.
1414        assert_eq!(r & (1 << 2), 1 << 2);
1415        assert_eq!(r & (1 << 3), 1 << 3);
1416        assert_eq!(r & (1 << 4), 1 << 4);
1417    }
1418
1419    #[test]
1420    fn family_keyboard_column_select_picks_high_nibble() {
1421        let mut k = FamilyKeyboardState::new();
1422        // Key at row 0, column-half 1, switch 0 = linear index 4 (row*8 + 4).
1423        k.set_key(4, true);
1424        // Enable + select column-half 1 (bit0=1), clock low (resets row to 0).
1425        k.write_strobe(0b0000_0101);
1426        let r = k.read();
1427        assert_eq!(
1428            r & (1 << 1),
1429            0,
1430            "column-half-1 key reads active-low on bit 1"
1431        );
1432        // Selecting column-half 0 instead shows nothing pressed there.
1433        k.write_strobe(0b0000_0100);
1434        assert_eq!(k.read() & (1 << 1), 1 << 1, "column-half 0 has no key here");
1435    }
1436
1437    #[test]
1438    fn family_keyboard_clock_edge_advances_row() {
1439        let mut k = FamilyKeyboardState::new();
1440        // Press a key on row 1, column-half 0, switch 0 = linear index 8.
1441        k.set_key(8, true);
1442        // Enable + clock low -> row 0.
1443        k.write_strobe(0b0000_0100);
1444        assert_eq!(k.read() & (1 << 1), 1 << 1, "row 0 has no key");
1445        // Rising clock edge (bit1 0->1) advances to row 1.
1446        k.write_strobe(0b0000_0110);
1447        assert_eq!(
1448            k.read() & (1 << 1),
1449            0,
1450            "row 1 key now selected (active-low)"
1451        );
1452    }
1453
1454    #[test]
1455    fn family_keyboard_save_state_round_trip() {
1456        let mut k = FamilyKeyboardState::new();
1457        k.set_key(8, true);
1458        k.set_key(40, true);
1459        k.write_strobe(0b0000_0110);
1460        let restored = FamilyKeyboardState::from_parts(
1461            k.keys_raw(),
1462            k.row_raw(),
1463            k.column_raw(),
1464            k.enabled_raw(),
1465            k.clock_raw(),
1466        );
1467        assert_eq!(restored.read(), k.read());
1468        assert_eq!(restored.keys_raw(), k.keys_raw());
1469    }
1470
1471    #[test]
1472    fn family_keyboard_from_parts_clamps_out_of_range_row() {
1473        // A corrupt/malicious save-state must not be able to drive a row value
1474        // that would index `self.keys[row]` out of bounds in `read()`.
1475        let keys = [0u8; FAMILY_KEYBOARD_ROWS];
1476        let restored = FamilyKeyboardState::from_parts(keys, 250, false, true, false);
1477        assert!(
1478            (restored.row_raw() as usize) < FAMILY_KEYBOARD_ROWS,
1479            "out-of-range row saturated to the matrix bound"
1480        );
1481        // Must not panic: enabled matrix indexes keys[row] in read().
1482        let _ = restored.read();
1483    }
1484
1485    #[test]
1486    fn family_keyboard_set_key_out_of_range_is_noop() {
1487        let mut k = FamilyKeyboardState::new();
1488        k.set_key(FAMILY_KEYBOARD_KEYS, true); // index == 72, out of range
1489        k.set_key(1000, true);
1490        assert_eq!(k.keys_raw(), [0; FAMILY_KEYBOARD_ROWS]);
1491    }
1492
1493    // --- v1.3.0 Workstream F1 — niche peripheral aliases + Hyper Shots ---
1494
1495    #[test]
1496    fn family_trainer_reuses_power_pad_scan() {
1497        // The Family Trainer is layout-equivalent to the Power Pad: an identical
1498        // PowerPadState must produce an identical serial readout through both
1499        // InputDevice variants.
1500        let mut pad = InputDevice::PowerPad(PowerPadState::new());
1501        let mut mat = InputDevice::FamilyTrainer(PowerPadState::new());
1502        if let (InputDevice::PowerPad(p), InputDevice::FamilyTrainer(m)) = (&mut pad, &mut mat) {
1503            p.set(0b1010_0101_0011);
1504            m.set(0b1010_0101_0011);
1505        }
1506        pad.write_strobe(1);
1507        pad.write_strobe(0);
1508        mat.write_strobe(1);
1509        mat.write_strobe(0);
1510        for i in 0..8 {
1511            assert_eq!(pad.read(), mat.read(), "read {i}: trainer == power pad");
1512        }
1513    }
1514
1515    #[test]
1516    fn subor_keyboard_reuses_family_keyboard_scan() {
1517        // The Subor keyboard reuses the Family BASIC keyboard matrix scan; the
1518        // same key state must read identically through both variants.
1519        let mut fam = FamilyKeyboardState::new();
1520        let mut sub = FamilyKeyboardState::new();
1521        fam.set_key(8, true);
1522        sub.set_key(8, true);
1523        let mut famd = InputDevice::FamilyKeyboard(fam);
1524        let mut subd = InputDevice::SuborKeyboard(sub);
1525        // Enable + clock low (row 0), then rising edge -> row 1.
1526        for v in [0b0000_0100u8, 0b0000_0110] {
1527            famd.write_strobe(v);
1528            subd.write_strobe(v);
1529        }
1530        assert_eq!(famd.read(), subd.read(), "subor == family keyboard read");
1531        assert_eq!(famd.peek(), subd.peek());
1532    }
1533
1534    #[test]
1535    fn konami_hyper_shot_buttons_on_expected_bits() {
1536        let mut h = KonamiHyperShotState::new();
1537        // P1 Run (bit0) -> read bit 1; P2 Jump (bit3) -> read bit 4.
1538        h.set(0b1001);
1539        h.write_strobe(0); // enable both players (active-low)
1540        let r = h.read();
1541        assert_eq!(r & (1 << 1), 1 << 1, "P1 Run on bit 1");
1542        assert_eq!(r & (1 << 4), 1 << 4, "P2 Jump on bit 4");
1543        assert_eq!(r & (1 << 2), 0, "P1 Jump not pressed");
1544        assert_eq!(r & (1 << 3), 0, "P2 Run not pressed");
1545    }
1546
1547    #[test]
1548    fn konami_hyper_shot_disabled_player_reads_zero() {
1549        let mut h = KonamiHyperShotState::new();
1550        h.set(0b1111); // all four buttons held
1551        // Disable P1 (bit 1 set), enable P2 (bit 2 clear).
1552        h.write_strobe(0b0000_0010);
1553        let r = h.read();
1554        assert_eq!(r & (1 << 1), 0, "disabled P1 Run reads 0");
1555        assert_eq!(r & (1 << 2), 0, "disabled P1 Jump reads 0");
1556        assert_eq!(r & (1 << 3), 1 << 3, "enabled P2 Run reads pressed");
1557        assert_eq!(r & (1 << 4), 1 << 4, "enabled P2 Jump reads pressed");
1558    }
1559
1560    #[test]
1561    fn konami_hyper_shot_save_state_round_trip() {
1562        let mut h = KonamiHyperShotState::new();
1563        h.set(0b0110);
1564        h.write_strobe(0b0000_0100); // disable P2
1565        let r = KonamiHyperShotState::from_parts(
1566            h.buttons_raw(),
1567            h.p1_enabled_raw(),
1568            h.p2_enabled_raw(),
1569        );
1570        assert_eq!(r.peek(), h.peek());
1571        assert_eq!(r.buttons_raw(), 0b0110);
1572    }
1573
1574    #[test]
1575    fn bandai_hyper_shot_select_picks_sensor_group() {
1576        let mut b = BandaiHyperShotState::new();
1577        // Group A=0 = bits 0..=3 (Left Hook = bit 0), A=1 = bits 4..=7.
1578        b.set(0b0001_0001); // Left Hook (group0) + Left Jab (group1)
1579        b.write_strobe(0); // A=0 group
1580        assert_eq!(b.read() & (1 << 1), 1 << 1, "group0 sensor 0 on bit 1");
1581        b.write_strobe(0b0000_0010); // A=1 group
1582        assert_eq!(b.read() & (1 << 1), 1 << 1, "group1 sensor 0 on bit 1");
1583        // A sensor only in group0 must vanish when the A=1 group is selected.
1584        let mut b2 = BandaiHyperShotState::new();
1585        b2.set(0b0000_1000); // only group0 bit 3 (Right Hook)
1586        b2.write_strobe(0b0000_0010); // select A=1
1587        assert_eq!(b2.read() & 0b1_1110, 0, "group0-only sensor absent in A=1");
1588    }
1589
1590    #[test]
1591    fn bandai_hyper_shot_save_state_round_trip() {
1592        let mut b = BandaiHyperShotState::new();
1593        b.set(0b1100_0011);
1594        b.write_strobe(0b0000_0010);
1595        let r = BandaiHyperShotState::from_parts(b.sensors_raw(), b.select_raw());
1596        assert_eq!(r.peek(), b.peek());
1597        assert_eq!(r.sensors_raw(), 0b1100_0011);
1598        assert!(r.select_raw());
1599    }
1600
1601    #[test]
1602    fn hyper_shots_dispatch_through_input_device() {
1603        let mut k = InputDevice::KonamiHyperShot(KonamiHyperShotState::new());
1604        k.write_strobe(0);
1605        let _ = k.read();
1606        let _ = k.peek();
1607        let mut bd = InputDevice::BandaiHyperShot(BandaiHyperShotState::new());
1608        bd.write_strobe(0);
1609        let _ = bd.read();
1610        let _ = bd.peek();
1611    }
1612}