port: the game decodes all three voice streams at once, and two baseline rows were comparing blank frames

TWO FINDINGS, one mine and one handed to me, and the second retires a premise I
built on twice.

THE P1 BASELINE HAD ROWS THAT PROVED NOTHING. `build_12` and `build_15` render
pure black in BOTH renderers -- mean 0, max 0 -- so the difference is zero and
`verify-screen` scored them `max 0  over3 0  OK`, the strongest verdict it has.
Two of sixteen rows were comparing nothing against nothing. Worse than a missing
test, because a missing test is visible in the count.

Cause isolated by a control, not by reading: `build_00`/`build_01` are the same
loading screen minus three elements and render fine (mean 1.913, max 214.5). The
dressed variants add `pgloading_eff00`, a 1280x720 primitive resting OPAQUE BLACK
at t=38 inside its own opening black hold, with no layer key so paint order puts
it last.

The rule I was about to write -- "rest.t before the last timed keyframe is the
pathology" -- was killed by running the census first: 152 of 212 elements in this
export have rest.t earlier than their last timed keyframe. It is the norm. What
is actually unusual is the CONTENT, and its reach is one: `pgloading_eff00` is
the only element in the export whose resting pose is a fully opaque full-frame
quad. One instance is not a rule, so the renderer is unchanged and the HARNESS is
fixed: a blank pair now reports BLANK -- both renderers drew nothing; this row
proves nothing. `status` is untouched, so an unrelated DIFFERS still fails.

THE VOICE EXPORT IS KNOWN INCOMPLETE. The Decoder booted Canary with
--xma_param_probe and the game decodes ALL THREE streams CONCURRENTLY, in three
XMA contexts whose byte sizes match the disc payloads exactly. So "three
presentations of one take, pick one" is refuted by the running game and the
question I had been arguing -- WHICH presentation -- has no answer.

This one no census could have caught. Every measurement was right: the streams
are equal-duration, one is silence, one is 0.60x another with the residual 26.8
dB down. The frame around them was wrong, and the file says ChannelMask 0x0002 on
all three. It took the running game -- which is the mission's own sentence
arriving in practice.

BEHAVIOUR HELD DELIBERATELY. An equal-gain 1/n sum of channel pairs is not a
downmix either -- MISSION section 6 pins an explicit matrix for exactly that
reason -- and summing cost S00A 6.02 dB when one stream was silence. Swapping one
guess for another on a message is what produced this entry twice. What changed is
that the wrongness is now LOUD, because this failure sounds like success: one
stream is clean audible dialogue. A top-level manifest warning per movie, the
console line, and the authored entry all say `1 of 3 streams`.

"They are 5.1" is recorded as the Decoder's HYPOTHESIS with its own
counter-evidence attached, and nothing builds on it. What settles it is asked: a
recording of the game's own output over ADV through the null sink, which turns
channel roles into a fit against an oracle.

Refutation attempt, survived: the Decoder's loading-screen variant map. Entries
0/1 carry 7 elements and 12/15 carry those seven plus baseeff, eff00 and loop5 --
exact in count and identity, and it is what made build_00 a control.

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:50:02 +00:00
parent c43d44f57e
commit 8fba7944d4
6 changed files with 276 additions and 71 deletions

View File

@@ -110,54 +110,66 @@
},
"voice": {
"_": [
"WHICH PRESENTATION of a cutscene's voice to export. CHOSEN, not measured and",
"not decoded -- delete this the day a capture says which one the game plays.",
"🔴 KNOWN WRONG, HELD DELIBERATELY. Which of a voice region's streams to",
"export. The premise this entry was built on has been REFUTED BY THE RUNNING",
"GAME and the entry is kept, escalated, rather than swapped for another guess.",
"",
"A voice region carries THREE PRESENTATIONS OF ONE TAKE (decoded disc-wide by",
"the Decoder: 258 inter-descriptor spans hold one stream, 28 hold three, none",
"holds two). The exporter drops the leading one, which this port measured to",
"be the tail of the others, and must then pick between the two full-length",
"ones. Nothing on the disc ranks them: wEncodeOptions, channel count and",
"channel mask are byte-identical across them.",
"The premise was: a region carries THREE PRESENTATIONS OF ONE TAKE, so the",
"exporter picks one. The Decoder booted with `--xma_param_probe=true` -- the",
"cvar that reports which sub-wave the game decodes -- and the game decodes",
"ALL THREE, CONCURRENTLY, in three separate XMA contexts, with byte sizes",
"matching the three disc payloads exactly (1294336 / 1118208 / 1171456",
"against RIFF size - 60 of 1294396 / 1118268 / 1171516).",
"",
"This lives here rather than in the exporter because MISSION section 3 puts a",
"value we chose in authored/ with a why. A selection rule compiled into Rust",
"reads like something the exporter derived, and nobody deletes what nobody",
"can see."
"SO THERE IS NO 'WHICH ONE' TO ANSWER. `presentation` below discards two of",
"three streams the game plays. It is not a preference between rules any more;",
"it is a known-incomplete export.",
"",
"WHY IT IS NOT CHANGED TODAY. Reverting to the 1/n sum is not obviously less",
"wrong: an equal-gain sum of channel pairs is not a downmix -- MISSION",
"section 6 makes exactly that point when it pins an explicit matrix for the",
"movies' 5.1 fold rather than letting ffmpeg default -- and the 6.02 dB the",
"sum cost S00A was a real defect. Swapping one guess for another on a message",
"is what produced this entry twice already.",
"",
"🟡 HYPOTHESIS, NOT A RESULT, and it is the Decoder's: three concurrent stereo",
"streams is six channels, and N stereo streams is how XMA carries",
"multichannel on the 360, so 5.1 would explain the differing byte rates, the",
"near-silent stream and why cues are 1-stream or 3-stream and never 2. AGAINST",
"IT: all three declare ChannelMask = 0x0002 identically, which is odd for",
"distinct channel roles. Do not build on it.",
"",
"WHAT SETTLES IT: a recording of the game's own output over the intro,",
"through the PulseAudio null sink (AUDIO-VERIFICATION section 3). Candidate",
"combinations of the three decoded streams can then be correlated against",
"what the game actually played. Asked 2026-08-29."
],
"presentation": "loudest",
"presentation_why": [
"`loudest` = the full-length stream whose peak is nearest full scale.",
"",
"IT USED TO BE `highest_rate`, on the Decoder's recommendation, and that",
"recommendation was WITHDRAWN as self-contradictory -- it read 'the",
"highest-rate, highest-gain one is chunk 1', and those two criteria select",
"different streams: ADV chunk 1 is 1118268 B at 0.0 dBFS, chunk 2 is",
"1171516 B at -8.3 dBFS.",
"🔴 READ THE BLOCK ABOVE FIRST. This selects one of three streams the game",
"decodes concurrently, so whatever it selects, two are missing. The",
"paragraphs below are the history of how the value was arrived at, kept",
"because the reasoning is what makes the error checkable -- NOT because the",
"choice is defensible on its own terms any more.",
"",
"WHY `loudest` AND NOT `highest_rate`: this is a PER-ASSET CONTENT choice,",
"and that is the whole of its justification. The disc masters its other audio",
"near full scale -- the SE cues decode to +0.18 dBFS -- and S00A's only",
"surviving full-length stream is its louder one at -4.2 dBFS, so `loudest`",
"makes the two cutscenes' dialogue sit at comparable levels instead of 4.4 dB",
"apart. Neither half is strong alone; together they are what there is.",
"It was `highest_rate`, on a recommendation withdrawn as self-contradictory:",
"'the highest-rate, highest-gain one is chunk 1' selects different streams --",
"ADV stream 2 is 1118268 B at 0.0 dBFS, stream 3 is 1171516 B at -8.3.",
"",
"⚠️ A STRUCTURAL ARGUMENT WAS OFFERED HERE AND IS WITHDRAWN. It said ADV",
"chunk 1 is mono-in-stereo (channel 2 digitally silent) while chunk 2 is",
"dual-mono (channels identical at -8.318574), therefore the extra bytes",
"encode a duplicated channel rather than fidelity, therefore the rate",
"argument collapses. The CHANNEL MEASUREMENT stands -- it is ADV's, and it is",
"this port's own. THE GENERALISATION DOES NOT. The Decoder tested it",
"disc-wide over the 28 three-stream cues: the stream-3/stream-2 size ratio",
"runs min 0.0778, median 1.2565, max 2.9163, sd 0.5057, with only 12 of 28",
"within 15% of 1.0, and declared rates scatter with them (S06A: 5661 against",
"16513 B/s). A 37x spread is not a duplicated channel.",
"A structural argument for `loudest` was offered and withdrawn too: ADV",
"stream 2 is mono-in-stereo and stream 3 is dual-mono, so the extra bytes",
"looked like a duplicated channel rather than fidelity. The CHANNEL",
"MEASUREMENT stands and now reads differently -- these are channel pairs, and",
"0.60x with the residual 26.8 dB down is what a correlated pair at a lower",
"level looks like. The GENERALISATION was refuted by census: the stream-3 /",
"stream-2 size ratio over the 28 three-stream cues runs 0.0778 to 2.9163.",
"",
"So `highest_rate` was not refuted as a rule; it was simply never argued for,",
"and neither was this. That is why the entry is CHOSEN and why it says so.",
"",
"STILL A CHOICE. One capture of the intro with dialogue audible settles it,",
"and it is the last unforced decision in the voice pipeline."
"⚠️ THE FAILURE MODE HERE IS THAT IT SOUNDS FINE. A single stream decodes to",
"clean audible dialogue, so nothing in the output reveals that two streams",
"are missing. That is why the manifest says it in words on every voice entry",
"rather than leaving it to this file."
]
}
}

View File

@@ -741,22 +741,21 @@ 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), 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. 🟡 WHICH of the equal-duration survivors is kept is a CHOICE, \
not a decoded field -- authored/audio.json voice.presentation = {:?}, with its \
why. Nothing on the disc ranks the presentations: wEncodeOptions, channel count \
and channel mask are byte-identical across them. One capture of the movie with \
dialogue audible deletes that entry.{} 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}",
others. 🔴 KNOWN INCOMPLETE: of {} region chunk(s) exactly ONE is exported, \
and the RUNNING GAME DECODES ALL THREE CONCURRENTLY -- measured with Canary's \
--xma_param_probe, three separate XMA contexts whose byte sizes match the three \
disc payloads exactly. So this file is MISSING TWO STREAMS, and because a single \
stream decodes to clean audible dialogue, nothing in the audio reveals that. The \
earlier reading -- three presentations of one take, pick one -- is REFUTED; the \
streams are believed to be channels, though 5.1 is a hypothesis and all three \
declare ChannelMask 0x0002 identically, which argues against distinct roles. \
Held rather than changed: an equal-gain 1/n sum of channel pairs is not a \
downmix either (MISSION section 6 pins an explicit matrix for exactly this \
reason), and summing cost S00A 6.02 dB when one stream was silence. Which stream \
is kept: authored/audio.json voice.presentation = {:?}. What settles it: a \
recording of the game's own output over the movie. See docs/port/BLOCKED.md.{} \
Folded to mono from the {} of {channels} declared channel(s) that carry signal.\
{against}",
riffs.len(),
match presentation {
Presentation::Loudest => "loudest",
@@ -766,15 +765,10 @@ pub fn export_voice<S: DiscSource + ?Sized>(
String::new()
} else {
format!(
" 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). 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: {}.",
" Also dropped, and this one IS understood: the leading chunk, which this \
port measured to be the TAIL of the kept stream (sliding envelope \
correlation r=0.998 ADV / 0.932 S00A, lag flush against that stream's \
end, controls 1.000 self and 0.289 for a different movie): {}.",
dropped.join(", ")
)
},

View File

@@ -441,12 +441,30 @@ fn run_export(disc: &Path, out: &Path, authored_dir: &Path) -> Result<()> {
let want = audio_cfg.as_ref().map(|c| c.voice).unwrap_or_default();
match audio::export_voice(&source, out, stem, *len, want)? {
Some(a) => {
// 🔴 A TOP-LEVEL WARNING, not just a `why` on the entry. The
// export is known to be missing audio the game plays, and
// the failure sounds like success: one stream decodes to
// clean dialogue, so nobody listening finds out.
if a.sub_waves > 1 {
warnings.push(format!(
"{}: KNOWN INCOMPLETE. This region holds {} streams and the RUNNING \
GAME DECODES ALL OF THEM CONCURRENTLY (Canary --xma_param_probe: \
three XMA contexts, byte sizes matching the disc payloads exactly). \
The export carries ONE. Nothing in the audio reveals this -- a \
single stream is clean audible dialogue. Held rather than summed \
because an equal-gain sum of channel pairs is not a downmix and \
would be a second guess, not a fix. See authored/audio.json voice \
and docs/port/BLOCKED.md.",
a.file, a.sub_waves
));
}
println!(
" voice {:<8} -> {} ({}, {} region chunk(s))",
" voice {:<8} -> {} ({}, 1 of {} streams{})",
a.name,
a.file,
describe(&a),
a.sub_waves
a.sub_waves,
if a.sub_waves > 1 { " -- KNOWN INCOMPLETE, see warnings" } else { "" }
);
audio.push(ManifestAudio::from(a));
}

View File

@@ -96,7 +96,7 @@ HANDOFF.
| Milestone | Needs | HANDOFF | State |
|---|---|---|---|
| P4/P7 — the intro's dialogue | ~~why the intro has no voices~~ | Q9 | ✅ **answered and TAKEN 2026-08-29, and the obvious diagnosis was wrong.** Not a transcode fault: `ADV.wmv` carries music and effects only, and a cutscene's voice is a *separate* continuous XMA stream in `sound.pak` bound by the movie manifest. `audio::export_voice` now resolves it with `media::resolve_movie_voice_region` — never by filename, because `RT01A`'s voice lives inside `VOICE_ADV.slb` and a name match is right on exactly the two movies this port would have spot-checked. Region chunks are **concatenated** (one continuous stream), not summed. This is **decoded, nothing authored**. |
| P4/P7 — the intro's dialogue | ~~why the intro has no voices~~ | Q9 | ✅ **answered and TAKEN 2026-08-29, and the obvious diagnosis was wrong.** Not a transcode fault: `ADV.wmv` carries music and effects only, and a cutscene's voice is a *separate* continuous XMA stream in `sound.pak` bound by the movie manifest. `audio::export_voice` now resolves it with `media::resolve_movie_voice_region` — never by filename, because `RT01A`'s voice lives inside `VOICE_ADV.slb` and a name match is right on exactly the two movies this port would have spot-checked. This is **decoded, nothing authored**. ⚠️ **This row's original text said the region's chunks are "concatenated (one continuous stream), not summed" — that was the first of three wrong readings and it is superseded**; see the incomplete-export row below for where it ended up. Left visible rather than silently rewritten, because the sequence of wrong readings is what makes the final one checkable. |
| P4/P7 — the movie downmix | **is the exporter allowed to ship a matrix MISSION §6 did not pin?** | — | 🔴 **with the HUMAN, not the Decoder, and now visible for the first time.** §6 pins the 5.1 fold as a human decision of 2026-08-29; `video.rs` has shipped that matrix scaled by **0.4142** since P4 — same weighting, **7.65 dB quieter** — and said so nowhere. Re-measured this iteration with the right instrument (float decode, whole file, count the samples that would clamp, not a peak reading): under the **pinned** matrix `ADV` peaks at **+4.26 dBFS** with **4 406** samples at or over full scale and 1 874 more than 1 dB over, while `S00A` peaks at 1.34 dBFS and **never clips**. So the pin overloads one movie and the exporter's constant is over-broad for the other. Smallest single scalar under which neither clamps: **0.612**, +3.39 dB on today. **Not changed** — the level of a mix is what §6 reserves. The export now carries a manifest warning with these numbers. |
| ~~P4/P7 — a voice region's chunks~~ | ~~what is the leading chunk, and is the second one played?~~ | — | ✅ **CLOSED 2026-08-29, decoded disc-wide, and it cost this exporter three wrong readings in one session.** A region carries **three presentations of one take** — the Decoder counted stream starts inside every inter-descriptor span: 258 spans hold one, 28 hold three, nothing holds two (`auto/no-disc-and-menu-captures` at `801062c`). So `359 = 84.55 + 137.32 + 137.32`. My concatenation was wrong, my "two stems" reading was wrong (and had already been adopted into the Decoder's page before I tested it — withdrawn in both), and summing was wrong a third time because a take plus a 0.60× copy of itself is ~4 dB louder and coloured. The exporter now keeps **one stream** and does no arithmetic on it. The leading chunk is this movie's own dialogue, and I measured it to be the **tail** of the kept stream (r=0.998 / 0.932, controls 1.000 and 0.289), so dropping it removes a duplicate. 🟡 **What is left open is which presentation to keep**: the selector is highest byte rate on the Decoder's recommendation, nothing on the disc says which the game plays, and on `ADV` it picks the quieter of two. Settled by a capture of the movie's dialogue level. ❔ Why the disc stores three at all is unanswered by either agent. |
| P4 — is an attract movie skippable at all? | **does the real game let Ⓐ end `ADV`, or does it play through?** | Q9 | 🔴 **a human play-test reports Ⓐ does not skip the port's intro, and the port could not tell which bug that is.** It is *implemented*, not assumed: `authored/flow.json` carries `skippable: true` with a `why` citing Q9 as measured (title at 57 s against a 193 s baseline), and `boot.gd` `_unhandled_input` acts on it. What did not exist was any way to **test** it: `--script` structurally cannot press during a movie, because `_script_settled` waits while `_player != null`. `--skip-at=SECONDS` was added this iteration to close that hole. ⚠️ Two different questions sit behind the one symptom, and only the first is mine: (a) does the synthetic press reach `_unhandled_input` — measurable here; (b) does the **game** permit skipping an attract movie — `INDEX.md` still marks skippability 🟡 and only a capture settles it. If (b) is no, the port's skip path is deleted rather than debugged. Asked 2026-08-29. |
@@ -114,7 +114,7 @@ HANDOFF.
| P6 BGM — the sub-wave count | **is a music bank's LEADING REGION a stem, or a decoder artefact?** | Q10 | 🔴 **HANDOFF and the decoders disagree, and P6 ships the disagreement.** `media::sound_bank_riffs("BGM_103.slb")` returns **three** sub-waves; HANDOFF Q10's census says a music bank is *"exactly two waves of identical duration (32/32 banks on the disc)"*. The third comes from `slb.rs:380` `to_xma_riffs`, whose hybrid branch emits a leading headerless packet region ahead of the `RIFF` waves — and `docs/re/REFUTED.md` already records that region as what makes `BGM_106``BGM_109` *"break the two-wave rule"*. Derived at HANDOFF `9ca1eb5`. **The exporter sums all three and writes a manifest warning**, because choosing which sub-wave to drop is a decoding question and MISSION §2 forbids this exporter answering one. So the menu currently plays a sum of three things where the census predicts two. What settles it: whether that leading region carries music. Raised with the Decoder 2026-08-29. |
| ~~P3 — the plate's ONSET~~ | ~~visible 2.13 s after settle, or group starts then?~~ | Q2 | ✅ **resolved 2026-08-29, and the answer is AUTHOR NOTHING.** The port's refutation held and produced a better answer than either option it offered. Correction at `5b0a6e6` on `auto/no-disc-and-menu-captures`: **both builds run on one clock, started together**, and the plate arrives at its own declared `t=238`. Checked against this export rather than taken on trust — build 4's visible build-in ends at `t=118` (`pteff01`, `pteff02`, `ptlogoall_eff` finish together), `ptbtn00` reaches alpha 255 at `t=238`, difference **120 units = 2.000 s**, against a measured 2.138 / 2.132 s at an emulator presenting 28.1 fps rather than 30. The 2.13 s constant is **deleted**. |
| ~~P3/P5 — `settle_time()`~~ | ~~`rest.t` is not when a screen settles, and the port's sequencer uses it~~ | — | ✅ **MEASURED 2026-08-29 and the row was HALF WRONG — mine.** The Decoder took it on a cold profile with no shader cache (`auto/no-disc-and-menu-captures` at `4bd4779`, `docs/re/boot-settle-times-measured.md`). The principle holds: the title's `rest.t` is 251 units = **4.183 s** where its art finishes at ~2 s. **But "everything the sequencer paces off that landmark is therefore late" does not.** Measured the port the way the game was measured — visible span, `--film` at 4 fps — the publisher wordmark runs **4.25 s** against the game's 4.297/4.604/4.370 and the developer logos **3.50 s** against 3.508/3.503/3.366. Dead on. My earlier reading compared the port's *arrival-to-arrival* timestamps against the game's *visible spans*, which differ by the exit ramp plus the black hold — the whole of the discrepancy I was about to chase. `rest.t` is still the wrong landmark; its blast radius is `_script_settled` waiting longer than it needs to, which is a slow test and not a wrong frame. `dwell_seconds` stays `null`, now for a measured reason. 🔴 **Do not author an Ⓐ→menu dwell**: it measures 3.763 s and contains a 1.53 s guest load stall, third independent reproduction. 🟡 Menu build-in 0.531 s and Ⓑ→title 0.482 s rest on one run and are not authored; the port is within ~0.1 s of both from the disc. |
| P4/P7 — which voice presentation | **which of a region's two full-length streams does the game play?** | — | 🟡 **the last unforced decision in the voice pipeline, and it is now unambiguously the port's.** The Decoder's "highest byte rate" was **withdrawn as self-contradictory** — its sentence read *"the highest-rate, highest-gain one is chunk 1"*, and those select different streams (`ADV` chunk 1: 1 118 268 B at 0.0 dBFS; chunk 2: 1 171 516 B at 8.3). Nothing on the disc ranks them: `wEncodeOptions`, channel count and channel mask are byte-identical. Moved to `authored/audio.json` `voice.presentation` per MISSION §3, set to **`loudest`** — a **per-asset content** choice and nothing more: the disc masters its other audio near full scale (the SE cues decode to +0.18 dBFS), and it puts the two cutscenes' dialogue at comparable levels instead of 4.4 dB apart. ⚠️ **A structural argument for it was offered and is withdrawn.** I said `ADV`'s higher-rate stream is dual-mono where the louder is mono-in-stereo, so its extra bytes encode a duplicated channel rather than fidelity. The **channel measurement stands** — it is `ADV`'s and it is mine — but the Decoder tested the *inference* disc-wide over the 28 three-stream cues and the stream-3/stream-2 size ratio runs **min 0.0778, median 1.2565, max 2.9163, sd 0.5057**, only 12 of 28 within 15 % of 1.0. A 37× spread is not a duplicated channel. So `highest_rate` was never *refuted*, it was merely never argued for — and neither is `loudest`. That is precisely why the entry is marked CHOSEN. What settles it: **one capture of the intro with dialogue audible.** That did not ride the settle-time boot, which drove the title path and never played the movie with audio. |
| P4/P7 — the voice export is INCOMPLETE | **what are the three concurrent streams, and how do they combine?** | — | 🔴 **the premise of every earlier row here was refuted by the RUNNING GAME, 2026-08-29.** Canary's `--xma_param_probe` shows the game decoding **all three streams concurrently** in three XMA contexts, byte sizes matching the disc payloads exactly (1 294 336 / 1 118 208 / 1 171 456 against 1 294 396 / 1 118 268 / 1 171 516). So they are **not** three presentations of one take, there is no "which one" to answer, and the export — which ships one — is **missing two streams the game plays**. ⚠️ **The failure sounds like success**: one stream is clean audible dialogue. Stated as a top-level manifest warning per movie, on the console, and in `authored/audio.json`. **Behaviour deliberately unchanged**: an equal-gain `1/n` sum of channel pairs is not a downmix either (MISSION §6 pins an explicit matrix for exactly this reason) and summing cost `S00A` 6.02 dB when one stream was silence — swapping one guess for another is what produced this row twice. 🟡 "They are 5.1" is the Decoder's **hypothesis**: three stereo streams is six channels and N stereo streams is how XMA carries multichannel on the 360, but all three declare `ChannelMask = 0x0002` identically, which argues against distinct roles. **What settles it: a recording of the game's own output over `ADV` through the PulseAudio null sink** — candidate combinations can then be correlated against what the game played. Asked 2026-08-29, emulator was up. |
| ~~P3/P5 — the title plate~~ | ~~does the idle title show `PRESS Ⓐ`~~ | Q2 | ✅ **answered and TAKEN at this iteration.** `auto/no-disc-and-menu-captures` at `fb536df`, `docs/re/title-plate-delay-measured.md`, traces in `docs/re/data/plate-timing-run{1,2}.tsv`. It is the third case: build 4 alone, then the plate composited over it. ⚠️ The delay is timed from where build 4 **stops animating**, not from where it first appears — measured the other way the two runs differ by 0.48 s against 6 ms. `ScreenView` now draws two builds at once, as a second `ScreenView` in the same `SubViewport` rather than a subordinate screen inside one. The onset question above is what is left. |
| P3 — the plate's PULSE | **does the plate's focus record loop, and with what period?** | Q2 | ❔ **open, and the port's earlier reading of it was wrong.** The port had looked for the pulse in `ptbtn00`'s own group; `5b0a6e6` identifies it as the plate's **focus record** `ptbtn00f` — a glow ramping alpha `0x00``0x50` and back, t=6…105. Measured on the running game at 2.12 / 2.19 / 2.34 / 2.31 s, mean **2.24 s**. 🟡 **The port has not taken it.** Looping that record needs a period, and its group is 105 timed units plus the **authored** 24-unit exit ramp = 129 units = 2.15 s — composing an authored constant with a loop assumption to land on a measured number is tuning, not measuring. Separately: the port draws no focus record on `press_start` at all, because the screen has no `buttons` and nothing is focused, so *whether the game always draws it* is its own question. |

View File

@@ -2971,3 +2971,162 @@ the same breath. The corpus catches it because someone else runs the census.
* **`BIRD_224` is three-stream and is not a movie cue** — so the three-stream
shape is not exclusive to cutscenes, which narrows how it was described to this
port earlier. Neither affects `export_voice`, which only resolves movies.
## Two rows of the P1 baseline were comparing blank frames and reporting OK
`docs/port/BLOCKED.md` has carried a 🔴 since P3: *"the loading screen's fade quad
rests OPAQUE BLACK … it will bite whoever first draws a loading screen."* It had
already bitten, in the one place nobody looks — the regression harness.
`build_12` and `build_15` render as **pure black in both renderers**: mean 0,
max 0, on the Godot side and on `sylpheed-cli`'s. The difference between two
blank frames is zero, so `verify-screen` scored them `max 0 over3 0 OK` — the
strongest verdict it has. **Two of sixteen rows were comparing nothing against
nothing**, and the committed baseline reads as sixteen passes.
That is worse than a missing test. A missing test is visible in the count.
### The cause, isolated by a control rather than by reading
`build_00` and `build_01` are the *plain* loading variant — the same screen minus
three elements. They render: **mean 1.913, max 214.5**. `build_12`/`build_15` add
`pgloading_baseeff`, `pgloading_loop5` and **`pgloading_eff00`**, a 1280×720
primitive whose `rest` is `0xff000000` — opaque black — at `t=38`, inside its own
opening black hold (`0xff000000` at 38, `0xff000000` at 48, clear on the untimed
final). It carries `layer_source: "none"`, so paint order puts it **last**, over
everything.
Same screen, one element different, one renders and one does not. That is the
diagnosis, and it did not require an opinion about `rest`.
### The rule I was about to write, and the census that killed it
The obvious reading is that `rest.t = 38` is wrong because it precedes the
element's last timed keyframe at 48 — so "`rest.t` before the last timed
keyframe" would flag the pathology. **I ran the census before writing the rule,
and it does not survive: 152 of 212 elements in this export have `rest.t` earlier
than their last timed keyframe.** It is the norm.
What actually distinguishes this element is its *content*, and the reach of that
is one:
| screen | full-frame primitive | rest.t | last | rest fade |
|---|---|---|---|---|
| `build_12` / `build_15` | `pgloading_eff00` | 38 | 48 | **`0xff000000`** |
| `extras` / `extras_jp` | `pteff00` | 64 | 74 | `0x00000000` |
| `main_menu` / `main_menu_jp` | `pteff00` | 70 | 80 | `0x00000000` |
| `title` / `title_jp` | `pteff00` | 261 | 269 | `0x00000000` |
| `title` / `title_jp` | `pteff02` | 46 | 236 | `0x40000000` |
**`pgloading_eff00` is the only element in the whole export whose resting pose is
a fully opaque full-frame quad — 1 of 212.** Every other full-frame primitive
rests clear or at 25 %. One instance is not a rule about `rest`, and keying the
renderer on "an opaque full-frame quad at rest is probably wrong" would be a
content heuristic of exactly the kind this port refuses elsewhere.
### So nothing in the renderer changed, and the harness did
The screens stay black. Nothing draws a loading screen, and the honest position
is that either `rest` is mis-identified for this one element — a decoding
question, asked — or the screen really does begin fully black and `--pose=rest`
is simply the wrong thing to photograph it at.
What changed is that **a blank pair can no longer score.** `verify-screen` now
checks both frames for ink first and reports
`BLANK -- both renderers drew nothing; this row proves nothing`. It is not a
failure — the port may legitimately have nothing to draw — and `status` is
untouched, so an unrelated `DIFFERS` still fails the run. The corrected baseline:
```
build_00/01 max 3 OK press_start(_jp) max 1 OK
title max 6 DIFFERS main_menu(_jp) max 4 DIFFERS
extras(_jp) max 3 OK title_jp max 155 DIFFERS
publisher_logo(_r) max 1 OK developer_logos(_r) max 2 OK
build_12 / build_15 BLANK ← previously OK
```
Fourteen rows, not sixteen. No new drift among the fourteen.
## Refutation attempt — the loading-screen variants, and it survived
The Decoder's `ui-title-build-map.md` says entries 0/1 are the plain loading
variant at 7 elements and 12/15 the dressed one at 10, the three additions being
`pgloading_eff00`, `pgloading_loop5` and `pgloading_baseeff`. Checked against this
export, which addresses by pak entry:
| entry | elements |
|---|---|
| 0, 1 | 7 — `eff01 eff02 line loop1 loop3 loop4 str` |
| 12, 15 | 10 — the same seven **plus** `baseeff`, `eff00`, `loop5` |
**Exact, in both the count and the identity of the three.** The claim survives,
and it paid for itself immediately: the two variants differing by exactly the
black quad is what made `build_00` a control for `build_12` and turned "the
loading screen is black" into "this one element blacks it out".
## 🔴 The voice export is known incomplete — the game decodes all three streams at once
The Decoder booted with Canary's `--xma_param_probe=true` — the cvar whose own
comment exists to say which sub-wave a movie's `.slb` the game decodes — and the
answer is that **it does not pick one. It decodes all three, concurrently, in
three separate XMA contexts.**
| ctx | packets | byte_size | disc payload (RIFF 60) |
|---|---|---|---|
| 0 | 632 | 1 294 336 | 1 294 396 |
| 1 | 546 | 1 118 208 | 1 118 268 |
| 2 | 572 | 1 171 456 | 1 171 516 |
Three-way, byte-exact. **So "three presentations of one take, pick one" is
refuted by the running game**, and the question I had been arguing about —
*which* presentation — has no answer, because its premise was wrong.
### This one was not caught by a census, and could not have been
The last three claims of mine that overreached were all killed by counting
something. This one survived every count available: the streams really are
equal-duration, one really is silence, one really is 0.60 × another with the
residual 26.8 dB down. Every measurement was right and the frame around them was
wrong, and **no amount of looking harder at the file would have moved it** — the
file says `ChannelMask = 0x0002` on all three. It took the running game.
That is the mission's own sentence arriving in practice: *the Port has no oracle
— if it needs to know what the game does, it asks.* I did ask, repeatedly, and
each time for the wrong thing: which stream, rather than whether the premise held.
### What changed, and what deliberately did not
**The behaviour is held.** Reverting to the `1/n` sum is not obviously less
wrong: an equal-gain sum of channel pairs is **not** a downmix — MISSION §6 makes
exactly that point when it pins an explicit matrix for the movies' 5.1 fold
rather than letting ffmpeg default — and the sum cost `S00A` 6.02 dB when one
stream was silence. Swapping one guess for another on the strength of a message
is what produced this entry twice already.
**What changed is that the wrongness is now loud.** ⚠️ *This failure sounds like
success*: a single stream decodes to clean, audible dialogue, so nothing a
listener hears reveals that two streams are missing. So it is stated in three
places a reader cannot miss — a top-level `manifest.json` warning per movie, the
console line (`1 of 3 streams -- KNOWN INCOMPLETE`), and the entry's own `why`
and 🟡 became 🔴 in `authored/audio.json` and `BLOCKED.md`.
🟡 **"They are 5.1" is the Decoder's hypothesis and is not established.** Three
concurrent stereo streams is six channels and N stereo streams is how XMA carries
multichannel on the 360, which would explain the differing byte rates, the
near-silent stream, and why cues are 1-stream or 3-stream and never 2. Against
it: all three declare `ChannelMask = 0x0002` identically, which is odd for
distinct channel roles. Nothing here builds on it.
**What settles it, and it is asked:** a recording of the game's own output over
`ADV`, through the PulseAudio null sink (`AUDIO-VERIFICATION` §3). Candidate
combinations of the three decoded streams can then be correlated against what the
game actually played — which turns the channel-role question from a decode into a
fit against an oracle. Twenty seconds over dialogue is enough.
### The measurements survive; only their meaning moved
`S00A`'s silent stream and `ADV`'s 0.60 × relationship are untouched and now read
as facts about **channels**: 0.60 × with the residual 26.8 dB down is what a
correlated channel pair at a lower level looks like, and a silent channel is an
unused one. Nothing measured here is retracted. What is retracted is every
sentence that called them *presentations*.

View File

@@ -133,11 +133,33 @@ print(json.load(open("export/"+f))["source"]["build"])' "$name")
-compose difference -composite -colorspace Gray -threshold $((3*65535/255)) \
-format "%[fx:int(mean*w*h)]" info:)
# 3/255 is what integer-truncating compositing in the CLI and float rounding
# in a GPU differ by. Anything above that is a placement, order or colour
# disagreement and needs a reason, not a threshold.
# BOTH FRAMES BLANK IS NOT AGREEMENT, AND THIS SCRIPT USED TO SAY IT WAS.
#
# `build_12` and `build_15` -- the two dressed loading screens -- render as
# pure black in BOTH renderers, mean 0 and max 0, so the difference is 0 and
# the row read `max 0 over3 0 OK`. Two of the sixteen rows in the committed
# baseline were comparing nothing against nothing and reporting the strongest
# verdict this script has.
#
# That is worse than a missing test: it is a test that reports a pass. The
# screens are black because `pgloading_eff00` is a full-frame opaque black
# quad whose `rest.t` (38) sits inside its own opening black hold, and
# `--pose=rest` freezes it there -- see docs/port/DECISIONS.md. Whether that
# is the port's bug or the decoders' reading of `rest` is open; what is not
# open is that a blank pair may not be scored.
#
# So blankness is checked FIRST and reported as its own verdict. It is not a
# failure -- the port may legitimately have nothing to draw -- but it is not a
# pass either, and `status` is left alone so an unrelated screen's DIFFERS is
# still what fails the run.
ink=$(convert "$OUT/$name.godot.png" "$OUT/$name.ref.png" \
-evaluate-sequence max -colorspace Gray -format "%[fx:maxima*255]" info:)
verdict=OK
awk "BEGIN{exit !($max > 3)}" && { verdict=DIFFERS; status=1; }
if awk "BEGIN{exit !($ink <= 0)}"; then
verdict="BLANK -- both renderers drew nothing; this row proves nothing"
else
awk "BEGIN{exit !($max > 3)}" && { verdict=DIFFERS; status=1; }
fi
printf '%-17s build %-3s max %-5s mean %-8s over3 %-7s %s\n' \
"$name" "$build" "$max" "${mean:0:6}" "$over" "$verdict"
done