fix(dm2-saves): Mac byte-order fidelity + robustness batch (#168–#176) #209
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "refs/pull/209/head"
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?
Addresses the dm2-saves fidelity/robustness batch (#168–#176) from a full-crate review — the same shape as the dm2-graphics batch (#186–#194 / PR #208). (#158, the Sega CD BRAM format, is a separate large reverse-engineering feature and is out of scope here.)
All CI gates pass locally:
cargo fmt --check,cargo clippy --workspace -D warnings,cargo test --workspace(997 passed),RUSTDOCFLAGS=-D warnings cargo doc --workspace.Fixed
SaveFile::tile_at/set_tile_type_attrindexedmap_datarow-major while the grid is column-major (x*height+y); reads/edits were transposed forx≠y. Added an asymmetric-coordinate regression test.decode_full,GameplaySections::decode, anddm2 info; also fixes the&0xFFextended-mode truncation), GlobalWords, Timerw_00/wvalueA/wvalueB, and the 10-bit cloud indices. Verified against the Mac fixtures (early_game'slast_creature_ticknow reads the0xffffff38new-game constant; timer counts 1/22/24).convert_savenow transcodes the SGB scalars, timer records, GlobalWords, 10-bit cloud indices, and phase-6 link records to the target byte order, so e.g. a Mac→DOS conversion preservesgame_tickinstead of leaving it ~256× off. Pinned by a fixture test.TimerActuatorLinknow preserves the decodedCheckcodeChainverbatim, so re-encode replays exactly; corrected thewvalueBdoc claim.n_recordsfor zero-size types; propagate map-table parse errors instead of silently dropping entries; recursion depth cap onread_chain/write_chain; bounds-check directmap.tiles[…]indexing; validate-before-unlink in the removal paths; debug-only cross-check for the phase-7 cloud-postfix count. None change behavior on valid input.table1d6356note (verified byte-identical toCHAMPION_MASK, popcount 1045 — earlier "8 extra bytes" was a comment-parsing artifact); the "always little-endian" claim; and the mac-post-sgb-reads claims (timer-count formula, the mask is identical/interpretation is BE, the CHECKCODE index-offset is 2 bits on both platforms).Investigated → documented, no code change (deliberately)
SKSAVE0fixture (engine-written ground truth), which contains a standalone missile that round-trips faithfully only under the current persistent-flag / sibling-termination model. The retailsksvgame.cpphypothesis and the Beta binary disagree; resolving it needs binary-level tracing, not a fixture-blind edit. Recorded as an NB comment.CREATURE_GIDX_ALT_TABLEextraction (re-derives exactly fromskull.exe0x72568). Extracting the Mac/Beta equivalents to prove identity-or-difference is blocked on binary work out of scope here (BetaFires.exeis packed; Mac keeps its descriptors in the 68k resource fork, big-endian). It stays a risk flag, not a proven divergence — both the Mac and Beta fixtures round-trip byte-exact with the retail table, so re-selecting per variant would be a guess that could regress them. Documented precisely on the constant.Code review
Found 1 issue:
T = SaveGameBuffer.w_14 & 0xFF timers— the pre-#170, 8-bit, always-little-endian formula this PR retires. It now contradicts the byte-order section rewritten earlier in the same file (which names the 9-bit timer count as byte-order-dependent), the adjacent GlobalWords row updated in the same table, and the shipped code, which decodes the count via the 9-bit byte-order-correctdecode_timer_count_raw/SaveGameBuffer::timer_count().codeberg.org/jqueuniet/dm2-tools@dca12e2ca0/docs/formats/saves.md (L324-L329)codeberg.org/jqueuniet/dm2-tools@dca12e2ca0/crates/dm2-saves/src/savegamebuffer.rs (L53-L60)Checked for bugs (byte-order inverses, 9/10-bit field codecs, column-major indexing, recursion caps, transcode paths), historical regressions, doc/code consistency, and prior review feedback; also ran the fixture-backed byte-exact round-trip and Mac→DOS→Mac conversion tests locally — all pass. No functional issues found.
Fixed in
da48cd9f. TheTimerrow of the "Decoded sections" table now points atSaveGameBuffer::timer_count()and the 9-bit byte-order-dependent decode (DOSw_14 & 0x1FF, Mac(b20<<1)|(b21&1)), matching the byte-order section, the adjacent GlobalWords row, and the shipped code. (The remaining& 0xFFoccurrences are only in the dateddocs/superpowers/plans/*.mdhistory, left as-is.)