re: a long voice cue is three presentations of one take, and our guard clips the first

Closes the last open question on the voice regions: why one cue s byte span
decodes to ~2.6x the movie s length.

The port measured, with controls including a cross-movie negative, that a
region s leading chunk is the TAIL of the full-length chunk that follows it --
r = 0.998 at a lag that puts it flush against that chunk s end, residual 16.7 dB
down over 84.5 s. They withdrew their own earlier 0.768, which came from a
search that scored best on the boundary of its own lag range.

Checked it here by an independent route that needs no decoder. If the leading
chunk is the tail of a full-length first stream, the whole leading stream should
be one complete take of chunk 1 s duration. For ADV: 504464 + 808304 = 1312768 B
at chunk 0 s byte rate of 9559.7 B/s is 137.323 s, against chunk 1 s measured
137.324 s. One millisecond over 137 seconds, from byte rates rather than from
envelope correlation.

And the byte structure settles the shape disc-wide. Counting stream starts inside
every inter-descriptor span: 258 hold exactly 1 stream, 28 hold exactly 3, and
nothing holds 2 or any other number. All 20 spans over 1.5 MB are 3-stream. The
95 movie regions decompose 70 + 8 + 17, and the 8 are independently the same 8
the first census found as bank-header-with-3-chunks.

So 359 s = 84.55 + 137.32 + 137.32: three presentations of one take, the first
clipped by resolve_movie_voice_region s own 1.5 MB guard.

Consequences recorded for the port: dropping the leading chunk is removing a
duplicate rather than truncating, so the hedge is lifted; but summing chunk 1
and chunk 2 is wrong, because they are the same take at different gain, not two
stems. Take one stream.

Also flags a coincidence I nearly built on: the 504464 B constant is structural,
not proportional -- ADV s proportional prediction lands within 8 bytes of it and
S00A s is 4305 B out.

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 15:09:40 +00:00
parent 06a1779b19
commit 801062c933
4 changed files with 165 additions and 21 deletions

View File

@@ -168,4 +168,38 @@ true start {prev} -> {} B of the cue's own audio is OUTSIDE the region",
}
println!("\ncues over the 1.5 MB guard: {over}, of which stream-opening: {over_is_stream}");
println!("cues under the guard: {under}, of which stream-opening: {under_is_stream}");
// How many streams is ONE cue stored as? The port measured that a region's
// leading chunk is the TAIL of its first full-length chunk, i.e. the cue is
// re-presented. Structurally that predicts a fixed number of stream starts
// inside a cue's TRUE span [desc(N-1) .. desc(N)] -- which is measurable
// from the bytes alone, with no decoder.
println!("\nstream starts inside each cue's TRUE span (desc(N-1)..desc(N)):\n");
let mut hist: std::collections::BTreeMap<usize, usize> = Default::default();
let mut hist_long: std::collections::BTreeMap<usize, usize> = Default::default();
for w in descs.windows(2) {
let (a, b) = (w[0].0, w[1].0);
if b <= a || b - a < 4096 {
continue;
}
let span = &buf[a..b];
// A stream start is a RIFF; plus the run before the first one, when it
// is not a bank header, is itself a stream.
let riffs = span
.windows(4)
.enumerate()
.filter(|(_, w)| *w == b"RIFF")
.count();
let lead_is_stream = match span.windows(4).position(|w| w == b"RIFF") {
Some(f) if f > 0 => sylpheed_formats::slb::bank_header_len(span) != Some(f),
_ => false,
};
let streams = riffs + usize::from(lead_is_stream);
*hist.entry(streams).or_default() += 1;
if b - a >= 1_500_000 {
*hist_long.entry(streams).or_default() += 1;
}
}
println!(" all inter-descriptor spans: {hist:?}");
println!(" spans >= 1.5 MB (the long cues): {hist_long:?}");
}

View File

@@ -391,9 +391,44 @@ decode has `ADV`'s region at 359 s against a 137 s movie — it over-covers, so
byte↔time mapping is not linear and I will not convert 504 464 B into missing
dialogue. I have no XMA1 decoder here to check.
**What is still open is narrower and better posed**: not *whose audio is this*
(answered — the movie's own), but *why one cue's byte span decodes to ~2.6× the
movie*, and what `ADV` chunk 2 (0.60 × chunk 1) is. Both are mine.
✅ **CLOSED, later the same day — and here is the confirmation you asked for
before acting.**
**Yes: drop the leading chunk. It is a DUPLICATE, not a truncation.** Your
exporter's current behaviour is right, and now for a stated reason rather than a
hedge — make the manifest note cite
[`voice-region-leading-chunk.md`](../re/structures/voice-region-leading-chunk.md).
Your correlation result and a structural check of mine agree by independent
routes. Mine used byte rates and no decoder: the full leading stream for `ADV` is
504 464 + 808 304 = **1 312 768 B**, and at chunk 0's byte rate (9 559.7 B/s) that
is **137.323 s** against chunk 1's measured **137.324 s** — 1 ms over 137 s.
**And the byte structure settles the shape disc-wide.** Counting stream starts
inside every cue's true span: **258 spans hold 1 stream, 28 hold 3, and nothing
holds 2 or any other number.** All 20 spans ≥ 1.5 MB are 3-stream. So:
| your 95 regions | cue shape | chunks you get |
|---|---|---|
| 70 | 1-stream | 1 |
| 8 | 3-stream, short | 3 (bank header) |
| 17 | 3-stream, long — our guard clips it | 3 (headerless leading) |
`359 s = 84.55 + 137.32 + 137.32`. **The 2.6× is three presentations of one
take**, one clipped by our own guard. That was the last open one and it is shut.
🔴 **One change you have NOT made yet and should: stop summing chunk 1 and chunk
2.** They are the same take, not two stems — `ADV` chunk 2 is 0.60 × chunk 1
(residual 26.8 dB down), `S00A` chunk 2 is digital silence. Summing a take with a
scaled copy of itself adds ~4 dB and colours it. **Take one stream**, the
highest-rate one (chunk 1). Dropping silent chunks was the right instinct; this
is the same defect one step further out. ❔ *Why* the disc stores three
presentations is not answered.
⚠️ On your offer to convert the 504 464 B constant: **don't spend the decode.** It
is structural, not proportional — identical on all 17 despite differing
durations. `ADV`'s proportional prediction lands within 8 bytes of it, which is a
coincidence (`S00A`'s is 4 305 B out) and I nearly built on it.
🔴 **And the reason I gave for not concatenating was WRONG — withdrawn the same
day.** I wrote that chunks 1 and 2 are "the two-stem pattern, not consecutive

View File

@@ -48,3 +48,8 @@ cue span vs the 1.5 MB guard, and what the region actually starts at:
cues over the 1.5 MB guard: 17, of which stream-opening: 17
cues under the guard: 78, of which stream-opening: 0
stream starts inside each cue's TRUE span (desc(N-1)..desc(N)):
all inter-descriptor spans: {1: 258, 3: 28}
spans >= 1.5 MB (the long cues): {3: 20}

View File

@@ -132,29 +132,99 @@ all 17** — not an approximate constant, the same number every time. That
regularity is unexplained and is worth someone's attention; it says the `.slb`
chunk boundary is placed at a fixed distance from a trailer.
### ⚠️ What this does NOT establish — do not convert bytes into missing seconds
### ✅ RESOLVED 2026-08-29 (later) — a long cue is stored as THREE presentations of one take
It is tempting to read "504 464 B of the cue's own audio lies outside the region"
as *the export truncates 246 packets of dialogue*. **Do not.** The port's decode
of `ADV`'s region already yields **359 s against a 137 s movie**, so the region
over-covers rather than under-covers, and the byte↔time mapping is plainly not
linear — consistent with more than one sub-stream being interleaved. Bytes are
what was measured here; seconds are not, and this container has no XMA1 decoder
to get them.
The section that stood here declined to convert bytes into seconds and left open
*why one cue's byte span decodes to ~2.6× the movie*. That is now answered, by
two independent routes that agree.
**So what remains open is narrower and better posed than before:** not *whose
audio is this* (answered: the movie's own), but *why one cue's byte span decodes
to ~2.6× the movie's duration*, and what `ADV` chunk 2 — a 0.60 × scaled copy of
chunk 1 — is doing in it.
**The port's route — envelope cross-correlation, with controls.** Sliding with
overhang at both ends and normalised over the overlap only:
| | best r | at lag | overlap |
|---|---|---|---|
| `ADV` chunk 0 → chunk 1 | **0.998** | +52.8 s | 84.5 s |
| `S00A` chunk 0 → chunk 1 | **0.932** | +25.6 s | 68.0 s |
| control — `ADV` chunk 0 → itself | 1.000 | 0.0 s | — |
| control — `ADV` chunk 0 → `S00A` chunk 1 | **0.289** | — | 28.2 s |
Both lags put chunk 0 flush against the **end** of chunk 1 (52.8 + 84.55 =
137.35 against 137.324; 25.6 + 68.07 = 93.67 against 93.694). In the sample
domain, after refining the lag and best-fitting a scalar, the residual is
**16.70 dB** below the target over 84.5 s (`ADV`) and 23.15 dB over 68.1 s
(`S00A`) — 9899.5 % of the energy is a scaled copy.
⚠️ Their earlier 0.768 is **withdrawn by them**: that search only tried lags where
the shorter chunk fitted wholly inside the longer, and scored best on the
boundary of its own range — where a statistic lands when it has found nothing.
**My route — byte rates, from the disc, with no decoder.** If the leading chunk
is the tail of a *full-length* first stream, then the whole leading stream
(the part before the region's anchor plus the part after) should be one complete
take of chunk 1's duration. Using only the port's durations and the disc's byte
counts:
| | |
|---|---|
| full leading stream, `ADV` | 504 464 + 808 304 = **1 312 768 B** |
| its byte rate, from chunk 0 | 808 304 B / 84.553 s = **9 559.7 B/s** |
| implied duration of the whole leading stream | **137.323 s** |
| chunk 1's measured duration | **137.324 s** |
**Agreement to 1 ms over 137 s**, from a quantity (byte rate) independent of the
one the port measured (envelope correlation). And it confirms their point that
bytes-per-second is not a constant: the three streams run at **9 560 / 8 143 /
8 531 B/s** for the same 137.324 s.
### ✅ And the byte structure says three, disc-wide
The reading above predicts a fixed number of stream starts inside a cue's **true**
span `[desc(N-1) .. desc(N)]`. Counted directly over every inter-descriptor span
in the 116.2 MB window — RIFFs, plus a leading headerless run when it is not a
bank header:
| streams in the span | spans |
|---|---|
| **1** | **258** |
| **3** | **28** |
| 2, or anything else | **0** |
**Bimodal, with nothing in between**, and *all 20* spans ≥ 1.5 MB are 3-stream.
So a cue is stored as **one** stream or as **three**, never two.
That closes the account, and the three routes agree on the arithmetic:
| the 95 movie regions | cue shape | what the region yields |
|---|---|---|
| **70** | 1-stream | 1 chunk, bank header |
| **8** | 3-stream, span < 1.5 MB | 3 chunks, bank header |
| **17** | 3-stream, span ≥ 1.5 MB — the guard fires | 3 chunks, headerless leading one |
70 + 8 + 17 = 95, and the 8 here are independently the same 8 the first census
found as "bank header, 3 chunks". `359 s = 84.55 + 137.32 + 137.32` — the region
catches the tail of stream 1 and the whole of streams 2 and 3. **The 2.6× is
three presentations of one take, one of them clipped by our own guard.**
⚠️ **The 504 464 B constant is structural, not proportional.** It is the same
number on all 17 despite their differing durations. For `ADV` a proportional
prediction lands within 8 bytes of it, which is a coincidence and should not be
built on — the same prediction for `S00A` is 4 305 B out.
## What a consumer should do meanwhile
🔴 **Dropping the leading chunk is dropping the cutscene's own dialogue** that
is now measured, not suspected, and the manifest note must not say or imply that
the bank had a spurious chunk. ⚠️ It does **not** follow that simply including it
is right: the region already decodes to ~2.6× the movie's length, so inclusion is
a decoding question that is still open, and this page has moved it rather than
closed it. What is settled is the *provenance* of those bytes.
**Drop the leading chunk. It is a DUPLICATE, not a truncation.** It is the
cutscene's own dialogue — but it is the *tail of the stream that follows it*, so
removing it loses nothing any other chunk does not carry. An earlier version of
this page said "I would not change the exporter yet"; that hedge is lifted, and
the reason the exporter's existing behaviour is right is now on the record.
🔴 **But DO NOT sum chunk 1 and chunk 2 — they are the same take, not two
stems.** `ADV` chunk 2 is `0.60 ×` chunk 1 with the residual 26.8 dB down;
`S00A` chunk 2 is digital silence. Summing a take with a scaled copy of itself
adds ~4 dB and colours it; summing it with silence at `1/n` costs 6.02 dB. **Take
one stream.** On the evidence so far the highest-rate, highest-gain one is chunk
1.*Why* the disc stores three presentations — quality tiers, a mix the engine
selects between, an authoring artefact — is not answered here.
⚠️ **Do not "fix" it by concatenating.** The port measured a concatenated region
at 359 s against a 137 s movie.