dat/tables.pak holds a 5798-entry SOUNDS record (cue name -> sound id) and a 5135-entry FILES record (.slb bank paths). Cue names are the join key, so a script message id now resolves all the way to the bank that voices it: MSG_VOICE_D_257 -> VOICE_D_257 -> 6945 -> jpn\etc\VOICE_D_257.slb. The prefix rule is MSG_ -> VOICE_, not strip-MSG_. My first rule was the latter; it left 88 names unresolved and I was about to write those families up as text-only announcements, until VOICE_TCAF_592.slb turned up in FILES and refuted it. Corrected rule resolves 1326 of 1338, and SOUNDS and FILES agree on exactly the same 12 absentees. Separately, MSG_DEMO_* is driven by its own IDXD tables in the language packs, which carry speaker, portrait, on-screen seconds and audio cue per page. Field count is 9*PageCount+2 for all 7 distinct PageCounts; 1252/1252 caption-key slots match <ID>_<page>_<line>; the 78 multi-page records equal the 78 counted independently from the caption side; 138 ids close exactly against the caption table both ways. Does not settle the known VOICE_D_452 wrong-recording case -- every cue id is distinct, so bank sharing is not happening at this layer.
95 lines
4.2 KiB
Markdown
95 lines
4.2 KiB
Markdown
# The sound-cue table, and how dialogue binds to a recording
|
||
|
||
**✅ Settled 2026-08-26.** `dat/tables.pak` carries the game's cue index. It
|
||
closes the join from a message id to the `.slb` bank that voices it — the
|
||
missing link between the script/caption side and the audio side.
|
||
|
||
## The table
|
||
|
||
One IDXD object in `dat/tables.pak` (373 680 bytes, 5 records, 10 937 fields)
|
||
holds:
|
||
|
||
| record | shape | content |
|
||
|---|---|---|
|
||
| `SETTINGS` | 3 fields | `PATH` = `game:\dat\sound.pak+`, `PARAM` = `Pj_Silph.xgs`, and a positional `BANK_SE` |
|
||
| `BANK_SE` | 1 positional | `Static.slb` |
|
||
| `FILES` | 5 135 positional | bank paths — `jpn\Voice\VOICE_TCAF_592.slb`, `BGM_001.slb`, … |
|
||
| `STAGES` | **0 fields** | present but empty |
|
||
| `SOUNDS` | 5 798 **named** | cue name → numeric sound id |
|
||
|
||
`SOUNDS` is the only record on the disc found so far that is entirely named
|
||
fields, and the names are the join key.
|
||
|
||
Cue families and their id ranges — the id space is partitioned by family, with
|
||
no overlap:
|
||
|
||
VOICE n=5044 1500 .. 7331
|
||
SE n= 322 1 .. 901
|
||
DEMO n= 295 8000 .. 8408
|
||
BR n= 101 8500 .. 8600
|
||
BGM n= 32 1001 .. 1109
|
||
JNGL n= 3 1201 .. 1203
|
||
|
||
5 797 of the 5 798 values are numeric and **all 5 797 are distinct** — the ids
|
||
are unique, so the id is not how banks get shared. One cue's value is
|
||
non-numeric.
|
||
|
||
The id is **not** an index into `FILES`: ids run to 8 600 and `FILES` has only
|
||
5 135 entries. `FILES` is reached by *name*, not by id.
|
||
|
||
## The join: a message id to a recording
|
||
|
||
MSG_VOICE_D_257 (built-in 64's operand, see isl-message-dialogue-link.md)
|
||
-> cue VOICE_D_257 `MSG_` becomes `VOICE_`
|
||
-> id 6945 SOUNDS
|
||
-> bank jpn\etc\VOICE_D_257.slb FILES, matched on stem
|
||
|
||
The prefix rule has one wrinkle, and getting it wrong is easy:
|
||
|
||
* **`MSG_` is replaced by `VOICE_`** — it is not simply stripped.
|
||
* A name that already carries `VOICE_` keeps the one it has rather than
|
||
doubling it: `MSG_VOICE_D_257` → `VOICE_D_257`, but `MSG_TCAF_592` →
|
||
`VOICE_TCAF_592` (not `TCAF_592`).
|
||
|
||
**I got this wrong first.** Stripping `MSG_` resolved 1 250 of 1 338 script
|
||
message names and left 88 unresolved, and the 88 were exactly the four-letter
|
||
ship families (`ADAN` 32, `ADPL` 27, `TCAF` 12, `ACRO` 3, `RHIN` 2) — which I
|
||
was ready to write up as "text-only announcements with no recording". Seeing
|
||
`jpn\Voice\VOICE_TCAF_592.slb` in `FILES` refuted that: the recordings exist,
|
||
my rule was wrong. Under the corrected rule the count is **1 326 of 1 338**.
|
||
|
||
`SOUNDS` and `FILES` agree exactly — the same 12 names are absent from both,
|
||
and **every one of the 5 135 bank files has a cue** (zero orphans). 663 cues
|
||
have no bank file; those are `SE`/`BGM`-family cues living in other banks.
|
||
|
||
## The 12 script lines with no recording
|
||
|
||
MSG_VOICE_A_273 MSG_VOICE_D_183
|
||
MSG_VOICE_D_600 … MSG_VOICE_D_608 MSG_VOICE_D_611
|
||
|
||
All twelve are in the `VOICE` family, have caption text, are called from the
|
||
stage scripts, and have neither a cue nor a bank. Ten of them form the near
|
||
contiguous block `D_600`–`D_611`. Whether they are subtitle-only by design or
|
||
a late cut is **not settled** — the shape (a contiguous high block) suggests
|
||
lines added after the recording sessions, but that is a guess, not a
|
||
measurement.
|
||
|
||
## What this does *not* settle
|
||
|
||
* **The known wrong-recording case is not explained by this table.**
|
||
`VOICE_D_452` — the generic "Resupply complete" line documented in
|
||
[voice-bank-leading-region.md](../voice-bank-leading-region.md) — has its own
|
||
unique id (7140) and its own bank. Since every id is distinct, bank sharing
|
||
is **not** happening at the cue level, so that symptom still has to be
|
||
explained inside the bank/`sound.pak` layer.
|
||
* **`STAGES` is empty**, and what would have populated it is unknown.
|
||
* **`SOUNDS`' one non-numeric value** was not chased down.
|
||
* Only the English pack and the `jpn\` bank paths were examined. Every path in
|
||
`FILES` begins with `jpn\`, which is consistent with the already-recorded
|
||
finding that the voice set is Japanese, but the other language packs'
|
||
tables were not diffed against this one.
|
||
|
||
## Tool
|
||
|
||
tools/re-capture/sound_cues.py summary | resolve <MSG_id> | demo | unbound
|