dm2-saves: convert_save passes Mac-endian SGB scalars, GlobalWords, timer fields, and cloud indices into converted saves untranscoded #172

Closed
opened 2026-07-04 15:15:16 +02:00 by jqueuniet · 1 comment
jqueuniet commented 2026-07-04 15:15:16 +02:00 (Migrated from codeberg.org)

Problem

convert_save (convert.rs) endian-transcodes champions, the raw record arrays, the u16 tables (obj_link/obj_list/text), the map table, the decoded record-stream object bodies, and re-lays timer_count_raw — but the following decoded fields pass through in source byte order and are re-encoded bit-for-bit into the target save, where the target engine reads them with its own layout:

  • SGB scalars: game_tick, random_seed, last_creature_tick, last_action_tick, rain_next_special. Concretely, converting fixtures/mac-us/saves/skullkeep_void_gate to DOS produces a save the DOS engine reads with game_tick = 6,371,843 instead of 211,553 (~30× off), plus a scrambled RNG seed and tick timestamps. The engine uses these for timer scheduling and regen logic.
  • GlobalWords (64 × u16 quest/script state). All-zero in current fixtures, so latent, but any nonzero word converts ×256 off.
  • Timer fields: w_00 (tick-countdown low word), val_a, val_b. val_b of type-0x3c/0x3d timers is rewritten by phase 6 on load, but the rest goes straight into the target engine.
  • Type-7 val_b in timer-actuator link records (records.rs ~855, hardcoded LE).
  • 10-bit cloud indices (cloud_index, cloud-postfix values) — bit-permuted on Mac under the LE mask layout; transcode_record_stream_objects transcodes rec.data but never these. cloud_index is a timer-array index in the engine.

This is the conversion-side counterpart of #168: today the wrong interpretation and the missing transcode cancel out for round-trips but compound for conversion.

Why existing verification missed it

The Mac↔DOS↔Mac byte-exact round-trip is blind to interpretation (values pass through unchanged in both directions), and the semantic conversion assertions cover champions (whose accessors are byte-order-aware) plus record types/counts — not SGB scalars, global words, or timer payloads.

Fix direction

Once the decode layer interprets these fields byte-order-correctly, convert_save needs explicit transcoding for them (analogous to transcode_champion), and conversion tests should pin at least game_tick equality across a Mac→DOS conversion (decode source and converted output, compare semantic values).

May be related to previously observed converted-save runtime anomalies attributed to engine caps (black screen / frozen controls) — worth re-testing a converted save in DOSBox once fixed.

## Problem `convert_save` (`convert.rs`) endian-transcodes champions, the raw record arrays, the u16 tables (obj_link/obj_list/text), the map table, the decoded record-stream object bodies, and re-lays `timer_count_raw` — but the following decoded fields pass through in **source** byte order and are re-encoded bit-for-bit into the target save, where the target engine reads them with its own layout: - **SGB scalars**: `game_tick`, `random_seed`, `last_creature_tick`, `last_action_tick`, `rain_next_special`. Concretely, converting `fixtures/mac-us/saves/skullkeep_void_gate` to DOS produces a save the DOS engine reads with `game_tick` = 6,371,843 instead of 211,553 (~30× off), plus a scrambled RNG seed and tick timestamps. The engine uses these for timer scheduling and regen logic. - **GlobalWords** (64 × u16 quest/script state). All-zero in current fixtures, so latent, but any nonzero word converts ×256 off. - **Timer fields**: `w_00` (tick-countdown low word), `val_a`, `val_b`. `val_b` of type-0x3c/0x3d timers is rewritten by phase 6 on load, but the rest goes straight into the target engine. - **Type-7 `val_b`** in timer-actuator link records (`records.rs` ~855, hardcoded LE). - **10-bit cloud indices** (`cloud_index`, cloud-postfix values) — bit-permuted on Mac under the LE mask layout; `transcode_record_stream_objects` transcodes `rec.data` but never these. `cloud_index` is a timer-array index in the engine. This is the conversion-side counterpart of #168: today the wrong interpretation and the missing transcode cancel out for round-trips but compound for conversion. ## Why existing verification missed it The Mac↔DOS↔Mac byte-exact round-trip is blind to interpretation (values pass through unchanged in both directions), and the semantic conversion assertions cover champions (whose accessors are byte-order-aware) plus record types/counts — not SGB scalars, global words, or timer payloads. ## Fix direction Once the decode layer interprets these fields byte-order-correctly, `convert_save` needs explicit transcoding for them (analogous to `transcode_champion`), and conversion tests should pin at least `game_tick` equality across a Mac→DOS conversion (decode source and converted output, compare semantic values). May be related to previously observed converted-save runtime anomalies attributed to engine caps (black screen / frozen controls) — worth re-testing a converted save in DOSBox once fixed.
jqueuniet commented 2026-07-06 08:47:21 +02:00 (Migrated from codeberg.org)

Fixed by #209 (merged into main).

Fixed by #209 (merged into `main`).
Sign in to join this conversation.
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#172
No description provided.