port: fold only the channels that carry signal, and measure what the leading chunk actually is

TWO DEFECTS AND ONE MEASUREMENT, all from verifying the previous commit rather
than from reading it.

Channel 2 of both voice streams is DIGITALLY SILENT -- peak -inf over the whole
file. The voice is a mono recording carried in a nominally stereo stream, and
averaging it with silence cost 5.94 dB. The doc comment directly above the code
that did it warned that "a stereo matrix applied to a mono voice track is not an
error, it is a -6 dB attenuation that nothing reports", and then the code checked
the DECLARED channel count instead of the content. `live_channels` now measures
which channels carry signal and averages only those.

Three defects this iteration were the same shape: a silent chunk in a sum, a
silent channel in a fold, and a pan matrix naming channels that do not exist.
Each is an input contributing nothing while still counting in a divisor, and none
is visible in anything but a level.

THE LEADING CHUNK IS THE TAIL OF THE FULL-LENGTH ONE. The Decoder settled by
byte-span analysis that it is the movie's own dialogue, 17 of 17 -- killing its
own hypothesis that it was an in-mission line -- and asked whether dropping it is
a truncation, having no XMA1 decoder. Sliding envelope correlation with overhang
allowed and normalised over the overlap: ADV r=0.998 at +52.8 s, S00A r=0.932 at
+25.6 s, against controls of 1.000 (self) and 0.289 (a different movie). Both
lags put chunk 0 flush against the END of chunk 1. Sample domain, lag refined to
one sample then a scalar best-fit: residuals 16.70 dB and 23.15 dB below target.

So dropping it removes a DUPLICATE and the exporter was right for a worse reason
than it gave. The manifest note is NOT rewritten to claim that -- the structural
conclusion belongs on the Decoder's page, not in my manifest -- but it no longer
equates the chunk with BGM_103's third sub-wave, which a disc-wide census showed
is a different structure, and it now says in words that the omission must not be
read as junk removal.

Not converted, deliberately: the Decoder's 504464 B anchor constant. Bytes per
second is not constant even inside one region -- chunk 1 is 1118268 B and chunk 2
is 1171516 B for the SAME 137.324 s -- so any figure in seconds off it would be
invented.

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:07:30 +00:00
parent cb8d77febc
commit 4817e5ea9e
2 changed files with 182 additions and 24 deletions

View File

@@ -2656,3 +2656,81 @@ out on its own. But a duration tie is an *observation* and `bank_header_len` is
*decoded*, so the rule switches the day `c1f3608` reaches `main`.
`sylpheed-formats` is a path dependency and merging another agent's topic branch
is not the port's to do.
## The mono fold I warned about, in the comment directly above the code that did it
`export_voice`'s first version folded to mono by averaging every **declared**
channel, and the doc comment above it said, in as many words, that *"`pan`
silently ignores a channel the input does not have — so a stereo matrix applied
to a mono voice track is not an error, it is a −6 dB attenuation that nothing
reports."*
It then did exactly that. Per-channel `astats` on both voice streams:
| | channel 1 | channel 2 |
|---|---|---|
| `ADV` chunk 1 | peak +0.000 dBFS | **peak −inf** |
| `S00A` chunk 1 | peak −4.207 dBFS | **peak −inf** |
The voice is a **mono recording carried in a nominally stereo stream**, and
averaging it with silence cost **5.94 dB** — which is most of why `S00A`'s
exported dialogue sat at −16.2 dBFS against a source chunk peaking at −4.2 (the
other 6.02 dB was summing a silent *chunk*, corrected in the same iteration).
**Checking the declared channel count is not checking the content, and only the
content is the fold.** `live_channels` now measures which channels carry signal
and averages only those. `sylpheed-viewer`'s `pan=mono|c0=c0` reaches the right
answer here for a reason it does not state; this reaches it for a stated one, and
would still be right if a stream ever did carry two live channels.
Worth recording as a pattern rather than a bug: **three defects this iteration
were all the same shape** — a silent chunk in a sum, a silent channel in a fold,
and a `pan` matrix naming channels that do not exist. Each is an input that
contributes nothing being counted in a divisor, and none of them is visible in
anything but a level.
## The leading chunk is the TAIL of the full one — measured, and it is why the region over-covers
The Decoder settled by byte-span analysis that a voice region's leading chunk is
**the movie's own dialogue, 17 of 17** — killing its own standing hypothesis that
it was an in-mission `VOICE_D_*` line — and asked whether dropping it is
therefore a truncation. It has no XMA1 decoder; this container does.
Envelope cross-correlation, sliding with overhang allowed at both ends and
normalised over the overlap only. ⚠️ **This corrects an earlier number of mine**:
a first pass scored 0.768 and I called it nothing, correctly — that search only
tried lags where the shorter chunk fitted *wholly inside* the longer one, and it
peaked on the boundary of its own range.
| | 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 against itself | 1.000 | 0.0 s | — |
| control — `ADV` chunk 0 against `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 s against chunk 1's 137.324, and 25.6 + 68.07 = 93.67 against 93.694.
Confirmed in the sample domain — lag refined to ±1 sample on the loudest second,
then a scalar best-fit over the whole overlap: `ADV` +52.8000 s, gain 0.833,
residual **16.70 dB** below the target; `S00A` +25.6320 s, gain 0.365, residual
**23.15 dB**. 98–99.5 % of the energy is a scaled copy: the same material at a
different gain, not bit-identical, which is what a lossy decode at two gains
should look like.
**So dropping chunk 0 removes a duplicate, and is not a truncation** — the
exporter's existing behaviour is right for a better reason than the one it gave.
🟡 **The manifest note has NOT been rewritten to say so.** The structural claim —
that the region over-covers because it re-presents its own tail, and that this
accounts for the whole 2.6× — is the Decoder's to write down; this page reports
the measurement and says which is which. The note stays hedged until its page
carries the conclusion, and the hedge is true either way.
⚠️ **The 504 464 B constant was deliberately not converted.** The Decoder found
the region anchor sitting that far after the true predecessor trailer on all 17
and pointedly declined to call it missing dialogue. Converting it needs a
byte↔time mapping, and the numbers above are the reason there isn't one: chunk 1
is 1 118 268 B and chunk 2 is 1 171 516 B for **the same 137.324 s**, so bytes per
second is not constant even inside a single region. Any figure in seconds off
that constant would be invented.