feat: resolve console CD-DA routing (CD.DAT music_id → Red Book track); close Phase 3 #280

Merged
jqueuniet merged 0 commits from refs/pull/280/head into main 2026-07-20 21:13:31 +02:00
jqueuniet commented 2026-07-20 21:03:31 +02:00 (Migrated from codeberg.org)

Summary

Resolves CD-DA routing for the console-family ports, closing the last open
item in Phase 3 (dm2-music). The Sega CD / Mac JP / PC-9821 / FM-Towns
editions deliver their soundtrack as Red Book CD-DA tracks and route music
with a 40-byte CD.DAT ([DMWEB] Format 1); the parser already read those
records, and dm2 convert --cdda already extracted a track to WAV — but
nothing connected a record's music_id to the specific disc track it names.

Finding: music_id is the absolute Red Book CD audio-track number
(music_id == cd_track; the disc's audio session is TRACK 02–07). Three
independent lines of evidence:

  • Value range. The retail CD.DAT (byte-identical across every
    console-family disc) references only music_id ∈ {2, 3, 6} — all inside
    the physical audio-track range [2, 7], never the data track.
  • The reverse-engineered engine. In [SKPROJECT]'s console engine,
    CHECK_TRIGGER_MUSIC_FROM_PLAYER_POSITION matches the party's absolute
    world (x, y, level) against the ten triggers (its tCDMusicMap struct
    is byte-for-byte Format1Record) and calls REQUEST_PLAY_MUSIC(music_id),
    which resolves the track as sprintf(path, "CD%02x.…", music_id) — the
    value is used directly as the CD track number, no table indirection.
  • Distinct audio. The mapping lands music_id 2 → track02 (the
    Skullkeep-exterior theme, 8 of 10 triggers), 3 → track03, 6 → track06
    (the level-10 trigger). The measured durations rule out a 1-based index:
    under absolute numbering music_id 6 is track06 (a full 291 s level
    theme), whereas 1-based would map it to track07 (a 15 s sting).

What landed

  • dm2-musicFormat1Record::cd_track() -> u8 (the absolute CD track
    a trigger plays) and Format1Routing::referenced_tracks() -> Vec<u8> (the
    sorted, deduped track set a table references — [2, 3, 6] for retail).
    The music_id field doc now states the resolved mapping and its evidence.
  • dm2-clidm2 dump <CD.DAT> --format music-map emits the resolved
    routing as JSON: one entry per trigger (level, x, y, music_id,
    cd_track, track_file) plus a referenced_tracks summary
    (cd_track, track_file, trigger_count). It handles the 40-byte
    console Format-1 table; the 176-byte Amiga/Mac Format-2 CD.DAT (which
    routes to MODs / 'Midi', not CD-DA) gets a clear error.
  • Tests — in-crate unit tests for both accessors, a fixture-gated test
    asserting cd_track/referenced_tracks against the real CD.DAT and
    that each referenced track resolves to an actual audio fixture, and a
    fixture-gated CLI test over dump --format music-map. All skip cleanly
    where the gitignored game data is absent.
  • Docsmusic.md §Red Book rewritten with the resolved mapping and
    evidence (and the stale appendix / routing-TODO closed); ROADMAP.md
    Phase 3 → (remaining Phase-3 items are intentional deferrals: AIFF
    writer, compressed 'snd ', SMS pitch-bend/pan), plus the now-stale
    Phase 5 "convert paths pending" clause corrected.

Notes

track_file follows the repo's track{NN}.bin naming, which is exactly
what dm2 convert --cdda --all emits (trackNN.bin → trackNN.wav), so
extraction and routing are consistent by construction — no extraction
changes were needed. Scope is deliberately the console Format-1 table only;
the Amiga/Mac Format-2 and DOS songlist.dat routing already have their own
parsers and route to non-CD-DA assets.

Full-workspace cargo test is green; cargo fmt --check and cargo clippy
are clean.

## Summary Resolves CD-DA routing for the console-family ports, closing the last open item in Phase 3 (`dm2-music`). The Sega CD / Mac JP / PC-9821 / FM-Towns editions deliver their soundtrack as Red Book CD-DA tracks and route music with a 40-byte `CD.DAT` ([DMWEB] Format 1); the parser already read those records, and `dm2 convert --cdda` already extracted a track to WAV — but nothing connected a record's `music_id` to the specific disc track it names. **Finding: `music_id` is the absolute Red Book CD audio-track number** (`music_id == cd_track`; the disc's audio session is TRACK 02–07). Three independent lines of evidence: - **Value range.** The retail `CD.DAT` (byte-identical across every console-family disc) references only `music_id ∈ {2, 3, 6}` — all inside the physical audio-track range `[2, 7]`, never the data track. - **The reverse-engineered engine.** In [SKPROJECT]'s console engine, `CHECK_TRIGGER_MUSIC_FROM_PLAYER_POSITION` matches the party's absolute world `(x, y, level)` against the ten triggers (its `tCDMusicMap` struct is byte-for-byte `Format1Record`) and calls `REQUEST_PLAY_MUSIC(music_id)`, which resolves the track as `sprintf(path, "CD%02x.…", music_id)` — the value is used directly as the CD track number, no table indirection. - **Distinct audio.** The mapping lands `music_id 2 → track02` (the Skullkeep-exterior theme, 8 of 10 triggers), `3 → track03`, `6 → track06` (the level-10 trigger). The measured durations rule out a 1-based index: under absolute numbering `music_id 6` is track06 (a full 291 s level theme), whereas 1-based would map it to track07 (a 15 s sting). ## What landed - **`dm2-music`** — `Format1Record::cd_track() -> u8` (the absolute CD track a trigger plays) and `Format1Routing::referenced_tracks() -> Vec<u8>` (the sorted, deduped track set a table references — `[2, 3, 6]` for retail). The `music_id` field doc now states the resolved mapping and its evidence. - **`dm2-cli`** — `dm2 dump <CD.DAT> --format music-map` emits the resolved routing as JSON: one entry per trigger (`level`, `x`, `y`, `music_id`, `cd_track`, `track_file`) plus a `referenced_tracks` summary (`cd_track`, `track_file`, `trigger_count`). It handles the 40-byte console Format-1 table; the 176-byte Amiga/Mac Format-2 `CD.DAT` (which routes to MODs / `'Midi'`, not CD-DA) gets a clear error. - **Tests** — in-crate unit tests for both accessors, a fixture-gated test asserting `cd_track`/`referenced_tracks` against the real `CD.DAT` and that each referenced track resolves to an actual audio fixture, and a fixture-gated CLI test over `dump --format music-map`. All skip cleanly where the gitignored game data is absent. - **Docs** — `music.md` §Red Book rewritten with the resolved mapping and evidence (and the stale appendix / routing-TODO closed); `ROADMAP.md` Phase 3 → ✅ (remaining Phase-3 items are intentional deferrals: AIFF writer, compressed `'snd '`, SMS pitch-bend/pan), plus the now-stale Phase 5 "convert paths pending" clause corrected. ## Notes `track_file` follows the repo's `track{NN}.bin` naming, which is exactly what `dm2 convert --cdda --all` emits (`trackNN.bin → trackNN.wav`), so extraction and routing are consistent by construction — no extraction changes were needed. Scope is deliberately the console Format-1 table only; the Amiga/Mac Format-2 and DOS `songlist.dat` routing already have their own parsers and route to non-CD-DA assets. Full-workspace `cargo test` is green; `cargo fmt --check` and `cargo clippy` are clean.
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!280
No description provided.