Complete DOS Beta v0.9 save decode (bit-exact, all 9 slots, splice retired) #256

Merged
jqueuniet merged 0 commits from refs/pull/256/head into main 2026-07-16 22:02:05 +02:00
jqueuniet commented 2026-07-16 20:54:29 +02:00 (Migrated from codeberg.org)

Completes the DOS Beta v0.9 savegame decode: all 9 local Beta fixture slots now decode bit-exactly to the end of the suppression bitstream and field-round-trip byte-for-byte, including slot 4 (previously failed to decode at all). Closes the ROADMAP Phase 4 Beta item.

What was wrong

Three Beta-specific mechanisms, found by diffing the decoder against a patched-DOSBox-X execution-trace oracle running the real Beta engine (Fires.exe), hooked at the suppression reader's per-consumed-bit instruction + the CHECKCODE reader entry (exact-bit, per-call trace):

  1. Existing-chain body overlays used retail-width masks. The Beta narrows three per-type masks (door 0x1e vs retail 0x3e, etc.); the narrowing was wired into the closing-CHECKCODE path but not the overlay path. First divergence: the first door overlay read +1 bit, and the shift garbled every downstream dynamic decision.
  2. Detail-teleporter pair fold (Beta-only). For a teleporter with a type-3/0x27 companion at both ends, the first end reached in walk order reads both tiles' static overlay prefixes before a single closing CHECKCODE; the other end is skipped. Ordering is lexicographic over (map, x, y); destination coords come from the teleporter head record. Retail is map-only — the fold is Beta-gated, and the 16 retail --ignored round-trip tests stay green.
  3. No per-save map cap. The engine walks all n_maps (54) maps, exactly like retail. The historical "exact landing at 44 maps" was an artifact of bugs 1–2 inflating reads; the 7-bit [DUNGEON+4] bound theory is retracted. BETA_TILE_CHAIN_MAP_COUNT and the map_limit plumbing are removed.

Evidence

  • Slot-0 comparator: PERFECT MATCH — all 15,295 engine CHECKCODE-entry bit positions identical to the decoder's.
  • All 9 slots land inside the final flush byte (new dos_beta_v09_decode_consumes_full_bitstream test, with a retail criterion-sanity leg) and re-encode byte-for-byte (slot 4 added to the round-trip loop).
  • Full model + methodology + retractions recorded in docs/formats/save-record-stream.md; ROADMAP updated.

Notes for review

  • Beta fixtures are local-only and non-redistributable, so CI exercises none of the new Beta assertions (skip-on-missing); the retail fixture guards don't run in CI either. Local gates: fmt / clippy -D warnings / full workspace tests / doc -D warnings all green.
  • The lexicographic x/y tie-break legs of the teleporter rule rest on the Fires.exe disassembly (no fixture has a same-map detail pair); all-slot exact landings corroborate.
  • One drive-by: a pre-existing clippy lint fix in dm2-graphics tests (own commit).
Completes the DOS Beta v0.9 savegame decode: all 9 local Beta fixture slots now decode **bit-exactly** to the end of the suppression bitstream and field-round-trip byte-for-byte, including slot 4 (previously failed to decode at all). Closes the ROADMAP Phase 4 Beta item. ## What was wrong Three Beta-specific mechanisms, found by diffing the decoder against a patched-DOSBox-X execution-trace oracle running the real Beta engine (`Fires.exe`), hooked at the suppression reader's per-consumed-bit instruction + the CHECKCODE reader entry (exact-bit, per-call trace): 1. **Existing-chain body overlays used retail-width masks.** The Beta narrows three per-type masks (door `0x1e` vs retail `0x3e`, etc.); the narrowing was wired into the closing-CHECKCODE path but not the overlay path. First divergence: the first door overlay read +1 bit, and the shift garbled every downstream dynamic decision. 2. **Detail-teleporter pair fold (Beta-only).** For a teleporter with a type-3/0x27 companion at *both* ends, the first end reached in walk order reads both tiles' static overlay prefixes before a single closing CHECKCODE; the other end is skipped. Ordering is lexicographic over (map, x, y); destination coords come from the teleporter head record. Retail is map-only — the fold is Beta-gated, and the 16 retail `--ignored` round-trip tests stay green. 3. **No per-save map cap.** The engine walks all `n_maps` (54) maps, exactly like retail. The historical "exact landing at 44 maps" was an artifact of bugs 1–2 inflating reads; the 7-bit `[DUNGEON+4]` bound theory is retracted. `BETA_TILE_CHAIN_MAP_COUNT` and the `map_limit` plumbing are removed. ## Evidence - Slot-0 comparator: **PERFECT MATCH** — all 15,295 engine CHECKCODE-entry bit positions identical to the decoder's. - All 9 slots land inside the final flush byte (new `dos_beta_v09_decode_consumes_full_bitstream` test, with a retail criterion-sanity leg) and re-encode byte-for-byte (slot 4 added to the round-trip loop). - Full model + methodology + retractions recorded in `docs/formats/save-record-stream.md`; ROADMAP updated. ## Notes for review - Beta fixtures are local-only and non-redistributable, so **CI exercises none of the new Beta assertions** (skip-on-missing); the retail fixture guards don't run in CI either. Local gates: fmt / clippy `-D warnings` / full workspace tests / doc `-D warnings` all green. - The lexicographic x/y tie-break legs of the teleporter rule rest on the `Fires.exe` disassembly (no fixture has a same-map detail pair); all-slot exact landings corroborate. - One drive-by: a pre-existing clippy lint fix in `dm2-graphics` tests (own commit).
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
jqueuniet/dm2-tools!256
No description provided.