Skip to content

v0.2.0 "Persistence"

Choose a tag to compare

@doublegate doublegate released this 03 Jul 00:51
· 452 commits to main since this release
5ced920

v0.2.0 "Persistence"

A versioned, deterministic core-wide save-state format — the prerequisite for rewind/run-ahead
(v0.3.0 "Continuum"), netplay rollback (v1.2.0), and TAS movie replay (v1.4.0), none of which
build until this lands (docs/adr/0006-save-state-format.md, now Accepted).

Oracle/golden suites: all held, no regressions. The full workspace test suite (including
--features test-roms) is green; the new round-trip determinism test additionally proves the
save-state format bit-identical (framebuffer + audio) across a no-coprocessor ROM, a Curated
Super FX ROM, and a BestEffort commercial coprocessor ROM.

Save-state wire format (rustysnes-savestate, new leaf crate):

  • SaveWriter/SaveReader: an allocation-free tagged/length-prefixed section format, no serde/
    reflection (keeps no_std targets byte-identical, keeps each component's format change local
    and auditable). Nested sections write directly into the parent buffer with a patched length
    placeholder rather than a throwaway Vec per section.
  • SaveStateError rejects truncated input, a wrong/unexpected section tag, a bad top-level magic,
    and a newer format version than the build understands -- never a panic, never a silent
    partial load.

Coprocessor boards (rustysnes-cart, T-52-002 -- every board round-trips its state):

  • Obc1Board, Dsp1Board, NecDspVariantBoard (DSP-1/2/4, ST010 via the shared Upd77c25 engine),
    Cx4Board (Hg51b), Sdd1Board (+ its Decompressor), SuperFxBoard (Gsu), Sa1Board, Spc7110Board
    (+ its Decompressor and paired EpsonRtc).
  • Untrusted-input validation throughout: an out-of-range semantic value (a cursor, a state-
    machine discriminant, a decompressor mode) is rejected with a typed error; a genuine N-bit
    hardware register width (a pointer register, a bus-address-line value) is masked to that
    width on load, matching the exact mask every normal-operation write already applies at its
    own use site. Firmware/chip-ROM dumps are never written to a save-state (ADR 0003).

Core engines (T-52-003 -- Cpu/Ppu/Apu/Bus/System all round-trip their exact state):

  • Cpu::save_state/load_state: the full 65C816 register file, WAI/STP latches, cycle counter;
    register widths re-masked to the emulation/Status::X invariants on load.
  • Ppu::save_state/load_state: VRAM/CGRAM/OAM, the full register file (including the six-layer
    window unit), write latches, the dot/scanline timeline, interrupt/frame-poll state, region,
    and the framebuffer.
  • Apu::save_state/load_state (rustysnes-apu): Spc700 + Dsp (128-byte register mirror, all 8
    voices, echo/noise/BRR/latch/clock sub-units, the queued output-sample FIFO) + ARAM + the
    three timers + the in-flight instruction micro-op plan -- the SPC700 analogue of the GSU's
    pending-checkpoint queue, needed because sub-instruction lockstep execution can leave an
    instruction genuinely mid-drain at any save point.
  • System::save_state()/load_state() (rustysnes-core): the top-level versioned envelope -- a
    4-byte magic (b"RSNS") + u16 format version wrapping Cpu, the whole Bus (Ppu/Apu/Dma/Clock/
    MulDiv/WRAM, plus a loaded cart's coprocessor state + battery SRAM), the determinism seed, and
    the SA-1 second CPU when present. A save-state's cart/SA-1 presence and SRAM length are
    cross-checked against the target System's own installed state and rejected on mismatch --
    restoring a cart-carrying save-state requires the caller to have already loaded the same ROM
    first, the same "never embed a ROM/firmware byte" posture every coprocessor already follows.

Round-trip determinism test (T-52-004, crates/rustysnes-test-harness/tests/
save_state_determinism.rs): boot a ROM, run 30 frames, snapshot, restore onto a separate
freshly-booted System, run 30 more frames on both the original (continuing uninterrupted) and
the restored system, assert byte-identical framebuffer + audio between the two -- extends the
existing determinism-contract test pattern (docs/adr/0004) rather than inventing a new
verification method.

This release landed across PRs #4-#12, each independently reviewed by Gemini + Copilot and
adjudicated before merge.