closing Q8 Last iteration I named Pj_Silph.xgs as the route to the SE cue index, on the reasoning that XACT keeps cue->wave there. That route is dead, and the negative is properly bounded: its 533-byte entry is high entropy with no XGSF magic, +/-8 KB around it has none either, all 1.08 GB of sound.pak contains zero XGSF, SDBK and WBND, and the executable has no XACT or .xgs string anywhere. Control run first -- the same scan finds RIFF in BGM_001 exactly where the bank structure says it should be, so the scan works and the magic genuinely is not there. So the .xgs and .slb extensions came from the authoring tool and survived into shipped data that is not XACT at all. That goes in METHOD as a general trap: check for the magic before committing to a parser for the format an extension advertises. Writing an XACT parser would have been wasted work. What that leaves for the SE audio is undecodable-with-reach rather than pending: Static.slb has no wave boundaries, there is no project file, and none of the sound table's five records carries an offset. The bank is certainly the right one -- 8 353 472 bytes over 322 cues is 0.84 to 1.01 seconds each at the bitrates the disc uses elsewhere, exactly the shape of a bank of short effects. Only the index into it is missing, and it exists only at runtime. Q8 closes: vocabulary and bank decoded, the event binding a name match on the authors' own event names, and the audio explicitly something the port must source or author rather than extract. With this every MISSION row is answered.
145 lines
7.2 KiB
Markdown
145 lines
7.2 KiB
Markdown
# Menu audio — the event vocabulary is on the disc; the binding is not
|
||
|
||
**Status:** ✅ `CONFIRMED` and **decoded** for the cue vocabulary and the bank
|
||
that holds it. 🟡 the cue↔event binding is a **name match**, not a measurement.
|
||
❔ two negatives with their reach: no cue names a screen's music, and the audio
|
||
for an individual SE cue cannot yet be extracted.
|
||
|
||
Answers most of [MISSION Q8](../port/MISSION.md).
|
||
|
||
## ✅ The UI cue vocabulary — decoded
|
||
|
||
`dat/tables.pak`'s sound table has a `SOUNDS` record of 5 798 cues. 322 are `SE_*`
|
||
and the **low block is the user-interface vocabulary**, named by the game's
|
||
authors after the *events*, not after the sounds:
|
||
|
||
| id | cue | id | cue |
|
||
|---|---|---|---|
|
||
| 1 | `SE_UI_START` | 9 | `SE_UI_SUB_WIN_CLS` |
|
||
| **2** | **`SE_UI_CURSOR`** | 10 | `SE_UI_ALART_WIN` |
|
||
| **3** | **`SE_UI_DECIDE`** | 11 | `SE_UI_PAUSE` |
|
||
| **4** | **`SE_UI_CANSEL`** *(sic)* | 12 | `SE_UI_SPLASH_IN` |
|
||
| **5** | **`SE_UI_IMPOSI`** | 13 | `SE_UI_SPLASH_OUT` |
|
||
| 6 | `SE_UI_WAIT` | 14 | `SE_UI_LOAD_CMP` |
|
||
| 7 | `SE_UI_NEXT` | 17/18 | `SE_UI_WEAPON_PLAN` / `_CREATE` |
|
||
| 8 | `SE_UI_SUB_WIN_OPN` | 81–83 | `SE_UI_MISSION_START` / `_UPDATE` / `_END` |
|
||
|
||
Full 322-cue list in [`data/se-ui-cues.txt`](data/se-ui-cues.txt). The remaining
|
||
blocks are gameplay: `SE_HUD_*` (21–83), `SE_BR_*` briefing (50–72), `SE_SW_*` /
|
||
`SE_MW_*` weapons, `SE_DS_*` engine, `SE_EXP_*` explosions, `SE_SHIELD_*`.
|
||
|
||
## ✅ They all live in one bank — decoded
|
||
|
||
The sound table's `BANK_SE` record is a **single field**: `Static.slb`. And the
|
||
disc-wide check agrees — **0 of the 322 `SE_*` cues has an entry in `FILES`**, the
|
||
5 135-path list where every voice, briefing and BGM bank is named. SE is the one
|
||
family that is not one-cue-one-file.
|
||
|
||
`SETTINGS` names the XACT project too: `PATH` = `game:\dat\sound.pak+`,
|
||
`PARAM` = `Pj_Silph.xgs`.
|
||
|
||
## 🟡 The binding: a name match, and I am calling it that
|
||
|
||
MISSION asks which cue fires on **move / confirm / back / error**. Read off the
|
||
names:
|
||
|
||
| event | cue |
|
||
|---|---|
|
||
| cursor moves | `SE_UI_CURSOR` (2) |
|
||
| Ⓐ confirm | `SE_UI_DECIDE` (3) |
|
||
| Ⓑ back | `SE_UI_CANSEL` (4) |
|
||
| invalid / error | `SE_UI_IMPOSI` (5) |
|
||
| submenu opens / closes | `SE_UI_SUB_WIN_OPN` / `_CLS` (8/9) |
|
||
| the splash | `SE_UI_SPLASH_IN` / `_OUT` (12/13) |
|
||
|
||
**Nobody has watched the game emit cue 2 on a d-pad press.** This is inference
|
||
from identifiers, and it belongs in the same box as Q4's GamePart ids — with one
|
||
honest difference worth stating: these are the *authors' own event names*, chosen
|
||
to describe when the sound plays, not asset labels I am interpreting. It is a
|
||
strong name match. It is still a name match, and the port is authoring it.
|
||
|
||
Measuring it needs the guest's cue-play call observed with its argument — the
|
||
same instrumentation Q4's residual wants, and not something this container can do
|
||
today (it runs `--mute=true` against an SDL dummy device, so there is no audio
|
||
path to watch either).
|
||
|
||
## ❔ Which BGM per screen — undecodable, reach already recorded
|
||
|
||
All 32 BGM cues are named `BGM_001`…`BGM_109`. Nothing in `SOUNDS`, `FILES` or the
|
||
bank headers names a screen — see
|
||
[`structures/bgm-two-stems.md`](structures/bgm-two-stems.md). Unchanged by this
|
||
iteration.
|
||
|
||
## ❔ And a new negative: an individual SE's audio is not extractable yet
|
||
|
||
`Static.slb` is **8 353 472 readable bytes** (declared 8 970 240 — exactly the
|
||
616 768 over-declaration the corpus already records), and it contains
|
||
**0 `RIFF`, 0 `seek`, 0 `WAVE`** — scanned over the whole buffer, so this is not
|
||
an offset problem. Every other bank on the disc is delimited by `seek` magic at
|
||
`data_at + declared_size` (7 620/7 620 in
|
||
[`slb-data-offset.md`](structures/slb-data-offset.md)); `Static.slb` is outside
|
||
that population. So the route that extracts every voice line and every BGM does
|
||
**not** give you `SE_UI_CURSOR`'s audio: the cue is named, the bank is named, and
|
||
the wave inside the bank is not locatable by any boundary marker.
|
||
|
||
**The named next step is `Pj_Silph.xgs`** — the XACT project file `SETTINGS`
|
||
points at, which in XACT is exactly where cue→wave-index lives. It **is** in
|
||
`sound.pak`: `name_hash("Pj_Silph.xgs")` resolves to TOC index 9454. ⚠️ Its entry
|
||
is 533 bytes at a 2048-aligned offset and carries no `XGSF` magic — and its region
|
||
has phase 1728, so those 533 bytes are very likely the *previous* bank's tail
|
||
rather than its own (the leading-region effect, [`slb-data-offset.md`]
|
||
(structures/slb-data-offset.md)). Locating its real bytes is the first job, not
|
||
parsing XACT.
|
||
|
||
## For the port
|
||
|
||
* the cue **names and ids** are disc facts — use them as the event vocabulary;
|
||
* which event fires which cue is **authored from the table above**;
|
||
* which BGM plays on which screen is **authored** — nothing on the disc says;
|
||
* and the UI **sound effects cannot be exported yet**. That is a gap in the
|
||
assets, not in the naming.
|
||
|
||
## ❔ `Pj_Silph.xgs` is a dead end — retracting the lead, with the reach
|
||
|
||
The section above named `Pj_Silph.xgs` as "the named next step", on the reasoning
|
||
that in XACT the cue→wave index lives in the project file. **That route is dead.**
|
||
|
||
| check | result |
|
||
|---|---|
|
||
| the entry's own bytes | 533 bytes, high entropy, **no `XGSF` magic** |
|
||
| ±8 KB around the entry in the flat concatenation | no `XGSF`, `SDBK`, `WBND`, `RIFF` or `seek` |
|
||
| **all of `sound.pak` — 1.08 GB across `.p00`–`.p04`** | **0 × `XGSF`, 0 × `SDBK`, 0 × `WBND`** |
|
||
| the executable | **0** occurrences of `XGSF`/`XACT`; no string matching `xact`, `.xgs`, `wavebank` or `soundbank` |
|
||
|
||
**Control, run first:** the same magic scan over `BGM_001.slb`'s neighbourhood
|
||
finds `RIFF` exactly where the bank structure says it should be. The scan works;
|
||
the magic genuinely is not there.
|
||
|
||
So the `.xgs` / `.slb` names are **inherited from the authoring tool, not from the
|
||
shipped format** — nothing on this disc is a parseable XACT container. There is no
|
||
XACT project file to read, and writing an XACT parser would have been wasted work.
|
||
|
||
### Where that leaves the SE audio: undecodable, with reach
|
||
|
||
Locating an individual SE cue's wave inside `Static.slb` is **not possible from
|
||
anything found on the disc**. Looked in:
|
||
|
||
* `Static.slb` itself — 8 353 472 readable bytes, **0 `RIFF` / 0 `seek` / 0 `WAVE`**,
|
||
where every one of the other 7 620 banks is delimited by `seek` magic at
|
||
`data_at + declared_size` ([`structures/slb-data-offset.md`](structures/slb-data-offset.md));
|
||
* the XACT project file — absent, as above;
|
||
* the sound table's five records — `SOUNDS` gives cue→id, `FILES` gives bank
|
||
paths, `BANK_SE` gives the one bank name, `SETTINGS` gives a path and the
|
||
(absent) project file, `STAGES` is empty. **No record carries an offset.**
|
||
|
||
**What does corroborate the binding:** `Static.slb` is 8 353 472 bytes, and at the
|
||
bitrates the disc uses elsewhere (25 697–31 000 B/s) that is **269–325 seconds of
|
||
audio — 0.84 to 1.01 s per cue over 322 cues.** Exactly the shape of a bank of
|
||
short UI/HUD/weapon effects, which is what `BANK_SE` says it is. The bank is the
|
||
right bank; only the index into it is missing.
|
||
|
||
**If it is ever picked up**, the route is the running game, not the disc: watch
|
||
the guest hand a wave offset/length to the decoder when a cue fires. That is the
|
||
same instrumentation the cue↔event binding needs, and the same one this container
|
||
cannot run.
|