dm2-saves: phase-6 timer-actuator link model is lossy — empty chain re-encodes as a full record, record type hardcoded to 7 #173

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

Problem

The phase-6 timer-actuator link model (records.rs ~839-913, decode/encode_timer_actuator_links) is lossy, and encode is not decode's inverse on two edges:

  1. Empty chain: decode maps an empty link chain to val_b = 0xfffe, but encode unconditionally writes a one-record chain. A save where a 0x3c/0x3d timer's link chain is empty round-trips 1 bit into 21 bits — desyncing every phase after 6.
  2. Record type/payload: decode discards the linked record's rec_type and all payload except bytes 2-3; encode hardcodes type 7 with zeros elsewhere. A non-type-7 record in that slot (different mask width, e.g. type 3's body) desyncs on re-encode.

Current fixtures evidently never contain either shape, so the round-trip tests pass.

Also, the doc comment's claim that "the engine writes back the chain's 16-bit value stored in bytes 2-3" doesn't match the engine: wvalueB receives the newly allocated record ID via DM2_APPEND_RECORD_TO, not the record's data word. The crate model works today only because it captures the raw stream bits.

Fix direction

Preserve the whole decoded CheckcodeRecord (including the empty-chain state) in TimerActuatorLink instead of a synthesized type-7 val_b pair, so re-encode replays exactly what was decoded.

## Problem The phase-6 timer-actuator link model (`records.rs` ~839-913, `decode/encode_timer_actuator_links`) is lossy, and encode is not decode's inverse on two edges: 1. **Empty chain**: decode maps an empty link chain to `val_b = 0xfffe`, but encode unconditionally writes a one-record chain. A save where a 0x3c/0x3d timer's link chain is empty round-trips 1 bit into 21 bits — desyncing every phase after 6. 2. **Record type/payload**: decode discards the linked record's `rec_type` and all payload except bytes 2-3; encode hardcodes type 7 with zeros elsewhere. A non-type-7 record in that slot (different mask width, e.g. type 3's body) desyncs on re-encode. Current fixtures evidently never contain either shape, so the round-trip tests pass. Also, the doc comment's claim that "the engine writes back the chain's 16-bit value stored in bytes 2-3" doesn't match the engine: `wvalueB` receives the newly *allocated record ID* via `DM2_APPEND_RECORD_TO`, not the record's data word. The crate model works today only because it captures the raw stream bits. ## Fix direction Preserve the whole decoded `CheckcodeRecord` (including the empty-chain state) in `TimerActuatorLink` instead of a synthesized type-7 `val_b` pair, so re-encode replays exactly what was decoded.
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#173
No description provided.