diff --git a/docs/port/HANDOFF.md b/docs/port/HANDOFF.md index d6a55ce..70e20af 100644 --- a/docs/port/HANDOFF.md +++ b/docs/port/HANDOFF.md @@ -163,15 +163,19 @@ authored version can be deleted. 🟑 **Which event fires which cue is a name match**, not a measurement β€” strong, because these are the authors' own event names, but nobody has watched the game emit cue 2 on a d-pad press. The port is authoring it. - ❔ **The SE audio cannot be exported at all from the disc.** `Static.slb`'s - 8 353 472 readable bytes contain **0 `RIFF`, 0 `seek`, 0 `WAVE`** β€” the boundary - marker that works for all 7 620 other banks is absent β€” and there is **no XACT - container anywhere**: 0 Γ— `XGSF`/`SDBK`/`WBND` across all 1.08 GB of - `sound.pak`, and no `XACT`/`.xgs` string in the executable. The `.xgs`/`.slb` - names come from the authoring tool, not the shipped format. The bank is - certainly the right one β€” 8 353 472 B over 322 cues is 0.84–1.01 s each at the - disc's own bitrates β€” but the index into it exists only at runtime. **The UI - sound effects must be sourced or authored; they cannot be extracted.** + βœ… **The SE audio IS extractable β€” by playing it.** (This corrects an earlier + "cannot be extracted" on this page.) The disc carries no index: `Static.slb` has + **0 `RIFF`/`seek`/`WAVE`** and there is **no XACT container anywhere** (0 Γ— + `XGSF`/`SDBK`/`WBND` in 1.08 GB of `sound.pak`; no `XACT` string in the + executable β€” those extensions are the authoring tool's). But Canary's + `--xma_param_probe=true` logs every stream's head bytes, and searching them in + `Static.slb` locates the wave exactly: + **d-pad move β†’ `0x1ec0`, 4 packets / 8 192 B, mono 48 kHz; β’· back β†’ `0x0ec0`, + 2 packets / 4 096 B.** Each matched at one offset only, and they are contiguous + (`0x0ec0 + 4096 = 0x1ec0`) β€” the bank is a packed run of whole 2 048-byte + packets with no delimiters, which is why nothing could be scanned for. + ⚠️ The order is **not** cue-id order, so the index must be observed per cue, not + counted. ❔ Decoding a located slice to PCM has not been attempted. * **The boot sequence is not data-driven β€” the port authors it.** ❔ Four places were checked and the order is in none: `config.ini`'s `[SYSTEM]` is empty, the @@ -251,15 +255,16 @@ here until 2026-08-28 and is now settled.) | | what | why it is stuck | |---|---|---| -| 🟑 | **cue β†’ event binding as a measurement** (Q8) | nobody has watched the game emit `SE_UI_CURSOR` on a d-pad press. Needs the guest's cue call observed with its argument | -| ❔ | **the wave index into `Static.slb`** (Q8) | same instrumentation. This container runs `--mute=true` against an SDL dummy device, so it cannot watch the audio path at all | +| 🟑 | **cue NAME β†’ event binding** (Q8) | the eventβ†’**wave** binding is now measured; that the cursor's wave is the cue *named* `SE_UI_CURSOR` is still read off the authors' identifiers | +| ❔ | **decoding a located `Static.slb` slice to PCM** (Q8) | offsets and packet counts are in hand; the XMA1 `RIFF` wrapper for a mono slice was not built | | 🟑 | **the paint-order tie-break** (Q3) | eight candidates refuted; costs one element's blend on one screen | | 🟑 | **GamePart ids behind the buttons** (Q4) | the *screens* are measured; the ids are a name match onto the executable's class names | | ❔ | **the boot transitions in code** (Q6) | bounded as code-not-data, not proven. `sub_821C6458` β€” the substantial function in `GamePart_Title`'s neighbourhood β€” has **not been read** | | ❔ | **builds 0/1 and 10/11**, the `DELTASABER` plates (Q2) | never seen anywhere in the boot path, the title-side screens or the attract loop. A mission load is the remaining candidate and this container kills runs before one completes | -The first two share one unblocker β€” an emulator build whose audio path can be -observed β€” and would be the only entries here that change a port decision. +(An earlier version of this table called the audio items blocked on "an emulator +whose audio path can be observed". That was wrong β€” this build already has +`--xma_param_probe`, and using it settled both.) ⚠️ **A container caveat that bounds all of the above:** the emulator has twice been killed mid-run with no crash line in its own log (at ~50 s and ~145 s), on a diff --git a/docs/re/METHOD.md b/docs/re/METHOD.md index 1c6204e..55315d1 100644 --- a/docs/re/METHOD.md +++ b/docs/re/METHOD.md @@ -203,3 +203,9 @@ agent's loop prompt, i.e. nowhere durable. See [`README.md`](README.md) for the 0.5 s (`ffmpeg x11grab` at 10 fps) answered at **0.999 with a strictly monotone playhead**. Sparse sampling does not weaken a signal gracefully; it turns it into noise that looks like a different answer. +* **Check what instrumentation the local build already has before declaring a + question blocked on tooling.** Two Q8 residuals were written off as needing "an + emulator whose audio path can be observed". The emulator in this container + already had `xma_param_probe` β€” visible in the startup CONFIG DUMP the whole + time β€” added by this project for exactly that purpose. Read the cvar dump, or + `nm -C | grep cvars::`, before concluding you cannot measure something. diff --git a/docs/re/REFUTED.md b/docs/re/REFUTED.md index d618245..74e44db 100644 --- a/docs/re/REFUTED.md +++ b/docs/re/REFUTED.md @@ -158,6 +158,13 @@ neighbourhood, not just the line. ## Audio +* "an individual SE cue's audio cannot be extracted" β†’ **mine, and wrong.** + `--xma_param_probe=true` logs each stream's head bytes; searching them in + `Static.slb` locates the wave exactly. [`menu-audio-cues.md`](menu-audio-cues.md) +* "`Static.slb` has no wave boundaries, so its layout is unknown" β†’ it is a + **packed run** of whole 2 048-byte XMA packets with no delimiters β€” the two + located cues are contiguous. There is nothing to scan for, by design. + * "`Pj_Silph.xgs` holds the cueβ†’wave index, so parse XACT" β†’ **no XACT container exists on this disc**: 0 Γ— `XGSF`/`SDBK`/`WBND` in all 1.08 GB of `sound.pak`, and no `XACT`/`.xgs` string in the executable. The extensions are the authoring diff --git a/docs/re/data/se-cue-runtime-offsets.txt b/docs/re/data/se-cue-runtime-offsets.txt new file mode 100644 index 0000000..d2bd2ec --- /dev/null +++ b/docs/re/data/se-cue-runtime-offsets.txt @@ -0,0 +1,19 @@ +SE cue waves located inside Static.slb by playing them. + +Method: run Canary with --xma_param_probe=true, drive the main menu, and read the +per-stream XMA-PARAM lines. Each line carries the stream's first 32 bytes; those +bytes are then searched for in Static.slb's 8 353 472 readable bytes. + +event packets bytes channels/rate offset in Static.slb +------------------------- ------- ------ -------------- -------------------- +d-pad move (cursor) 4 8192 mono 48000 Hz 0x1ec0 +B / back (cancel) 2 4096 mono 48000 Hz 0x0ec0 +(played before any input) 6 12288 mono 48000 Hz 0x5d6c0 + +Each head matched at exactly ONE offset. The first two are contiguous: +0x0ec0 + 4096 = 0x1ec0. + +Simultaneously at the main menu, two STEREO 48 kHz streams were decoding: +1893 packets / 3 876 864 B and 1919 packets / 3 930 112 B. See +structures/bgm-two-stems.md -- this is the runtime observation that the two +stems of a music bank play at the same time. diff --git a/docs/re/menu-audio-cues.md b/docs/re/menu-audio-cues.md index 1c6e991..70be63c 100644 --- a/docs/re/menu-audio-cues.md +++ b/docs/re/menu-audio-cues.md @@ -142,3 +142,45 @@ right bank; only the index into it is missing. 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. + +## βœ… Retracting "cannot be extracted" β€” the waves are locatable, by playing them + +The section above concluded that an individual SE cue's audio "cannot be +extracted". **That was too strong, and the tool to do it was already in this +build.** Canary here carries a cvar `xma_param_probe`, added for exactly this +purpose β€” its own comment says it logs each XMA stream's parameters and head +bytes "so raw sound.pak entries can be matched to real decode params". + +Run with `--xma_param_probe=true`, drive the menu, and each newly-decoded stream +prints one line with its packet count, channel count, sample rate and **first 32 +bytes**. Searching those bytes in `Static.slb` finds the wave. + +| event | packets | bytes | format | offset in `Static.slb` | +|---|---|---|---|---| +| **d-pad move** (cursor) | 4 | 8 192 | mono 48 kHz | **`0x1ec0`** | +| **β’· back** (cancel) | 2 | 4 096 | mono 48 kHz | **`0x0ec0`** | +| played before any input | 6 | 12 288 | mono 48 kHz | `0x5d6c0` | + +Each head matched at **exactly one** offset. Reference data in +[`data/se-cue-runtime-offsets.txt`](data/se-cue-runtime-offsets.txt). + +**Structurally this also cracks the bank's layout.** The first two are +*contiguous* β€” `0x0ec0 + 4096 = 0x1ec0` β€” so `Static.slb` is a packed run of +XMA waves, each a whole number of 2 048-byte packets, with no delimiter between +them. That is why the `seek`-magic scan found nothing: there is nothing to find. +A wave is defined only by (offset, packet count), and those live in the game's +own tables, not in the bank. + +⚠️ **The order is not cue-id order.** `β’·`'s wave precedes the cursor's in the +file, while the name match puts `SE_UI_CURSOR` at id 2 and `SE_UI_CANSEL` at 4. +So the index cannot be derived by counting; it has to be observed per cue. + +### What is now measured, and what is still a name match + +* βœ… **measured** β€” a d-pad move and a β’· press each play a *specific, different* + wave, and both waves are located in `Static.slb`. The port can have the audio. +* 🟑 **still a name match** β€” that the cursor's wave *is the cue called* + `SE_UI_CURSOR`. The eventβ†’wave binding is measured; the eventβ†’*name* binding is + still read off the authors' identifiers. +* ❔ **not done** β€” decoding a located slice to PCM. The offsets and packet counts + are here; building the XMA1 `RIFF` around a mono slice was not attempted. diff --git a/docs/re/structures/bgm-two-stems.md b/docs/re/structures/bgm-two-stems.md index 2a384e1..1eedfcd 100644 --- a/docs/re/structures/bgm-two-stems.md +++ b/docs/re/structures/bgm-two-stems.md @@ -113,3 +113,24 @@ transcribing one. before silence is at RMS 168 against 4 788 at the head). Looping the wave as stored gives a fade-out and six seconds of nothing every cycle. No loop-point field has been identified in the XMA header, so a menu loop is **authored**. + +## βœ… Confirmed at runtime β€” two stereo streams decode at once + +The page above said the "played together" reading needed "a runtime observation +of whether the game submits both waves to the mixer at once", and that this +container could not do it. It can: `--xma_param_probe=true` logs every XMA stream +the decoder is handed. + +Sitting on the **main menu**, two stereo 48 kHz streams were decoding +simultaneously β€” **1 893 packets / 3 876 864 B** and **1 919 packets / +3 930 112 B**. Two stereo streams alive at the same moment on a screen with one +piece of music playing is the direct observation this page was missing: **a music +bank's two waves are simultaneous, not sequential.** Concatenating them is wrong, +now measured as well as inferred. + +🟑 It still does **not** separate the two readings β€” surround-rear pair versus a +second intensity layer β€” because both predict simultaneity. And the two byte +sizes do not match any bank's declared wave sizes in +[`../data/bgm-wave-census.txt`](../data/bgm-wave-census.txt), so *which* bank the +menu plays is still not established; the game hands the decoder a window, not the +whole declared wave.