port: a voice region is three presentations of one take -- keep one stream, sum nothing

Third reading of the same bytes in one session, and the first one that is
decoded rather than inferred.

  1. concatenate  -> 359 s of dialogue for a 137 s movie. Dead on measurement.
  2. sum as Q10's two stems -> refuted here: S00A's second full-length chunk is
     DIGITAL SILENCE and ADV's is 0.60x the first with 26.8 dB of residual. That
     claim was mine, and the Decoder had already adopted it before I tested it;
     it is withdrawn in both places.
  3. keep ONE stream. Decoded disc-wide by the Decoder, counting stream starts
     inside every inter-descriptor span: 258 spans hold one stream, 28 hold
     three, nothing holds two. So 359 = 84.55 + 137.32 + 137.32.

Summing was therefore wrong a third time, and for a third reason: a take plus a
0.60x copy of itself is ~4 dB louder and coloured, not a mix of parts. The filter
is now `[0:a]anull` plus the mono fold -- no gain applied at all.

`check` moves `voice` off the strict peak bound as a consequence. It sat with
`bgm` because it was a sum this exporter produced; it is now a single wave off
the disc, mastered near full scale -- ADV's louder presentation measures +0.0003
dBFS at source -- so refusing that would be refusing the disc's own mastering.

The leading chunk is dropped as a DUPLICATE, not a truncation. It is this
movie's own dialogue (the Decoder, 17 of 17) and I measured it to be the TAIL of
the kept stream: sliding envelope correlation r=0.998 / 0.932 with the lag flush
against that stream's end, controls 1.000 self and 0.289 for a different movie,
confirmed in the sample domain at 16.7 / 23.2 dB of residual.

STILL OPEN, and flagged rather than absorbed: WHICH presentation to keep. Highest
byte rate is the Decoder's recommendation and not a decoded field, and on ADV it
selects the QUIETER of two (-8.3 dBFS against 0.0). Said in the manifest with the
consequence, so it is visible and reversible. A capture of the intro with
dialogue audible settles it.

Not converted, and the Decoder has since withdrawn the ask with a better reason
than mine: its 504464 B anchor constant is structural, not proportional --
identical on all 17 regions, and a proportional prediction lands within 8 bytes
on ADV while being 4305 B out on S00A.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N7FiFFFwbvG2uxdcEh8HyF
This commit is contained in:
Sylpheed port agent
2026-08-29 15:12:41 +00:00
parent 0e7fcc2dda
commit 29fd735b96
4 changed files with 165 additions and 61 deletions

View File

@@ -463,16 +463,15 @@ pub fn export_bgm<S: DiscSource + ?Sized>(
///
/// ## Three choices made here, and the reason each is not a guess
///
/// * **One file per movie** (MODDING rule 1), and the region's `RIFF`s are
/// **summed, not concatenated** — the reverse of what this function did when
/// it was first written an hour earlier. Concatenating produced a 359 s voice
/// track for a 137 s movie.
/// * **One file per movie** (MODDING rule 1), and **exactly one region chunk is
/// kept**. Not concatenated, not summed. This function got that wrong twice
/// before it got it right, and the history is kept below because each wrong
/// reading was ended by a measurement, not by an argument.
///
/// ## The chunks are stems, and only a measurement showed it
/// ## A region holds THREE PRESENTATIONS OF ONE TAKE — decoded, and not by me
///
/// A resolved region decodes to several chunks, and the two readings — segments
/// to join end to end, or stems to mix — look identical in the bytes. Decoded
/// and timed against the movies' own lengths:
/// A resolved region decodes to several chunks. Decoded and timed against the
/// movies' own lengths:
///
/// | movie | movie | chunk 0 | chunk 1 | chunk 2 |
/// |---|---|---|---|---|
@@ -480,29 +479,50 @@ pub fn export_bgm<S: DiscSource + ?Sized>(
/// | `S00A` | 93.779 s | 68.072 | **93.694** | **93.694** |
/// | `RT01A` | — | 0.009 | **34.034** | — |
///
/// Chunks 1 and 2 are **equal to six decimals and each span the whole movie**.
/// That is exactly HANDOFF Q10's decoded shape — *a bank is two stems of one
/// performance, played together; do not concatenate* — arriving on a different
/// asset kind, which is why they are summed at `1/n` like [`export_bgm`]'s.
/// **Reading 1, concatenate:** 359 s of dialogue for a 137 s movie. Dead.
///
/// ⚠️ **Chunk 0 is dropped, and dropping it may be a TRUNCATION.** This comment
/// first guessed it was the same thing as `BGM_103`'s third sub-wave — a bank
/// header — and a disc-wide census over all 95 English movie-voice regions
/// showed that is a *different structure*: 78 regions open with a 10 240 B bank
/// header, 17 with a leading headerless stream at the disc's own `1392 mod 2048`
/// data offset, and the chunk count discriminates neither. Then the byte-span
/// test settled what it holds: **the leading chunk is this movie's own dialogue,
/// 17 of 17** — not an in-mission line, which was the standing hypothesis.
/// **Reading 2, sum them as HANDOFF Q10's two stems** — equal duration, each
/// spanning the movie, which is exactly Q10's *music* shape. ❌ **Refuted here,
/// and the claim had already been adopted into the RE corpus before I tested
/// it**: `S00A`'s second full-length chunk is **digital silence** (peak inf)
/// and `ADV`'s is **0.60 × the first** with 26.8 dB of residual. Equal duration
/// was a shape match and carrying a music census across on it was my error.
///
/// It is dropped anyway, and only for this reason: **the region over-covers.**
/// Taking everything measures 2.6× the movie's length, which nothing explains
/// yet. So the omission is a *bounded* choice, not junk removal, and the
/// manifest says so in those words — a consumer must not read a dropped chunk
/// here as a defect the exporter cleaned up.
/// **Reading 3, one stream. ✅ Decoded disc-wide by the Decoder**, by counting
/// stream starts inside every inter-descriptor span: **258 spans hold one
/// stream, 28 hold three, and nothing holds two or any other number.** The 95
/// movie-voice regions decompose 70 + 8 + 17. So a region is three presentations
/// of one take, and `359 = 84.55 + 137.32 + 137.32`. Summing a take with a
/// scaled copy of itself adds ~4 dB and colours it.
///
/// The selection rule is therefore stated in terms of what was measured — *keep
/// the longest duration and everything tying with it, minus anything digitally
/// silent* — and every dropped chunk is named with its length and peak.
/// 🟡 **Which presentation to keep is a recommendation, not a field.** The
/// selector is the **highest byte rate** among the equal-duration survivors, on
/// the Decoder's advice. Nothing on the disc says which one the game plays, and
/// on `ADV` this picks the **quieter** of the two — 8.3 dBFS against 0.0. It is
/// stated in the manifest with that consequence so the choice is reversible; a
/// capture of the intro with dialogue audible settles it.
///
/// ## Chunk 0 is dropped, and it is a DUPLICATE rather than a truncation
///
/// This comment first guessed it was `BGM_103`'s third sub-wave — a bank header
/// — and a census over all 95 regions showed that is a *different structure*:
/// 78 open with a 10 240 B bank header, 17 with a leading headerless stream at
/// the disc's own `1392 mod 2048` data offset, and **the chunk count
/// discriminates neither**. The byte-span test then found it is **this movie's
/// own dialogue, 17 of 17** — not an in-mission line, which was the standing
/// hypothesis.
///
/// Which raised the real question: is dropping it a truncation? **No.** Measured
/// here with a decoder the RE container does not have — sliding envelope
/// correlation, overhang allowed, normalised over the overlap: **r = 0.998**
/// (`ADV`) and **0.932** (`S00A`), against controls of 1.000 (self) and 0.289 (a
/// different movie), with both lags placing chunk 0 **flush against the end** of
/// the kept stream. Confirmed in the sample domain at 16.7 / 23.2 dB of
/// residual. It is the tail of the take, presented again.
///
/// So the selection rule is stated in terms of what was measured — *the longest
/// duration, ties broken by byte rate, minus anything digitally silent* — and
/// every dropped chunk is named in the manifest with its length and peak.
/// * **Mono**, with the fold chosen from the stream's own declared channel
/// count rather than by passing `-ac 1` and hoping. A voice track that is
/// already mono is passed through untouched.
@@ -559,9 +579,31 @@ pub fn export_voice<S: DiscSource + ?Sized>(
// A tie at 1 ms. The two stems agree to six decimals and the chunk that is
// not one of them misses by tens of seconds, so nothing sits near this
// bound: it separates the measured cases without being a tuned threshold.
let keep: Vec<usize> = (0..all.len())
// ONE STREAM, NOT A SUM -- and this is the third reading of these chunks, each
// one refuted by a measurement rather than by an argument.
//
// They were concatenated (359 s for a 137 s movie), then summed as HANDOFF
// Q10's two stems (refuted here: `S00A`'s second is silence, `ADV`'s is
// 0.60x the first). The Decoder then decoded the shape disc-wide -- counting
// stream starts inside every inter-descriptor span gives 258 spans with ONE
// stream and 28 with THREE, and nothing with two or any other number, so a
// region carries **three presentations of one take**, not a mix. Summing a
// take with a scaled copy of itself adds ~4 dB and colours it.
//
// The selector is the **highest byte rate** among the equal-duration
// survivors, on the Decoder's recommendation. 🟡 That is a recommendation and
// not a decoded field: no flag on the disc says which presentation the game
// plays, and on `ADV` it picks the quieter of the two (-8.3 dBFS against
// 0.0). Recorded in the manifest so the choice is visible and reversible.
let tied: Vec<usize> = (0..all.len())
.filter(|&i| !silent.contains(&i) && (longest - lengths[i]).abs() < 0.001)
.collect();
let keep: Vec<usize> = tied
.iter()
.copied()
.max_by_key(|&i| riffs[i].len())
.into_iter()
.collect();
let dropped: Vec<String> = (0..all.len())
.filter(|i| !keep.contains(i))
.map(|i| {
@@ -619,16 +661,9 @@ pub fn export_voice<S: DiscSource + ?Sized>(
argv.push("-i".into());
argv.push(s.display().to_string());
}
// SUMMED at 1/n, with the coefficient written out rather than left to
// `amix`'s `normalize=1` default, so it appears in the manifest's command
// line. Same reasoning as `export_bgm`: a default is a decision nobody made
// and it can move under an ffmpeg upgrade.
let filter = format!(
"{}amix=inputs={n}:normalize=0,volume={:.6}{fold}[a]",
(0..staged.len()).map(|i| format!("[{i}:a]")).collect::<String>(),
1.0 / staged.len() as f64,
n = staged.len(),
);
// One input, so no mix and no normalising coefficient: the stream reaches the
// Ogg at the level the disc has it, and the only filter is the mono fold.
let filter = format!("[0:a]anull{fold}[a]");
argv.push("-filter_complex".into());
argv.push(filter);
argv.push("-map".into());
@@ -665,31 +700,39 @@ pub fn export_voice<S: DiscSource + ?Sized>(
movie -> token -> sound id -> byte region [{start}, {end}) of the continuous \
voice stream. NOT matched by filename: RT01A's voice lives inside \
VOICE_ADV.slb, so the name is right for this movie by luck and wrong for \
others. Of {} region chunk(s), {} were SUMMED at 1/{} -- they are \
equal-duration and each spans the whole movie, which is HANDOFF Q10's decoded \
two-stem shape, so joining them end to end would play the dialogue twice.{} \
Folded to mono from the {} of {channels} declared channel(s) that carry \
signal -- averaging a silent channel in would cost 6.02 dB, and channel 2 of \
both voice streams IS silent.{against}",
live.len(),
others. Of {} region chunk(s), exactly ONE is kept -- not summed. A region \
carries THREE PRESENTATIONS OF ONE TAKE, decoded disc-wide by counting stream \
starts inside every inter-descriptor span: 258 spans hold one stream and 28 hold \
three, and nothing holds two. This exporter read these chunks wrongly twice \
before that landed -- first concatenating them (359 s for a 137 s movie), then \
summing them as HANDOFF Q10's two stems, which its own measurements refuted: \
S00A's second full-length chunk is DIGITAL SILENCE and ADV's is 0.60x the first \
with 26.8 dB of residual. Summing a take with a scaled copy of itself adds ~4 dB \
and colours it. 🟡 The kept stream is the HIGHEST BYTE RATE among the \
equal-duration survivors, which is a recommendation and NOT a decoded field: \
nothing on the disc says which presentation the game plays, and on ADV this \
picks the quieter of two.{} Folded to mono from the {} of {channels} declared \
channel(s) that carry signal -- channel 2 of both voice streams is digitally \
silent, and averaging it in cost 5.94 dB until this was measured rather than \
read off the declared count.{against}",
riffs.len(),
staged.len(),
staged.len(),
if dropped.is_empty() {
String::new()
} else {
format!(
" DROPPED, and NOT because it is spurious -- the leading chunk is DECODED \
to be this movie's OWN dialogue, 17 of 17 regions (the Decoder's \
" DROPPED, and NOT as junk -- the leading chunk is DECODED to be this \
movie's OWN dialogue, 17 of 17 regions (docs/re/structures/\
voice-region-leading-chunk.md; an earlier note here wrongly equated it \
with BGM_103's third sub-wave, which a disc-wide census showed is a \
different structure). It is dropped because the region OVER-COVERS: \
including everything measures 2.6x the movie's length. So this may be a \
TRUNCATION, it is an open decoding question, and a consumer must not read \
the omission as junk removal. See docs/port/BLOCKED.md: {}.",
different structure). This port then measured it to be the TAIL of the \
kept stream -- sliding envelope correlation r=0.998 (ADV) and 0.932 \
(S00A), the lag placing it flush against that stream's end, against \
controls of 1.000 self and 0.289 for a different movie -- so dropping it \
removes a DUPLICATE, not dialogue. Dropped: {}.",
dropped.join(", ")
)
}
},
live.len()
),
peak_dbfs: peak,
duration_s: dur,

View File

@@ -340,13 +340,18 @@ fn check_audio(root: &Path, m: &Value, errors: &mut Vec<String>) {
// the overshoot distribution across a corpus of cues, and if a cue
// ever trips this the right response is that measurement, not a
// looser bound.
// `voice` joins `bgm` on the strict side of this bound for the same
// reason: it is a sum of stems this exporter produced, not a single
// wave taken off the disc, so a peak at full scale is our arithmetic.
Some(p) if matches!(kind, "bgm" | "voice") && p >= 0.0 => errors.push(format!(
// `voice` was on the strict side of this bound while it was a SUM of a
// region's chunks. It no longer is: a region carries three
// presentations of one take, so the exporter keeps ONE stream and
// performs no arithmetic on it. That puts `voice` with `se` -- a
// single wave off the disc, mastered near full scale, whose lossy
// decode overshoots by a fraction of a dB. `ADV`'s louder
// presentation measures +0.0003 dBFS at source; refusing that would
// be refusing the disc's own mastering.
Some(p) if kind == "bgm" && p >= 0.0 => errors.push(format!(
"{file}: peak is {p:.1} dBFS -- a SUM we produced clips"
)),
Some(p) if !matches!(kind, "bgm" | "voice") && p > 1.0 => errors.push(format!(
Some(p) if kind != "bgm" && p > 1.0 => errors.push(format!(
"{file}: peak is {p:.1} dBFS -- too far over full scale to be decode overshoot"
)),
Some(_) => {}