Pre-flight checks
Problem statement
The current save-state/snapshot serialization in nesium-core relies directly on runtime structs encoded with postcard. This makes save-states fragile: routine refactors (field reordering, renames, splitting structs, adding caches/derived fields) can easily break old snapshots and raises the long-term maintenance cost.
Proposed solution
Introduce a stable wire layer and a lightweight container format: TLV/chunk container + postcard payloads.
High-level design:
- Add a new
snapshot (or save_state) module in nesium-core.
- File format:
[MAGIC] header (fixed bytes)
- followed by 0..N chunk records.
- Chunk record format:
[chunk_id varint][chunk_ver varint][len varint][payload bytes]
payload is postcard-encoded wire structs (not runtime structs).
Per-subsystem responsibilities:
- Each subsystem (CPU/APU/PPU/Mapper/Bus/...) owns its wire types:
wire::<subsystem>::v1, v2, ... (frozen once published)
upgrade_from(ver, bytes) -> wire::Current
- runtime applies:
runtime.apply_wire(wire::Current) and recomputes derived/cached fields.
Parsing behavior:
- Unknown
chunk_id: skip (keeps the format extensible).
- Missing required chunks: error (or a defined defaulting strategy).
Optional enhancements:
- Global or per-chunk CRC32 to detect corruption.
- Golden fixtures (committed snapshot bytes) + CI test to ensure old snapshots remain readable.
Goals:
- Keep snapshots compact (near
postcard size; container overhead is minimal).
- Lower maintenance cost by decoupling runtime refactors from the snapshot format.
- Establish clear subsystem boundaries and a path to backward compatibility when/if needed.
Alternatives considered
- Keep the current approach: lowest upfront work but very fragile across refactors.
- Self-describing formats (CBOR/JSON/MessagePack map): more tolerant to structure changes, but typically larger.
- Protobuf/FlatBuffers/Cap’n Proto: stronger evolution rules but higher tooling/integration cost.
- Hand-written binary layout: smallest, but highest implementation/maintenance cost.
Scope
Core emulator (CPU/PPU/APU/mappers)
Additional context
Even if we don’t commit to backwards compatibility immediately, starting with a chunked container + explicit wire structs makes future compatibility feasible with incremental per-chunk version upgrades.
Pre-flight checks
Problem statement
The current save-state/snapshot serialization in
nesium-corerelies directly on runtime structs encoded withpostcard. This makes save-states fragile: routine refactors (field reordering, renames, splitting structs, adding caches/derived fields) can easily break old snapshots and raises the long-term maintenance cost.Proposed solution
Introduce a stable wire layer and a lightweight container format: TLV/chunk container +
postcardpayloads.High-level design:
snapshot(orsave_state) module innesium-core.[MAGIC]header (fixed bytes)[chunk_id varint][chunk_ver varint][len varint][payload bytes]payloadispostcard-encoded wire structs (not runtime structs).Per-subsystem responsibilities:
wire::<subsystem>::v1,v2, ... (frozen once published)upgrade_from(ver, bytes) -> wire::Currentruntime.apply_wire(wire::Current)and recomputes derived/cached fields.Parsing behavior:
chunk_id: skip (keeps the format extensible).Optional enhancements:
Goals:
postcardsize; container overhead is minimal).Alternatives considered
Scope
Core emulator (CPU/PPU/APU/mappers)
Additional context
Even if we don’t commit to backwards compatibility immediately, starting with a chunked container + explicit wire structs makes future compatibility feasible with incremental per-chunk version upgrades.