feat(saves): field-driven full save-JSON variant (#308 Plan 2) #312
No reviewers
Labels
No labels
area/ci-release
area/cli
area/docs
area/dungeon
area/ftl
area/graphics
area/gui
area/music
area/saves
area/tooling
port/amiga
port/dos
port/fm-towns
port/mac
port/pc98
port/sega-cd
priority/high
priority/low
priority/medium
type/bug
type/chore
type/docs
type/feature
type/research
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
jqueuniet/dm2-tools!312
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "feat/save-json-field-driven"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Completes issue #308 (lossless round-trippable save JSON) — the field-driven "full" variant (Plan 2), building on the merged self-contained variant (Plan 1, #311).
What this adds
record_streamgraph — direct derives on the 8-type graph (RecordStream,CheckcodeRecord,TileChain, …), spec-sanctioned.bitstream_padcapture — the field-driven variant stores only the bitstream tail (fromphase7_end_bit/8), not the whole bitstream; mutually exclusive with the self-containedbitstream.SaveDump::full(&SaveFileFull, variant)— forward constructor for the field-driven dump.SaveDump::reconstruct(map_info, gdat)— public reverse method: re-encodes phases 1–7 from decoded fields viato_bytes_from_fields, feeding a synthetic bitstream that carries only the pad.tests/json_field_roundtrip.rs) — parse → decode_full → SaveDump::full → JSON → deserialize → reconstruct → assert byte-identical.dm2 dump <save> --full --gdat <graphics.dat> --dungeon <dungeon.dat>, with flag validation (--fullrequires both; mutually exclusive with--self-contained).Proof
Byte-exact round-trip verified over live fixtures: DOS slots 0–3 (4/4) and Mac saves (14/14) byte-identical, plus a gameplay-edit-reflected test. Full suite green: dm2-saves lib 183 passed, dm2-cli dump_save 6/6, json_field_roundtrip 3/3.
Coverage bound (by design)
Field-driven byte-exactness holds for saves whose record stream fully decodes (
tile_chainsnon-empty ∧champion_inventory.len() == heros_in_party) — the engine's own field-encode ceiling. Other saves remain covered by the self-contained variant.Robustness
reconstructdeserializes untrusted JSON (web save editor #301/#307 downstream); hardened against malformed input — empty pad and oversizedphase7_end_bitreturnJsonError(16 MB cap) rather than panicking or over-allocating.Follow-up (documented, non-blocking):
record_stream.raw_arraysround-trips butraw.raw_record_arraysis authoritative on reconstruct — candidate to skip-serialize for the web editor.synthesize_bitstream deserializes JSON that may be attacker-controlled (the web save editor is an explicit downstream), so it must not panic or over-allocate on malformed input: - Empty bitstream_pad (with phase7_end_bit < 8) previously panicked via `.expect("pad is non-empty")`. synthesize_bitstream is now fallible and returns JsonError::BadLength instead. - An unbounded phase7_end_bit previously drove an unbounded `vec![0u8; phase7_end_bit / 8]` allocation. Added MAX_BITSTREAM_BYTES (16 MiB, ~1000x headroom over real saves) and reject before allocating. Also fixes the Mac field-driven round-trip test, which discarded its per-save result and had no aggregate assertion: it could pass while proving nothing if no Mac fixture took the field path. Mirrors the DOS test's `asserted >= 1` guard. Verified the byte-exact gate still round-trips all 4 DOS slots and all 14 Mac saves byte-identical after these changes.