Skip to content

Lockstep Scheduler

DoubleGate edited this page Jun 29, 2026 · 1 revision

Lockstep Scheduler

References: docs/scheduler.md

The scheduler is the heart of RustyNES. It lives in crates/rustynes-core and owns the primary emulation run loop. The scheduler is what makes RustyNES cycle-accurate and capable of passing the most rigorous testing suites (Mesen2 / higan / ares bar).

The PPU is the Master Clock

In most simpler emulators, the CPU runs an instruction, counts the cycles, and then tells the PPU to "catch up." This creates race conditions when PPU events (like the Sprite Zero Hit) happen in the middle of a CPU instruction.

In RustyNES, one tick equals one PPU dot.

  • The scheduler advances one PPU dot per tick_one_dot().
  • The CPU advances on every third dot (NTSC).
  • The APU advances every other CPU cycle.

Code Pattern

fn tick_one_dot(&mut self) {
    self.ppu.tick(&mut self.ppu_bus);
    self.dot_count += 1;
    // ... CPU and APU scheduling based on dot_count ...
}

The Single Bus Model

To avoid complex borrow-checker issues, RustyNES uses a single mutable Bus struct that owns the PPU, APU, Mappers, WRAM, and Controllers. The CPU merely borrows a mutable reference to the Bus during its tick.

This guarantees:

  1. Safe memory access without RefCell overhead.
  2. Perfect deterministic execution.
  3. Mid-instruction PPU events (like MMC3 IRQs at PPU dot 260 or mid-scanline scroll writes) work automatically without per-game hacks.

Timebase Refactor (v2.0.0)

The forward roadmap includes a migration from the current "3 dots per CPU cycle" model to an integer master clock (fractional timebase) to close the remaining esoteric edge cases (e.g. PAL 3.2:1 ratios), cementing a 100% accurate phase alignment model.

Clone this wiki locally