Releases: doublegate/RustyNES
Release list
RustyNES v2.2.1 — Housekeeping patch (dev-tooling archival + dependency consolidation + FDS test corpus)
RustyNES v2.2.1 — Housekeeping patch
A housekeeping patch on top of v2.2.0 "Capstone": dev-tooling archival, a zero-source-change dependency consolidation, and a new gitignored FDS test corpus. Zero accuracy, feature, or core changes — the deterministic #![no_std] chip stack, save-state / TAS / netplay-replay formats, and every golden vector are untouched: AccuracyCoin holds 141/141 (100.00%), unchanged from v2.2.0.
Added
Game Genie re-key research tooling archived
Preserved the six intermediate research / verification scripts behind the header-robust Game Genie code re-key (which shipped in #262) beside the generator in scripts/gg/: crc_combine.py (a pure-Python zlib.crc32_combine implementation, self-tested against 2000 random synthetic trials, underpinning the rom_crc32 == crc32_combine(prgCRC, chrCRC, chrLen) identity), alias_resolve.py (long-tail title-alias CRC resolution), coverage.py / coverage2.py (name-join coverage accounting), inspect.py, and verify.py (which now proves the combine identity over every standard nes20db cart dump). Dev / research tooling only — touches no crate, no build, no core.
2A03-revision DMA-divergence probe archived
scripts/probes/probe_rev.rs, a diagnostic test probe behind the already-shipped v2.1.7 "Stepping" DMA-frontier work (ADR 0033) — snapshot-hashes DMA test ROMs under Cpu2A03Revision::Rp2A03G vs Rp2A03H. Kept for reference; not wired into CI.
TakuikaNinja FDS hardware-verification probes (gated, gitignored)
Four RUSTYNES_FDS_BIOS-gated smoke tests (crates/rustynes-test-harness/tests/fds_takuikaninja.rs) against FDS-Mirroring-Test, FDS-4023-Test, FDS-Audio-Registers, and FDS-4030D1-Addr — real hardware-verified probes of $4023 / mirroring / audio-register behavior and the FDS DRAM-refresh-watchdog IRQ. None of the four carries an explicit permissive license, so they're staged gitignored under tests/roms/external/fds-takuikaninja/ rather than committed, mirroring the existing commercial-ROM convention; every test skips cleanly when the BIOS or a probe disk is absent. The underlying $4023 and mirroring behaviors these probes exercise are already implemented and unit-tested independently in crates/rustynes-mappers/src/fds.rs — this is regression insurance against a second, hardware-verified oracle, not a fix for a gap. The $4030.D1 DRAM-watchdog probe tracks a known, honest residual (not yet modeled by RustyNES or, per upstream, by most current FDS emulators) — see docs/accuracy-ledger.md.
Changed
Dependency consolidation (closes Dependabot #298–#303)
Rolled all six open Dependabot bumps into one reviewed change, each verified against the code and gates; all landed with no source changes and the deterministic #![no_std] core untouched: pollster 0.4 → 1.0 (frontend / android / iOS block_on for wgpu/Metal init), wide 0.7 → 1.5 (the desktop u32x8 SIMD blitter — the SIMD-vs-scalar byte-identity gate still passes), tungstenite + tokio-tungstenite 0.29 → 0.30 (netplay + mobile signaling; default-features = false + handshake + rustls-tls-webpki-roots retained for clean pure-rustls cross-compiles), bytemuck 1.25.0 → 1.25.1 and cc 1.2.66 → 1.2.67 (patch), and CI actions/setup-python v5 → v6 (MkDocs step).
Notes
- Version: workspace
2.2.0 → 2.2.1. No mobile app version change (versionCode/versionNameon Android andMARKETING_VERSIONon iOS stay frozen at their v2.0.x host-only release points; the joint mobile store launch remains v2.3.0). - No accuracy/oracle number moves. AccuracyCoin 141/141, nestest 0-diff,
blargg_apu_2005/pal_apu_tests(10/10) /visual_regression/ the 60-ROM commercial oracle all unchanged. Save-state / TAS / netplay-replay formats untouched. - Full host gate green: fmt · clippy (all feature combos + wasm32) · rustdoc (
-D warnings) ·no_stdcross-compile (thumbv7em) · markdownlint ·cargo deny check.
RustyNES v2.2.0 — "Capstone" (milestone cut — netplay matchmaking/lobby + FDS medium model + peripherals + quality/security pass)
RustyNES v2.2.0 — Capstone
The milestone cut that closes the v2.1.5 → v2.2.0 "deepen the existing project" run, landing its two remaining marquees — the netplay matchmaking/lobby stack and the FDS medium model — alongside a peripherals + quality/security pass. Every change is additive or default-off: the deterministic core is untouched, so AccuracyCoin holds 141/141 (100.00%), visual_regression is byte-identical, nestest is 0-diff, pal_apu_tests 10/10, and save-state / TAS / netplay replay stay bit-identical. Two PRs (#290, #291).
Added
Netplay matchmaking + lobby, spectators, desync/liveness hardening (B5) — #290
- Lobby + matchmaking — the pure signaling protocol (
crates/rustynes-netplay/src/signaling.rs) grows a browse-and-join lobby directory and a matchmaking path atop the existing room-code / TURN stack. NewSignalMessagevariants —ListRooms { rom_hash }→RoomList { rooms }(open, joinable, optionally game-filtered rooms; eachRoomInfocarries code / player-count / capacity /rom_hashand no SDP/ICE/identity), andQuickMatch { rom_hash, max_players }→Matched { room, slot, max_players }(server-side quick-play: join any open room for the ROM via the sharedadd_to_roomprimitive, or mint a freshQM-NNNNNNroom). Theroom-listframe is parsed by a brace-depth walk bounded atMAX_ROOM_LIST(256) so an oversized frame cannot force an unbounded allocation. Determinism / rollback contract untouched — signaling only. - Delayed-stream spectators —
SpectatorConfig.delay_frames(clamped toMAX_DELAY_FRAMES= 512 ≈ 8.5 s) layers an intentional broadcast / anti-spoiler / jitter-smoothing hold atop the natural spectator lag: framefis revealed only once framef + delay_framesis confirmed (reveal_horizon()). Purely a presentation delay — frames are still produced byte-identically and in order and the spectator still sends nothing — so it cannot perturb the match. Wired toNetplayUi::spectator_delay_frames(default 0). - Hardened desync surface —
DesyncDiagnosticsgains a single gradedDesyncStatus{InSync/Suspect/Desynced} verdict with a hysteresis threshold (DEFAULT_DESYNC_THRESHOLD= 3 consecutive mismatches ≈ 1.5 s at the 30-frame checksum interval) so a lone reordered / late peer checksum no longer flashes a false desync banner, plus a sticky peak-run rule so a confirmed desync never silently downgrades. Pure telemetry over theNetMessage::Checksumdigests the session already exchanges. - Peer-liveness RTT timeouts — a graded
PeerLink{Live/Interrupted/TimedOut} for an already-syncedNetplayConnection, driven bylast_recvagainstpeer_interrupt_timeout(2 s) /peer_disconnect_timeout(5 s), plus a terminalDisconnectReason::PeerTimeout. Deliberately far above Mesen's trigger-happy ~150 ms: a single lost 1 HzQualityping or a routine Wi-Fi/LTE retransmit spike never trips it, matching the multi-second grace windows GGPO/Parsec use. Both thresholds are builder-configurable (with_peer_timeouts).
FDS medium model completion — CRC-16 / gap / continuous head-seek (F4.3, a marquee) — #291
- The Famicom Disk System RAM adapter (
crates/rustynes-mappers/src/fds.rs) completes the disk medium model. The disk is a synthesized byte-stream wire image — lead-in / inter-block gaps, a$80start mark, the block bytes, and a CRC-16/KERMIT per block — and each BIOS-written block now re-emits a fresh per-block CRC-16 over its updated payload (resynth_block_crc), modelling the RP2C33 controller's continuous CRC generator so the medium stays self-consistent after a write. - A new continuous analog head-seek / velocity model (opt-in, default-OFF —
Fds::set_analog_head_seek) replaces the flat fixedHEAD_RESEEK_CYCLESmotor-restart not-ready window with a belt-driven, distance-proportional seek time (HEAD_SEEK_BYTES_PER_CYCLEvelocity +HEAD_SEEK_SETTLE_CYCLESsettle, clamped to a cold spin-up), sized from the head-travel distance captured at motor-off. - A BIOS-free synthetic write-verify oracle (
Fds::medium_write_verify) walks the wire image and asserts every block's CRC-16 and gap/mark framing round-trips — the CI-verifiable half of the medium model; the real-BIOS write-CRC path needs a copyrightdisksys.romand runs only from a gitignored local dump (docs/accuracy-ledger.mdrecords the CI-verifiable-vs-local-only split). Additive and deterministic: with the head-seek model off (the default) a non-writing.fdsrun is byte-identical to prior releases; the new state round-trips an additive v4 FDS save-state tail (v1/v2/v3 blobs load with the model disabled). AccuracyCoin has no FDS ROM, so 141/141 is unaffected.
Peripherals — Famicom microphone + Zapper light-timing (peripherals) — #291
- The Famicom built-in controller-2 microphone is modelled on
$4016bit 2 (Nes::set_microphone/Bus::set_microphone), wired through the frontend input path (hold-to-talkMkey →FrameInputs.microphone→ latch), for games such as The Legend of Zelda (Pols Voice) and Kid Icarus. It is a$4016-only signal (never touches$4017). - The Zapper photodiode now integrates a 3×3 aperture (field-of-view) around the aim point, asserting light only when ≥2 pixels cross the luma threshold (
ZAPPER_APERTURE_*) — hardening detection against sub-pixel aim error and PPU edge noise vs the prior single-pixel sample, while staying a deterministic pure function of the presented framebuffer (no save-state change). Both are additive and default-off.
Quality / security — fuzz expansion, ROM Info browser, handbook (quality) — #290
- Fuzz-target expansion —
fuzz/grows from 3 to 8 cargo-fuzz targets covering the remaining untrusted-input boundaries:ppu_reg_io,apu_reg_io,netplay_message(the highest-value target —NetMessage::from_bytesbinary UDP +SignalMessage::parseJSON signaling/lobby, both ingesting bytes straight off the wire),save_state(parse_header+extract_thumbnail+restore_quiet), andmovie(Movie::deserialize). Each builds under nightly cargo-fuzz and runs clean for tens of thousands of iterations. Themovietarget found two real OOM-DoS paths (see Changed). - Read-only ROM Info browser — a new Tools → ROM Info panel surfaces, for the loaded ROM, the two dump-identity CRC32 keys (the header-excluded game-DB key + the full-file No-Intro key), the SHA-256, the effective per-game database entry, and the decoded cartridge header read straight off the running
Nes. Read-only (&Nes) — never mutates the emulator or the DB overlay; the deterministic core never consults it. - MkDocs handbook deepening — four new Material-for-MkDocs pages (
docs/expansion-audio.md,docs/pal-region.md,docs/crt-composite.md,docs/creator-tools.md) — curated entry points for the newer subsystems, cross-linked to the authoritative specs, with matchingmkdocs.ymlnav entries.
Changed
- Movie (
.rnm) deserializer hardening — the newmoviefuzz target surfaced two OOM-DoS paths inMovie::deserialize(crates/rustynes-core/src/movie.rs), both now fixed byte-identically for valid input: (1) the untrusted 4-byteframe_countwas passed straight toVec::with_capacity, so a 49-byte header could claim a multi-gigabyte reservation — now capped atremaining_bytes / width; (2) abytes_per_frameof 0 made eachr.take(0)consume no input, so the frame loop pushedframe_countempty records out of a finite file — now rejected up front. Regression testdeserialize_hostile_frame_count_does_not_oomadded; the existing 44 movie tests (incl. the determinism round-trip) stay green.
Notes
- Version: workspace
2.1.10 → 2.2.0(Cargo.lock refreshed). No dependency or mobile changes (the mobile apps re-release at v2.3.0). - No default-build behavior change. Netplay lobby / spectators / desync / liveness are signaling + telemetry only; the FDS head-seek model and both peripherals are default-OFF; the ROM Info browser is read-only. AccuracyCoin stays 141/141 (100.00%),
visual_regressionbyte-identical,pal_apu_tests10/10, and save-state / TAS / netplay replay stay bit-identical (verified by the netplay rollback-determinism + snapshot-restore-replay + FDS synthetic write-verify gates). - Review-surfaced fixes landed alongside: a netplay liveness-spoofing fix (
last_recvrefreshes only for the adopted peer's source, so a third party can no longer suppressPeerTimeout);QuickMatchnow scansself.roomsuncapped (was capped atMAX_ROOM_LIST, contradicting its own contract); the FDS DMC-conflict$4016read now ORs the mic bit (mic << 2); and the FDSload_disk_tailoffset carries adebug_assert_eq!full-consume invariant. - Docs updated in-change:
docs/frontend.md,docs/mappers.md,docs/accuracy-ledger.md, the four new handbook pages,fuzz/README.md,CHANGELOG.md. - Full host gate green: fmt · clippy (native all-targets +
scripting/scripting,hd-pack/retroachievements+ all wasm32 targets) · rustdoc (-D warnings) · markdownlint (pinned v0.39.0) · AccuracyCoin 141/141 +visual_regressionbyte-identical + nestest 0-diff +pal_apu_tests10/10 + the netplay determinism / FDS write-verify suites ·no_stdthumbv7emcross-compile · all 8 cargo-fuzz targets build · version consistency.
RustyNES v2.1.10 — "Fathom" (creator tools and web parity — TAStudio greenzone + Lua API breadth + browser-RA auth-proxy deploy stack + Vs. DualSystem libretro presentation — "Loom")
RustyNES v2.1.10 — Fathom (creator tools and web parity — Loom)
The creator-tools-and-web-parity step of the v2.1.5 → v2.2.0 "deepen the existing project" run. It deepens the content-creation surface (TAStudio + Lua) and closes the deferred browser gaps (RetroAchievements + Vs. DualSystem libretro present). The deterministic core is untouched — greenzone snapshots and Lua reads/writes preserve the TAS/replay contract — so AccuracyCoin 141/141 (100.00%), visual_regression byte-identical, and save-state / TAS / netplay replay stay bit-identical. Two PRs (#287, #288).
Added
Creator tools — TAStudio depth + Lua API breadth (B8/B9) — #288
- TAStudio greenzone — per-frame cached save-states with a force-greenzone range so piano-roll seeking / rewinding is instant (bounded,
MAX_FORCED_GREENZONE_FRAMES≈ 3 min; forced frames pinned as non-evictable anchors). Named markers and richer branch slots (the.rnmprojmodel already carried these) round out the surface. - Lua API breadth — the full lifecycle-callback surface (
startFrame/endFrame/nmi/irq/reset/inputPolled/spriteZeroHit/codeBreak/stateLoaded/stateSaved), CPU / PPU / palette / OAM / CHR read-write (reads are the side-effect-free debug-peek path — no open-bus / mapper side effects; writes stay gated / deferred),emu.drawLineHUD drawing (mlua + piccolo + both render sites), a bundled example-script library (hud_graph.lua,palette_viewer.lua,lifecycle_events.lua), and a wasmpiccoloparity uplift (read-parity memory table +drawLine, the deferred-but-lands write contract preserved, ADR 0012).
Web parity — browser RetroAchievements + Vs. DualSystem libretro (ADR 0015) — #287
- Browser RetroAchievements — completes the wasm trampoline marshalling and adds the deployable auth-proxy the browser client needs (
deploy/Dockerfile.raproxy+ anra-proxycompose service behind Caddy; RA credentials via env only, never committed). The full code path is in place with marshalling-contract tests; the live login + unlock is a documented deploy-to-verify step (docs/cheevos-browser.md). - Vs.
DualSystempresentation in the libretro core — detects the cabinet, steps both consoles, and composes a 512×240 side-by-side XRGB8888 image with the correct port mapping + audio + save-state — delivering the dual-console view for the mainstream RetroArch target.
Deferred (documented honestly)
- Vs.
DualSystemon the wasm desktop-style present — the core compositor and the libretro path are done, but un-gating the browser GPU present for the four Vs. arcade boards is a niche, CI-unverifiable (no headless-browser GPU) change to the common wasm present hot-path; kept deferred with the rationale indocs/frontend.md.
Notes
- Version: workspace
2.1.9 → 2.1.10(Cargo.lock refreshed). No dependency or mobile changes (the mobile apps re-release at v2.3.0). - No default-build behavior change. TAStudio greenzone is pure frontend anchor bookkeeping; Lua writes stay gated/deferred; the web-parity work is browser/libretro/deploy. AccuracyCoin stays 141/141 (100.00%),
visual_regressionbyte-identical, and save-state / TAS / netplay replay stay bit-identical (verified by the greenzone bit-identity + TAS-replay gates). Two review-surfaced fixes landed alongside: a realrustynes-libretro::push_audiohot-path realloc (capacity 4096 vs stereo-interleave 8192) and two TAStudio greenzone anchor-bookkeeping bugs (each with a new regression test). - Docs updated in-change:
docs/scripting.md,docs/frontend.md,docs/cheevos-browser.md,deploy/README.md,CHANGELOG.md. - Full host gate green: fmt · clippy (native all-targets +
scripting/scripting,hd-pack/retroachievements+ all wasm32 targets incl.browser-cheevos/script-wasm) ·cargo build -p rustynes-libretro· rustdoc (-D warnings) · markdownlint (pinned v0.39.0) · AccuracyCoin 141/141 +visual_regressionbyte-identical + the greenzone / TAS bit-identity suites +rustynes-script(mlua + piccolo) tests · version consistency.
RustyNES v2.1.9 — "Fathom" (presentation and signal — marquee CRT shader stack (CRT-Royale / guest-advanced / Sony Megatron) + raw NTSC composite signal-decode path + GIF/WAV capture + generated-palette editor — "Aperture")
RustyNES v2.1.9 — Fathom (presentation and signal — Aperture)
The presentation-and-signal step of the v2.1.5 → v2.2.0 "deepen the existing project" run — a marquee display release. Every shader / signal / capture / editor path is opt-in and default-off, so the shipped default presentation stays byte-identical: AccuracyCoin 141/141 (100.00%), nestest 0-diff, visual_regression byte-identical. Landed in PR #283.
Added
Marquee CRT shader stack (B6)
WGSL ports of three reference slang CRT shaders, first-class BuiltinPass variants selectable in Settings → Shaders, each with #pragma parameter sliders wired to the uniform slots:
- CRT-Royale — the aperture/shadow/slot-mask + scanline + convergence classic.
- crt-guest-advanced / guest-dr-venom — mask type, scanline weight, beam, glow, curvature.
- Sony Megatron — the HDR CRT model (mask/gamma/HDR controls).
Plus per-game shader presets (PerGameConfig.shader_preset, resolved on ROM load via ShaderPresetBank::resolve; None/unknown = byte-identical default) and five showcase built-in presets. All four new shaders parse and validate under the exact naga-29 validator wgpu uses.
Raw NTSC composite signal-decode path (P4)
A new rustynes-ppu::raw_signal module generates the un-decoded 2C02 composite waveform (the Bisqwit nes_ntsc / Mesen2 "raw palette" model — a 512×12 chroma/level/emphasis table, no_std, transcendental-free with a GOLDEN_SIGNAL const-eval lock), and a new signal_decode.wgsl pass reconstructs the true two-level chip square wave from the index framebuffer and demodulates it — giving signal-accurate artifact color (waterfall dithering, transparency tricks) alongside the existing generated-palette + shader ladder. The demodulator uses a rotating-oscillator (3 transcendentals/pixel, not per-tap sin/cos) and a non-negative modulo so edge pixels stay phase-consistent.
Capture + palette editor
- GIF + WAV export (
Container::{Gif,Wav}) extending theav-recordcapture pipeline — video-only palettegen/paletteuse GIF at 25 fps, audio-onlypcm_s16leWAV, with save-dialog filters. The recorder is unchanged (read-only taps; the emulation timeline is never perturbed). - Generated-NTSC palette editor with a live 16×4 preview that regenerates from the current hue/sat/contrast/brightness/gamma params via
generate_base_paletteas you drag.
Notes
- Version: workspace
2.1.8 → 2.1.9(Cargo.lock refreshed). No dependency or mobile changes (the mobile apps re-release at v2.3.0). - No default-build behavior change. All shaders, the raw-composite path, capture, and the palette editor are opt-in; the default presentation and the deterministic core are untouched. AccuracyCoin stays 141/141 (100.00%), nestest 0-diff,
visual_regressionbyte-identical, save-state and the 60-ROM commercial oracle unchanged. - Docs updated in-change:
docs/frontend.md(the CRT stack, capture, palette editor),docs/ppu-2c02.md(raw-composite output),CHANGELOG.md. - Full host gate green: fmt · clippy (native all-targets + feature combos incl.
av-record+ wasm32) · rustdoc (-D warnings) ·no_stdcross-compile (thumbv7em) · markdownlint (pinned v0.39.0) · the new-shader naga validation +shader_pass/raw_signal/per_gamesuites · AccuracyCoin 141/141 +visual_regressionbyte-identical · version consistency.
RustyNES v2.1.8 — "Fathom" (performance — "Tempo")
RustyNES v2.1.8 — "Fathom" (performance — "Tempo")
The performance step of the v2.1.5 → v2.2.0 "deepen the existing project" run — profile-first optimization of the hot render + display paths, every change proven byte-identical (or shipped default-off behind a runtime knob). The shipped default build is byte-identical: AccuracyCoin 141/141 (100.00%), nestest 0-diff, visual_regression / pal_apu_tests unchanged. Two PRs (#284, #286).
Performance
Specialized visible-scanline fast PPU dot path (A1) — #284
Profiling (perf on a mixed corpus) put Ppu::tick at 46.5% of frame self-time — the real hot spot. A1 adds a straight-line fast path for the common visible-scanline case (visible scanline, dots 1..256, rendering stably enabled, no sub-dot disturbance), running the identical helper sequence with the statically-dead event branches pruned; the instant anything sub-scanline happens (a $2000/$2001/$2005/$2006/$2007 write, a $2007 read during render, a sprite-0-hit dot, rendering toggling, an armed OAMADDR corruption from the v2.1.7 Rp2c02G model, etc.) it drops to the exact 2-cycle-ALE loop.
- Measured: ~12% faster on the rendering-heavy
nes_run_frame_nestest(4.54 → 3.98 ms/frame); neutral on the rendering-disabled path. - Byte-identity is the contract, and it's proven: a new
fast_dotloop_diffdifferential test runs the corpus (nestest, flowing_palette, oam_stress, AccuracyCoin, MMC1/MMC3 Holy Mapperel, the scanline demo — and a dedicatedRp2c02GOAMADDR-corruption dimension) through both the fast and exact paths and asserts bit-identical framebuffer + palette-index buffer + audio + CPU cycles + full snapshot, every frame. - Shipped default-OFF (opt-in
Nes::set_fast_dotloop) — as the roadmap's highest-risk item, it's held off the shipped default this cut so the build stays byte-identical, with promotion-to-default recommended after review + a clean-host Criterion confirmation. - Honest architectural finding: a whole-scanline batch is precluded by the v2.0.0 lockstep every-cycle-bus-access scheduler (the PPU is never invited to run a scanline uninterrupted), so this is a per-dot specialization, not a dot-batch.
Vectorized software blitter + wasm size/startup (A2/A4) — #286
- A2 — SIMD software blitter (
crates/rustynes-frontend/src/gfx_blit.rs): scalar /blit_u32/wide::u32x8(desktop) /core::arch::wasm32v128(+simd128) / scalar-fallback paths for the palette-index → RGBA conversion, all asserted SIMD == scalar byte-identical (simd_equals_scalar_byte_identical, sweeping all 512 LUT entries for both the composite-2C02 and RGB-2C05 LUTs). Honest measured result: the conversion is a memory-bound LUT gather (~19 GiB/s, the box's single-thread DRAM ceiling), so no SIMD path clears the >3% adoption bar — the dispatcher keeps scalar-u32as the default and the SIMD variants are validated + available but not forced. (The NTSC ladders are GPU WGSL shaders, so there's no hot CPU display transform there to vectorize.) - A4 — wasm size/startup:
wasm-opt -O4(SIMD + bulk-memory preserved) shrinks the wasm-bindgen output 12.7 → 11.1 MiB raw; streaming instantiation documented. Realtrunk build --release: 3.99 MiB gzip total — 1.01 MiB headroom under the 5 MiB budget.
Notes
- Version: workspace
2.1.7 → 2.1.8(Cargo.lock refreshed). No dependency or mobile changes (the mobile apps re-release at v2.3.0). - No default-build behavior change. The fast dot path is default-off (and differential-tested bit-identical when on); the SIMD blitter is byte-identical to scalar and stays non-default. AccuracyCoin stays 141/141 (100.00%), nestest 0-diff,
visual_regressionbyte-identical, save-state and the 60-ROM commercial oracle unchanged. - Docs updated in-change:
docs/performance.md(the profiled fast-path design + measured before/after + the A2/A4 tables),docs/ppu-2c02.md,docs/frontend.md. Newfast_dotloop_diff+gfx_blittests. - Full host gate green: fmt · clippy (native all-targets + feature combos + all wasm32 targets incl.
+simd128) · rustdoc (-D warnings) ·no_stdcross-compile (thumbv7em) · markdownlint (pinned v0.39.0) · the CPU/PPU +--features test-romssuites (incl.fast_dotloop_diff3/3,gfx_blit3/3) · a realtrunk build --releaseunder the gzip budget · version consistency.
RustyNES v2.1.7 — "Fathom" (hardware revisions & DMA frontier — "Stepping")
RustyNES v2.1.7 — "Fathom" (hardware revisions & DMA frontier — "Stepping")
The hardware-revisions & DMA-frontier step of the v2.1.5 → v2.2.0 "deepen the existing project" run — and the plan's designated honest-frontier release. It adds opt-in, default-off selectable chip-revision + power-on models, and pursues the officially-unsolved sub-instruction DMA frontier to the public oracle's limit, documenting the residual (ADR 0033) rather than faking it. Every new behavior defaults off, so the shipped build is byte-identical: AccuracyCoin 141/141 (100.00%), nestest 0-diff, visual_regression / pal_apu_tests unchanged. Two PRs (#280, #281).
Added
Selectable PPU die-revision + power-on hardware model (P5) — #280
An opt-in PpuRevision enum (default Rp2c02H, opt-in Rp2c02G) gating revision-dependent 2C02 quirks:
- OAMADDR (
$2003) write-during-render OAM corruption on the rev-E+ die — the mid-frame$2003corruption glitch games like Huge Insect trip (modeled per Mesen2; the default die never corrupts → byte-identical). - Power-up palette-RAM model (
PaletteInit; default zeroed, opt-in the canonical blargg power-up dump). - Power-on work-RAM model (
PowerOnConfig/PowerOnRam: zeroed / seeded / patterned) viaNes::from_rom_with_power_on_config— for titles that read uninitialized RAM (Final Fantasy RNG seed, River City Ransom, Cybernoid). Default all-zero = current deterministic behavior.
All wired through additive Nes setters + default-off [emulation] config keys (mirroring v2.1.4's OAM-decay shape); the state round-trips the save-state, and every default is inert (byte-identical).
2A03 die-revision config + the DMA "unexpected read" frontier (P2/P3) — #281
An opt-in Cpu2A03Revision (Rp2A03G default / Rp2A03H) gating the "unexpected read" the late-die 2A03 performs during a DMC↔OAM-DMA overlap. This is the honest DMA-frontier deliverable:
- The full DMA machinery (from the v2.0.0 Timebase rewrite) was verified against every committed oracle —
dmc_dma_during_read4(dma_2007_read/write,dma_4016_read),sprdma_and_dmc_dma,cpu_dummy_writes_oam,oam_read/stress— all pass. - Direct instrumentation of the die-revision gate showed it fires but is a no-op on every committed oracle — the parked address during a DMC+OAM overlap is always the post-
$4014instruction fetch, never a side-effect register — soRp2A03His byte-identical toRp2A03Gtoday. A survey of Mesen2 / ares / BizHawk / nestopia / puNES / fceux found no reference emulator branches DMA on 2A03 die stepping, and no test ROM captures it. - ADR 0033 records this honestly (in the ADR 0002 tradition): what was modeled + verified, and the exact mechanism-level residual that no public oracle can distinguish. Not solved, not faked — documented, with the config surfaced for a future observable case.
Notes
- Version: workspace
2.1.6 → 2.1.7(Cargo.lock refreshed). No dependency or mobile changes (the mobile apps re-release at v2.3.0). - No default-build behavior change and no accuracy-number moves. Every revision/power-on toggle defaults off; the DMA gate is a proven no-op on the shipped default. AccuracyCoin stays 141/141 (100.00%), nestest 0-diff, save-state and the 60-ROM commercial oracle unchanged.
- Docs added/updated in-change: the new
docs/adr/0033-cpu-2a03-revision-dma-frontier.md,docs/ppu-2c02.md,docs/cpu-6502.md,docs/scheduler.md,docs/accuracy-ledger.md. - Full host gate green: fmt · clippy (native all-targets + feature combos) · rustdoc (
-D warnings) ·no_stdcross-compile (thumbv7em) · markdownlint (pinned v0.39.0) · the CPU/PPU/DMA +--features test-romssuites (incl.cpu_2a03_revision,dmc_dma, the PPU-revision unit tests) · version consistency.
RustyNES v2.1.6 — Fathom (expansion audio — decibel oracle + hardware/Mesen2 channel-level calibration + Namco 163 12 dB fix + mix UI/scopes; Timbre)
RustyNES v2.1.6 — "Fathom" (expansion audio — "Timbre")
The expansion-audio fidelity step of the v2.1.5 → v2.2.0 "deepen the existing project" run. It turns the expansion-audio characterization corpus into a real accuracy oracle, calibrates the expansion channel levels to the hardware / Mesen2 targets, and adds a per-channel audio mixer UI — all while keeping the base 2A03 NTSC output byte-identical (expansion audio is a separate additive mix term): AccuracyCoin 141/141 (100.00%), nestest 0-diff, blargg_apu_2005 / pal_apu_tests / visual_regression unchanged. Three PRs (#277, #279, plus the #278 docs sync).
Added
Expansion-audio decibel oracle — #279
Upgraded crates/rustynes-test-harness/tests/audio_expansion.rs from pure insta snapshots into a real accuracy oracle: each bbbradsmith db_* decibel-comparison ROM now has a machine-verifiable level criterion. The new level_db_* tests measure the peak amplitude of the reference-2A03-square and expansion-square segments in the rendered waveform (common::capture_frame_peaks over deterministic frame windows) and assert the expansion/reference ratio against the Mesen2 / hardware target — APU triangle÷square ≈ 0.524, VRC6 ≈ 1.506, MMC5 ≈ 1.000, N163 1-channel ≈ 6.02. The VRC7 patch set is verified against the canonical Nuke.YKT OPLL instrument table (patch_vrc7), and the N163 long-wave wavetable handling and 5B log-DAC step law get unit-test coverage. The 19 insta snapshots are retained as byte-exact regression guards for the pure-listening ROMs.
Expansion-audio mix UI + per-channel visualization (B7) — #277
A dedicated frontend Audio Mixer panel (Tools → Audio Mixer): per-source mix-balance sliders for the five base APU channels and every active expansion channel (VRC6 / VRC7 / N163 / Sunsoft 5B / MMC5 / FDS), with presets (Authentic / Balanced-Mesen-style / Expansion-boost / Reset) and live per-channel oscilloscope + VU meters. The mixer edits the same determinism-safe channel_gain / channel_mask frontend overlay as the Settings → Audio tab — the deterministic core produces identical samples regardless of slider positions (byte-identical save-state / TAS / netplay replay preserved; the per-channel taps are a read-only copy, never fed back into synthesis).
Changed
Expansion-audio channel levels calibrated to hardware / Mesen2 db_* levels — #279
VRC6 square 256 → 979 (≈ 0.39× → ≈ 1.51× the 2A03 pulse), MMC5 pulse/PCM 256/16 → 650/40 (≈ 0.39× → ≈ 1.0×, "equivalent to the APU" per hardware), and Namco 163 64 → 261 (≈ 1.48× → ≈ 6.02× for 1-channel mode — no reference emulator attenuates N163; ours was ~12 dB too quiet). The N163 fix is shared with the NSF playback path, and the VRC6/MMC5 mix constants are now single shared pub(crate) consts referenced by both the cartridge and NSF paths (they can no longer silently drift). The base 2A03 NTSC output stays byte-identical — expansion audio is a separate additive mix_audio term (0 for non-expansion mappers) — so AccuracyCoin (141/141), blargg_apu_2005, nestest, and visual_regression are unchanged; only the three db_vrc6a/b / db_mmc5 expansion snapshots were re-blessed (audio hash only, provably more accurate vs the measured targets).
Deferred (documented honestly)
- Sunsoft 5B absolute level and VRC7 FM level are honest documented gaps (
docs/accuracy-ledger.md§Expansion-audio levels). The 5B log-DAC shape is hardware-exact but its full vol-15 / 3-simultaneous-tone range overflows thei16mix_audiocontract (needs a wider mix path); the VRC7 OPLL FM synth + patch ROM are correct, but the pseudo-sine absolute level is patch-dependent and has no clean square-vs-square oracle. Both stay snapshot-guarded rather than faked.
Notes
- Version: workspace
2.1.5 → 2.1.6(Cargo.lock refreshed). No dependency or mobile changes (the mobile apps re-release at v2.3.0). - No default-build behavior change and no accuracy-number moves. Expansion-audio is a separate additive mix term and the mixer UI is a frontend gain overlay; the base APU + core golden vectors are byte-identical. AccuracyCoin stays 141/141 (100.00%), nestest 0-diff, save-state and the 60-ROM commercial oracle unchanged.
- Docs updated in-change:
docs/apu-2a03.md,docs/mappers.md,docs/accuracy-ledger.md,docs/testing-strategy.md,docs/frontend.md,docs/STATUS.md. - Full host gate green: fmt · clippy (native all-targets + feature combos + wasm32) · rustdoc (
-D warnings) ·no_stdcross-compile (thumbv7em) · markdownlint (pinned v0.39.0) · the APU/mapper +--features test-romssuites (incl.audio_expansion24/24,pal_apu_tests10/10) · version consistency.
RustyNES v2.1.5 — Fathom (regression net & residual — Holy Mapperel mapper regression net + PAL APU frame-counter 10/10 + real TURN NAT-retransmit production fix + fat-LTO A/B validation + MMC3 F5.0 A12-phase study; Vernier)
RustyNES v2.1.5 — "Fathom" (regression net & residual — "Vernier")
The regression-net & residual step that opens the v2.1.5 → v2.2.0 "deepen the existing project" run (the iOS App Store / Google Play launch is deferred to v2.3.0). Five workstreams across six PRs (#268/#270, #272, #273, #274, #275): a new mapper regression net, the first PAL-region APU oracle, a real netplay production-bug fix, a measured fat-LTO validation, and an MMC3 IRQ-timing instrumentation study. The deterministic NTSC core stays byte-identical — AccuracyCoin 141/141 (100.00%), nestest 0-diff, blargg_apu_2005 11/11, the 60-ROM commercial oracle and visual_regression unchanged, the #![no_std] chip stack untouched.
Added
Mapper bank-reachability + IRQ regression net (Holy Mapperel) — #268 / #270
Wired the tepples Holy Mapperel cartridge-PCB-assembly test ROMs into CI as a dedicated mapper regression net (crates/rustynes-test-harness/tests/holy_mapperel.rs, on the default --features test-roms). Holy Mapperel detects which mapper it is running on purely from the console's mirroring + bank-switching response (no header trust), sizes PRG/CHR ROM/RAM, proves every PRG/CHR bank is reachable, and exercises WRAM + the MMC3/FME-7 interval-timer IRQ — coverage the AccuracyCoin and blargg corpora barely touch. Each of the 17 committed zlib-licensed ROMs is driven to its settled result screen and pinned by an insta framebuffer-hash snapshot, with settled (byte-stable across a late window, so a Morse-code hard-crash never green-lights) and non-blank structural guards running first. 15/17 reach every bank with code 0000; the two MMC1 and two FME-7 ROMs surface an honestly-pinned WRAM-protection residual whose cause differs per mapper (MMC1 genuinely omits software WRAM write-protect — a widely-shared simplification; FME-7's is a narrow "RAM-selected-but-disabled → open-bus" edge). Purely additive: no core behavior changes, AccuracyCoin stays 141/141. Residuals recorded in docs/accuracy-ledger.md.
First PAL-region APU oracle + PAL frame counter — 10/10 pal_apu_tests — #275
Modeled the PAL (2A07) APU frame-counter sequencer step positions and wired blargg's freely-redistributable pal_apu_tests corpus (10 PAL-calibrated sub-ROMs) into CI as the first PAL-region APU oracle. In doing so it corrects a false oracle: the prior revision drove these 2005-era NROM ROMs (which have no PRG-RAM) through the $6000 status runner and asserted status == 0 — so $6000 read 0 forever and every test "passed" vacuously, validating nothing. The suite now decodes the ROMs' real on-screen verdict (PASSED / FAILED: #n) from the nametable via a new run_nes_screen harness runner.
- PAL frame counter (
crates/rustynes-apu/src/frame_counter.rs): a region-derivedpalselector (true only forRegion::Pal; NTSC/Dendy keep the NTSC positions) clocks the 4-step sequence at 8313 / 16627 / 24939 / 33252-33254 and the 5-step at 8313 / 16627 / 24939 / 41565-41566 (Mesen2stepCyclesPal), replicating the IRQ-flag-visibility /irq_line_activesplit verbatim. - Length halt/reload write-ordering fix (
crates/rustynes-apu/src/length.rs) — closes10.len_halt_timingand11.len_reload_timing, and is the notable find of this release: the length counter applied a$4000-bit-5 halt change and a$4003reload immediately during the CPU bus write, i.e. before the APU's half-frame length clock — the inverse of silicon, which applies both after.LengthCounternow defers them (new_halt/reload_val+ aprevious_countsnapshot, promoted once per CPU cycle after the half-frame clock), mirroring TetaNESLengthCounter::reloadand Mesen2's_newHaltValue+ reload-request. This bug was latent on NTSC too — the NTSC blargg 10/11 ROMs are the same vacuous$6000no-PRG-RAM ROMs, so only the PAL screen-verdict oracle could catch it; the fix quietly improves NTSC correctness while staying byte-identical. - Result: 10/10 (was a vacuous 10/10, honestly 3/10 pre-model). NTSC byte-identity is sacred and preserved: the step-position change is region-gated (the
palselector is derived, not persisted — the APU snapshot format is untouched), and the ordering fix is byte-identical on NTSC by construction. AccuracyCoin 141/141,blargg_apu_200511/11,apu_mixer/volume_tests/nestest/visual_regressionall unchanged.
MMC3 R1/R2 residual A12-phase instrumentation study (F5.0) — #274
A purely-observational, default-off probe feature (mmc3-a12-phase-probe) plus a reproducible study fixture (crates/rustynes-test-harness/tests/mmc3_r1r2_phase_probe.rs) that answers, with fresh direct instrumentation, the one avenue ADR 0002's F5.0 closure left open: on the four #[ignore]'d MMC3 IRQ residuals, does any qualifying (gap >= 3) A12 rising edge that clocks the IRQ counter ever land in the post-access (M2-high) half of a host CPU cycle? The study refines the finding: the two scanline_timing residuals have zero post-access rises (confirming Session B's earlier indirect result), but the two "reload/set-IRQ-every-clock" residuals — never tested before — have 4 post-access rises each. So the "no post-access rise" premise is ROM-specific, not a structural NTSC-MMC3 property. No production, scheduler, or MMC3 default behavior changed; AccuracyCoin stays 141/141 and all four residuals stay #[ignore]'d. The ares-style M2-edge low-time filter is recorded in ADR 0002 as the one genuinely-untested axis-B lever, deferred to a maintainer decision.
Changed
fat-LTO release profile — measured & validated — #273
[profile.release] already shipped lto = "fat" + codegen-units = 1 (since the v1.0.0 transplant), but the choice had never been backed by an in-repo A/B and docs/performance.md even mis-stated the profile as lto = "thin". The measure-first A/B (everything else held; same host, back-to-back Criterion, taskset-pinned) shows fat is +8.4% (nes_run_frame_nestest) / +20.8% (ppu_tick_one_frame, flowing_palette) faster on every cross-crate path and within noise on the single-crate control — the signature of a cross-crate-inlining win — at zero byte-identity cost (both profiles pass the golden oracle byte-for-byte). No default-build change; this documents and retroactively justifies the existing default, corrects the stale text, documents the opt-in release-native / x86-64-v3 variants, and refreshes stale 139/139 oracle comments to 141/141.
Fixed
Netplay: TURN client now retransmits — a real symmetric-NAT production bug — #272
The native TURN client (crates/rustynes-netplay/src/relay.rs) sent each Allocate / CreatePermission request exactly once, so a single dropped UDP datagram hard-failed the whole NAT traversal — real symmetric-NAT netplay over any lossy internet path was as fragile as the intermittently-red CI loopback test (which was blocking release-auto). The client now retransmits every 250 ms (RTO) until the caller's overall timeout, guided by RFC 5389 §7.2.1 (a fixed 250 ms RTO, not the RFC's 500 ms default + exponential backoff), recovering transparently from a dropped datagram (STUN/TURN requests are idempotent). The receive loop also tolerates a transient ConnectionReset / ConnectionRefused — the ICMP "Port Unreachable" a peer's not-yet-bound socket provokes during startup, the true windows-latest flake cause — as "retransmit and retry" rather than a hard failure. The session-digest determinism contract is unchanged.
Notes
- Version: workspace
2.1.4 → 2.1.5(Cargo.lock refreshed). No dependency or mobile changes (the mobile apps re-release at v2.3.0). - No default-build behavior change and no accuracy-number moves. The PAL frame counter and length-ordering fix are region-gated / byte-identical on NTSC; the mapper net and MMC3 study are test-only; the fat-LTO work is documentation. AccuracyCoin stays 141/141 (100.00%), nestest 0-diff, save-state and the 60-ROM commercial oracle unchanged.
- Docs updated in the same changes:
docs/accuracy-ledger.md,docs/apu-2a03.md,docs/mappers.md,docs/performance.md,docs/testing-strategy.md,docs/STATUS.md,docs/netplay-webrtc.md, ADR 0002. New oracles + the study fixture undercrates/rustynes-test-harness/tests/. - Full host gate green: fmt · clippy (native all-targets + feature combos + wasm32) · rustdoc (
-D warnings) ·no_stdcross-compile (thumbv7em) · markdownlint (pinned v0.39.0) · the CPU/PPU/APU/mapper +--features test-romssuites (incl.pal_apu_tests10/10,holy_mapperel,mmc3_r1r2_phase_probe) · version consistency.
RustyNES v2.1.4 — Fathom (accuracy hardening — opt-in OAM decay + BestEffort boot-smoke sweep + MMC3-clone A12/IRQ timing oracle; Caliper)
RustyNES v2.1.4 — "Fathom" (accuracy hardening — "Caliper")
The accuracy-hardening step of the v2.1.x "Fathom" line: one opt-in PPU accuracy feature plus two chip-level test oracles, all on the byte-identical cycle-accurate core. The deterministic core is untouched — AccuracyCoin stays 141/141 (100.00%), nestest 0-diff, and the default-build framebuffer/audio are byte-identical (the OAM-decay feature defaults to OFF; the mapper work is test-only). Three PRs (#264–#266), the last of the actionable Fathom accuracy tasks.
Added
Optional OAM decay (accuracy, default-OFF) — F2.3 (#265)
The 2C02's Object Attribute Memory is dynamic RAM: sprite evaluation implicitly refreshes it every rendered scanline, but with rendering disabled long enough the un-refreshed rows lose charge and decay to a fixed garbage pattern. RustyNES now models this exactly like Mesen2 (ReadSpriteRam/WriteSpriteRam, a 3000-CPU-cycle refresh window per 8-byte row): every OAM read ($2004 and the sprite-evaluation reads) and write refreshes the row's timestamp, and a row un-touched past the window decays on the next read to ((sprAddr & 3) == 2) ? (sprAddr & 0xE3) : sprAddr. It is off by default — with the default the framebuffer / audio / replay output and the AccuracyCoin / commercial / visual-regression suites are byte-identical to a decay-free build. NTSC/Dendy only (PAL's refresh cadence masks decay). Deterministic when on (driven off the PPU's monotonic dot counter, never wall-clock / OS-RNG). Enable via Settings → Emulation → "OAM decay (accuracy)", the [emulation] oam_decay config bool, or Nes::set_oam_decay(true). The per-row decay state round-trips the save-state via an additive PPU_SNAPSHOT_VERSION v7 tail (stored as a relative age so a run-ahead / netplay snapshot→restore stays byte-identical); pre-v7 .rns blobs still load.
CI boot-smoke sweep of every BestEffort mapper family — F3.1 (#264)
A new test-harness suite (crates/rustynes-test-harness/tests/v21_best_effort_sweep.rs) exercises the full parse → construct → dispatch → run-loop integration for all 26 BestEffort (Tier-2) mapper families — the reference-ported long-tail boards that lack a cleanly-booting redistributable dump and so can never be honestly oracle-gated. The target set is derived live from the rustynes-mappers::mapper_tier classifier (the single source of truth) across the full 12-bit NES 2.0 mapper space, so any future family promoted into or out of BestEffort is swept — or dropped — automatically with no edit. Each family is built into a synthetic minimal iNES / NES 2.0 image and run for ~60 headless deterministic frames, asserting no panic, an exact mapper-id round-trip, and a well-formed 256×240 framebuffer. A panic in a BestEffort register decode, bank wiring, or per-tick hook is now caught in CI instead of only when a user loads a real cart. Pure safety net: it promotes nothing, adds no accuracy claim, and leaves runtime + the #![no_std] core byte-identical. No real panics were found.
Shared MMC3-clone A12/IRQ timing oracle — F3.3 (#266)
A new chip-level oracle (crates/rustynes-test-harness/tests/mmc3_clone_a12.rs) that drives the eleven MMC3-clone boards sharing the Mmc3Clone core (44/49/52/115/134/189/205/238/245/348/366) and a reference plain Mmc3 through an identical canonical rendering-scanline A12 edge sequence, and asserts the clone reproduces the reference's per-scanline IRQ-assert bitmap bit-for-bit: with reload latch L, the IRQ first fires on rising edge L + 1 and re-asserts every L + 1 scanlines once acknowledged. Supporting tests pin the enable/ack gate, reload periodicity, and the A12 edge filter (a held-high line clocks exactly once). It is additive evidence deepening the eight Curated members' classification (and giving the three high-id BestEffort boards 238/348/366 real IRQ-timing evidence too) — it moves no tier. No timing bug was found; the clone core genuinely matches MMC3.
Notes
- Version: workspace
2.1.3 → 2.1.4(Cargo.lock refreshed). No mobile / dependency changes (the mobile apps re-release at v2.2.0). - No accuracy/oracle number moves. OAM decay defaults to OFF, and the mapper work is test-only. AccuracyCoin stays 141/141 (100.00%), nestest 0-diff, save-state and the 60-ROM commercial oracle unchanged.
- With v2.1.4 the actionable Fathom accuracy tasks are complete. The two remaining plan items are dispositioned, not dropped: F4.3 (FDS medium model) needs the copyright-gated real FDS BIOS to verify write-CRC synthesis and stays a deferred stretch; F5.1 (the MMC3 R1/R2 axis-B prototype) is moot because F5.0 closed the residual by design (see ADR 0002).
- Docs updated:
docs/ppu-2c02.md(OAM decay),docs/mappers.md(both oracles),docs/accuracy-ledger.md. New tooling undercrates/rustynes-test-harness/tests/. - Full host gate green: fmt · clippy (native all-targets +
retroachievements+ wasm32) · rustdoc (-D warnings) ·no_stdcross-compile (thumbv7em) · markdownlint · the PPU/mapper/OAM-decay +--features test-romssuites · version consistency.
RustyNES v2.1.3 — Fathom (quality-of-life — APU filter-model audio fix + Game Genie code nomination/database + universal header-robust matching + MkDocs docs handbook; Codex)
RustyNES v2.1.3 — "Fathom" (quality-of-life — "Codex")
The quality-of-life step of the v2.1.x "Fathom" line: an audio-fidelity fix, a full Game Genie code database with per-game nomination, and a proper documentation handbook — all on the byte-identical cycle-accurate core. The deterministic core is untouched: AccuracyCoin stays 141/141 (100.00%), nestest 0-diff, and the default-build audio + framebuffer are byte-identical (the new audio filter model defaults to the authentic nes chain; the Game Genie work is frontend-only). Four PRs (#259–#262).
Added
APU audio filter-model selector — fixes the "thin / missing channel" sound (#259)
RustyNES applies the authentic NES front-loader analog filter — a 90 Hz + an aggressive 440 Hz high-pass + a 14 kHz low-pass. That is byte-correct (identical to ares/TetaNES; verified by the APU golden vectors), but the 440 Hz high-pass rolls off the bass/triangle register hard, which reads as a missing channel next to Mesen2 / FCEUX / Nestopia (they omit that high-pass, so they sound fuller). You can now pick the model in Settings → Audio → Filter model ([audio] filter_model): nes (default, authentic — byte-identical to earlier builds), famicom (a single ~37 Hz high-pass — the nesdev Famicom spec, fuller low end), or clean (a ~10 Hz DC-block only — fullest, the Mesen2-like character). Core hooks: Apu::set_filter_model / Nes::set_apu_filter_model. Tonal only — channel content, determinism, save-states, and the audio oracle are unchanged on the default. The DRC resampler + band-limited BLEP synthesis were audited and found correct (they match Mesen2's approach); no change needed there.
Game Genie per-game code nomination + a full USA/World database (#261)
The Cheats panel now nominates the known Game Genie codes for the loaded game — a category-grouped "Known codes" pick-list, each row feeding the same validated GenieCode::new + persistence path as a hand-typed code — instead of only decoding codes you type (it previously showed "No Game Genie cheats…" for essentially every commercial ROM). A new bulk catalog (genie_database_full.tsv, ~10,800 codes across ~520 USA/World games) is ingested from the openly-licensed libretro-database Game Genie files and keyed to every known dump's CRC32 via the No-Intro NES DAT. To match whatever dump "flavor" a user has, a ROM is recognized on two CRC32 keys — the header-excluded rom_crc32 and the full-file No-Intro rom_crc32_full, unioned + de-duplicated. The catalog ships on every target including the wasm browser demo (it gzips small enough to fit the size budget). Frontend-only — the deterministic core is untouched and codes re-validate at load.
Game Genie matching is now header-insensitive for all ~520 games (#262)
The full-file key only matches a dump whose 16-byte iNES header is byte-identical to No-Intro's, so a re-headered dump (common — different tools rewrite the header) missed. A third catalog (genie_database_headerless.tsv, ~16.5k rows / 521 games) carries the same codes re-keyed to the header-excluded rom_crc32 — derived from the NES 2.0 database's per-game content CRCs (<rom crc32> = crc32(PRG++CHR), proven equal to rom_crc32 and to the zlib crc32_combine identity), joined by normalized game name with a committed manual alias table for the long-tail titles. A game now resolves from PRG + CHR content regardless of its header. (Previously only 6 curated classics had a header-excluded key — and those turned out to be mislabeled full-file CRCs.) Regenerated by scripts/gg/gen_headerless_genie_db.py; the NES 2.0 DB is a build-time input, never committed.
Material for MkDocs documentation handbook at /docs/ (#260)
GitHub Pages now serves three sections from one deployment: the playable wasm demo at the site root (/), the workspace rustdoc at /api/, and a new Material-themed handbook at /docs/ (https://doublegate.github.io/RustyNES/docs/). The handbook renders the existing docs/ subsystem specs and user guide directly (no duplicated content — docs-as-spec), with a curated grouped navigation, a light/dark palette toggle, instant navigation, search, copy-to-clipboard code, per-page social preview cards, and privacy-plugin self-hosted fonts.
Notes
- Version: workspace
2.1.2 → 2.1.3(Cargo.lock refreshed). No mobile / dependency changes (the mobile apps re-release at v2.2.0). - No accuracy/oracle number moves. The audio filter model defaults to the authentic
neschain (byte-identical to prior builds); the Game Genie and docs work is frontend-only. AccuracyCoin stays 141/141 (100.00%), nestest 0-diff, save-state and the 60-ROM commercial oracle unchanged. - wasm size budget verified with all three Game Genie catalogs compiled in: bundle is ~3.96 MiB gzip against the 5 MiB cap (~1 MiB headroom).
- Docs updated:
docs/frontend.md,docs/apu-2a03.md, plus the new/docs/handbook. New tooling:scripts/gg/(Game Genie header-robust re-key generator + alias table + README). - Full host gate green: fmt · clippy (native all-targets +
retroachievements+ wasm32) · rustdoc (-D warnings) · markdownlint · the APU filter + Game Genie unit suites · version consistency.