Skip to content

Releases: doublegate/RustyNES

Release list

RustyNES v2.2.1 — Housekeeping patch (dev-tooling archival + dependency consolidation + FDS test corpus)

Choose a tag to compare

@github-actions github-actions released this 15 Jul 06:40
7761c80

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/versionName on Android and MARKETING_VERSION on 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_std cross-compile (thumbv7em) · markdownlint · cargo deny check.

RustyNES v2.2.0 — "Capstone" (milestone cut — netplay matchmaking/lobby + FDS medium model + peripherals + quality/security pass)

Choose a tag to compare

@github-actions github-actions released this 12 Jul 16:56
966de0f

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. New SignalMessage variants — ListRooms { rom_hash }RoomList { rooms } (open, joinable, optionally game-filtered rooms; each RoomInfo carries code / player-count / capacity / rom_hash and no SDP/ICE/identity), and QuickMatch { rom_hash, max_players }Matched { room, slot, max_players } (server-side quick-play: join any open room for the ROM via the shared add_to_room primitive, or mint a fresh QM-NNNNNN room). The room-list frame is parsed by a brace-depth walk bounded at MAX_ROOM_LIST (256) so an oversized frame cannot force an unbounded allocation. Determinism / rollback contract untouched — signaling only.
  • Delayed-stream spectatorsSpectatorConfig.delay_frames (clamped to MAX_DELAY_FRAMES = 512 ≈ 8.5 s) layers an intentional broadcast / anti-spoiler / jitter-smoothing hold atop the natural spectator lag: frame f is revealed only once frame f + delay_frames is 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 to NetplayUi::spectator_delay_frames (default 0).
  • Hardened desync surfaceDesyncDiagnostics gains a single graded DesyncStatus { 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 the NetMessage::Checksum digests the session already exchanges.
  • Peer-liveness RTT timeouts — a graded PeerLink { Live / Interrupted / TimedOut } for an already-synced NetplayConnection, driven by last_recv against peer_interrupt_timeout (2 s) / peer_disconnect_timeout (5 s), plus a terminal DisconnectReason::PeerTimeout. Deliberately far above Mesen's trigger-happy ~150 ms: a single lost 1 Hz Quality ping 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 $80 start 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 fixed HEAD_RESEEK_CYCLES motor-restart not-ready window with a belt-driven, distance-proportional seek time (HEAD_SEEK_BYTES_PER_CYCLE velocity + HEAD_SEEK_SETTLE_CYCLES settle, 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 copyright disksys.rom and runs only from a gitignored local dump (docs/accuracy-ledger.md records the CI-verifiable-vs-local-only split). Additive and deterministic: with the head-seek model off (the default) a non-writing .fds run 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 $4016 bit 2 (Nes::set_microphone / Bus::set_microphone), wired through the frontend input path (hold-to-talk M key → 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 expansionfuzz/ 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_bytes binary UDP + SignalMessage::parse JSON signaling/lobby, both ingesting bytes straight off the wire), save_state (parse_header + extract_thumbnail + restore_quiet), and movie (Movie::deserialize). Each builds under nightly cargo-fuzz and runs clean for tens of thousands of iterations. The movie target 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 matching mkdocs.yml nav entries.

Changed

  • Movie (.rnm) deserializer hardening — the new movie fuzz target surfaced two OOM-DoS paths in Movie::deserialize (crates/rustynes-core/src/movie.rs), both now fixed byte-identically for valid input: (1) the untrusted 4-byte frame_count was passed straight to Vec::with_capacity, so a 49-byte header could claim a multi-gigabyte reservation — now capped at remaining_bytes / width; (2) a bytes_per_frame of 0 made each r.take(0) consume no input, so the frame loop pushed frame_count empty records out of a finite file — now rejected up front. Regression test deserialize_hostile_frame_count_does_not_oom added; 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_regression byte-identical, pal_apu_tests 10/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_recv refreshes only for the adopted peer's source, so a third party can no longer suppress PeerTimeout); QuickMatch now scans self.rooms uncapped (was capped at MAX_ROOM_LIST, contradicting its own contract); the FDS DMC-conflict $4016 read now ORs the mic bit (mic << 2); and the FDS load_disk_tail offset carries a debug_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_regression byte-identical + nestest 0-diff + pal_apu_tests 10/10 + the netplay determinism / FDS write-verify suites · no_std thumbv7em cross-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")

Choose a tag to compare

@github-actions github-actions released this 12 Jul 15:02
36bbb86

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 .rnmproj model 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.drawLine HUD drawing (mlua + piccolo + both render sites), a bundled example-script library (hud_graph.lua, palette_viewer.lua, lifecycle_events.lua), and a wasm piccolo parity 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 + an ra-proxy compose 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. DualSystem presentation 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. DualSystem on 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 in docs/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_regression byte-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 real rustynes-libretro::push_audio hot-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_regression byte-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")

@github-actions github-actions released this 12 Jul 09:04
3b53475

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 the av-record capture pipeline — video-only palettegen/paletteuse GIF at 25 fps, audio-only pcm_s16le WAV, 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_palette as 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_regression byte-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_std cross-compile (thumbv7em) · markdownlint (pinned v0.39.0) · the new-shader naga validation + shader_pass / raw_signal / per_game suites · AccuracyCoin 141/141 + visual_regression byte-identical · version consistency.

RustyNES v2.1.8 — "Fathom" (performance — "Tempo")

Choose a tag to compare

@doublegate doublegate released this 12 Jul 07:24
31d11bd

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_diff differential test runs the corpus (nestest, flowing_palette, oam_stress, AccuracyCoin, MMC1/MMC3 Holy Mapperel, the scanline demo — and a dedicated Rp2c02G OAMADDR-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::wasm32 v128 (+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-u32 as 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. Real trunk 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_regression byte-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. New fast_dotloop_diff + gfx_blit tests.
  • Full host gate green: fmt · clippy (native all-targets + feature combos + all wasm32 targets incl. +simd128) · rustdoc (-D warnings) · no_std cross-compile (thumbv7em) · markdownlint (pinned v0.39.0) · the CPU/PPU + --features test-roms suites (incl. fast_dotloop_diff 3/3, gfx_blit 3/3) · a real trunk build --release under the gzip budget · version consistency.

RustyNES v2.1.7 — "Fathom" (hardware revisions & DMA frontier — "Stepping")

Choose a tag to compare

@doublegate doublegate released this 12 Jul 06:48
4666217

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 $2003 corruption 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) via Nes::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-$4014 instruction fetch, never a side-effect register — so Rp2A03H is byte-identical to Rp2A03G today. 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_std cross-compile (thumbv7em) · markdownlint (pinned v0.39.0) · the CPU/PPU/DMA + --features test-roms suites (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)

Choose a tag to compare

@github-actions github-actions released this 12 Jul 04:18
ba6d580

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 the i16 mix_audio contract (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_std cross-compile (thumbv7em) · markdownlint (pinned v0.39.0) · the APU/mapper + --features test-roms suites (incl. audio_expansion 24/24, pal_apu_tests 10/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)

@github-actions github-actions released this 12 Jul 01:52
af6b14b

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-identicalAccuracyCoin 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-derived pal selector (true only for Region::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 (Mesen2 stepCyclesPal), replicating the IRQ-flag-visibility / irq_line_active split verbatim.
  • Length halt/reload write-ordering fix (crates/rustynes-apu/src/length.rs) — closes 10.len_halt_timing and 11.len_reload_timing, and is the notable find of this release: the length counter applied a $4000-bit-5 halt change and a $4003 reload immediately during the CPU bus write, i.e. before the APU's half-frame length clock — the inverse of silicon, which applies both after. LengthCounter now defers them (new_halt / reload_val + a previous_count snapshot, promoted once per CPU cycle after the half-frame clock), mirroring TetaNES LengthCounter::reload and Mesen2's _newHaltValue + reload-request. This bug was latent on NTSC too — the NTSC blargg 10/11 ROMs are the same vacuous $6000 no-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 pal selector 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_2005 11/11, apu_mixer / volume_tests / nestest / visual_regression all 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 under crates/rustynes-test-harness/tests/.
  • Full host gate green: fmt · clippy (native all-targets + feature combos + wasm32) · rustdoc (-D warnings) · no_std cross-compile (thumbv7em) · markdownlint (pinned v0.39.0) · the CPU/PPU/APU/mapper + --features test-roms suites (incl. pal_apu_tests 10/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)

Choose a tag to compare

@github-actions github-actions released this 11 Jul 18:26
f4b2077

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 snapshotrestore 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 under crates/rustynes-test-harness/tests/.
  • Full host gate green: fmt · clippy (native all-targets + retroachievements + wasm32) · rustdoc (-D warnings) · no_std cross-compile (thumbv7em) · markdownlint · the PPU/mapper/OAM-decay + --features test-roms suites · 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)

Choose a tag to compare

@github-actions github-actions released this 11 Jul 13:51
a5a5909

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 nes chain (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.