Skip to content

[Feature] Save-state: TLV/chunk container + postcard payload (wire structs + per-chunk versioning) #102

@mikai233

Description

@mikai233

Pre-flight checks

  • I searched existing issues for duplicates.
  • This is not a general support question.

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions