pub struct Nes { /* private fields */ }Expand description
Top-level NES emulator handle.
Owns the CPU, PPU, mapper, RAM, and controller stub. Construct via
Nes::from_rom; drive forward via Nes::run_frame or
Nes::step_instruction. The framebuffer can be sampled at any time via
Nes::framebuffer.
Implementations§
Source§impl Nes
impl Nes
Sourcepub const TRACE_CAP: usize = 50_000
pub const TRACE_CAP: usize = 50_000
Maximum cycle-trace ring depth (oldest records drop past this).
Sourcepub const fn timeline_generation(&self) -> u64
pub const fn timeline_generation(&self) -> u64
The current timeline generation — see the field’s documentation.
A consumer holds the last value it saw and clears itself when this
differs. It is meaningless to compare across two different Nes
instances: a fresh one starts at zero, which is why ROM changes are
handled by their own hook (DebuggerOverlay::clear_rom_bound_analysis)
rather than by this counter.
Sourcepub fn sram_mut(&mut self) -> &mut [u8] ⓘ
pub fn sram_mut(&mut self) -> &mut [u8] ⓘ
Returns a mutable reference to the cartridge SRAM (if any).
Sourcepub fn save_data(&self) -> &[u8] ⓘ
pub fn save_data(&self) -> &[u8] ⓘ
v2.9.6 — the cartridge’s non-volatile data, what a battery save
persists: Self::sram on almost every board, and the flash image on
a self-flashable one (GTROM, a flashable UNROM 512), which has no RAM
at $6000. Hosts that persist saves read and restore THIS, gated on
Self::has_battery. sram() stays the $6000 RAM that memory maps
describe. See Mapper::save_data.
Sourcepub fn save_data_mut(&mut self) -> &mut [u8] ⓘ
pub fn save_data_mut(&mut self) -> &mut [u8] ⓘ
Mutable Self::save_data, for loading a save.
Sourcepub fn clear_save_data(&mut self)
pub fn clear_save_data(&mut self)
v2.9.6 — return the save data to a never-saved cartridge: zeroed RAM, or the flash image as loaded.
Sourcepub const fn has_battery(&self) -> bool
pub const fn has_battery(&self) -> bool
v2.7.3 — whether the cartridge has non-volatile save data: the header
declares battery-backed PRG-RAM (iNES flags 6 bit 1), or (v2.9.6) the
board is self-flashable and its flash is the save (GTROM, mapper 111,
and flashable UNROM 512, mapper 30, whose headers need not set the
bit). Persist Self::save_data when this is true.
Self::sram is not the same question: several boards expose their
work RAM through it whatever the header says (NROM returns its 8 KiB
PRG-RAM for every image, and MMC1 and MMC3 allocate RAM by default). A
host that persists save RAM to disk — the desktop frontend’s .sav
files — must gate on this, or a volatile cartridge acquires a save it
never had and restores stale RAM across power cycles. Always false
for an FDS disk (which has its own writable-disk save) and an NSF.
Sourcepub fn vram_mut(&mut self) -> &mut [u8] ⓘ
pub fn vram_mut(&mut self) -> &mut [u8] ⓘ
Returns a mutable reference to the internal VRAM (nametables).
Sourcepub fn from_disk(disk_bytes: &[u8], bios_bytes: &[u8]) -> Result<Self, RomError>
pub fn from_disk(disk_bytes: &[u8], bios_bytes: &[u8]) -> Result<Self, RomError>
Build an emulator from a Famicom Disk System .fds disk image and a
user-supplied 8 KiB BIOS (disksys.rom).
The BIOS is never committed to this repo (it is Nintendo IP); the caller
supplies it (a frontend BIOS prompt is Stage 2). Construction parses the
disk container, builds the FDS device as the bus’s mapper, and runs the
standard cold-boot reset (the BIOS reset vector at $FFFC drives the
disk-load sequence).
Uses the default 44.1 kHz audio sample rate; use
Nes::from_disk_with_sample_rate to pick the rate.
§Errors
Returns the underlying RomError if the disk image is unparseable or
the BIOS is not exactly 8 KiB.
Sourcepub fn from_disk_with_sample_rate(
disk_bytes: &[u8],
bios_bytes: &[u8],
sample_rate: u32,
) -> Result<Self, RomError>
pub fn from_disk_with_sample_rate( disk_bytes: &[u8], bios_bytes: &[u8], sample_rate: u32, ) -> Result<Self, RomError>
Build an FDS emulator with an explicit audio sample rate. See
Nes::from_disk.
The reported rom_sha256 hashes the disk-image bytes (not the BIOS), so
save-states / movies key off the disk the way cartridge builds key off
the ROM. A host booting a SAVED (game-written) copy of the disk passes
the pristine image’s hash to Nes::set_rom_identity afterwards, so
the identity does not move with the game’s disk saves (v2.9.9, NF-17).
§Errors
Returns the underlying RomError if the disk image is unparseable or
the BIOS is not exactly 8 KiB.
Sourcepub fn from_nsf(nsf_bytes: &[u8]) -> Result<Self, RomError>
pub fn from_nsf(nsf_bytes: &[u8]) -> Result<Self, RomError>
Build an emulator that plays an NSF-family music file: the classic
NESM\x1a container or the chunked NSFE one, told apart by their magic
in rustynes_mappers::parse_nsf. Expansion-chip audio declared in the
header (VRC6, VRC7, FDS, MMC5, N163, 5B) is synthesized by the same cores
the cartridge boards use. (Until v2.9.3 this comment said NSFe and
expansion audio were deferred, which stopped being true in v2.1.x.)
NSF files carry a ripped NES sound engine plus an init/play address
pair, not a PPU program. Construction parses the file, installs a
rustynes_mappers::NsfMapper (a synthetic 6502 driver + the program
image) as the bus’s mapper, and runs the standard cold-boot reset — the
driver’s reset vector calls init for the starting song. At the standard
60 Hz rate the driver enables vblank NMI and the NMI calls play once per
frame; at any other header rate a mapper cycle-timer IRQ calls it
instead (see docs/mappers.md). Audio is produced through the unchanged
lockstep loop; there is no video.
Uses the default 44.1 kHz sample rate; see
Nes::from_nsf_with_sample_rate.
§Errors
Returns the underlying RomError when the NSF header is malformed.
Sourcepub fn from_nsf_with_sample_rate(
nsf_bytes: &[u8],
sample_rate: u32,
) -> Result<Self, RomError>
pub fn from_nsf_with_sample_rate( nsf_bytes: &[u8], sample_rate: u32, ) -> Result<Self, RomError>
Build an NSF player with an explicit audio sample rate. See
Nes::from_nsf.
§Errors
Returns the underlying RomError when the NSF header is malformed.
Sourcepub fn nsf_song_count(&self) -> u8
pub fn nsf_song_count(&self) -> u8
Number of selectable songs in the loaded NSF (0 for a cartridge / disk).
Sourcepub fn nsf_current_song(&self) -> u8
pub fn nsf_current_song(&self) -> u8
The currently-selected 0-based NSF song (0 for a cartridge / disk).
Sourcepub fn nsf_set_song(&mut self, song: u8)
pub fn nsf_set_song(&mut self, song: u8)
Select a 0-based NSF song and restart playback on it (re-runs init via
a warm reset). No-op for a cartridge / disk.
Sourcepub fn from_rom_with_power_on_seed(
bytes: &[u8],
seed: u64,
) -> Result<Self, RomError>
pub fn from_rom_with_power_on_seed( bytes: &[u8], seed: u64, ) -> Result<Self, RomError>
Build an emulator with a randomized power-on RAM state (developer mode; Phase 7 / T-72-005).
Identical to Nes::from_rom except the 2 KiB CPU work RAM and the
open-bus latch are filled from a deterministic xorshift64 PRNG keyed
on seed, modelling the unreliable power-on RAM of real hardware
(nesdev “CPU power up state”). Use this to shake out game/test code
that depends on a particular post-power-on RAM pattern.
The randomization is seeded and deterministic — the same seed
yields the same state, so the same seed + ROM + input ⇒ bit-identical
contract still holds. The default Nes::from_rom (zeroed RAM) is
what CI, the regression oracle, and save-state tests use.
§Errors
Returns the underlying RomError if the bytes don’t parse.
Sourcepub fn from_rom_with_power_on_config(
bytes: &[u8],
config: PowerOnConfig,
) -> Result<Self, RomError>
pub fn from_rom_with_power_on_config( bytes: &[u8], config: PowerOnConfig, ) -> Result<Self, RomError>
v2.1.7 P5 — build an emulator with an explicit PowerOnConfig.
Generalizes Nes::from_rom_with_power_on_seed: the caller chooses the
power-on work-RAM fill (PowerOnRam::Zeroed / PowerOnRam::Seeded /
PowerOnRam::Filled). The config is stored on the bus so a subsequent
power-cycle re-applies the same fill (keeping power_cycle == fresh boot). All fills are deterministic, so the same config + ROM + input ⇒ bit-identical contract still holds. PowerOnConfig::default
(PowerOnRam::Zeroed) is byte-identical to Nes::from_rom.
§Errors
Returns the underlying RomError if the bytes don’t parse.
Sourcepub fn power_cycle(&mut self)
pub fn power_cycle(&mut self)
Power-cycle (cold boot). Zeroes WRAM, re-rolls phase, reloads vectors.
A cold boot of the CONSOLE, not of its configuration. The PPU and the
APU are rebuilt to their power-on state, and since v2.9.8 every host
setting stored on them survives: the custom or generated palette
(Self::set_custom_palette), the overclock scanlines
(Self::set_extra_scanlines), the fast dot path
(Self::set_fast_dotloop), the OAM-decay model
(Self::set_oam_decay), the APU channel mask, per-channel gain and
filter model, and the armed state of the provenance stores (emptied
below). The settings stored on the bus survived already: the die
revisions, the console model, the Vs. DIP switches and PPU type, the
mirroring override and the Four Score. The power-on FILLS
(Self::set_power_on_ram, Self::set_power_up_palette) are
re-applied, as a fresh boot applies them. What a cold boot does clear
is console state: RAM, registers, the mapper (battery RAM excepted),
the controllers’ latches, and any non-standard input device, which the
host re-attaches. Until v2.9.8 the PPU and APU settings reverted to
their defaults, and each host had to re-push them.
Sourcepub fn run_frame(&mut self) -> &[u8] ⓘ
pub fn run_frame(&mut self) -> &[u8] ⓘ
Run until the PPU finishes a frame. Returns the framebuffer slice.
A CPU JAM ends the call early, as the 150,000-cycle budget does: the
returned framebuffer is whatever the PPU had drawn, and later calls
return at once until a reset. It does not panic (until v2.9.9 this
section said it did; NC-15). Check Self::cpu’s is_jammed to tell
a JAM from a completed frame.
Sourcepub const fn set_rewind_capture(&mut self, enabled: bool)
pub const fn set_rewind_capture(&mut self, enabled: bool)
v2.8.0 Phase 3 — enable/disable the per-frame rewind capture while
the ring stays armed. Run-ahead turns it off around its hidden +
visible frames so only persistent-timeline frames land in the ring.
Default true; with no rewind ring armed this is a no-op.
Sourcepub const fn rewind_capture_enabled(&self) -> bool
pub const fn rewind_capture_enabled(&self) -> bool
Whether the per-frame rewind capture is currently armed.
Added in v2.3.6 so a caller that needs to suppress capture temporarily can
save and restore the caller’s setting rather than assume the default.
rustynes-probe does exactly that around a trial: its replayed frames
never happened on the user’s timeline, so they must not enter the ring —
but nor may re-enabling capture afterwards turn it on for someone who had
deliberately turned it off. Run-ahead predates this and still restores an
unconditional true, which is correct only because nothing else disables
capture today.
Sourcepub fn step_instruction(&mut self) -> u8
pub fn step_instruction(&mut self) -> u8
Step exactly one CPU instruction. For debuggers / step-through tools.
Sourcepub fn add_breakpoint(&mut self, addr: u16)
pub fn add_breakpoint(&mut self, addr: u16)
v1.1.0 beta.2 (Workstream C) — add an exec/PC breakpoint at addr.
Nes::run_frame stops the frame the next time the program counter
reaches addr (reportable via Nes::take_break_hit). Idempotent.
debug-hooks only.
Sourcepub fn remove_breakpoint(&mut self, addr: u16)
pub fn remove_breakpoint(&mut self, addr: u16)
Remove a previously-added exec breakpoint (no-op if absent).
Sourcepub fn clear_breakpoints(&mut self)
pub fn clear_breakpoints(&mut self)
Remove all breakpoints.
Sourcepub fn breakpoints(&self) -> &[u16]
pub fn breakpoints(&self) -> &[u16]
The current exec breakpoints (insertion order).
Sourcepub const fn set_breakpoints_enabled(&mut self, enabled: bool)
pub const fn set_breakpoints_enabled(&mut self, enabled: bool)
Arm/disarm breakpoint checking without discarding the list. Default on.
Sourcepub const fn breakpoints_enabled(&self) -> bool
pub const fn breakpoints_enabled(&self) -> bool
Whether breakpoint checking is armed.
Sourcepub const fn take_break_hit(&mut self) -> Option<u16>
pub const fn take_break_hit(&mut self) -> Option<u16>
Take the PC that last hit a breakpoint (cleared on read). The frontend
polls this after Nes::run_frame to pause when a breakpoint fired.
Sourcepub const fn set_event_breakpoints(&mut self, mask: u16)
pub const fn set_event_breakpoints(&mut self, mask: u16)
v1.4.0 Workstream D (D2) — arm the event-driven breakpoint categories
(a bit-OR of crate::EventBpKind::bit). 0 (default) disarms every
category — the per-access taps are then a single cheap mask == 0
early-out. Output-only: a hit pauses + reports but never mutates state.
debug-hooks only.
Sourcepub const fn event_breakpoints(&self) -> u16
pub const fn event_breakpoints(&self) -> u16
The armed event-breakpoint category mask.
Sourcepub const fn take_event_break_hit(&mut self) -> Option<EventBreakHit>
pub const fn take_event_break_hit(&mut self) -> Option<EventBreakHit>
Take the first event-breakpoint hit of the current frame (cleared on
read). The frontend polls this after Nes::run_frame to pause when an
armed hardware event fired, reporting its kind + frame/cycle/scanline/dot.
Sourcepub const fn set_trace_enabled(&mut self, enabled: bool)
pub const fn set_trace_enabled(&mut self, enabled: bool)
v1.1.0 beta.2 (T-110-C2) — start/stop the cycle-trace logger. While on,
each executed instruction’s CPU state is pushed to a ring buffer (capped
at Self::TRACE_CAP). Default off.
Sourcepub const fn trace_enabled(&self) -> bool
pub const fn trace_enabled(&self) -> bool
Whether the cycle-trace logger is recording.
Sourcepub fn clear_trace(&mut self)
pub fn clear_trace(&mut self)
Clear the trace ring.
Sourcepub fn trace_records(&self) -> Vec<TraceRec>
pub fn trace_records(&self) -> Vec<TraceRec>
Copy the trace ring oldest-first (for the trace panel / file export).
Sourcepub fn trace_tail_vec(&self, n: usize) -> Vec<TraceRec>
pub fn trace_tail_vec(&self, n: usize) -> Vec<TraceRec>
Copy the most recent n trace records (oldest-first) — for the live
trace panel’s tail view, cheaper than Self::trace_records on a full
ring.
Sourcepub const fn set_event_logging(&mut self, enabled: bool)
pub const fn set_event_logging(&mut self, enabled: bool)
v1.1.0 beta.2 (T-110-C3) — start/stop the event viewer. While on, the bus
records this frame’s PPU/APU/mapper writes (with their PPU position); the
log is reset at each Self::run_frame. Default off; output-only.
Sourcepub const fn event_logging(&self) -> bool
pub const fn event_logging(&self) -> bool
Whether the event viewer is recording.
Sourcepub fn events(&self) -> &[EventRec]
pub fn events(&self) -> &[EventRec]
The current frame’s captured events (for the event-viewer panel).
Sourcepub fn set_write_attribution(&mut self, enabled: bool)
pub fn set_write_attribution(&mut self, enabled: bool)
v2.3.2 “Lucid” — arm or disarm per-byte write attribution for the PPU’s own memories (CIRAM, OAM, palette RAM).
While armed, every write to those memories is stamped with the program
counter and CPU cycle of the instruction that performed it, which is the
edge the pixel-provenance panel walks to answer “which instruction put
this byte here?”. The Event Viewer records the CPU-side $2000-$3FFF
write and the memory-access counter records a cycle stamp, but neither
carries the PC and the resolved destination — see
rustynes_ppu::provenance for why the two halves are recorded on
opposite sides of the bus.
Default off. Arming allocates
rustynes_ppu::WriteAttribution::HEAP_BYTES; disarming frees it.
Output-only, so emulation is bit-identical either way.
Sourcepub fn write_attribution(&self) -> Option<&WriteAttribution>
pub fn write_attribution(&self) -> Option<&WriteAttribution>
The write-attribution store, or None when not armed.
Sourcepub fn clear_pixel_provenance(&mut self)
pub fn clear_pixel_provenance(&mut self)
Forget the current frame’s per-pixel provenance, keeping it armed.
Called automatically on power-cycle and on both restore paths; exposed so a host that rewinds by other means can do the same.
Sourcepub fn clear_write_attribution(&mut self)
pub fn clear_write_attribution(&mut self)
Forget every recorded write attribution, keeping the store armed.
Call this after a save-state restore or a power-cycle: the restored bytes were not written by any instruction this session executed, and reporting the PCs that wrote those offsets before the restore would be a confidently wrong answer rather than an absent one.
Sourcepub fn set_pixel_provenance(&mut self, enabled: bool)
pub fn set_pixel_provenance(&mut self, enabled: bool)
v2.3.2 “Lucid” phase 2 — arm or disarm per-pixel provenance.
While armed, every emitted pixel records the layer that won the priority
decision, the exact $3Fxx palette address behind its color, and the
nametable / attribute / pattern addresses of the tile actually on
screen — which v cannot answer, because by display time it has
advanced two tiles past the pixel.
Composes with Self::set_write_attribution: provenance says which
bytes produced a pixel, attribution says which instruction wrote them.
Each is useful alone; together they are the full causal chain.
Default off. Arming allocates
rustynes_ppu::PixelProvenanceFrame::HEAP_BYTES. Output-only, so
emulation is bit-identical either way.
Sourcepub fn pixel_provenance(&self) -> Option<&PixelProvenanceFrame>
pub fn pixel_provenance(&self) -> Option<&PixelProvenanceFrame>
The current frame’s per-pixel provenance, or None when not armed.
Sourcepub fn set_audio_provenance(&mut self, enabled: bool)
pub fn set_audio_provenance(&mut self, enabled: bool)
Arm or disarm audio provenance (v2.3.7 “Overtone”).
Off by default. Arming allocates the per-register write attribution and the per-CPU-cycle mix trace; disarming frees both. Output-only — nothing recorded is read back into synthesis or carried in the save state, so the deterministic audio contract is unaffected either way.
Sourcepub const fn audio_provenance_armed(&self) -> bool
pub const fn audio_provenance_armed(&self) -> bool
Whether audio provenance is armed.
Sourcepub fn register_attribution(&self) -> Option<&RegisterAttribution>
pub fn register_attribution(&self) -> Option<&RegisterAttribution>
The per-register write attribution — which instruction last wrote each of
$4000-$4017 — or None when disarmed.
Sourcepub fn mix_trace(&self) -> Option<&MixTrace>
pub fn mix_trace(&self) -> Option<&MixTrace>
This frame’s per-CPU-cycle mix trace, or None when disarmed.
Sourcepub fn take_audio_provenance(&mut self) -> AudioProvenanceStash
pub fn take_audio_provenance(&mut self) -> AudioProvenanceStash
Lift the audio provenance stores out for a same-timeline restore.
The audio counterpart of Self::take_provenance, and it exists for the
identical reason: run-ahead’s rollback runs AFTER the visible frame is
produced and BEFORE the frontend releases the emulator lock, so a store
the restore clears can never be observed by the UI. That is exactly how
Pixel Provenance shipped non-functional from v2.3.2 to v2.3.6. Take
before the restore, Self::put_audio_provenance after.
Save-state loads and netplay rollback still clear, unchanged — those are genuine timeline changes. Run-ahead’s rollback is not.
Sourcepub fn put_audio_provenance(&mut self, stash: AudioProvenanceStash)
pub fn put_audio_provenance(&mut self, stash: AudioProvenanceStash)
Put back stores taken by Self::take_audio_provenance.
Sourcepub const fn take_provenance(&mut self) -> ProvenanceStash
pub const fn take_provenance(&mut self) -> ProvenanceStash
v2.3.6 — move both provenance stores out, leaving them unarmed.
For a host that performs a same-timeline restore whose result the user
is about to inspect. Self::restore and Self::restore_quiet both
clear the stores, which is correct when the restore replaces the timeline
the records describe — and wrong for run-ahead, whose rollback is the last
thing before the UI reads, so the clear discards the record for the frame
actually on screen. Take before the restore, Self::put_provenance
after.
A move, not a copy: the stores are boxed, so this is two pointer moves.
See rustynes_ppu::ProvenanceStash.
Sourcepub fn put_provenance(&mut self, stash: ProvenanceStash)
pub fn put_provenance(&mut self, stash: ProvenanceStash)
Put back stores taken by Self::take_provenance.
Sourcepub fn ciram_offset_for_nametable_addr(&self, addr: u16) -> Option<usize>
pub fn ciram_offset_for_nametable_addr(&self, addr: u16) -> Option<usize>
Resolve a PPU-space nametable address ($2000-$3EFF) to the physical
internal-CIRAM offset it reads, applying the mapper’s mirroring and any
per-game mirroring override.
This is what turns a rustynes_ppu::PixelProvenance::nt_addr into the
key rustynes_ppu::WriteAttribution::ciram is indexed by, so the
provenance panel can go from “this pixel’s tile came from $2002” to
“and instruction X wrote that byte” without reimplementing mirroring.
Returns None for an address outside the nametable window.
§Boards with mapper-supplied nametable memory
On MMC5 (ExRAM nametables) and 4-screen boards, some nametable writes are
absorbed by the mapper via PpuBus::write_nametable and never reach
internal CIRAM. This function still returns the CIRAM offset the standard
mirroring would select, because the only way to know whether the mapper
absorbed a particular write is to perform one — write_nametable takes
&mut self and has side effects, and a read-only query has no business
inventing one. Callers should treat a missing attribution on such a board
as “the mapper owns this byte”, which is what it means.
Sourcepub const fn set_access_logging(&mut self, enabled: bool)
pub const fn set_access_logging(&mut self, enabled: bool)
v1.1.0 beta.3 (T-110-E2) — start/stop the Lua bus-access log. While on,
the bus records this frame’s CPU reads + writes (with values); the log is
reset at each Self::run_frame. Default off; output-only. Enabled by
the scripting engine only while onRead/onWrite callbacks exist.
Sourcepub const fn access_logging(&self) -> bool
pub const fn access_logging(&self) -> bool
Whether the bus-access log is recording.
Sourcepub fn accesses(&self) -> &[AccessRec]
pub fn accesses(&self) -> &[AccessRec]
The current frame’s captured CPU bus accesses (for the Lua engine).
Sourcepub const fn set_exec_logging(&mut self, enabled: bool)
pub const fn set_exec_logging(&mut self, enabled: bool)
v1.1.0 beta.3 (T-110-E2) — start/stop the per-frame exec-PC log for the
Lua onExec callback. Independent of the Trace Logger (set_trace_enabled),
so enabling it does not disturb the debugger’s trace recording. Cleared
every Self::run_frame; output-only.
Sourcepub const fn exec_logging(&self) -> bool
pub const fn exec_logging(&self) -> bool
Whether the per-frame exec-PC log is recording.
Sourcepub fn exec_log(&self) -> &[u16]
pub fn exec_log(&self) -> &[u16]
This frame’s executed PCs, in execution order (for the Lua engine).
Sourcepub const fn was_input_polled_this_frame(&self) -> bool
pub const fn was_input_polled_this_frame(&self) -> bool
true if the running program read a controller port ($4016/$4017)
during the most recent Self::run_frame — the inverse of a TAStudio
“lag frame” (v1.6.0 Workstream A3). The greenzone / piano-roll lag log
queries this each frame. Output-only; debug-hooks-gated, so the
shipped build is byte-identical and the determinism contract holds.
Sourcepub const fn set_interrupt_logging(&mut self, enabled: bool)
pub const fn set_interrupt_logging(&mut self, enabled: bool)
v1.2.0 (T-110-E1) — start/stop the per-frame interrupt-service log for
the Lua onNmi / onIrq callbacks. The log records this frame’s
committed NMI / IRQ / BRK service entries (captured at the CPU’s
service-vector commit point, NOT the speculative poll sampler); it is
cleared at each Self::run_frame. Default off; output-only. Enabled by
the scripting engine only while onNmi/onIrq callbacks exist. Mirrors
Self::set_exec_logging.
Sourcepub const fn interrupt_logging(&self) -> bool
pub const fn interrupt_logging(&self) -> bool
Whether the per-frame interrupt-service log is recording.
Sourcepub fn interrupt_log(&self) -> &[InterruptRec]
pub fn interrupt_log(&self) -> &[InterruptRec]
This frame’s committed interrupt-service entries, in service order (for
the Lua engine). Mirrors Self::exec_log / Self::accesses.
Sourcepub fn framebuffer(&self) -> &[u8] ⓘ
pub fn framebuffer(&self) -> &[u8] ⓘ
Borrow the framebuffer (RGBA8, 256x240).
Sourcepub fn debug_set_framebuffer(&mut self, rgba: &[u8])
pub fn debug_set_framebuffer(&mut self, rgba: &[u8])
v1.7.0 “Forge” Workstream B (B3) — overwrite the RGBA8 output framebuffer
(the Lua emu:setScreenBuffer(t) paints output only). Output-only; see
rustynes_ppu::Ppu::debug_set_framebuffer. Reached only through the
script crate’s gated post-frame path, so the shipped build is
byte-identical and the determinism contract holds. debug-hooks-gated.
Sourcepub fn index_framebuffer(&self) -> &[u16]
pub fn index_framebuffer(&self) -> &[u16]
Borrow the parallel palette-index framebuffer (256x240 u16s, each
(emphasis << 6) | colour) for the NES_NTSC composite filter.
See rustynes_ppu::Ppu::index_framebuffer.
Sourcepub fn hd_tile_source(&self) -> &[HdTileSource]
pub fn hd_tile_source(&self) -> &[HdTileSource]
v1.2.0 C3 (hd-pack) — borrow the per-pixel HD-pack tile-source buffer
(256x240 rustynes_ppu::HdTileSource records). Each entry names the
CHR tile that produced the pixel; the frontend HD-pack loader groups
these by 8x8 cell, hashes the CHR bytes, and substitutes hi-res tiles.
Output-only telemetry; the determinism contract is unaffected. See
rustynes_ppu::Ppu::hd_tile_source.
Sourcepub const fn hd_bg_scroll(&self) -> (i32, i32)
pub const fn hd_bg_scroll(&self) -> (i32, i32)
The frame’s background scroll (x, y) in NES pixels, for offsetting
parallax HD-pack <background> layers (see
rustynes_ppu::Ppu::hd_bg_scroll). Output-only.
Sourcepub const fn ntsc_phase(&self) -> u8
pub const fn ntsc_phase(&self) -> u8
The per-frame NTSC composite colour phase consumed by the NES_NTSC
filter (0..=2 on NTSC; frame parity 0..=1 on PAL/Dendy). See
rustynes_ppu::Ppu::ntsc_phase.
Sourcepub const fn frame(&self) -> u64
pub const fn frame(&self) -> u64
The completed-frame counter (PPU frames since power-on). A monotonic, deterministic, save-state-restored value — the frontend uses it to phase turbo/autofire so the strobe is reproducible under rollback / TAS replay.
Sourcepub const fn bus_mut(&mut self) -> &mut SystemBus
pub const fn bus_mut(&mut self) -> &mut SystemBus
Mutably borrow the underlying bus (debugger / tests).
Sourcepub const fn is_jammed(&self) -> bool
pub const fn is_jammed(&self) -> bool
true when the CPU has executed a JAM/KIL/STP and is halted.
(v2.0.0 beta.5: the VsDualSystem soft-lockstep driver guards its
per-instruction stepping — the pre-existing debugger
Self::step_instruction — on this.)
Sourcepub const fn region(&self) -> Region
pub const fn region(&self) -> Region
Cartridge region (NTSC / PAL / Dendy / Multi). Drives wall-clock frame pacing in the frontend and clock dividers in the chip cores.
Sourcepub const fn prg_rom_len(&self) -> usize
pub const fn prg_rom_len(&self) -> usize
Length in bytes of the loaded cartridge’s PRG-ROM (read-only metadata).
Exposed for the Lua scripting cart:prg_size() query (and any other
read-only consumer); does not touch deterministic state.
Sourcepub const fn chr_rom_len(&self) -> usize
pub const fn chr_rom_len(&self) -> usize
Length in bytes of the loaded cartridge’s CHR-ROM (0 when the board uses
CHR-RAM). Read-only metadata; backs the Lua cart:chr_size() query.
Sourcepub const fn mapper_id(&self) -> u16
pub const fn mapper_id(&self) -> u16
The loaded cartridge’s iNES / NES 2.0 mapper id, after any load-time
header correction (backs the Lua cart:mapper_id(), the ROM-info
panel and the mobile RomInfo). A Famicom Disk System image reports
20 and an NSF 31, the ids their synthetic cartridges carry.
v2.9.8 — read from the cartridge. It used to read the mapper’s debug
view, whose default debug_info names mapper 0, so every board
without its own override (UxROM, CNROM, AxROM, …) reported 0, and
an NSF reported 0 rather than 31.
Sourcepub const fn submapper(&self) -> u8
pub const fn submapper(&self) -> u8
v2.9.8 — the loaded cartridge’s NES 2.0 submapper (0 for an iNES 1.0 image, which has none), after any load-time header correction.
Sourcepub const fn frame_duration(&self) -> Duration
pub const fn frame_duration(&self) -> Duration
Wall-clock frame duration for this cartridge’s region. The frontend
uses this to pace emulator advance independently of monitor refresh
rate — without it, Fifo present mode on a 144 Hz monitor would
run the emulator 2.4× too fast.
Sourcepub fn drain_audio(&mut self) -> Vec<f32>
pub fn drain_audio(&mut self) -> Vec<f32>
Drain accumulated audio samples at the host sample rate. Call once per frame from the frontend’s audio thread or batch driver.
The samples are bipolar and DC-blocked, not the mixer’s raw
[0.0, ~1.0]: measured over 900 frames (v2.8.1), the 2A03 alone spans
about -0.385..0.223 with a zero mean, and expansion audio reaches
further (a Namco 163 channel, +/-0.870). Nothing measured passes
+/-1.0, so a frontend converting to i16 should put full scale at
1.0. This comment said [0.0, ~1.0] until v2.8.1.
Set the buttons currently held on player port. Ports 0/1 are the
standard controllers ($4016/$4017); ports 2/3 are players 3/4 on
the Four Score adapter (only polled when Self::set_four_score is
on). The change takes effect on the next strobe edge.
§Panics
Panics if port is not in 0..=3.
Get the buttons currently held on player port (0/1 = $4016/$4017;
2/3 = Four Score players 3/4). Read-only; does not advance emulator
state.
Used by the TAS movie recorder (crate::movie) to capture the inputs
applied before each Self::run_frame. (Movies record players 1 & 2;
Four Score players 3/4 are not part of the .rnm stream.)
§Panics
Panics if port is not in 0..=3.
Sourcepub const fn set_four_score(&mut self, enabled: bool)
pub const fn set_four_score(&mut self, enabled: bool)
Enable/disable the Four Score 4-player adapter. Off by default; while
off, controller reads are byte-identical to the standard two-pad
behavior (the determinism contract and save-states are unaffected).
When on, players 3/4 (ports 2/3) are multiplexed onto $4016/$4017
across a 24-read serial sequence.
Sourcepub const fn four_score(&self) -> bool
pub const fn four_score(&self) -> bool
Whether the Four Score adapter is currently enabled.
Sourcepub fn is_vs_system(&self) -> bool
pub fn is_vs_system(&self) -> bool
True when the running cart is Nintendo Vs. System arcade hardware (NES 2.0 console type = Vs. System). The RGB PPU + DIP/coin inputs only take effect on such carts.
Sourcepub const fn is_vs_dual_system(&self) -> bool
pub const fn is_vs_dual_system(&self) -> bool
True when the cart’s header marks a Vs. DualSystem board (two CPUs /
two PPUs; NES 2.0 byte-13 high nibble = Vs. hardware type 5/6).
Detection only: this single-system core cannot boot a DualSystem title
past its attract handshake, so the frontend uses this to surface a clear
note. The two-CPU/two-PPU emulation is a documented v2.0 deferral
(docs/audit/vs-dualsystem-design-2026-06-11.md).
Sourcepub const fn set_vs_dip(&mut self, dip: u8)
pub const fn set_vs_dip(&mut self, dip: u8)
Set the Vs. System 8-bit DIP-switch bank (switch 1 = bit 0 .. switch 8 =
bit 7). Read through the upper bits of $4016/$4017. No effect on
non-Vs. carts; the standard controller read stays byte-identical.
Sourcepub const fn set_vs_ppu_type(&mut self, t: VsPpuType)
pub const fn set_vs_ppu_type(&mut self, t: VsPpuType)
Override the Vs. System PPU type and re-apply the output palette.
iNES-1.0 Vs. dumps default to the 2C03 palette (no NES 2.0 byte-13);
the per-game database (crate::vs_db) supplies the correct
2C04-000x / 2C05 type, which the frontend applies through this setter.
Mostly the colour LUT the PPU emits through, but a 2C05 also returns
its identification bits in $2002 and swaps $2000 / $2001, which
game code reads – so it is emulation state, and movies and netplay
carry it (v2.9.8, crate::HardwareOptions). (Until v2.9.8 this said
“never game logic”, which the 2C05 path contradicts.) No effect on
non-Vs. carts.
Sourcepub const fn vs_ppu_type(&self) -> VsPpuType
pub const fn vs_ppu_type(&self) -> VsPpuType
v2.9.8 — the Vs. System PPU type in effect: the header’s, or the one a
later Self::set_vs_ppu_type installed. rustynes_mappers::VsPpuType::None
on every non-Vs. cart.
Read by crate::HardwareOptions::capture: on a 2C05 the type also
sets the $2002 identification bits and swaps $2000 / $2001, which
game code reads, so a movie or a netplay peer must run the same one.
Sourcepub const fn insert_coin(&mut self, acceptor: u8)
pub const fn insert_coin(&mut self, acceptor: u8)
Latch a Vs. System coin insertion on the given acceptor (0 = #1, 1 = #2).
Reads true for a real-hardware ~40-70 ms window; the frontend should
clear it (see Self::clear_coin) after a few frames.
Sourcepub const fn clear_coin(&mut self)
pub const fn clear_coin(&mut self)
Clear all latched Vs. System coin-insert signals.
Sourcepub const fn set_vs_service(&mut self, pressed: bool)
pub const fn set_vs_service(&mut self, pressed: bool)
Set / clear the Vs. System service button.
Sourcepub fn disk_side_count(&self) -> usize
pub fn disk_side_count(&self) -> usize
Number of disk sides in the inserted FDS image. Returns 0 for cartridge builds (so a frontend can branch on “is this an FDS game?”).
Sourcepub fn inserted_disk_side(&self) -> Option<usize>
pub fn inserted_disk_side(&self) -> Option<usize>
The currently inserted FDS disk side index, or None when ejected (or
for a cartridge build). A game that prompts “insert side B” is asking the
user to call Self::set_disk_side.
Sourcepub fn set_disk_side(&mut self, side: Option<usize>)
pub fn set_disk_side(&mut self, side: Option<usize>)
Insert FDS side i (Some(i)) or eject the disk (None). Inserting
resets the head and opens a short deterministic “not ready” window (the
BIOS polls $4032 and waits for ready); an out-of-range index is
ignored. No-op on cartridge builds. This is how the user complies with a
game’s “insert side N” prompt.
Sourcepub fn enable_fds_trace(&mut self)
pub fn enable_fds_trace(&mut self)
Start recording the diagnostic FDS read-stream trace (the $4031 disk-byte
stream + $4025 control writes + side changes). Off by default and
observation-only — it never affects emulation, so the determinism contract
holds. Drain it with Self::take_fds_trace. No-op on cartridge builds.
Used by the fds_trace diagnostic harness to debug disk-read / side-swap
failures (e.g. the Kid Icarus side-B ERR.07 stall).
Sourcepub fn take_fds_trace(&mut self) -> Vec<FdsTraceRec>
pub fn take_fds_trace(&mut self) -> Vec<FdsTraceRec>
Drain the accumulated FDS read-stream trace records. Empty for cartridge
builds or when Self::enable_fds_trace was never called.
Sourcepub fn disk_image_bytes(&self) -> Vec<u8> ⓘ
pub fn disk_image_bytes(&self) -> Vec<u8> ⓘ
Re-serialize the (possibly-modified) FDS disk image to the headerless
.fds byte layout so the host can write it to a side-car .fds.sav
(keyed by Self::rom_sha256; a console booted from that file reports
the same key once the host calls Self::set_rom_identity). Empty for
cartridge builds.
Sourcepub fn disk_is_dirty(&self) -> bool
pub fn disk_is_dirty(&self) -> bool
Whether the FDS disk image has unsaved writes since the last
Self::clear_disk_dirty. A frontend checks this on quit / periodically
to decide whether to persist the disk.
Sourcepub fn clear_disk_dirty(&mut self)
pub fn clear_disk_dirty(&mut self)
Clear the FDS disk dirty flag after persisting the image.
Sourcepub fn set_disk_write_protected(&mut self, protected: bool)
pub fn set_disk_write_protected(&mut self, protected: bool)
Mark the inserted FDS disk read-only (true) or writable (false,
the default). Drives the $4032 write-protect flag; a write-protected
disk drops bytes in write mode without modifying the medium.
Sourcepub fn set_expansion_device(&mut self, port: usize, device: Option<InputDevice>)
pub fn set_expansion_device(&mut self, port: usize, device: Option<InputDevice>)
Attach a non-standard overlay input device on port (0 = $4016, 1 =
$4017). Pass None to unplug it and return the port to the standard
controller / Four Score path (byte-identical reads). Devices are
unplugged on power-cycle.
§Panics
Panics if port is not in 0..=1.
Sourcepub const fn expansion_device(&self, port: usize) -> &Option<InputDevice>
pub const fn expansion_device(&self, port: usize) -> &Option<InputDevice>
Borrow the overlay device attached to port (0 = $4016, 1 =
$4017), if any.
§Panics
Panics if port is not in 0..=1.
Sourcepub fn set_paddle(&mut self, port: usize, position: u8, fire: bool)
pub fn set_paddle(&mut self, port: usize, position: u8, fire: bool)
Attach an Arkanoid “Vaus” paddle on port (typically port 1 / $4017)
and set its position + fire state. position is the raw 8-bit
potentiometer value ($00 far-left .. $FF far-right); fire is the
single button. Convenience wrapper that attaches the device if absent
then updates it.
§Panics
Panics if port is not in 0..=1.
Sourcepub fn set_zapper(&mut self, port: usize, x: u16, y: u16, trigger: bool)
pub fn set_zapper(&mut self, port: usize, x: u16, y: u16, trigger: bool)
Attach an NES Zapper light gun on port (typically port 1 / $4017)
and set its aim point + trigger. (x, y) is the screen pixel the gun is
aimed at (0..256, 0..240; out of range = off-screen); trigger is the
trigger state. Convenience wrapper that attaches the device if absent
then updates it.
Light detection is sampled from the framebuffer at the end of each
Self::run_frame; the determinism contract holds because the sample
only runs when a Zapper is attached (the no-device path is unchanged).
§Panics
Panics if port is not in 0..=1.
Sourcepub const fn set_zapper_temporal_light(&mut self, on: bool)
pub const fn set_zapper_temporal_light(&mut self, on: bool)
A3 (v2.2.3): enable the beam-relative Zapper light model.
Default ON since v2.3.6 (was off in v2.2.3-v2.3.5). See
crate::bus::SystemBus::set_zapper_temporal_light for the model and
for why it was promoted; in short, the light bit is a function of where
the CRT beam is at the moment of the read (dark before the beam paints
the aim row, lit for the ~19-26-scanline photodiode hold, dark after)
instead of one answer for the whole frame — and the frame model made a
Duck Hunt hit impossible.
Pass false to restore the pre-v2.3.6 frame-granular behaviour.
Deterministic either way: the temporal answer is a pure function of framebuffer + aim + current scanline and holds no extra state, so it adds nothing to serialize and cannot desync a save state or a netplay rollback.
Sourcepub const fn zapper_temporal_light(&self) -> bool
pub const fn zapper_temporal_light(&self) -> bool
Whether the beam-relative Zapper light model is enabled (A3).
Sourcepub const fn set_microphone(&mut self, pressed: bool)
pub const fn set_microphone(&mut self, pressed: bool)
Drive the Famicom built-in microphone (read on $4016 bit 2).
The hardwired second Famicom controller carries a push-to-talk mic that
games poll on $4016.D2 (e.g. Zelda’s Pols Voice, Kid Icarus). Pass
pressed = true while the frontend’s mic key is held / an audio source
crosses the loudness threshold. Additive and opt-in: false (the
default) keeps the $4016 read byte-identical to a stock NES.
Sourcepub fn set_power_pad(&mut self, port: usize, buttons: u16)
pub fn set_power_pad(&mut self, port: usize, buttons: u16)
Attach an NES Power Pad / Family Fun Fitness mat on port (typically
port 1 / $4017) and set its live button mask (bit i = mat button
i+1, 0..=11). Convenience wrapper that attaches the device if absent
then updates it. Opt-in: the no-device path stays byte-identical.
§Panics
Panics if port is not in 0..=1.
v1.6.0 B3 — the latched standard-controller button bitmask for port
(0 = P1 / $4016, 1 = P2 / $4017; 2/3 are the Four Score
players), in Buttons bit order (A = bit 0 .. Right =
bit 7). Read-only and side-effect-free — it reads the latched state, not
the shift register, so it never perturbs a controller poll. Exposed for
the Lua joypad.get query.
§Panics
Panics if port is not in 0..=3.
Sourcepub fn set_snes_mouse(
&mut self,
port: usize,
dx: i16,
dy: i16,
left: bool,
right: bool,
sensitivity: u8,
)
pub fn set_snes_mouse( &mut self, port: usize, dx: i16, dy: i16, left: bool, right: bool, sensitivity: u8, )
v1.2.0 Workstream D — attach a SNES-style serial mouse on port (0 =
$4016, 1 = $4017) and set its movement / buttons / sensitivity.
(dx, dy) are the signed per-frame deltas (clamped to +/-127 on latch);
sensitivity is 0 (low) / 1 (medium) / 2 (high). Convenience wrapper
that attaches the device if absent then updates it. Opt-in: the no-device
path stays byte-identical.
§Panics
Panics if port is not in 0..=1.
Sourcepub fn set_family_keyboard(&mut self, port: usize, keys: [u8; 9])
pub fn set_family_keyboard(&mut self, port: usize, keys: [u8; 9])
v1.2.0 Workstream D — attach a Famicom Family BASIC keyboard on port
(typically port 1 / $4017) and set its pressed-key bitmap. keys is
one byte per matrix row (keys[row] bits 0..=3 = column-half 0 keys,
bits 4..=7 = column-half 1 keys); the frontend builds it from host keys.
Convenience wrapper that attaches the device if absent then updates it.
Opt-in: the no-device path stays byte-identical.
§Panics
Panics if port is not in 0..=1.
Sourcepub fn set_family_trainer(&mut self, port: usize, buttons: u16)
pub fn set_family_trainer(&mut self, port: usize, buttons: u16)
v1.3.0 Workstream F1 — attach a Bandai Family Trainer mat on port
and set its 12-button mask (bit i = mat button i+1). The Family
Trainer is layout-equivalent to the Power Pad and reuses its scan; this
attaches the InputDevice::FamilyTrainer variant (distinct from
Self::set_power_pad so the selected device round-trips through a
save-state). Opt-in: the no-device path stays byte-identical.
§Panics
Panics if port is not in 0..=1.
Sourcepub fn set_subor_keyboard(&mut self, port: usize, keys: [u8; 9])
pub fn set_subor_keyboard(&mut self, port: usize, keys: [u8; 9])
v1.3.0 Workstream F1 — attach a Subor keyboard on port and set its
pressed-key bitmap (one byte per matrix row, like
Self::set_family_keyboard). The Subor keyboard reuses the Family
BASIC keyboard matrix scan; this attaches the
InputDevice::SuborKeyboard variant. Opt-in: the no-device path stays
byte-identical.
§Panics
Panics if port is not in 0..=1.
Sourcepub fn set_konami_hyper_shot(&mut self, port: usize, buttons: u8)
pub fn set_konami_hyper_shot(&mut self, port: usize, buttons: u8)
v1.3.0 Workstream F1 — attach a Konami Hyper Shot on port and set
its 4-button mask (bit 0 = P1 Run, 1 = P1 Jump, 2 = P2 Run, 3 = P2 Jump).
Opt-in: the no-device path stays byte-identical.
§Panics
Panics if port is not in 0..=1.
Sourcepub fn set_bandai_hyper_shot(&mut self, port: usize, sensors: u8)
pub fn set_bandai_hyper_shot(&mut self, port: usize, sensors: u8)
v1.3.0 Workstream F1 — attach a Bandai Hyper Shot (Exciting Boxing
punching bag) on port and set its 8-sensor mask (bits 0..=3 = the A=0
group, bits 4..=7 = the A=1 group). Opt-in: the no-device path stays
byte-identical.
§Panics
Panics if port is not in 0..=1.
Sourcepub const fn set_mirroring_override(&mut self, m: Option<Mirroring>)
pub const fn set_mirroring_override(&mut self, m: Option<Mirroring>)
v1.1.0 beta.1 (T-110-B4) — set (Some) or clear (None) a per-game
nametable mirroring override, a load-time correction for ROMs whose
iNES header carries the wrong mirroring flag (supplied by the frontend’s
game database). None (default) defers to the mapper — byte-identical,
so the determinism / AccuracyCoin contract and the core test suites are
unaffected (they never set it). Persisted in the save-state. Does not
affect mappers with on-cart VRAM (4-screen).
Sourcepub const fn mirroring_override(&self) -> Option<Mirroring>
pub const fn mirroring_override(&self) -> Option<Mirroring>
v2.9.8 — the per-game nametable mirroring override in effect (None =
the mapper decides). See Self::set_mirroring_override.
Sourcepub fn mapper_has_hardwired_mirroring(&self) -> bool
pub fn mapper_has_hardwired_mirroring(&self) -> bool
Whether the loaded mapper’s nametable mirroring is hardwired by the cartridge (solder pads / header bit) rather than controlled by the mapper’s own registers at runtime.
The frontend consults this before honoring a game-database mirroring
correction: a static override is only valid for a hardwired board, and
force-applying one to a mapper that switches mirroring itself (MMC1/3/5,
AxROM, VRC, …) corrupts its rendering. See
rustynes_mappers::Mapper::has_hardwired_mirroring.
Sourcepub fn poke_ram(&mut self, addr: u16, value: u8)
pub fn poke_ram(&mut self, addr: u16, value: u8)
Write a byte directly into CPU work RAM ($0000-$1FFF). Used by the
frontend’s raw RAM cheats (GameShark-style); applied after
Self::run_frame, so the deterministic core run loop is unchanged
(the determinism contract holds for the no-cheat path). No-op outside
system RAM.
Sourcepub fn debug_poke_ppu(&mut self, addr: u16, value: u8)
pub fn debug_poke_ppu(&mut self, addr: u16, value: u8)
v1.7.0 “Forge” Workstream A1 — debugger writeback into the PPU bus
($0000-$3FFF): CHR pattern bytes (mapper ppu_write, a no-op on
CHR-ROM), nametable tiles/attributes (mapper-absorbed, else CIRAM via the
active mirroring), and palette RAM. The PPU-bus counterpart of
Self::poke_ram.
Reached only through the frontend’s gated post-frame poke path (the same
caller-side, after-Self::run_frame stage the raw RAM cheats use), so
the deterministic core run loop is unchanged and the no-edit path is
byte-identical. debug-hooks-gated.
Sourcepub const fn poke_oam_byte(&mut self, idx: u8, value: u8)
pub const fn poke_oam_byte(&mut self, idx: u8, value: u8)
v1.7.0 “Forge” Workstream A1 — debugger writeback for one OAM byte
(idx = 0..256: byte 0 = Y, 1 = tile, 2 = attributes, 3 = X per
sprite). debug-hooks-gated; reached only through the gated post-frame
poke path, so the default build is byte-identical.
Sourcepub fn debug_set_cpu_state(
&mut self,
a: u8,
x: u8,
y: u8,
s: u8,
p_bits: u8,
pc: u16,
)
pub fn debug_set_cpu_state( &mut self, a: u8, x: u8, y: u8, s: u8, p_bits: u8, pc: u16, )
v1.7.0 “Forge” Workstream B (Lua API parity) — debugger/scripted
writeback of the CPU register file (a/x/y/s/p bits/pc). The
structured-state counterpart of Self::poke_ram, backing the Lua
emu:setState(t) field map (Mesen2 parity).
Reached only through the frontend / script crate’s gated post-frame poke
path (the same caller-side, after-Self::run_frame stage the raw RAM
cheats + the other debug_poke_* writebacks use), so the deterministic
core run loop is unchanged and the no-edit path is byte-identical.
debug-hooks-gated. p is taken as a raw status-bits byte (truncated to
the defined flags, mirroring a PLP / save-state restore).
Sourcepub fn peek(&mut self, addr: u16) -> u8
pub fn peek(&mut self, addr: u16) -> u8
Read a byte from the CPU address space ($0000-$FFFF) for inspection,
without the register side effects of a real CPU read — reading
$2002 does not clear the VBL flag / address latch and $2007 does not
advance the PPU read buffer. Used by the debugger and the Lua scripting
API (emu.read); it observes state without advancing the emulator,
preserving determinism.
Sourcepub fn peek_ppu(&mut self, addr: u16) -> u8
pub fn peek_ppu(&mut self, addr: u16) -> u8
v1.2.0 C3 (hd-pack) — side-effect-free read of the PPU bus
($0000-$3FFF): CHR pattern data, nametables, palette RAM. Used by the
HD-pack compositor to hash a tile’s 16 CHR bytes. Observes state without
advancing the emulator, preserving determinism.
Sourcepub fn add_genie_code(&mut self, code: &str) -> Result<(), GenieError>
pub fn add_genie_code(&mut self, code: &str) -> Result<(), GenieError>
Add a Game Genie code (6 or 8 characters, case-insensitive) that
substitutes a byte the CPU reads from PRG-ROM ($8000-$FFFF).
Codes are a runtime overlay — they are not part of the save-state and do not perturb the determinism contract when none are active. With codes active, the substituted bytes are part of the deterministic input (record a movie with the same codes to reproduce a run).
§Errors
Returns GenieError if the code string cannot be decoded.
Sourcepub fn remove_genie_code(&mut self, code: &str)
pub fn remove_genie_code(&mut self, code: &str)
Remove the active Game Genie code whose canonical (upper-case) string
matches code. No-op if no such code is active.
Sourcepub fn clear_genie_codes(&mut self)
pub fn clear_genie_codes(&mut self)
Remove all active Game Genie codes.
Sourcepub fn genie_codes(&self) -> impl Iterator<Item = &GenieCode>
pub fn genie_codes(&self) -> impl Iterator<Item = &GenieCode>
Iterate the active Game Genie codes (address-sorted).
Sourcepub fn drain_audio_into(&mut self, out: &mut [f32]) -> usize
pub fn drain_audio_into(&mut self, out: &mut [f32]) -> usize
Drain into a slice; returns the count copied. Excess samples are
dropped if out is smaller than the buffered count.
Sourcepub const fn rom_sha256(&self) -> &[u8; 32]
pub const fn rom_sha256(&self) -> &[u8; 32]
The ROM’s persistent identity: SHA-256 of an iNES / NES 2.0 image’s bytes after its 16-byte header, or of the whole image for FDS and NSF.
Everything that persists per game is keyed by it: save-state slot
directories, the battery .sav, cheats, the .rns header’s ROM tag
(Self::rom_hash_tag), movies and netplay’s ROM match. Computed once
at construction; subsequent calls are O(1). A host that boots a saved
copy of an FDS disk replaces it with the pristine disk’s hash
(Self::set_rom_identity, v2.9.9), so a game’s own disk saves never
move its identity.
Why the header is excluded (v2.9.8). Until v2.9.8 this hashed the whole image as constructed, which on the desktop is the image AFTER the game database corrected its header. Any change to those corrections therefore renamed a game’s saves and invalidated its states, and v2.9.8 alone changed them three times (the dirty-tail mapper nibble, the NES 2.0 guard, the region promotion). The header is the one part of an image that load-time corrections rewrite; PRG and CHR never change, so an identity built from them survives every past and future correction, and two dumps of the same game with different headers share their saves. The maintainer accepted the one-time break this causes (2026-10-01).
Sourcepub fn rom_identity_of(image: &[u8]) -> [u8; 32]
pub fn rom_identity_of(image: &[u8]) -> [u8; 32]
The identity Self::rom_sha256 reports for a console built from
image, computed without building one: SHA-256 of the bytes after the
16-byte header of an iNES / NES 2.0 image, of the whole image otherwise
(FDS, NSF, UNIF).
For a host that keys stores before, or without, constructing a console
– a library import that must not need the FDS BIOS, or a key
migration (v2.9.9 re-audit NF-21: the mobile hosts keyed saves by the
whole file’s hash). It is the same function every constructor uses, so
the two cannot drift. It does not include a later
Self::set_rom_identity.
Sourcepub const fn image_sha256(&self) -> &[u8; 32]
pub const fn image_sha256(&self) -> &[u8; 32]
SHA-256 of the complete image as constructed, header included.
The Vs. System database (crate::vs_db) keeps a whole-file key on
each row as a fallback to its identity key (Self::rom_sha256), so a
row added from a dump that was never staged stays reachable;
crate::vs_db::lookup consults both. Nothing else is keyed by it.
Identical to Self::rom_sha256 for FDS and NSF.
Sourcepub const fn set_rom_identity(&mut self, sha256: [u8; 32])
pub const fn set_rom_identity(&mut self, sha256: [u8; 32])
Replace this console’s persistent identity (Self::rom_sha256) with
sha256, for a host that boots a game from a SAVED copy of its image.
The case it exists for is a Famicom Disk System disk. FDS games save by
writing to the disk, so a host persists the written image
(Self::disk_image_bytes) and boots from THAT file on the next
launch, which is how a disk game’s progress carries over. Every
constructor hashes the bytes it is given, so from the game’s first disk
save on, an Nes booted that way reported the MODIFIED disk’s hash, and
everything keyed on Self::rom_sha256 changed identity with it:
save-state slots and the .rns ROM tag (Self::rom_hash_tag),
cheats, movies and TAStudio, netplay’s ROM match, the HD-pack and
per-game keys, the RA progress sidecar (v2.9.9 re-audit NF-17). The
host knows the pristine image’s hash (it keys the saved disk with it),
so it passes that here, right after construction, and the console then
reports the identity of the game rather than of one of its saves.
Only the identity moves. Self::image_sha256 stays the hash of the
bytes actually loaded: its one consumer is the Vs. System database’s
whole-file fallback, which must describe the image in hand (and a disk
never matches it). Emulation is untouched: nothing in the chips reads
the identity.
A setter rather than a constructor variant because the identity is the
one field the host has to supply, and each image kind already has two
constructors (with and without a sample rate); a setter covers all of
them, including a host that builds the console through Emu. It is the
host’s word: the core cannot check that sha256 belongs to an earlier
state of the loaded image. Call it before anything is keyed on the
identity (a movie recorded before the call stamps the old one).
Sourcepub fn rom_hash_tag(&self) -> [u8; 6]
pub fn rom_hash_tag(&self) -> [u8; 6]
Truncated ROM hash tag stored in the save-state header.
Sourcepub fn snapshot(&self) -> Vec<u8> ⓘ
pub fn snapshot(&self) -> Vec<u8> ⓘ
Encode the entire emulator state into a .rns snapshot blob.
Includes a versioned container header and the four chip + bus
sections (CPU , PPU , APU , MAP , BUS ), plus an optional
THM thumbnail section (128x120 RGBA8 nearest-neighbor downsample
of the current framebuffer). The thumbnail is for UI slot pickers
only – per ADR 0003 it is NOT part of the deterministic save-state
contract.
Sourcepub fn snapshot_core_into(&self, out: &mut Vec<u8>)
pub fn snapshot_core_into(&self, out: &mut Vec<u8>)
v2.8.0 Phase 3 — Self::snapshot minus the THM thumbnail
section, encoded into a caller-owned reused buffer. The fast path
for per-frame consumers (run-ahead, the netplay save-state ring):
no allocation in steady state and no 61 KiB thumbnail build. The
output parses with Self::restore / Self::restore_quiet
exactly like a full snapshot (THM is optional by format).
Sourcepub fn snapshot_core_into_slim(&self, out: &mut Vec<u8>)
pub fn snapshot_core_into_slim(&self, out: &mut Vec<u8>)
v2.3.3 — Self::snapshot_core_into with the PPU encoded slim
(no framebuffer), for the rewind ring.
The ring snapshots on every frame inside the frame budget and then XORs
and LZ4-compresses the result. The framebuffer is 245,760 of the ~250 KB
and is the worst possible payload for that scheme, since it changes
every frame: the XOR never zeroes and the delta never compresses.
Measured, that made rewind roughly double the produce-interval p95 and
it was the cause of a user-visible judder report — see
docs/performance.md v2.3.3 F3/F4.
A blob written here restores every field except the framebuffer, so the
caller must regenerate the image; Self::rewind_step_back runs one
frame to do exactly that.
Sourcepub fn thumbnail(&self) -> Vec<u8> ⓘ
pub fn thumbnail(&self) -> Vec<u8> ⓘ
Generate a 128x120 RGBA8 thumbnail of the current framebuffer.
Nearest-neighbor downsample (sample every 2nd pixel of every 2nd row). The 1/4-resolution result is small enough that storing it in slot files is cheap (61,440 bytes uncompressed, ~10-20 KiB after the LZ4 path the rewind ring uses if it is ever wired through there).
Per ADR 0003: NOT part of the deterministic save-state contract. Different builds may produce different pixel-perfect framebuffers at the same cycle if post-pass filters change.
Sourcepub fn extract_thumbnail(data: &[u8]) -> Result<Option<Vec<u8>>, SnapshotError>
pub fn extract_thumbnail(data: &[u8]) -> Result<Option<Vec<u8>>, SnapshotError>
Extract a thumbnail from an .rns save-state blob without restoring
it. Used by frontends to populate slot pickers.
Returns Ok(None) if the blob is well-formed but contains no
thumbnail section (older v0.9.0 slot files).
§Errors
Returns SnapshotError when the container header is malformed.
Sourcepub fn restore(&mut self, data: &[u8]) -> Result<(), SnapshotError>
pub fn restore(&mut self, data: &[u8]) -> Result<(), SnapshotError>
Apply a previously Self::snapshoted blob.
Loading from a different ROM is allowed (the embedded hash tag is only a sanity check), but the result is undefined unless the chip section bodies are appropriate for the running mapper.
The header’s ROM hash tag is NOT compared with the running ROM, by
decision (v2.9.0 re-audit NL-01): the paragraph above has always
allowed a load from a different ROM. (The decision first rested on a
second reason too, that the tag hashed the whole file including its
iNES header, so a header-only correction of the same dump would have
refused every state. Since v2.9.8 the tag is the leading bytes of
Self::rom_sha256, which leaves the header out, so that reason no
longer applies; the first still does.) The section-level validation is what keeps a foreign state from
crashing the core; see docs/frontend.md § “Save state files”.
§Errors
Returns SnapshotError for malformed inputs; the machine is then
unchanged.
Sourcepub fn restore_quiet(&mut self, data: &[u8]) -> Result<(), SnapshotError>
pub fn restore_quiet(&mut self, data: &[u8]) -> Result<(), SnapshotError>
v2.8.0 Phase 3 — Self::restore WITHOUT clearing the rewind ring.
For internal, machine-driven restores on the same timeline —
run-ahead’s per-frame rollback and netplay’s rollback-resimulate —
where the buffered rewind history remains exactly as valid as
before. User-driven loads (save-state slots) keep using
Self::restore, which invalidates the ring.
“Quiet” says nothing about whether the bytes are trusted: the libretro
core routes every retro_unserialize here, including user state files.
Since v2.9.0 a rejected blob therefore leaves the machine exactly as it
was, on this path as on Self::restore.
§Errors
Returns SnapshotError for malformed inputs; the machine is then
unchanged.
Sourcepub fn enable_rewind(&mut self)
pub fn enable_rewind(&mut self)
Enable the rewind ring buffer with default capacity (32 MiB) and keyframe period (60).
Sourcepub fn enable_rewind_with(&mut self, max_bytes: usize, keyframe_period: u32)
pub fn enable_rewind_with(&mut self, max_bytes: usize, keyframe_period: u32)
Enable rewind with explicit byte budget + keyframe period.
Sourcepub fn disable_rewind(&mut self)
pub fn disable_rewind(&mut self)
Disable rewind and free the buffer.
Sourcepub fn rewind_capture(&mut self)
pub fn rewind_capture(&mut self)
Push the current state onto the rewind ring. Frontends call this at the end of each completed frame.
No-op if rewind is disabled.
Sourcepub fn rewind_step_back(&mut self) -> bool
pub fn rewind_step_back(&mut self) -> bool
Pop the most recent rewind entry and restore it. Returns true on
success, false if the ring is empty (or rewind is disabled).
Sourcepub fn rewind_clear(&mut self)
pub fn rewind_clear(&mut self)
Drop every buffered rewind entry. Called when the user releases the rewind key, so subsequent forward play overwrites — there’s nothing to overwrite, but we want forward play to capture into a fresh ring rather than tail-of-old-history.
Sourcepub const fn rewind_enabled(&self) -> bool
pub const fn rewind_enabled(&self) -> bool
true if rewind is enabled.
Sourcepub fn rewind_len(&self) -> usize
pub fn rewind_len(&self) -> usize
Number of buffered rewind entries.
Sourcepub fn rewind_bytes_used(&self) -> usize
pub fn rewind_bytes_used(&self) -> usize
Approximate memory used by the rewind ring, in bytes.
Sourcepub fn cpu_snapshot(&self) -> CpuDebugView
pub fn cpu_snapshot(&self) -> CpuDebugView
Snapshot the CPU register file.
Sourcepub fn ppu_snapshot(&self) -> PpuDebugView
pub fn ppu_snapshot(&self) -> PpuDebugView
Snapshot PPU state for the debugger.
Sourcepub fn apu_snapshot(&self) -> ApuDebugView
pub fn apu_snapshot(&self) -> ApuDebugView
Snapshot APU channel outputs and IRQ flags.
Sourcepub const fn set_apu_channel_mask(&mut self, mask: u8)
pub const fn set_apu_channel_mask(&mut self, mask: u8)
Set the APU per-channel enable mask (a UI playback overlay, NOT NES hardware state). Bit 0 = pulse 1, 1 = pulse 2, 2 = triangle, 3 = noise, 4 = DMC, 5 = external/mapper audio. A cleared bit mutes that channel.
The default (rustynes_apu::CHANNEL_MASK_ALL) is byte-identical to
the un-masked mixer — the deterministic per-frame audio is unchanged
unless the frontend explicitly mutes a channel. This is never written
into the save state, so it never affects determinism or round-trips.
Sourcepub const fn apu_channel_mask(&self) -> u8
pub const fn apu_channel_mask(&self) -> u8
Current APU per-channel enable mask. See Self::set_apu_channel_mask.
Sourcepub fn set_apu_channel_gain(&mut self, gain: [f32; 6])
pub fn set_apu_channel_gain(&mut self, gain: [f32; 6])
v1.4.0 Workstream C — set the APU per-channel output gain (a UI mixing
overlay, NOT NES hardware state), generalizing Self::set_apu_channel_mask.
Index 0 = pulse 1, 1 = pulse 2, 2 = triangle, 3 = noise, 4 = DMC,
5 = external/mapper audio. Each gain is clamped to 0.0..=2.0.
The default (rustynes_apu::CHANNEL_GAIN_UNITY, all 1.0) is
byte-identical to the un-scaled mixer — the deterministic per-frame audio
is unchanged unless the frontend explicitly changes a gain. Never written
into the save state, so it never affects determinism or round-trips.
Sourcepub fn set_apu_filter_model(&mut self, model: FilterModel)
pub fn set_apu_filter_model(&mut self, model: FilterModel)
v2.1.3 — select the APU analog output-filter model (see
rustynes_apu::FilterModel). The default
rustynes_apu::FilterModel::NesRf (NES front-loader: 90 + 440 Hz HPF +
14 kHz LPF) is byte-identical to the pre-v2.1.3 output; Famicom (37 Hz
HPF) and Clean (~10 Hz DC-block) drop the aggressive 440 Hz high-pass
for a fuller low end. Tonal only — channel content is unchanged, and the
model is never written into the save state (a frontend/config concern,
re-applied at load), so determinism and round-trips are unaffected.
Sourcepub const fn apu_filter_model(&self) -> FilterModel
pub const fn apu_filter_model(&self) -> FilterModel
v2.9.8 — the APU analog output-filter model last selected with
Self::set_apu_filter_model (the default until one is). Survives a
Self::power_cycle.
Sourcepub const fn apu_channel_gain(&self) -> [f32; 6]
pub const fn apu_channel_gain(&self) -> [f32; 6]
Current APU per-channel output gain. See Self::set_apu_channel_gain.
Sourcepub fn oam(&self) -> [u8; 256]
pub fn oam(&self) -> [u8; 256]
Borrow OAM (256 bytes = 64 sprites x 4 bytes).
Returns a cloned [u8; 256] so the caller doesn’t have to manage
a borrow lifetime against &self.
Sourcepub fn oam_byte(&self, index: u8) -> u8
pub fn oam_byte(&self, index: u8) -> u8
One OAM byte (index = 0..=255), without copying the whole 256-byte
array — for single-byte readers (e.g. the Lua memory:read_oam) that
would otherwise pay a full oam() copy per access. Read-only.
Sourcepub const fn palette_ram(&self) -> [u8; 32]
pub const fn palette_ram(&self) -> [u8; 32]
Borrow palette RAM (32 bytes).
Sourcepub const fn set_custom_palette(&mut self, base: Option<[[u8; 3]; 64]>)
pub const fn set_custom_palette(&mut self, base: Option<[[u8; 3]; 64]>)
v1.1.0 beta.1 — install (Some) or clear (None) a custom 64-entry base
palette loaded from a .pal file. A frontend presentation override: it
re-tints the displayed RGBA framebuffer via the PPU’s colour LUT but does
not touch any logical core state. None (the default) is byte-identical to
the built-in palette, so AccuracyCoin + the commercial oracle (which never
set one) are unaffected. Not part of the save-state.
Sourcepub const fn custom_palette(&self) -> Option<[[u8; 3]; 64]>
pub const fn custom_palette(&self) -> Option<[[u8; 3]; 64]>
v2.9.8 — the custom base palette installed by
Self::set_custom_palette, or None for the built-in one. Survives a
Self::power_cycle.
Sourcepub const fn set_extra_scanlines(&mut self, lines: u16)
pub const fn set_extra_scanlines(&mut self, lines: u16)
v1.7.0 “Forge” Workstream F3 — set the PPU extra-scanlines overclock: the
number of EXTRA idle vblank scanlines the PPU inserts per frame (at the
existing dot resolution, Mesen2 UpdateTimings). Each extra line is pure
additional CPU run-time — it renders nothing, sets/clears no PPU flag, and
fires no VBL/NMI/A12 event, so the visible image is unchanged. 0 (the
default) is byte-identical to stock NES timing — AccuracyCoin, the
commercial oracle, and nestest (which never set it) are unaffected.
Off by default; a frontend config knob, not part of the save-state.
Distinct from the CPU-multiplier overclock (Self::set_cpu_overclock,
v3.1.0), which shortens the CPU cycle instead of lengthening the frame.
Clamped to MAX_EXTRA_SCANLINES (v2.9.9, NC-11): the cap used to
live only in the desktop frontend, so a movie’s options record could
set 65,535 lines and leave every run_frame ending on its cycle
budget with no frame.
Sourcepub const fn set_cpu_overclock(&mut self, k: u8)
pub const fn set_cpu_overclock(&mut self, k: u8)
v3.1.0 (T-CPU-OVERCLOCK, FE-01) — set the CPU-multiplier overclock:
the CPU runs k times faster against an unchanged PPU, so a game gets
k times the CPU time per frame.
It splits the region’s master-clock CPU divider into k cycles whose
lengths sum to exactly one stock cycle (NTSC 12 -> 6 / 4 / 3; PAL 16 at
x3 is 5, 5, 6 and Dendy 15 at x4 is 3, 4, 4, 4), so the multiplier is
exact on every region. Until the v3.1.0 review it divided with integer
division, which made PAL x3 run x3.2 and Dendy x4 x5. The APU, the
DMC, every mapper’s CPU-cycle hook (the VRC / FME-7 / N163 IRQ
counters) and the PPU’s open-bus and post-reset timers stay at the
STOCK rate, so the pitch, the music tempo and the cycle-timed raster
IRQs are unchanged: what speeds up is the game’s own code. DMA follows
the APU’s get/put phase, so a DMA takes about k times as many CPU
cycles and the same real time.
1 (the default) is stock and byte-identical to a build without the
option. 0 is treated as 1, and values above MAX_CPU_OVERCLOCK
clamp to it. Configuration, not save-state: the host re-applies it,
and movies and netplay carry it in crate::HardwareOptions. Not
hardware behaviour: no real console runs its CPU faster than its APU.
Sourcepub const fn cpu_overclock(&self) -> u8
pub const fn cpu_overclock(&self) -> u8
v3.1.0 — the CPU-multiplier overclock (1 = stock).
Sourcepub const fn set_sprite_limit_disabled(&mut self, disabled: bool)
pub const fn set_sprite_limit_disabled(&mut self, disabled: bool)
v3.1.0 (T-SPRITE-LIMIT, FE-02) — draw the sprites beyond the eighth
on a scanline, removing sprite flicker.
Render-only. Sprite evaluation, secondary OAM, the overflow flag,
sprite-0 hit and every real sprite fetch (with its A12 edges, which an
MMC3 counts) are exactly stock, so the game sees no difference; only
the picture does. The extra sprites draw behind all eight hardware ones.
On the boards whose CHR reads have an effect (MMC2, MMC4, the J.Y.
ASIC, Bandai 96, Nanjing 163; Mapper::chr_reads_are_pure) the option
draws eight, as stock, because the extra pattern reads would change
emulation there.
Off by default. Configuration, not save-state; carried across a power
cycle, and in movies and netplay by crate::HardwareOptions (the
picture differs, so a movie’s frame hashes and a netplay peer’s desync
checks depend on it).
Sourcepub const fn sprite_limit_disabled(&self) -> bool
pub const fn sprite_limit_disabled(&self) -> bool
v3.1.0 — whether the sprites beyond the eighth are drawn.
Sourcepub fn set_mmc3_revision_override(
&mut self,
revision: Option<Mmc3Revision>,
) -> bool
pub fn set_mmc3_revision_override( &mut self, revision: Option<Mmc3Revision>, ) -> bool
v3.1.0 (T-MMC3-NEC-OVERRIDE, ACC-13) — run an MMC3 (mapper 4) under
a chosen IRQ revision, or None for the one its header selects.
The MMC3’s two IRQ behaviours are mutually exclusive: the Sharp MMC3B
/ MMC3C (the default) asserts IRQ whenever a clock leaves the counter at
0 with IRQs enabled, so a latch of 0 fires every scanline. The MMC3A
and non-Sharp MMC3B (Mmc3Revision::Nec) assert on a 1 -> 0 decrement
and on a $C001 reload to 0 (one IRQ per $C001 write while $C000
is 0, even if the counter was already 0), but not when the counter,
already 0, reloads 0 by itself. A NES 2.0 header can say which (submapper 4); an iNES 1.0
dump cannot, so this override is how a player runs a game, or blargg’s
mmc3_test_2/6-MMC3_alt, on the other chip. The default (None) is
unchanged. Configuration, re-applied when a power cycle rebuilds the
board, and carried in crate::HardwareOptions. Returns whether the
board is one that applies it.
Sourcepub const fn mmc3_revision_override(&self) -> Option<Mmc3Revision>
pub const fn mmc3_revision_override(&self) -> Option<Mmc3Revision>
v3.1.0 — the forced MMC3 IRQ revision (None = the header’s).
Sourcepub const fn extra_scanlines(&self) -> u16
pub const fn extra_scanlines(&self) -> u16
v1.7.0 F3 — the configured extra-scanline overclock count (0 = stock).
Sourcepub const fn set_fast_dotloop(&mut self, enabled: bool)
pub const fn set_fast_dotloop(&mut self, enabled: bool)
v2.1.8 A1 — enable or disable the specialized visible-scanline fast dot path (a pure performance optimization for the PPU’s hottest per-dot case).
The PPU dot FSM (Ppu::tick) is the emulator’s single hottest function
(~46% of a representative frame’s self-time). This knob dispatches the
common “clean” visible BG-render dots (visible scanline, dots 1..=256,
rendering stably enabled, no sub-dot disturbance) to a straight-line
handler that runs the identical helper sequence with the statically-dead
event branches pruned. On by default since v2.2.3 (OFF through
v2.2.2) and byte-identical to the exact path — proven bit-for-bit
every frame by the differential test (fast_dotloop_diff) and the full
AccuracyCoin / visual-regression / nestest oracle, and measured at
-11.3% on the rendering-heavy full_frame bench. Setting it false
selects the fully-general per-dot path and remains the fallback. A
frontend/config knob, NOT part of the save-state.
Sourcepub const fn fast_dotloop(&self) -> bool
pub const fn fast_dotloop(&self) -> bool
v2.1.8 A1 — whether the visible-scanline fast dot path is enabled
(true = default since v2.2.3; both settings produce identical frames).
Sourcepub const fn set_oam_decay(&mut self, enabled: bool)
pub const fn set_oam_decay(&mut self, enabled: bool)
v2.1.4 F2.3 — enable or disable the optional OAM-decay accuracy model.
The 2C02’s OAM is dynamic RAM refreshed by sprite evaluation; with rendering
disabled for a while its un-refreshed 8-byte rows decay to a fixed garbage
pattern. This models that (à la Mesen2’s EnableOamDecay): a row un-touched
for > 3000 CPU cycles decays on the next read. Off by default and
byte-identical to a decay-free core when off — AccuracyCoin, the
commercial oracle, and the visual regression suites are unaffected. It is
NTSC/Dendy-only (PAL’s refresh cadence masks decay). Deterministic when on
(driven off the PPU’s monotonic dot counter — no wall-clock / OS RNG), and a
frontend/config knob re-applied on load, NOT part of the save-state (the
in-flight per-row ages are serialized as a relative age so a rollback stays
deterministic; the enable flag is not).
Sourcepub const fn oam_decay_enabled(&self) -> bool
pub const fn oam_decay_enabled(&self) -> bool
v2.1.4 F2.3 — whether the optional OAM-decay model is enabled (false =
default, byte-identical to a decay-free core).
Sourcepub const fn set_ppu_revision(&mut self, revision: PpuRevision)
pub const fn set_ppu_revision(&mut self, revision: PpuRevision)
v2.1.7 P5 — select the emulated 2C02 die revision (see PpuRevision).
The PpuRevision::default (PpuRevision::Rp2c02H) models no extra
quirks, so at the default this is inert and the core is byte-identical
to a build without it — AccuracyCoin, the commercial oracle, and the
visual / audio regression suites are unaffected. Selecting
PpuRevision::Rp2c02G additionally models the OAMADDR ($2003)
write-during-rendering OAM corruption glitch (Huge Insect). The
selection is stored so a power-cycle re-applies it; it is config, not
save-state (the corruption state it can arm already round-trips via the
v6 PPU snapshot tail). Deterministic. A frontend/config knob re-applied on
load, mirroring Nes::set_oam_decay.
Sourcepub const fn ppu_revision(&self) -> PpuRevision
pub const fn ppu_revision(&self) -> PpuRevision
v2.1.7 P5 — the currently-selected 2C02 die revision (default
PpuRevision::Rp2c02H, byte-identical).
Sourcepub const fn set_console_model(&mut self, model: ConsoleModel)
pub const fn set_console_model(&mut self, model: ConsoleModel)
v2.9.8 — select the console whose reset wiring is modelled (see
ConsoleModel).
The selection is stored, so every later Nes::power_cycle and
Nes::reset follows it. It also takes effect at once in one respect:
selecting ConsoleModel::Famicom ends any PPU warm-up still in
progress, because a Famicom’s PPU is never held in reset while its CPU
runs. A host that applies its configuration straight after building the
machine (as the frontend does on every ROM load and power-cycle) thereby
gets the Famicom power-on. Selecting ConsoleModel::Nes never re-arms
a window that has already closed; it applies from the next reset.
ConsoleModel::Nes is the default and is byte-identical to every
earlier release. Deterministic; config, not save-state.
Sourcepub const fn console_model(&self) -> ConsoleModel
pub const fn console_model(&self) -> ConsoleModel
v2.9.8 — the currently-selected console reset wiring (default
ConsoleModel::Nes, byte-identical).
Sourcepub const fn set_power_up_palette(&mut self, init: PaletteInit)
pub const fn set_power_up_palette(&mut self, init: PaletteInit)
v2.1.7 P5 — apply a power-up palette-RAM pattern (see PaletteInit).
The 2C02’s palette RAM is not cleared at power-on; this selects the
power-up contents. PaletteInit::default (PaletteInit::Zeroed)
keeps the established all-zero power-up palette, so at the default this is
byte-identical. PaletteInit::Blargg loads the canonical blargg
power-up dump for software that samples uninitialized palette RAM. Writes
only palette RAM (already part of the snapshot), so no snapshot change is
needed; the selection is stored so a power-cycle re-applies it. Best
called at power-on (palette RAM is preserved across a warm reset, like
real hardware).
Sourcepub const fn power_up_palette(&self) -> PaletteInit
pub const fn power_up_palette(&self) -> PaletteInit
v2.1.7 P5 — the currently-selected power-up palette pattern (default
PaletteInit::Zeroed, byte-identical).
Sourcepub fn set_power_on_ram(&mut self, ram: PowerOnRam)
pub fn set_power_on_ram(&mut self, ram: PowerOnRam)
v2.1.7 P5 — select the power-on work-RAM fill (see PowerOnRam).
Applies the fill to the current 2 KiB work RAM (and open-bus latch)
immediately and stores it so a power-cycle re-applies the same fill
(power_cycle == fresh boot). PowerOnRam::default
(PowerOnRam::Zeroed) is the established all-zero power-up state
(byte-identical); the other variants are opt-in and deterministic,
surfacing software that reads uninitialized RAM (Final Fantasy RNG,
River City Ransom, Cybernoid). RAM is not consulted during reset, so
applying it here is safe.
Sourcepub const fn power_on_ram(&self) -> PowerOnRam
pub const fn power_on_ram(&self) -> PowerOnRam
v2.1.7 P5 — the currently-selected power-on work-RAM fill (default
PowerOnRam::Zeroed, byte-identical).
Sourcepub const fn set_cpu_2a03_revision(&mut self, revision: Cpu2A03Revision)
pub const fn set_cpu_2a03_revision(&mut self, revision: Cpu2A03Revision)
v2.1.7 “Hardware Revisions & DMA Frontier” — select the emulated Ricoh 2A03 die revision, which gates the DMA unit’s “unexpected DMA” extra halt-read on the DMC-halt-overlaps-OAM-halt cycle.
Cpu2A03Revision::Rp2A03G is the default and is byte-identical to
the core as it shipped before v2.1.7 (AccuracyCoin 141/141, nestest
0-diff, every committed DMA oracle ROM Passed).
Cpu2A03Revision::Rp2A03H is a purely additive, opt-in accuracy knob
that omits the extra read; its direction is an unverified hypothesis
(no public reference emulator or test ROM models the 2A03 die-revision
DMA difference — see the type docs and ADR 0033). It is deterministic and
a config knob re-applied on load, NOT part of the save-state.
Sourcepub const fn cpu_2a03_revision(&self) -> Cpu2A03Revision
pub const fn cpu_2a03_revision(&self) -> Cpu2A03Revision
v2.1.7 — the configured 2A03 die revision (default
Cpu2A03Revision::Rp2A03G, byte-identical to the pre-v2.1.7 core).
Sourcepub fn mapper_info(&self) -> MapperDebugView
pub fn mapper_info(&self) -> MapperDebugView
Mapper debug info (bank registers, IRQ counters, mirroring, …).
Sourcepub const fn expansion_audio_chip(&self) -> Option<&'static str>
pub const fn expansion_audio_chip(&self) -> Option<&'static str>
v1.4.0 Workstream C — the loaded mapper’s on-cart expansion-audio chip
name (e.g. "VRC6", "VRC7 (OPLL)", "MMC5", "Namco 163",
"Sunsoft 5B", "FDS"), or None when the board has no expansion audio
(or the mapper-audio feature is compiled out). Used by the frontend to
show the expansion-channel volume slider only when present, with a label.
Discovery is dynamic: it consults the cached rustynes_mappers::MapperCaps
audio flag (true only when the mapper overrides mix_audio with the
feature on) and the mapper id to name the chip family.
Sourcepub fn cpu_bus_peek(&mut self, addr: u16) -> u8
pub fn cpu_bus_peek(&mut self, addr: u16) -> u8
Side-effect-free CPU bus peek (for the hex viewer).
Sourcepub fn ppu_bus_peek(&mut self, addr: u16) -> u8
pub fn ppu_bus_peek(&mut self, addr: u16) -> u8
Side-effect-free PPU bus peek (for the hex viewer + visualizers).
Sourcepub fn pattern_table_rgba(&mut self, table: u8) -> Vec<u8> ⓘ
pub fn pattern_table_rgba(&mut self, table: u8) -> Vec<u8> ⓘ
Render the 256 tiles of a CHR pattern table as RGBA8 (128x128).
table selects which of the two pattern tables: 0 -> $0000,
1 -> $1000. Uses BG palette 0 ($3F00-$3F03) for grayscale-ish
rendering. ~80 KiB cloned; only call when the PPU pattern viewer
is open.