The port hit `sound_bank_riffs("BGM_103.slb")` returning three against a census
that says two, and refused to guess which to drop. It was our reader.
`to_xma_riffs`'s hybrid branch derives a leading packet stream's start as
`first_riff % XMA1_PACKET`. That is right only when the bank header is smaller
than one 2048-byte packet -- true of the voice banks the branch was written for
(1392/1468/1600/1728), false of a music bank, whose header is exactly five
packets. The modulus returned 0 and the whole 10 240-byte header was emitted as
sub-wave 0.
The header states its own length, so the guard needs no threshold: BE u32 0x800
at +0x18 with the bank id repeated at +0x00 and +0x20, header length in blocks at
+0x24. Disc-wide over sound.pak's 9 519 entries, 28 match at offset 0 -- every
music bank, ids 1001-1023 and 1101-1105 -- and on 28/28 the declared header ends
EXACTLY at the first RIFF. Zero have a gap, so a header and a leading packet
stream never coexist here; zero false positives among the other 9 491.
Controlled rather than argued: decoding the emitted region through the same
chain, on the same bank, in the same run gives 0.009 s of PCM where the bank's
real wave 0 gives 87.744 s against a declared 87.75. The region is also 99.1%
zero bytes. And the oracle had already said two -- the XMA probe at the main menu
saw exactly two streams, at BGM_103's two declared wave sizes.
BGM_106-109 are deliberately NOT in the 28: their entries start mid-bank, so they
have no header at offset 0 and their leading region is real audio. The
VOICE_D_453 recovery is untouched and its tests still pass, 10/10 green.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014voBspJ6kFncNErZJuZcLw
5.4 KiB
✅ A music bank's "third sub-wave" is its header, and the bug was arithmetic
Status: ✅ CONFIRMED — decoded, with a disc-wide check over all 9 519
sound.pak entries, a decode control, and independent corroboration from the
running game. Fixed in sylpheed-formats 2026-08-29.
Raised by the port, on its P6 critical path:
sound_bank_riffs("BGM_103.slb") returned three sub-waves against
bgm-two-stems.md's census, which says a music bank is
exactly two. Its exporter was summing all three, so the shipped menu music was
the sum of three things where the corpus predicted two. It declined to choose
which to drop, which was right — that is a decoding question.
The answer
The third thing is the bank header. Not a stem, not an artefact of the disc: our own reader was emitting it.
to_xma_riffs has a hybrid branch for banks that carry a headerless packet
stream before their first RIFF — the fix that recovered VOICE_D_453's line
(slb-data-offset.md). It derives that stream's start as
first_riff % XMA1_PACKET // XMA1_PACKET = 2048
which is correct only when the bank header is smaller than one packet. It is,
in the voice banks the branch was written for: their headers put the first RIFF
at 1392, 1468, 1600 or 1728 mod 2048.
A music bank's header is exactly five packets — 10 240 bytes — so the
modulus returns 0, and the branch emitted slb[0..10240]: the whole header,
as sub-wave 0.
The header states its own length, so nothing here needs a heuristic:
BGM_103.slb
+0x00 BE u32 1103 bank id
+0x18 BE u32 0x00000800 block size = 2048
+0x1c BE u32 7839244 data size
+0x20 BE u32 1103 the id again ← signature, with +0x18
+0x24 BE u32 5 HEADER LENGTH IN BLOCKS → 5 × 2048 = 10240
+0x28 BE u32 0x00100002 16 bit / 2 ch
The disc-wide check
Over all 9 519 entries of sound.pak
(tools/re-capture/slb_segment_phase.py
supplies the reader):
| entries matching the header signature at offset 0 | 28 |
...whose declared header ends exactly at the first RIFF |
28 / 28 |
...with a real gap between header and first RIFF |
0 |
| false positives among the 9 491 others | 0 |
The 28 are exactly the music banks — ids 1001–1023 and 1101–1105. So on
this disc a bank header at offset 0 and a leading packet stream never
coexist, and the guard is not a threshold: if a bank states a header, believe
it, and there is nothing before the first RIFF.
⚠️ BGM_106–BGM_109 are not in the 28 and must not be: their pak entries
start mid-bank, so they have no header at offset 0 and their leading region is
real audio (the tail of the previous bank). That is the same straddle
bgm-two-stems.md already documents.
The decode control
Decoding the emitted region proves it is not audio, and the control is run through the same chain, on the same bank, in the same invocation:
| bytes | PCM decoded | |
|---|---|---|
BGM_103 — what we emitted as "sub-wave 0" |
10 240 | 0.009 s |
BGM_103 — its real wave 0 (control) |
3 876 864 | 87.744 s (declared 87.75) |
BGM_001 — what we emitted as "sub-wave 0" |
10 240 | 0.009 s |
BGM_001 — its real wave 0 (control) |
4 466 688 | 173.809 s (declared 173.82) |
FFmpeg xma1, mono/stereo taken from the bank's own fmt . The region is also
99.1 % zero bytes (67–93 non-zero of 10 240 across the 28 banks) and its last
non-zero byte is at 6431, so its final 1.86 packets are entirely empty.
Corroboration from the oracle, which was already in the corpus
bgm-two-stems.md records that at the main menu, with
--xma_param_probe=true, the decoder was handed two stereo 48 kHz streams —
of 3 876 864 and 3 930 112 bytes, byte-for-byte BGM_103's two declared
waves. A third stem would have been a third stream. The running game was already
saying two.
The fix
slb::bank_header_len (new, pub) reads the signature and returns the declared
length; the hybrid branch uses it in preference to the modulus:
let start = bank_header_len(slb).unwrap_or_else(|| leading_data_offset(ri));
if ri > start { /* emit the leading stream */ }
Two regression tests in
tests/slb_leading_segment_disc.rs:
the disc-wide 28/28 identity, and BGM_103/BGM_001 returning exactly two
sub-waves at their declared payload sizes. The pre-existing voice-bank tests —
broken_banks_recover_their_line, derived_offset_recovers_voice_banks_without_regressing_etc
— still pass, so the VOICE_D_453 recovery is untouched. 10/10 green with
SYLPHEED_DISC set.
Reach
- The 28 are the only banks on the disc that state a header at offset 0. A bank format elsewhere with a header ≥ 2048 B that we have not seen would have had the same bug; nothing on this disc does.
- This says nothing about which of the two remaining waves is which — that is
still 🟡 in
bgm-two-stems.md(surround-rear pair vs a second intensity layer), and both readings predict playing them together. - It does not change the count for any voice bank:
VOICE_*entries have no header at offset 0, so their leading region is emitted exactly as before.