re: the voice-region third chunk is a different structure from the BGM one

The port hit a 2+1 chunk signature on a resolved movie-voice region and asked
whether the bank-header explanation that closed HANDOFF Q10 also covers it,
rather than assuming it. It does not, and the discriminator is mechanical.

Disc-wide over the 95 English movie-voice regions the manifest binds:

  78 open with a bank header -- bank_header_len fires, 10240 B = 5 packets
     exactly, every time. That is the BGM case.
  17 open with a leading headerless stream -- bank_header_len is None, and all
     17 have length congruent to 1392 mod 2048, the disc s own derived data
     offset. No other residue occurs.
   0 begin at a RIFF.

Counting chunks does not discriminate: 8 bank-header regions also yield three
chunks. slb.rs already predicted this in its own doc comment -- the header
signature has "zero false positives on the 7993 mid-bank windows, where the
leading region IS real" -- and a voice region is a mid-bank window by
construction.

Also tested the obvious defence of dropping the leading chunk, that it is the
predecessor cue s audio: 0 of 17 leading spans lie inside any other resolved
region, 0.0 percent on every one. The test finds overlaps where they exist (16
overlapping pairs among the regions, 60 exactly-adjacent boundaries, 73 of 78
bank-header regions starting where another ends), so the zero is not the
instrument.

Left open, with reach: the census covers movie-voice regions only, and the same
stream carries the in-mission VOICE_D_* cues, which are not enumerated -- the
leading bytes plausibly belong to one of those. Could not be settled by
listening: no XMA1 decoder in this container, and sylpheed-cli audio info
reports these chunks as 16 channels / 4310 Hz / 2-bit, which is visibly wrong.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QsEPXWVaEpyfudtR6re1Pd
This commit is contained in:
sylph-decoder
2026-08-29 14:56:17 +00:00
parent 3e7a258a9a
commit de2fe4a110
4 changed files with 350 additions and 0 deletions

View File

@@ -0,0 +1,110 @@
# 🟡 A movie-voice region's THIRD chunk is not the bank-header case — and nothing else claims it
**Status:** ✅ the *structure* is decoded, disc-wide, 95/95 regions. 🟡 what the
leading chunk **contains** is open, and this page states the reach of that
negative rather than guessing.
Raised by the port: `media::sound_bank_riffs("BGM_103.slb")` used to return three
sub-waves where [`bgm-two-stems`](bgm-two-stems.md) says two, and
[`slb-bank-header-not-a-wave`](slb-bank-header-not-a-wave.md) attributed the
extra to the **bank header**. The port then hit *the same 2+1 signature on a
different asset kind* — a resolved movie-voice region also decoding to three
chunks — and asked whether one explanation covers both.
**It does not.** They are two different structures, and the corpus's own code
already tells them apart; what it does not do is say which one it is looking at.
Tool: `cargo run -p sylpheed-formats --example voice_region_chunks -- $SYLPHEED_DISC`.
Census committed at [`data/voice-region-chunk-census.txt`](../data/voice-region-chunk-census.txt).
## ✅ Disc-wide: a voice region never begins at a `RIFF`
All 95 English movie-voice regions the manifest binds:
| how the region opens | regions | chunks it yields |
|---|---|---|
| a **bank header**`bank_header_len` fires, **10 240 B = 5 packets exactly**, every time | **78** | 1 (×70) or 3 (×8) |
| a **leading headerless stream**`bank_header_len` is `None` | **17** | **3, every time** |
| directly at a `RIFF` | **0** | — |
And the leading streams are not ragged. **All 17 have a length ≡ 1392 (mod
2048)** — no other residue occurs — which is exactly `HEADERLESS_DATA_OFFSET`,
the `<lang>\etc\` data offset that [`slb-data-offset`](slb-data-offset.md)
derives. So a leading stream is `1392 B` of preamble followed by a whole number
of 2048-byte XMA1 packets: 394 of them on `ADV`, 646 on `S00A`, 900 on `S12C`.
That is the discriminator the port needed, and it is mechanical:
```
bank_header_len(region) == Some(n) -> n is 10240, a header, already consumed
bank_header_len(region) == None -> first_riff % 2048 == 1392, a real stream
```
## 🔴 So the BGM explanation does not transfer
`slb.rs`'s own doc comment predicted this and disagrees with "drop it": the
header signature fires on 28 `sound.pak` entries, all music banks, with *"zero
false positives on the 7 993 mid-bank windows, **where the leading region IS
real**"*. A movie-voice region is a mid-bank window by construction —
`resolve_movie_voice_region` anchors its start at the **predecessor cue's
trailer**, deliberately, because the cue may sit either side of its own `.slb`
chunk.
⚠️ **The 3-chunk count is not evidence of the leading region at all.** Eight
regions open with a bank header *and still yield three chunks* (`S11A`, `S12A`,
`S12B`, `S13B`, `S15B`, …). Counting chunks cannot distinguish the two cases;
only `bank_header_len` can.
## 🔴 "It is the previous cue's audio, so dropping it is right" — TESTED, and it fails
The obvious defence of dropping the leading chunk is that the region starts at
the predecessor's trailer, so those bytes are the previous line of dialogue.
That is checkable without decoding anything: take each leading span
`[start, start + first_riff)` and ask whether any *other* resolved region covers
it.
| | |
|---|---|
| leading spans lying wholly or partly inside another movie-voice region | **0 of 17** |
| …expressed as covered fraction | **0.0 % on every one** |
For contrast, the regions themselves are not disjoint — 16 overlapping pairs, 60
exactly-adjacent boundaries, 18 gaps — so the test is capable of finding an
overlap, and it finds none here. **73 of 78** bank-header regions start exactly
where another region ends; **0 of 17** leading-stream regions do.
So the leading chunk is not another *movie's* voice.
## 🟡 What it is, is open — and here is the reach
What is established: the leading chunk is a whole number of XMA1 packets at the
disc's own derived data offset, inside this movie's region, claimed by no other
movie-voice region. What is **not** established is what it sounds like.
The reach of the negative, stated plainly:
* The census enumerates the **95 movie-voice regions the manifest binds in
English**. The same stream also carries the in-mission voice cues
(`VOICE_D_*`), which are *not* enumerated here. The leading bytes could belong
to one of those, and this test would not see it. **That is the leading
hypothesis and it is untested.**
* 🔴 **It could not be settled by listening in this container.** There is no XMA1
decoder here — `sylpheed-cli audio info` reports `decode not supported (needs
an XMA2 decoder + the sound-bank descriptor)`, and its header read of these
chunks is visibly wrong (16 channels, 4310 Hz, 2-bit depth), so it cannot even
be used for durations. Settling this needs a decoder run, which the port has
and this container does not.
## What a consumer should do meanwhile
🟡 Dropping the leading chunk is **defensible and should stay labelled**, which is
what the port already does. It is not junk and it is not a header — it is
undecoded audio — so the manifest note must not harden into "the bank had a
spurious chunk". If it turns out to be an in-mission line, dropping it is
correct; if it turns out to be part of the cutscene, it is a truncation.
⚠️ **Do not "fix" it by concatenating.** The port measured a concatenated region
at 359 s against a 137 s movie, and chunks 1 and 2 are the two-stem pattern
[`bgm-two-stems`](bgm-two-stems.md) documents for music — equal duration, played
together, not in sequence. Concatenation is wrong here for the same reason it is
wrong there.