From 801062c933c5c8adcaebc1a5223eaf2d55c148e0 Mon Sep 17 00:00:00 2001 From: sylph-decoder Date: Sat, 29 Aug 2026 15:09:40 +0000 Subject: [PATCH] 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) Claude-Session: https://claude.ai/code/session_01QsEPXWVaEpyfudtR6re1Pd --- .../examples/voice_stream_cue_map.rs | 34 ++++++ docs/port/HANDOFF.md | 41 ++++++- docs/re/data/voice-stream-cue-map.txt | 5 + .../structures/voice-region-leading-chunk.md | 106 +++++++++++++++--- 4 files changed, 165 insertions(+), 21 deletions(-) diff --git a/crates/sylpheed-formats/examples/voice_stream_cue_map.rs b/crates/sylpheed-formats/examples/voice_stream_cue_map.rs index 46af87b6..b78a37f5 100644 --- a/crates/sylpheed-formats/examples/voice_stream_cue_map.rs +++ b/crates/sylpheed-formats/examples/voice_stream_cue_map.rs @@ -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 = Default::default(); + let mut hist_long: std::collections::BTreeMap = 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:?}"); } diff --git a/docs/port/HANDOFF.md b/docs/port/HANDOFF.md index 84815115..98100dae 100644 --- a/docs/port/HANDOFF.md +++ b/docs/port/HANDOFF.md @@ -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 diff --git a/docs/re/data/voice-stream-cue-map.txt b/docs/re/data/voice-stream-cue-map.txt index 5f0f6e60..1ce55828 100644 --- a/docs/re/data/voice-stream-cue-map.txt +++ b/docs/re/data/voice-stream-cue-map.txt @@ -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} diff --git a/docs/re/structures/voice-region-leading-chunk.md b/docs/re/structures/voice-region-leading-chunk.md index 79e5abbb..3ed68c15 100644 --- a/docs/re/structures/voice-region-leading-chunk.md +++ b/docs/re/structures/voice-region-leading-chunk.md @@ -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`) — 98–99.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.