Files
Sylpheed/docs/port/BLOCKED.md
MechaCat02 1494b094c7 port: record H3 -- the PRESS (A) plate arrives late, and which half is ours
Human play-test, 2026-09-01. Three candidate causes and they do not share an
owner: the unit->seconds constant is the Decoder's, the clock origin and rest.t
are ours. REFUTED.md records that believing rest.t already put a plate 3.97 s
late once. Establish which half before asking.
2026-09-01 17:59:17 +02:00

1182 lines
112 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Waiting on the RE agent
**This page is the port's standing ask list.** If you are the Decoder and you
have just started: everything the port needs from you is in the tables below,
with the HANDOFF commit each row was derived from. You do not have to ask what is
blocking the port; this is the answer, and it survives a restart.
⚠️ **It is not in your loop brief's read list.** `docs/agents/decoder-loop.md`
names PROTOCOL, `MISSION.md`, `HANDOFF.md`, `REFUTED.md`, `METHOD.md`,
`INDEX.md`, `docs/game/navigation.md` and `CONTAINER-NOTES.md` — not this file.
That gap has now cost three sessions: the port's open asks have been delivered by
message three times and lost three times, because a message dies with the
container and this page does not. Whether the brief should change is the human's
call, not either agent's. Until it does, read this page anyway.
What this port cannot do until an answer lands in HANDOFF.md. Recorded so it is
not re-discovered every iteration.
**None of these may be guessed.** A value invented here is indistinguishable from
a decoded one a month from now. Where a milestone can proceed with a placeholder,
the placeholder goes in `authored/` with a `why` naming the question it stands in
for, so it is deleted rather than forgotten when the answer arrives.
## Provenance of this page
🟢 **The oracle is live again (2026-08-29).** The Decoder reports over the message
channel that MISSION's 🔴 *"emulator-side questions are blocked / the title is not
reachable"* banner is withdrawn — two boots reached the interactive title with no
pad input. **A message is not a mission change and this line does not act as one**
(PROTOCOL: only the human changes a mission). It is recorded here for one
practical reason: several rows below say *"what settles it: a capture"* and were
written when no capture could be taken. Those are now askable.
Reconciled **2026-08-29** against [`docs/port/HANDOFF.md`](HANDOFF.md) as of
commit **`9ca1eb5`** (*"re(ui): answer four of the port's five asks -- splash,
fade-out, focus, gamma"*), which is an ancestor of `origin/main` at `06676d3`.
Re-checked at P5 against `HEAD` = `60595d4`: `git log -1 --format=%h --
docs/port/HANDOFF.md` still answers `9ca1eb5`, so HANDOFF itself has not moved.
**Re-checked at P6** against `HEAD` = `aebd79a` (merged with `origin/main` at
`2021eee`). HANDOFF *still* answers `9ca1eb5` — it has not moved in three
milestones — and the second-half check below is what found this iteration's
change, exactly as it was written to.
🔴 **HANDOFF has not moved, and that is now the problem.** The check above tests
whether *this page* is stale relative to HANDOFF. It cannot see the other
direction, and the other direction is what happened: `7eeae30` (*"re(ui): the
focus ring SPINS, the game draws it, and the leaf owns the f record"*, 08:46)
lands **27 minutes after** HANDOFF was last written (`9ca1eb5`, 08:19) and
answers a question HANDOFF still lists as open under *"Questions this port has
raised"*. Both are ancestors of `HEAD`.
So the staleness check needs a second half, and this is it:
```sh
git log --oneline 9ca1eb5..HEAD -- docs/re/ # RE landed since HANDOFF was written?
```
Anything it lists may already answer a row below. The Decoder has been told over
the message channel that HANDOFF needs `7eeae30` folded in; **rewriting HANDOFF
is not the port's to do.**
⚠️ **The address of HANDOFF.md changed and this page did not notice.** The
previous line here cited `/reborn` HEAD `9a0ca0d`. Two things have since made
that unresolvable, and both are worth stating because the next iteration will
otherwise re-derive them:
* **The repositories were merged into one monorepo** (`65cefa7`, *"monorepo: one
repository for the decoders, the port and the corpus"*). HANDOFF.md is no
longer in a separate `Syplheed-Reborn` repo reached over a mount — it is
`docs/port/HANDOFF.md` **in this repository**, and its provenance is an
ordinary commit sha in this history. A sha from the old repo cannot be looked
up here at all.
* **The `/reborn` mount is now an empty directory.** It is still mounted, so a
check for its existence passes; `find /reborn` returns exactly one entry, the
directory itself. Anything that reads `/reborn/docs/...` fails with `No such
file or directory`, not with a mount error. Do not read it. Read the in-repo
copy and cite its sha.
Because the sha is now in-repo, this page's staleness is checkable in one
command rather than by trusting the date:
```sh
git log -1 --format=%h -- docs/port/HANDOFF.md # newer than 9ca1eb5? re-reconcile
```
## New asks, 2026-09-01 — from a HUMAN PLAY-TEST on real hardware, port `HEAD` `6b713e8` + input fix
**The first play-test on a physical controller found four things.** Two were port
defects and are fixed; **two are oracle questions and are recorded here unguessed.**
⚠️ Read the fixed pair first, because the *reason* they survived so long is a
method finding that applies to the Decoder's harness as much as this one:
> **`--script` sends `InputEventAction`, which bypasses the input map.** So every
> check this port had asserted the code *below* the map and nothing about the map
> — which turned out to have **no joypad binding for `ui_accept` or `ui_cancel`
> at all** in Godot 4.7.2, while binding the d-pad *and* the left stick to
> `ui_up`/`ui_down`. Ⓐ and Ⓑ were dead on a real pad for the whole of P5 while
> the unattended walk passed every iteration. The same blind spot hid the second
> defect: an `InputEventAction` is not an analog axis, so nothing could observe
> that a held stick fires once per *jitter*. Now asserted by
> `tools/port/verify-input`, with a control.
| # | ask | why the port cannot answer it |
|---|---|---|
| **H1** | **Does a held direction REPEAT in the menus, and at what rate?** Initial delay and repeat interval, for the d-pad and for the left stick separately if they differ. | Behavioural. The port now does **one step per deflection**`authored`, and deliberately the conservative half: it cannot run away and it invents no rate. If the game repeats, a human will feel it as *"I have to flick it again"*. |
| **H2** | **The splash blur/fade-in does not match the game.** A human watching both says the game's is **more pronounced** than the port's. | Needs a capture of the publisher/developer splash across its fade, at a known frame cadence. The port's ramp comes from the declared keyframes; whether the game applies a **blur** on top — and whether what we read as a fade is one — is not answerable from the export. |
| **H3** | **The `PRESS Ⓐ` plate arrives LATE.** The port raises it at `t=236`, derived `238 118 = 120 units = 2.000 s`. | 🟡 **Half of this may be the port's, and that half must be settled here first.** Three candidate causes: the **unit→seconds constant** (Decoder's), the **clock origin** — do builds 2 and 4 really start together? — and **`rest.t`** (both ours). `REFUTED.md` records that believing `rest.t` already put a plate **3.97 s late** once. Establish which half it is *before* asking, and say how. |
**On H2, three things the port can say that narrow it**, none of which settle it:
* The port draws the splash from the declared keyframe alphas only. It applies
**no blur at all**, so "more pronounced in the game" is consistent with a
post-effect the export does not describe, with a different ramp shape, or with
both.
* 🔴 **The `rest()` question is open in both directions** — see
[`REFUTED.md`](../re/REFUTED.md)'s `rest()` pair after the 2026-09-01 R1
reclassification. The two splashes are the *only* screens that reach the
plateau-less fallback (title, main menu and `EXTRAS` reach it zero times), so
**H2 lands exactly where our resting-pose heuristic is least trustworthy.**
That is not a coincidence worth ignoring.
* The register also has *"the declared keyframe timeline reproduces the captured
splash"* now sitting at 🟡 `⟨our-reader⟩` rather than ❌, because the
record-layout fix re-times a group's final pose and the entry was never
re-derived under it. **H2 may already be half-answered by re-running that.**
**What would settle H2:** a capture of the developer splash across its build-in
at a known cadence, compared frame-by-frame against the port's ramp — and, if
they differ in shape rather than in extent, a draw capture naming what is
submitted per frame.
## Still open — these block work
🔴 **This table's rows carry no derivation sha, and that is why they rot.** The standing instruction is to record the HANDOFF commit each row was derived from; every *prose* section added since does, and **every row in this table and the next does not**. On 2026-08-30 an audit found three stale rows here — one of them contradicted by a struck row four lines below it, claiming the boot ends on a plateless title when it had drawn the plate for weeks. The undated rows are exactly the ones that went stale, which is as close to a controlled experiment as this page is going to get.
⚠️ Rows are **not** being back-dated: nobody knows when most were written, and inventing a sha would be worse than admitting there is none. New rows carry one. This table was last audited **2026-08-30** against a running port.
🔴 **"HANDOFF has not moved in four milestones" [refuted] — WITHDRAWN 2026-08-30,
and the missing qualifier carried the whole meaning.** HANDOFF has moved **96
times**. It has not moved *on `main`*, which is the copy this port opens. The
live document is 4 111 lines at `27938aa`; `main`'s is **926** at `9ca1eb5`, a
gap of +3 930/745, and several of those commits are addressed to the port by
name. Run `tools/port/blocked-provenance`: every row below derives from
`9ca1eb5`, so **the derivation sha this file demands cannot separate a fresh row
from a rotten one** — it is constant by construction. The rot is not that rows
are old; it is that the document they derive from is frozen while the thing it
copies moves. See `DECISIONS.md`.
**Re-checked at the voice export**, `HEAD` = `3a4c6ac` (merged with `origin/main`
at `1b1a4df`). The second-half check,
`git log --oneline 9ca1eb5..HEAD -- docs/re/`, lists four commits, of which
`3491d30` (*"the disc ships movies in TWO audio profiles, and 28 of them are
5.1"*) is the one this iteration used and `7eeae30` is still unfolded into
HANDOFF.
| Milestone | Needs | HANDOFF | State |
|---|---|---|---|
| ~~P1P7 — the paint-order tie-break~~ | ~~how many pixels can a wrong tie-break cost?~~ | Q3 | ✅ **MEASURED TO ZERO on every screen this port ships, 2026-08-29** (`docs/re/structures/ui-tie-break-cost-at-settle.md`). The old figure — 24 overlapping tied pairs — was a `rest()` count, and **10 of the title's 11 tied pairs are between the five transient flashes**, which are transparent on a settled screen. The only non-zero anywhere in `GP_TITLE` is **1 px at Δ1 on the Japanese title**. Not a knife-edge either: sweeping every keyframe time and midpoint, the live-pair count is flat across the whole settle window. ⚠️ Four loading bundles report zero with **no live control**, so those are a weaker zero than the other six. ❔ *Why* ties order as they do is still unknown — it now costs one pixel, on a screen out of scope. |
| 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) ANSWERED, (b) still open — corrected 2026-08-30.** The headline used to say *the port could not tell which bug that is*. It can and it did: `DECISIONS.md` records ***does* skip the intro in this build**, and every boot run since prints `video skipped at …`. 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. |
| ~~P5 — a real submenu cycle~~ | ~~is any submenu reachable without a new archive?~~ | Q2/Q4 | ✅ **already shipped at P5, and one premise of the ask is refuted by this repo.** `ptbtn05` (EXTRAS) → screen `extras` (entries 6/9), and `extras`' `on_cancel` returns to `main_menu` with focus restored — a full main-menu → submenu → back cycle, in `GP_TITLE`, live since P5. ⚠️ **Build 8 is not a submenu.** It is `main_menu_jp`, the Japanese five-button main menu; `authored/screen_names.json` records that an earlier reading called 8 a submenu and that HANDOFF Q2 **withdrew it** against a capture. That coordinates identical to build 5 mean a language twin rather than a second menu is exactly the inference the port is not allowed to make on layout similarity — in either direction. The other four main-menu items really are blocked: `GP_SAVE_LOAD`, `GP_OPTIONS`, `GP_MISSION_SELECT` and the `DIFFICULTY`/`TUTORIAL_MENU` builds are not in this archive. |
| Milestone | Needs | HANDOFF | State |
|---|---|---|---|
| ~~P6 audio~~ | ~~which cue fires on move / confirm / back~~ | Q8 | ✅ **answered 2026-08-28** — the RE agent retracted "cannot be extracted". The waves are located in `Static.slb` by playing them: **move `0x1ec0`** (8 192 B, 0.533 s), **confirm `0x5d6c0`** (12 288 B, 1.016 s), **back `0x0ec0`** (4 096 B, 0.344 s), and ⬅➡ play nothing. Move and back reproduce across two boots. 🟡 that the cursor's wave is the cue *named* `SE_UI_CURSOR` is still a name match, and Ⓐ's wave is not separated between `SE_UI_DECIDE` and `SE_UI_SUB_WIN_OPN`. P6 can now export real audio; the exporter has to grow an SE path. **✅ TAKEN at P6, 2026-08-29.** The three offsets now live in `authored/audio.json` `se.*`*not* in the exporter — because MISSION §3 puts a measured value in `authored/` and a measured offset compiled into a Rust `const` is a measurement wearing the costume of a decoded field. `sylpheed_formats::media::se_wave_riff` does the assembly. |
| ~~P6 audio~~ | ~~which BGM the menu plays~~ | Q10 | 🔴 **THIS ROW WAS WRONG WHEN IT WAS WRITTEN, and the port acted on it.** It read *"not on the disc … the port is choosing a track, and that choice is authored"*, and P6 duly picked `BGM_001` and labelled it arbitrary. **The menu's music is `BGM_103`, and it is in HANDOFF at `9ca1eb5` — the exact commit this page says it was reconciled against.** Not stale: misread. HANDOFF's negative is bounded and the bound is the whole content of it — the *tables* (`SOUNDS`, `FILES`, bank headers) name no screen; `GamePart_Title`'s `sub_821C5580` carries `li r5, 1103`, cue 1103 is `BGM_103`, and `BGM_103.slb`'s two declared waves (3 876 864 / 3 930 112 B) are byte-for-byte the two streams the XMA probe saw decoding at the main menu. HANDOFF's own sentence: *"The port does not have to choose a track."* **The lesson is not "re-read HANDOFF" — this page's own staleness check passed.** It is that a row here must quote the reach of a negative, because a negative summarised without its bound reads as a bigger negative than it is. |
| ~~P6 looping~~ | ~~where a menu loop restarts~~ | Q10 | ✅ **ANSWERED AND SHIPPED, and this row stood stale for days while the fix was live.** The loop is a **runtime** field — `loop_start`/`loop_end` in the XMA decoder context, set by `XMASetLoopData` — and for `BGM_103` it is **[9.44 s, 71.31 s], cycling every 61.87 s**. `authored/audio.json` has shipped `loop_start_s: 9.44` / `loop_end_s: 61.87` since, and its `loop_why` marks the dead sentence `[refuted]`. 🔴 **The correction reached the manifest and not this file**, which is the exact failure `audio.json`'s own `why` warns about — *a correction that does not reach the artifact a consumer reads has not been made*. Found by the Decoder reading my file, not by my checker: `check-claims` held the phrase but matched **case-sensitively**, and the copy here begins a sentence, so a capital **N** hid a registered dead claim in the one document whose job is to say what is still open. Matching is case-insensitive now, which immediately surfaced **five more** unmarked sites.
| ~~P5 focus marker~~ | ~~the focus ring's spin PERIOD, and whether it loops~~ | Q1 + *"groups hold"* | ✅ **answered 2026-08-29, and NOT ON `main` YET.** The Decoder pointed at it over the message channel and the pointer resolves: branch `auto/no-disc-and-menu-captures`, commit **`4fa3099`** (branch head `66e74d4`), file `docs/re/focus-ring-spin-measured.md`, frames under `docs/re/captures/focus-ring/`. **The ring spins continuously — period 2.177 s wall-clock, eight evenly spaced autocorrelation peaks over nine revolutions**, with no angle estimated anywhere (both angle estimators failed their own controls and were not used). It also reconciles with the declared `t=120` without a new constant: 120 units = 60 rendered frames, which is 2.00 s at a true 30 Hz and 2.082.17 s at the 27.628.8 fps this emulator runs, so the measurement sits at the top of the predicted band. 🟡 The Decoder is explicit that this is *consistency, not closure* — the guest frame rate was not measured in the same run. ⚠️ **Do not read `captures/focus-ring/ring-20s-mean-uniform.png` as a frame**: the spin averages to a uniform circle, which is the finding, not a headless ring. **The port has not implemented this yet** — it still draws 0°, which the same corpus says is a pose the game never shows. That is next iteration's work and it is no longer blocked. |
| ~~P3/P5 — the title screen~~ | ~~does the idle post-boot title show the `PRESS Ⓐ` plate?~~ | Q2 | ✅ **STALE — struck 2026-08-30, and it had been wrong for weeks.** Every factual claim in it is now false: the boot does **not** end on a plateless build 4, `press_start` is **not** unused, and the port **has** drawn two builds at once since the plate-delay work. Verified this iteration — `boot ends on title + press_start`, `overlay press_start … drew 1: ptbtn00`, plate region mean **95.70** against 33.6 for the bare title. 🔴 The row directly below it was already marked *answered and TAKEN* for the same question: two rows on one question, one struck and one live claiming the opposite, and the live one was the stale one. That is this page's own documented failure mode, caught by auditing it rather than by reading it. ⚠️ What remains open is a **different** question and has its own row: whether the plate *stays up* after its 8-unit window. |
| P5 — Ⓑ on the main menu | ~~is Ⓑ what returns to the title, or the idle timer?~~ **how long does Ⓑ take?** | Q5 | 🟡 **the ORDERING is answered, the latency is not — and this row's own argument is refuted, 2026-08-30.** It reasoned that *"the title self-returns after ~810 s idle, so one unrecorded observation cannot separate them"*. HANDOFF `27938aa` measures the main menu as **not self-returning for ≥ 60 s untouched**, and places the ~810 s idle on the **title**, not the menu — so the confound the row was built on does not exist. Ⓑ is delivered (Canary logs `vk=5801`) and is the only input in ≥ 100 s before the return: **the ordering is measured**. What stays open is only the latency, from a backlogged run. `authored/flow.json` already implements the ordering and is now under-claiming rather than over-claiming. Surfaced by `blocked-provenance` at score 10.6 against `9a10258`, unread for a day. |
| ~~P6 BGM — the sub-wave count~~ | ~~is a music bank's LEADING REGION a stem, or a decoder artefact?~~ | Q10 | ✅ **CLOSED 2026-08-29 — the census was right and the port was summing a bank header into the music.** Decoded and timed, sub-wave 0 of `BGM_103`, `BGM_102` and `BGM_001` is identical: **10 300 B → 0.009 s, peak inf**, i.e. digitally silent. 10 300 B is the 10 240-byte bank header (the Decoder's disc-wide census) plus a 60-byte RIFF wrapper. So it is not a stem, and `export_bgm` had been counting it in the divisor — putting every real stem at 1/3 instead of 1/2, **3.52 dB of attenuation on all menu music shipped since P6**. Dropping a *silent* input is arithmetic, not a decoding decision, so this closed on the port's side; measured after the fix, `main_menu.ogg` goes 7.69 → **4.20 dBFS**, +3.49 dB against 3.52 predicted. Corroborates the Decoder's `c1f3608` from the other direction. The export now reports 2 sub-waves and the manifest warning is gone. |
| ~~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 — the voice export | ~~what are the three concurrent streams~~ **what do streams 1 and 3 contribute?** | — | ✅ **RESOLVED 2026-08-30 — and the body below is superseded in three places, which striking the heading did not mark.** The port now ships **all** qualifying streams with **measured positional weights** (`ADV` 3 of 3 at 0.4142 / 0.2929 / 0.2929 for FL/FR, FC, BL/BR). 🔴 Three sentences below still assert in the present tense and are false: **(a)** *"the `1 of 3` warning stays"* — it is gone, the export is complete; **(b)** *"stream 1 … consistent with being stream 2's tail"* — refuted, it was a **start-truncated simultaneous stream**, the resolver having begun 238 packets inside it; **(c)** *"streams 2 and 3 are indistinguishable to this instrument"* — superseded, the assignment is settled by declared `byte_size`. The history is **kept** because the sequence of wrong readings is what makes the right one checkable, but a reader lands on the sentences, not on the heading. See `DECISIONS.md`. Original status follows. 🟡 **HALF ANSWERED BY THE ORACLE, 2026-08-29.** A faithful capture finally exists (`1788022539-4529c72ed7fb`, ALSA tee + `--gpu=null`, 0.35 % silence, passes `check-capture`, header verified here). Fitted with the calibrated correlator against its own controls (present +0.248 / absent +0.005): **the exported voice matches the game's CENTRE channel at r = 0.989, margin +0.305**, while the movie bed matches FL/FR/RL/RR and *not* FC. So the dialogue is in FC, the bed is in the corners, and **the port's exported file is the material the game plays there** — measured, where the header (`ChannelMask = 0x0002` on all three) could never have said it. ⚠️ Streams 2 and 3 are indistinguishable to this instrument, as expected from stream 3 being 0.60× stream 2 with 26.8 dB residual; stream 1 is undetectable in a 59.7 s window, consistent with being stream 2's tail. 🔴 **The `1 of 3` warning stays** — nothing here says what the other two contribute. Reach: 59.7 s of a 137 s movie, one run, no screen provenance (the `--gpu=null` route costs video; provenance is the XMA probe). **Superseded history, kept because the sequence of wrong readings is what makes this one checkable:** 🔴 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. 🔴 **NOT OBTAINABLE THIS SESSION — both capture routes are closed, and this is now the blocker.** Take 1 (`1788018994-16f9d19d90b8`) was corrupted by a **channel-map remap**: PulseAudio remapping between mismatched maps silently drops and duplicates, proved by a tone control (six tones in, `400 / 3200 / 200 / 800 / 800 / 200` out, two source channels gone). Take 2 (`1788019777-43f27c791bef`) is **starved** — verified here independently: **35.6 % of frames silent on all six channels, 10 482 alternating runs, median burst 13.5 ms / gap 3.9 ms, a 17.4 ms period at 57 Hz.** That destroys envelope correlation by construction, so the negative it produced said nothing about the game. ⚠️ **Withdrawn with it: the reading that the game may not play the `.wmv`'s WMA track** — neither supported nor refuted by a starved file, and nothing changed on account of it. **The monitor-sink route cannot be fixed by configuration**: it advances at wall-clock rate and substitutes silence, so every moment the emulator runs slow is a hole and deleting them warps the timebase non-uniformly. The route that works is an internal tap at `SDLAudioDriver::SubmitFrame`, needing a **Canary rebuild the Decoder has costed at a whole session** — the build root `build-canary` targets does not exist in that container, the warm tree is configured against the same missing path, ~700 MB free, with prior parallel builds OOM-killing the host. **That is the human's call, not an agent's.** `tools/port/check-capture` now catches both failure modes, so no future capture costs an analysis to discover it is unusable. |
| ~~P4/P7 — `S00A` as a second asset~~ | ~~does a structurally different movie also put dialogue in FC?~~ | — | 🔴 **NOT OBTAINABLE IN THIS CONTAINER — closed as a route finding, 2026-08-29.** The drive works end to end (main menu +0.999, `newgame-difficulty` +0.999, `newgame-selectdata-crash` +0.997, with the focus detector validated live against a known transition) and then **the guest throws at `PC: 0x82307128` ×349**; no `S00A` stream ever decodes. ⚠️ It also refines `title-crash-stl-tree.md` rather than confirming it: the mechanism survives but the container it names, `aab216c3`, is **complete here** — the throw is on `1b556564`, which holds one file plus a stray `.tmp`. **So the new-game path builds a different cache container, and the documented remedy does not transfer** — it restores a *previously complete* cache, and no complete `1b556564` has ever existed here. **Consequence for the port: the centre-channel result rests on `ADV` alone.** `S00A` was wanted precisely because its second stream is digital silence where `ADV`'s is a 0.60× copy. That corroboration is behind a crash outside menu-port scope and neither agent is chasing it. |
| ~~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 | ✅ **ANSWERED, and it had been answered for a day in a document this port was not reading.** HANDOFF `27938aa` (delivered at `07e93ce`): a nested record is its own RATC bundle and the header's **`+0x08` is the loop length**, so `ptbtn00f` ramps 0→80→0 over 105 units inside a **120**-unit cycle and rests dark for 15. Found by `tools/port/blocked-provenance`, which ranked that commit against this row at **14.0, the highest score in the file** — not by an experiment. ✅ **Refutation attempted and it survived**: their falsifier (`+0x08 < max t` must never occur) re-run on my own read of the disc gives **0 of 1 781**, and on the eight records this port animates, 7 exact and `ptbtn00f` the lone hold — their table cell for cell (`cargo run -p sylpheed-export --example record_loop_control`). ⚠️ The port never shipped 105: `authored/timing.json` already had 120 from a **wall-clock measurement**, so the disc and an emulator stopwatch agree while sharing no instrument. The exporter now derives it (`focus.loop_length_units`) and the authored value becomes the second witness. 🔴 **New ask below** — the field is in an example, not in the crate's API. |
| ~~P5 focus ring — implementation~~ | ~~the ring's spin period~~ | Q1 | ✅ **implemented 2026-08-29.** `ScreenView.spin_period_units` drives it: one turn per the element's own declared `t`, looping, from the screen clock. The period comes off the **disc**; what the RE agent supplied is that the turn repeats rather than stopping. Verified on the port's own render — the ring is bit-identical one period apart across the whole frame, differs by 3.6/255 inside its box at quarter-period steps, and conserves box luminance to **0.027 %** over eight phases, which is the same observable the RE agent used to separate rotation from a pulse. 🟡 **Direction is not measured** — the port turns 0°→+360°, which is the sign the disc declares, but the RE agent's angle estimators failed their controls and no signed angle was ever taken. 🟡 **Phase across a focus change is not measured** either: the port drives the ring off the screen clock, so it does not reset when focus moves. Settled by two frames straddling a focus change. |
| P7 / naming — the four unnamed builds | **which locale and variant is each of `GP_TITLE` entries 0, 1, 12, 15?** | — | 🟢 **found by the port, not blocking, and handed over.** All four are **loading screens**: every element in all four is named `pgloading_*` (`pgloading_processing.png`, `pgloading_circle1`, `pgloading_delta`, `pgloading_ring`), and `LOADING` is one of the three screen names the Decoder read out of `sub_821C6458`. They export today as `build_00`, `build_01`, `build_12`, `build_15`. Two variants: 0/1 carry 7 elements, 12/15 carry 10 (adding `pgloading_eff00`, `pgloading_loop5`, `pgloading_baseeff`). The archive's own pairing — adjacent for 2/3, `+3` for 4…9 and 10/13, 11/14 — suggests 0 is the twin of 1 and 12 the twin of 15, but **which member is which locale is an inference and the port has not named them on it**. Naming is cheap for the Decoder and a guess for the port. |
| P7 — what fills the 4.5 s before `S00A` | **is the LOADING screen what appears between the save slot and the new-game movie?** | Q4 + Q9 | ❔ **not observed, and the port has not assumed it.** Q9 measures `S00A.wmv` starting ~4.5 s after Ⓐ on the save slot. The run that would have shown what is on screen for those 4.5 s hit the documented `sub_823070B0` cache crash after `SELECT DATA`. `GP_TITLE` carries a loading screen (row above) and 4.5 s is about the right shape for one, and that is **exactly why it is written here and not in `flow.json`**. Settled by one run that reaches the movie without crashing. |
| ~~P3 — a second `rest.t` casualty~~ | ~~the loading screen's fade quad rests OPAQUE BLACK~~ | — | ✅ **resolved 2026-08-29 by the forced-backdrop rule, and this row carried a stale number.** The quad still rests opaque — that part was right — but it no longer *hides* anything: it is a layerless full-screen element opaque across a span containing all 9 others, so it is **forced to paint first** and the loading screen draws over it. At `--pose=rest`, `build_12`/`build_15` went from **mean 0, the whole screen black** to mean 1.95 with **59 530 non-black pixels**. ⚠️ Two corrections to what this row said. Its `rest.t` is **0, not 38** — stale since the keyframe-layout correction (`formats-pin-2026-08-29c`) timed every pose; the group is `0xff000000` at t=0 **and** t=38, then `0x00000000` at t=48, so the conclusion held while the number did not. And the general `rest.t` complaint is **not** resolved: an opaque element at its rest instant is still opaque, and this one stopped mattering only because it is a full-screen backdrop. **The row below still stands for anything that is not.**
| ~~P6 — runtime headroom~~ | ~~the Master bus clips~~ | — | 🟢 **withdrawn by the port, 2026-08-29 — it was my own overstatement.** Filed 🔴 twice on a peak reading of 0.0 dBFS. Measured properly: **43 samples at full scale in 5.9 s and 24 in 98.5 s, longest clamped run 0.25 ms** — the disc's own `confirm` cue touching the ceiling on a transient, possibly only in the recording's 16-bit conversion since Godot mixes in float. Not a defect, and nothing is changed: attenuating to buy headroom would be an unmeasured level decision of exactly the kind this port refused for the BGM loop point. **A peak reading is not a clipping measurement** — one sample at 0 dBFS and two seconds of square wave give the same number. |
| Modding — rule 4 | ~~base-and-overrides is unimplemented~~ | — | ✅ **implemented 2026-08-29, and it was not blocked on anybody.** `MODDING.md` calls it a constraint on the exporter *today*; nothing read `data/mods/` for eight milestones. `ExportTree.resolve` now shadows by path for every asset kind, each replacement is logged as it is read, and `.gitignore` excludes the directory's contents — a mod is usually an edited game asset, and that directory was the one place git would have taken one. ⚠️ The `export/` vs `data/base/` naming split between `PORT-MISSION.md` §3 and `MODDING.md` is **raised, not resolved**: only the human changes a mission. |
| ~~P1P7 — the keyframe record layout~~ | ~~adopt the corrected pose/time pairing~~ | — | ✅ **ADOPTED 2026-08-29 by pinning `formats-pin-2026-08-29c`.** This row was wrong twice: it said the change *"cannot be taken yet"* and that it *"reaches the port only when that branch lands on `main`"*. **It arrives when the tag is pinned**, which is what MISSION §2's tagging rule exists for. ⚠️ And the knob I tested first, `SYLPHEED_KF_TIME_SHIFT`, is a **retired partial fix** that left pose 0 untimed — the real correction is the tagged crate's default, with the old reading behind `SYLPHEED_KF_TIME_LEGACY=1`. **The blast radius was far smaller than this row predicted**: under the correction *every pose is timed* (866 keyframes, 0 untimed), so `pose_at`'s synthetic-exit branch became dead code rather than wrong code and nothing needed re-deriving. Oracle: `publisher_logo` 1.00 %→**0.75 %**, `developer_logos` 0.39 %→**0.33 %**, `extras`' differing region collapsing from 736×525 to **398×295 at the sweep position**. 🔴 Open cost: `sylpheed-cli` builds from the workspace crate, so `verify-screen` compares two decoder eras until the tag reaches `main`. Revert to the path dependency then. |
| ~~P7 / naming — the four unnamed builds~~ | ~~which locale and variant is each of entries 0, 1, 12, 15?~~ | — | ✅ **answered 2026-08-29** (`docs/re/ui-title-build-map.md`): all four are the loading screen, two variants — plain (7 elements) and dressed (10) — decoded from their own `pgloading_*` element names. ⚠️ **Not adopted as names yet, for two reasons the RE agent gave and one the port found.** Theirs: the executable names exactly two, and *which* bundle takes which name is 🟡 undecided, so `LOADING`/`LOADING2` must not go in an asset path; and locale is 🟡 — the English member of a pair is the one in the first half of `GP_TITLE.p00`, 8/8 structurally but only 3/3 where a capture can check, and the three pairs that matter are the three no capture can check. Mine: **the message gives the bundles as "0/1 and 10/11", which is the `is_build` ordinal, and `authored/screen_names.json` is keyed by PAK ENTRY** — in entry space 10 and 11 are `palogo_sqex` and `palogo_gamearts`, the splashes. See the refutation section in `DECISIONS.md`. |
## ✅ ANSWERED and TAKEN — the four EXTRAS elements, and a fifth. Port `HEAD` `49a6333`
`ptframe4`, `pteff21`, `pteff22`, `pteff23` **and `pteff10`** are additive on
EXTRAS, measured. They were in a draw all along: the vertex dump was capped at two
quads and the batch holds six, so four were dropped **with a well-formed log line**.
Adopted — EXTRAS whole-screen residual **1.97 → 0.63**, `ptframe4` 31.90 → 1.14.
See `DECISIONS.md` for the `pteff10` metric split and why I took it anyway.
## New ask, 2026-08-31 — a capture of the title plate at a NON-ZERO loop phase, port `HEAD` `9a8b43e`
**Every title-plate capture we hold is at the plate's blind phase**, so nothing in
this repository can tell whether the port now draws the plate's pulse correctly.
`ptbtn00f` — the plate's highlight, measured **additive** (entry 2 of
`docs/re/data/blend-bit-vs-oracle.txt`) — contributes **0 px at loop phase 0** and
22 00029 000 px at phases 20100. `verify-capture` poses `title_plate` at
`--loop-phase=0`, matching `live-title-press-a.png`. So the row agrees to 0.09 %
and is **blind to the pulse by construction**, and switching the element to its
measured blend moved 26 319 px at phase 20 while reporting exactly zero here.
📌 **The blend itself is not in question** — it is theirs and measured off the GPU.
What is unverifiable on my side is whether *my renderer* now reproduces it. One
capture of the title with the plate at a visibly mid-pulse instant closes it.
**NARROWED 2026-08-31, and the cheap alternative is no longer needed** — the
ramp was already in my own export. `ptbtn00f` declares eight keyframes on a
120-unit loop peaking at α **80** and holding it across **t = 35…50**, and the port
reproduces that ramp at **r = +0.9982** against the declared values. So *when* and
*how strongly* the port draws the highlight is verified without an oracle.
📌 **The ask is now one capture in a named window: t mod 120 ∈ [35, 50].** Only the
composite is open. The existing capture provably cannot answer it — at t=237 the
phase is 117 (α ≈ 0), and the harness independently pins phase 0 (α exactly 0);
both readings of the clock agree.
## New ask, 2026-08-31 — three, derived from today's completed blend delivery, port `HEAD` `49a6333`
**1. Is `pteff10` additive on the MAIN MENU too?** It is measured additive on
EXTRAS. The main menu has an unidentified additive draw of **819.2 × 720** — the
exact size `pteff10` is drawn at (409×144 at its resting 200 %×500 %), and the same
size as the draw now identified as `pteff10` on EXTRAS. **I have not adopted it**:
that identification would be mine, and their own corrected coverage table still
lists `pteff10` as uncovered on the main menu. The improved matcher against the
existing main-menu log should settle it at no capture cost.
**2. The sweep strips' vertex alpha ramp.** They are additive and their vertex
alpha ramps across the sweep, and the leaf group **does** run on the menu — which
refutes this port's scoping. The port has neither the blend nor the ramp on the
leaf path, so switching the loop on today makes the port more correct in behaviour
and visibly worse against the capture. **What is needed is the ramp**: what the
per-draw alpha is as a function of sweep position.
**3. Does `kind & 0x2` belong in the exporter?** Their focusable-flag result (0
violations in 15 493 entries, 24 paks) implies `ptbtn00` on the PRESS Ⓐ plate —
`0x73002` in my export — is focusable, and my exporter classifies it `unknown`
rather than `button`.
📌 **The consequence is concrete, not theoretical.** `export/screens/title/press_start.json`
carries **`"buttons": []`** — an empty button list — on a screen whose one element
**has a focus record**. The port is describing a screen with a focusable element as
having no buttons. And it is the same element the Decoder says the PRESS Ⓐ pulse is
made of: `ptbtn00f` additive over a non-additive `ptbtn00`. That is a **decode**, so consuming it is legitimate; I am
asking rather than taking because it changes an exporter classification that other
things read, and `0x3000` (817 elements disc-wide, button-shaped and *not*
focusable) is exactly the trap a looser rule would fall into.
**Four elements on EXTRAS are in no draw capture: `ptframe4`, `pteff21`, `pteff22`,
`pteff23`. What blend mode do they use?**
The measured table (`docs/re/data/ui-blend-mode-measured.txt`) named `pteff20` and
`ptframe3` additive on EXTRAS, and the port now draws those two that way. **The
result is exactly the shape you want and exactly why this ask matters:**
| EXTRAS element | before | after | signed after |
|---|---|---|---|
| **`ptframe3`** — measured, applied | 34.80 | **7.97** | **0.61** |
| **`ptframe4`** — *not measured, left alone* | 25.58 | **31.90** | 31.81 |
| `pteff21` / `22` / `23`*not measured* | 10.25 / 10.63 / 10.09 | 14.34 / 13.15 / 12.04 | all negative |
📌 **Where the blend is measured the element is now near-exact; where it is not,
it is the worst thing on the screen.** The four unmeasured ones also got *worse* in
absolute terms, which is consistent rather than alarming: their neighbours are now
correctly brighter, so an alpha-over deficit that scales with the background scales
up with it.
🔴 **I am not inferring them from the pattern.** `ptframe4` is the third frame on a
screen whose other frame is measured additive, and it is dark, and additive would
plainly help. That is precisely the argument I must not act on — the Decoder's own
warning with the table was to read it as **per-element facts**, because which field
selects the mode is still unknown, and a fourth frame added by pattern would be
indistinguishable from a measured one in a month.
⚠️ They appear in **no** captured draw on either screen, so this may need a
different pose rather than a re-read: they may simply not have been drawing in the
frames that were captured.
🔴 **And the measurement's own reach sentence does not say so.** It reads *"every
element on the two screens the port ships is in the table except the two above and
`pteff10`"*. Checked element by element, counting the summary table's prose rows as
coverage: that is **exactly right for `main_menu`** and misses **four** on EXTRAS —
these four. A reader of that page would take the coverage for complete but for one
unidentifiable quad. On EXTRAS a quarter of what the port draws is unmeasured, and
it is the quarter that is visibly wrong.
## ✅ ANSWERED SAME DAY, and the answer is a negative — HANDOFF `5a7f34d`, port `HEAD` `6af06bd`
**Asked: what blend mode do `ptframe1` and `ptframe2` use on the main menu?**
🔴 **There is none on the disc.** The Decoder read all 15 words of the 60-byte
`.t32` declaration: three are the name, eight are constant across every element,
the rest are kind, focus index, position and pivot. **Both frames are kind 0 —
identical to `ptbase`, `pteff05`, `pteff10`, `pteff12` and `ptmsg`.** Nothing
distinguishes them. They refuted their own single candidate (T8aD `+0x08` = 0x8050)
before sending: 38 sprites carry it disc-wide, only 8 named frame, and its high
byte tracks the archive — an atlas word.
[`docs/re/structures/t32-blend-mode-not-on-disc.md`]
📌 **So any blend the port picks is AUTHORED**, and my refusal to brighten these is
now evidenced rather than principled. ⚠️ Their stated reach: the negative covers
the *data*. **The executable's draw path is the route they have not taken**, and a
mode selected in code rather than data would live there — see the measurement in
`DECISIONS.md` arguing something must be there.
**(original ask below, kept because the measurement it carries is still current)**
**What blend mode do `ptframe1` and `ptframe2` use on the main menu?**
`crates/sylpheed-export/src/screen.rs` already records blend mode as undecoded —
*"assumes straight alpha-over. Blend mode is undecoded, and an additive quad at
alpha 255 would not occlude."* This turns that from a caveat into a **measured
cost**, at a specific place, with a number.
Measured by suppression — each sprite shadowed with a transparent PNG through the
mod tree, so the footprint is the pixels that actually changed and no coordinate
transform is assumed:
| element | footprint | signed mean, render capture | render vs capture | render-brighter |
|---|---|---|---|---|
| **`ptframe1`** | 0.45 % of frame | **22.72** | **88.4 vs 129.1** | **0.1 %** |
| **`ptframe2`** | 0.50 % | 12.31 | 83.6 vs 111.3 | 12.5 % |
| `pteff12` | 17.31 % | 4.53 | 45.6 vs 47.2 | 5.8 % |
📌 **The port draws both frames too dark, in one direction, on essentially every
pixel** — 99.9 % of `ptframe1`'s. And it is **not an edge effect**: `ptframe1`'s
residual is *higher on flat pixels* (25.41) than on edges (19.85), the only
elements on the screen where that is true. Body intensity, not outline.
Those two elements carry **21.9 % of the frame's total squared error from under
1 % of its pixels**, after a global tone LUT has already been applied — so this is
element-specific and survives every global correction.
⚠️ **I am not fixing this and the answer may not be guessed.** Brightening
`ptframe1` until it matches is exactly *"tuning until they match"*, and a blend
mode invented here is indistinguishable from a decoded one in a month. **What the
port needs is the blend/alpha mode bits for these two elements**, or a statement
that they are not on the disc.
⚠️ Reach: one screen, one pose (`--loop-phase=0 --leaf-time=0`, focus `ptbtn01`).
I have not checked whether other screens' frame elements do the same.
## Closed on my own tooling, 2026-08-31 — derived from HANDOFF `abeea3b`
| Milestone | Needs | HANDOFF | State |
|---|---|---|---|
| ~~all — checkers that pass on an empty input~~ | ~~liveness guards~~ | `abeea3b` | ✅ **SWEPT AND FIXED.** Every one of my tools reported clean when it examined **nothing**: `audit-kinds` exited **0** on a tree with no `authored/*.json`; `verify-transcode-fidelity` would call every transcode faithful with no videos in the manifest; `check-claims` exited **1** from a `FileNotFoundError` in the withdrawal hook — *"a refuted claim is still being asserted"* as the diagnosis for **a wrong directory**, a real failure with a fabricated reason and the third of that family. All three now exit **2**, and both self-tests assert the liveness case as subprocesses (`check-claims --control` is six cases). 📌 None of these tools was ever wrong on real input; what none could do was tell *"I checked and it was fine"* from *"I checked nothing"*. |
## Half-answered, 2026-08-31 — derived from HANDOFF (today's `DIFFICULTY` delivery)
| Milestone | Needs | HANDOFF | State |
|---|---|---|---|
| P5 — "resets to the named item" vs "resets to the top item" | **move the cursor in `DIFFICULTY`, leave, re-enter** | today | 🟡 **HALF ANSWERED, and the half that landed was in my own file.** *"A screen opens on its first item"* is **refuted**: `DIFFICULTY` is EASY/NORMAL/HARD/BACK and opens on **NORMAL, the second of four** — measured, no d-pad, unchanged 90 s, r=+0.999 against the committed capture. 🔴 `authored/flow.json` has recorded *"opening on NORMAL"* since `eef45ec` (2026-08-29) — **I wrote the counter-example I then spent iterations asking for**, and framed the ambiguity as conditional in the same file. ❔ Still open is the **reset** half, which needs the cursor moved inside `DIFFICULTY` and the screen re-entered; its forward path crashes the guest at `SELECT DATA`, so the run must go back rather than on. ⚠️ No authored value moves — `DIFFICULTY` is not a `GP_TITLE` build; `EXTRAS` keeps `ptbtn11`, correct under either reading. |
## Open on my own tooling, 2026-08-31 — derived from HANDOFF `d38adcf`
| Milestone | Needs | HANDOFF | State |
|---|---|---|---|
| ~~all — control harnesses that assert themselves~~ | ~~one tool still lacks it~~ | `d38adcf` | ✅ **COMPLETE 2026-08-31.** `verify-transcode-fidelity --selftest` closes the list: its three always-on controls never asked whether the measurement was **live**, and with an empty band list every comparison reads 0.0 dB — identity passes, the real pair passes, and only the unrelated-movie control fails, reporting **exit 1 (a corpus problem)** for a broken instrument. Now **exit 2**. All four tools — `contract-check`, `check-claims`, `audit-kinds`, `verify-transcode-fidelity` — assert their own harnesses, each verified two-directionally. Earlier text: 🟡 **DONE FOR `contract-check`, `check-claims` AND `audit-kinds`.** `audit-kinds --selftest` pushes three synthetic rows through the real classifier — citing nothing must read BARE, a real path ok, a missing path DANGLING — and returns **2** when stubbed to accept everything. `check-claims --control` gained a **fifth case**: the identical plant text *outside* the scanned root must give 0, so the boundary is asserted rather than hand-verified once. Remaining: `verify-transcode-fidelity`. Earlier text: 🟡 **DONE FOR `contract-check` AND `check-claims`, not for the rest.** `check-claims --control` now executes four cases as subprocesses — clean 0, unmarked revival 1, marked revival 0, **empty register 2** — where before it had **no control machinery at all** and an empty register reported clean forever. Verified two-directionally: pointing the plant at an unscanned path makes the control report itself broken. Remaining: `audit-kinds`, `verify-transcode-fidelity`. Earlier text: 🟡 **DONE FOR `contract-check`, NOT for the rest.** `--selftest` feeds the machinery a stub that cannot fail and requires it to be flagged; exit codes separate **0** all good / **1** a real check failed / **2** the harness is broken. Asserting in `check-all`. ⚠️ `check-claims`, `audit-kinds` and `verify-transcode-fidelity` have controls and **no harness self-test** — the shape is known and the fix is cheap, and this row exists so the gap does not read as finished. 🔴 The self-test caught two defects while being written: a first version that *argued* the harness would flag the stub instead of measuring it, and a `src` selection that anchored anything outside one list at the wrong document, flagging the stub for a fabricated reason. |
## Coverage hole in my own check, 2026-08-31 — derived from HANDOFF `0159527`
| Milestone | Needs | HANDOFF | State |
|---|---|---|---|
| ~~P4/P7 — band check sensitivity~~ | ~~my check does not cover `S00A`'s top end~~ **it does; the margin is thin** | `0159527` | ✅ **RETRACTED 2026-08-31 — the hole was my CONTROL'S FILTER.** `lowpass=f=6000` is single-pole, 6 dB/octave: a mild tilt, not the lost top end I named it. With a real 4-pole brick wall the loss is caught on **`ADV` at 6.52 dB (4.3×)** and on **`S00A` at 1.83 dB (1.2×)** — thin, not absent. The tool prints *⚠️ THIN — little HF in this material* below 2× margin. 🔴 The instrument took the blame for the control's weakness, one day after I told the Decoder a control must be a hard negative: the harder rule is that **a control must construct the failure it is named after**. Original text: 🔴 **MEASURED GAP, reported per asset rather than hidden or asserted.** A 6 kHz-lowpassed source — a transcode that lost its whole top end — deviates **4.27 dB on `ADV` (covered, 2.8×)** and **1.28 dB on `S00A`, under the 1.5 dB pass threshold**, because `S00A`'s own 616 kHz content sits at 67 dB. **So that failure would pass on `S00A`.** Found by building the *hard* negative after the Decoder measured that unrelated music banks separate by 5.28 dB where an unrelated movie gave me 1920 — a movie is an easy negative. Splitting the top band raised `ADV` from 2.58 to 4.27 dB; the pass threshold is unchanged. ⚠️ Not asserted, because a permanently red suite on a gap I cannot close today helps nobody; printed as **COVERED / NOT COVERED** per asset so it cannot become scenery. |
## Open on my own side, 2026-08-30 — derived from HANDOFF `0159527`
| Milestone | Needs | HANDOFF | State |
|---|---|---|---|
| P4/P7 — transcode fidelity | ~~nothing from anybody~~ **the WAVEFORM half is open WITH A DISQUALIFIED INSTRUMENT** | `0159527` | 🟡 **PARTLY ANSWERED — and the other half got worse, 2026-08-31.** The difference path is **disqualified, not inconclusive**: identity (source vs a second decode of itself) subtracts to **inf** so the pipeline is exact, but a **lossless flac of the identical fold reaches only 14.2 dB down** searched exhaustively at stride 1, where it must reach ~90. An instrument that cannot verify an encode known to preserve every sample says nothing about a lossy one, so every difference number in this thread was an artefact of the lag search. Ruled out and recorded so nobody re-runs them: **drift** (offset stable at ≈2465 across t=2/10/20 s), **container start time** (`start_time` is 0), **the codec being perceptual** (the lossless control fails identically), **level or content** (bands agree to 0.66 dB). Acceptance test now in the code: **lossless-vs-source ≥ 60 dB down before believing this path**. Earlier text: 🟡 **PARTLY ANSWERED, by changing the kind of quantity.** Band energies need no alignment, and both transcodes match their sources to **0.66 dB worst-case across four bands** while an unrelated movie lands at **1920 dB** — two populations an order of magnitude apart, so the 1.5 dB tolerance sits between measured values. Asserting in `check-all`, with the known negative on every run. ⚠️ **This is weaker than P4 wanted**: band agreement cannot distinguish a faithful transcode from one that kept the spectrum and mangled the waveform. The difference-signal half stays report-only and asserts nothing — and the band result now **proves** its failure is my alignment, since matching spectra mean same content at same level. Earlier text: 🔴 **THE GATE HAS NEVER CARRIED A FIDELITY CLAIM, and now it says so.** `AUDIO-VERIFICATION.md` §1 calls this the question P4 actually raised; nothing implemented it, and `verify-video-audio` explicitly declines it. `tools/port/verify-transcode-fidelity` exists and is **report-only**: best alignment is corr 0.763 on `S00A` and 0.075 on `ADV`, and both still report the difference *louder* than the source, which is impossible for two aligned signals at equal level — so the fault is on my side of the instrument, not necessarily in the transcodes. Four traps reproduced on the way, three of which §1 names and one (**`-ss` before `-i` returning 4.6 s for a 4.0 s request**) it does not. ⚠️ A tool printing "not faithful" in this state would put a **false defect on the exporter**. |
| P4/P7 — a `§1` addition for the HUMAN | **add the imprecise-seek trap to `AUDIO-VERIFICATION.md` §1?** | `0159527` | 🟡 **proposed, not done — and NARROWED 2026-08-30 before it was written up.** The Decoder reproduced it independently (4.597 s on `ADV`, 4.256 s on `S00A` for a 4.0 s request, correlations 0.03 and 0.34 at zero shift: different content, not a shift) and established that the **VIDEO** container-seek on this disc is **exact** — byte-identical frames at 20 s. So the trap belongs to the **audio stream**, not to `-ss` placement, and the phrasing matters in the direction that bites: a check looking only at video would clear a path still unsafe for audio. Original: 🟡 **proposed, not done.** §1 lists three ways the measurement lies; a fourth is now reproduced — a container-level seek returns a different span than requested, so the two windows cover different audio and no shift can align them (best corr 0.172). It is **indistinguishable from the alignment trap §1 already names**. §1 is the human's document, so this is a proposal rather than an edit. |
## Not blocking, recorded so nobody re-investigates — 2026-08-30, HANDOFF `91ada14`
| Milestone | Needs | HANDOFF | State |
|---|---|---|---|
| P4P7 — the `ObjectDB` leak line | **nothing; it is engine-side** | `91ada14` | 🟢 **NEGATIVE RESULT, and the obvious diagnosis is wrong.** Every run prints `N ObjectDB instances were leaked at exit`, and the objects are the Ogg streams and playbacks of exactly the cues that sounded — which reads as `MenuAudio` holding references past teardown. It does not. Releasing **every reference the port owns** (stop each player, null every `stream`, clear `_players`, clear `cues`/`beds`/`voices`) moved the count **not at all: 8 before, 8 after**, with a debug print confirming `_exit_tree` runs. The cleanup was **reverted** rather than kept, because code that changes nothing under a comment claiming to fix a leak is worse than none — the next reader sees it handled and stops looking. ⚠️ Cost of leaving it: it is log noise on every run, and the previous iteration found two real defects by reading that log. |
## Caveat on my own artifacts, 2026-08-30 — derived from HANDOFF `4ed75e6`
| Milestone | Needs | HANDOFF | State |
|---|---|---|---|
| P3/P4/P7 — my wall-clock seconds | **nothing from anybody; read the numbers correctly** | `4ed75e6` | 🔴 **CORRECTED THREE TIMES, 2026-08-30. Settled reading: playback runs +6.7 %…+6.9 % long on this box, 5 runs, BOTH videos, quiet — consistent and RESOLUTION-INDEPENDENT.** The `ADV`-versus-`S00A` contrast I reported (+6.7 % against 0.5 %) is **refuted**: the 0.5 % run was contended and the player dropped frames to hold schedule. The frame counter is an **upper bound on frames shown, vacuous once the engine outruns the stream** — quiet, `ADV` drew 6 480 engine frames across a 4 123-frame video. So *"the player skips heavily"* is **not supported** either; at 8.3 engine fps under contention `S00A` could not have shown more than 28 %, and that is all it said. Earlier text: **CORRECTED TWICE. The player SKIPS: `Engine.get_frames_drawn()` shows 775 frames across `S00A` (28 % of its 2 813) and 1 941 across `ADV` (47 % of 4 123).** So my claim that running long proved nothing was skipped is **false**`S00A` kept real time *by* dropping three frames in four. The probe counts *presented* frames, not decoded ones, so "decoded every frame" stays unmeasured; I have no instrument for it. ⚠️ And the deficit is a spread, not a constant: three `ADV` runs give **+6.5 %, +6.7 %, +2.4 %** — report it as **+2.4 %…+6.7 %, n=3, load-dependent**, not the +6.7 % I quoted twice. Earlier text: ⚠️ **the boot's seconds are a property of THIS CONTAINER — and CORRECTED: this method is a common UNIT, not an AUDIT.** Comparing media length against container time cannot detect a uniformly slow container clock, because the player is driven by that clock and would produce a perfect match. Three `S00A` replicates give 0.44/0.51/0.50 %, tight and unable to answer the question. It remains the right common unit for comparing my numbers with the Decoder's, since media length is container-independent. Original text: **the boot's seconds are a property of THIS CONTAINER.** `ADV` (1280×720) takes **146.6 s of wall clock for 137.44 s of media, +6.7 %**, while `S00A` (768×432) runs real time at 0.4 %. Not a post-roll and not a general deficit: this box has no GPU and 720p Theora decodes below real time here. The transcode is faithful (137.44 s against a 137.71 s source) and the exporter does not rescale — `S00A.wmv` is natively 768×432. 🔴 **P3/P7 artifacts quote wall-clock seconds that contain this deficit.** They reproduce here and are not a statement about the port or the game. Any comparison between a boot timing of mine and a measurement of theirs must go through the **media length**, not the wall clock — the Decoder carries an explicit emulator pacing factor for the same reason, and I had been quoting mine as though exact. Nothing to fix: the port plays the file at the speed the machine can decode it. |
## Reported to the DECODER, 2026-08-30 — derived from HANDOFF `12c9f04`
| Milestone | Needs | HANDOFF | State |
|---|---|---|---|
| ~~P0P7 — Q2's map of `GP_TITLE`~~ | ~~list entries 10, 11, 13 and 14 in the Q2 row~~ | `12c9f04` | ✅ **FIXED SAME DAY, and the row was worse than I reported.** All eight states are enumerated now and the per-entry names are committed as reference data, so the next reader checks rather than counts. ⚠️ My report said the splashes were *missing*; they were also **mis-paired as 10/11**, which is one half each of two different pairs — 10/13 is `palogo_sqex`, 11/14 is `gamearts`/`seta`/`anima`, matching my export's entry map exactly. Original text: 🟡 **the count is right and the enumeration is short; not blocking.** Q2 says *"`GP_TITLE` is 8 screens shipped twice, EN/JP"* and lists `2/3`, `4/7`, `5/8`, `6/9`, `0/1`, `12/15`**six states of the eight**. The two boot splashes, `publisher_logo` (10/13) and `developer_logos` (11/14), appear nowhere in it. ✅ The headline is confirmed by my export's entry map: 4 UI states + 2 loading variants + **2 splashes** = 8, shipped twice = the 16 entries the archive holds. 🔴 A reader counting Q2 gets twelve and has no slot for the splashes — **and this is the row already corrected once for an ordinal-versus-entry error**, which is the mistake four unlisted entries feed. The port is unaffected: both splashes are exported, named, and verified against captures at RMSE 2.17 and 3.05. |
## Ask for the HUMAN, 2026-08-30 — derived from HANDOFF `27938aa`
| Milestone | Needs | HANDOFF | State |
|---|---|---|---|
| P0P7 — the contract itself, **and P5's gate behind it** | **land HANDOFF on `main`**~~or tell the port to read the branch~~ | `27938aa` | 📌 **QUANTIFIED 2026-09-01, both directions.** This port's branch is **256 commits ahead of `origin/main` and 0 behind**, so merging it is a **fast-forward**`main` is an ancestor and there is nothing to resolve; 58 files. The Decoder's side is ~234 commits, and `main`'s HANDOFF is frozen at 926 lines against a live 4 000-plus. **Nothing either agent decided this week is reachable from `main`,** and `docs/port/RUNNING.md` §6 now states what a person is actually being asked to do for P5. Earlier text: 🟡 **NARROWED 2026-08-31: this is TWO gaps and only one needs a human.** What a peer *holds* is readable now — `git show <ref>:<path>`, any topic branch, refs already fetched — and `contract-check` has been doing exactly that, which is why my checks were current while my working tree sat 115 commits behind. What a peer must be *TOLD* still needs the merge. I had filed both as blocked on a human; half never was. `tools/port/peer-head` makes the readable half cost one command. **The merge is still the ask**, for the telling half. Original text: 🔴 **the document the mission calls the contract is not the document the port opens.** `main`'s copy is **926 lines** frozen at `9ca1eb5`; the live one is **4 111** at `27938aa`, **99 commits** unread, **70 sections** this port has never opened — several titled *"deliver … to the page the port reads"*. Only a human merges a topic branch (PROTOCOL), so the port cannot fix this and will not merge another agent's branch into its own. **Mitigated, not solved:** `tools/port/contract-check` now reads the newest HANDOFF on any ref and reconciles seven of its numbers against `export/` and `authored/` — all seven agree — and `check-all` asserts it plus its known-negative control. That is seven values out of 4 111 lines. ⚠️ The rest is still read by eye, and two consecutive iterations have found instructions addressed to the port sitting unread for a day. |
## New ask, 2026-08-30 — derived from HANDOFF `27938aa`, at port `HEAD` `f33aeca`
| Milestone | Needs | HANDOFF | State |
|---|---|---|---|
| ~~P3/P5 — the record loop length~~ **now: a tag** | ~~expose `+0x08` in the API~~ **cut a `formats-pin-*` tag carrying `b5df02a`** | `27938aa` | ✅ **ANSWERED IN CODE 2026-08-30, and still not consumable.** `ui_layout::loop_length_units` is public at `b5df02a` and is byte-for-byte what `screen.rs` holds — same guard, same offset, same BE read — so the deletion is one line. ⚠️ But `Cargo.toml` pins a **tag**, and no tag carries that commit; moving to a bare `rev` on an unmerged branch swaps a deliberate pin for an incidental one, and this pin is recorded load-bearing. Keeping the guarded local read until a tag exists. Original text: 🔴 **the port could not obey the instruction with anything published.** HANDOFF says *"stop shipping 105"*, which presumes the port can read the loop length. It is decoded in `examples/record_loop_length.rs`, asserted in `tests/ui_record_loop_length_disc.rs`, written up in `docs/re/structures/ui-record-loop-length.md` — and exposed in the crate's API **on no ref at all** (checked against every ref touching `crates/sylpheed-formats/src/`). `screen.rs` reads the four bytes itself, guarded on the `RATC` magic, because `parse_build` publishes each record's `(offset, size)`. That works and it is **the port holding a format detail it should not own**: one `pub` field on the record type takes it back where it belongs, and the exporter's helper is documented to be deleted the day it appears. Not blocking — the value is shipping. |
| P3/P5 — the other focus records | **do `ptbtn01f…05f` and `ptbtn11f…13f` animate while focused?** | `27938aa` | ❔ **open, and deliberately not inferred.** The export shows all eleven declaring the same 120-unit cycle, and `looping_focus_records` names only the plate. A declared cycle is not evidence that the game runs it — `authored/timing.json` already argues the pulse rule matches 82 of 212 elements and would make the copyright notice pulse. Whether a focused menu button glows is **behavioural**: outside my role, asking. |
## Answered since this file was last written — no longer blocking
Q1 (keyframe time unit — linear ramp, 2 units per rendered frame, 1 unit = 1/60 s
*measured*), Q2 (which build is which screen), Q3 (paint order — a `u16` layer key
at `+0x0A`, **decoded**), Q5 (navigation: ⬆⬇ wrap, ⬅➡ nothing, Ⓑ up with focus
restored), Q7 (transitions: a fade through black, fade-in decoded, ~0.4 s fade-out
measured), Q9 (`ADVERTISE_MOVIE``ADV.wmv` is boot intro *and* attract; `MS00A`
`S00A.wmv` is the new-game intro), Q10 (a bank is two stems played **together**
do not concatenate), S1 (Ready Room: no-go).
**Cleared at `9ca1eb5`, and previously listed above as blocking:**
| Was blocking | HANDOFF | What the answer is |
|---|---|---|
| P5 — what Ⓐ on `NEW GAME` opens | Q4 | 🔴 **the old row was wrong, not merely stale.** It said "❔ untested: Ⓐ on it **hangs the emulator**". Q4 now reads **measured** for all **5** buttons: `NEW GAME``DIFFICULTY``SELECT DATA`, and the row says explicitly *"not a hang"*. ⚠️ The **GamePart id** behind those names is still a name match, so `flow.json` cites the destination as measured and the id as a name match. P5 is not blocked here. |
| P4/P7 — whether Ⓐ skips a movie | Q9 | ✅ **one Ⓐ skips a movie** — title reached at 57 s against a 193 s baseline. P4 already took this (`DECISIONS.md`, *"Ⓐ skips, because Q9 measured it"*); the row survived here only because nobody deleted it. |
| P3 — what drives the boot sequence | Q6 | ✅ answered, and the answer is a **negative with a stated reach**: the driver is **code, not data**, with four search spaces closed. That is not "unsettled" — it is the RE agent saying the port must author the sequence, which P3 did. Filed as answered so it stops reading like an open question. |
Also newly available, and useful to P3/P5 when they author the flow: the title
part's transitions are a **lookup by name**, and the game's own screen
vocabulary includes `TITLE_SCREEN`, `TITLE_MENU`, `LOADING`, `DIFFICULTY`,
`EXTRA_MENU`, `TUTORIAL_MENU`. Three of those are corroborated by measurements
taken before the function was opened (`DIFFICULTY` is what `NEW GAME` opens,
`EXTRA_MENU` is `EXTRAS`, `TUTORIAL_MENU` the lesson list). 🟡 **Candidate, not
decoded** — the RE agent is explicit that the strings are what the call sites
*reference*, not proven arguments, and the same list mixes in `TEXT_FONT` and
`GAMMA_RGB`. So `authored/flow.json` may use these as `goto` names — which is
better than inventing names — but must mark them as a name match, not a
measurement.
Three of those are **measured**, not decoded, and so are authored here rather
than exported:
| Authored because it is not on the disc | HANDOFF | Where it lives |
|---|---|---|
| `1 keyframe unit = 1/60 s` | Q1 | not yet written — P2 |
| initial menu focus (not stable across boots; pick one and say so) | Q5 | `authored/flow.json`, `screens.main_menu.initial_focus` — landed at P5 |
| the ~0.4 s fade-out and the 0.170.23 s black hold | Q7 | not yet written — P3 |
## The five asks — four answered at `9ca1eb5`
Sent to the RE agent 2026-08-29 and answered the same day. Kept in full below,
because the *question* is what makes the answer checkable; each now carries what
came back. **Only ask 4 is still open, and it is with the human, not the RE
agent.**
| Ask | For | State at `9ca1eb5` |
|---|---|---|
| 1 — how to recognise the splash | P3 | ✅ answered, and the answer is **"no content rule exists"** — design size and element count both fail. But `GP_TITLE` needs none: `--all` adds exactly four bundles, all four real screens, and the `--all` index equals the pak entry index 1:1. 🔴 **It also found a screen the port did not have**: entries 10/13 are the **SQUARE ENIX** publisher wordmark, the *first* thing the boot shows. |
| 2 — is the 0.4 s fade-out the whole ramp | P3 | ✅ **(a)** — one authored constant (~0.4 s / ~24 units), and **play the group to its end on every element**. (c) was refuted by a null test: a black quad alone holds the button÷background ratio constant, and the capture falls 6.50 → 1.94. |
| 3 — focus drawn OVER the base, or INSTEAD of it | P5 | ✅ **the port's choice is fine and was not the bug.** The focused sprite covers the base at 100.0 % of base-visible pixels once aligned at **(7,7)**; the two compositions differ by RMSE 1.1 inside the button rect. 🔴 **The real miss is the focus record's SECOND element**`ptbtneff01.t32`, a 42×46 glowing ring. That is the ring marker. **This is what P5 builds on; see the refutation below.** |
| 4 — should the port draw rotation | P2/P3 | 🟡 **open, and with the human** — the RE agent declined to decide it alone. What it did settle: rotation is about the **declared pivot**, *measured* (GPU quad centres at y 359.1/360.0 against the pivot formula's 360.0; top-left predicts 810/990). ⚠️ It changes nothing on the five screens **at rest**. |
| 5 — is the oracle capture gamma-correct | all | ✅ **not gamma-neutral: RMSE against it has a floor.** `capture ≈ 255·(render/255)^γ`, γ**1.49** (main menu, `EXTRAS`), **1.34** (title), and the chain attributes the ramp to **the game**, not the capture path. ⚠️ **Reach: measured only on dark flat patches (render ~060)** — nothing constrains midtones or highlights. **Do not chase RMSE below the floor.** |
### Ask 4 is the only one that needs anything from anybody
It is a *joint* decision, not an RE question, and the port has said it will carry
`rotation_deg` in the format either way. Nothing in P5 touches it.
## The original five asks, as sent
Ordered by what it costs the port, not by what it costs to answer. Preserved
verbatim; see the table above for what came back.
### ~~1. How should the exporter recognise the developer-logo splash?~~ — ANSWERED
**Answered in HANDOFF `9ca1eb5`: there is no content rule; take the entry index.**
That is the answer this row said would be usable. ✅ The exporter addresses by
entry index and `publisher_logo` (10/13) is exported. Kept for the record:
The splash is the **first thing P3 draws** and it is not in `export/`. It
declares its sprites directly and has no `.rat` layout child, so `is_build`
rejects it; `sylpheed-cli` reaches it only via `--all`, which the CLI's own help
says **renumbers `--build`**. So the port cannot address it by build index
without the index meaning something different from everywhere else in this
format.
What I need is a **predicate**, not an index: something the exporter can apply to
say "this bundle is a composable screen" that admits the splash and does not
admit the 1 894 two-element fragments `--all` also lets in. If the honest answer
is "there is no such rule, take `GP_TITLE` entries 11/14", that is a usable
answer — I will export it under a synthetic name with `name_source` saying it was
located by entry index and not by a rule.
### ~~2. Is the ~0.4 s fade-out the whole ramp, or a segment of it?~~ — ANSWERED **(a)**, but see below
**Answered in HANDOFF `9ca1eb5`: (a).** ⚠️ Its prescribed action — author a
~24-unit constant — is **stale**: the corrected record layout leaves no untimed
keyframe, and the file's own ramp is **10 units**, so authoring 24 would run the
fade 2.4x too long. See DECISIONS.md. Kept for the record:
Q7 measures the screen fade-out at ~0.4 s and the black hold at 0.170.23 s.
The port needs to know **which quantity that 0.4 s is**, because the last
keyframe of a group carries no `t` and the port refuses to invent one:
* the ramp from the hold to the exit pose — i.e. the missing duration of that
final untimed keyframe; or
* hold → exit → fully black, the 0.4 s covering several keyframes; or
* something the game does independently of the group.
Under the first reading the port writes one authored constant and plays the
group to its end. Under the third it must not.
### ~~3. Focus: drawn OVER the base element, or INSTEAD of it?~~ — ANSWERED
**Answered in HANDOFF `9ca1eb5`: the choice was fine and not the bug.** The miss was
the focus record's SECOND element, `ptbtneff01.t32` — the glowing ring. ✅ Now
exported and drawn. Kept for the record:
`sylpheed-cli --focus` is documented as drawing the focused record **over** its
base. The port **replaces** the sprite. Those are different operations and the
port picked its one without evidence.
Evidence that the port is wrong: rendering `main_menu` with `ptbtn01` focused —
which is how `main-menu-oracle.png` was taken — makes the RMSE against that
capture **worse**, 5.92 % → 7.00 %. The capture also shows a **ring marker**
beside `NEW GAME` that the port draws nowhere. Cheap to answer from a capture
that already exists, and it decides how P5 is built.
### 4. Rotation — should the port draw it, and about what? (P2/P3, needs a joint decision)
`67fa1a1` decodes `rotation_deg` at keyframe `+12` and explicitly does **not**
render it: `ui_layout::blit` is axis-aligned. `ptloop01`/`ptloop02` on the title
declare +30° and 45°, and the framebuffer submits them at +30.26 and 45.28.
A canvas rotation is a few lines in Godot, so the port *can* draw these. But
then the port is deliberately more correct than the reference renderer, and
`verify-screen` — the port's whole verification method — starts reporting a large
diff on the title that means "the port is right". That is a bad state to be in
silently, so I would rather agree it than do it.
Two sub-questions: **is the rotation about the declared pivot** or about the
element's centre or corner? And would you rather `blit` grow a rotating path so
the diff stays meaningful? The format would go to **v3** to carry
`rotation_deg`; that is my side and I will do it either way, since carrying a
decoded field the renderer ignores is better than dropping it.
### ~~5. Is `main-menu-oracle.png` gamma-correct?~~ — ANSWERED: no
**Answered in HANDOFF `9ca1eb5`: the captures are not gamma-neutral and RMSE against
them has a floor.** ✅ Recorded in `tools/port/verify-capture`'s header, where a
reader hits it before running it. Kept for the record:
With the background in, the port sits at 5.92 % RMSE against that capture and is
visibly **darker and less saturated** than it across the whole frame. If the
capture path applies a gamma or a colour transform the game does not, then RMSE
against captures has a floor and the port should stop chasing it. If it does
not, something is still missing. The port cannot tell these apart from inside.
## Questions this port has raised
🔴 **This table's rows carry no derivation sha, and that is why they rot.** The standing instruction is to record the HANDOFF commit each row was derived from; every *prose* section added since does, and **every row in this table and the next does not**. On 2026-08-30 an audit found three stale rows here — one of them contradicted by a struck row four lines below it, claiming the boot ends on a plateless title when it had drawn the plate for weeks. The undated rows are exactly the ones that went stale, which is as close to a controlled experiment as this page is going to get.
⚠️ Rows are **not** being back-dated: nobody knows when most were written, and inventing a sha would be worse than admitting there is none. New rows carry one. This table was last audited **2026-08-30** against a running port.
### ~~Does a keyframe group loop, or hold its last pose?~~ — answered
**Answered 2026-08-28 by the RE agent: groups hold.** `ptloop01`/`ptloop02` park
their sprites at x=1521 and x=839, both off a 1280-wide design, and 18 s of
settled title sits at sd ≤ 0.01. `loop*.rat` is a misleading name — these
animate once during build-in and then rest off-screen.
The port's own error here was different and is fixed: it settled at the last
*timed* keyframe rather than at the hold. See `docs/DECISIONS.md`.
Kept for the record:
Raised at P2 and **unsettled**. The port holds the last timed keyframe, which is
right for an entry animation (the main menu settles at t=80, 1.33 s) and is
proven on the screen P2 gates. The **title** runs to t=269 — 4.48 s — and there
the port's settled pose and the decoders' `rest` disagree badly (max 142/255).
🔴 **REFUTED 2026-08-29, by the port, against its own export.** This paragraph said: *"no element's alpha reverses direction anywhere in this export, so nothing pulses, which removes the obvious reason to expect a loop without disproving one."* `press_start`'s `ptbtn00` reverses: `fade_argb` is `0x00ffffff` at t=214 and t=236, `0xffffffff` at t=238 and t=244, and `0x00ffffff` again on the final untimed keyframe. It was in the export the whole time and the claim was never checked against it — it was checked against the screens P2 happened to be looking at. The reason to expect a loop is back, and the running game pulses this exact element. What would settle it: **a capture of build 4 alone**. The one
live title capture composites the `PRESS Ⓐ` plate (build 2) over it, so it
cannot be diffed against the title by itself.
⚠️ Independently, **both** of the port's modes draw a washed-out cyan glow over
the title logo that the running game does not have. That is a third problem and
it is P3's; it is noted here so nobody reads the loop question as its cause.
Not blocking anything today; raised because the port found them and a guess here
would be believed later.
### ~~`rest_plateau` misfires on elements with no exit animation~~ — fixed
**Fixed 2026-08-28** in `sylpheed-formats`, and this port's pin moved
`8b6dbcf → 5414db3` to take it. The rule adopted is **not** the condition this
port proposed, which was too loose: a trailing run is the hold exactly when it
is **visible**. The port's condition would have erased the word PAUSE on
`pgptitle.rat`, whose trailing run is two identical *transparent* frames.
Kept for the record, since the reasoning is still what found it:
**This one is a decoder bug, not a question**, and it was the highest-value item
on this page for the RE agent. `ui_layout::rest_plateau` excludes a run of
identical keyframes that ends the group, on the grounds that it is the exit. For
an element that **has no exit animation** the trailing run *is* the hold, and the
rule falls back to an earlier run — for a slide-in, the invisible pre-roll.
The condition that identifies the affected elements exactly, with no false
positives across this export, is: **the final untimed keyframe has the same pose
as the last timed one.** Six elements match; `rest()` misses all six.
`ptframe1` and `ptframe2` on the main menu are the visible case, and
`docs/re/captures/main-menu-oracle.png` settles it — the game draws the circuit
bracket that `rest` calls invisible. `sylpheed-cli screen render` is missing it
too, so this is not only a port concern.
The port needs nothing here: it derives the arrived pose from the keyframes and
does not use `rest`. Filed because `rest()` is used elsewhere and because a
capture already proves it.
### Does the game sample a scaled sprite at the pixel corner or the pixel centre?
Found at P1, by the only screen it could have been found on. `title_jp`'s
`ptlogo_eff2` is the **single drawn element in the whole export** at a scale that
is not a whole multiple of 100 % (125 %), and `title_jp` is the only one of the
twelve screens whose Godot-vs-CLI diff exceeds 6/255.
The two renderers pick different source texels at a non-integer ratio.
`sylpheed_formats::ui_layout::blit` samples at the destination pixel's **top-left
corner** (`sxi = col * sw / dw`); a GPU samples at its **centre**
(`floor((col+0.5)*sw/dw)`). At 125 % they disagree on one column in five — ~30
pixels above 100/255, strung along thin diagonal edges. At every whole multiple
of 100 % they agree exactly, which is why the other eleven screens are clean.
The port has **not** changed to match: matching would mean reproducing a half-
pixel bias on purpose to make a number smaller. The question for the RE agent,
when it is cheap: **a framebuffer capture of the Japanese title screen** would
settle it outright, and it is the kind of thing a capture answers in one look.
Cost of being wrong either way: a one-texel edge on one glow, on a screen the
English boot path never shows. This is filed, not urgent.
### The pivot is not half the texture on `GP_TITLE`
`sylpheed-formats`'s `ui_layout::Element::pivot_x` is documented as "for a `.t32`
element this is exactly half the decoded texture's dimensions (verified 7/7 on
the tutorial bundle)". Counting it over the whole of `GP_TITLE` as exported:
* **55 of 93** sprite-bearing `.t32` elements match within ±1 px.
* **38 do not**, and several are not close: `ptlogo_back2` is 1118×262 with pivot
(500, 117) where half is (559, 131); `ptmsg` is 223×38 with pivot (123, 19)
where half is (111.5, 19) — the Y matches and the X does not.
This changes nothing today: the exporter emits the **declared** pivot and never
derives one, and the pivot only affects drawing when scale ≠ 100 %. But it does
matter, because scale is genuinely animated here — **177 keyframes** across
`GP_TITLE` are not 100 %, including on the title screen the port must draw at P1.
The question for the RE agent, when it is cheap to answer: **does the running
game anchor a scale to the declared pivot, or to half the texture?** The two
differ by up to 59 px on `ptlogo_back2`, which is visible. Until then the port
follows the decoders and uses the declared pivot, which is also what
`sylpheed-cli screen render` does — so a P1 diff cannot distinguish them, and
agreement between the two is not evidence.
**P1 has now been run and that prediction held.** The port and the CLI agree on
every scaled element across all twelve screens; the question is untouched by it.
It will stay untouched by P2 as well, since P2 animates the same two renderers'
shared assumption. Only a capture answers this.
### Does the focus ring spin while a button is focused? (P5)
Raised 2026-08-29 against HANDOFF `9ca1eb5`. **Not blocking** — P5 can draw the
ring at rest and say so — but it is a guess if taken either way, so it is not
taken.
`ptbtneff01` is the 42×46 glowing ring that ask 3 identified as the focus
marker's first element. In every one of the five focus records it declares
exactly two keyframes:
```
t=120 pos (500, y) scale 100% rotation_deg 0
— pos (500, y) scale 100% rotation_deg 360 (untimed, the hold)
```
A full turn, ending on the untimed final keyframe. The settled answer *"groups
hold, they do not loop"* (2026-08-28, from `ptloop01`/`ptloop02` parking
off-screen) does not decide this one, because **0° and 360° are the same pose**
a ring that spins forever and a ring that turns once and stops are
indistinguishable by their rest pose, which is the evidence that settled the
other case. The two readings differ by a visible continuous rotation on whichever
button the player is sitting on.
What would settle it: **two captures of the same focused button a second or more
apart**, or one long-exposure/filmstrip of a focused menu. Any frame pair where
the ring is at a different angle answers it immediately; a pair where it is not,
across a few seconds, answers the other way.
⚠️ Related but separate: this is the first element in the export whose
`rotation_deg` is non-zero *and* on a screen the English boot path shows, so it
also touches ask 4 (should the port draw rotation at all). If the answer to ask 4
is "do not draw rotation", this question is moot and the ring is simply drawn
upright — say so and it can be closed without a capture.
---
## ~~Whose span is the opaque span?~~ — ✅ answered, and it was never a disagreement
*Derived from HANDOFF `9ca1eb5`. Closed 2026-08-29 by the Decoder.*
**Both numbers were right, and both were mine.** `palogo_eff0.prm` is on *both*
splashes: publisher (entries 10, 13) runs to t=255 → **256** instants, developer
(11, 14) to t=210 → **211**. I filed a disagreement by comparing one of my two
numbers to one of theirs without checking the other row of my own census table,
which had 211 in it. 🔴 **A per-screen quantity needs its screen named beside it**
— this row named neither, and that is the whole defect.
The convention is confirmed as the port already implemented it: span is
`0 ..= max keyframe over every element in the build`, and **an element holds its
final pose**. Not a convenience — the header's `+0x08` never falls short of the
last keyframe, and `+0x08` and the elements' maximum are interchangeable disc-wide.
⚠️ The hold is load-bearing: dropping it changes **72 of 130** verdicts (55 %),
and `palogo_eff0` — one keyframe, opaque for one instant — is called *free*
without it, against a measured order. See `DECISIONS.md`.
---
## ~~The two splash dwells~~ — ✅ answered 2026-08-29, and the row was my over-correction
*Derived from HANDOFF `9ca1eb5`. Closed by the Decoder over 3 cold boots.*
**The dwells are declared on the disc** — publisher t=0…255, developer t=0…210 —
and the port was **already playing them**, each plus the 9-unit black hold, to
4.400 s and 3.650 s. The developer's declared value agrees with wall clock to
**1.1 %**.
🔴 So this row should never have been filed. I generalised build 4's ~9× hold
onto two screens it does not govern: the title's exit is caused from outside its
timeline, a splash's is caused by nothing. See `DECISIONS.md` — the withdrawal is
recorded there rather than only struck here.
⚠️ And the ask itself was wrong-shaped: I asked for **two wall-clock timestamps**.
The Decoder's own container timed these same dwells 1520 % long, so a seconds
figure records one emulator's pacing. Anything that ever goes in
`authored/flow.json` `dwell` is in **units**.
---
## `black_hold_units = 9` rests on numbers that have since moved
*Derived from HANDOFF `9ca1eb5`. Raised 2026-08-29 by the port, after the Decoder
disclosed that `ARM=early` silently loses its trigger ~40 % of the time.*
That disclosure means every draw-stream run is **n = 1**, so I audited what the
port authors from one. Exactly one constant does: the black hold between the two
boot splashes. It is **not wrong**, but three of its supports have moved:
* its conversion used **105 drawn frames**, revised by their own truncation fix
to **114** (2.284 → 2.096 units/frame);
* its second corroboration, 2.231, is the figure behind their **retracted**
114-unit plate period;
* a run-average rate is the wrong shape for a 34 frame event now that the
presented rate is known to rise 33 % across a boot.
Redone on their corrected segments the two runs give **8.95** and **6.71** units.
⚠️ They were reconciled as agreeing within ±1 — but one frame is a **third** of
this quantity, and overlapping error bars are not agreeing central values. The
range is ~6.59.2 and the port sits at the top.
**The value is unchanged and I am not changing it**: that would be my arithmetic
on their instrument, and the port does not author a number the corpus has not
given. It is proposed, not adopted.
What would settle it: **one more draw-stream run of the boot**, with the arm
confirmed, counting frames with no sprite quad between the splashes. A third
sample turns a 3-vs-4 disagreement into a measurement. It needs no button press,
so it should sit the safe side of the Ⓐ blocker.
✅ Not in doubt: the hold is real. Until 2026-08-29 the port had **no black frame
at all** where the oracle measures a plateau, and both boundary frames still
carry picture (alpha 7 and 34), so the true hold is shorter than whatever this
lands on.
---
## Does the game's menu music duck under a cutscene?
*Derived from HANDOFF `9ca1eb5`. Raised 2026-08-29 by the port.*
The port plays the main-menu bed **underneath `S00A`**, so two unrelated music
tracks sound at once. `MenuAudio.stop_bed()` exists and has no caller — this is
an **unmade decision**, not a choice, and it has been true since P6.
🔴 **Deliberately not fixed.** Stopping the bed would sound right and would be
invented; MISSION's rule is to leave the unmeasured detail plainly wrong. The
runtime now announces it on every movie that starts with the bed sounding.
What would settle it: **any capture with sound of the real game entering a
cutscene from the main menu** — the first second answers it. Three outcomes are
all useful: the music stops, it ducks, or it genuinely continues (in which case
the port is already right and this row closes as ✅).
⚠️ Do **not** answer this from our own renderer or by reasoning about what a
shipped game would do. That reasoning is what makes an invented answer feel safe.
⚠️ Related but separate, and already authored with a `why`: the bed **loops by
restart**, seam and all (`authored/audio.json`, `loop_why`). That is a known-ugly
deliberate choice pending a loop point, not this question.
---
## Does the `PRESS Ⓐ` plate stay up, or blink once and go?
*Derived from HANDOFF `9ca1eb5`. Raised 2026-08-29 by the port.*
`ptbtn00`'s group makes the plate opaque for **8 units only** — alpha 0 until
t=214, 255 at t=236 and t=238, back to 0 by t=244 — and its group then ends. So
in the port the plate flashes for 0.133 s and is gone, and the boot's end-state
capture (taken at t=246.54, once build 4 has finished fading up at t=261) contains
**no plate at all**.
🔴 The two states cannot share a frame: the plate's window closes 17 units before
the title stops presenting. The port captures the title, by an earlier and sound
decision it is not reversing.
What would settle it: **a capture of the real title held for several seconds after
the plate first appears.** Three outcomes, all decisive — the plate stays up; it
pulses on a period (`authored/timing.json` already carries a *speculative*
`looping_focus_records` entry for `press_start/ptbtn00` at 120 units, which is
what a pulse would need); or it genuinely blinks once and vanishes, in which case
the port is right and this closes ✅.
🔴 **Correction, 2026-08-30.** This row previously said the looping focus record
was "the only thing making the plate [refuted] reappear at all". The opposite was true: the
entry was the only thing making it **disappear** — it drew a dim glow *instead of*
the plate's own sprite, max 0 against max 252.5. It has been deleted, and the port
now draws the plate from its own decoded fade. See `DECISIONS.md`.
⚠️ So the question narrows rather than closes. The port shows the plate at its
declared instants and holds it. What a capture would still settle is whether the
real plate **pulses** after that, and whether `ptbtn00f.rat` — the focus record,
which the port now draws not at all — is a glow layered *over* the plate. Drawing
both would be a rendering rule nobody has measured.
---
## The `ptloop` sweep phase: ~400 units fits the capture, the refined fit says 357.7
*Derived from HANDOFF `9ca1eb5`. Raised 2026-08-30 by the port.*
`--leaf-time=` now poses the sweeps' leaf independently of the screen, so the
Decoder's refined sweep fit is testable for the first time — it never was, because
`verify-capture` passed it as a whole-screen `--time` that `pose_at` discarded.
Against `live-title-build4-no-plate.png`, structural disagreement bottoms out in a
sharp basin at **390415 units (0.0124 %)** and reads **0.2532 % at t=357.7** — a
20× separation, on a deterministic renderer, with the sweeps demonstrably moving
0.40 % of the frame between phases.
**Nothing in the port changes and nothing is being asked for urgently.** The port
loops the leaf freely; there is no phase to set, and re-posing the harness to the
fitted value would be tuning until they match.
What would be useful: **whether 357.7 and this are the same quantity.** If the
refined fit was measured against this same capture, one of the two is off by ~42
units and it is worth knowing which. If it came from a different frame, then ~400
is a second, independent phase measurement and neither is wrong.
⚠️ The port cannot separate a phase error from a systematic error in how it draws
the sweeps — a geometry mistake could be absorbed by shifting the phase. The
sharpness of the basin argues against that, but one capture cannot settle it.
---
## A failed export leaves a partial tree that reads as "not an export tree"
*Derived from HANDOFF `9ca1eb5`. Raised 2026-08-30 by the port. **Not blocked on
anybody** — filed because the fix is a judgement about failure semantics, not a
bug.*
When `sylpheed-export export` fails part-way — for instance on one of the new
authored-value assertions — it leaves `export/` **without a `manifest.json`**.
Every tool then reports *"has no manifest.json — is that an export tree?"*, which
reads as a broken harness rather than as the aftermath of a deliberate abort.
It cost a wrong reading within minutes of being introduced: an audit of the
validator reported every case as "no manifest" and nearly concluded the validator
was checking nothing.
Writing the manifest **last** is correct — a manifest is a claim about a finished
tree. So the candidate fixes are about the *message*, not the order:
* leave a marker on failure that the next tool can name (`export/.failed`), or
* have `check` say *"no manifest — the last export did not finish"* when the tree
has screens but no manifest, which is exactly the distinguishable case.
⚠️ Deliberately not chosen here: both change failure semantics across every tool,
and neither is measured against anything. It goes to whoever owns that call.
---
## ~~`title` differs from `sylpheed-cli` on 790 pixels~~ — 🟢 localised by the port, ask withdrawn
*Derived from HANDOFF `9ca1eb5`. Raised 2026-08-30 by the port. Not urgent — the
port agrees with the **oracle** on this screen at 0.21 %, and that is the check
that counts.*
`verify-screen` has `title` at **790 pixels** over the bar and `title_jp` at
**20 498**, and neither carries a forced-backdrop element — so they are not
covered by the pinned-tag allowance the other six sit under. I had been reporting
"six expected DIFFERS [refuted]"; the real count was ten.
🔴 **The obvious explanation is wrong.** `authored/rendering.json` records that the
consistency harness compares against a renderer drawing no `.rat` leaves, so the
`ptloop` sweeps were the candidate. Emptying `draw_leaf_for` and
`loop_leaf_on_screens` changes the figures **not at all** — the harness poses at
`rest`, where the leaves do not draw.
🟢 **WITHDRAWN 2026-08-30 — the premise was refuted and the question was mine to
answer.** I asked for the CLI's element list on the theory that one renderer drew
something the other did not. It does not: the differing pixels sit at
`ptlogo_back2eff1` (`pos=[938, 194]`), and **both renderers draw it** — mean 95.60
in the port against 95.08 in the CLI. A set difference would have confirmed
nothing at somebody else's cost.
Also ruled out: a placement offset. Every ±1 px roll makes it two orders of
magnitude worse (790 against ≥ 175 406).
❔ The mechanism is still unknown, and I am not guessing at it: the antialiasing
test I tried failed its own control, with the edge mask covering 92 % of the frame.
See `DECISIONS.md`. **Nobody is asked for anything** — the residual is 0.086 % of
one frame between two of our own renderers, on a screen matching the oracle at
0.21 %.
⚠️ Do not read this as the port being wrong. Against the **capture**, `title` is at
0.21 % and `title_plate` at 0.00093 %. This is two of our renderers disagreeing,
and the one with an oracle behind it is not the one under suspicion.
---
## The loading screens hide a visible leaf animation
*Derived from HANDOFF `9ca1eb5`. Raised 2026-08-30 by the port. **Low priority**,
and nothing is waiting on it.*
`pgloading_loop5` on `build_12`/`build_15` carries a leaf — `pgloading_ring`, one
sprite — that expands **scale 0 → 1000** while fading in and out over t=0…130. At
the port's own pose instant (~t=44) it would draw at **scale 140, alpha 143**. The
port does not draw it.
⚠️ It is withheld for a reason that is sound — no oracle capture exists for a
loading screen, and `verify-screen`'s reference draws no leaves, so the content
would be unadjudicable either way — but the reason previously recorded was
**false** (*"leaf scale (0,0)"*), which made it look like there was nothing to
draw. There is.
What would settle it: **any capture of a loading screen with the ring mid-expansion**.
The Decoder records these screens as unreachable from the title path, so this may
never become answerable, and that is an acceptable outcome — the entry now says
what is being withheld rather than implying the question is empty.
---
## ~~`verify-menu-audio`'s dead-press check fails~~ — ✅ diagnosed and fixed 2026-08-30
*Derived from HANDOFF `9ca1eb5`. Raised 2026-08-30 by the port. **Mine, not
anybody else's** — filed here so it is not lost, not because it needs an answer
from the Decoder.*
The check asserts that five presses bound to nothing (`left`, a measured no-op)
produce a Master bus bit-identical to five waits. It now reports DIFFER across
three consecutive runs: divergence at 0.085 s, 92 % of samples differing, and
recording durations of 1.300 s against 1.207 s where they were previously equal.
**Diagnosed: none of the candidates.** The recording is not sample-deterministic
across runs and never was. Three **identical** invocations give two outcomes —
1.207438 s and 1.300317 s — differing by **exactly 4096 samples, one mixing
buffer**. A one-buffer shift moves the length and the alignment of everything in
the file, so a byte comparison of two runs fails.
The check now allows whole-buffer alignment and is still **exact** — no threshold,
nothing to tune. Proved it can still fail: `ctrl` against `walk` differs at every
alignment.
⚠️ The check's premise is **cross-run bit-determinism**, which is what made it a
strong assertion with no threshold to tune, and also what makes it brittle:
nothing in it verifies that startup is still deterministic. Left failing rather
than silenced.
---
## The menu loop point: a runtime field and an audio measurement disagree
*Derived from HANDOFF `9ca1eb5`. Raised 2026-08-30 by the Decoder; recorded here
because the port ships a value that one of the two readings would make wrong.*
`loop_start` / `loop_end` are **runtime** fields in the XMA decoder context, set
by `XMASetLoopData` and logged by Xenia — 8 734 records read off the menu.
Converted they imply a cycle of roughly **[10 s, 72 s]**, against the
**[0.25, 57.18 s]** the same agent's audio tracking reported. Both cannot be right
and neither is withdrawn.
The port ships `loop_end_s: 61.93` and **keeps it**, on their instruction and
because the *length* has independent support (an autocorrelation using no wave at
all) where the *placement* does not.
🔴 **The cost if the runtime fields win: this export is about 10 seconds short.**
Under [10 s, 72 s] the content in [61.93, 72] is played by the game and absent
here. That is the number to weigh, not "the loop point may move".
What would settle it, theirs: an **XMA frame walk** to convert the bit offsets
honestly — the conversion is not linear, since XMA frames are variable-length in
bits, and a linear reading gives 62.34 s and 63.29 s for two stems that must be
sample-synchronous, which refutes itself. Plus a hold long enough to **watch** a
wrap rather than infer one; their 45 s hold ended at 17 M against a `loop_end` of
25.6 M.
⚠️ One check the port added, and its limit: over 126.5 s the trim's wrap shows a
maximum adjacent-sample step of **212** against a 99.9th percentile of **3 737**,
so the join is not a click. **It does not discriminate the two readings** — a cut
near a zero crossing is smooth wherever it falls.
### ~~Ⓑ on the title — what happens, actually measured~~ — ANSWERED 2026-08-30
**Derived from HANDOFF `9ca1eb5`; Decoder branch `86a8ce7`.**
`authored/flow.json` holds `title/on_cancel: null` and previously stamped it
`MEASURED, HANDOFF Q5`. That stamp is now removed: the source table's evidence
cell reads `none`, and the Decoder states their 2026-08-30 run cannot be counted
because the second Ⓑ arrived during the title's build-in.
**The ask:** a run that presses Ⓑ *after the title has settled*. They have
already named this as the shape a valid run needs, so this is a pointer, not a
request for a method.
**What it changes if the answer is "something":** `null` is currently the safe
default and the port ships it either way, so nothing is blocked — this is a
provenance repair, not a stall.
### ~~Auto-repeat on a held direction~~ — ANSWERED 2026-08-30: none
**Derived from HANDOFF `9ca1eb5`; Decoder branch `86a8ce7`.**
Q5's `up / down` cell is empty in the source. *One item per press* is fine — the
wrap montage's count carries it. *No auto-repeat* is not evidenced, and the
source hedges with "at the durations tried". The port holds `auto_repeat: false`
as a stated choice. **The ask:** hold a direction for ~2 s and say whether the
cursor moves more than once.
### The black hold: my 0.15 s against the game's 0.17-0.23 s
**Derived from HANDOFF `9ca1eb5`.**
`authored/timing.json` holds `black_hold_units: 9` (0.15 s), measured in the draw
stream. HANDOFF Q7's plateau, measured off the running game, is **0.17-0.23 s**.
Mine sits **below their floor** by 1-5 units. Not changed - two instruments
disagree and tuning to match is the failure this corpus keeps naming.
**The ask:** is the ~0.4 s fade-out the ramp alone, or **ramp + hold**? The file
gives the quad's ramp as 10 units (0.17 s); 10 + your 10-14 = 20-24 units =
0.33-0.40 s, which brackets your 0.4 s at the top. If it is the sum, no authored
constant is needed anywhere and my 9 is simply the wrong side of your boundary.
The two readings differ in whether a screen is still drawing during the last
0.2 s.
### How long after Ⓑ does the `PRESS Ⓐ` plate come back?
**Derived from Decoder `daf8f47`.**
They measured press 351.2 s -> pulse back 358.5 s, i.e. **7.3 s**. The port
raises the plate on arrival and its own group takes it opaque at t=238, giving
**press -> plate visible 4.33 s**. Pulse detection can lag first paint by up to
one 120-unit period (2 s), which closes the gap to ~6.3 s at most and leaves
roughly a second unaccounted for.
**The ask:** is 358.5 s the plate's FIRST paint or the first pulse peak your
detector could see? If the latter, what is the first-paint time? I am not
authoring a delay to close this - a `2.13 s` authored delay in this same block
was already refuted once by arithmetic.
### ~~A capture of the Japanese title (`GP_TITLE` build 7)~~ — DELIVERED 2026-08-30, and it went against the port
**Derived from Decoder `daf8f47`.**
`verify-screen` has `title_jp` at max 233 / over3 61208 against a committed
baseline of 155 / 20498. Established: deterministic, on the Godot side (your
byte-identical-renders finding rules out the reference), and localized to one
350x396 block at (405, 74) - the logo stack, where the port draws `ptlogo_jp`,
`ptlogo3a/b/c` and five `ptlogo_back2eff*` layers.
**The ask:** a capture of the Japanese title at rest. Without one there is no
oracle for this screen, and agreement with `sylpheed-cli` is not correctness -
so I can say the two renderers moved apart but not which one moved, and I am not
going to pick a direction.
### Is the Ⓑ "no black interval" general, or specific to menu->title?
**Derived from Decoder `auto/build-ordinal-audit`.**
Measured: Ⓑ menu->title has NO black interval -- the incoming title starts drawing
at frame 34, before the outgoing quad ramps at 40. Ⓐ title->menu is sequential
with ~5 frames of black.
`boot.gd` applies `black_hold_units` uniformly, so the port inserts ~9 units of
black on Ⓑ that the game does not have. **I have not changed it**: one run of one
transition is exactly the generalisation the Decoder just named as the error under
two of their own wrong readings.
**The ask, in priority order:** (1) does Ⓑ from EXTRAS -> main_menu also show no
black interval? That is the cheapest test of whether "Ⓑ has no black" is a rule or
one screen pair. (2) Is the Ⓐ ~10 units a designed hold or a load? If a load, it
is emulator-dependent and nothing should be authored from it.
### Do nested leaf records advance at half the top-level rate?
**Derived from Decoder `auto/build-ordinal-audit`, draw-stream fit over 132/112
frames.**
Measured 1.072 and 1.070 units/frame for the two title sweeps, against Q1's 2
units/frame for top-level elements. My port drives both from one clock at 60
units/s, so if that holds **the title's sweeps ship 1.87x too fast** -- a 10.0 s
cycle where the game takes 18.7 s.
**Not changed.** `keyframe_units_per_second` is authored from a measurement and
governs build-in, transitions and the plate; a leaf-only clock is a claim about
the game, not about my renderer.
**The ask:** is the factor a property of nested records, or does Q1's 2
units/frame simply not apply to them? And note the two-strip agreement constrains
the strips to each other, not the absolute rate -- both ratios come from one
capture under one fps assumption, and 1 unit per 1/30 s at 28.5 fps gives 1.053.
### ~~`black_hold_units`: 0 is now wrong on 4 of 5 transitions~~ — RESOLVED: uniform is excluded
**Derived from Decoder `auto/build-ordinal-audit` plus my own `verify-dwell`.**
Measured gaps: 0, 4, 6, 6 units (theirs) and ~7.9 (mine, from the port running
0.131 s short on publisher->developer). Four of five non-zero, mean 6.0.
My authored 0 was justified as "adds no black the game does not have". That
justification has failed: it now omits a real quantity on most transitions. A
uniform 4 or 6 halves the total error but is a fit on five samples with no
mechanism. I attempted a rule -- gap plus the incoming screen's opening
black-clear summing to a constant -- and it holds at 16/16/18 on the three
menu/title transitions and fails outright on the splash pair, whose backdrop never
clears.
**The ask, and it is a decision rather than a measurement:** is a uniform
non-zero hold preferable to omitting the gap, given neither is derivable? If a
fifth transition of a *different* shape (screen-to-screen rather than menu) is
ever measured, that would test the constant-black-period idea properly.
Value stays 0 meanwhile; `verify-dwell` reports the shortfall rather than
absorbing it.
### When there is more than one gap measurement per outgoing screen
**Derived from Decoder `auto/build-ordinal-audit`.**
They ordered the gaps by the screen being LEFT: menu 0 and 1 frames, EXTRAS 2,
title 3 -- ruling out direction, button and incoming screen positively. That
resolves the earlier escalation: **a uniform value is excluded**, so
`black_hold_units` stays 0 meaning *not modelled*.
**The ask, low priority:** a second measurement for any one outgoing screen. Three
screens with one value each restates the data; two values for one screen would
make "keyed on the outgoing screen" predictive rather than descriptive. Nothing
ships on it -- the port omits the gap and says so in `verify-dwell`.