Skip to main content

rustynes_mappers/
m004_mmc3.rs

1//! MMC3 (iNES mapper 4) implementation.
2//!
3//! See `docs/mappers.md` §MMC3 and `ref-docs/research-report.md` §MMC3.
4//!
5//! # Banking
6//!
7//! Eight internal registers `R0`-`R7` selected by the low 3 bits of the
8//! value written to `$8000` (bank-select).  Subsequent writes to `$8001`
9//! (bank-data) commit a value into the selected register:
10//!
11//! | Register | Purpose                                          |
12//! |----------|--------------------------------------------------|
13//! | R0       | 2 KiB CHR bank @ `$0000-$07FF` (CHR mode 0)      |
14//! | R1       | 2 KiB CHR bank @ `$0800-$0FFF` (CHR mode 0)      |
15//! | R2       | 1 KiB CHR bank @ `$1000-$13FF` (CHR mode 0)      |
16//! | R3       | 1 KiB CHR bank @ `$1400-$17FF` (CHR mode 0)      |
17//! | R4       | 1 KiB CHR bank @ `$1800-$1BFF` (CHR mode 0)      |
18//! | R5       | 1 KiB CHR bank @ `$1C00-$1FFF` (CHR mode 0)      |
19//! | R6       | 8 KiB PRG bank @ `$8000-$9FFF` (PRG mode 0)      |
20//! | R7       | 8 KiB PRG bank @ `$A000-$BFFF`                   |
21//!
22//! `$8000` bit 6 swaps the PRG window: in mode 1, R6 maps to `$C000-$DFFF`
23//! and the second-to-last bank is fixed at `$8000-$9FFF`.  Bit 7 swaps the
24//! CHR layout: in mode 1, the 2 KiB R0/R1 banks occupy `$1000-$1FFF` and
25//! the four 1 KiB banks occupy `$0000-$0FFF`.
26//!
27//! `$E000-$FFFF` is hardwired to the LAST 8 KiB PRG bank.
28//!
29//! `$A000` even (`$A000-$BFFE` even addresses): mirroring (bit 0).
30//! `$A001` odd: PRG-RAM enable + protect (bit 7 enable, bit 6 write-protect).
31//! `$C000` even: IRQ counter reload value.
32//! `$C001` odd: latches `irq_reload_pending` and forces counter to 0.
33//! `$E000` even: disable IRQ + acknowledge any pending IRQ line.
34//! `$E001` odd: enable IRQ.
35//!
36//! # IRQ counter
37//!
38//! Clocked by PPU A12 rising edges, filtered to ignore rising edges within
39//! 3 M2 (CPU) cycles of the previous A12 fall.  Standard pattern-table
40//! layout (BG @ `$0000`, sprites @ `$1000`) yields exactly one filtered
41//! edge per scanline, at PPU dot 260.  Reversed layout (BG @ `$1000`,
42//! sprites @ `$0000`) places the edge at the END of the previous
43//! scanline's sprite fetches (Wario's Woods relies on this).
44//!
45//! On each filtered rising edge (`clock_irq`):
46//! - a `$C001` reload (`irq_reload_pending`): counter = `irq_reload_value`,
47//!   flag cleared; BOTH revisions assert if the new value is 0 and IRQs are
48//!   enabled (v3.1.0; the alternate one used not to, see `clock_irq`);
49//! - else if `counter == 0`: counter = `irq_reload_value`; only **Sharp**
50//!   asserts if the new value is 0, so a latch of 0 fires every scanline on
51//!   Sharp and stops on the alternate chip;
52//! - else: counter -= 1; if it reached 0 and IRQs are enabled, assert.
53//!
54//! Default revision is **Sharp** per project policy (Star Trek: 25th
55//! Anniversary requires it). NES 2.0 submapper 4 selects the alternate
56//! behaviour of the MMC3A and non-Sharp MMC3B ([`Mmc3Revision::Nec`]), 1 the
57//! MMC6 (v2.9.6 corrected both; this paragraph said "submapper 1 selects
58//! MMC3B (NEC)" until v3.1.0). For an iNES 1.0 dump, which cannot say,
59//! `Mapper::set_mmc3_revision_override` selects it (v3.1.0).
60
61#![allow(
62    clippy::cast_possible_truncation,
63    clippy::cast_lossless,
64    clippy::missing_const_for_fn,
65    clippy::struct_excessive_bools,
66    clippy::match_same_arms,
67    clippy::manual_range_patterns,
68    clippy::too_many_arguments,
69    clippy::useless_let_if_seq,
70    clippy::doc_markdown,
71    clippy::if_not_else,
72    clippy::nonminimal_bool
73)]
74
75use crate::cartridge::Mirroring;
76use crate::mapper::{Mapper, MapperCaps, MapperError};
77use alloc::{boxed::Box, vec::Vec};
78use alloc::{format, vec};
79
80const PRG_BANK_8K: usize = 0x2000;
81const CHR_BANK_1K: usize = 0x0400;
82const PRG_RAM_DEFAULT: usize = 0x2000;
83const NAMETABLE_SIZE: usize = 0x0400;
84const NAMETABLE_SIZE_U16: u16 = 0x0400;
85
86/// v3 (v2.9.6) appends the MMC6 PRG-RAM state and the MC-ACC prescaler.
87/// Only v3 is read since v2.9.8 (ADR 0042); v1 and v2 used to load with the
88/// later fields at defaults.
89const SAVE_STATE_VERSION: u8 = 4;
90
91/// MMC6 internal PRG-RAM: 1 KiB, two 512-byte halves (`MMC6.md`).
92const MMC6_RAM: usize = 0x0400;
93
94/// MMC3 IRQ-counter behaviour. Default is the Sharp ("new") behaviour; the
95/// alternative suppresses the "reload to 0 asserts IRQ" behaviour.
96///
97/// **Naming, corrected in v2.9.6.** These two variants were documented as
98/// "Sharp MMC3A" and "NEC MMC3B". `MMC3.md` says otherwise: the old or
99/// alternate behaviour belongs to the MMC3A and to non-Sharp MMC3B chips
100/// ("1 (Sharp MMC3B, MMC3C) or 2 (MMC3A, Non-Sharp MMC3B) to 256 scanlines"),
101/// and the NES 2.0 submapper for it is 4, not 1 (`NES_2_0_submappers.md`).
102/// The behaviour of each variant was always right; only the labels were
103/// wrong, together with the submapper mapping that followed them.
104#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
105pub enum Mmc3Revision {
106    /// Sharp MMC3B and MMC3C: reloading the IRQ counter to 0 asserts IRQ if
107    /// IRQs are enabled, so a latch of 0 fires every scanline. Default.
108    #[default]
109    Sharp,
110    /// NEC MMC3B and the MMC3A: the IRQ fires on the counter's 1 -> 0
111    /// transition, so a latch of 0 stops IRQs. NES 2.0 submapper 4.
112    Nec,
113}
114
115/// Which chip or board wiring an [`Mmc3`] models, beyond the IRQ revision
116/// (the NES 2.0 submappers of mapper 4, `NES_2_0_submappers.md`). v2.9.6.
117#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
118pub enum Mmc3Variant {
119    /// A standard MMC3 board (submappers 0 and 4).
120    #[default]
121    Standard,
122    /// Submapper 1, the MMC6 (`MMC6.md`): 1 KiB of internal PRG-RAM at
123    /// `$7000-$7FFF`, enabled by `$8000` bit 5, with separate read and write
124    /// enables for each 512-byte half in `$A001` bits 4-7.
125    Mmc6,
126    /// Submapper 2: an MMC3C with hard-wired mirroring; `$A000` does nothing.
127    HardwiredMirroring,
128    /// Submapper 3, Acclaim's MC-ACC (`MMC3.md`, "IRQ Specifics"): the
129    /// scanline counter is clocked by FALLING edges of PPU A12 through a
130    /// divide-by-8 prescaler instead of the M2 filter. The page gives only
131    /// that. The two details used here come from the forum measurement it
132    /// links (forums.nesdev.org, p=242427): writing `$C001` resets the
133    /// prescaler, and the counter clocks on the first edge of each group of
134    /// eight. That evidence is forum-level, so mapper 4 submapper 3 is
135    /// BestEffort in `tier.rs`.
136    McAcc,
137}
138
139/// MMC3 mapper (iNES mapper 4).
140pub struct Mmc3 {
141    prg_rom: Box<[u8]>,
142    chr: Box<[u8]>,
143    prg_ram: Box<[u8]>,
144    vram: Box<[u8]>,
145    chr_is_ram: bool,
146
147    // R0..R7 bank registers (8 internal regs selected via $8000).
148    regs: [u8; 8],
149    // Selected register index (low 3 bits of $8000).
150    bank_select: u8,
151    // PRG mode (bit 6 of $8000): 0 = R6 @ $8000, last-1 fixed @ $C000;
152    //                             1 = R6 @ $C000, last-1 fixed @ $8000.
153    prg_mode: bool,
154    // CHR mode (bit 7 of $8000): 0 = 2K@$0000 + 1K@$1000;
155    //                             1 = 1K@$0000 + 2K@$1000.
156    chr_mode: bool,
157
158    // Mirroring (set via $A000 even).  Ignored on 4-screen carts.
159    mirroring: Mirroring,
160    fixed_4screen: bool,
161
162    // PRG-RAM enable + protect ($A001 odd).
163    prg_ram_enabled: bool,
164    prg_ram_protect: bool,
165
166    // IRQ counter state.
167    irq_counter: u8,
168    irq_reload_value: u8,
169    irq_reload_pending: bool,
170    irq_enabled: bool,
171    irq_pending_line: bool,
172    // A clocking A12 rise sets this instead of the IRQ line, and the first
173    // `notify_cpu_cycle` after the rise moves it to `irq_pending_line`
174    // (T-ORACLE-001, v2.9.9). WHICH cycle that is depends on the bus, not on
175    // this mapper: `Cpu::start_cycle` catches the PPU up to the access and
176    // then calls `SystemBus::cpu_clock`, which calls `notify_cpu_cycle`. So a
177    // rise caught up before the access is raised in its own cycle, and one
178    // caught up after the access (`end_cycle`) from the next cycle on. (This
179    // comment said "one CPU cycle after the rise" for every rise until the
180    // #583 review; ADR 0002's 2026-10-05 correction has the detail.)
181    //
182    // Why: the oracle raised it a cycle early relative to the MiSTer
183    // sibling's MMC3, which registers its IRQ output on the CPU clock enable
184    // as a synchronous design must. Its per-cycle trace of
185    // `mapper4mmc3irq065` and of blargg's `4-scanline_timing` put the /IRQ
186    // fall one cycle apart, and with the oracle deferred by one cycle the two
187    // traces agree for 6,253,826 cycles instead of 1,250,766. The deferral
188    // also makes `mmc3_test` v1 `5-MMC3` pass and moves `4-scanline_timing`'s
189    // first failure from sub-test 3 to sub-test 9. It is not a filter: which
190    // rises clock the counter is unchanged, only when the line is seen.
191    //
192    // This replaced v2.0.0's `mmc3-m2-phase-irq` experiment, which deferred
193    // only rises seen in the M2-high half of a cycle. Measured against the
194    // same ROMs it passed the same set, because the split above is in effect
195    // the same one; this form is kept because it gets it from the order of
196    // the catch-up and the per-cycle hook, with no phase data from the bus.
197    // A delay of one cycle for EVERY rise cannot be built here: a pre-access
198    // rise of cycle N and a post-access rise of cycle N-1 both arrive between
199    // the same two `notify_cpu_cycle` calls.
200    irq_assert_pending_next_cycle: bool,
201
202    // A12 filter state.
203    last_a12: bool,
204    // CPU cycle at which A12 last went low; used to filter rising edges
205    // closer than 3 M2 cycles.
206    a12_low_cycle: u64,
207    cpu_cycle: u64,
208
209    revision: Mmc3Revision,
210    /// v3.1.0 (`T-MMC3-NEC-OVERRIDE`): the revision the cartridge header
211    /// selected, which `revision` returns to when an override is cleared.
212    /// Board identity, fixed at construction; not save-state (a state carries
213    /// the live `revision`).
214    header_revision: Mmc3Revision,
215    variant: Mmc3Variant,
216    /// MMC6: `$8000` bit 5, the PRG-RAM enable.
217    mmc6_ram_enabled: bool,
218    /// MMC6: `$A001` bits 4-7 (`HhLl`): read/write enables of each half.
219    mmc6_protect: u8,
220    /// MC-ACC: falling A12 edges counted, modulo 8.
221    mcacc_prescaler: u8,
222
223    // v2.1.5 F5.0 MMC3 R1/R2 residual instrumentation study (`mmc3-a12-phase-
224    // probe`, default-off): purely OBSERVATIONAL tallies of *qualifying*
225    // (`gap >= 3`) A12 rising edges, bucketed by the M2-phase half of the host
226    // CPU cycle in which they were observed. `sub_dot < 2` is the PRE-access
227    // (M2-low, φ1) half; `sub_dot >= 2` is the POST-access (M2-high, φ2) half.
228    // The `*_irq_*` pair further restricts to rises that actually clocked the
229    // counter to a state that asserts the IRQ line (`clock_irq()` returned
230    // true). These are counters only — they do NOT influence `irq_pending_line`
231    // or any emulated state, so the timeline is byte-identical to the default
232    // build. Surfaced via `debug_state().extra` for the study fixture to read.
233    // See ADR 0002 §"Decision update (2026-07-11, v2.1.5 F5.0 instrumentation
234    // study)". Compiled out entirely when the feature is off.
235    #[cfg(feature = "mmc3-a12-phase-probe")]
236    probe: Mmc3A12PhaseProbe,
237}
238
239/// Observational A12-phase probe state for the v2.1.5 F5.0 MMC3 R1/R2 residual
240/// instrumentation study. See the field doc on [`Mmc3::probe`]. Every counter
241/// is monotonic over a run; none feeds back into emulated state.
242#[cfg(feature = "mmc3-a12-phase-probe")]
243#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
244struct Mmc3A12PhaseProbe {
245    /// Qualifying (`gap >= 3`) A12 rises seen in the PRE-access (M2-low, φ1,
246    /// `sub_dot < 2`) half of a host CPU cycle.
247    qual_rises_pre: u64,
248    /// Qualifying (`gap >= 3`) A12 rises seen in the POST-access (M2-high, φ2,
249    /// `sub_dot >= 2`) half of a host CPU cycle. **The study's headline metric:
250    /// if this is 0 across all four failing sub-tests, no A12-phase / M2-half-
251    /// cycle filter refinement can move the residual (axis B is dead).**
252    qual_rises_post: u64,
253    /// Of the qualifying rises, those that clocked the counter to an
254    /// IRQ-asserting state (`clock_irq()` true) in the PRE-access half.
255    irq_clock_pre: u64,
256    /// Of the qualifying rises, those that clocked the counter to an
257    /// IRQ-asserting state (`clock_irq()` true) in the POST-access half.
258    irq_clock_post: u64,
259}
260
261impl Mmc3 {
262    /// Construct a new MMC3 mapper.
263    ///
264    /// `prg_rom` must be a non-zero multiple of 8 KiB (typical 32-512 KiB).
265    /// CHR-RAM is selected when `chr_rom` is empty; otherwise CHR-ROM
266    /// length must be a multiple of 1 KiB.  `prg_ram_bytes == 0` selects
267    /// the default 8 KiB.  Set `revision` from the iNES NES 2.0 submapper
268    /// (default Sharp).
269    ///
270    /// # Errors
271    ///
272    /// Returns [`MapperError::Invalid`] on size mismatch.
273    pub fn new(
274        prg_rom: Box<[u8]>,
275        chr_rom: Box<[u8]>,
276        initial_mirroring: Mirroring,
277        prg_ram_bytes: usize,
278        revision: Mmc3Revision,
279    ) -> Result<Self, MapperError> {
280        if prg_rom.is_empty() || !prg_rom.len().is_multiple_of(PRG_BANK_8K) {
281            return Err(MapperError::Invalid(format!(
282                "MMC3 PRG-ROM size {} is not a non-zero multiple of 8 KiB",
283                prg_rom.len()
284            )));
285        }
286        let chr_is_ram = chr_rom.is_empty();
287        let chr: Box<[u8]> = if chr_is_ram {
288            vec![0u8; 8 * CHR_BANK_1K].into_boxed_slice()
289        } else if chr_rom.len().is_multiple_of(CHR_BANK_1K) {
290            chr_rom
291        } else {
292            return Err(MapperError::Invalid(format!(
293                "MMC3 CHR-ROM size {} is not a multiple of 1 KiB",
294                chr_rom.len()
295            )));
296        };
297        let prg_ram_size = if prg_ram_bytes == 0 {
298            PRG_RAM_DEFAULT
299        } else {
300            prg_ram_bytes
301        };
302        let fixed_4screen = matches!(initial_mirroring, Mirroring::FourScreen);
303        // For four-screen, allocate the full 4 KiB nametable VRAM region.
304        let vram_size = if fixed_4screen {
305            4 * NAMETABLE_SIZE
306        } else {
307            2 * NAMETABLE_SIZE
308        };
309        Ok(Self {
310            prg_rom,
311            chr,
312            prg_ram: vec![0u8; prg_ram_size].into_boxed_slice(),
313            vram: vec![0u8; vram_size].into_boxed_slice(),
314            chr_is_ram,
315            regs: [0; 8],
316            bank_select: 0,
317            prg_mode: false,
318            chr_mode: false,
319            mirroring: initial_mirroring,
320            fixed_4screen,
321            prg_ram_enabled: true,
322            prg_ram_protect: false,
323            irq_counter: 0,
324            irq_reload_value: 0,
325            irq_reload_pending: false,
326            irq_assert_pending_next_cycle: false,
327            irq_enabled: false,
328            irq_pending_line: false,
329            last_a12: false,
330            a12_low_cycle: 0,
331            cpu_cycle: 0,
332            revision,
333            header_revision: revision,
334            variant: Mmc3Variant::Standard,
335            mmc6_ram_enabled: false,
336            mmc6_protect: 0,
337            mcacc_prescaler: 0,
338            #[cfg(feature = "mmc3-a12-phase-probe")]
339            probe: Mmc3A12PhaseProbe::default(),
340        })
341    }
342
343    /// Select the board wiring (the NES 2.0 submapper), v2.9.6. An MMC6 gets
344    /// its 1 KiB of internal RAM in place of the board PRG-RAM.
345    ///
346    /// Call it once, on a board fresh from a constructor: choosing
347    /// [`Mmc3Variant::Mmc6`] replaces the board PRG-RAM with the 1 KiB
348    /// internal RAM, and a later call with another variant does not restore
349    /// the original allocation.
350    #[must_use]
351    pub fn with_variant(mut self, variant: Mmc3Variant) -> Self {
352        self.variant = variant;
353        if variant == Mmc3Variant::Mmc6 {
354            self.prg_ram = vec![0u8; MMC6_RAM].into_boxed_slice();
355        }
356        self
357    }
358
359    /// MMC6: which half `$7000-$7FFF` addresses, and its read / write
360    /// enables. Half 0 is `$7000-$71FF` (bits 5 / 4), half 1 `$7200-$73FF`
361    /// (bits 7 / 6), mirrored through `$7FFF`.
362    const fn mmc6_half(&self, addr: u16) -> (usize, bool, bool) {
363        let high = addr & 0x0200 != 0;
364        let (r, w) = if high { (0x80, 0x40) } else { (0x20, 0x10) };
365        (
366            addr as usize & (MMC6_RAM - 1),
367            self.mmc6_protect & r != 0,
368            self.mmc6_protect & w != 0,
369        )
370    }
371
372    /// MMC6: the `$7000-$7FFF` window floats when RAM is disabled or neither
373    /// half is readable.
374    const fn mmc6_window_open(&self) -> bool {
375        !self.mmc6_ram_enabled || self.mmc6_protect & 0xA0 == 0
376    }
377
378    /// An MMC3 used only as a register file and IRQ counter, for boards that
379    /// resolve PRG and CHR themselves (`mmc3_boards.rs`, v2.9.6).
380    ///
381    /// Those boards put an outer bank or a CHR-RAM overlay between the MMC3's
382    /// bank outputs and the memories, so they own the ROM and read the raw
383    /// outputs through [`Self::prg_bank_raw`] and [`Self::chr_bank_1k`]. The
384    /// core therefore carries only placeholder memories: 8 KiB of PRG and
385    /// 1 KiB of CHR it never reads, no PRG-RAM, and the real nametable VRAM
386    /// (which the board delegates to it). Nothing in the Nintendo MMC3 path
387    /// calls this, so mapper 4 is unchanged.
388    pub(crate) fn register_core(mirroring: Mirroring, revision: Mmc3Revision) -> Self {
389        // Both sizes are valid by construction, so `new` cannot fail.
390        let mut core = Self::new(
391            vec![0u8; PRG_BANK_8K].into_boxed_slice(),
392            vec![0u8; CHR_BANK_1K].into_boxed_slice(),
393            mirroring,
394            PRG_RAM_DEFAULT,
395            revision,
396        )
397        .unwrap_or_else(|_| unreachable!("fixed valid sizes"));
398        core.prg_ram = Box::new([]);
399        core
400    }
401
402    /// The MMC3's raw 8 KiB PRG bank output for the CPU address `addr`
403    /// (`$8000-$FFFF`), before any board masking: R6 / R7 as written, and
404    /// the fixed banks as the chip drives them, all-ones (`$FF`) for the last
405    /// and `$FE` for the second-last. A multicart ANDs this with its inner
406    /// mask and ORs its outer bank in, which is why the fixed banks must be
407    /// the chip's own all-ones pattern and not "last bank of the ROM"
408    /// (`nesdev_wiki/output/INES_Mapper_045.md`, "PRG-AND").
409    pub(crate) fn prg_bank_raw(&self, addr: u16) -> u8 {
410        match (addr & 0xE000, self.prg_mode) {
411            (0x8000, false) | (0xC000, true) => self.regs[6],
412            (0x8000, true) | (0xC000, false) => 0xFE,
413            (0xA000, _) => self.regs[7],
414            _ => 0xFF,
415        }
416    }
417
418    /// `$A001` bit 7: the PRG-RAM chip enable. Boards that put a register in
419    /// the PRG-RAM window (mappers 37 and 47) accept a write only while the
420    /// MMC3 would let it reach RAM.
421    pub(crate) const fn prg_ram_enabled(&self) -> bool {
422        self.prg_ram_enabled
423    }
424
425    /// `$A001` bit 7 set and bit 6 clear: a write to `$6000-$7FFF` would
426    /// reach RAM.
427    pub(crate) const fn prg_ram_writable(&self) -> bool {
428        self.prg_ram_enabled && !self.prg_ram_protect
429    }
430
431    /// Resolve a CPU PRG address (`$8000-$FFFF`) to a byte offset in
432    /// `prg_rom`.  Implements PRG modes 0 and 1.
433    fn prg_offset(&self, addr: u16) -> usize {
434        let total_banks = self.prg_rom.len() / PRG_BANK_8K;
435        let last = total_banks.saturating_sub(1);
436        let second_last = total_banks.saturating_sub(2);
437        // R6/R7 are masked to total_banks (typical sizes <= 64 banks => 6 bits).
438        let r6 = (self.regs[6] as usize) & last;
439        let r7 = (self.regs[7] as usize) & last;
440        let bank = match (addr & 0xE000, self.prg_mode) {
441            (0x8000, false) => r6,
442            (0x8000, true) => second_last,
443            (0xA000, _) => r7,
444            (0xC000, false) => second_last,
445            (0xC000, true) => r6,
446            (0xE000, _) => last,
447            _ => 0,
448        };
449        bank * PRG_BANK_8K + ((addr as usize) & 0x1FFF)
450    }
451
452    /// Resolve a PPU CHR address (`$0000-$1FFF`) to the **raw** 1 KiB CHR
453    /// bank number selected by the active CHR bank registers, *before* any
454    /// masking against the installed CHR size.
455    ///
456    /// This is the value a TQROM-style board (mapper 119) inspects: bit 6
457    /// of the bank number selects CHR-RAM vs CHR-ROM, and the low bits
458    /// index within the selected memory. The MMC3 itself never exposes this
459    /// distinction (it masks the bank straight into a single CHR slice), so
460    /// the helper is provided for variant boards that embed an [`Mmc3`].
461    #[must_use]
462    pub fn chr_bank_1k(&self, addr: u16) -> usize {
463        let addr = (addr & 0x1FFF) as usize;
464        let slot = addr / CHR_BANK_1K;
465        self.chr_bank_1k_for_slot(slot)
466    }
467
468    /// The raw (unmasked) 1 KiB CHR bank number selected for `slot` (0..8),
469    /// honoring the current CHR-A12-inversion mode. Shared by [`Self::chr_offset`]
470    /// and [`Self::chr_bank_1k`].
471    fn chr_bank_1k_for_slot(&self, slot: usize) -> usize {
472        if !self.chr_mode {
473            match slot {
474                0 => (self.regs[0] as usize) & !1, // 2K @ $0000
475                1 => ((self.regs[0] as usize) & !1) | 1,
476                2 => (self.regs[1] as usize) & !1, // 2K @ $0800
477                3 => ((self.regs[1] as usize) & !1) | 1,
478                4 => self.regs[2] as usize, // 1K @ $1000
479                5 => self.regs[3] as usize,
480                6 => self.regs[4] as usize,
481                7 => self.regs[5] as usize,
482                _ => 0,
483            }
484        } else {
485            match slot {
486                0 => self.regs[2] as usize, // 1K @ $0000
487                1 => self.regs[3] as usize,
488                2 => self.regs[4] as usize,
489                3 => self.regs[5] as usize,
490                4 => (self.regs[0] as usize) & !1, // 2K @ $1000
491                5 => ((self.regs[0] as usize) & !1) | 1,
492                6 => (self.regs[1] as usize) & !1, // 2K @ $1800
493                7 => ((self.regs[1] as usize) & !1) | 1,
494                _ => 0,
495            }
496        }
497    }
498
499    /// Resolve a PPU CHR address (`$0000-$1FFF`) to an offset in `chr`.
500    fn chr_offset(&self, addr: u16) -> usize {
501        let addr = (addr & 0x1FFF) as usize;
502        let total_banks_1k = self.chr.len() / CHR_BANK_1K;
503        let mask = total_banks_1k.saturating_sub(1);
504        // Slot index in 1K units (0..8).
505        let slot = addr / CHR_BANK_1K;
506        // chr_mode false (0): 2K + 2K + 1K + 1K + 1K + 1K
507        //                     R0/R0+1   R1/R1+1   R2 R3 R4 R5
508        // chr_mode true  (1): 1K + 1K + 1K + 1K + 2K + 2K
509        //                     R2 R3 R4 R5  R0/R0+1 R1/R1+1
510        let bank_1k = self.chr_bank_1k_for_slot(slot);
511        let bank = bank_1k & mask;
512        bank * CHR_BANK_1K + (addr & (CHR_BANK_1K - 1))
513    }
514
515    /// Compute the CIRAM byte offset for a PPU address in
516    /// `$2000-$3EFF`.  Honors per-mapper mirroring; supports four-screen
517    /// (the extra 2 KiB lives in our `vram`).
518    fn nametable_offset(&self, addr: u16) -> usize {
519        let table = (((addr - 0x2000) / NAMETABLE_SIZE_U16) & 0x03) as u8;
520        let local = (addr as usize) & (NAMETABLE_SIZE - 1);
521        if self.fixed_4screen {
522            (table as usize) * NAMETABLE_SIZE + local
523        } else {
524            let physical = self.mirroring.physical_bank(table);
525            physical * NAMETABLE_SIZE + local
526        }
527    }
528
529    /// Clock the IRQ counter on a filtered A12 rising edge, and report
530    /// whether the IRQ should assert.
531    ///
532    /// The NESdev MMC3 page's rule: "When the IRQ is clocked (filtered A12
533    /// 0→1), the counter value is checked - if zero or the reload flag is
534    /// true, it's reloaded with the IRQ latched value at $C000; otherwise, it
535    /// decrements. If the IRQ counter is zero and IRQs are enabled ($E001),
536    /// an IRQ is triggered." That is the Sharp chip; the NEC chip asserts
537    /// only on a decrement to zero, not on a reload that leaves the counter
538    /// at zero ("Old/alternate behavior").
539    ///
540    /// 1. `irq_reload_pending` (set by a `$C001` write): reload from
541    ///    `irq_reload_value`, clear the flag.
542    /// 2. Counter already zero: reload from `irq_reload_value`.
543    /// 3. Otherwise: decrement.
544    ///
545    /// After any of the three, Sharp asserts when the counter is zero and
546    /// IRQs are enabled. The alternate revision (`Nec`) asserts after path 3,
547    /// and after path 1 when the reloaded value is 0, but never after path 2.
548    ///
549    /// **Changed in v3.1.0 (`T-MMC3-NEC-OVERRIDE`).** The alternate revision
550    /// asserted only after path 3. `MMC3.md`: "The 'alternate revision' checks
551    /// the IRQ counter transition 1→0, whether from decrementing or
552    /// reloading", and "writing to $C001 with $C000 still at $00 will result in
553    /// another single IRQ being generated". blargg's `mmc3_test_2/6-MMC3_alt`
554    /// says the same ("IRQ should be set when reloading due to clear, even if
555    /// counter was already 0") and could not run until v3.1.0 added a way to
556    /// select this revision for an iNES 1.0 ROM; it failed there on exactly
557    /// this, and passes now. The Sharp path is unchanged.
558    ///
559    /// **Changed in v2.9.9 (T-ORACLE-001).** Path 1 used to assert only when
560    /// the `$C001` write had cleared a non-zero counter (a latch named
561    /// `irq_reload_pending_with_nonzero_clear`). The page has no such
562    /// condition; the latch existed so `4-scanline_timing` sub-test 2 would
563    /// pass, and it did so by raising the IRQ a scanline late, which is what
564    /// failed sub-test 3. With the IRQ output deferred to the next per-cycle hook
565    /// (`irq_assert_pending_next_cycle`) the page's rule passes sub-test 2
566    /// by itself. ADR 0002 keeps the history of the earlier attempts.
567    fn clock_irq(&mut self) -> bool {
568        let mut would_assert = false;
569        if self.irq_reload_pending {
570            // Path 1: explicit $C001 reload. Both revisions assert when the
571            // reloaded value is 0 (for the alternate one, the single IRQ a
572            // $C001 write with $C000 = $00 produces).
573            self.irq_counter = self.irq_reload_value;
574            self.irq_reload_pending = false;
575            if self.irq_enabled && self.irq_counter == 0 {
576                would_assert = true;
577            }
578        } else if self.irq_counter == 0 {
579            // Path 2: natural counter-at-zero reload.  Sharp asserts when
580            // the new value is 0; NEC does not.
581            self.irq_counter = self.irq_reload_value;
582            if self.irq_enabled
583                && self.irq_counter == 0
584                && matches!(self.revision, Mmc3Revision::Sharp)
585            {
586                would_assert = true;
587            }
588        } else {
589            // Path 3: decrement.  Assert on transition to 0.
590            self.irq_counter = self.irq_counter.wrapping_sub(1);
591            if self.irq_counter == 0 && self.irq_enabled {
592                would_assert = true;
593            }
594        }
595        would_assert
596    }
597}
598
599impl Mapper for Mmc3 {
600    /// v3.1.0 (`T-MMC3-NEC-OVERRIDE`): `Some` forces the IRQ revision, `None`
601    /// returns to the header's. Applies to mapper 4 itself; the MMC3-derived
602    /// boards that embed this core keep their own revision.
603    fn set_mmc3_revision_override(&mut self, revision: Option<Mmc3Revision>) -> bool {
604        self.revision = revision.unwrap_or(self.header_revision);
605        true
606    }
607
608    fn sram(&self) -> &[u8] {
609        &self.prg_ram
610    }
611    fn sram_mut(&mut self) -> &mut [u8] {
612        &mut self.prg_ram
613    }
614    // v2.8.0 Phase 4 — CPU-cycle hook + IRQ source; no on-cart audio.
615    fn caps(&self) -> MapperCaps {
616        MapperCaps::CYCLE_IRQ
617    }
618
619    fn cpu_read_unmapped(&self, addr: u16) -> bool {
620        match addr {
621            0x4020..=0x5FFF => true,
622            0x6000..=0x6FFF if self.variant == Mmc3Variant::Mmc6 => true,
623            0x7000..=0x7FFF if self.variant == Mmc3Variant::Mmc6 => self.mmc6_window_open(),
624            0x6000..=0x7FFF => self.prg_ram.is_empty(),
625            _ => false,
626        }
627    }
628
629    fn cpu_read(&mut self, addr: u16) -> u8 {
630        match addr {
631            0x6000..=0x7FFF if self.variant == Mmc3Variant::Mmc6 => {
632                // "If only one bank is enabled for reading, the other reads
633                // back as zero" (`MMC6.md`).
634                let (off, readable, _) = self.mmc6_half(addr);
635                if addr >= 0x7000 && readable && self.mmc6_ram_enabled {
636                    self.prg_ram[off]
637                } else {
638                    0
639                }
640            }
641            0x6000..=0x7FFF => {
642                if self.prg_ram_enabled && !self.prg_ram.is_empty() {
643                    let off = (addr - 0x6000) as usize;
644                    if off < self.prg_ram.len() {
645                        return self.prg_ram[off];
646                    }
647                }
648                0
649            }
650            0x8000..=0xFFFF => {
651                let off = self.prg_offset(addr);
652                self.prg_rom[off % self.prg_rom.len()]
653            }
654            _ => 0,
655        }
656    }
657
658    fn cpu_write(&mut self, addr: u16, value: u8) {
659        match addr {
660            0x6000..=0x7FFF if self.variant == Mmc3Variant::Mmc6 => {
661                // "The write-enable bits only have effect if that bank is
662                // enabled for reading".
663                let (off, readable, writable) = self.mmc6_half(addr);
664                if addr >= 0x7000 && self.mmc6_ram_enabled && readable && writable {
665                    self.prg_ram[off] = value;
666                }
667            }
668            0x6000..=0x7FFF => {
669                if self.prg_ram_enabled && !self.prg_ram_protect && !self.prg_ram.is_empty() {
670                    let off = (addr - 0x6000) as usize;
671                    if off < self.prg_ram.len() {
672                        self.prg_ram[off] = value;
673                    }
674                }
675            }
676            0x8000..=0x9FFF => {
677                if addr & 1 == 0 {
678                    // $8000 even: bank-select.
679                    self.bank_select = value & 0x07;
680                    self.prg_mode = (value & 0x40) != 0;
681                    self.chr_mode = (value & 0x80) != 0;
682                    if self.variant == Mmc3Variant::Mmc6 {
683                        // "When PRG RAM is disabled via $8000, the mapper
684                        // continuously sets $A001 to $00".
685                        self.mmc6_ram_enabled = value & 0x20 != 0;
686                        if !self.mmc6_ram_enabled {
687                            self.mmc6_protect = 0;
688                        }
689                    }
690                } else {
691                    // $8001 odd: bank-data.
692                    self.regs[(self.bank_select & 0x07) as usize] = value;
693                }
694            }
695            0xA000..=0xBFFF => {
696                if addr & 1 == 0 {
697                    // Mirroring (ignored on 4-screen carts and on the
698                    // hard-wired MMC3C board, submapper 2).
699                    if !self.fixed_4screen && self.variant != Mmc3Variant::HardwiredMirroring {
700                        self.mirroring = if value & 1 == 0 {
701                            Mirroring::Vertical
702                        } else {
703                            Mirroring::Horizontal
704                        };
705                    }
706                } else if self.variant == Mmc3Variant::Mmc6 {
707                    if self.mmc6_ram_enabled {
708                        self.mmc6_protect = value & 0xF0;
709                    }
710                } else {
711                    // PRG-RAM protect / enable.
712                    self.prg_ram_enabled = (value & 0x80) != 0;
713                    self.prg_ram_protect = (value & 0x40) != 0;
714                }
715            }
716            0xC000..=0xDFFF => {
717                if addr & 1 == 0 {
718                    self.irq_reload_value = value;
719                } else {
720                    // $C001: clear the counter; the next filtered A12 rise
721                    // reloads it (`clock_irq` path 1).
722                    self.irq_counter = 0;
723                    self.irq_reload_pending = true;
724                    // MC-ACC: "Writing to $C001 resets pulse counter".
725                    self.mcacc_prescaler = 0;
726                }
727            }
728            0xE000..=0xFFFF => {
729                if addr & 1 == 0 {
730                    self.irq_enabled = false;
731                    self.irq_pending_line = false;
732                    // The ack/disable also cancels an assertion still in
733                    // flight: it is the same IRQ line, one cycle earlier.
734                    self.irq_assert_pending_next_cycle = false;
735                } else {
736                    self.irq_enabled = true;
737                }
738            }
739            _ => {}
740        }
741    }
742
743    fn chr_phys(&self, addr: u16) -> Option<u32> {
744        if self.chr_is_ram {
745            None
746        } else {
747            // The same per-bank offset `ppu_read` resolves (the 2/4 KiB MMC3 banks).
748            u32::try_from(self.chr_offset(addr & 0x1FFF) % self.chr.len().max(1)).ok()
749        }
750    }
751
752    fn ppu_read(&mut self, addr: u16) -> u8 {
753        let addr = addr & 0x3FFF;
754        match addr {
755            0x0000..=0x1FFF => {
756                let off = self.chr_offset(addr);
757                self.chr[off % self.chr.len()]
758            }
759            0x2000..=0x3EFF => self.vram[self.nametable_offset(addr) % self.vram.len()],
760            _ => 0,
761        }
762    }
763
764    fn ppu_write(&mut self, addr: u16, value: u8) {
765        let addr = addr & 0x3FFF;
766        match addr {
767            0x0000..=0x1FFF => {
768                if self.chr_is_ram {
769                    let off = self.chr_offset(addr);
770                    let len = self.chr.len();
771                    self.chr[off % len] = value;
772                }
773            }
774            0x2000..=0x3EFF => {
775                let off = self.nametable_offset(addr) % self.vram.len();
776                self.vram[off] = value;
777            }
778            _ => {}
779        }
780    }
781
782    fn nametable_address(&self, addr: u16) -> u16 {
783        // For 2 KiB CIRAM (the bus's PPU vram) the offset must fit in 0..0x800.
784        // For 4-screen we keep the full 4 KiB on-cart and serve via ppu_read/write,
785        // so the bus's CIRAM index does not matter — just return a canonical 0.
786        let off = self.nametable_offset(addr);
787        u16::try_from(off & 0x07FF).unwrap_or(0)
788    }
789
790    fn current_mirroring(&self) -> Mirroring {
791        self.mirroring
792    }
793
794    fn notify_a12(&mut self, level: bool) {
795        // Plumbing is in place to receive the sub-dot via
796        // `notify_a12_at_sub_dot`, but the MMC3 implementation does not
797        // yet differentiate behavior by M2 phase — see ADR-0002 →
798        // "Sub-dot plumbing landed (2026-05-14)" for the open
799        // implementation choice.  This legacy entry point treats the
800        // unknown sub-dot as M2-low (immediate assertion), matching
801        // the pre-M2-phase-pipeline behavior.
802        self.notify_a12_at_sub_dot(level, 1);
803    }
804
805    fn notify_a12_at_sub_dot(&mut self, level: bool, sub_dot: u8) {
806        // Track the M2-cycles-since-last-fall filter.  A rising edge that
807        // arrives < 3 CPU cycles after the prior fall is filtered.
808        //
809        //
810        // `sub_dot` (the M2 half of the CPU cycle the rise landed in) is read
811        // only by the `mmc3-a12-phase-probe` tally below. Until v2.9.9 the
812        // `mmc3-m2-phase-irq` experiment also used it to defer M2-high rises.
813        // The deferral that replaced it (`irq_assert_pending_next_cycle`)
814        // needs no phase: the bus's order gives it the same split (see that
815        // field's doc and ADR 0002's 2026-10-05 correction).
816        #[cfg(not(feature = "mmc3-a12-phase-probe"))]
817        let _ = sub_dot;
818        if self.variant == Mmc3Variant::McAcc {
819            // Falling edges through the divide-by-8 prescaler; the counter
820            // clocks on the first edge of each group of eight.
821            if self.last_a12 && !level {
822                if self.mcacc_prescaler == 0 && self.clock_irq() {
823                    self.irq_pending_line = true;
824                }
825                self.mcacc_prescaler = (self.mcacc_prescaler + 1) & 0x07;
826            }
827            self.last_a12 = level;
828            return;
829        }
830        if !self.last_a12 && level {
831            // Rising edge.
832            let gap = self.cpu_cycle.saturating_sub(self.a12_low_cycle);
833            // Restructured from the original `if gap >= 3 && self.clock_irq()`
834            // into a nested form so the v2.1.5 F5.0 probe can observe the
835            // *qualifying* rise (gap accepted) independently of whether it went
836            // on to clock the counter. This is behavior-identical: `clock_irq`
837            // (which mutates) is still only evaluated when `gap >= 3`, exactly
838            // the short-circuit the original `&&` provided.
839            if gap >= 3 {
840                // v2.1.5 F5.0 instrumentation study (observational only — no
841                // emulated-state change): bucket this qualifying A12 rise by the
842                // M2-phase half of the host CPU cycle it landed in. See ADR 0002
843                // F5.0. `sub_dot` carries real phase data only when the paired
844                // `rustynes-core/mmc3-a12-phase-probe` seeds it on the live
845                // one-clock scheduler path.
846                #[cfg(feature = "mmc3-a12-phase-probe")]
847                {
848                    if sub_dot >= 2 {
849                        self.probe.qual_rises_post += 1;
850                    } else {
851                        self.probe.qual_rises_pre += 1;
852                    }
853                }
854                if self.clock_irq() {
855                    // Of the qualifying rises, count those that actually clocked
856                    // the counter into an IRQ-asserting state, by phase half —
857                    // the strictest form of the study's question.
858                    #[cfg(feature = "mmc3-a12-phase-probe")]
859                    {
860                        if sub_dot >= 2 {
861                            self.probe.irq_clock_post += 1;
862                        } else {
863                            self.probe.irq_clock_pre += 1;
864                        }
865                    }
866                    // Seen by the CPU from the next cycle on; see the field
867                    // doc on `irq_assert_pending_next_cycle`.
868                    self.irq_assert_pending_next_cycle = true;
869                }
870            }
871        } else if self.last_a12 && !level {
872            // Falling edge.
873            self.a12_low_cycle = self.cpu_cycle;
874        }
875        self.last_a12 = level;
876    }
877
878    fn notify_cpu_cycle(&mut self) {
879        self.cpu_cycle = self.cpu_cycle.wrapping_add(1);
880        if self.irq_assert_pending_next_cycle {
881            self.irq_assert_pending_next_cycle = false;
882            self.irq_pending_line = true;
883        }
884    }
885
886    fn irq_pending(&self) -> bool {
887        self.irq_pending_line
888    }
889
890    fn irq_acknowledge(&mut self) {
891        // Hardware: the IRQ line stays asserted until $E000 disables / acks.
892        // The CPU's interrupt service does not clear it; the program does.
893        // We expose ack as a no-op to satisfy the trait but $E000 is the
894        // real path.
895    }
896
897    fn debug_info(&self) -> crate::mapper::MapperDebugInfo {
898        let mut info = crate::mapper::MapperDebugInfo {
899            mapper_id: 4,
900            name: format!("MMC3 ({:?})", self.revision),
901            mirroring: crate::mapper::mirroring_name(self.mirroring),
902            ..Default::default()
903        };
904        info.prg_banks
905            .push(("mode".into(), format!("{}", u8::from(self.prg_mode))));
906        info.prg_banks
907            .push(("R6".into(), format!("{:#04x}", self.regs[6])));
908        info.prg_banks
909            .push(("R7".into(), format!("{:#04x}", self.regs[7])));
910        info.chr_banks
911            .push(("mode".into(), format!("{}", u8::from(self.chr_mode))));
912        for i in 0..6 {
913            info.chr_banks
914                .push((format!("R{i}"), format!("{:#04x}", self.regs[i])));
915        }
916        info.irq_state
917            .push(("counter".into(), format!("{:#04x}", self.irq_counter)));
918        info.irq_state
919            .push(("reload".into(), format!("{:#04x}", self.irq_reload_value)));
920        info.irq_state
921            .push(("enabled".into(), format!("{}", self.irq_enabled)));
922        info.irq_state
923            .push(("pending".into(), format!("{}", self.irq_pending_line)));
924        info.extra
925            .push(("bank_select".into(), format!("{:#04x}", self.bank_select)));
926        info.extra.push((
927            "prg_ram".into(),
928            format!("en={} prot={}", self.prg_ram_enabled, self.prg_ram_protect),
929        ));
930        // v2.1.5 F5.0 instrumentation study: surface the observational A12-phase
931        // tallies so the `mmc3_r1r2_phase_probe` fixture can read them after a
932        // run without new trait methods. Present only under the (default-off)
933        // probe feature — the shipped `debug_state` is unchanged. See ADR 0002.
934        #[cfg(feature = "mmc3-a12-phase-probe")]
935        {
936            info.extra.push((
937                "probe_qual_pre".into(),
938                format!("{}", self.probe.qual_rises_pre),
939            ));
940            info.extra.push((
941                "probe_qual_post".into(),
942                format!("{}", self.probe.qual_rises_post),
943            ));
944            info.extra.push((
945                "probe_irq_pre".into(),
946                format!("{}", self.probe.irq_clock_pre),
947            ));
948            info.extra.push((
949                "probe_irq_post".into(),
950                format!("{}", self.probe.irq_clock_post),
951            ));
952        }
953        info
954    }
955
956    fn save_state(&self) -> Vec<u8> {
957        // Tagged blob: version + scalar regs + RAM blocks.
958        let mut out =
959            Vec::with_capacity(64 + self.prg_ram.len() + self.vram.len() + self.chr.len());
960        out.push(SAVE_STATE_VERSION);
961        out.extend_from_slice(&self.regs);
962        out.push(self.bank_select);
963        out.push(u8::from(self.prg_mode));
964        out.push(u8::from(self.chr_mode));
965        out.push(self.mirroring as u8);
966        out.push(u8::from(self.fixed_4screen));
967        out.push(u8::from(self.prg_ram_enabled));
968        out.push(u8::from(self.prg_ram_protect));
969        out.push(self.irq_counter);
970        out.push(self.irq_reload_value);
971        out.push(u8::from(self.irq_reload_pending));
972        out.push(u8::from(self.irq_assert_pending_next_cycle));
973        out.push(u8::from(self.irq_enabled));
974        out.push(u8::from(self.irq_pending_line));
975        out.push(u8::from(self.last_a12));
976        out.extend_from_slice(&self.a12_low_cycle.to_le_bytes());
977        out.extend_from_slice(&self.cpu_cycle.to_le_bytes());
978        out.push(match self.revision {
979            Mmc3Revision::Sharp => 0,
980            Mmc3Revision::Nec => 1,
981        });
982        out.extend_from_slice(&self.prg_ram);
983        out.extend_from_slice(&self.vram);
984        if self.chr_is_ram {
985            out.extend_from_slice(&self.chr);
986        }
987        // v3 tail.
988        out.push(u8::from(self.mmc6_ram_enabled));
989        out.push(self.mmc6_protect);
990        out.push(self.mcacc_prescaler);
991        out
992    }
993
994    #[allow(clippy::too_many_lines)] // tagged-blob deserializer + v1/v2 fork
995    fn load_state(&mut self, data: &[u8]) -> Result<(), MapperError> {
996        let chr_part = if self.chr_is_ram { self.chr.len() } else { 0 };
997        // Tagged scalars laid out below. Only the current version is read
998        // (v2.9.8, ADR 0042): v1 and v2 used to load with defaults. v3
999        // (v2.9.8) is refused too: its byte 19 was the retired
1000        // `irq_reload_pending_with_nonzero_clear` latch, which v4 replaces
1001        // with `irq_assert_pending_next_cycle` (T-ORACLE-001).
1002        if data.is_empty() {
1003            return Err(MapperError::WrongLength {
1004                expected: 1,
1005                got: 0,
1006            });
1007        }
1008        let version = data[0];
1009        if version != SAVE_STATE_VERSION {
1010            return Err(MapperError::UnsupportedVersion(version));
1011        }
1012        let tail = 3;
1013        let scalar_len = 1 + 8 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 8 + 8 + 1 + 1;
1014        let expected = scalar_len + self.prg_ram.len() + self.vram.len() + chr_part + tail;
1015        if data.len() != expected {
1016            return Err(MapperError::WrongLength {
1017                expected,
1018                got: data.len(),
1019            });
1020        }
1021        self.regs.copy_from_slice(&data[1..9]);
1022        self.bank_select = data[9];
1023        self.prg_mode = data[10] != 0;
1024        self.chr_mode = data[11] != 0;
1025        self.mirroring = match data[12] {
1026            0 => Mirroring::Horizontal,
1027            1 => Mirroring::Vertical,
1028            2 => Mirroring::SingleScreenA,
1029            3 => Mirroring::SingleScreenB,
1030            4 => Mirroring::FourScreen,
1031            5 => Mirroring::MapperControlled,
1032            other => {
1033                return Err(MapperError::Invalid(format!(
1034                    "unknown mirroring tag {other}"
1035                )));
1036            }
1037        };
1038        self.fixed_4screen = data[13] != 0;
1039        self.prg_ram_enabled = data[14] != 0;
1040        self.prg_ram_protect = data[15] != 0;
1041        self.irq_counter = data[16];
1042        self.irq_reload_value = data[17];
1043        self.irq_reload_pending = data[18] != 0;
1044        let mut cur = 19usize;
1045        self.irq_assert_pending_next_cycle = data[cur] != 0;
1046        cur += 1;
1047        self.irq_enabled = data[cur] != 0;
1048        cur += 1;
1049        self.irq_pending_line = data[cur] != 0;
1050        cur += 1;
1051        self.last_a12 = data[cur] != 0;
1052        cur += 1;
1053        self.a12_low_cycle = u64::from_le_bytes(
1054            data[cur..cur + 8]
1055                .try_into()
1056                .map_err(|_| MapperError::Invalid("a12_low_cycle truncated".into()))?,
1057        );
1058        cur += 8;
1059        self.cpu_cycle = u64::from_le_bytes(
1060            data[cur..cur + 8]
1061                .try_into()
1062                .map_err(|_| MapperError::Invalid("cpu_cycle truncated".into()))?,
1063        );
1064        cur += 8;
1065        self.revision = match data[cur] {
1066            0 => Mmc3Revision::Sharp,
1067            1 => Mmc3Revision::Nec,
1068            other => {
1069                return Err(MapperError::Invalid(format!(
1070                    "unknown MMC3 revision tag {other}"
1071                )));
1072            }
1073        };
1074        cur += 1;
1075        self.prg_ram
1076            .copy_from_slice(&data[cur..cur + self.prg_ram.len()]);
1077        cur += self.prg_ram.len();
1078        self.vram.copy_from_slice(&data[cur..cur + self.vram.len()]);
1079        cur += self.vram.len();
1080        if self.chr_is_ram {
1081            self.chr.copy_from_slice(&data[cur..cur + self.chr.len()]);
1082            cur += self.chr.len();
1083        }
1084        self.mmc6_ram_enabled = data[cur] != 0;
1085        self.mmc6_protect = data[cur + 1] & 0xF0;
1086        self.mcacc_prescaler = data[cur + 2] & 0x07;
1087        Ok(())
1088    }
1089}
1090
1091#[cfg(test)]
1092#[allow(clippy::cast_possible_truncation)]
1093mod tests {
1094    use super::*;
1095
1096    fn synth_prg(banks_8k: usize) -> Box<[u8]> {
1097        let mut v = vec![0u8; banks_8k * PRG_BANK_8K];
1098        for b in 0..banks_8k {
1099            v[b * PRG_BANK_8K] = b as u8;
1100        }
1101        v.into_boxed_slice()
1102    }
1103
1104    fn synth_chr(banks_1k: usize) -> Box<[u8]> {
1105        let mut v = vec![0u8; banks_1k * CHR_BANK_1K];
1106        for b in 0..banks_1k {
1107            v[b * CHR_BANK_1K] = b as u8;
1108        }
1109        v.into_boxed_slice()
1110    }
1111
1112    fn fresh(prg_banks: usize, chr_banks: usize) -> Mmc3 {
1113        Mmc3::new(
1114            synth_prg(prg_banks),
1115            synth_chr(chr_banks),
1116            Mirroring::Horizontal,
1117            0,
1118            Mmc3Revision::Sharp,
1119        )
1120        .unwrap()
1121    }
1122
1123    #[test]
1124    fn last_8k_bank_fixed_at_e000() {
1125        let mut m = fresh(8, 8);
1126        // Default state: PRG mode 0, R6=R7=0.  $E000 should map to last bank.
1127        assert_eq!(m.cpu_read(0xE000), 7);
1128    }
1129
1130    #[test]
1131    fn second_to_last_bank_fixed_at_c000_in_mode0() {
1132        let mut m = fresh(8, 8);
1133        m.cpu_write(0x8000, 0); // mode 0
1134        assert_eq!(m.cpu_read(0xC000), 6);
1135    }
1136
1137    #[test]
1138    fn r6_swaps_8000_in_mode0_and_c000_in_mode1() {
1139        let mut m = fresh(8, 8);
1140        m.cpu_write(0x8000, 6); // select R6
1141        m.cpu_write(0x8001, 3); // R6 = 3
1142        // Mode 0: $8000 -> R6 = bank 3
1143        assert_eq!(m.cpu_read(0x8000), 3);
1144        // Mode 1: $8000 -> second-to-last (bank 6); $C000 -> R6 = bank 3.
1145        m.cpu_write(0x8000, 0x40 | 6); // PRG mode bit
1146        assert_eq!(m.cpu_read(0x8000), 6);
1147        assert_eq!(m.cpu_read(0xC000), 3);
1148    }
1149
1150    #[test]
1151    fn chr_mode0_layout_2k_2k_1k_1k_1k_1k() {
1152        let mut m = fresh(8, 8);
1153        m.cpu_write(0x8000, 0); // R0
1154        m.cpu_write(0x8001, 4); // R0 = 4 (LSB ignored, so bank 4)
1155        m.cpu_write(0x8000, 1); // R1
1156        m.cpu_write(0x8001, 6); // R1 = 6
1157        m.cpu_write(0x8000, 2); // R2
1158        m.cpu_write(0x8001, 1); // R2 = bank 1
1159        // $0000-$03FF (slot 0) -> R0 & ~1 = 4.
1160        assert_eq!(m.ppu_read(0x0000), 4);
1161        // $0400 (slot 1) -> R0 | 1 = 5.
1162        assert_eq!(m.ppu_read(0x0400), 5);
1163        // $0800 (slot 2) -> R1 & ~1 = 6.
1164        assert_eq!(m.ppu_read(0x0800), 6);
1165        // $1000 (slot 4) -> R2 = 1.
1166        assert_eq!(m.ppu_read(0x1000), 1);
1167    }
1168
1169    #[test]
1170    fn chr_mode1_swaps_2k_and_1k_regions() {
1171        let mut m = fresh(8, 8);
1172        m.cpu_write(0x8000, 0x80); // CHR mode 1
1173        m.cpu_write(0x8000, 0x80); // R0
1174        m.cpu_write(0x8001, 4);
1175        m.cpu_write(0x8000, 0x80 | 2);
1176        m.cpu_write(0x8001, 1); // R2
1177        // Mode 1: $0000 (slot 0) -> R2 = 1; $1000 (slot 4) -> R0 & ~1 = 4.
1178        assert_eq!(m.ppu_read(0x0000), 1);
1179        assert_eq!(m.ppu_read(0x1000), 4);
1180    }
1181
1182    #[test]
1183    fn mirroring_register_toggles_h_v() {
1184        let mut m = fresh(8, 8);
1185        m.cpu_write(0xA000, 0);
1186        assert_eq!(m.mirroring, Mirroring::Vertical);
1187        m.cpu_write(0xA000, 1);
1188        assert_eq!(m.mirroring, Mirroring::Horizontal);
1189    }
1190
1191    #[test]
1192    fn prg_ram_enable_protect_via_a001() {
1193        let mut m = fresh(8, 8);
1194        // PRG-RAM defaults to enabled, not protected.
1195        m.cpu_write(0x6000, 0xAB);
1196        assert_eq!(m.cpu_read(0x6000), 0xAB);
1197        // Disable.
1198        m.cpu_write(0xA001, 0x00);
1199        m.cpu_write(0x6000, 0xCD); // ignored
1200        assert_eq!(m.cpu_read(0x6000), 0); // returns 0 (open bus stub)
1201        // Re-enable + write-protect.
1202        m.cpu_write(0xA001, 0x80 | 0x40);
1203        m.cpu_write(0x6000, 0x12); // ignored (protected)
1204        assert_eq!(m.cpu_read(0x6000), 0xAB); // original value preserved
1205    }
1206
1207    #[test]
1208    fn irq_counter_decrements_and_asserts() {
1209        let mut m = fresh(8, 8);
1210        m.cpu_write(0xC000, 3); // reload = 3
1211        m.cpu_write(0xC001, 0); // pending reload
1212        m.cpu_write(0xE001, 0); // enable IRQ
1213        // Simulate four filtered A12 rising edges, advancing CPU cycles
1214        // between each so the M2 filter accepts.
1215        for _ in 0..4 {
1216            // Fall A12 low, advance >= 3 CPU cycles, then raise.
1217            m.notify_a12(false);
1218            for _ in 0..4 {
1219                m.notify_cpu_cycle();
1220            }
1221            m.notify_a12(true);
1222        }
1223        // First edge: reload to 3.  Edges 2,3,4: decrement to 2,1,0 (assert),
1224        // visible from the next CPU cycle.
1225        m.notify_cpu_cycle();
1226        assert!(m.irq_pending());
1227    }
1228
1229    #[test]
1230    fn irq_disabled_no_assert() {
1231        let mut m = fresh(8, 8);
1232        m.cpu_write(0xC000, 1);
1233        m.cpu_write(0xC001, 0);
1234        // No $E001 enable.
1235        for _ in 0..3 {
1236            m.notify_a12(false);
1237            for _ in 0..4 {
1238                m.notify_cpu_cycle();
1239            }
1240            m.notify_a12(true);
1241        }
1242        assert!(!m.irq_pending());
1243    }
1244
1245    #[test]
1246    fn e000_acks_pending_irq() {
1247        let mut m = fresh(8, 8);
1248        m.irq_pending_line = true;
1249        m.cpu_write(0xE000, 0);
1250        assert!(!m.irq_pending());
1251    }
1252
1253    #[test]
1254    fn a12_filter_rejects_close_rising_edges() {
1255        let mut m = fresh(8, 8);
1256        m.cpu_write(0xC000, 1);
1257        m.cpu_write(0xC001, 0);
1258        m.cpu_write(0xE001, 0);
1259        // First edge: filter accepts (reload to 1).
1260        m.notify_a12(false);
1261        for _ in 0..4 {
1262            m.notify_cpu_cycle();
1263        }
1264        m.notify_a12(true);
1265        // Now toggle low->high again with only 1 cycle gap: filter REJECTS.
1266        m.notify_a12(false);
1267        m.notify_cpu_cycle();
1268        m.notify_a12(true);
1269        // Counter should still be 1 (only first edge was accepted).
1270        assert_eq!(m.irq_counter, 1);
1271        assert!(!m.irq_pending());
1272    }
1273
1274    /// Helper: emit one filter-accepted A12 toggle (low ≥ 3 M2 cycles
1275    /// then high), then run the CPU cycle after it, from which an IRQ the
1276    /// rise asserted is visible (`irq_assert_pending_next_cycle`). Used by
1277    /// the Sharp/NEC reload-to-zero unit tests below.
1278    fn a12_rise<F: Mapper>(m: &mut F) {
1279        m.notify_a12(false);
1280        for _ in 0..4 {
1281            m.notify_cpu_cycle();
1282        }
1283        m.notify_a12(true);
1284        m.notify_cpu_cycle();
1285    }
1286
1287    /// Sharp asserts IRQ when the counter is decremented to 0 via a
1288    /// natural A12 clock (decrement-to-0 path).  This is the primary
1289    /// Sharp/NEC commonality — both revisions assert here.  See
1290    /// `clock_irq` path 3 (decrement).
1291    #[test]
1292    fn sharp_asserts_on_decrement_to_zero() {
1293        let mut m = fresh(8, 8);
1294        // Reload value = 1.  Pre-condition: counter at 0, reload_pending
1295        // set (from $C001 after start-up).
1296        m.cpu_write(0xC000, 1);
1297        m.cpu_write(0xC001, 0);
1298        m.cpu_write(0xE001, 0);
1299        // First A12 rise: silent reload to 1 (was_nonzero_at_clear = false
1300        // — counter was already 0 at the $C001 write).
1301        a12_rise(&mut m);
1302        assert_eq!(m.irq_counter, 1, "first rise reloaded silently");
1303        assert!(
1304            !m.irq_pending(),
1305            "first $C001-induced reload (counter was 0) must not assert"
1306        );
1307        // Second A12 rise: counter decrements from 1 to 0 → asserts (both
1308        // Sharp and NEC).
1309        a12_rise(&mut m);
1310        assert_eq!(m.irq_counter, 0);
1311        assert!(
1312            m.irq_pending(),
1313            "decrement-to-0 asserts on both Sharp and NEC"
1314        );
1315    }
1316
1317    /// Sharp's Rev-A-specific "reload-to-0 asserts" rule.  Distinct from
1318    /// NEC (Rev B) in `nec_does_not_assert_on_reload_to_zero` below.
1319    /// This is the path `mmc3_test_2/5-MMC3.nes` ("set IRQ every clock
1320    /// when reload is 0") exercises in the steady state.
1321    ///
1322    /// Setup: `$C001` clears a non-zero counter; the next A12 rise reloads
1323    /// it to 0 and Sharp asserts. Whether the cleared counter was non-zero
1324    /// does not matter (see the next test); before v2.9.9 it did.
1325    #[test]
1326    fn sharp_asserts_on_reload_to_zero_after_nonzero_clear() {
1327        let mut m = fresh(8, 8);
1328        // Prime the counter to a non-zero value: write reload_value=1,
1329        // $C001 (counter was 0 — silent), one A12 (silent reload to 1).
1330        m.cpu_write(0xC000, 1);
1331        m.cpu_write(0xC001, 0);
1332        m.cpu_write(0xE001, 0);
1333        a12_rise(&mut m);
1334        assert_eq!(m.irq_counter, 1);
1335        assert!(!m.irq_pending(), "silent reload, no assertion");
1336        // Now write reload_value=0 and $C001 again (counter WAS non-zero
1337        // at this write, so the next A12 rise asserts on Sharp).
1338        m.cpu_write(0xC000, 0);
1339        m.cpu_write(0xC001, 0);
1340        // Filter accepts after ≥ 3 M2 cycles; the prior `a12_rise` already
1341        // re-armed the filter low.
1342        a12_rise(&mut m);
1343        assert_eq!(m.irq_counter, 0);
1344        assert!(
1345            m.irq_pending(),
1346            "Sharp asserts on reload-to-0 after non-zero-to-zero $C001 clear"
1347        );
1348    }
1349
1350    /// The page's rule has no "was the cleared counter non-zero" condition:
1351    /// a `$C001` written while the counter is already 0 still reloads on the
1352    /// next rise, and a reload to 0 with IRQs enabled asserts on Sharp.
1353    ///
1354    /// Until v2.9.9 this test pinned the opposite (a "no-op clear" that
1355    /// reloaded silently), which existed only to pass `4-scanline_timing`
1356    /// sub-test 2 and raised that test's IRQ a scanline late
1357    /// (T-ORACLE-001). The IRQ output deferral passes sub-test 2 under
1358    /// the page's rule.
1359    #[test]
1360    fn sharp_asserts_on_reload_to_zero_after_zero_to_zero_clear() {
1361        let mut m = fresh(8, 8);
1362        m.cpu_write(0xC000, 0);
1363        m.cpu_write(0xC001, 0); // counter already 0
1364        m.cpu_write(0xE001, 0);
1365        a12_rise(&mut m);
1366        assert_eq!(m.irq_counter, 0);
1367        assert!(
1368            m.irq_pending(),
1369            "a reload to 0 asserts on Sharp whatever the cleared counter held"
1370        );
1371        m.cpu_write(0xE000, 0);
1372        m.cpu_write(0xE001, 0);
1373        a12_rise(&mut m);
1374        assert!(
1375            m.irq_pending(),
1376            "natural reload-to-0 (no pending reload) asserts on Sharp"
1377        );
1378    }
1379
1380    /// NEC (the "alternate" revision) asserts once on a `$C001` reload to 0,
1381    /// and never on the natural `was_zero` reload. `MMC3.md`: it "generates
1382    /// only a single IRQ when `$C000` is `$00`", and "writing to `$C001` with
1383    /// `$C000` still at `$00` will result in another single IRQ"; blargg's
1384    /// `6-MMC3_alt` fails with "IRQ should be set when reloading due to
1385    /// clear" otherwise. v3.1.0 corrected this: until then the test pinned
1386    /// NEC as silent on both paths.
1387    #[test]
1388    fn nec_asserts_once_on_a_c001_reload_to_zero_and_not_after() {
1389        let mut m = Mmc3::new(
1390            synth_prg(8),
1391            synth_chr(8),
1392            Mirroring::Horizontal,
1393            0,
1394            Mmc3Revision::Nec,
1395        )
1396        .unwrap();
1397        m.cpu_write(0xC000, 1);
1398        m.cpu_write(0xC001, 0);
1399        m.cpu_write(0xE001, 0);
1400        a12_rise(&mut m);
1401        m.cpu_write(0xC000, 0);
1402        m.cpu_write(0xC001, 0);
1403        a12_rise(&mut m);
1404        assert_eq!(m.irq_counter, 0);
1405        assert!(m.irq_pending(), "the $C001 reload to 0 asserts on NEC too");
1406        // Acknowledge, then the natural was_zero reload stays silent.
1407        m.cpu_write(0xE000, 0);
1408        m.cpu_write(0xE001, 0);
1409        assert!(!m.irq_pending());
1410        a12_rise(&mut m);
1411        a12_rise(&mut m);
1412        assert!(!m.irq_pending(), "NEC: the was_zero reload to 0 is silent");
1413        // A second $C001 write with $C000 still 0: another single IRQ.
1414        m.cpu_write(0xC001, 0);
1415        a12_rise(&mut m);
1416        assert!(m.irq_pending(), "each $C001 write gives one more IRQ");
1417    }
1418
1419    /// T-41-005 — reversed pattern-table layout (`PPUCTRL` bit 4 set,
1420    /// bit 3 clear: BG=`$1000`, sprites=`$0000`). Canary: Wario's Woods.
1421    ///
1422    /// In the standard layout (BG=`$0000`, sprites=`$1000`) the per-scanline
1423    /// A12 rising edge happens during the sprite tile fetch group at PPU
1424    /// dot 260, after the BG fetches at dots 1-256 (all of which used the
1425    /// `$0000` pattern table). In the reversed layout the per-scanline A12
1426    /// rising edge happens during the next scanline's BG fetch group at
1427    /// dot 1, after the sprite tile fetches at dots 260-320 (which used the
1428    /// `$0000` pattern table).
1429    ///
1430    /// From MMC3's perspective the two layouts produce **the same sequence
1431    /// of A12 transitions per scanline** — one fall after the prior
1432    /// scanline's BG fetches (or this scanline's sprite fetches), followed
1433    /// by a rise after enough M2 cycles for the filter to accept. The IRQ
1434    /// counter should clock identically. This test exercises both layouts
1435    /// in the same `Mmc3` and asserts the per-rising-edge counter behavior
1436    /// matches.
1437    #[test]
1438    fn reversed_pattern_table_layout_clocks_irq_identically() {
1439        // Helper: emit one filter-accepted A12 rise (low for ≥ 3 M2 cycles,
1440        // then high). Returns the IRQ-pending flag immediately after.
1441        fn pulse_a12(m: &mut Mmc3) -> bool {
1442            m.notify_a12(false);
1443            for _ in 0..4 {
1444                m.notify_cpu_cycle();
1445            }
1446            m.notify_a12(true);
1447            m.notify_cpu_cycle();
1448            m.irq_pending()
1449        }
1450
1451        // Standard layout simulation (BG=$0000, sprites=$1000). Per-scanline:
1452        // 1. BG fetches at dots 1-256 read patterns from $0000-$0FFF (A12 low).
1453        // 2. Sprite tile fetches at dots 260-320 read from $1000-$1FFF (A12
1454        //    rises around dot 260).
1455        // 3. After dot 320 the BG fetches for the *next* scanline run at
1456        //    dots 321-336 from $0000-$0FFF (A12 falls again).
1457        // We model this as: A12=false (during BG fetches) -> A12=true (sprite
1458        //    fetches) per scanline. Filter sees one rise per scanline.
1459        let mut std_layout = fresh(8, 8);
1460        std_layout.cpu_write(0xC000, 4); // reload = 4
1461        std_layout.cpu_write(0xC001, 0); // pending reload
1462        std_layout.cpu_write(0xE001, 0); // enable IRQ
1463        // 5 scanlines: edges 1 (reload 4), 2 (3), 3 (2), 4 (1), 5 (0 + assert).
1464        for n in 0..5 {
1465            let pending = pulse_a12(&mut std_layout);
1466            // Only the 5th edge should assert (counter went 4→reload, then
1467            // 4→3→2→1→0).
1468            assert_eq!(
1469                pending,
1470                n == 4,
1471                "standard layout edge #{n} pending should be {} (counter={})",
1472                n == 4,
1473                std_layout.irq_counter
1474            );
1475        }
1476        std_layout.cpu_write(0xE000, 0); // ack
1477
1478        // Reversed layout simulation (BG=$1000, sprites=$0000). Per-scanline:
1479        // 1. BG fetches at dots 1-256 read patterns from $1000-$1FFF (A12
1480        //    high — but the rising edge happened at the start of the BG fetch
1481        //    group, not at dot 260).
1482        // 2. Sprite tile fetches at dots 260-320 read from $0000-$0FFF (A12
1483        //    falls).
1484        // 3. Next scanline's BG fetches at dots 321-336 read from $1000 again
1485        //    (A12 rises).
1486        // From the mapper's perspective: one fall + one rise per scanline,
1487        // just shifted relative to where the rise happens within the
1488        // scanline. The filter behavior is identical.
1489        let mut rev_layout = fresh(8, 8);
1490        rev_layout.cpu_write(0xC000, 4);
1491        rev_layout.cpu_write(0xC001, 0);
1492        rev_layout.cpu_write(0xE001, 0);
1493        for n in 0..5 {
1494            let pending = pulse_a12(&mut rev_layout);
1495            assert_eq!(
1496                pending,
1497                n == 4,
1498                "reversed layout edge #{n} pending should be {} (counter={})",
1499                n == 4,
1500                rev_layout.irq_counter
1501            );
1502        }
1503
1504        // Both layouts must reach the same internal state at the same edge.
1505        assert_eq!(
1506            std_layout.irq_counter, rev_layout.irq_counter,
1507            "standard and reversed layouts must produce identical IRQ counter \
1508             values after the same number of filter-accepted A12 rises"
1509        );
1510    }
1511
1512    /// T-41-005 follow-up — the A12 filter must remain stable against
1513    /// the **fast-low-high** pulse pattern that the reversed layout
1514    /// produces at the boundary between sprite fetches (dot 320, A12
1515    /// low) and the next scanline's BG fetches (dot 321 onward, A12
1516    /// high). Real silicon's 3-M2-cycle filter rejects rises that come
1517    /// less than ~3 CPU cycles after the previous fall. We assert the
1518    /// filter rejects the rise if too few cycles have elapsed AND
1519    /// accepts it when enough have.
1520    #[test]
1521    fn reversed_layout_a12_filter_3_m2_boundary() {
1522        let mut m = fresh(8, 8);
1523        m.cpu_write(0xC000, 2);
1524        m.cpu_write(0xC001, 0);
1525        m.cpu_write(0xE001, 0);
1526
1527        // First rise — filter primes from low state.
1528        m.notify_a12(false);
1529        for _ in 0..4 {
1530            m.notify_cpu_cycle();
1531        }
1532        m.notify_a12(true);
1533        let counter_after_first = m.irq_counter;
1534        assert_eq!(counter_after_first, 2, "first rise reloads to 2");
1535
1536        // Rapid fall+rise with only 1 CPU cycle gap: filter REJECTS.
1537        m.notify_a12(false);
1538        m.notify_cpu_cycle();
1539        m.notify_a12(true);
1540        assert_eq!(
1541            m.irq_counter, counter_after_first,
1542            "rapid rise within < 3 M2 cycles must be filtered out"
1543        );
1544
1545        // Same pulse but with 3 cycles between fall and rise: ACCEPTED.
1546        m.notify_a12(false);
1547        for _ in 0..4 {
1548            m.notify_cpu_cycle();
1549        }
1550        m.notify_a12(true);
1551        assert!(
1552            m.irq_counter < counter_after_first,
1553            "rise after >= 3 M2 cycles must clock the counter; counter={}",
1554            m.irq_counter
1555        );
1556    }
1557
1558    #[test]
1559    fn save_load_round_trip() {
1560        let mut m = fresh(8, 8);
1561        m.cpu_write(0x8000, 6);
1562        m.cpu_write(0x8001, 3);
1563        m.cpu_write(0x8000, 7);
1564        m.cpu_write(0x8001, 5);
1565        m.cpu_write(0xC000, 0x42);
1566        m.cpu_write(0xE001, 0);
1567        let blob = m.save_state();
1568        let mut other = fresh(8, 8);
1569        other.load_state(&blob).unwrap();
1570        assert_eq!(other.regs, m.regs);
1571        assert_eq!(other.bank_select, m.bank_select);
1572        assert_eq!(other.irq_reload_value, m.irq_reload_value);
1573        assert_eq!(other.irq_enabled, m.irq_enabled);
1574    }
1575
1576    // T-ORACLE-001 (v2.9.9): a clocking A12 rise raises the IRQ line at the
1577    // first `notify_cpu_cycle` after it, whatever `sub_dot` the rise carries.
1578    // Which CPU cycle that is comes from the bus's order (see the field doc on
1579    // `irq_assert_pending_next_cycle`): the next test pins both cases.
1580
1581    #[test]
1582    fn irq_becomes_visible_one_cpu_cycle_after_the_rise() {
1583        for sub_dot in [0u8, 2] {
1584            let mut m = fresh(8, 8);
1585            m.cpu_write(0xC000, 1);
1586            m.cpu_write(0xC001, 0);
1587            m.cpu_write(0xE001, 0);
1588            a12_rise(&mut m); // reload to 1, silent
1589            m.notify_a12_at_sub_dot(false, 0);
1590            for _ in 0..4 {
1591                m.notify_cpu_cycle();
1592            }
1593            m.notify_a12_at_sub_dot(true, sub_dot);
1594            assert_eq!(m.irq_counter, 0, "the counter itself moves at the rise");
1595            assert!(
1596                !m.irq_pending(),
1597                "sub_dot {sub_dot}: not visible in the rise's own cycle"
1598            );
1599            m.notify_cpu_cycle();
1600            assert!(
1601                m.irq_pending(),
1602                "sub_dot {sub_dot}: visible from the next cycle"
1603            );
1604        }
1605    }
1606
1607    /// #583 review (Copilot): the deferral is "until the next per-cycle
1608    /// hook", not "one cycle for every rise". In the bus, `start_cycle` runs
1609    /// the pre-access PPU catch-up and then the hook, so a rise caught up
1610    /// there is followed by its own cycle's hook and raised in that cycle,
1611    /// while a rise caught up after the access waits for the next cycle's.
1612    /// Both orders, as the mapper sees them.
1613    #[test]
1614    fn irq_line_is_raised_at_the_first_cpu_cycle_hook_after_the_rise() {
1615        let armed = || {
1616            let mut m = fresh(8, 8);
1617            m.cpu_write(0xC000, 1);
1618            m.cpu_write(0xC001, 0);
1619            m.cpu_write(0xE001, 0);
1620            a12_rise(&mut m); // reload to 1, silent
1621            m.notify_a12(false);
1622            for _ in 0..4 {
1623                m.notify_cpu_cycle();
1624            }
1625            m
1626        };
1627        // Pre-access: the rise, then this cycle's hook in `cpu_clock`.
1628        let mut pre = armed();
1629        pre.notify_a12(true);
1630        pre.notify_cpu_cycle();
1631        assert!(
1632            pre.irq_pending(),
1633            "a pre-access rise is raised in its own cycle"
1634        );
1635        // Post-access: this cycle's hook already ran; the rise comes after it
1636        // and is raised only by the next cycle's.
1637        let mut post = armed();
1638        post.notify_cpu_cycle();
1639        post.notify_a12(true);
1640        assert!(
1641            !post.irq_pending(),
1642            "a post-access rise is not raised in its own cycle"
1643        );
1644        post.notify_cpu_cycle();
1645        assert!(post.irq_pending(), "it is raised from the next cycle on");
1646    }
1647
1648    #[test]
1649    fn e000_ack_cancels_an_assertion_in_flight() {
1650        let mut m = fresh(8, 8);
1651        m.cpu_write(0xC000, 1);
1652        m.cpu_write(0xC001, 0);
1653        m.cpu_write(0xE001, 0);
1654        a12_rise(&mut m);
1655        m.notify_a12(false);
1656        for _ in 0..4 {
1657            m.notify_cpu_cycle();
1658        }
1659        m.notify_a12(true); // assertion queued
1660        assert!(!m.irq_pending());
1661        m.cpu_write(0xE000, 0); // ack/disable before it lands
1662        m.notify_cpu_cycle();
1663        assert!(
1664            !m.irq_pending(),
1665            "an ack write must cancel an in-flight assertion"
1666        );
1667    }
1668
1669    #[test]
1670    fn assertion_in_flight_survives_a_save_state_round_trip() {
1671        let mut m = fresh(8, 8);
1672        m.cpu_write(0xC000, 1);
1673        m.cpu_write(0xC001, 0);
1674        m.cpu_write(0xE001, 0);
1675        a12_rise(&mut m);
1676        m.notify_a12(false);
1677        for _ in 0..4 {
1678            m.notify_cpu_cycle();
1679        }
1680        m.notify_a12(true);
1681        let state = m.save_state();
1682        let mut other = fresh(8, 8);
1683        other.load_state(&state).unwrap();
1684        assert!(!other.irq_pending());
1685        other.notify_cpu_cycle();
1686        assert!(other.irq_pending(), "the queued assertion was restored");
1687    }
1688
1689    // ---- v2.9.6: the NES 2.0 submapper variants --------------------------
1690
1691    fn variant(v: Mmc3Variant) -> Mmc3 {
1692        Mmc3::new(
1693            synth_prg(16),
1694            synth_chr(64),
1695            Mirroring::Vertical,
1696            0,
1697            Mmc3Revision::Sharp,
1698        )
1699        .unwrap()
1700        .with_variant(v)
1701    }
1702
1703    /// `MMC6.md`: RAM at `$7000` only, off until `$8000` bit 5, then per
1704    /// half read/write enables; `$6000-$6FFF` floats.
1705    #[test]
1706    fn mmc6_prg_ram_follows_its_own_protect_scheme() {
1707        let mut m = variant(Mmc3Variant::Mmc6);
1708        assert_eq!(m.sram().len(), MMC6_RAM, "1 KiB of internal RAM");
1709        assert!(m.cpu_read_unmapped(0x6000));
1710        assert!(m.cpu_read_unmapped(0x7000), "RAM disabled at power-on");
1711        m.cpu_write(0xA001, 0xF0);
1712        assert!(
1713            m.cpu_read_unmapped(0x7000),
1714            "$A001 ignored while $8000.5 = 0"
1715        );
1716        m.cpu_write(0x8000, 0x20);
1717        m.cpu_write(0xA001, 0x30); // low half: read + write
1718        m.cpu_write(0x7001, 0x11);
1719        m.cpu_write(0x7201, 0x22); // high half not writable
1720        assert_eq!(m.cpu_read(0x7001), 0x11);
1721        assert_eq!(m.cpu_read(0x7201), 0x00, "the unreadable half reads zero");
1722        assert_eq!(m.cpu_read(0x7401), 0x11, "mirrored every 1 KiB");
1723        m.cpu_write(0xA001, 0x80); // high half readable only
1724        m.cpu_write(0x7201, 0x33);
1725        assert_eq!(m.cpu_read(0x7201), 0x00, "readable but not writable");
1726        m.cpu_write(0xA001, 0x20); // low half read-only
1727        m.cpu_write(0x7001, 0x44);
1728        assert_eq!(m.cpu_read(0x7001), 0x11);
1729        m.cpu_write(0x8000, 0x00); // disable: $A001 forced to $00
1730        m.cpu_write(0x8000, 0x20);
1731        assert!(m.cpu_read_unmapped(0x7000), "the protect bits were cleared");
1732    }
1733
1734    #[test]
1735    fn hardwired_board_ignores_a000() {
1736        let mut m = variant(Mmc3Variant::HardwiredMirroring);
1737        m.cpu_write(0xA000, 1);
1738        assert_eq!(m.current_mirroring(), Mirroring::Vertical);
1739        let mut s = variant(Mmc3Variant::Standard);
1740        s.cpu_write(0xA000, 1);
1741        assert_eq!(s.current_mirroring(), Mirroring::Horizontal);
1742    }
1743
1744    /// MC-ACC: falling A12 edges, /8, first edge of each group, `$C001`
1745    /// resets the prescaler.
1746    #[test]
1747    fn mc_acc_counts_falling_edges_through_a_prescaler() {
1748        let mut m = variant(Mmc3Variant::McAcc);
1749        m.cpu_write(0xC000, 1);
1750        m.cpu_write(0xC001, 0);
1751        m.cpu_write(0xE001, 0);
1752        let fall = |m: &mut Mmc3| {
1753            m.notify_a12(true);
1754            m.notify_a12(false);
1755        };
1756        fall(&mut m); // edge 0 of group 0: reload to 1
1757        assert!(!m.irq_pending());
1758        for _ in 0..7 {
1759            fall(&mut m); // edges 1-7: no clock
1760        }
1761        assert!(!m.irq_pending());
1762        fall(&mut m); // edge 0 of group 1: 1 -> 0, IRQ
1763        assert!(m.irq_pending());
1764        // `$C001` resets the prescaler. Four edges into a group (latch 1,
1765        // reloaded on edge 0), a `$C001` makes the next edge a group start:
1766        // it reloads, and the 9th edge after the write decrements 1 -> 0.
1767        // Without the reset the group would restart only at the 5th edge and
1768        // the IRQ would come at the 13th.
1769        let mut p = variant(Mmc3Variant::McAcc);
1770        p.cpu_write(0xC000, 1);
1771        p.cpu_write(0xE001, 0);
1772        for _ in 0..4 {
1773            fall(&mut p);
1774        }
1775        p.cpu_write(0xC001, 0);
1776        for _ in 0..8 {
1777            fall(&mut p);
1778        }
1779        assert!(!p.irq_pending());
1780        fall(&mut p);
1781        assert!(p.irq_pending(), "the 9th edge after $C001");
1782        // Rising edges alone never clock it.
1783        let mut r = variant(Mmc3Variant::McAcc);
1784        r.cpu_write(0xC000, 0);
1785        r.cpu_write(0xE001, 0);
1786        for _ in 0..16 {
1787            r.notify_a12(true);
1788            r.notify_cpu_cycle();
1789            r.notify_cpu_cycle();
1790            r.notify_cpu_cycle();
1791            r.notify_cpu_cycle();
1792        }
1793        assert!(!r.irq_pending());
1794    }
1795
1796    #[test]
1797    fn variant_state_round_trips_and_v2_is_refused() {
1798        let mut a = variant(Mmc3Variant::Mmc6);
1799        a.cpu_write(0x8000, 0x20);
1800        a.cpu_write(0xA001, 0x30);
1801        a.cpu_write(0x7003, 0x99);
1802        let blob = a.save_state();
1803        let mut b = variant(Mmc3Variant::Mmc6);
1804        b.load_state(&blob).unwrap();
1805        assert_eq!(b.cpu_read(0x7003), 0x99);
1806        assert_eq!(b.save_state(), blob);
1807        // A v2 blob (no tail) is refused since v2.9.8 (ADR 0042); it used to
1808        // load with the MMC6 / MC-ACC state at defaults.
1809        let std_blob = variant(Mmc3Variant::Standard).save_state();
1810        let mut v2 = std_blob;
1811        v2[0] = 2;
1812        v2.truncate(v2.len() - 3);
1813        assert!(matches!(
1814            variant(Mmc3Variant::Standard).load_state(&v2),
1815            Err(MapperError::UnsupportedVersion(2))
1816        ));
1817    }
1818}