Skip to main content

rustynes_mappers/
m001_mmc1.rs

1//! MMC1 (iNES mapper 1) implementation.
2//!
3//! See `docs/mappers.md` §Mapper coverage matrix and §MMC1; see
4//! `ref-docs/research-report.md` §MMC1 for the source material.
5//!
6//! MMC1 is a serial mapper: writes to `$8000-$FFFF` are accumulated in a 5-bit
7//! shift register over five consecutive CPU writes. The fifth write commits
8//! the assembled value to one of four internal registers selected by bits
9//! 14-13 of the destination address (`$8000-$9FFF` -> control, `$A000-$BFFF`
10//! -> CHR0, `$C000-$DFFF` -> CHR1, `$E000-$FFFF` -> PRG). A write whose data
11//! has bit 7 set resets the shift register and ORs the control register
12//! with `$0C` (forcing PRG mode 3 = "fix last bank @ $C000").
13//!
14//! Consecutive-write bug: on real hardware the serial port ignores the DATA
15//! bit of a write that lands on the CPU cycle immediately after another
16//! serial-port write, whether that earlier write was accepted or itself
17//! ignored (nesdev: it "ignores every write after the first"). A write with
18//! bit 7 set is never ignored: the reset takes effect on any cycle. A
19//! read-modify-write instruction's two back-to-back writes are the case that
20//! matters: Bill & Ted's Excellent Adventure relies on the second data write
21//! being dropped, and Shinsenden on a reset in that second write landing. We
22//! track the cycle counter via [`Mapper::notify_cpu_cycle`]. Until v2.8.2 the
23//! reset was filtered too (RTL audit R-3.5a, which found the `MiSTer` core right
24//! and this implementation wrong).
25//!
26//! The default revision when the cartridge header lacks an NES 2.0 submapper
27//! byte is **Sharp** (project policy: Star Trek: 25th Anniversary requires
28//! the Sharp variant). NES 2.0 submapper 5 selects SEROM/SHROM/SH1ROM (no
29//! PRG-RAM), but at the level of MMC1 register semantics those are
30//! observationally equivalent.
31
32use crate::cartridge::Mirroring;
33use crate::mapper::{Mapper, MapperCaps, MapperError};
34use alloc::{boxed::Box, vec::Vec};
35use alloc::{format, vec};
36
37const PRG_BANK_16K: usize = 0x4000;
38const CHR_BANK_4K: usize = 0x1000;
39const CHR_BANK_8K: usize = 0x2000;
40const PRG_RAM_DEFAULT: usize = 0x2000;
41const NAMETABLE_SIZE: usize = 0x0400;
42const NAMETABLE_SIZE_U16: u16 = 0x0400;
43
44/// v2 appends one byte: the latched PPU A12 of the last CHR fetch, which picks
45/// the CHR register that drives SUROM / SOROM / SXROM's outer lines in 4 KiB
46/// CHR mode. Since v2.9.8 (ADR 0042) a v1 blob is refused; it used to load
47/// with that latch cleared.
48const SAVE_STATE_VERSION: u8 = 2;
49/// Size of one outer PRG-ROM half on SUROM / SXROM.
50const PRG_OUTER_256K: usize = 0x4_0000;
51/// One PRG-RAM bank.
52const PRG_RAM_BANK_8K: usize = 0x2000;
53
54/// MMC1 mapper.
55pub struct Mmc1 {
56    prg_rom: Box<[u8]>,
57    chr: Box<[u8]>,
58    prg_ram: Box<[u8]>,
59    /// Internal nametable VRAM (2 KiB) — owned by the console, but we keep it
60    /// here for now to mirror the NROM stop-gap until the PPU integration in
61    /// Sprint 2-1 moves CIRAM into the PPU.
62    vram: Box<[u8]>,
63    chr_is_ram: bool,
64
65    // MMC1 internal registers (5 bits each).
66    control: u8, // mirror, prg-mode, chr-mode
67    chr0: u8,    // CHR bank 0 ($A000-$BFFF write target)
68    chr1: u8,    // CHR bank 1 ($C000-$DFFF write target)
69    prg: u8,     // PRG bank ($E000-$FFFF write target)
70
71    // 5-write protocol shift register + count.
72    shift: u8,
73    shift_count: u8,
74
75    // Cycle of the most recent serial-port write, accepted or ignored (for the
76    // consecutive-write bug; since v2.8.2 an ignored write counts too, per
77    // "ignores every write after the first"). `u64::MAX` means "no prior
78    // write to inhibit on".
79    last_write_cycle: u64,
80    cpu_cycle: u64,
81
82    /// The PPU A12 level, as the PPU last reported it through `notify_a12`.
83    /// In 4 KiB CHR mode it selects which CHR register drives the outer PRG /
84    /// PRG-RAM lines on SUROM / SOROM / SXROM (see [`Self::outer_reg`]).
85    /// Latched from the A12 notification, not from `ppu_read`: a debugger
86    /// peek of CHR (`Bus::debug_peek_ppu`) goes through `ppu_read` and must
87    /// not change CPU-side banking (PR #550 review).
88    chr_a12_high: bool,
89}
90
91impl Mmc1 {
92    /// Construct a new MMC1 mapper.
93    ///
94    /// `prg_rom` must be a multiple of 16 KiB (typical sizes: 32 KiB up to
95    /// 512 KiB for SXROM). CHR-RAM is selected when `chr_rom` is empty;
96    /// otherwise CHR-ROM length must be a non-zero multiple of 4 KiB.
97    /// `prg_ram_bytes` of 0 selects the default 8 KiB.
98    ///
99    /// # Errors
100    ///
101    /// Returns [`MapperError::Invalid`] when sizes don't match.
102    pub fn new(
103        prg_rom: Box<[u8]>,
104        chr_rom: Box<[u8]>,
105        initial_mirroring: Mirroring,
106        prg_ram_bytes: usize,
107    ) -> Result<Self, MapperError> {
108        if prg_rom.is_empty() || !prg_rom.len().is_multiple_of(PRG_BANK_16K) {
109            return Err(MapperError::Invalid(format!(
110                "MMC1 PRG-ROM size {} is not a non-zero multiple of 16 KiB",
111                prg_rom.len()
112            )));
113        }
114        let chr_is_ram = chr_rom.is_empty();
115        let chr: Box<[u8]> = if chr_is_ram {
116            vec![0u8; CHR_BANK_8K].into_boxed_slice()
117        } else if chr_rom.len().is_multiple_of(CHR_BANK_4K) {
118            chr_rom
119        } else {
120            return Err(MapperError::Invalid(format!(
121                "MMC1 CHR-ROM size {} is not a multiple of 4 KiB",
122                chr_rom.len()
123            )));
124        };
125
126        let prg_ram_size = if prg_ram_bytes == 0 {
127            PRG_RAM_DEFAULT
128        } else {
129            prg_ram_bytes
130        };
131
132        // Initial control: PRG mode 3 (fix last bank at $C000-$FFFF), CHR
133        // mode 0 (8 KiB), single-screen-A. Per MMC1 power-on per nesdev wiki:
134        // "Common sense suggests $0C as a likely starting value because it
135        // forces $C000-$FFFF to be the last 16 KiB of PRG and the
136        // initial mirroring is undefined; software should set it itself."
137        // We start with mirroring derived from the iNES header byte (rather
138        // than letting the mapper pick), matching most test ROMs that don't
139        // set the control register before issuing reads.
140        let initial_control = match initial_mirroring {
141            Mirroring::SingleScreenA => 0x0C,
142            Mirroring::SingleScreenB => 0x0D,
143            Mirroring::Horizontal => 0x0F,
144            // Vertical / FourScreen / MapperControlled: default to vertical
145            // layout (a sensible neutral pick for headers that don't strictly
146            // belong to MMC1 like four-screen).
147            _ => 0x0E,
148        };
149
150        Ok(Self {
151            prg_rom,
152            chr,
153            prg_ram: vec![0u8; prg_ram_size].into_boxed_slice(),
154            vram: vec![0u8; 2 * NAMETABLE_SIZE].into_boxed_slice(),
155            chr_is_ram,
156            control: initial_control,
157            chr0: 0,
158            chr1: 0,
159            prg: 0,
160            shift: 0x10, // bit 4 set marks "5 writes still needed"
161            shift_count: 0,
162            last_write_cycle: u64::MAX,
163            cpu_cycle: 0,
164            chr_a12_high: false,
165        })
166    }
167
168    /// The four internal registers, `(control, chr0, chr1, prg)`, for boards
169    /// that embed an MMC1 and resolve banking themselves (mapper 105,
170    /// NES-EVENT, v2.9.6). Read-only: the serial port stays the only writer.
171    pub(crate) const fn registers(&self) -> (u8, u8, u8, u8) {
172        (self.control, self.chr0, self.chr1, self.prg)
173    }
174
175    /// Bits collected by the serial port so far (0-4). A write that finds 4
176    /// here and leaves 0 without the reset bit committed a register; that is
177    /// how mapper 105 sees a write to `$A000` even when its value is unchanged.
178    pub(crate) const fn shift_count(&self) -> u8 {
179        self.shift_count
180    }
181
182    /// Map a PPU address in `$2000-$3EFF` to a 2 KiB-VRAM offset using the
183    /// current mirroring mode (control bits 1-0).
184    fn nametable_offset(&self, addr: u16) -> usize {
185        let table = (((addr - 0x2000) / NAMETABLE_SIZE_U16) & 0x03) as u8;
186        let local = (addr as usize) & (NAMETABLE_SIZE - 1);
187        let physical = self.current_mirroring().physical_bank(table);
188        physical * NAMETABLE_SIZE + local
189    }
190
191    /// Does the board repurpose the CHR register's upper bits?
192    ///
193    /// SOROM, SUROM and SXROM "address only 8 KiB of CHR-ROM/-RAM" and route
194    /// the spare CHR lines to PRG A18 and PRG-RAM A13/A14
195    /// (`nesdev_wiki/MMC1.xhtml`). A board with more CHR uses those bits as
196    /// real CHR address lines, so nothing here applies to it.
197    fn chr_lines_repurposed(&self) -> bool {
198        self.chr.len() <= CHR_BANK_8K
199    }
200
201    /// The CHR register currently driving the outer lines: CHR bank 0 in 8 KiB
202    /// CHR mode; in 4 KiB mode, whichever register the last CHR fetch selected.
203    /// The wiki warns that mismatched registers make PRG "bankswitched ... as
204    /// the PPU renders", which is exactly this behaviour.
205    const fn outer_reg(&self) -> u8 {
206        if self.control & 0x10 != 0 && self.chr_a12_high {
207            self.chr1
208        } else {
209            self.chr0
210        }
211    }
212
213    /// Base offset of the 256 KiB PRG-ROM half selected by `P` (bit 4), or 0.
214    fn prg_outer_base(&self) -> usize {
215        if self.chr_lines_repurposed() && self.prg_rom.len() > PRG_OUTER_256K {
216            usize::from((self.outer_reg() >> 4) & 1) * PRG_OUTER_256K
217        } else {
218            0
219        }
220    }
221
222    /// Byte offset of the selected 8 KiB PRG-RAM bank: SXROM (32 KiB) uses
223    /// bits 3-2, SOROM (16 KiB) bit 3 only ("only implements the upper S").
224    fn prg_ram_bank_base(&self) -> usize {
225        if !self.chr_lines_repurposed() {
226            return 0;
227        }
228        let reg = usize::from(self.outer_reg());
229        match self.prg_ram.len() {
230            0x8000 => ((reg >> 2) & 0x03) * PRG_RAM_BANK_8K,
231            0x4000 => ((reg >> 3) & 0x01) * PRG_RAM_BANK_8K,
232            _ => 0,
233        }
234    }
235
236    /// Number of 16 KiB PRG banks the cartridge holds.
237    const fn prg_bank_count(&self) -> usize {
238        self.prg_rom.len() / PRG_BANK_16K
239    }
240
241    /// Resolve a CPU read at `$8000-$FFFF` into a PRG-ROM byte.
242    fn map_prg(&self, addr: u16) -> u8 {
243        let prg_mode = (self.control >> 2) & 0x03;
244        // The PRG register's 4 low bits address 16 x 16 KiB = 256 KiB; its bit
245        // 4 is the PRG-RAM disable, never a bank bit. SUROM / SXROM reach
246        // 512 KiB through the CHR register's `P` bit instead, which picks the
247        // 256 KiB half for the WHOLE window, fixed bank included (core audit
248        // §5.4; this used to claim all five PRG bits and read only four).
249        let outer = self.prg_outer_base();
250        // Never zero: `outer` is non-zero only on a ROM larger than 256 KiB, and
251        // the floor keeps mode 3's `bank_count - 1` and the modulo below safe
252        // even if that invariant is ever broken.
253        let bank_count = self
254            .prg_bank_count()
255            .saturating_sub(outer / PRG_BANK_16K)
256            .clamp(1, PRG_OUTER_256K / PRG_BANK_16K);
257        let prg_bank = self.prg & 0x0F;
258
259        let (bank_low, bank_high): (usize, usize) = match prg_mode {
260            0 | 1 => {
261                // 32 KiB switch: bank-low = prg & 0xE, bank-high = bank-low + 1
262                let bl = (prg_bank & 0x0E) as usize;
263                (bl, bl + 1)
264            }
265            2 => {
266                // First bank fixed to bank 0; second selectable
267                (0, prg_bank as usize)
268            }
269            _ => {
270                // Mode 3: first selectable; second fixed to last
271                (prg_bank as usize, bank_count - 1)
272            }
273        };
274        let bank_low = bank_low % bank_count;
275        let bank_high = bank_high % bank_count;
276
277        let offset_in_bank = (addr - 0x8000) as usize & (PRG_BANK_16K - 1);
278        let bank = if (addr & 0x4000) == 0 {
279            bank_low
280        } else {
281            bank_high
282        };
283        self.prg_rom[outer + bank * PRG_BANK_16K + offset_in_bank]
284    }
285
286    /// Resolve a PPU read at `$0000-$1FFF` into a CHR byte.
287    fn map_chr(&self, addr: u16) -> usize {
288        let chr_mode_8k = (self.control & 0x10) == 0;
289        if chr_mode_8k {
290            // 8 KiB CHR bank: CHR0 selects, low bit forced to 0.
291            let bank_count = (self.chr.len() / CHR_BANK_8K).max(1);
292            let bank = ((self.chr0 as usize) >> 1) % bank_count;
293            bank * CHR_BANK_8K + (addr as usize & (CHR_BANK_8K - 1))
294        } else {
295            // 4 KiB banks
296            let bank_count = (self.chr.len() / CHR_BANK_4K).max(1);
297            let bank = if addr < 0x1000 {
298                self.chr0 as usize
299            } else {
300                self.chr1 as usize
301            };
302            let bank = bank % bank_count;
303            bank * CHR_BANK_4K + (addr as usize & (CHR_BANK_4K - 1))
304        }
305    }
306
307    /// Is the `$6000-$7FFF` PRG-RAM window currently disabled?
308    ///
309    /// A2 (v2.2.3). MMC1 has **two** software write-protect layers and `RustyNES`
310    /// previously modelled neither, reading and writing `prg_ram`
311    /// unconditionally:
312    ///
313    /// * **`$E000` bit 4** — the PRG-RAM disable common to every MMC1 board.
314    ///   Set = RAM deselected (`/CE` deasserted).
315    /// * **SNROM's second layer** — on a board whose CHR is 8 KiB of RAM, the
316    ///   CHR bank register doubles as a PRG-RAM enable: `$A000` bit 4 is wired
317    ///   to the RAM's second enable. Only meaningful when `chr_is_ram`, which
318    ///   is how SNROM is distinguished from the CHR-ROM boards (SJROM etc.)
319    ///   that route those bits to real CHR banking instead.
320    ///
321    /// Holy Mapperel distinguishes the two: `M1_P128K_C32K` (SJROM, CHR-ROM)
322    /// reported `1000` — the `$E000` layer alone — while `M1_P128K_CR8K`
323    /// (SNROM, CHR-RAM) reported `5000`, both layers
324    /// (`MAPTEST_WRAMEN2 $40 | MAPTEST_WRAMEN $10`).
325    ///
326    /// The second layer is SNROM's alone. On SUROM / SXROM (> 256 KiB PRG) the
327    /// same CHR bit 4 is PRG A18, and on SOROM / SXROM (> 8 KiB PRG-RAM) the
328    /// board wires the S bits instead, so neither is a RAM enable there.
329    fn prg_ram_disabled(&self) -> bool {
330        if self.prg & 0x10 != 0 {
331            return true;
332        }
333        let snrom = self.chr_is_ram
334            && self.prg_rom.len() <= PRG_OUTER_256K
335            && self.prg_ram.len() <= PRG_RAM_BANK_8K;
336        snrom && (self.outer_reg() & 0x10) != 0
337    }
338
339    /// Index into `prg_ram` for a `$6000-$7FFF` access, after banking.
340    fn prg_ram_index(&self, addr: u16) -> usize {
341        (self.prg_ram_bank_base() + usize::from(addr - 0x6000)) % self.prg_ram.len().max(1)
342    }
343
344    /// Apply a completed 5-bit write to the appropriate internal register.
345    const fn commit(&mut self, addr: u16, value: u8) {
346        match addr & 0xE000 {
347            0x8000 => self.control = value,
348            0xA000 => self.chr0 = value,
349            0xC000 => self.chr1 = value,
350            // 0xE000
351            _ => self.prg = value,
352        }
353    }
354}
355
356impl Mapper for Mmc1 {
357    fn sram(&self) -> &[u8] {
358        &self.prg_ram
359    }
360    fn sram_mut(&mut self) -> &mut [u8] {
361        &mut self.prg_ram
362    }
363    // v2.8.0 Phase 4 — MMC1 overrides ONLY notify_cpu_cycle (the
364    // consecutive-write throttle); it has no IRQ and no audio.
365    fn caps(&self) -> MapperCaps {
366        MapperCaps {
367            cpu_cycle_hook: true,
368            audio: false,
369            frame_event_hook: false,
370            irq_source: false,
371        }
372    }
373
374    /// A2: a disabled PRG-RAM window is not driven, so the databus floats.
375    ///
376    /// Same contract as the FME-7 fix: report the window unmapped and let the
377    /// bus preserve its open-bus latch, rather than inventing a byte here.
378    fn cpu_read_unmapped(&self, addr: u16) -> bool {
379        if matches!(addr, 0x6000..=0x7FFF) {
380            return self.prg_ram.is_empty() || self.prg_ram_disabled();
381        }
382        addr < 0x6000
383    }
384
385    fn cpu_read(&mut self, addr: u16) -> u8 {
386        match addr {
387            0x6000..=0x7FFF => {
388                if self.prg_ram.is_empty() {
389                    0
390                } else {
391                    self.prg_ram[self.prg_ram_index(addr)]
392                }
393            }
394            0x8000..=0xFFFF => self.map_prg(addr),
395            _ => 0,
396        }
397    }
398
399    fn cpu_write(&mut self, addr: u16, value: u8) {
400        match addr {
401            0x6000..=0x7FFF => {
402                // A2: a disabled window is write-protected (both layers).
403                if self.prg_ram_disabled() {
404                    return;
405                }
406                if !self.prg_ram.is_empty() {
407                    let idx = self.prg_ram_index(addr);
408                    self.prg_ram[idx] = value;
409                }
410            }
411            0x8000..=0xFFFF => {
412                // Consecutive-cycle writes (nesdev MMC1): the serial port
413                // "ignores every write after the first", but "this restriction
414                // only applies to the data being written on bit 0; the bit 7
415                // reset is never ignored". So the filter is checked AFTER the
416                // reset, and every serial-port write -- ignored ones included
417                // -- counts as the last write. Until v2.8.2 the reset was
418                // filtered too, which *Shinsenden* (a reset on an `RRA abs,X`'s
419                // second write) needs not to be; the MiSTer RTL was right and
420                // this was wrong (RTL audit R-3.5a, inverted).
421                let consecutive = self.last_write_cycle != u64::MAX
422                    && self.cpu_cycle == self.last_write_cycle.wrapping_add(1);
423                self.last_write_cycle = self.cpu_cycle;
424
425                if value & 0x80 != 0 {
426                    // Reset: clear shift; OR control with $0C (force PRG mode 3).
427                    self.shift = 0x10;
428                    self.shift_count = 0;
429                    self.control |= 0x0C;
430                    return;
431                }
432                if consecutive {
433                    return;
434                }
435                // Shift bit 0 of value into bit 4 of shift, sliding right.
436                // After 5 writes, bit 0 of (original) shift is the LSB of
437                // the latched value; equivalently: low 5 bits, LSB first.
438                let new_lsb = value & 0x01;
439                self.shift = (self.shift >> 1) | (new_lsb << 4);
440                self.shift_count += 1;
441                if self.shift_count == 5 {
442                    let latched = self.shift & 0x1F;
443                    self.commit(addr, latched);
444                    self.shift = 0x10;
445                    self.shift_count = 0;
446                }
447            }
448            _ => {}
449        }
450    }
451
452    fn chr_phys(&self, addr: u16) -> Option<u32> {
453        if self.chr_is_ram {
454            None
455        } else {
456            u32::try_from(self.map_chr(addr & 0x1FFF)).ok()
457        }
458    }
459
460    // The only writer of `chr_a12_high`. The PPU reports every A12 transition
461    // here (not gated on capabilities), including the ones its `$2007`
462    // accesses make, which is the line the MMC1 actually watches.
463    fn notify_a12(&mut self, level: bool) {
464        self.chr_a12_high = level;
465    }
466
467    fn ppu_read(&mut self, addr: u16) -> u8 {
468        let addr = addr & 0x3FFF;
469        match addr {
470            0x0000..=0x1FFF => {
471                let off = self.map_chr(addr);
472                self.chr[off]
473            }
474            0x2000..=0x3EFF => {
475                let off = self.nametable_offset(addr);
476                self.vram[off]
477            }
478            _ => 0,
479        }
480    }
481
482    fn ppu_write(&mut self, addr: u16, value: u8) {
483        let addr = addr & 0x3FFF;
484        match addr {
485            0x0000..=0x1FFF => {
486                if self.chr_is_ram {
487                    let off = self.map_chr(addr);
488                    self.chr[off] = value;
489                }
490            }
491            0x2000..=0x3EFF => {
492                let off = self.nametable_offset(addr);
493                self.vram[off] = value;
494            }
495            _ => {}
496        }
497    }
498
499    fn notify_cpu_cycle(&mut self) {
500        self.cpu_cycle = self.cpu_cycle.wrapping_add(1);
501    }
502
503    fn current_mirroring(&self) -> Mirroring {
504        match self.control & 0x03 {
505            0 => Mirroring::SingleScreenA,
506            1 => Mirroring::SingleScreenB,
507            2 => Mirroring::Vertical,
508            _ => Mirroring::Horizontal,
509        }
510    }
511
512    fn debug_info(&self) -> crate::mapper::MapperDebugInfo {
513        let mut info = crate::mapper::MapperDebugInfo {
514            mapper_id: 1,
515            name: "MMC1".into(),
516            mirroring: crate::mapper::mirroring_name(self.current_mirroring()),
517            ..Default::default()
518        };
519        info.prg_banks
520            .push(("PRG".into(), format!("{:#04x}", self.prg)));
521        info.chr_banks
522            .push(("CHR0".into(), format!("{:#04x}", self.chr0)));
523        info.chr_banks
524            .push(("CHR1".into(), format!("{:#04x}", self.chr1)));
525        info.extra
526            .push(("control".into(), format!("{:#04x}", self.control)));
527        info.extra.push((
528            "shift".into(),
529            format!("{:#04x} (count {})", self.shift, self.shift_count),
530        ));
531        info
532    }
533
534    fn save_state(&self) -> Vec<u8> {
535        // Tagged blob: [version, control, chr0, chr1, prg, shift, count,
536        //   prg_ram..., vram..., chr_if_ram..., chr_a12_high (v2)]
537        let mut out = Vec::with_capacity(
538            8 + self.prg_ram.len()
539                + self.vram.len()
540                + if self.chr_is_ram { self.chr.len() } else { 0 },
541        );
542        out.push(SAVE_STATE_VERSION);
543        out.push(self.control);
544        out.push(self.chr0);
545        out.push(self.chr1);
546        out.push(self.prg);
547        out.push(self.shift);
548        out.push(self.shift_count);
549        out.extend_from_slice(&self.prg_ram);
550        out.extend_from_slice(&self.vram);
551        if self.chr_is_ram {
552            out.extend_from_slice(&self.chr);
553        }
554        out.push(u8::from(self.chr_a12_high));
555        out
556    }
557
558    fn load_state(&mut self, data: &[u8]) -> Result<(), MapperError> {
559        let need_chr = if self.chr_is_ram { self.chr.len() } else { 0 };
560        let version = *data.first().ok_or(MapperError::WrongLength {
561            expected: 1,
562            got: 0,
563        })?;
564        if version != SAVE_STATE_VERSION {
565            return Err(MapperError::UnsupportedVersion(version));
566        }
567        let expected = 7 + self.prg_ram.len() + self.vram.len() + need_chr + 1;
568        if data.len() != expected {
569            return Err(MapperError::WrongLength {
570                expected,
571                got: data.len(),
572            });
573        }
574        // The serial port holds at most four bits between commits (the fifth
575        // write commits and resets), in a 5-bit register. A restored count
576        // above 4 would wrap or overflow at the next write (NC-12, v2.9.9).
577        if data[6] > 4 || data[5] > 0x1F {
578            return Err(MapperError::Invalid(format!(
579                "MMC1 serial port out of range: shift {:#04x}, count {}",
580                data[5], data[6]
581            )));
582        }
583        self.control = data[1];
584        self.chr0 = data[2];
585        self.chr1 = data[3];
586        self.prg = data[4];
587        self.shift = data[5];
588        self.shift_count = data[6];
589        let mut cursor = 7;
590        self.prg_ram
591            .copy_from_slice(&data[cursor..cursor + self.prg_ram.len()]);
592        cursor += self.prg_ram.len();
593        self.vram
594            .copy_from_slice(&data[cursor..cursor + self.vram.len()]);
595        cursor += self.vram.len();
596        if self.chr_is_ram {
597            self.chr
598                .copy_from_slice(&data[cursor..cursor + self.chr.len()]);
599            cursor += self.chr.len();
600        }
601        self.chr_a12_high = data[cursor] != 0;
602        Ok(())
603    }
604}
605
606#[cfg(test)]
607#[allow(clippy::cast_possible_truncation)]
608mod tests {
609    use super::*;
610
611    fn synth_prg(banks: usize) -> Box<[u8]> {
612        // Each bank starts with a marker byte equal to the bank index, then
613        // address-low rolls. Lets us check which bank an address resolves to.
614        let mut v = vec![0u8; banks * PRG_BANK_16K];
615        for b in 0..banks {
616            for o in 0..PRG_BANK_16K {
617                v[b * PRG_BANK_16K + o] = if o == 0 { b as u8 } else { (o & 0xFF) as u8 };
618            }
619        }
620        v.into_boxed_slice()
621    }
622
623    fn synth_chr(banks_4k: usize) -> Box<[u8]> {
624        let mut v = vec![0u8; banks_4k * CHR_BANK_4K];
625        for b in 0..banks_4k {
626            for o in 0..CHR_BANK_4K {
627                v[b * CHR_BANK_4K + o] = if o == 0 { b as u8 } else { (o ^ 0x55) as u8 };
628            }
629        }
630        v.into_boxed_slice()
631    }
632
633    /// Issue the 5 bit-writes that latch `value` into the register bank
634    /// selected by the high two address bits.
635    fn write5(m: &mut Mmc1, addr: u16, value: u8) {
636        for i in 0..5 {
637            let bit = (value >> i) & 1;
638            // Drive notify_cpu_cycle far enough between each write so the
639            // consecutive-write bug never fires in tests.
640            for _ in 0..3 {
641                m.notify_cpu_cycle();
642            }
643            m.cpu_write(addr, bit);
644        }
645    }
646
647    /// nesdev MMC1, "Consecutive-cycle writes": the serial port ignores a
648    /// write on the cycle after another, but "this restriction only applies to
649    /// the data being written on bit 0; the bit 7 reset is never ignored".
650    /// *Shinsenden* sets bit 7 on a read-modify-write's SECOND write (`RRA
651    /// abs,X`) "and will crash ... if this reset is ignored". Before v2.8.2 the
652    /// oracle filtered the reset too; the `MiSTer` RTL never did (RTL audit
653    /// R-3.5a had the two the wrong way round).
654    #[test]
655    fn a_reset_on_the_cycle_after_a_write_is_never_ignored() {
656        let mut m = Mmc1::new(synth_prg(4), synth_chr(2), Mirroring::Vertical, 0).unwrap();
657        write5(&mut m, 0x8000, 0b0_1000); // PRG mode 2: $C000 switchable
658        write5(&mut m, 0xE000, 1);
659        assert_eq!(m.cpu_read(0xC000), 1, "mode 2 set up");
660        for _ in 0..3 {
661            m.notify_cpu_cycle();
662        }
663        m.cpu_write(0x8000, 0x7F); // RMW's first write: data, accepted
664        m.notify_cpu_cycle();
665        m.cpu_write(0x8000, 0x80); // the next cycle: the reset
666        assert_eq!(
667            m.cpu_read(0xC000),
668            3,
669            "the reset ORs $0C into Control, so the last bank is fixed at $C000"
670        );
671        // The shift register was cleared too: a fresh 5-write sequence latches.
672        write5(&mut m, 0xE000, 2);
673        assert_eq!(m.cpu_read(0x8000), 2);
674    }
675
676    /// The other half of the same rule, as *Bill & Ted's Excellent Adventure*
677    /// needs it: `INC` on a `$FF` byte writes `$FF` (a reset) and then `$00`
678    /// on the next cycle, and that `$00` data write must be ignored.
679    #[test]
680    fn a_data_write_on_the_cycle_after_a_reset_is_ignored() {
681        let mut m = Mmc1::new(synth_prg(4), synth_chr(2), Mirroring::Vertical, 0).unwrap();
682        for _ in 0..3 {
683            m.notify_cpu_cycle();
684        }
685        m.cpu_write(0x8000, 0xFF);
686        m.notify_cpu_cycle();
687        m.cpu_write(0x8000, 0x00); // ignored: would misalign the next sequence
688        write5(&mut m, 0xE000, 2);
689        assert_eq!(m.cpu_read(0x8000), 2, "the ignored write left no stray bit");
690    }
691
692    #[test]
693    fn mmc1_default_is_prg_mode_3_last_bank_at_c000() {
694        // Build a 4-bank PRG (64 KiB). Default control sets PRG mode 3:
695        // bank 0 selectable @ $8000, last bank fixed @ $C000.
696        let mut m = Mmc1::new(synth_prg(4), synth_chr(2), Mirroring::Vertical, 0).unwrap();
697        // $C000 should map to bank 3 (last); marker byte = 3.
698        assert_eq!(m.cpu_read(0xC000), 3);
699        // $8000 starts at bank 0 (prg register defaults to 0).
700        assert_eq!(m.cpu_read(0x8000), 0);
701    }
702
703    #[test]
704    fn mmc1_prg_register_switches_first_bank_in_mode_3() {
705        let mut m = Mmc1::new(synth_prg(4), synth_chr(2), Mirroring::Vertical, 0).unwrap();
706        // Set PRG bank 2.
707        write5(&mut m, 0xE000, 2);
708        assert_eq!(m.cpu_read(0x8000), 2);
709        assert_eq!(m.cpu_read(0xC000), 3); // last bank still fixed
710    }
711
712    #[test]
713    fn mmc1_prg_mode_2_fixes_first_bank_at_8000() {
714        let mut m = Mmc1::new(synth_prg(4), synth_chr(2), Mirroring::Vertical, 0).unwrap();
715        // Control: prg-mode = 2 (bits 3-2 = 10), chr-mode = 0, mirror = horiz (3).
716        write5(&mut m, 0x8000, 0b0_1011);
717        // PRG register selects bank for $C000. Set to 1.
718        write5(&mut m, 0xE000, 1);
719        assert_eq!(m.cpu_read(0x8000), 0); // fixed bank 0
720        assert_eq!(m.cpu_read(0xC000), 1); // selectable
721    }
722
723    #[test]
724    fn mmc1_prg_mode_0_or_1_switches_32k() {
725        let mut m = Mmc1::new(synth_prg(4), synth_chr(2), Mirroring::Vertical, 0).unwrap();
726        // Control: prg-mode = 0 (bits 3-2 = 00), chr-mode = 0, mirror = horiz.
727        write5(&mut m, 0x8000, 0b0_0011);
728        // PRG register = 2 -> 32 KiB switch picks (bank 2, bank 3).
729        write5(&mut m, 0xE000, 2);
730        assert_eq!(m.cpu_read(0x8000), 2);
731        assert_eq!(m.cpu_read(0xC000), 3);
732    }
733
734    #[test]
735    fn mmc1_chr_mode_4k_switches_independent_banks() {
736        let mut m = Mmc1::new(synth_prg(2), synth_chr(4), Mirroring::Vertical, 0).unwrap();
737        // Control: chr-mode = 1 (bit 4 set), prg-mode = 3, mirror = horiz.
738        write5(&mut m, 0x8000, 0b1_1111);
739        // CHR0 = bank 1, CHR1 = bank 2.
740        write5(&mut m, 0xA000, 1);
741        write5(&mut m, 0xC000, 2);
742        assert_eq!(m.ppu_read(0x0000), 1);
743        assert_eq!(m.ppu_read(0x1000), 2);
744    }
745
746    #[test]
747    fn mmc1_chr_mode_8k_uses_chr0_only() {
748        let mut m = Mmc1::new(synth_prg(2), synth_chr(4), Mirroring::Vertical, 0).unwrap();
749        // chr-mode = 0 (bit 4 clear), prg-mode = 3, mirror = horiz.
750        write5(&mut m, 0x8000, 0b0_1111);
751        // CHR0 = 2 -> 8 KiB bank starts at CHR4K-bank (2 >> 1) * 8 KiB = bank index 1.
752        write5(&mut m, 0xA000, 2);
753        assert_eq!(m.ppu_read(0x0000), 2); // 4K-bank index 2 -> first byte
754        assert_eq!(m.ppu_read(0x1000), 3); // next 4K-bank
755    }
756
757    #[test]
758    fn mmc1_reset_bit_forces_prg_mode_3() {
759        let mut m = Mmc1::new(synth_prg(4), synth_chr(2), Mirroring::Vertical, 0).unwrap();
760        // Set prg-mode = 0 first.
761        write5(&mut m, 0x8000, 0b0_0011);
762        // Now write reset bit.
763        m.cpu_write(0x8000, 0x80);
764        // Control should have been ORed with $0C. With our prior control of
765        // 0b0_0011 (= 0x03), after | 0x0C = 0x0F (mode 3, mirror horiz).
766        // Last bank should be fixed at $C000.
767        assert_eq!(m.cpu_read(0xC000), 3);
768    }
769
770    #[test]
771    fn mmc1_consecutive_write_bug_drops_second_write() {
772        let mut m = Mmc1::new(synth_prg(4), synth_chr(2), Mirroring::Vertical, 0).unwrap();
773        // Write bit 0 of shift. Then on the very next cycle, write bit 1 —
774        // hardware drops it.
775        m.notify_cpu_cycle();
776        m.cpu_write(0xE000, 1); // accepted; shift = 0x10 -> 0x18 (bit 4 from new_lsb)
777        m.notify_cpu_cycle();
778        m.cpu_write(0xE000, 1); // dropped (cycle = last_write+1)
779        // Continue with 4 more accepted writes.
780        for _ in 0..4 {
781            for _ in 0..3 {
782                m.notify_cpu_cycle();
783            }
784            m.cpu_write(0xE000, 0);
785        }
786        // We accepted bits: 1, 0, 0, 0, 0 -> latched value = 0b00001 = 1.
787        // PRG register = 1, mode 3 default, so $8000 -> bank 1.
788        assert_eq!(m.cpu_read(0x8000), 1);
789    }
790
791    #[test]
792    fn mmc1_mirroring_switches() {
793        let mut m = Mmc1::new(synth_prg(2), synth_chr(2), Mirroring::Vertical, 0).unwrap();
794        // single-screen-A
795        write5(&mut m, 0x8000, 0b0_1100);
796        m.ppu_write(0x2000, 0xAA);
797        assert_eq!(m.ppu_read(0x2400), 0xAA);
798        assert_eq!(m.ppu_read(0x2800), 0xAA);
799        assert_eq!(m.ppu_read(0x2C00), 0xAA);
800        // single-screen-B
801        write5(&mut m, 0x8000, 0b0_1101);
802        m.ppu_write(0x2000, 0xBB); // writes physical bank 1
803        // All four nametables now alias to bank 1.
804        assert_eq!(m.ppu_read(0x2400), 0xBB);
805        // vertical
806        write5(&mut m, 0x8000, 0b0_1110);
807        m.ppu_write(0x2000, 0x11); // bank 0
808        m.ppu_write(0x2400, 0x22); // bank 1
809        assert_eq!(m.ppu_read(0x2800), 0x11); // mirror of $2000
810        assert_eq!(m.ppu_read(0x2C00), 0x22); // mirror of $2400
811        // horizontal
812        write5(&mut m, 0x8000, 0b0_1111);
813        m.ppu_write(0x2000, 0x33); // bank 0
814        m.ppu_write(0x2800, 0x44); // bank 1
815        assert_eq!(m.ppu_read(0x2400), 0x33); // mirror of $2000
816        assert_eq!(m.ppu_read(0x2C00), 0x44); // mirror of $2800
817    }
818
819    #[test]
820    fn mmc1_save_state_round_trip() {
821        let mut m = Mmc1::new(synth_prg(2), synth_chr(2), Mirroring::Vertical, 0).unwrap();
822        write5(&mut m, 0xE000, 1);
823        m.cpu_write(0x6010, 0xCC);
824        m.ppu_write(0x2000, 0xDD);
825        let blob = m.save_state();
826        let mut m2 = Mmc1::new(synth_prg(2), synth_chr(2), Mirroring::Vertical, 0).unwrap();
827        m2.load_state(&blob).unwrap();
828        assert_eq!(m2.cpu_read(0x6010), 0xCC);
829        assert_eq!(m2.ppu_read(0x2000), 0xDD);
830        // PRG register should have round-tripped.
831        assert_eq!(m2.cpu_read(0x8000), 1);
832    }
833
834    // ---------------------------------------------------------------
835    // A2 (v2.2.3): the two PRG-RAM write-protect layers.
836    // ---------------------------------------------------------------
837
838    /// `$E000` bit 4 is the disable common to every MMC1 board. Write-protect
839    /// AND read-float, on a CHR-ROM board where the SNROM layer is inert.
840    #[test]
841    fn mmc1_e000_bit4_write_protects_and_floats_prg_ram() {
842        let mut m = Mmc1::new(synth_prg(8), synth_chr(8), Mirroring::Vertical, 0).unwrap();
843        // Enabled by default: a write sticks and the window is mapped.
844        m.cpu_write(0x6000, 0xA5);
845        assert_eq!(m.cpu_read(0x6000), 0xA5);
846        assert!(
847            !m.cpu_read_unmapped(0x6000),
848            "enabled window must be mapped"
849        );
850
851        // $E000 bit 4 set -> RAM deselected.
852        write5(&mut m, 0xE000, 0x10);
853        assert!(m.cpu_read_unmapped(0x6000), "disabled window must float");
854        m.cpu_write(0x6000, 0x5A); // must be discarded
855
856        // Re-enable: the pre-disable byte survives, proving the write above
857        // was dropped rather than merely hidden by the float.
858        write5(&mut m, 0xE000, 0x00);
859        assert!(!m.cpu_read_unmapped(0x6000));
860        assert_eq!(
861            m.cpu_read(0x6000),
862            0xA5,
863            "write while disabled must not land"
864        );
865    }
866
867    /// SNROM's second layer: on a CHR-**RAM** board the CHR register's bit 4 is
868    /// wired to the RAM's other enable, so it disables PRG-RAM independently of
869    /// `$E000`. This is what separated Holy Mapperel's `5000` (SNROM) from
870    /// `1000` (SJROM).
871    #[test]
872    fn mmc1_snrom_chr_bit4_is_a_second_prg_ram_enable() {
873        // Empty CHR => chr_is_ram, i.e. an SNROM-class board.
874        let mut m = Mmc1::new(synth_prg(8), Box::new([]), Mirroring::Vertical, 0).unwrap();
875        m.cpu_write(0x6000, 0x3C);
876        assert_eq!(m.cpu_read(0x6000), 0x3C);
877
878        // $E000 stays ENABLED; only the CHR-register layer disables.
879        write5(&mut m, 0xA000, 0x10);
880        assert!(
881            m.cpu_read_unmapped(0x6000),
882            "SNROM: $A000 bit 4 alone must disable PRG-RAM"
883        );
884        m.cpu_write(0x6000, 0x99); // discarded
885
886        write5(&mut m, 0xA000, 0x00);
887        assert_eq!(m.cpu_read(0x6000), 0x3C);
888    }
889
890    /// The same CHR-register bit must NOT touch PRG-RAM on a CHR-ROM board —
891    /// there it is a real CHR bank select. This is the regression that would
892    /// break every SJROM/SUROM title if the layer were applied unconditionally.
893    #[test]
894    fn mmc1_chr_rom_board_ignores_the_snrom_layer() {
895        let mut m = Mmc1::new(synth_prg(8), synth_chr(8), Mirroring::Vertical, 0).unwrap();
896        m.cpu_write(0x6000, 0x77);
897        write5(&mut m, 0xA000, 0x10); // a CHR bank select, not a RAM enable
898        assert!(
899            !m.cpu_read_unmapped(0x6000),
900            "CHR-ROM board: $A000 bit 4 is CHR banking, must not gate PRG-RAM"
901        );
902        assert_eq!(m.cpu_read(0x6000), 0x77);
903    }
904
905    // ---- v2.7.2: SUROM / SOROM / SXROM (core audit §5.4) -----------------
906    //
907    // Written from nesdev_wiki/MMC1.xhtml §"SOROM, SUROM and SXROM": on boards
908    // that address only 8 KiB of CHR, the CHR bank register reads `PSSxC`.
909    // P (bit 4) selects the 256 KiB PRG-ROM half and "applies to all the PRG
910    // area, including the normally fixed bank"; SS (bits 3-2) select the 8 KiB
911    // PRG-RAM bank (SOROM wires only the upper S, SXROM both: bit 3 = A14,
912    // bit 2 = A13).
913
914    #[test]
915    fn surom_bit_4_selects_the_upper_256k_half_including_the_fixed_bank() {
916        // 512 KiB PRG (32 x 16 KiB), CHR-RAM: SUROM.
917        let mut m = Mmc1::new(synth_prg(32), Box::new([]), Mirroring::Vertical, 0).unwrap();
918        // Power-on: PRG mode 3, outer half 0 -> the fixed bank is bank 15.
919        assert_eq!(
920            m.cpu_read(0xC000),
921            15,
922            "fixed bank = last of the LOWER half"
923        );
924        write5(&mut m, 0xA000, 0x10); // P = 1
925        assert_eq!(m.cpu_read(0xC000), 31, "fixed bank follows the outer half");
926        write5(&mut m, 0xE000, 2);
927        assert_eq!(
928            m.cpu_read(0x8000),
929            18,
930            "switchable bank 2 of the upper half"
931        );
932        write5(&mut m, 0xA000, 0x00);
933        assert_eq!(m.cpu_read(0x8000), 2);
934    }
935
936    #[test]
937    fn surom_bit_4_is_not_snrom_prg_ram_disable() {
938        // On SNROM (<= 256 KiB, CHR-RAM) CHR bit 4 disables PRG-RAM; on SUROM
939        // the same line is PRG A18, so selecting the upper half must leave the
940        // RAM enabled.
941        let mut m = Mmc1::new(synth_prg(32), Box::new([]), Mirroring::Vertical, 0).unwrap();
942        write5(&mut m, 0xA000, 0x10);
943        m.cpu_write(0x6000, 0x5A);
944        assert_eq!(m.cpu_read(0x6000), 0x5A);
945    }
946
947    #[test]
948    fn sxrom_bits_3_and_2_bank_32k_of_prg_ram() {
949        let mut m = Mmc1::new(synth_prg(32), Box::new([]), Mirroring::Vertical, 0x8000).unwrap();
950        for bank in 0..4u8 {
951            write5(&mut m, 0xA000, bank << 2);
952            m.cpu_write(0x6000, 0xA0 | bank);
953        }
954        for bank in 0..4u8 {
955            write5(&mut m, 0xA000, bank << 2);
956            assert_eq!(
957                m.cpu_read(0x6000),
958                0xA0 | bank,
959                "bank {bank} kept its own byte"
960            );
961            assert_eq!(
962                m.sram()[usize::from(bank) * 0x2000],
963                0xA0 | bank,
964                "sram() is the whole 32 KiB, in bank order"
965            );
966        }
967    }
968
969    #[test]
970    fn sorom_banks_16k_of_prg_ram_with_bit_3_only() {
971        // 256 KiB PRG, CHR-RAM, 16 KiB PRG-RAM: SOROM. Bit 2 is unconnected.
972        let mut m = Mmc1::new(synth_prg(16), Box::new([]), Mirroring::Vertical, 0x4000).unwrap();
973        write5(&mut m, 0xA000, 0x00);
974        m.cpu_write(0x6000, 0x11);
975        write5(&mut m, 0xA000, 0x08);
976        m.cpu_write(0x6000, 0x22);
977        write5(&mut m, 0xA000, 0x04); // bit 2 alone: still bank 0
978        assert_eq!(m.cpu_read(0x6000), 0x11);
979        write5(&mut m, 0xA000, 0x0C);
980        assert_eq!(m.cpu_read(0x6000), 0x22);
981    }
982
983    #[test]
984    fn in_4k_chr_mode_the_outer_bits_follow_the_register_the_ppu_last_used() {
985        // "In 4KB CHR bank mode ... P and S bits in both CHR bank registers
986        // must be set to the same values, or the PRG-ROM and/or RAM will be
987        // bankswitched as the PPU renders" -- so the live register is the one
988        // selected by the last CHR fetch's A12.
989        let mut m = Mmc1::new(synth_prg(32), Box::new([]), Mirroring::Vertical, 0).unwrap();
990        write5(&mut m, 0x8000, 0b1_1110); // CHR 4 KiB mode, PRG mode 3
991        write5(&mut m, 0xA000, 0x00);
992        write5(&mut m, 0xC000, 0x10);
993        m.notify_a12(true); // PPU A12 high -> CHR bank 1 drives
994        assert_eq!(m.cpu_read(0xC000), 31);
995        m.notify_a12(false); // A12 low -> CHR bank 0
996        assert_eq!(m.cpu_read(0xC000), 15);
997    }
998
999    #[test]
1000    fn a_chr_peek_does_not_change_cpu_banking() {
1001        // `Bus::debug_peek_ppu` documents a side-effect-free sample and routes
1002        // CHR through `ppu_read`/`ppu_write`. Only the PPU's A12 notification
1003        // may move the live register (PR #550 review).
1004        let mut m = Mmc1::new(synth_prg(32), Box::new([]), Mirroring::Vertical, 0).unwrap();
1005        write5(&mut m, 0x8000, 0b1_1110);
1006        write5(&mut m, 0xA000, 0x00);
1007        write5(&mut m, 0xC000, 0x10);
1008        let _ = m.ppu_read(0x1000);
1009        m.ppu_write(0x1000, 0);
1010        assert_eq!(m.cpu_read(0xC000), 15, "still CHR bank 0's half");
1011        assert!(!m.chr_a12_high);
1012    }
1013
1014    #[test]
1015    fn chr_rom_boards_keep_bits_4_to_2_as_chr_banking() {
1016        // SKROM-style: 128 KiB CHR-ROM means the CHR register's upper bits are
1017        // real CHR address lines, never PRG / PRG-RAM selects.
1018        let mut m = Mmc1::new(synth_prg(16), synth_chr(32), Mirroring::Vertical, 0).unwrap();
1019        write5(&mut m, 0xA000, 0x1C);
1020        assert_eq!(
1021            m.cpu_read(0xC000),
1022            15,
1023            "no outer PRG bank on a CHR-ROM board"
1024        );
1025        m.cpu_write(0x6000, 0x33);
1026        write5(&mut m, 0xA000, 0x00);
1027        assert_eq!(
1028            m.cpu_read(0x6000),
1029            0x33,
1030            "no PRG-RAM banking on a CHR-ROM board"
1031        );
1032    }
1033
1034    #[test]
1035    fn a_pre_v2_7_2_save_state_is_refused() {
1036        // v1 blobs lack the trailing CHR-A12 latch byte. They loaded with the
1037        // latch cleared until v2.9.8, which reads the current layout only
1038        // (ADR 0042).
1039        let mut m = Mmc1::new(synth_prg(32), Box::new([]), Mirroring::Vertical, 0).unwrap();
1040        m.notify_a12(true);
1041        let mut blob = m.save_state();
1042        assert_eq!(blob[0], 2);
1043        assert_eq!(*blob.last().unwrap(), 1, "the latch is saved");
1044        let mut fresh = Mmc1::new(synth_prg(32), Box::new([]), Mirroring::Vertical, 0).unwrap();
1045        fresh.load_state(&blob).expect("a v2 blob loads");
1046        assert!(fresh.chr_a12_high, "the latch is restored");
1047        blob.pop();
1048        blob[0] = 1;
1049        assert!(matches!(
1050            m.load_state(&blob),
1051            Err(MapperError::UnsupportedVersion(1))
1052        ));
1053        assert!(m.load_state(&[9]).is_err(), "an unknown version is refused");
1054    }
1055
1056    /// NC-12 (v2.9.9 re-audit): a restored serial count above 4, or a shift
1057    /// register with bits above 4, is refused rather than wrapping (release)
1058    /// or overflowing (debug) at the next serial write.
1059    #[test]
1060    fn an_out_of_range_serial_port_is_refused_on_restore() {
1061        let m = Mmc1::new(synth_prg(4), synth_chr(2), Mirroring::Vertical, 0).unwrap();
1062        let good = m.save_state();
1063        let mut probe = Mmc1::new(synth_prg(4), synth_chr(2), Mirroring::Vertical, 0).unwrap();
1064        assert!(probe.load_state(&good).is_ok());
1065        for (idx, value) in [(6usize, 5u8), (6, 0xFF), (5, 0x20), (5, 0xFF)] {
1066            let mut bad = good.clone();
1067            bad[idx] = value;
1068            assert!(
1069                matches!(probe.load_state(&bad), Err(MapperError::Invalid(_))),
1070                "byte {idx} = {value:#04x} must be refused"
1071            );
1072        }
1073        let mut edge = good;
1074        edge[6] = 4;
1075        edge[5] = 0x1F;
1076        assert!(
1077            probe.load_state(&edge).is_ok(),
1078            "count 4, shift $1F is a real state"
1079        );
1080    }
1081}