Skip to main content

FrameCounter

Struct FrameCounter 

Source
pub struct FrameCounter {
    pub mode: Mode,
    pub irq_inhibit: bool,
    pub irq_flag: bool,
    pub irq_line_active: bool,
    pub apu_aligned: bool,
    /* private fields */
}
Expand description

Frame counter state.

Fields§

§mode: Mode

Current mode.

§irq_inhibit: bool

IRQ inhibit flag.

§irq_flag: bool

$4015 bit 6 visibility flag (cleared by reading $4015 or by writing $4017 with bit 6 set). Independent of the CPU IRQ line (see irq_line_active) since Session-26 Sprint 2 iter 5 (2026-05-23) — the AccuracyCoin APU Tests :: Frame Counter IRQ Tests I/J/K specifically test that with inhibit SET, $4015 bit 6 is still visible for 2 CPU cycles (29828, 29829) before clearing at cycle 29830. Mesen2 separates these two concepts: _irqFlag (this field) vs IRQSource::FrameCounter registration on the CPU’s _irqSource list. RustyNES previously conflated them into a single field, which broke the J/K axis. See docs/audit/session-26-sprint2-iter5-frame-counter-irq-split-2026-05-23.md.

§irq_line_active: bool

CPU IRQ line driver — true iff the frame counter is currently asserting an IRQ on the CPU’s _irqSource list (Mesen2’s IRQSource::FrameCounter registration). Set at FC steps 3, 4, 5 (cycles 29828, 29829, 29830) ONLY when not inhibited; cleared by $4015 read or $4017 inhibit-set. The CPU’s IRQ-poll path reads via Apu::irq_line, which ORs this with dmc.irq_flag. Distinct from irq_flag: when inhibited, irq_flag may be transiently set at cycles 29828-29829 to make $4015 bit 6 visible per Tests I/J/K, but irq_line_active stays false so no spurious IRQ fires on the CPU.

§apu_aligned: bool

apu_phase: false on cycle that aligns with APU clock, true otherwise. Used to time the $4017 reset delay (3 vs 4 cycles).

Implementations§

Source§

impl FrameCounter

Source

pub const fn new() -> Self

New frame counter (mode 0, IRQ enabled, cycle 0).

Source

pub fn reset_rewrite_4017(&mut self) -> u8

v2.0.0 beta.3 (A4 cycle-accurate reset): warm-reset with the hardware $4017 re-write. Per blargg’s apu_reset spec, the 2A03 reset sequence behaves as if the LAST value written to $4017 were written again: the retained last_4017 is re-issued through the normal write path (pending mode + the 3/4-cycle aligned delay, and — for a mode-1 value — the immediate quarter+half clock), then the sequencer restarts. The CPU’s subsequent 8-cycle reset delay (real clocked cycles on the master clock since Workstream A2) ages the re-armed counter so execution resumes ~9-12 cycles after the effective write — the window blargg’s 4017_timing brackets.

Two prior frame-granular re-arm attempts (see tests/apu_reset.rs’s history preamble) failed precisely because the reset was a function call with no clocked delay; this variant exists on the one-clock sequence path (promoted to the only path in beta.4).

Source

pub fn write(&mut self, value: u8, apu_aligned: bool)

$4017 write. apu_aligned is true if the current CPU cycle is also an APU cycle (i.e., even CPU-cycle alignment). The reset happens 3 or 4 CPU cycles later depending on alignment.

Source

pub fn read_status(&mut self, cpu_cycle: u64, apu_aligned: bool) -> bool

Reading $4015 returns the current frame IRQ flag value and SCHEDULES a future clear that matures one or two CPU cycles later, mirroring Mesen2’s ApuFrameCounter::GetIrqFlag lazy algorithm (Core/NES/APU/ApuFrameCounter.h lines 214-227).

Semantics:

  • If irq_flag is true and no clear is scheduled, schedule irq_flag_clear_cycle = cpu_cycle + delta where delta = 1 on a RustyNES “get” cycle (apu_phase=true) and delta = 2 on a “put” cycle (apu_phase=false). Return the OLD flag value (true).
  • If a schedule is already pending and cpu_cycle >= irq_flag_clear_cycle, perform the clear NOW (the silicon observed enough APU clocks since the read) and return the freshly-cleared flag (false).
  • If no flag is set, return false (no schedule needed).

The delta polarity is INVERTED vs Mesen2’s (clock & 0x01) ? 2 : 1 because RustyNES’s apu_phase polarity at the $4015 read site is opposite to Mesen2’s master-clock parity. Verified against the frame-counter-irq.nes oracle pair (Session-25, 2026-05-23).

cpu_cycle is the bus’s CPU-cycle counter at the moment of the read (passed in from apu.rs::read_status). apu_aligned is self.apu_phase of the APU at the same moment (also passed in from apu.rs::read_status).

Source

pub fn tick(&mut self, cpu_cycle: u64, apu_aligned: bool) -> FrameEvents

One CPU clock — return any frame-counter events fired by this cycle.

cpu_cycle is the bus’s CPU-cycle counter for the cycle being ticked (the post-increment value, since apu.tick_with_external advances apu.cpu_cycle BEFORE invoking this). apu_aligned: true iff this CPU cycle is also an APU “get” cycle. The lazy $4015 IRQ clear matures here if cpu_cycle >= irq_flag_clear_cycle; the per-frame step events fire as before.

Trait Implementations§

Source§

impl Clone for FrameCounter

Source§

fn clone(&self) -> FrameCounter

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for FrameCounter

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for FrameCounter

Source§

fn default() -> Self

Returns the “default value” for a type. Read more
Source§

impl Copy for FrameCounter

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.