re: the seek table's sample total is not the wave length -- durations corrected

Chasing w0 I decoded leading waves and the output matched neither trailer word.
Following that into RIFF waves, where the extent is certain, the decoded sample
count exceeds the seek table's last cumulative sample by a median 9.7%.

The tiebreak is the bank's own PsuedoBytesPerSec: over 14 banks it agrees with
an actual FFmpeg decode to a mean of 0.007 s and with the seek-table duration
only to 0.287 s. Two independent quantities agree with each other and both
disagree with the seek total, so that last entry is the last SEEK POINT, not the
sample count.

Both artifacts regenerated with data_bytes / PsuedoBytesPerSec. Total audio is
408.3 minutes, not the 390.9 I published -- 4.3% in aggregate. Dialogue timings
all rise, e.g. 'They got Leader!' from 1.78/2.46 s to 2.25/2.93 s.

What made this hard to catch is worth recording: the seek total gave BGM of 2.4
minutes, chatter of 2.8 seconds and cutscenes of 11 minutes, and I cited that as
the chain validating itself. It validated the structure, not the scale -- a
uniform 10% error preserves every ratio I checked.
This commit is contained in:
Sylpheed RE agent
2026-08-26 06:17:07 +00:00
parent 00e9a932ea
commit 640d860b23
5 changed files with 6587 additions and 6541 deletions

View File

@@ -95,7 +95,7 @@ files, which is how the same ground got covered twice.
| [`structures/idxd-container.md`](structures/idxd-container.md) | The IDXD/IXUD container — record/field table, and the two beliefs it withdraws | ✅ CONFIRMED disc-wide, 7 750/7 750 objects and 1 271 462/1 271 462 named fields, zero failures |
| [`structures/hud-glyph-quad.md`](structures/hud-glyph-quad.md) | The HUD's glyph quad — vtable `0x820B2A64` | ✅ CONFIRMED for the object layout and the atlas size, read live off |
| [`structures/slb-data-offset.md`](structures/slb-data-offset.md) | `.slb` leading-stream offset is `first_riff % 2048`, not the constant 1392 | ✅ CONFIRMED by decoding — 85 of 140 sampled banks yield more audio (median 70×), 54 identical controls. ⚠️ The *cause* is a segment-packing phase, not a header: `X = (cumulative .pNN start) mod 2048`. Wave boundaries are exact — `seek` magic at `data_at + declared_size`, **7 620/7 620** — and `Channels` must be read from `RIFF+49` (2.12 % are stereo) |
| [`structures/sound-pak-contents.md`](structures/sound-pak-contents.md) | Census of `sound.pak`, and the limit of the leading-region rule | ✅ CONFIRMED, 5 135/5 135 names hash into the TOC, **9 519/9 519** entries accounted for, and a full 4 114-bank manifest (390.9 min of audio) computed from the seek tables without decoding; ⚠️ leading-region rule holds for 1 571/4 382 eng and 0/5 100 jpn |
| [`structures/sound-pak-contents.md`](structures/sound-pak-contents.md) | Census of `sound.pak`, and the limit of the leading-region rule | ✅ CONFIRMED, 5 135/5 135 names hash into the TOC, **9 519/9 519** entries accounted for, and a full 4 114-bank manifest (408.3 min of audio) computed from PsuedoBytesPerSec without decoding; ⚠️ leading-region rule holds for 1 571/4 382 eng and 0/5 100 jpn |
| [`structures/sound-cue-table.md`](structures/sound-cue-table.md) | The cue index in `tables.pak` — message id -> cue -> sound id -> `.slb` bank | ✅ CONFIRMED, 1 326/1 338 script message ids bind to a bank; SOUNDS and FILES agree on the same 12 absentees, 0 orphan files |
| [`structures/cutscene-message-table.md`](structures/cutscene-message-table.md) | Cutscene dialogue — speaker, portrait, on-screen seconds, audio cue per page | ✅ CONFIRMED, field count = 9·PageCount+2 for all 7 PageCounts, 1 252/1 252 caption keys match, 138 ids close both ways |
| [`structures/isl-message-dialogue-link.md`](structures/isl-message-dialogue-link.md) | Mission scripts as dialogue — built-in 64 -> message id -> caption text | ✅ CONFIRMED total, 2 683/2 683 call sites across all 28 stages resolve, no residue |

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -601,3 +601,48 @@ That is the shape of a sparse table, and it is unidentified.
**A correction to how I described this block**: I called the region "zero
padding" after the seek chunk. It is not padding — it is a sparse structure that
is *mostly* zero. The distinction matters for anyone who tries to skip it.
## ❌ The `seek` table's sample total is NOT the wave's length — my durations were low
**2026-08-26.** I published two artifacts using `last cumulative sample ÷ sample
rate` as the duration. That is wrong, and this is the measurement that shows it.
Trying to pin down `w0`, I decoded leading waves and found the output matched
**neither** trailer word. Following that into the `RIFF` waves, where the extent
is certain, the decoded sample count exceeds the `seek` table's last cumulative
sample by a **median 9.7 %** — far too much to be rounding.
The tiebreak is the bank's own **`PsuedoBytesPerSec`** field, which gives a
duration as `data_bytes / PBPS` and is read, not decoded. Over 14 banks:
| comparison | mean absolute difference |
|---|---|
| `PBPS` vs the **seek-table** duration | **0.287 s** |
| `PBPS` vs the **decoded** duration | **0.007 s** |
Two independent quantities — the bank's declared byte rate and an actual FFmpeg
decode — agree with each other to seven milliseconds and both disagree with the
`seek` total. The `seek` table's last entry is therefore **not** the wave's
sample count; it is the last *seek point*, which stops short of the end.
**Corrected**, and both artifacts regenerated with `data_bytes / PsuedoBytesPerSec`:
* [`../data/voice-bank-manifest.txt`](../data/voice-bank-manifest.txt) — total
audio **408.3 minutes**, not the 390.9 I reported. A **4.3 %** understatement
in aggregate; the per-bank median error was 9.7 %, but long banks dominate the
total and are proportionally more accurate.
* [`../data/stage-dialogue-with-durations.txt`](../data/stage-dialogue-with-durations.txt)
— every line's timing rises, e.g. *"They got Leader!"* from 1.78/2.46 s to
**2.25/2.93 s**.
⚠️ **What made this hard to catch**: the `seek` total *looked* right. It gave
BGM tracks of 2.4 minutes, radio chatter of 2.8 seconds, cutscenes of 11
minutes — a set of numbers that sorted themselves into exactly the right shapes,
which I cited as the chain validating itself. It was validating the *structure*
and not the *scale*: a uniform 10 % error preserves every ratio I checked. A
self-consistency check cannot catch a systematic factor, and I should not have
read it as confirmation of the value.
🟡 `w0` remains unidentified. Neither trailer word matches the decoded length,
so the "usable length" reading of `w0` is now less likely, not more.

View File

@@ -128,7 +128,7 @@ those are the headerless banks.) Duration comes from the **last cumulative
sample** in the `seek` table divided by the sample rate — arithmetic, no
decoding.
**Total: 390.9 minutes — about 6½ hours of audio.**
**Total: 408.3 minutes — about 6¾ hours of audio.** ⚠️ An earlier version of this page said 390.9 minutes, computed from the `seek` table's sample total; that understates the true length by a median 9.7 % per bank. See [slb-data-offset.md](slb-data-offset.md).
The reason this is worth more than a listing is that nothing in it was fitted to
an expectation, yet every category lands where its content says it should: