Skip to main content

rustynes_cpu/
cpu.rs

1// SPDX-License-Identifier: GPL-3.0-or-later
2//
3// Provenance: the 6502/2A03 core is RustyNES's own, but the unstable-store opcode group (SHA/SHX/SHY/SHS/TAS — the `SyaSxaAxa` family) is derived from Mesen2 (GPL-3.0-or-later), `Core/NES/NesCpu.h`. See docs/originality-and-provenance.md (Section 1)
4// and NOTICE for the complete, audited derivation record.
5//! Ricoh 2A03 CPU (6502 derivative without BCD mode).
6//!
7//! See `docs/cpu-6502.md` for the spec. The implementation here matches:
8//!
9//! - all 151 documented 6502 opcodes,
10//! - all 105 unofficial / illegal opcodes that real software depends on,
11//! - the 12 JAM / KIL / STP halt opcodes,
12//! - cycle counts including page-crossing penalties on indexed reads, the
13//!   `+1 if branch taken / +2 if branch crosses page` branch convention, and
14//!   the dummy-read / dummy-write cycles of read-modify-write opcodes,
15//! - NMI (edge), IRQ (level), and BRK with the documented BRK/IRQ B-flag
16//!   distinction,
17//! - the `JMP ($XXFF)` indirect page-bug.
18//!
19//! The CPU steps one *instruction* at a time, returning the cycle count. The
20//! `Bus::on_cpu_cycle` callback is invoked once per consumed cycle so the
21//! scheduler / test harness can advance the PPU and count cycles. This is
22//! sufficient for nestest, blargg `instr_test_v5`, `cpu_timing_test`, and
23//! `branch_timing_tests` — the Phase-2 lockstep `tick()` is layered on top in
24//! a later sprint without changing this stepping interface.
25
26// Truncating casts are intentional throughout: this module is byte-arithmetic
27// against the 6502's 8/16-bit register file. `as u8` / `as i8` is the
28// canonical encoding of the wrap behavior the hardware exhibits.
29#![allow(
30    clippy::cast_possible_truncation,
31    clippy::cast_lossless,
32    clippy::cast_possible_wrap,
33    clippy::cast_sign_loss
34)]
35
36use crate::bus::Bus;
37// `M2Phase` is used only by the legacy lockstep interrupt-sampling paths
38// (idle_tick / read1 / write1), which are gated off under the R1 substrate.
39use crate::status::Status;
40
41/// Stack base address: the CPU stack lives at `$0100 + S`.
42const STACK_BASE: u16 = 0x0100;
43
44// v2.0 master-clock R1 substrate constants (Phase 2; `mc-r1-substrate`).
45// The PPU sub-cycle offset and the read/write master-clock split (`pre` in
46// start_cycle, `post` in end_cycle). Mesen `_ppuOffset=1` /
47// `_startClockCount`/`_endClockCount`. Master clocks per CPU cycle are NOT a
48// constant — they are the cartridge region's `cpu_divider` (NTSC 12 / PAL 16 /
49// Dendy 15), read from `bus.cpu_divider()` and fed to `read_split`/
50// `write_split`; the master-clock unit is shared with the bus's `run_ppu_to`,
51// which does the regioned dot conversion off `ppu_divider`.
52/// PPU sub-cycle offset: the PPU is run to `master_clock - PPU_OFFSET` in BOTH
53/// halves of every access (the double catch-up). Mesen `_ppuOffset = 1`.
54const PPU_OFFSET: u64 = 1;
55
56/// The effective PPU-sample offset for `run_ppu_to` (no BP sweep: the constant).
57#[inline]
58const fn ppu_sample_offset() -> u64 {
59    PPU_OFFSET
60}
61/// READ access master-clock split (`+= pre` in `start_cycle`, `+= post` in
62/// `end_cycle`); `pre + post = div`, the region's `cpu_divider`. Derived per
63/// region from `bus.cpu_divider()` so PAL (16) / Dendy (15) get the right
64/// CPU<->PPU phase; for the NTSC divisor 12 these are exactly (5, 7), so the
65/// NTSC path is byte-identical to the prior `const`s.
66#[inline]
67const fn read_split(div: u64) -> (u64, u64) {
68    let pre = div / 2 - PPU_OFFSET;
69    (pre, div - pre)
70}
71/// WRITE access split — swapped (writes commit `2 * PPU_OFFSET` mc later than
72/// reads). NTSC divisor 12 → (7, 5), byte-identical to the prior `const`s.
73#[inline]
74const fn write_split(div: u64) -> (u64, u64) {
75    let pre = div / 2 + PPU_OFFSET;
76    (pre, div - pre)
77}
78
79/// NMI vector low byte address (`$FFFA/B`).
80const NMI_VECTOR: u16 = 0xFFFA;
81
82/// Reset vector low byte address (`$FFFC/D`).
83const RESET_VECTOR: u16 = 0xFFFC;
84
85/// IRQ / BRK vector low byte address (`$FFFE/F`).
86const IRQ_VECTOR: u16 = 0xFFFE;
87
88/// 6502 CPU core.
89//
90// Multiple boolean state bits track distinct interrupt-pipeline stages
91// (jam, NMI pending, NMI armed, IRQ pending, IRQ armed).  These map directly
92// onto orthogonal hardware latches; collapsing them into an enum or bitflags
93// would obscure rather than clarify the model.
94#[allow(clippy::struct_excessive_bools)]
95#[derive(Debug, Clone)]
96pub struct Cpu {
97    /// Accumulator.
98    pub a: u8,
99    /// X index register.
100    pub x: u8,
101    /// Y index register.
102    pub y: u8,
103    /// Program counter.
104    pub pc: u16,
105    /// Stack pointer (low byte; effective address `0x0100 | s`).
106    pub s: u8,
107    /// Processor status.
108    pub p: Status,
109    /// Cumulative CPU cycle count.
110    pub cycles: u64,
111    /// `true` when the CPU has executed a JAM/KIL/STP and is waiting for reset.
112    pub jammed: bool,
113    /// Edge-detected NMI latch.  Real hardware samples the NMI line at the
114    /// second-to-last cycle of every instruction; if asserted, the NMI
115    /// sequence is queued for AFTER the current instruction completes.  Our
116    /// `step` model dispatches all bus operations atomically before ticking
117    /// cycles, so a write that itself raises NMI (e.g.  enabling NMI in
118    /// `$2000` while VBL is set) is observed by the bus's edge detector
119    /// during this instruction's cycle tally.  Hardware would not have
120    /// observed it at the second-to-last cycle (the write logically happens
121    /// at the LAST cycle), so the NMI is queued for the NEXT instruction's
122    /// sample point and serviced AFTER the next instruction completes.
123    /// We approximate that by promoting `pending_nmi` to `armed_nmi` after
124    /// each instruction; only `armed_nmi` actually services.
125    pub(crate) pending_nmi: bool,
126    /// NMI ready to be serviced before the next instruction starts.
127    pub(crate) armed_nmi: bool,
128    /// IRQ pending — captured edge of the level line that fires once the
129    /// I-flag is clear.  Same double-latch promotion as NMI.
130    pub(crate) pending_irq: bool,
131    /// IRQ ready to be serviced before the next instruction starts.
132    pub(crate) armed_irq: bool,
133    /// First tick (within the current instruction's tally loop) at which
134    /// NMI was sampled high; `u8::MAX` if not seen.  Used by the
135    /// second-to-last-cycle interrupt classification.
136    pub(crate) nmi_first_tick: u8,
137    /// First tick at which IRQ was sampled high; `u8::MAX` if not seen.
138    pub(crate) irq_first_tick: u8,
139    /// Snapshot of the I flag at the *start* of the current instruction.
140    /// Hardware samples IRQ near the end of the instruction with the old
141    /// I-flag value; CLI / SEI / PLP / RTI take effect at the very last
142    /// cycle, AFTER the IRQ sample point.  We arm IRQ only when this
143    /// snapshot says I was clear at sample time.
144    pub(crate) irq_sample_i_flag: bool,
145    /// Number of cycles emitted by the per-cycle helpers
146    /// (`read1`/`write1`/`idle_tick`) within the *current* instruction.
147    /// Reset to zero at the top of `step()`. Used by the trailing
148    /// "burn remaining cycles" loop so we can incrementally migrate
149    /// opcodes from atomic-dispatch + trailing-loop to fully per-cycle
150    /// emission.
151    pub(crate) cycles_emitted: u8,
152    /// When `true`, [`Cpu::idle_tick`] does NOT update `irq_first_tick`.
153    /// Used by branch opcodes to model the `branch_delays_irq` quirk:
154    /// real 6502 branches poll IRQ at the same point a 2-cycle untaken
155    /// branch would (the opcode-fetch cycle, which `step()` performs
156    /// before entering dispatch).  The operand fetch and any extra
157    /// taken / page-cross cycles do *not* re-sample IRQ.  The branch
158    /// dispatch sets this flag *before* the operand fetch and `step()`
159    /// clears it at the top of every instruction.
160    /// NMI sampling is unaffected — the quirk is IRQ-only.
161    pub(crate) skip_irq_sample: bool,
162
163    // === v2.0 master-clock R1 substrate (Phase 2; `mc-r1-substrate`) ===
164    /// The CPU's authoritative master clock (Mesen `_masterClock` / `TetaNES`
165    /// `Cpu::master_clock`). Advanced by `start_cycle`/`end_cycle`; the bus is
166    /// caught up to `master_clock - PPU_OFFSET` from BOTH halves (double
167    /// catch-up). Only live under `mc-r1-substrate`.
168    pub(crate) master_clock: u64,
169    /// NMI edge-recognition latch (set on a /NMI rising edge in
170    /// `handle_interrupts`; consumed by the cycle-5 hijack in
171    /// `service_interrupt`). Mesen `_needNmi`.
172    pub(crate) mc_need_nmi: bool,
173    /// One-cycle-delayed copy of `mc_need_nmi` (the dispatch + hijack gate).
174    /// Mesen `_prevNeedNmi`.
175    pub(crate) mc_prev_need_nmi: bool,
176    /// Live IRQ-recognition latch (`irq_level && !irq_sample_i_flag`,
177    /// recomputed every `end_cycle`). Mesen `_runIrq`.
178    pub(crate) mc_run_irq: bool,
179    /// One-cycle-delayed copy of `mc_run_irq` (the dispatch gate). Mesen
180    /// `_prevRunIrq`.
181    pub(crate) mc_prev_run_irq: bool,
182    /// Previous /NMI line level, for the φ2 rising-edge detector in
183    /// `handle_interrupts`. Mesen `_prevNmiFlag`.
184    pub(crate) mc_prev_nmi_line: bool,
185    /// v2.0.0 beta.2 (A2 scoping diagnostic): per-opcode count of cycles the
186    /// trailing burn-loop had to fill (`cycles - cycles_emitted`) — the exact
187    /// remaining busless-cycle surface the every-cycle-bus-access conversion
188    /// must turn into dummy reads of the held address. Read by the harness
189    /// `burn_probe` bin; never consulted by emulation.
190    #[cfg(feature = "cpu-instr-cycle-trace")]
191    pub burn_histogram: [u64; 256],
192}
193
194impl Default for Cpu {
195    fn default() -> Self {
196        Self::new()
197    }
198}
199
200/// Effective address + page-crossed flag, returned by addressing-mode resolvers.
201#[derive(Clone, Copy)]
202struct Operand {
203    addr: u16,
204    page_crossed: bool,
205}
206
207impl Cpu {
208    /// New CPU in "post-reset" state. Caller must invoke [`Cpu::reset`] with a
209    /// real bus before stepping (PC is undefined until reset reads `$FFFC/D`).
210    ///
211    /// This constructor is the convenience entry-point used by unit tests and
212    /// nestest fixtures that drive the CPU without going through a full
213    /// power-on path: `S=$FD`, `P=$24` (`UNUSED` + `INTERRUPT_DISABLE`). If the
214    /// caller subsequently invokes [`Cpu::reset`] the stack pointer will be
215    /// decremented by 3 (per the reset sequence), landing on `$FA` — that is
216    /// the input shape several `tests/opcodes.rs` fixtures expect.
217    ///
218    /// **For the real cold-boot path** (`Nes::from_rom`, `Nes::power_cycle`),
219    /// use [`Cpu::power_on`] instead, which seeds `S=$00`. After the 3-decrement
220    /// reset sequence that lands `S=$FD`, matching Mesen2's power-up state.
221    /// See `docs/audit/session-13-cpu-boot-fix-2026-05-21.md` for the reference
222    /// behaviour from `Core/NES/NesCpu.cpp::NesCpu::Reset(softReset=false)`.
223    #[must_use]
224    pub const fn new() -> Self {
225        Self {
226            a: 0,
227            x: 0,
228            y: 0,
229            pc: 0,
230            s: 0xFD,
231            p: Status::power_on(),
232            cycles: 0,
233            jammed: false,
234            pending_nmi: false,
235            armed_nmi: false,
236            pending_irq: false,
237            armed_irq: false,
238            nmi_first_tick: u8::MAX,
239            irq_first_tick: u8::MAX,
240            irq_sample_i_flag: true,
241            cycles_emitted: 0,
242            skip_irq_sample: false,
243            master_clock: 0,
244            mc_need_nmi: false,
245            mc_prev_need_nmi: false,
246            mc_run_irq: false,
247            mc_prev_run_irq: false,
248            mc_prev_nmi_line: false,
249            #[cfg(feature = "cpu-instr-cycle-trace")]
250            burn_histogram: [0; 256],
251        }
252    }
253
254    /// New CPU in real-hardware cold-boot state (`S=$00`).
255    ///
256    /// Real silicon comes up with the stack pointer in an undefined state;
257    /// the convention used by Mesen2 (and adopted here for trace parity) is
258    /// to treat power-up as `S=$00` and rely on the reset sequence's three
259    /// "phantom" decrements to wrap into `S=$FD`. See Mesen2
260    /// `Core/NES/NesCpu.cpp::Reset(softReset=false)`:
261    ///
262    /// ```cpp
263    /// if(softReset) {
264    ///     _state.SP -= 0x03;          // soft reset path
265    /// } else {
266    ///     _state.SP = 0xFD;           // power-up: direct assignment
267    /// }
268    /// ```
269    ///
270    /// `RustyNES` models that two-path behaviour by gating the SP delta through
271    /// the constructor: `Cpu::power_on() + reset()` ⇒ `$00 - 3 = $FD` (cold);
272    /// `cpu.reset()` again ⇒ `$FD - 3 = $FA` (subsequent soft reset).
273    ///
274    /// `P` is left at `$24` (`INTERRUPT_DISABLE` | `UNUSED`). Mesen2's trace
275    /// surface shows `P = $04` because it masks `UNUSED` out of the displayed
276    /// byte, but the bit is conventionally always set on a 6502 internally
277    /// (nesdev: "Bit 5: Always 1, the so-called 'unused' bit"); the trace
278    /// divergence on P is cosmetic.
279    #[must_use]
280    pub const fn power_on() -> Self {
281        let mut cpu = Self::new();
282        cpu.s = 0x00;
283        cpu
284    }
285
286    /// Returns `true` when the CPU has executed a JAM/KIL/STP.
287    #[must_use]
288    pub const fn is_jammed(&self) -> bool {
289        self.jammed
290    }
291
292    /// Read-only accessor for the CPU's authoritative master clock
293    /// (v2.0.0-beta.1 one-clock instrumentation).
294    ///
295    /// `master_clock` counts master-clock units (NTSC: 12 per CPU cycle,
296    /// PAL: 16, Dendy: 15) and is advanced only by `start_cycle` /
297    /// `end_cycle` (the asymmetric read 5/7 vs write 7/5 φ1/φ2 split on
298    /// NTSC) plus the bus-side DMA coherence fold
299    /// (`Bus::take_dma_mc_consumed`). It is the counter the v2.0.0
300    /// "Timebase" rewrite (ADR 0002) promotes to the ONE canonical
301    /// timebase; the test harness asserts the affine relation
302    /// `master_clock == seed + cpu_divider * cycles` against the other
303    /// cycle counters (`one_clock_invariants.rs`) as the gate for the
304    /// beta.1 counter collapse.
305    #[must_use]
306    pub const fn master_clock(&self) -> u64 {
307        self.master_clock
308    }
309
310    /// Reset (warm boot).
311    ///
312    /// Real hardware: 8-cycle sequence with suppressed pushes, then PC loads
313    /// from the reset vector and the I flag is set. We model the cycle count
314    /// (advances `cycles` by 8 and fires `on_cpu_cycle` 8 times) without
315    /// mutating registers other than P (set I), S (decrement by 3), and PC.
316    ///
317    /// Matches Mesen2's `NesCpu::Reset()` 8-cycle post-power-up loop ("CPU
318    /// takes 8 cycles before it starts executing the ROM's code"). Combined
319    /// with the PPU power-up at (scanline=-1, dot=340) (see `Ppu::new`),
320    /// this closes the +344-dot PPU offset identified empirically in
321    /// Session-13 (docs/audit/session-13-cpu-boot-fix-2026-05-21.md).
322    pub fn reset<B: Bus>(&mut self, bus: &mut B) {
323        // Real hardware decrements S three times during reset (no actual
324        // pushes occur, but the decrements happen).
325        self.s = self.s.wrapping_sub(3);
326        self.p.insert(Status::INTERRUPT_DISABLE);
327        self.jammed = false;
328        self.pending_nmi = false;
329        self.armed_nmi = false;
330        self.pending_irq = false;
331        self.armed_irq = false;
332        self.nmi_first_tick = u8::MAX;
333        self.irq_first_tick = u8::MAX;
334        // R1/R3 cold-boot: advance master_clock by one CPU divider BEFORE the
335        // 8-cycle reset loop (Mesen `NesCpu::Reset()` `_masterClock += cpuDivider
336        // + cpuOffset`). Without it the first start_cycle's `run_ppu_to(mc-1)`
337        // would leave the PPU 3 dots behind. See R1 port plan / branch `acddd22`.
338        {
339            self.master_clock = self.master_clock.wrapping_add(bus.cpu_divider());
340        }
341        // V-axis: Mesen adds `cpuDivider + cpuOffset` (13), not just cpuDivider
342        // (12). The +PPU_OFFSET corrects R1's power-up CPU/PPU sub-cycle
343        // alignment to the reference, shifting every $2002 poll-exit to match
344        // hardware (nesdev `PPU_frame_timing`: the read sees the flag change iff
345        // it starts at/after the set tick). Default-off; A/B against Y/C1/6-10.
346        // 8-cycle reset sequence: 6 idle/internal cycles + 2 vector reads.
347        self.cycles_emitted = 0;
348        for _ in 0..6 {
349            self.idle_tick(bus);
350        }
351        let lo = self.read1(bus, RESET_VECTOR);
352        let hi = self.read1(bus, RESET_VECTOR + 1);
353        self.pc = u16::from(lo) | (u16::from(hi) << 8);
354    }
355
356    /// Force PC to `addr`. Used by the nestest harness which enters at
357    /// `$C000` rather than the reset vector.
358    pub const fn set_pc(&mut self, addr: u16) {
359        self.pc = addr;
360    }
361
362    /// Step one instruction (or service an interrupt). Returns the number of
363    /// CPU cycles consumed.
364    ///
365    /// On a JAM-state CPU this is a no-op returning 0.
366    ///
367    /// # Interrupt timing model
368    ///
369    /// Real 6502 hardware samples the NMI / IRQ lines at the *second-to-last*
370    /// cycle of every instruction and, if asserted there, queues the
371    /// interrupt to be serviced *after* the current instruction completes.
372    /// Our model dispatches all bus operations atomically before ticking
373    /// cycles, so a write that itself raises NMI (e.g. `STA $2000` enabling
374    /// NMI while VBL is set) appears to the bus's edge detector during the
375    /// FIRST cycle of the tally loop — earlier than hardware would observe
376    /// it.  Hardware places the actual write at the *last* cycle of the
377    /// instruction, so the second-to-last sample point would NOT see the new
378    /// line state; only the NEXT instruction's sample sees it.  We model
379    /// that by introducing a one-instruction promotion delay: edges captured
380    /// at end-of-step land in `pending_*` and, after the following step,
381    /// promote to `armed_*` which is the gate that actually triggers
382    /// service.  This passes `04-nmi_control` test 11 ("Immediate occurence
383    /// should be after NEXT instruction") without regressing the
384    /// instruction-count-insensitive tests like `02-vbl_set_time`,
385    /// `09-even_odd_frames`, or any `instr_test_v5` ROM (which never raise
386    /// NMI from within a single instruction).
387    #[allow(clippy::too_many_lines, clippy::missing_panics_doc)]
388    pub fn step<B: Bus>(&mut self, bus: &mut B) -> u8 {
389        if self.jammed {
390            return 0;
391        }
392        // v2.0 master-clock R1 UNIFIED interrupt dispatch (`mc-r1-substrate`):
393        // a SINGLE service sequence gated on the one-cycle-delayed `prev_*`
394        // copies (Mesen `_prevRunIrq || _prevNeedNmi`). The vector is chosen
395        // INSIDE `service_interrupt` by the live `mc_need_nmi` at cycle 5 (the
396        // NMI hijack); NMI priority is resolved there, not here. Setting
397        // `irq_sample_i_flag = true` BEFORE the service masks the φ2 sampler
398        // for the 7-cycle sequence (no re-entry). Clears `skip_irq_sample`
399        // (a prior taken-branch could have left it set, freezing the recompute)
400        // and defers any still-pending NMI by one instruction.
401        if self.mc_prev_run_irq || self.mc_prev_need_nmi {
402            self.armed_irq = false;
403            self.irq_sample_i_flag = true;
404            self.skip_irq_sample = false;
405            self.service_interrupt(bus, IRQ_VECTOR, false);
406            self.mc_prev_need_nmi = false;
407            self.promote_post_step_interrupts(7);
408            return 7;
409        }
410        // Service an armed interrupt before the next instruction.  NMI has
411        // priority over IRQ; both are mutually exclusive for a single
412        // service window.
413        // Once armed, the IRQ services unconditionally — the I-flag
414        // gating already happened at the sample point (second-to-last
415        // cycle of the prior instruction).  This is what produces the
416        // "CLI SEI should still allow one IRQ to fire" behavior:
417        // SEI's I=1 takes effect at end-of-SEI but the sample at SEI's
418        // second-to-last cycle saw I=0 (CLI cleared it) and queued the
419        // IRQ, which now fires regardless of the current I-flag.
420
421        // Per-instruction state for the per-cycle helpers
422        // (`read1`/`write1`/`idle_tick`).  These track the FIRST tick at
423        // which each interrupt line was seen high; hardware samples at the
424        // second-to-last cycle so seen < last_tick = arm now;
425        // seen == last_tick = defer one instruction (the next instruction's
426        // sample window catches it instead).
427        self.nmi_first_tick = u8::MAX;
428        self.irq_first_tick = u8::MAX;
429        self.cycles_emitted = 0;
430        // Cleared every instruction; set inside the branch dispatch arms
431        // (after the operand fetch / canonical IRQ poll) to suppress
432        // further IRQ sampling on the additional taken / page-cross
433        // branch cycles.
434        self.skip_irq_sample = false;
435        // Snapshot the I flag for this instruction.  CLI / SEI / PLP /
436        // RTI mutate `self.p` *during* the instruction, but the hardware
437        // IRQ sample reads the I value as it was at the start.  This is
438        // what produces the documented "exactly one instruction after
439        // CLI executes before IRQ is taken" delay.
440        self.irq_sample_i_flag = self.p.contains(Status::INTERRUPT_DISABLE);
441
442        #[cfg(feature = "cpu-instr-cycle-trace")]
443        bus.trace_instr(self.pc, self.cycles);
444
445        let opcode = self.fetch_pc(bus);
446        let mut cycles = 0u8;
447        self.dispatch(bus, opcode, &mut cycles);
448        // Burn whichever cycles the dispatch did NOT emit through helpers.
449        // As opcodes migrate to fully per-cycle emission, this loop runs
450        // for fewer iterations; eventually it can be removed entirely.
451        //
452        // v2.0.0 beta.2 (A2 scoping): the diagnostic histogram below records,
453        // per opcode, how many cycles the burn-loop had to fill — the exact
454        // empirical work list for the every-cycle-bus-access conversion (the
455        // remaining busless cycles that must become dummy reads of the held
456        // address). Default-off; the `burn_probe` harness bin prints it.
457        #[cfg(feature = "cpu-instr-cycle-trace")]
458        {
459            let burned = cycles.saturating_sub(self.cycles_emitted);
460            if burned > 0 {
461                self.burn_histogram[opcode as usize] =
462                    self.burn_histogram[opcode as usize].saturating_add(u64::from(burned));
463            }
464        }
465        // v2.0.0 beta.2 (A2, promoted to the only path in beta.4): every
466        // instruction cycle is a bus access — the resolvers + RMW arms emit
467        // the canonical dummy reads, so the burn-loop must never fire.
468        // Proven empirically at zero across AccuracyCoin, nestest, both
469        // blargg_nes_cpu_test5 suites, and cpu_timing_test6 (the full
470        // official + unofficial opcode space); this assert makes any future
471        // under-emitting dispatch arm fail loud in dev-profile runs instead
472        // of silently reintroducing a busless cycle.
473        debug_assert!(
474            self.cycles_emitted >= cycles,
475            "opcode ${opcode:02X} under-emitted: declared {cycles} cycles but emitted \
476             only {} — a busless burn-loop cycle would fill the gap (A2 regression; \
477             see the v2.0.0 plan Workstream A2)",
478            self.cycles_emitted
479        );
480        while self.cycles_emitted < cycles {
481            self.idle_tick(bus);
482        }
483        self.promote_post_step_interrupts(cycles);
484        cycles
485    }
486
487    /// Promote any per-instruction interrupt edges captured by the
488    /// per-cycle helpers into the `armed_*` / `pending_*` latches the
489    /// next [`Cpu::step`] consults.  Hardware samples interrupts at the
490    /// second-to-last cycle of an instruction; we approximate that with
491    /// "first sampled tick strictly before the last cycle = arm now,
492    /// else defer one instruction."
493    const fn promote_post_step_interrupts(&mut self, cycles: u8) {
494        // Promote any previously-pending interrupt (latched at the very
495        // last cycle of the prior instruction).
496        if self.pending_nmi {
497            self.armed_nmi = true;
498            self.pending_nmi = false;
499        }
500        if self.pending_irq {
501            self.armed_irq = true;
502            self.pending_irq = false;
503        }
504        let last_tick = cycles.saturating_sub(1);
505        if self.nmi_first_tick != u8::MAX {
506            if self.nmi_first_tick < last_tick {
507                self.armed_nmi = true;
508            } else {
509                self.pending_nmi = true;
510            }
511        }
512        if self.irq_first_tick != u8::MAX {
513            // IRQ is masked by the I-flag value as it was at the START of
514            // this instruction; CLI / SEI / PLP / RTI mutations take effect
515            // at end-of-instruction.  If IRQ was already disabled when we
516            // entered, the second-to-last-cycle sample sees I=1 and the
517            // edge is dropped (the next instruction's sample will pick it
518            // up if I has since cleared).
519            if !self.irq_sample_i_flag {
520                if self.irq_first_tick < last_tick {
521                    self.armed_irq = true;
522                } else {
523                    self.pending_irq = true;
524                }
525            }
526        }
527    }
528
529    fn fetch_pc<B: Bus>(&mut self, bus: &mut B) -> u8 {
530        let v = self.read1(bus, self.pc);
531        self.pc = self.pc.wrapping_add(1);
532        v
533    }
534
535    fn fetch_pc_u16<B: Bus>(&mut self, bus: &mut B) -> u16 {
536        let lo = self.fetch_pc(bus);
537        let hi = self.fetch_pc(bus);
538        u16::from(lo) | (u16::from(hi) << 8)
539    }
540
541    fn read_u16_with_wrap<B: Bus>(&mut self, bus: &mut B, addr: u16) -> u16 {
542        // Used by indirect modes to honor the 6502 page-wrap quirk.
543        let lo = self.read1(bus, addr);
544        let hi_addr = (addr & 0xFF00) | u16::from((addr as u8).wrapping_add(1));
545        let hi = self.read1(bus, hi_addr);
546        u16::from(lo) | (u16::from(hi) << 8)
547    }
548
549    fn push<B: Bus>(&mut self, bus: &mut B, value: u8) {
550        self.write1(bus, STACK_BASE | u16::from(self.s), value);
551        self.s = self.s.wrapping_sub(1);
552    }
553
554    fn pull<B: Bus>(&mut self, bus: &mut B) -> u8 {
555        self.s = self.s.wrapping_add(1);
556        self.read1(bus, STACK_BASE | u16::from(self.s))
557    }
558
559    fn push_u16<B: Bus>(&mut self, bus: &mut B, value: u16) {
560        self.push(bus, (value >> 8) as u8);
561        self.push(bus, (value & 0xFF) as u8);
562    }
563
564    fn pull_u16<B: Bus>(&mut self, bus: &mut B) -> u16 {
565        let lo = self.pull(bus);
566        let hi = self.pull(bus);
567        u16::from(lo) | (u16::from(hi) << 8)
568    }
569
570    // ------------------------------------------------------------------
571    // Per-cycle bus interleaving primitives.
572    //
573    // Real 6502 hardware reads or writes the bus *exactly once per CPU
574    // cycle*; "internal" cycles (ALU work, stack pointer increment, etc.)
575    // still tick the system clock without driving the address bus.
576    // `read1` / `write1` / `idle_tick` model that one-cycle granularity:
577    // each one ticks the bus exactly once and samples the NMI/IRQ lines
578    // at the end of the cycle, mirroring what the existing trailing
579    // tally loop in `step` did but with the polling now coupled to the
580    // *actual* memory access ordering.
581    //
582    // `step()` resets `cycles_emitted` to 0; each call here increments
583    // it.  The trailing burn-loop in `step` consumes whatever cycles
584    // the opcode declared but didn't emit through these helpers.
585    // ------------------------------------------------------------------
586
587    // === v2.0 master-clock R1 substrate core (Phase 2; `mc-r1-substrate`) ===
588
589    /// Start half of one CPU cycle: advance `master_clock` by the PRE split,
590    /// catch the PPU up to `master_clock - PPU_OFFSET`, then fire the bus's
591    /// per-cycle work (`cpu_clock`). After this the PPU is at the access's
592    /// exact master clock (so a `$2002`/`$2007` read sees on-time state).
593    fn start_cycle<B: Bus>(&mut self, bus: &mut B, for_read: bool) {
594        let div = bus.cpu_divider();
595        let pre = if for_read {
596            read_split(div).0
597        } else {
598            write_split(div).0
599        };
600        self.master_clock = self.master_clock.wrapping_add(pre);
601        bus.run_ppu_to(self.master_clock.saturating_sub(ppu_sample_offset()), false);
602        bus.cpu_clock();
603        // v2.0.0 beta.1 (A1 one-clock collapse, promoted to the only path in
604        // beta.4): `cycles` is ASSIGNED from the canonical bus cycle counter
605        // at this single per-cycle site instead of being independently
606        // incremented by every `read1`/`write1`/`idle_tick`/DMA-loop caller.
607        // `bus.cpu_clock()` above advanced the canonical counter for THIS
608        // cycle, so the assignment lands on the same post-increment value the
609        // legacy caller-side `+= 1` produced (the `one_clock_invariants`
610        // harness test pins the residue at zero).
611        self.cycles = bus.cycle_count();
612    }
613
614    /// End half of one CPU cycle: fold any bus-side DMA span into
615    /// `master_clock` (coherence — keeps the CPU<->PPU phase aligned across a
616    /// DMA), advance by the POST split, catch the PPU up again (the double
617    /// catch-up), then sample interrupts (φ2, the T_last-1 rule).
618    fn end_cycle<B: Bus>(&mut self, bus: &mut B, for_read: bool) {
619        // v2.0.0 beta.1 (A1 one-clock collapse, promoted to the only path in
620        // beta.4): the `dma_mc_consumed` coherence fold is RETIRED. On the
621        // live unified-DMA path every DMA cycle is a first-class
622        // `start_cycle`/`end_cycle` (advancing `master_clock` directly), so
623        // the bus-side accumulator is structurally zero — the fold only ever
624        // mattered for the legacy bus-side burst engine, which is dead code.
625        // The accumulator is drained UNCONDITIONALLY (identical dev/release
626        // behavior — clippy's `debug_assert_with_mut_call` rightly forbids
627        // the take inside the assertion) and the structural-zero claim is
628        // asserted in dev profiles; the byte-identity gate (AccuracyCoin
629        // 139/139 + nestest 0-diff) proves it for release.
630        let folded = bus.take_dma_mc_consumed();
631        debug_assert_eq!(
632            folded, 0,
633            "dma_mc_consumed accumulated on the live path — a legacy \
634             bus-side DMA cycle ran outside the unified engine (see the \
635             v2.0.0 plan A1)"
636        );
637        let _ = folded;
638        let div = bus.cpu_divider();
639        let post = if for_read {
640            read_split(div).1
641        } else {
642            write_split(div).1
643        };
644        self.master_clock = self.master_clock.wrapping_add(post);
645        bus.run_ppu_to(self.master_clock.saturating_sub(ppu_sample_offset()), true);
646        // F-2: tick the DMC byte-timer at END of cycle (after the access),
647        // matching main's DMC fire-phase for DMASync, BEFORE the φ2 interrupt
648        // sample so handle_interrupts sees the post-tick DMC IRQ line.
649        bus.cpu_clock_apu_dmc();
650        self.handle_interrupts(bus);
651        // Diagnostic trace hook (no-op unless the bus enables irq-timing-trace).
652        bus.trace_end_cycle();
653    }
654
655    /// φ2 interrupt sampler (Mesen `EndCpuCycle`): edge-detect /NMI into
656    /// `mc_need_nmi` (after copying the one-cycle-delayed `mc_prev_need_nmi`),
657    /// and recompute `mc_run_irq = irq_level && !irq_sample_i_flag` (after the
658    /// `mc_prev_run_irq` copy). The `step()`-top dispatch reads the `prev_*`
659    /// copies — i.e. second-to-last-cycle recognition. The I-mask uses the
660    /// start-of-instruction snapshot (`irq_sample_i_flag`), not live `self.p`,
661    /// so CLI/SEI/PLP delay their I-change one instruction.
662    #[allow(clippy::needless_pass_by_ref_mut)] // &mut B for signature parity
663    fn handle_interrupts<B: Bus>(&mut self, bus: &mut B) {
664        self.mc_prev_need_nmi = self.mc_need_nmi;
665        let nmi_level = bus.nmi_level();
666        if !self.mc_prev_nmi_line && nmi_level {
667            self.mc_need_nmi = true;
668        }
669        self.mc_prev_nmi_line = nmi_level;
670        self.mc_prev_run_irq = self.mc_run_irq;
671        // W1 (`mc-r1-branch-poll-points`): a taken branch polls IRQ ONCE —
672        // before C2 — so while `skip_irq_sample` is set (the branch dispatch
673        // arms set it after the C1 opcode fetch, before the C2 operand fetch)
674        // the recognition latch is FROZEN at its end-of-C1 value instead of
675        // recomputed from the live line every cycle. DMC-DMA halt cycles
676        // drained inside the branch's own `read1`/`idle_tick` therefore
677        // cannot make a freshly-asserted IRQ visible to THIS instruction
678        // (AccuracyCoin `Interrupt flag latency` Test A; TriCNES polls at
679        // C2-start only, plus a can-set poll at C4-start handled in
680        // `branch()`). The `mc_prev_run_irq` copy above still runs, so the
681        // held end-of-C1 value is what the next `step()` dispatch reads. NMI
682        // edge detection above is untouched (sampled every cycle, per
683        // hardware — the quirk is IRQ-only).
684        if self.skip_irq_sample {
685            return;
686        }
687        let irq_level = bus.irq_level();
688        self.mc_run_irq = irq_level && !self.irq_sample_i_flag;
689    }
690
691    /// Tick the bus once and sample interrupt lines, *without* a bus
692    /// access.  Models a 6502 internal cycle.
693    fn idle_tick<B: Bus>(&mut self, bus: &mut B) {
694        {
695            // F-2 re-coupling (`mc-r1-dmc-idle-halt`): a DMC DMA can halt the CPU
696            // on a 6502 INTERNAL cycle too — on hardware every cycle is a bus
697            // read, and Mesen's `ProcessPendingDma` runs on every `MemoryRead`
698            // (incl. dummy reads), NOT only instruction/operand reads. R1's
699            // `read1` loop only services on real reads, so the DMA waits through
700            // internal cycles (the `lat=4` idle-runs the per-fetch trace pinned
701            // as the period-jitter source). Service it here too, on the held
702            // (last-read) bus address. Default-off; the banked 6/10 path skips it.
703            // W3-Stage-1 (`mc-r1-dma-unified`): the unified-engine replacement
704            // for the idle DMC drain above — same loop shape, ONE engine. The
705            // bus supplies the held (last-read) address for the parked 6502
706            // address bus. Same budget accounting as the loop it replaces.
707            while bus.unified_dma_pending() {
708                self.cycles_emitted = self.cycles_emitted.saturating_add(1);
709                self.start_cycle(bus, true);
710                bus.unified_dma_cycle_idle();
711                self.end_cycle(bus, true);
712            }
713            // R1: a pure internal cycle — busless (idle_tick stays busless).
714            self.cycles_emitted = self.cycles_emitted.saturating_add(1);
715            self.start_cycle(bus, true);
716            self.end_cycle(bus, true);
717        }
718    }
719
720    /// Canonical cycle-2 PC dummy read for implied / accumulator /
721    /// transfer / flag instructions (per nesdev `6502_cpu.txt` + MOS
722    /// 6502 datasheet). Real silicon fetches the byte AFTER the opcode
723    /// during cycle 2 of these single-byte instructions and discards
724    /// it (the would-be operand). Without this dummy read, our emulator
725    /// instead "burns an idle cycle" via `idle_tick` for the second
726    /// cycle, which counts the cycle for time but produces no bus
727    /// access — diverging from real silicon's bus-access pattern.
728    ///
729    /// Wired into 22 dispatch arms (ASL/LSR/ROL/ROR A; CLC/SEC/CLI/SEI/
730    /// CLV/CLD/SED; TAX/TAY/TSX/TXA/TXS/TYA; INX/DEX/INY/DEY; NOP;
731    /// 6 unofficial 1-byte NOPs) under the `cpu-implied-dummy-reads`
732    /// cargo feature. Default-off pending the coordinated DMC scheduler
733    /// audit per `docs/audit/sprint-2.3-implied-dummy-dmc-recon-2026-05-25.md`
734    /// — Session-19 documented that this fix alone (Step 1+2 of the
735    /// recipe) cascades into `Implicit DMA Abort [error 2]`. Step 3
736    /// (DMC scheduler awareness of cycle-2 bus-active reads) is the
737    /// next-session attack.
738    ///
739    /// When the feature flag is OFF, this helper compiles to a no-op
740    /// (the `bus` parameter is silenced via `_ = bus`), and the
741    /// existing `*cycles = 2` + caller's idle-tick burn loop preserves
742    /// pre-Sprint-2.3 behavior byte-identically.
743    // `&mut self` + `&mut bus` are required for the feature-ON branch;
744    // when the feature is off the helper is a no-op (cfg-gated). The
745    // lint suppressions cover the OFF branch's "unused argument /
746    // could be const fn / inline(always) is suspicious" complaints.
747    #[inline(always)]
748    #[allow(
749        clippy::inline_always,
750        clippy::needless_pass_by_ref_mut,
751        clippy::unused_self,
752        clippy::missing_const_for_fn
753    )]
754    fn implied_dummy_read<B: Bus>(&mut self, bus: &mut B) {
755        {
756            let _ = self.read1(bus, self.pc);
757        }
758    }
759
760    /// Read a byte at `addr` *and* consume one CPU cycle (with bus tick
761    /// + interrupt sampling).
762    #[allow(clippy::too_many_lines)] // mc-r1 DMA-interleave arms push this past 100
763    fn read1<B: Bus>(&mut self, bus: &mut B, addr: u16) -> u8 {
764        {
765            // accuracycoin-100 Phase 2 (`mc-r1-dmc-abort-cancel`): a 1-byte
766            // non-looping implicit abort that matured during the prior APU tick
767            // is serviced HERE, before the (cancelled) reload could run. On a
768            // GET (read) cycle the abort is a 1-cycle DMA (one halt re-read) →
769            // CalculateDMADuration Y=1; on a PUT cycle it does NOT occur → Y=0
770            // ("the 1-cycle abort will not land on a write cycle"). Both clear
771            // the pending reload so the `dmc_dma_pending` loop below skips it.
772            if bus.dmc_abort_pending() {
773                if bus.dmc_abort_is_get_cycle() {
774                    self.cycles_emitted = self.cycles_emitted.saturating_add(1);
775                    self.start_cycle(bus, true);
776                    bus.dmc_abort_halt_step(addr);
777                    self.end_cycle(bus, true);
778                } else {
779                    bus.dmc_abort_cancel();
780                }
781            }
782            // W3-Stage-1 (`mc-r1-dma-unified`): ONE DMA loop replacing the
783            // three loops below (the standalone DMC drain, the sequential
784            // Stage-D OAM loop, and the Program-M overlap loop). Each
785            // iteration is one full R1 cycle (start_cycle -> the bus's
786            // unified TriCNES-dispatch cycle -> end_cycle), so every DMA
787            // cycle keeps the φ2 IRQ sample — the C1-safe shape. The
788            // engine itself (ONE driver for standalone DMC, standalone OAM,
789            // and the overlap) lives bus-side in `unified_dma_cycle`; the
790            // load-get-entry defer is folded into `unified_dma_pending`
791            // (pre-cycle, like the floor's while-gate) AND the engine's
792            // in-cycle entry gate (post-flip parity).
793            while bus.unified_dma_pending() {
794                // DMA halt cycles count against `cycles_emitted`.
795                self.cycles_emitted = self.cycles_emitted.saturating_add(1);
796                self.start_cycle(bus, true);
797                bus.unified_dma_cycle(addr);
798                self.end_cycle(bus, true);
799            }
800            // Phase B (interleaved DMC DMA): a DMC DMA halts the CPU only on a
801            // READ cycle (TriCNES `CPU_Read`). While one is pending, consume R1
802            // cycles ONE AT A TIME — each a full R1 cycle (PPU caught up,
803            // `tick_dmc` advances the DMC timer once = the span↔fire feedback,
804            // arm gated by `in_dmc_dma` = no cascade) — BEFORE the CPU's own
805            // read. `dmc_dma_step` re-reads `addr` on halt/align cycles and
806            // fetches the sample on the get cycle (`!put_cycle`).
807            // W3-Stage-0: when an OAM DMA can overlap this DMC
808            // (`oam_dma_overlap_ready` — under `mc-r1-counter-collapse` that
809            // includes a `$4014` write still pending its first cycle), do NOT
810            // drain the DMC standalone here: the combined overlap loop below
811            // services both engines as ONE shared-cycle event. Draining it here
812            // first pays a full unshared reload span = the DMC+OAM idx[7]
813            // regime-transition `03`. Without the overlap feature this bus query
814            // is the trait default (`oam_dma_in_flight` = `false` at read1 entry),
815            // so the floor path is unchanged by construction.
816            // W3-Stage-1: replaced by the unified engine loop above under
817            // `mc-r1-dma-unified` (cfg'd out, not deleted).
818            // Stage-D (`mc-r1-full-cpu`): OAM DMA runs CPU-driven, one cycle at a
819            // time through start_cycle/end_cycle (so each OAM cycle samples
820            // IRQ/NMI via the φ2 `_prev*` pipeline in end_cycle — the surface the
821            // bus burst bypassed and RW-2 regressed). A pending DMC DMA preempts
822            // (the DMC loop above already drained first = DMC-get-before-OAM-get).
823            //
824            // NOTE: this SEQUENTIAL nested form drains a mid-OAM DMC DMA fully
825            // before resuming OAM (no overlap), so the DMC+OAM test's `02/01`
826            // shared-cycle entries never appear. `mc-r1-dmc-oam-overlap` replaces
827            // it with the overlap model below.
828            // Program M (M-2, `mc-r1-dmc-oam-overlap`): the DMC-DMA-during-OAM-DMA
829            // overlap model. A single combined loop services BOTH engines per
830            // cycle: when a DMC DMA is pending while an OAM DMA is IN FLIGHT, the
831            // DMC halt/dummy/align cycles SHARE an OAM cycle (the 6502 is
832            // RDY-halted but the OAM engine keeps consuming its bus slot), and
833            // only the DMC GET steals an OAM slot. This is the per-cycle analogue
834            // of lockstep `service_dmc_dma_during_oam`, which produces the test's
835            // canonical `04,03,...,02,01` sweep (nesdev `DMA#DMC_DMA_during_OAM`).
836            // R1 clean access shape: start_cycle (PPU caught up to the
837            // access's exact mc + bus cpu_clock) → bus.read → end_cycle
838            // (double catch-up + φ2 interrupt sample). Mesen `MemoryRead`.
839            self.cycles_emitted = self.cycles_emitted.saturating_add(1);
840            self.start_cycle(bus, true);
841            let v = bus.read(addr);
842            self.end_cycle(bus, true);
843            v
844        }
845    }
846
847    /// Write `value` to `addr` *and* consume one CPU cycle (with bus
848    /// tick + interrupt sampling).
849    fn write1<B: Bus>(&mut self, bus: &mut B, addr: u16, value: u8) {
850        {
851            // accuracycoin-100 Phase 2: a CPU write cycle cannot be RDY-halted,
852            // so a 1-byte implicit abort matured before a write does NOT occur
853            // (Y=0). Cancel it with no halt cycle — this is the "will not land on
854            // a write cycle" half of the sweep that the read-only `read1` path
855            // can't reach.
856            if bus.dmc_abort_pending() {
857                bus.dmc_abort_cancel();
858            }
859            // R1 clean write shape (symmetric split — writes commit 2 mc
860            // later than reads). No interrupt sample latches here; end_cycle's
861            // handle_interrupts does the φ2 sample.
862            self.cycles_emitted = self.cycles_emitted.saturating_add(1);
863            self.start_cycle(bus, false);
864            bus.write(addr, value);
865            self.end_cycle(bus, false);
866        }
867    }
868
869    /// SH* unstable-store family helper (`SHA / SHX / SHY / SHS / TAS`,
870    /// opcodes `$9F / $93 / $9E / $9C / $9B`).
871    ///
872    /// Implements the 6502 unstable-store (SH*) algorithm — the
873    /// "unstable"/"highbyte" store opcodes (`value AND (high_byte + 1)`, with the
874    /// RDY/DMA quirk), pinned bit-for-bit by `AccuracyCoin`'s "Unofficial
875    /// Instructions: SH*" sub-test.
876    ///
877    /// Provenance: **derived from Mesen2's `SyaSxaAxa`** (`Core/NES/NesCpu.h`),
878    /// `GPL-3.0-or-later`. The `NESdev` community documents this behavior, but this
879    /// implementation was ported from Mesen2's — not written independently from
880    /// the documentation. The surrounding DMC-DMA interruption detection uses the
881    /// emulator's own bus cycle-count machinery. See NOTICE and
882    /// docs/originality-and-provenance.md (Section 1).
883    /// The algorithm:
884    ///
885    /// 1. Compute the page-crossed flag against `base + index_reg`.
886    /// 2. Perform a dummy read at the **unfixed** address
887    ///    (`base + index_reg - 0x100` if page-crossed, else
888    ///    `base + index_reg`).  This is the cycle DMC DMA can
889    ///    interrupt.
890    /// 3. Detect DMC-DMA interruption via `bus.cycle_count()`
891    ///    before/after the dummy read — if more than 1 bus cycle
892    ///    elapsed, a DMA fired.
893    /// 4. On page-cross, the address-high-byte is corrupted to
894    ///    `original_addr_high AND value_reg`.
895    /// 5. Compute the store value:
896    ///    - With DMA: just `value_reg` (the H+1 AND is suppressed
897    ///      because the DMC pulled the bus low).
898    ///    - Without DMA: `value_reg AND ((base >> 8) + 1)`.
899    /// 6. Write to the (possibly corrupted) final address.
900    ///
901    /// This shape is what `AccuracyCoin Unofficial Instructions: SH*`
902    /// sub-test 7 ("the cycle before the write had a DMA") brackets.
903    /// Pre-2026-05-23 `RustyNES` skipped the dummy read entirely and
904    /// always wrote `value_reg & (H+1)`, failing sub-test 7 across
905    /// all 5 SH* opcodes (error code 7).
906    fn sh_store<B: Bus>(&mut self, bus: &mut B, base: u16, index_reg: u8, value_reg: u8) {
907        let addr = base.wrapping_add(u16::from(index_reg));
908        let page_crossed = (base & 0xFF00) != (addr & 0xFF00);
909
910        // Dummy read at the unfixed address (this is the cycle DMC
911        // DMA can halt).  We sample the bus-side cycle count before
912        // and after to detect interruption — DMC DMA service path
913        // advances `bus.cycle` by 3+ extra ticks while the CPU's
914        // own `Cpu::cycles` (which `idle_tick` increments) only goes
915        // up by 1 for the read itself.
916        let cyc_before = bus.cycle_count();
917        let dummy_addr = if page_crossed {
918            addr.wrapping_sub(0x100)
919        } else {
920            addr
921        };
922        let _dummy = self.read1(bus, dummy_addr);
923        let had_dma = bus.cycle_count().wrapping_sub(cyc_before) > 1;
924
925        let addr_high = (addr >> 8) as u8;
926        let addr_low = (addr & 0xFF) as u8;
927        let final_high = if page_crossed {
928            addr_high & value_reg
929        } else {
930            addr_high
931        };
932
933        let write_value = if had_dma {
934            // DMC DMA interrupted the dummy read — bus latch was
935            // overwritten by the DMC fetch, so the store value loses
936            // its AND-with-(H+1) component.  Per Mesen2 `SyaSxaAxa`.
937            value_reg
938        } else {
939            // Canonical "documented behavior 1" path: AND with
940            // (base_high + 1).
941            value_reg & ((base >> 8) as u8).wrapping_add(1)
942        };
943
944        let final_addr = (u16::from(final_high) << 8) | u16::from(addr_low);
945        self.write1(bus, final_addr, write_value);
946    }
947
948    fn service_interrupt<B: Bus>(&mut self, bus: &mut B, vector: u16, brk: bool) {
949        // Per-cycle interrupt sequence (7 cycles total when entered from
950        // an interrupt edge, 6 from BRK because its opcode fetch already
951        // burned cycle 1):
952        //   C1: opcode fetch (BRK only — IRQ/NMI skip this and instead
953        //       perform an extra dummy read in C2).
954        //   C2: dummy read of PC+1 (BRK)  /  filler/internal read (IRQ/NMI).
955        //   C3-C5: push PCH, PCL, P.
956        //   C6-C7: read vector lo, hi.
957        // `cycles_emitted` is reset here so the caller's accounting starts
958        // from this routine's first tick (the opcode-fetch tick from a BRK
959        // is harmless — the BRK arm sets *cycles = 0 to suppress the
960        // trailing burn loop).
961        self.cycles_emitted = 0;
962        // Reset the per-instruction interrupt sample latches so any NMI
963        // edge during the push sequence below is captured here.
964        self.nmi_first_tick = u8::MAX;
965        self.irq_first_tick = u8::MAX;
966        // Two filler reads for IRQ/NMI; one for BRK (the opcode fetch
967        // counted as the other).
968        //
969        // W3-Stage-3 Part B (`mc-r1-brk-padding-read`): BRK's C2 is the
970        // canonical PADDING-BYTE read at PC+1 — a REAL bus access on
971        // silicon, not an internal cycle. AccuracyCoin `Implied Dummy
972        // Reads` error 31 brackets exactly this: the test choreographs a
973        // BRK whose padding read lands on `$4015`, which must clear the
974        // frame-counter IRQ flag (RTI/RTS already emit their canonical
975        // reads under `cpu-stack-dummy-reads`; BRK was the one gap — with
976        // it the whole test PASSES, one sub-check beyond Mesen2's error
977        // 34). The dispatch arm has already advanced PC past the padding
978        // byte, so it sits at `pc - 1`.
979        if brk {
980            let _ = self.read1(bus, self.pc.wrapping_sub(1));
981        } else {
982            // v2.0.0 beta.2 (A2 every-cycle-bus-access, promoted to the only
983            // path in beta.4): canonical hardware IRQ/NMI cycles 1-2 are
984            // DUMMY READS of the interrupted PC (the suppressed opcode fetch
985            // + suppressed operand fetch — nesdev `6502_cpu.txt`; Mesen2
986            // `NesCpu::IRQ` issues two `DummyRead`s). This is the C1-trio
987            // canary path: the conversion keeps the exact same two-cycle
988            // start/end structure (φ2 samples unchanged) and only adds the
989            // bus access + held-address update; the cpu_interrupts_v2 5/5
990            // strict gate + AccuracyCoin 139/139 hold (verified at the
991            // beta.2 gate).
992            let _ = self.read1(bus, self.pc);
993            let _ = self.read1(bus, self.pc);
994        }
995        self.push_u16(bus, self.pc);
996        let mut p = self.p | Status::UNUSED;
997        if brk {
998            p.insert(Status::BREAK);
999        } else {
1000            p.remove(Status::BREAK);
1001        }
1002        self.push(bus, p.bits());
1003        self.p.insert(Status::INTERRUPT_DISABLE);
1004        // NMI hijacking: real 6502 latches the vector to read on the
1005        // CYCLE just before the vector reads; if NMI is asserted at that
1006        // point, BRK / IRQ both read $FFFA / $FFFB instead of the
1007        // declared vector.  We approximate "NMI asserted by now" with
1008        // "the NMI sample latch was hit during cycles 1..=5 of this
1009        // sequence."
1010        // R1: the hijack reads the DELAYED `mc_prev_need_nmi`, NOT the live
1011        // `mc_need_nmi` — an NMI edge latched ON the P-push cycle's φ2 sampler
1012        // must NOT hijack (the BRK/IRQ completes to its own vector and the NMI
1013        // is taken after one handler instruction). `mc_prev_need_nmi` is the
1014        // pre-edge value: 1 only if the NMI was pending BEFORE this cycle
1015        // (oracle-derived, cpu_interrupts_v2/2). Legacy uses `nmi_first_tick`.
1016        let effective_vector = if self.mc_prev_need_nmi && vector != NMI_VECTOR {
1017            self.mc_need_nmi = false;
1018            self.mc_prev_need_nmi = false;
1019            NMI_VECTOR
1020        } else {
1021            vector
1022        };
1023        // Phase 1.2 of Track C1 attempt 14: notify the bus of the vector
1024        // fetch BEFORE the low-byte read so the trace records the cycle
1025        // at which the CPU enters its vector-fetch micro-op (C6 of the
1026        // 7-cycle service sequence).  `is_nmi` distinguishes a clean NMI
1027        // service entry from an IRQ/BRK service entry that an NMI edge
1028        // has hijacked to `$FFFA` — both fetch from `$FFFA` but only one
1029        // has `vector == NMI_VECTOR` at this call site.
1030        bus.notify_irq_service(effective_vector, vector == NMI_VECTOR);
1031        let lo = self.read1(bus, effective_vector);
1032        let hi = self.read1(bus, effective_vector + 1);
1033        self.pc = u16::from(lo) | (u16::from(hi) << 8);
1034    }
1035
1036    // ------------------------------------------------------------------
1037    // Addressing-mode resolvers. Each returns the effective address plus a
1038    // page-crossed flag; the caller decides whether to add a cycle.
1039    // ------------------------------------------------------------------
1040
1041    fn addr_zp<B: Bus>(&mut self, bus: &mut B) -> Operand {
1042        Operand {
1043            addr: u16::from(self.fetch_pc(bus)),
1044            page_crossed: false,
1045        }
1046    }
1047
1048    fn addr_zp_x<B: Bus>(&mut self, bus: &mut B) -> Operand {
1049        let base = self.fetch_pc(bus);
1050        // v2.0.0 beta.2 (A2 every-cycle-bus-access): canonical 6502 cycle 3
1051        // reads the UN-indexed zero-page address while the index add
1052        // completes, then discards it (nesdev `6502_cpu.txt`; Mesen2 models
1053        // it as a real `MemoryRead`). Zero-page addresses are always RAM
1054        // ($0000-$00FF), so the read is register-side-effect-free — but it
1055        // parks a real address on the bus (the held address a DMA halt
1056        // re-reads) instead of leaving the cycle busless in the burn-loop.
1057        // The burn-probe histogram pinned this family as 99% of the
1058        // remaining busless surface ($95 STA zp,X alone = 8,955 of 9,795
1059        // burned cycles over the AccuracyCoin battery). Promoted to the only
1060        // path in v2.0.0 beta.4.
1061        let _ = self.read1(bus, u16::from(base));
1062        Operand {
1063            addr: u16::from(base.wrapping_add(self.x)),
1064            page_crossed: false,
1065        }
1066    }
1067
1068    fn addr_zp_y<B: Bus>(&mut self, bus: &mut B) -> Operand {
1069        let base = self.fetch_pc(bus);
1070        // A2: same canonical un-indexed dummy read as `addr_zp_x` (cycle 3
1071        // of LDX/STX zp,Y and the unofficial LAX/SAX zp,Y arms).
1072        let _ = self.read1(bus, u16::from(base));
1073        Operand {
1074            addr: u16::from(base.wrapping_add(self.y)),
1075            page_crossed: false,
1076        }
1077    }
1078
1079    fn addr_abs<B: Bus>(&mut self, bus: &mut B) -> Operand {
1080        Operand {
1081            addr: self.fetch_pc_u16(bus),
1082            page_crossed: false,
1083        }
1084    }
1085
1086    fn addr_abs_x<B: Bus>(&mut self, bus: &mut B) -> Operand {
1087        let base = self.fetch_pc_u16(bus);
1088        let addr = base.wrapping_add(u16::from(self.x));
1089        let page_crossed = (base & 0xFF00) != (addr & 0xFF00);
1090        if page_crossed {
1091            // Canonical 6502 page-cross dummy read at the unfixed
1092            // address: (base_hi << 8) | ((base_lo + X) & 0xFF). The
1093            // high byte hasn't been incremented yet. This read has
1094            // side effects on PPU registers (`$2002` clears VBlank,
1095            // `$2007` advances the buffer) and is the hardware oracle
1096            // AccuracyCoin's `CPU Behavior :: Dummy read cycles`
1097            // Test 1 brackets via `LDA $20F2, X` with X=$10 reading
1098            // $2002 through the mirror.
1099            let dummy = (base & 0xFF00) | (addr & 0x00FF);
1100            let _ = self.read1(bus, dummy);
1101        }
1102        Operand { addr, page_crossed }
1103    }
1104
1105    fn addr_abs_y<B: Bus>(&mut self, bus: &mut B) -> Operand {
1106        let base = self.fetch_pc_u16(bus);
1107        let addr = base.wrapping_add(u16::from(self.y));
1108        let page_crossed = (base & 0xFF00) != (addr & 0xFF00);
1109        if page_crossed {
1110            // See addr_abs_x for the page-cross dummy-read rationale.
1111            let dummy = (base & 0xFF00) | (addr & 0x00FF);
1112            let _ = self.read1(bus, dummy);
1113        }
1114        Operand { addr, page_crossed }
1115    }
1116
1117    // ABS,X / ABS,Y operands for read-modify-write opcodes (ASL, LSR, ROL,
1118    // ROR, INC, DEC, and the unofficial SLO/RLA/SRE/RRA/DCP/ISC). Canonical
1119    // 6502: the unfixed-address dummy read happens UNCONDITIONALLY at
1120    // cycle 4 (not just on page cross) because the CPU has 7 cycles to
1121    // fill and cannot know the fixed address until the high-byte add
1122    // completes. Reads with side effects (`$2002` clears VBlank, `$4015`
1123    // clears frame-IRQ, `$2007` advances buffer) therefore fire twice on
1124    // RMW ABS,X. Bracketed by AccuracyCoin's `Implied Dummy Reads`
1125    // test 2: `SLO $4015,X` with X=0 expects the dummy read to clear the
1126    // frame-IRQ flag so the subsequent real read returns 0.
1127    fn addr_abs_x_rmw<B: Bus>(&mut self, bus: &mut B) -> u16 {
1128        let base = self.fetch_pc_u16(bus);
1129        let addr = base.wrapping_add(u16::from(self.x));
1130        let dummy = (base & 0xFF00) | (addr & 0x00FF);
1131        let _ = self.read1(bus, dummy);
1132        addr
1133    }
1134
1135    fn addr_abs_y_rmw<B: Bus>(&mut self, bus: &mut B) -> u16 {
1136        let base = self.fetch_pc_u16(bus);
1137        let addr = base.wrapping_add(u16::from(self.y));
1138        let dummy = (base & 0xFF00) | (addr & 0x00FF);
1139        let _ = self.read1(bus, dummy);
1140        addr
1141    }
1142
1143    /// (zp),Y operand for the unofficial read-modify-write opcodes
1144    /// (SLO/RLA/SRE/RRA/DCP/ISB `(zp),Y` — `$13/$33/$53/$73/$D3/$F3`).
1145    ///
1146    /// v2.0.0 beta.2 (A2 every-cycle-bus-access, promoted to the only path
1147    /// in beta.4): canonical 6502 8-cycle (zp),Y RMW performs the
1148    /// unfixed-address dummy read UNCONDITIONALLY at cycle 5 (like RMW
1149    /// ABS,X/Y above — the CPU cannot know the fixed address until the
1150    /// high-byte add completes), not only on page cross. The burn-probe
1151    /// histogram pinned these six arms as the last instruction-dispatch
1152    /// busless cycles (25 of the original 9,795).
1153    fn addr_ind_y_rmw<B: Bus>(&mut self, bus: &mut B) -> u16 {
1154        // Delegate to the plain resolver (which already emits the
1155        // unfixed-address dummy read on a page cross), then emit the
1156        // RMW's unconditional cycle-5 dummy for the non-crossing case.
1157        // On a non-crossing access the canonical unfixed address
1158        // `(base & 0xFF00) | (addr & 0xFF)` EQUALS the final address
1159        // (the high byte needed no fix-up), so reading `o.addr` here is
1160        // the silicon-exact target — do not "fix" this to a separate
1161        // unfixed computation, they are identical by construction.
1162        let o = self.addr_ind_y(bus);
1163        if !o.page_crossed {
1164            let _ = self.read1(bus, o.addr);
1165        }
1166        o.addr
1167    }
1168
1169    fn addr_ind_x<B: Bus>(&mut self, bus: &mut B) -> Operand {
1170        let base = self.fetch_pc(bus);
1171        // A2: canonical (zp,X) cycle 3 — dummy read of the UN-indexed
1172        // pointer address while the X add completes (same silicon behavior
1173        // as `addr_zp_x`; zero-page, so register-side-effect-free).
1174        let _ = self.read1(bus, u16::from(base));
1175        let ptr = base.wrapping_add(self.x);
1176        let lo = self.read1(bus, u16::from(ptr));
1177        let hi = self.read1(bus, u16::from(ptr.wrapping_add(1)));
1178        Operand {
1179            addr: u16::from(lo) | (u16::from(hi) << 8),
1180            page_crossed: false,
1181        }
1182    }
1183
1184    fn addr_ind_y<B: Bus>(&mut self, bus: &mut B) -> Operand {
1185        let ptr = self.fetch_pc(bus);
1186        let lo = self.read1(bus, u16::from(ptr));
1187        let hi = self.read1(bus, u16::from(ptr.wrapping_add(1)));
1188        let base = u16::from(lo) | (u16::from(hi) << 8);
1189        let addr = base.wrapping_add(u16::from(self.y));
1190        let page_crossed = (base & 0xFF00) != (addr & 0xFF00);
1191        if page_crossed {
1192            // Page-cross dummy read at the unfixed address — same as
1193            // addr_abs_x/y. Canonical 6502 behavior for LDA (zp),Y on
1194            // page crossing.
1195            let dummy = (base & 0xFF00) | (addr & 0x00FF);
1196            let _ = self.read1(bus, dummy);
1197        }
1198        Operand { addr, page_crossed }
1199    }
1200
1201    // ------------------------------------------------------------------
1202    // Top-level dispatch.
1203    //
1204    // The 256-way match is the cleanest way to express the entire opcode
1205    // table; the doc-comments are intentionally absent at the arm level
1206    // because each one is a single line of the standard 6502 reference and
1207    // adding individual arm comments would overwhelm the readability of the
1208    // table.
1209    // ------------------------------------------------------------------
1210
1211    #[allow(
1212        clippy::cognitive_complexity,
1213        clippy::too_many_lines,
1214        clippy::match_same_arms
1215    )]
1216    fn dispatch<B: Bus>(&mut self, bus: &mut B, op: u8, cycles: &mut u8) {
1217        match op {
1218            // === Loads ===
1219            0xA9 => {
1220                let v = self.fetch_pc(bus);
1221                self.lda(v);
1222                *cycles = 2;
1223            }
1224            0xA5 => {
1225                let o = self.addr_zp(bus);
1226                self.lda_addr(bus, o.addr);
1227                *cycles = 3;
1228            }
1229            0xB5 => {
1230                let o = self.addr_zp_x(bus);
1231                self.lda_addr(bus, o.addr);
1232                *cycles = 4;
1233            }
1234            0xAD => {
1235                let o = self.addr_abs(bus);
1236                self.lda_addr(bus, o.addr);
1237                *cycles = 4;
1238            }
1239            0xBD => {
1240                let o = self.addr_abs_x(bus);
1241                self.lda_addr(bus, o.addr);
1242                *cycles = 4 + u8::from(o.page_crossed);
1243            }
1244            0xB9 => {
1245                let o = self.addr_abs_y(bus);
1246                self.lda_addr(bus, o.addr);
1247                *cycles = 4 + u8::from(o.page_crossed);
1248            }
1249            0xA1 => {
1250                let o = self.addr_ind_x(bus);
1251                self.lda_addr(bus, o.addr);
1252                *cycles = 6;
1253            }
1254            0xB1 => {
1255                let o = self.addr_ind_y(bus);
1256                self.lda_addr(bus, o.addr);
1257                *cycles = 5 + u8::from(o.page_crossed);
1258            }
1259
1260            0xA2 => {
1261                let v = self.fetch_pc(bus);
1262                self.ldx(v);
1263                *cycles = 2;
1264            }
1265            0xA6 => {
1266                let o = self.addr_zp(bus);
1267                let v = self.read1(bus, o.addr);
1268                self.ldx(v);
1269                *cycles = 3;
1270            }
1271            0xB6 => {
1272                let o = self.addr_zp_y(bus);
1273                let v = self.read1(bus, o.addr);
1274                self.ldx(v);
1275                *cycles = 4;
1276            }
1277            0xAE => {
1278                let o = self.addr_abs(bus);
1279                let v = self.read1(bus, o.addr);
1280                self.ldx(v);
1281                *cycles = 4;
1282            }
1283            0xBE => {
1284                let o = self.addr_abs_y(bus);
1285                let v = self.read1(bus, o.addr);
1286                self.ldx(v);
1287                *cycles = 4 + u8::from(o.page_crossed);
1288            }
1289
1290            0xA0 => {
1291                let v = self.fetch_pc(bus);
1292                self.ldy(v);
1293                *cycles = 2;
1294            }
1295            0xA4 => {
1296                let o = self.addr_zp(bus);
1297                let v = self.read1(bus, o.addr);
1298                self.ldy(v);
1299                *cycles = 3;
1300            }
1301            0xB4 => {
1302                let o = self.addr_zp_x(bus);
1303                let v = self.read1(bus, o.addr);
1304                self.ldy(v);
1305                *cycles = 4;
1306            }
1307            0xAC => {
1308                let o = self.addr_abs(bus);
1309                let v = self.read1(bus, o.addr);
1310                self.ldy(v);
1311                *cycles = 4;
1312            }
1313            0xBC => {
1314                let o = self.addr_abs_x(bus);
1315                let v = self.read1(bus, o.addr);
1316                self.ldy(v);
1317                *cycles = 4 + u8::from(o.page_crossed);
1318            }
1319
1320            // === Stores ===
1321            0x85 => {
1322                let o = self.addr_zp(bus);
1323                self.write1(bus, o.addr, self.a);
1324                *cycles = 3;
1325            }
1326            0x95 => {
1327                let o = self.addr_zp_x(bus);
1328                self.write1(bus, o.addr, self.a);
1329                *cycles = 4;
1330            }
1331            0x8D => {
1332                let o = self.addr_abs(bus);
1333                self.write1(bus, o.addr, self.a);
1334                *cycles = 4;
1335            }
1336            0x9D => {
1337                let o = self.addr_abs_x(bus);
1338                // Canonical 6502: STA absolute,X performs a dummy
1339                // read at cycle 4 even when no page is crossed (unlike
1340                // LDA where cycle 4 is the real read). `addr_abs_x`
1341                // already issues the dummy read at the unfixed address
1342                // when page-crossed; for the no-page-cross case we add
1343                // it here at the final address.
1344                if !o.page_crossed {
1345                    let _ = self.read1(bus, o.addr);
1346                }
1347                self.write1(bus, o.addr, self.a);
1348                *cycles = 5;
1349            }
1350            0x99 => {
1351                let o = self.addr_abs_y(bus);
1352                if !o.page_crossed {
1353                    let _ = self.read1(bus, o.addr);
1354                }
1355                self.write1(bus, o.addr, self.a);
1356                *cycles = 5;
1357            }
1358            0x81 => {
1359                let o = self.addr_ind_x(bus);
1360                self.write1(bus, o.addr, self.a);
1361                *cycles = 6;
1362            }
1363            0x91 => {
1364                let o = self.addr_ind_y(bus);
1365                // Canonical STA (zp),Y always dummy-reads at cycle 5
1366                // even when no page is crossed. `addr_ind_y` already
1367                // handles the page-cross dummy at the unfixed address;
1368                // add the no-page-cross dummy here at the final address.
1369                if !o.page_crossed {
1370                    let _ = self.read1(bus, o.addr);
1371                }
1372                self.write1(bus, o.addr, self.a);
1373                *cycles = 6;
1374            }
1375
1376            0x86 => {
1377                let o = self.addr_zp(bus);
1378                self.write1(bus, o.addr, self.x);
1379                *cycles = 3;
1380            }
1381            0x96 => {
1382                let o = self.addr_zp_y(bus);
1383                self.write1(bus, o.addr, self.x);
1384                *cycles = 4;
1385            }
1386            0x8E => {
1387                let o = self.addr_abs(bus);
1388                self.write1(bus, o.addr, self.x);
1389                *cycles = 4;
1390            }
1391
1392            0x84 => {
1393                let o = self.addr_zp(bus);
1394                self.write1(bus, o.addr, self.y);
1395                *cycles = 3;
1396            }
1397            0x94 => {
1398                let o = self.addr_zp_x(bus);
1399                self.write1(bus, o.addr, self.y);
1400                *cycles = 4;
1401            }
1402            0x8C => {
1403                let o = self.addr_abs(bus);
1404                self.write1(bus, o.addr, self.y);
1405                *cycles = 4;
1406            }
1407
1408            // === Transfers ===
1409            0xAA => {
1410                self.implied_dummy_read(bus);
1411                self.x = self.a;
1412                self.p.set_nz(self.x);
1413                *cycles = 2;
1414            }
1415            0xA8 => {
1416                self.implied_dummy_read(bus);
1417                self.y = self.a;
1418                self.p.set_nz(self.y);
1419                *cycles = 2;
1420            }
1421            0xBA => {
1422                self.implied_dummy_read(bus);
1423                self.x = self.s;
1424                self.p.set_nz(self.x);
1425                *cycles = 2;
1426            }
1427            0x8A => {
1428                self.implied_dummy_read(bus);
1429                self.a = self.x;
1430                self.p.set_nz(self.a);
1431                *cycles = 2;
1432            }
1433            0x9A => {
1434                self.implied_dummy_read(bus);
1435                self.s = self.x;
1436                *cycles = 2;
1437            }
1438            0x98 => {
1439                self.implied_dummy_read(bus);
1440                self.a = self.y;
1441                self.p.set_nz(self.a);
1442                *cycles = 2;
1443            }
1444
1445            // === Stack ===
1446            0x48 => {
1447                // PHA: C2 dummy read PC (the 6502 always reads the next byte on
1448                // the second cycle of a stack push), then the push.
1449                let _ = self.read1(bus, self.pc);
1450                self.push(bus, self.a);
1451                *cycles = 3;
1452            }
1453            0x08 => {
1454                let _ = self.read1(bus, self.pc);
1455                self.push(bus, (self.p | Status::BREAK | Status::UNUSED).bits());
1456                *cycles = 3;
1457            }
1458            0x68 => {
1459                // PLA: C2 dummy read PC, C3 dummy stack read (pre-increment),
1460                // then the pull.
1461                {
1462                    let _ = self.read1(bus, self.pc);
1463                    let _ = self.read1(bus, STACK_BASE | u16::from(self.s));
1464                }
1465                self.a = self.pull(bus);
1466                self.p.set_nz(self.a);
1467                *cycles = 4;
1468            }
1469            0x28 => {
1470                {
1471                    let _ = self.read1(bus, self.pc);
1472                    let _ = self.read1(bus, STACK_BASE | u16::from(self.s));
1473                }
1474                let v = self.pull(bus);
1475                let mut new_p = Status::from_bits_truncate(v);
1476                new_p.remove(Status::BREAK);
1477                new_p.insert(Status::UNUSED);
1478                self.p = new_p;
1479                *cycles = 4;
1480            }
1481
1482            // === Logical ===
1483            0x29 => {
1484                let v = self.fetch_pc(bus);
1485                self.and(v);
1486                *cycles = 2;
1487            }
1488            0x25 => {
1489                let o = self.addr_zp(bus);
1490                let v = self.read1(bus, o.addr);
1491                self.and(v);
1492                *cycles = 3;
1493            }
1494            0x35 => {
1495                let o = self.addr_zp_x(bus);
1496                let v = self.read1(bus, o.addr);
1497                self.and(v);
1498                *cycles = 4;
1499            }
1500            0x2D => {
1501                let o = self.addr_abs(bus);
1502                let v = self.read1(bus, o.addr);
1503                self.and(v);
1504                *cycles = 4;
1505            }
1506            0x3D => {
1507                let o = self.addr_abs_x(bus);
1508                let v = self.read1(bus, o.addr);
1509                self.and(v);
1510                *cycles = 4 + u8::from(o.page_crossed);
1511            }
1512            0x39 => {
1513                let o = self.addr_abs_y(bus);
1514                let v = self.read1(bus, o.addr);
1515                self.and(v);
1516                *cycles = 4 + u8::from(o.page_crossed);
1517            }
1518            0x21 => {
1519                let o = self.addr_ind_x(bus);
1520                let v = self.read1(bus, o.addr);
1521                self.and(v);
1522                *cycles = 6;
1523            }
1524            0x31 => {
1525                let o = self.addr_ind_y(bus);
1526                let v = self.read1(bus, o.addr);
1527                self.and(v);
1528                *cycles = 5 + u8::from(o.page_crossed);
1529            }
1530
1531            0x09 => {
1532                let v = self.fetch_pc(bus);
1533                self.ora(v);
1534                *cycles = 2;
1535            }
1536            0x05 => {
1537                let o = self.addr_zp(bus);
1538                let v = self.read1(bus, o.addr);
1539                self.ora(v);
1540                *cycles = 3;
1541            }
1542            0x15 => {
1543                let o = self.addr_zp_x(bus);
1544                let v = self.read1(bus, o.addr);
1545                self.ora(v);
1546                *cycles = 4;
1547            }
1548            0x0D => {
1549                let o = self.addr_abs(bus);
1550                let v = self.read1(bus, o.addr);
1551                self.ora(v);
1552                *cycles = 4;
1553            }
1554            0x1D => {
1555                let o = self.addr_abs_x(bus);
1556                let v = self.read1(bus, o.addr);
1557                self.ora(v);
1558                *cycles = 4 + u8::from(o.page_crossed);
1559            }
1560            0x19 => {
1561                let o = self.addr_abs_y(bus);
1562                let v = self.read1(bus, o.addr);
1563                self.ora(v);
1564                *cycles = 4 + u8::from(o.page_crossed);
1565            }
1566            0x01 => {
1567                let o = self.addr_ind_x(bus);
1568                let v = self.read1(bus, o.addr);
1569                self.ora(v);
1570                *cycles = 6;
1571            }
1572            0x11 => {
1573                let o = self.addr_ind_y(bus);
1574                let v = self.read1(bus, o.addr);
1575                self.ora(v);
1576                *cycles = 5 + u8::from(o.page_crossed);
1577            }
1578
1579            0x49 => {
1580                let v = self.fetch_pc(bus);
1581                self.eor(v);
1582                *cycles = 2;
1583            }
1584            0x45 => {
1585                let o = self.addr_zp(bus);
1586                let v = self.read1(bus, o.addr);
1587                self.eor(v);
1588                *cycles = 3;
1589            }
1590            0x55 => {
1591                let o = self.addr_zp_x(bus);
1592                let v = self.read1(bus, o.addr);
1593                self.eor(v);
1594                *cycles = 4;
1595            }
1596            0x4D => {
1597                let o = self.addr_abs(bus);
1598                let v = self.read1(bus, o.addr);
1599                self.eor(v);
1600                *cycles = 4;
1601            }
1602            0x5D => {
1603                let o = self.addr_abs_x(bus);
1604                let v = self.read1(bus, o.addr);
1605                self.eor(v);
1606                *cycles = 4 + u8::from(o.page_crossed);
1607            }
1608            0x59 => {
1609                let o = self.addr_abs_y(bus);
1610                let v = self.read1(bus, o.addr);
1611                self.eor(v);
1612                *cycles = 4 + u8::from(o.page_crossed);
1613            }
1614            0x41 => {
1615                let o = self.addr_ind_x(bus);
1616                let v = self.read1(bus, o.addr);
1617                self.eor(v);
1618                *cycles = 6;
1619            }
1620            0x51 => {
1621                let o = self.addr_ind_y(bus);
1622                let v = self.read1(bus, o.addr);
1623                self.eor(v);
1624                *cycles = 5 + u8::from(o.page_crossed);
1625            }
1626
1627            0x24 => {
1628                let o = self.addr_zp(bus);
1629                let v = self.read1(bus, o.addr);
1630                self.bit(v);
1631                *cycles = 3;
1632            }
1633            0x2C => {
1634                let o = self.addr_abs(bus);
1635                let v = self.read1(bus, o.addr);
1636                self.bit(v);
1637                *cycles = 4;
1638            }
1639
1640            // === Arithmetic ===
1641            0x69 => {
1642                let v = self.fetch_pc(bus);
1643                self.adc(v);
1644                *cycles = 2;
1645            }
1646            0x65 => {
1647                let o = self.addr_zp(bus);
1648                let v = self.read1(bus, o.addr);
1649                self.adc(v);
1650                *cycles = 3;
1651            }
1652            0x75 => {
1653                let o = self.addr_zp_x(bus);
1654                let v = self.read1(bus, o.addr);
1655                self.adc(v);
1656                *cycles = 4;
1657            }
1658            0x6D => {
1659                let o = self.addr_abs(bus);
1660                let v = self.read1(bus, o.addr);
1661                self.adc(v);
1662                *cycles = 4;
1663            }
1664            0x7D => {
1665                let o = self.addr_abs_x(bus);
1666                let v = self.read1(bus, o.addr);
1667                self.adc(v);
1668                *cycles = 4 + u8::from(o.page_crossed);
1669            }
1670            0x79 => {
1671                let o = self.addr_abs_y(bus);
1672                let v = self.read1(bus, o.addr);
1673                self.adc(v);
1674                *cycles = 4 + u8::from(o.page_crossed);
1675            }
1676            0x61 => {
1677                let o = self.addr_ind_x(bus);
1678                let v = self.read1(bus, o.addr);
1679                self.adc(v);
1680                *cycles = 6;
1681            }
1682            0x71 => {
1683                let o = self.addr_ind_y(bus);
1684                let v = self.read1(bus, o.addr);
1685                self.adc(v);
1686                *cycles = 5 + u8::from(o.page_crossed);
1687            }
1688
1689            0xE9 | 0xEB => {
1690                let v = self.fetch_pc(bus);
1691                self.sbc(v);
1692                *cycles = 2;
1693            }
1694            0xE5 => {
1695                let o = self.addr_zp(bus);
1696                let v = self.read1(bus, o.addr);
1697                self.sbc(v);
1698                *cycles = 3;
1699            }
1700            0xF5 => {
1701                let o = self.addr_zp_x(bus);
1702                let v = self.read1(bus, o.addr);
1703                self.sbc(v);
1704                *cycles = 4;
1705            }
1706            0xED => {
1707                let o = self.addr_abs(bus);
1708                let v = self.read1(bus, o.addr);
1709                self.sbc(v);
1710                *cycles = 4;
1711            }
1712            0xFD => {
1713                let o = self.addr_abs_x(bus);
1714                let v = self.read1(bus, o.addr);
1715                self.sbc(v);
1716                *cycles = 4 + u8::from(o.page_crossed);
1717            }
1718            0xF9 => {
1719                let o = self.addr_abs_y(bus);
1720                let v = self.read1(bus, o.addr);
1721                self.sbc(v);
1722                *cycles = 4 + u8::from(o.page_crossed);
1723            }
1724            0xE1 => {
1725                let o = self.addr_ind_x(bus);
1726                let v = self.read1(bus, o.addr);
1727                self.sbc(v);
1728                *cycles = 6;
1729            }
1730            0xF1 => {
1731                let o = self.addr_ind_y(bus);
1732                let v = self.read1(bus, o.addr);
1733                self.sbc(v);
1734                *cycles = 5 + u8::from(o.page_crossed);
1735            }
1736
1737            // === Compare ===
1738            0xC9 => {
1739                let v = self.fetch_pc(bus);
1740                self.cmp_with(self.a, v);
1741                *cycles = 2;
1742            }
1743            0xC5 => {
1744                let o = self.addr_zp(bus);
1745                let v = self.read1(bus, o.addr);
1746                self.cmp_with(self.a, v);
1747                *cycles = 3;
1748            }
1749            0xD5 => {
1750                let o = self.addr_zp_x(bus);
1751                let v = self.read1(bus, o.addr);
1752                self.cmp_with(self.a, v);
1753                *cycles = 4;
1754            }
1755            0xCD => {
1756                let o = self.addr_abs(bus);
1757                let v = self.read1(bus, o.addr);
1758                self.cmp_with(self.a, v);
1759                *cycles = 4;
1760            }
1761            0xDD => {
1762                let o = self.addr_abs_x(bus);
1763                let v = self.read1(bus, o.addr);
1764                self.cmp_with(self.a, v);
1765                *cycles = 4 + u8::from(o.page_crossed);
1766            }
1767            0xD9 => {
1768                let o = self.addr_abs_y(bus);
1769                let v = self.read1(bus, o.addr);
1770                self.cmp_with(self.a, v);
1771                *cycles = 4 + u8::from(o.page_crossed);
1772            }
1773            0xC1 => {
1774                let o = self.addr_ind_x(bus);
1775                let v = self.read1(bus, o.addr);
1776                self.cmp_with(self.a, v);
1777                *cycles = 6;
1778            }
1779            0xD1 => {
1780                let o = self.addr_ind_y(bus);
1781                let v = self.read1(bus, o.addr);
1782                self.cmp_with(self.a, v);
1783                *cycles = 5 + u8::from(o.page_crossed);
1784            }
1785
1786            0xE0 => {
1787                let v = self.fetch_pc(bus);
1788                self.cmp_with(self.x, v);
1789                *cycles = 2;
1790            }
1791            0xE4 => {
1792                let o = self.addr_zp(bus);
1793                let v = self.read1(bus, o.addr);
1794                self.cmp_with(self.x, v);
1795                *cycles = 3;
1796            }
1797            0xEC => {
1798                let o = self.addr_abs(bus);
1799                let v = self.read1(bus, o.addr);
1800                self.cmp_with(self.x, v);
1801                *cycles = 4;
1802            }
1803
1804            0xC0 => {
1805                let v = self.fetch_pc(bus);
1806                self.cmp_with(self.y, v);
1807                *cycles = 2;
1808            }
1809            0xC4 => {
1810                let o = self.addr_zp(bus);
1811                let v = self.read1(bus, o.addr);
1812                self.cmp_with(self.y, v);
1813                *cycles = 3;
1814            }
1815            0xCC => {
1816                let o = self.addr_abs(bus);
1817                let v = self.read1(bus, o.addr);
1818                self.cmp_with(self.y, v);
1819                *cycles = 4;
1820            }
1821
1822            // === Increments / decrements ===
1823            0xE6 => {
1824                let o = self.addr_zp(bus);
1825                self.inc_addr(bus, o.addr);
1826                *cycles = 5;
1827            }
1828            0xF6 => {
1829                let o = self.addr_zp_x(bus);
1830                self.inc_addr(bus, o.addr);
1831                *cycles = 6;
1832            }
1833            0xEE => {
1834                let o = self.addr_abs(bus);
1835                self.inc_addr(bus, o.addr);
1836                *cycles = 6;
1837            }
1838            0xFE => {
1839                let addr = self.addr_abs_x_rmw(bus);
1840                self.inc_addr(bus, addr);
1841                *cycles = 7;
1842            }
1843            0xC6 => {
1844                let o = self.addr_zp(bus);
1845                self.dec_addr(bus, o.addr);
1846                *cycles = 5;
1847            }
1848            0xD6 => {
1849                let o = self.addr_zp_x(bus);
1850                self.dec_addr(bus, o.addr);
1851                *cycles = 6;
1852            }
1853            0xCE => {
1854                let o = self.addr_abs(bus);
1855                self.dec_addr(bus, o.addr);
1856                *cycles = 6;
1857            }
1858            0xDE => {
1859                let addr = self.addr_abs_x_rmw(bus);
1860                self.dec_addr(bus, addr);
1861                *cycles = 7;
1862            }
1863            0xE8 => {
1864                self.implied_dummy_read(bus);
1865                self.x = self.x.wrapping_add(1);
1866                self.p.set_nz(self.x);
1867                *cycles = 2;
1868            }
1869            0xCA => {
1870                self.implied_dummy_read(bus);
1871                self.x = self.x.wrapping_sub(1);
1872                self.p.set_nz(self.x);
1873                *cycles = 2;
1874            }
1875            0xC8 => {
1876                self.implied_dummy_read(bus);
1877                self.y = self.y.wrapping_add(1);
1878                self.p.set_nz(self.y);
1879                *cycles = 2;
1880            }
1881            0x88 => {
1882                self.implied_dummy_read(bus);
1883                self.y = self.y.wrapping_sub(1);
1884                self.p.set_nz(self.y);
1885                *cycles = 2;
1886            }
1887
1888            // === Shifts ===
1889            0x0A => {
1890                self.implied_dummy_read(bus);
1891                self.a = self.asl_value(self.a);
1892                *cycles = 2;
1893            }
1894            0x06 => {
1895                let o = self.addr_zp(bus);
1896                self.asl_addr(bus, o.addr);
1897                *cycles = 5;
1898            }
1899            0x16 => {
1900                let o = self.addr_zp_x(bus);
1901                self.asl_addr(bus, o.addr);
1902                *cycles = 6;
1903            }
1904            0x0E => {
1905                let o = self.addr_abs(bus);
1906                self.asl_addr(bus, o.addr);
1907                *cycles = 6;
1908            }
1909            0x1E => {
1910                let addr = self.addr_abs_x_rmw(bus);
1911                self.asl_addr(bus, addr);
1912                *cycles = 7;
1913            }
1914
1915            0x4A => {
1916                self.implied_dummy_read(bus);
1917                self.a = self.lsr_value(self.a);
1918                *cycles = 2;
1919            }
1920            0x46 => {
1921                let o = self.addr_zp(bus);
1922                self.lsr_addr(bus, o.addr);
1923                *cycles = 5;
1924            }
1925            0x56 => {
1926                let o = self.addr_zp_x(bus);
1927                self.lsr_addr(bus, o.addr);
1928                *cycles = 6;
1929            }
1930            0x4E => {
1931                let o = self.addr_abs(bus);
1932                self.lsr_addr(bus, o.addr);
1933                *cycles = 6;
1934            }
1935            0x5E => {
1936                let addr = self.addr_abs_x_rmw(bus);
1937                self.lsr_addr(bus, addr);
1938                *cycles = 7;
1939            }
1940
1941            0x2A => {
1942                self.implied_dummy_read(bus);
1943                self.a = self.rol_value(self.a);
1944                *cycles = 2;
1945            }
1946            0x26 => {
1947                let o = self.addr_zp(bus);
1948                self.rol_addr(bus, o.addr);
1949                *cycles = 5;
1950            }
1951            0x36 => {
1952                let o = self.addr_zp_x(bus);
1953                self.rol_addr(bus, o.addr);
1954                *cycles = 6;
1955            }
1956            0x2E => {
1957                let o = self.addr_abs(bus);
1958                self.rol_addr(bus, o.addr);
1959                *cycles = 6;
1960            }
1961            0x3E => {
1962                let addr = self.addr_abs_x_rmw(bus);
1963                self.rol_addr(bus, addr);
1964                *cycles = 7;
1965            }
1966
1967            0x6A => {
1968                self.implied_dummy_read(bus);
1969                self.a = self.ror_value(self.a);
1970                *cycles = 2;
1971            }
1972            0x66 => {
1973                let o = self.addr_zp(bus);
1974                self.ror_addr(bus, o.addr);
1975                *cycles = 5;
1976            }
1977            0x76 => {
1978                let o = self.addr_zp_x(bus);
1979                self.ror_addr(bus, o.addr);
1980                *cycles = 6;
1981            }
1982            0x6E => {
1983                let o = self.addr_abs(bus);
1984                self.ror_addr(bus, o.addr);
1985                *cycles = 6;
1986            }
1987            0x7E => {
1988                let addr = self.addr_abs_x_rmw(bus);
1989                self.ror_addr(bus, addr);
1990                *cycles = 7;
1991            }
1992
1993            // === Branches ===
1994            //
1995            // The `branch_delays_irq` quirk: real 6502 branches poll IRQ
1996            // at the same point a 2-cycle untaken branch would — at the
1997            // opcode-fetch cycle (the canonical 2-cycle "second-to-last"
1998            // poll).  The operand-fetch cycle and any extra taken /
1999            // page-cross cycles do NOT re-sample IRQ.  We suppress IRQ
2000            // sampling for the remaining cycles of the instruction
2001            // immediately *before* the operand fetch — the opcode-fetch
2002            // sample (in `step()`) has already happened by this point.
2003            // See `docs/cpu-6502.md` §Interrupt logic and
2004            // <https://www.nesdev.org/wiki/CPU_interrupts>.
2005            0x10 => {
2006                self.skip_irq_sample = true;
2007                let off = self.fetch_pc(bus);
2008                *cycles = self.branch(bus, off, !self.p.contains(Status::NEGATIVE));
2009            }
2010            0x30 => {
2011                self.skip_irq_sample = true;
2012                let off = self.fetch_pc(bus);
2013                *cycles = self.branch(bus, off, self.p.contains(Status::NEGATIVE));
2014            }
2015            0x50 => {
2016                self.skip_irq_sample = true;
2017                let off = self.fetch_pc(bus);
2018                *cycles = self.branch(bus, off, !self.p.contains(Status::OVERFLOW));
2019            }
2020            0x70 => {
2021                self.skip_irq_sample = true;
2022                let off = self.fetch_pc(bus);
2023                *cycles = self.branch(bus, off, self.p.contains(Status::OVERFLOW));
2024            }
2025            0x90 => {
2026                self.skip_irq_sample = true;
2027                let off = self.fetch_pc(bus);
2028                *cycles = self.branch(bus, off, !self.p.contains(Status::CARRY));
2029            }
2030            0xB0 => {
2031                self.skip_irq_sample = true;
2032                let off = self.fetch_pc(bus);
2033                *cycles = self.branch(bus, off, self.p.contains(Status::CARRY));
2034            }
2035            0xD0 => {
2036                self.skip_irq_sample = true;
2037                let off = self.fetch_pc(bus);
2038                *cycles = self.branch(bus, off, !self.p.contains(Status::ZERO));
2039            }
2040            0xF0 => {
2041                self.skip_irq_sample = true;
2042                let off = self.fetch_pc(bus);
2043                *cycles = self.branch(bus, off, self.p.contains(Status::ZERO));
2044            }
2045
2046            // === Jumps / subroutine ===
2047            0x4C => {
2048                self.pc = self.fetch_pc_u16(bus);
2049                *cycles = 3;
2050            }
2051            0x6C => {
2052                let ptr = self.fetch_pc_u16(bus);
2053                self.pc = self.read_u16_with_wrap(bus, ptr);
2054                *cycles = 5;
2055            }
2056            0x20 => {
2057                // Canonical 6502 JSR cycle sequence — the high byte of
2058                // the target is read AFTER PC is pushed to the stack.
2059                // Wrong order is observable when JSR overwrites its own
2060                // operand via the pushed return address (AccuracyCoin
2061                // `CPU Behavior 2 :: JSR Edge Cases` Test 2 brackets
2062                // this exactly):
2063                //   C1: opcode fetch (already done by `tick` dispatcher)
2064                //   C2: fetch low byte of target → advances PC
2065                //   C3: dummy read from stack at $0100|S (no-op)
2066                //   C4: push PC high (PC is currently at the high-byte
2067                //       operand address, which is exactly the return
2068                //       address minus one)
2069                //   C5: push PC low
2070                //   C6: fetch high byte of target → PC = target
2071                let lo = self.fetch_pc(bus);
2072                let _ = self.read1(bus, STACK_BASE | u16::from(self.s));
2073                // self.pc now points at the high-byte operand; this is
2074                // the "return - 1" address JSR canonically pushes.
2075                let return_minus_one = self.pc;
2076                self.push(bus, (return_minus_one >> 8) as u8);
2077                self.push(bus, (return_minus_one & 0xFF) as u8);
2078                let hi = self.fetch_pc(bus);
2079                self.pc = u16::from(lo) | (u16::from(hi) << 8);
2080                *cycles = 6;
2081            }
2082            0x60 => {
2083                // Canonical 6502 RTS bus pattern (every cycle is a bus access):
2084                //   C1 opcode fetch (dispatcher) | C2 dummy read PC |
2085                //   C3 dummy stack read (pre-increment) |
2086                //   C4 pull PCL | C5 pull PCH | C6 dummy read at the return addr.
2087                // Default build burns C2/C3/C6 as `idle_tick` (no bus access);
2088                // `cpu-stack-dummy-reads` emits the canonical dummy reads — the
2089                // DC-6 Y=3-vs-4 fix. See the cell-trace cross-diff.
2090                {
2091                    let _ = self.read1(bus, self.pc);
2092                    let _ = self.read1(bus, STACK_BASE | u16::from(self.s));
2093                    let v = self.pull_u16(bus);
2094                    let _ = self.read1(bus, v);
2095                    self.pc = v.wrapping_add(1);
2096                }
2097                *cycles = 6;
2098            }
2099            0x40 => {
2100                // Canonical RTI bus pattern: C2 dummy read PC, C3 dummy stack
2101                // read (pre-increment) before the pulls. Default-off helper.
2102                {
2103                    let _ = self.read1(bus, self.pc);
2104                    let _ = self.read1(bus, STACK_BASE | u16::from(self.s));
2105                }
2106                let p = self.pull(bus);
2107                let mut new_p = Status::from_bits_truncate(p);
2108                new_p.remove(Status::BREAK);
2109                new_p.insert(Status::UNUSED);
2110                self.p = new_p;
2111                // RTI's I-flag change is observed by the IRQ sample
2112                // (unlike PLP / CLI / SEI which delay one instruction).
2113                self.irq_sample_i_flag = self.p.contains(Status::INTERRUPT_DISABLE);
2114                self.pc = self.pull_u16(bus);
2115                *cycles = 6;
2116            }
2117            0x00 => {
2118                // BRK is a 7-cycle interrupt with PC+2 pushed (PC already
2119                // advanced by fetch; advance one more for the padding byte).
2120                self.pc = self.pc.wrapping_add(1);
2121                self.service_interrupt(bus, IRQ_VECTOR, true);
2122                // R1/A2: suppress an NMI that became pending during/just-after
2123                // the BRK sequence so the FIRST instruction of the IRQ handler
2124                // runs before the NMI is taken (Mesen2 `NesCpu::BRK`
2125                // `_prevNeedNmi = false`; "needed for nmi_and_brk"). The NMI is
2126                // not lost — `mc_need_nmi` stays set and re-arms next cycle.
2127                {
2128                    self.mc_prev_need_nmi = false;
2129                }
2130                // service_interrupt already burned 7 cycles; do NOT double-count.
2131                *cycles = 0;
2132            }
2133            0xEA => {
2134                self.implied_dummy_read(bus);
2135                *cycles = 2;
2136            }
2137
2138            // === Flag manipulation ===
2139            0x18 => {
2140                self.implied_dummy_read(bus);
2141                self.p.remove(Status::CARRY);
2142                *cycles = 2;
2143            }
2144            0x38 => {
2145                self.implied_dummy_read(bus);
2146                self.p.insert(Status::CARRY);
2147                *cycles = 2;
2148            }
2149            0x58 => {
2150                self.implied_dummy_read(bus);
2151                self.p.remove(Status::INTERRUPT_DISABLE);
2152                *cycles = 2;
2153            }
2154            0x78 => {
2155                self.implied_dummy_read(bus);
2156                self.p.insert(Status::INTERRUPT_DISABLE);
2157                *cycles = 2;
2158            }
2159            0xB8 => {
2160                self.implied_dummy_read(bus);
2161                self.p.remove(Status::OVERFLOW);
2162                *cycles = 2;
2163            }
2164            0xD8 => {
2165                self.implied_dummy_read(bus);
2166                self.p.remove(Status::DECIMAL);
2167                *cycles = 2;
2168            }
2169            0xF8 => {
2170                self.implied_dummy_read(bus);
2171                self.p.insert(Status::DECIMAL);
2172                *cycles = 2;
2173            }
2174
2175            // === Unofficial NOP variants ===
2176            // Implied / 1-byte NOPs
2177            0x1A | 0x3A | 0x5A | 0x7A | 0xDA | 0xFA => {
2178                self.implied_dummy_read(bus);
2179                *cycles = 2;
2180            }
2181            // Immediate / zero-page DOP (double NOP) variants: skip 1 byte.
2182            0x80 | 0x82 | 0x89 | 0xC2 | 0xE2 => {
2183                let _ = self.fetch_pc(bus);
2184                *cycles = 2;
2185            }
2186            0x04 | 0x44 | 0x64 => {
2187                let o = self.addr_zp(bus);
2188                let _ = self.read1(bus, o.addr); // unofficial DOP dummy read
2189                *cycles = 3;
2190            }
2191            0x14 | 0x34 | 0x54 | 0x74 | 0xD4 | 0xF4 => {
2192                let o = self.addr_zp_x(bus);
2193                let _ = self.read1(bus, o.addr); // unofficial DOP dummy read
2194                *cycles = 4;
2195            }
2196            // Absolute "TOP" (triple NOP) — must dummy-read the target so
2197            // that PPU-mirror side-effects (e.g. clearing $2002.7) fire,
2198            // matching real silicon and AccuracyCoin's All-NOPs Test 2.
2199            0x0C => {
2200                let o = self.addr_abs(bus);
2201                let _ = self.read1(bus, o.addr);
2202                *cycles = 4;
2203            }
2204            0x1C | 0x3C | 0x5C | 0x7C | 0xDC | 0xFC => {
2205                let o = self.addr_abs_x(bus);
2206                let _ = self.read1(bus, o.addr); // dummy read on TOP
2207                *cycles = 4 + u8::from(o.page_crossed);
2208            }
2209
2210            // === Stable unofficial: LAX, SAX ===
2211            0xA7 => {
2212                let o = self.addr_zp(bus);
2213                let v = self.read1(bus, o.addr);
2214                self.lax(v);
2215                *cycles = 3;
2216            }
2217            0xB7 => {
2218                let o = self.addr_zp_y(bus);
2219                let v = self.read1(bus, o.addr);
2220                self.lax(v);
2221                *cycles = 4;
2222            }
2223            0xAF => {
2224                let o = self.addr_abs(bus);
2225                let v = self.read1(bus, o.addr);
2226                self.lax(v);
2227                *cycles = 4;
2228            }
2229            0xBF => {
2230                let o = self.addr_abs_y(bus);
2231                let v = self.read1(bus, o.addr);
2232                self.lax(v);
2233                *cycles = 4 + u8::from(o.page_crossed);
2234            }
2235            0xA3 => {
2236                let o = self.addr_ind_x(bus);
2237                let v = self.read1(bus, o.addr);
2238                self.lax(v);
2239                *cycles = 6;
2240            }
2241            0xB3 => {
2242                let o = self.addr_ind_y(bus);
2243                let v = self.read1(bus, o.addr);
2244                self.lax(v);
2245                *cycles = 5 + u8::from(o.page_crossed);
2246            }
2247            0xAB => {
2248                let v = self.fetch_pc(bus);
2249                self.lax(v);
2250                *cycles = 2;
2251            } // LAX immediate (often listed as ATX). We follow nestest behavior.
2252
2253            0x87 => {
2254                let o = self.addr_zp(bus);
2255                self.write1(bus, o.addr, self.a & self.x);
2256                *cycles = 3;
2257            }
2258            0x97 => {
2259                let o = self.addr_zp_y(bus);
2260                self.write1(bus, o.addr, self.a & self.x);
2261                *cycles = 4;
2262            }
2263            0x8F => {
2264                let o = self.addr_abs(bus);
2265                self.write1(bus, o.addr, self.a & self.x);
2266                *cycles = 4;
2267            }
2268            0x83 => {
2269                let o = self.addr_ind_x(bus);
2270                self.write1(bus, o.addr, self.a & self.x);
2271                *cycles = 6;
2272            }
2273
2274            // === DCP (DEC + CMP) ===
2275            0xC7 => {
2276                let o = self.addr_zp(bus);
2277                self.dcp_addr(bus, o.addr);
2278                *cycles = 5;
2279            }
2280            0xD7 => {
2281                let o = self.addr_zp_x(bus);
2282                self.dcp_addr(bus, o.addr);
2283                *cycles = 6;
2284            }
2285            0xCF => {
2286                let o = self.addr_abs(bus);
2287                self.dcp_addr(bus, o.addr);
2288                *cycles = 6;
2289            }
2290            0xDF => {
2291                let addr = self.addr_abs_x_rmw(bus);
2292                self.dcp_addr(bus, addr);
2293                *cycles = 7;
2294            }
2295            0xDB => {
2296                let addr = self.addr_abs_y_rmw(bus);
2297                self.dcp_addr(bus, addr);
2298                *cycles = 7;
2299            }
2300            0xC3 => {
2301                let o = self.addr_ind_x(bus);
2302                self.dcp_addr(bus, o.addr);
2303                *cycles = 8;
2304            }
2305            0xD3 => {
2306                let addr = self.addr_ind_y_rmw(bus);
2307                self.dcp_addr(bus, addr);
2308                *cycles = 8;
2309            }
2310
2311            // === ISC (INC + SBC) ===
2312            0xE7 => {
2313                let o = self.addr_zp(bus);
2314                self.isc_addr(bus, o.addr);
2315                *cycles = 5;
2316            }
2317            0xF7 => {
2318                let o = self.addr_zp_x(bus);
2319                self.isc_addr(bus, o.addr);
2320                *cycles = 6;
2321            }
2322            0xEF => {
2323                let o = self.addr_abs(bus);
2324                self.isc_addr(bus, o.addr);
2325                *cycles = 6;
2326            }
2327            0xFF => {
2328                let addr = self.addr_abs_x_rmw(bus);
2329                self.isc_addr(bus, addr);
2330                *cycles = 7;
2331            }
2332            0xFB => {
2333                let addr = self.addr_abs_y_rmw(bus);
2334                self.isc_addr(bus, addr);
2335                *cycles = 7;
2336            }
2337            0xE3 => {
2338                let o = self.addr_ind_x(bus);
2339                self.isc_addr(bus, o.addr);
2340                *cycles = 8;
2341            }
2342            0xF3 => {
2343                let addr = self.addr_ind_y_rmw(bus);
2344                self.isc_addr(bus, addr);
2345                *cycles = 8;
2346            }
2347
2348            // === SLO (ASL + ORA) ===
2349            0x07 => {
2350                let o = self.addr_zp(bus);
2351                self.slo_addr(bus, o.addr);
2352                *cycles = 5;
2353            }
2354            0x17 => {
2355                let o = self.addr_zp_x(bus);
2356                self.slo_addr(bus, o.addr);
2357                *cycles = 6;
2358            }
2359            0x0F => {
2360                let o = self.addr_abs(bus);
2361                self.slo_addr(bus, o.addr);
2362                *cycles = 6;
2363            }
2364            0x1F => {
2365                let addr = self.addr_abs_x_rmw(bus);
2366                self.slo_addr(bus, addr);
2367                *cycles = 7;
2368            }
2369            0x1B => {
2370                let addr = self.addr_abs_y_rmw(bus);
2371                self.slo_addr(bus, addr);
2372                *cycles = 7;
2373            }
2374            0x03 => {
2375                let o = self.addr_ind_x(bus);
2376                self.slo_addr(bus, o.addr);
2377                *cycles = 8;
2378            }
2379            0x13 => {
2380                let addr = self.addr_ind_y_rmw(bus);
2381                self.slo_addr(bus, addr);
2382                *cycles = 8;
2383            }
2384
2385            // === RLA (ROL + AND) ===
2386            0x27 => {
2387                let o = self.addr_zp(bus);
2388                self.rla_addr(bus, o.addr);
2389                *cycles = 5;
2390            }
2391            0x37 => {
2392                let o = self.addr_zp_x(bus);
2393                self.rla_addr(bus, o.addr);
2394                *cycles = 6;
2395            }
2396            0x2F => {
2397                let o = self.addr_abs(bus);
2398                self.rla_addr(bus, o.addr);
2399                *cycles = 6;
2400            }
2401            0x3F => {
2402                let addr = self.addr_abs_x_rmw(bus);
2403                self.rla_addr(bus, addr);
2404                *cycles = 7;
2405            }
2406            0x3B => {
2407                let addr = self.addr_abs_y_rmw(bus);
2408                self.rla_addr(bus, addr);
2409                *cycles = 7;
2410            }
2411            0x23 => {
2412                let o = self.addr_ind_x(bus);
2413                self.rla_addr(bus, o.addr);
2414                *cycles = 8;
2415            }
2416            0x33 => {
2417                let addr = self.addr_ind_y_rmw(bus);
2418                self.rla_addr(bus, addr);
2419                *cycles = 8;
2420            }
2421
2422            // === SRE (LSR + EOR) ===
2423            0x47 => {
2424                let o = self.addr_zp(bus);
2425                self.sre_addr(bus, o.addr);
2426                *cycles = 5;
2427            }
2428            0x57 => {
2429                let o = self.addr_zp_x(bus);
2430                self.sre_addr(bus, o.addr);
2431                *cycles = 6;
2432            }
2433            0x4F => {
2434                let o = self.addr_abs(bus);
2435                self.sre_addr(bus, o.addr);
2436                *cycles = 6;
2437            }
2438            0x5F => {
2439                let addr = self.addr_abs_x_rmw(bus);
2440                self.sre_addr(bus, addr);
2441                *cycles = 7;
2442            }
2443            0x5B => {
2444                let addr = self.addr_abs_y_rmw(bus);
2445                self.sre_addr(bus, addr);
2446                *cycles = 7;
2447            }
2448            0x43 => {
2449                let o = self.addr_ind_x(bus);
2450                self.sre_addr(bus, o.addr);
2451                *cycles = 8;
2452            }
2453            0x53 => {
2454                let addr = self.addr_ind_y_rmw(bus);
2455                self.sre_addr(bus, addr);
2456                *cycles = 8;
2457            }
2458
2459            // === RRA (ROR + ADC) ===
2460            0x67 => {
2461                let o = self.addr_zp(bus);
2462                self.rra_addr(bus, o.addr);
2463                *cycles = 5;
2464            }
2465            0x77 => {
2466                let o = self.addr_zp_x(bus);
2467                self.rra_addr(bus, o.addr);
2468                *cycles = 6;
2469            }
2470            0x6F => {
2471                let o = self.addr_abs(bus);
2472                self.rra_addr(bus, o.addr);
2473                *cycles = 6;
2474            }
2475            0x7F => {
2476                let addr = self.addr_abs_x_rmw(bus);
2477                self.rra_addr(bus, addr);
2478                *cycles = 7;
2479            }
2480            0x7B => {
2481                let addr = self.addr_abs_y_rmw(bus);
2482                self.rra_addr(bus, addr);
2483                *cycles = 7;
2484            }
2485            0x63 => {
2486                let o = self.addr_ind_x(bus);
2487                self.rra_addr(bus, o.addr);
2488                *cycles = 8;
2489            }
2490            0x73 => {
2491                let addr = self.addr_ind_y_rmw(bus);
2492                self.rra_addr(bus, addr);
2493                *cycles = 8;
2494            }
2495
2496            // === ANC, ALR, ARR, AXS ===
2497            0x0B | 0x2B => {
2498                let v = self.fetch_pc(bus);
2499                self.a &= v;
2500                self.p.set_nz(self.a);
2501                self.p.set(Status::CARRY, self.a & 0x80 != 0);
2502                *cycles = 2;
2503            }
2504            0x4B => {
2505                let v = self.fetch_pc(bus);
2506                self.a &= v;
2507                let new_carry = self.a & 0x01 != 0;
2508                self.a >>= 1;
2509                self.p.set_nz(self.a);
2510                self.p.set(Status::CARRY, new_carry);
2511                *cycles = 2;
2512            }
2513            0x6B => {
2514                let v = self.fetch_pc(bus);
2515                self.a &= v;
2516                let carry_in = self.p.contains(Status::CARRY);
2517                self.a = (self.a >> 1) | (u8::from(carry_in) << 7);
2518                self.p.set_nz(self.a);
2519                let bit6 = self.a & 0x40 != 0;
2520                let bit5 = self.a & 0x20 != 0;
2521                self.p.set(Status::CARRY, bit6);
2522                self.p.set(Status::OVERFLOW, bit6 ^ bit5);
2523                *cycles = 2;
2524            }
2525            0xCB => {
2526                let v = self.fetch_pc(bus);
2527                let ax = self.a & self.x;
2528                let (res, overflow) = ax.overflowing_sub(v);
2529                self.x = res;
2530                self.p.set(Status::CARRY, !overflow);
2531                self.p.set_nz(res);
2532                *cycles = 2;
2533            }
2534
2535            // === Unstable: XAA, LAS, TAS, SHA, SHX, SHY ===
2536            0x8B => {
2537                // XAA / ANE: A = (A | const) & X & operand. nestest expects this.
2538                let v = self.fetch_pc(bus);
2539                self.a = (self.a | 0xFF) & self.x & v;
2540                self.p.set_nz(self.a);
2541                *cycles = 2;
2542            }
2543            0xBB => {
2544                let o = self.addr_abs_y(bus);
2545                let v = self.read1(bus, o.addr);
2546                let res = self.s & v;
2547                self.a = res;
2548                self.x = res;
2549                self.s = res;
2550                self.p.set_nz(res);
2551                *cycles = 4 + u8::from(o.page_crossed);
2552            }
2553            0x9B => {
2554                // TAS / SHS / XAS abs,Y: S = A & X; then SHA-style write
2555                // using `S` as the value register.
2556                let base = self.fetch_pc_u16(bus);
2557                self.s = self.a & self.x;
2558                self.sh_store(bus, base, self.y, self.s);
2559                *cycles = 5;
2560            }
2561            0x9F => {
2562                // SHA abs,Y. value_reg = A & X.
2563                let base = self.fetch_pc_u16(bus);
2564                self.sh_store(bus, base, self.y, self.a & self.x);
2565                *cycles = 5;
2566            }
2567            0x93 => {
2568                // SHA (zp),Y. Indirect; base from zp-pointer-resolved
2569                // low/high bytes.  value_reg = A & X.
2570                let zp = self.fetch_pc(bus);
2571                let lo = self.read1(bus, u16::from(zp));
2572                let hi_byte = self.read1(bus, u16::from(zp.wrapping_add(1)));
2573                let base = u16::from(lo) | (u16::from(hi_byte) << 8);
2574                self.sh_store(bus, base, self.y, self.a & self.x);
2575                *cycles = 6;
2576            }
2577            0x9E => {
2578                // SHX abs,Y. value_reg = X.
2579                let base = self.fetch_pc_u16(bus);
2580                self.sh_store(bus, base, self.y, self.x);
2581                *cycles = 5;
2582            }
2583            0x9C => {
2584                // SHY abs,X. value_reg = Y. Index register is X here.
2585                let base = self.fetch_pc_u16(bus);
2586                self.sh_store(bus, base, self.x, self.y);
2587                *cycles = 5;
2588            }
2589
2590            // === JAM / KIL / STP ===
2591            0x02 | 0x12 | 0x22 | 0x32 | 0x42 | 0x52 | 0x62 | 0x72 | 0x92 | 0xB2 | 0xD2 | 0xF2 => {
2592                // v2.0.0 (every-cycle-bus-access): cycle 2 is a real dummy
2593                // read of the byte after the opcode before the CPU wedges —
2594                // the same silicon shape as the implied-opcode cycle-2 dummy
2595                // read. Caught post-promote by the burn-loop fail-loud
2596                // assert (this arm declared 2 cycles but emitted only the
2597                // opcode fetch — invisible to every probe workload, since no
2598                // test ROM executes a JAM).
2599                let _ = self.read1(bus, self.pc);
2600                self.jammed = true;
2601                *cycles = 2;
2602            }
2603        }
2604    }
2605
2606    // ------------------------------------------------------------------
2607    // Helpers / micro-ops.
2608    // ------------------------------------------------------------------
2609
2610    fn lda(&mut self, value: u8) {
2611        self.a = value;
2612        self.p.set_nz(value);
2613    }
2614
2615    fn lda_addr<B: Bus>(&mut self, bus: &mut B, addr: u16) {
2616        let v = self.read1(bus, addr);
2617        self.lda(v);
2618    }
2619
2620    fn ldx(&mut self, value: u8) {
2621        self.x = value;
2622        self.p.set_nz(value);
2623    }
2624
2625    fn ldy(&mut self, value: u8) {
2626        self.y = value;
2627        self.p.set_nz(value);
2628    }
2629
2630    fn and(&mut self, value: u8) {
2631        self.a &= value;
2632        self.p.set_nz(self.a);
2633    }
2634
2635    fn ora(&mut self, value: u8) {
2636        self.a |= value;
2637        self.p.set_nz(self.a);
2638    }
2639
2640    fn eor(&mut self, value: u8) {
2641        self.a ^= value;
2642        self.p.set_nz(self.a);
2643    }
2644
2645    fn bit(&mut self, value: u8) {
2646        let result = self.a & value;
2647        self.p.set(Status::ZERO, result == 0);
2648        self.p.set(Status::NEGATIVE, value & 0x80 != 0);
2649        self.p.set(Status::OVERFLOW, value & 0x40 != 0);
2650    }
2651
2652    fn adc(&mut self, value: u8) {
2653        let carry = u16::from(self.p.contains(Status::CARRY));
2654        let sum = u16::from(self.a) + u16::from(value) + carry;
2655        let result = sum as u8;
2656        self.p.set(Status::CARRY, sum > 0xFF);
2657        let overflow = ((self.a ^ result) & (value ^ result) & 0x80) != 0;
2658        self.p.set(Status::OVERFLOW, overflow);
2659        self.a = result;
2660        self.p.set_nz(self.a);
2661    }
2662
2663    fn sbc(&mut self, value: u8) {
2664        // SBC = ADC of inverted value.
2665        self.adc(value ^ 0xFF);
2666    }
2667
2668    fn cmp_with(&mut self, lhs: u8, rhs: u8) {
2669        let (r, borrow) = lhs.overflowing_sub(rhs);
2670        self.p.set(Status::CARRY, !borrow);
2671        self.p.set_nz(r);
2672    }
2673
2674    fn inc_addr<B: Bus>(&mut self, bus: &mut B, addr: u16) {
2675        let original = self.read1(bus, addr);
2676        // RMW dummy write: real 6502 writes the original byte back to the
2677        // same address before writing the modified value (visible at memory-
2678        // mapped registers like $4014 and $2007). See `docs/cpu-6502.md` and
2679        // nesdev wiki "Dummy writes".
2680        self.write1(bus, addr, original);
2681        let v = original.wrapping_add(1);
2682        self.write1(bus, addr, v);
2683        self.p.set_nz(v);
2684    }
2685
2686    fn dec_addr<B: Bus>(&mut self, bus: &mut B, addr: u16) {
2687        let original = self.read1(bus, addr);
2688        self.write1(bus, addr, original);
2689        let v = original.wrapping_sub(1);
2690        self.write1(bus, addr, v);
2691        self.p.set_nz(v);
2692    }
2693
2694    fn asl_value(&mut self, value: u8) -> u8 {
2695        self.p.set(Status::CARRY, value & 0x80 != 0);
2696        let r = value << 1;
2697        self.p.set_nz(r);
2698        r
2699    }
2700
2701    fn asl_addr<B: Bus>(&mut self, bus: &mut B, addr: u16) {
2702        let v = self.read1(bus, addr);
2703        // RMW dummy write — see `inc_addr`.
2704        self.write1(bus, addr, v);
2705        let r = self.asl_value(v);
2706        self.write1(bus, addr, r);
2707    }
2708
2709    fn lsr_value(&mut self, value: u8) -> u8 {
2710        self.p.set(Status::CARRY, value & 0x01 != 0);
2711        let r = value >> 1;
2712        self.p.set_nz(r);
2713        r
2714    }
2715
2716    fn lsr_addr<B: Bus>(&mut self, bus: &mut B, addr: u16) {
2717        let v = self.read1(bus, addr);
2718        self.write1(bus, addr, v);
2719        let r = self.lsr_value(v);
2720        self.write1(bus, addr, r);
2721    }
2722
2723    fn rol_value(&mut self, value: u8) -> u8 {
2724        let carry_in = u8::from(self.p.contains(Status::CARRY));
2725        self.p.set(Status::CARRY, value & 0x80 != 0);
2726        let r = (value << 1) | carry_in;
2727        self.p.set_nz(r);
2728        r
2729    }
2730
2731    fn rol_addr<B: Bus>(&mut self, bus: &mut B, addr: u16) {
2732        let v = self.read1(bus, addr);
2733        self.write1(bus, addr, v);
2734        let r = self.rol_value(v);
2735        self.write1(bus, addr, r);
2736    }
2737
2738    fn ror_value(&mut self, value: u8) -> u8 {
2739        let carry_in = u8::from(self.p.contains(Status::CARRY)) << 7;
2740        self.p.set(Status::CARRY, value & 0x01 != 0);
2741        let r = (value >> 1) | carry_in;
2742        self.p.set_nz(r);
2743        r
2744    }
2745
2746    fn ror_addr<B: Bus>(&mut self, bus: &mut B, addr: u16) {
2747        let v = self.read1(bus, addr);
2748        self.write1(bus, addr, v);
2749        let r = self.ror_value(v);
2750        self.write1(bus, addr, r);
2751    }
2752
2753    fn branch<B: Bus>(&mut self, bus: &mut B, offset: u8, condition: bool) -> u8 {
2754        if !condition {
2755            return 2;
2756        }
2757        // T-60-001 (2026-05-17 — 9th C1 attempt, branch axis): per
2758        // nesdev wiki §"CPU interrupts" §"Branch instructions", TAKEN
2759        // branches DELAY IRQ detection. Our `step()` samples IRQ at
2760        // the opcode-fetch (cycle 1 = tick 0) via `idle_tick`, then
2761        // each branch opcode sets `skip_irq_sample = true` before the
2762        // operand fetch to suppress sampling on the extra cycles. But
2763        // the cycle-1 sample is still recorded in `irq_first_tick`
2764        // and `promote_post_step_interrupts` will ARM the IRQ at the
2765        // end of this instruction (cycle-1 sample < last_tick on a
2766        // 3- or 4-cycle taken branch). That contradicts the
2767        // "branches delay IRQ" rule — IRQ should be deferred to the
2768        // NEXT instruction's poll. Drop the cycle-1 sample here on
2769        // taken branches; the next instruction's opcode fetch will
2770        // re-sample the (still-asserted, level-triggered) IRQ line
2771        // and arm it normally. NMI is edge-triggered and sampled on
2772        // every cycle the CPU is alive (per nesdev) — its first-tick
2773        // latch is intentionally NOT dropped.
2774        self.irq_first_tick = u8::MAX;
2775        // Canonical 6502 branch cycle sequence per nesdev wiki and
2776        // AccuracyCoin `CPU Behavior 2 :: Branch Dummy Reads` Test 4:
2777        //   C1: opcode fetch (done by `tick` dispatcher)
2778        //   C2: operand fetch (done by per-opcode `fetch_pc` before call)
2779        //   C3: dummy read of PC (the byte after the operand) — this is
2780        //       cycle 3 of the taken branch, and is observable as a
2781        //       second consecutive read of `$2002` mirror through which
2782        //       AccuracyCoin brackets the dummy.
2783        //   C4: (only if page-crossed) dummy read of (old_pch | new_pcl)
2784        //       — the unfixed-high-byte address before the high-byte
2785        //       carry propagates.
2786        let _ = self.read1(bus, self.pc); // C3 dummy
2787        let signed = offset as i8 as i16;
2788        let old_pc = self.pc;
2789        let new_pc = (self.pc as i32 + i32::from(signed)) as u16;
2790        let crossed = (old_pc & 0xFF00) != (new_pc & 0xFF00);
2791        if crossed {
2792            // W1 (`mc-r1-branch-poll-points`): a page-cross taken branch
2793            // polls a SECOND time at C4-start — TriCNES's
2794            // `PollInterrupts_CantDisableIRQ` in the BPL microcode
2795            // (`golden/tricnes/tricnes-full-src/Emulator.cs`): if the C2-start
2796            // poll already saw the IRQ this one cannot un-see it (can-SET-
2797            // not-clear). `mc_run_irq` is frozen across the branch's
2798            // remaining cycles by the `handle_interrupts` early-return, so
2799            // sample the live line here (state as of end-of-C3) and OR it in;
2800            // the end-of-C4 `mc_prev_run_irq` copy then exposes it to the
2801            // next `step()` dispatch.
2802            if !self.mc_run_irq {
2803                self.mc_run_irq = bus.irq_level() && !self.irq_sample_i_flag;
2804            }
2805            // C4 page-cross dummy read at the unfixed address.
2806            let dummy = (old_pc & 0xFF00) | (new_pc & 0x00FF);
2807            let _ = self.read1(bus, dummy);
2808        }
2809        self.pc = new_pc;
2810        if crossed { 4 } else { 3 }
2811    }
2812
2813    fn lax(&mut self, value: u8) {
2814        self.a = value;
2815        self.x = value;
2816        self.p.set_nz(value);
2817    }
2818
2819    fn dcp_addr<B: Bus>(&mut self, bus: &mut B, addr: u16) {
2820        let original = self.read1(bus, addr);
2821        // RMW dummy write.
2822        self.write1(bus, addr, original);
2823        let v = original.wrapping_sub(1);
2824        self.write1(bus, addr, v);
2825        self.cmp_with(self.a, v);
2826    }
2827
2828    fn isc_addr<B: Bus>(&mut self, bus: &mut B, addr: u16) {
2829        let original = self.read1(bus, addr);
2830        self.write1(bus, addr, original);
2831        let v = original.wrapping_add(1);
2832        self.write1(bus, addr, v);
2833        self.sbc(v);
2834    }
2835
2836    fn slo_addr<B: Bus>(&mut self, bus: &mut B, addr: u16) {
2837        let v = self.read1(bus, addr);
2838        self.write1(bus, addr, v);
2839        let r = self.asl_value(v);
2840        self.write1(bus, addr, r);
2841        self.a |= r;
2842        self.p.set_nz(self.a);
2843    }
2844
2845    fn rla_addr<B: Bus>(&mut self, bus: &mut B, addr: u16) {
2846        let v = self.read1(bus, addr);
2847        self.write1(bus, addr, v);
2848        let r = self.rol_value(v);
2849        self.write1(bus, addr, r);
2850        self.a &= r;
2851        self.p.set_nz(self.a);
2852    }
2853
2854    fn sre_addr<B: Bus>(&mut self, bus: &mut B, addr: u16) {
2855        let v = self.read1(bus, addr);
2856        self.write1(bus, addr, v);
2857        let r = self.lsr_value(v);
2858        self.write1(bus, addr, r);
2859        self.a ^= r;
2860        self.p.set_nz(self.a);
2861    }
2862
2863    fn rra_addr<B: Bus>(&mut self, bus: &mut B, addr: u16) {
2864        let v = self.read1(bus, addr);
2865        self.write1(bus, addr, v);
2866        let r = self.ror_value(v);
2867        self.write1(bus, addr, r);
2868        self.adc(r);
2869    }
2870}