v2.4.0 "Save Point"
Rusty2600 v2.4.0 "Save Point"
The first release of the RustyNES gap-closure arc (v2.4.0 -> v3.0.0) — a
comprehensive gap analysis against Rusty2600's mature sibling NES emulator,
RustyNES, found a handful of real, closeable gaps despite the two projects'
otherwise-complete v1.1.0 -> v2.3.0 parity line. This release closes the
single most player-visible one, plus three smaller but genuine gaps
surfaced along the way.
This is also the first Rusty2600 release shipped through a real GitHub
PR with CI + automated review-bot adjudication (PR #1), rather than a
direct-to-main push — every release through v3.0.0 will follow this same
flow.
Manual save-state slots (the headline gap)
Rusty2600 had no player-facing "save my game" feature at all — despite
rusty2600-core::SaveState being a real, versioned, battle-tested format
(ADR 0007) since v1.10.0, consumed only by the rewind ring, run-ahead,
netplay rollback, and Lua's saveState()/loadState(). Every other modern
emulator lets a player save progress to a slot; Rusty2600 couldn't.
File -> Save State / Load State now offer 8 numbered slots per ROM,
each keyed by an FNV-1a hash of the loaded ROM's raw bytes
(crate::config::save_slot_path) — a slot can never silently load against
the wrong cartridge, since SaveState::restore's existing rom_tag check
enforces this. The File menu shows each slot's live status (empty, or its
last-saved timestamp). Loading a slot now also clears the rewind ring — a
real bug caught by automated PR review: without this, pressing Rewind
right after a slot load would jump back to the pre-load timeline, a
confusing discontinuity. Native-only for now; wasm-side persistence is a
later release's scope.
CI-gated performance-regression check
A new rusty2600-core::system_full_ntsc_frame Criterion bench drives one
full NTSC frame (262 lines x 228 color clocks) through the real
CPU+TIA+RIOT+cart System — not a per-chip proxy. scripts/ bench_regression_check.sh runs it in a new CI perf job and fails on a
measured mean above a fixed absolute ceiling (3.75 ms, ~3x the
measured ~1.25 ms baseline — deliberately not relative/percentage-based,
since CI-runner timing noise makes relative comparisons unreliable).
Validated for real: a regression was deliberately injected, confirmed to
fail the gate, then reverted.
Paddle-timing Stella-oracle differential test
The real RC-circuit analog paddle simulation (v2.1.0) had never been
cross-checked against an independent oracle with an executable test — only
by manual transcription at port time. A from-scratch re-derivation of
Stella's AnalogReadout formula, kept test-local (never shared with
production code), now confirms every RC constant and formula in
rusty2600-tia::paddle matches Stella's source exactly, across a full
position sweep and multiple charge/discharge scenarios. A real
commercial-game cross-check (Breakout/Warlords/Kaboom!) stays honestly
documented as blocked — no such ROM is legally obtainable in this
development environment.
ROM loading from .zip archives
Neither the native File -> Open ROM dialog nor the wasm GH-Pages demo
could previously load a ROM packaged inside a .zip — a real, current gap
given zipped ROM redistribution is the norm. A new shared rom_archive
module extracts the first .a26/.bin/.rom entry from an in-memory
zip, with reads bounded to a 1 MiB ceiling regardless of what the zip's
central directory claims (a decompression-bomb guard, not just a
declared-size check) and never panics on malformed input.
GitHub repo hygiene
CODE_OF_CONDUCT.md, SECURITY.md (with a Rusty2600-specific threat
model — ROM/zip parsing, the Thumb coprocessor interpreter, save-state
deserialization, netplay, Lua scripting), .github/CODEOWNERS,
.github/dependabot.yml, an issue-template config.yml plus a
scheme_request.md template scoped to the now-closed 26/26 bankswitch
catalogue, and GitHub Discussions enabled. The repo description's stale
"23-of-25 bankswitch schemes" claim (26/26 since v2.3.0) was also
corrected.
PR review, for real
PR #1 was reviewed by GitHub Copilot and Gemini Code Assist. All 9 inline
findings were genuine and fixed — none dismissed:
- Save-slot status is now cached instead of re-probed via 8 filesystem
statcalls every single frame at 60+ FPS. - Loading a state slot now clears the rewind ring (see above).
- RetroAchievements ROM identification is only attempted when the ROM
actually loaded successfully (a pre-existing issue, surfaced because
this release's zip-loading work touched both call sites anyway). - The zip-entry extraction buffer is now pre-allocated using the entry's
own (capped) declared size. bench_regression_check.shnow usesmktempinstead of a fixed/tmp
path.- Two documentation fixes (an algorithm-name mismatch, a typo).
Test count
327 tests passing on default features (331 with --features test-roms),
up from 313/317 at v2.3.0. Full CI green — Linux/macOS/Windows, the new
perf regression gate, and the no_std gate.
What's next
v2.5.0 "Web Awakens" — the largest capability gap this whole analysis
found: a real winit+wgpu+egui rendering build on wasm32, replacing the
current bare canvas-2D GH-Pages demo bootstrap. See
to-dos/ROADMAP.md and the gap-closure arc's full plan for the complete
v2.4.0 -> v3.0.0 roadmap.