port: running it as a player finds two defects reading it did not

--boot --script= parsed, was stored, and did nothing. The script only starts at
_menu_enter, and a --boot run without --play never enters a menu -- it holds on
the title and quits. The run completed, exit 0, no menu line, no press: a clean
result to a question never asked. This file already warns about that exact shape
600 lines above the bug, where --capture used to photograph the first frame of a
scripted run. The warning was written, kept, and did not stop the same class
recurring in the neighbouring flag. Now push_errors and exits 2, naming both
working forms, refusing rather than implying --play since the two runs differ by
157 s of intro. Verified: --boot --play --script walks power-on through splashes,
ADV, title, (A), main menu, down, (A).

A comment above audio.play_bed described the port as CHOOSING the menu track,
which HANDOFF Q10 refuted a week ago -- BGM_103 is measured on three independent
legs and audio.json says so. Third instance of the drifted-comment trap. The dead
phrase is now a check-claims register row, controlled: a planted revival fails and
removing it passes.

And the boot's wall-clock seconds are a property of this container. ADV takes
146.6 s of wall clock for 137.44 s of media, +6.7%, while S00A runs real time at
-0.4%. Not a post-roll and not a general deficit: ADV is 1280x720 and S00A is
768x432, this box has no GPU, and 720p Theora decodes below real time here. The
transcode is faithful against a 137.71 s source and the exporter does not rescale.
P3/P7 artifacts quote seconds containing that deficit -- reproducible here, not a
statement about the port or the game. Comparisons with the Decoder's measurements
must go through media length, not wall clock; they carry an explicit emulator
pacing factor for the same reason and I had been quoting mine as exact.

Their negative result on LOAD GAME, TUTORIAL and OPTIONS leaves guard_focus_scope
right to count them UNMEASURED rather than 'resets'. The transferable part is
their instrument story: a narrow calibrated reader failed, so they generalised to
a whole-frame comparison, which died the moment a crash dialog overlaid the frame
while the narrow reader kept working. contract-check is deliberately narrow,
individually anchored checks for the same reason, and the temptation after an
ANCHOR LOST will be to loosen the matching -- trading a failure I can see for one
I cannot.

Every asserting check passes.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N7FiFFFwbvG2uxdcEh8HyF
This commit is contained in:
Sylpheed port agent
2026-08-30 22:35:13 +00:00
parent 3ddd298262
commit cf3a9a0454
4 changed files with 138 additions and 6 deletions

View File

@@ -147,6 +147,12 @@ HANDOFF.
| ~~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`. |
## 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` | ⚠️ **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 |

View File

@@ -9,7 +9,7 @@ dies, which is what this file is for.
<!-- INDEX: generated by tools/port/index-decisions -- do not hand-edit -->
246 sections. Search this before re-deriving anything.
249 sections. Search this before re-deriving anything.
* [P0 — the exporter, 2026-08-28](#p0--the-exporter-2026-08-28)
* [P1 — Godot draws the screen, 2026-08-28](#p1--godot-draws-the-screen-2026-08-28)
@@ -257,6 +257,9 @@ dies, which is what this file is for.
* [A refutation attempt on Q2's map of `GP_TITLE` — the count is right, the list is short](#a-refutation-attempt-on-q2s-map-of-gp_title--the-count-is-right-the-list-is-short)
* [An authored value became a measured one, and a difference-only check got an origin](#an-authored-value-became-a-measured-one-and-a-difference-only-check-got-an-origin)
* [EXTRAS resets — measured. The assertion was right and that does not make it evidence.](#extras-resets--measured-the-assertion-was-right-and-that-does-not-make-it-evidence)
* [Running the port as a player finds two things reading it did not](#running-the-port-as-a-player-finds-two-things-reading-it-did-not)
* [The boot's wall-clock seconds are a property of this container, not of the port](#the-boots-wall-clock-seconds-are-a-property-of-this-container-not-of-the-port)
* [Their negative result, and the trap in choosing the more general instrument](#their-negative-result-and-the-trap-in-choosing-the-more-general-instrument)
<!-- /INDEX -->
## P0 — the exporter, 2026-08-28
@@ -12829,3 +12832,91 @@ not its first.
refusal is doing more work than any threshold they could have picked. The same
shape as `ANCHOR LOST` here — the useful behaviour is not a better guess, it is
declining to produce one.
## Running the port as a player finds two things reading it did not
### 🔴 `--boot --script=…` parsed, was stored, and did nothing
The script only ever starts at `_menu_enter`, and a `--boot` run without `--play`
never enters a menu — it holds on the title and quits. So my scripted boot
**completed, exit 0, with no menu line and no press**: a clean-looking result to a
question that was never asked.
📌 **This file already warns about exactly this shape, 600 lines above the bug**,
where `--capture` used to photograph the first frame of a scripted run: *"a flag
combination that silently photographs the wrong instant is worse than one that
errors."* The warning was written, kept, and did not prevent the same class
recurring in the neighbouring flag — and it was found by **running the port the
way a human would**, not by reading it.
It now `push_error`s and exits 2, naming both working forms. **Refusing rather
than implying `--play`**: the two runs differ by 157 seconds of intro, and quietly
choosing that for someone is its own surprise.
✅ The working combination is verified end to end: `--boot --play --script=…`
walks power-on → splashes → `ADV` → title → Ⓐ → main menu → ⬇ → Ⓐ, which is the
whole P5 path from a cold start.
### 🔴 A comment in `boot.gd` describing a world refuted a week ago
Above `audio.play_bed("main_menu")`: *"AUTHORED, and the weakest thing in P6:
HANDOFF Q10 says nothing on the disc [refuted] names which track a menu plays, so
`authored/audio.json` picks one."* `BGM_103` is **measured** on three independent
legs, and `audio.json` says so; only this comment still described the port as
choosing.
**Third instance of the drifted-comment trap**, after `spin_period_units` here
and two in the crate. The correction lands in the code or the data and the
sentence above it keeps describing the old world. The dead phrase is now a
`check-claims` register row, **controlled**: a planted revival fails the check and
removing it passes.
## The boot's wall-clock seconds are a property of this container, not of the port
`ADV` runs 7.78 → 154.38 s in the boot: **146.6 s of wall clock for 137.44 s of
media, +6.7 %**. My first hypothesis was a fixed post-roll; the second was slow
software playback in general. A second video of a different size separates them:
| | media | wall | |
|---|---|---|---|
| `ADV` 1280×720 | 137.44 s | 146.60 s | **+6.7 %** |
| `S00A` 768×432 | 93.78 s | 93.37 s | 0.4 %, real time |
So it is neither a post-roll nor a general deficit: **this box has no GPU, and
720p Theora decodes below real time here while 432p keeps up.** 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 on the disc.
🔴 **The consequence is about my own artifacts.** P3/P7 runs quote wall-clock
seconds — *"boot ends at 158.13 s"* — and those seconds contain this deficit.
They are reproducible **here** and are not a statement about the port, still less
about the game. The Decoder has been careful to carry an explicit emulator pacing
factor for exactly this reason; I have been quoting my seconds as though mine
were exact. **Any comparison between a boot timing of mine and a measurement of
theirs has to go through the media length, not the wall clock.**
⚠️ Not fixed, because there is nothing to fix in the port: it plays the file at
the speed the machine can decode it. Recorded so the numbers are read correctly.
## Their negative result, and the trap in choosing the more general instrument
`LOAD GAME`, `TUTORIAL` and `OPTIONS` remain unmeasured, so `guard_focus_scope`
counting them as **UNMEASURED rather than "resets"** stays right, and no value of
mine moves.
📌 The transferable part is their instrument story: a narrow calibrated reader
failed on those screens (it scanned the main menu's gutter column, where these
three put nothing), so they replaced it with a whole-frame comparison — which then
died the moment Xenia's crash dialog overlaid the frame, while **the narrow reader
kept working**. *"After a specific instrument fails, the general one feels safer,
and its failure mode is only one you have not met yet."*
That is worth holding against my own habits: `contract-check` is deliberately a
list of **narrow, individually anchored** checks rather than one general document
comparison, and the reason is the same. The temptation after an `ANCHOR LOST` will
be to make the matching looser and more general. **That would trade a failure I
can see for one I cannot.**
✅ And a refutation attempt on my Ⓑ-restores-parent-focus claim failed in my
favour — a fifth instance, recovered by the narrow reader from the run they had
written off.

View File

@@ -170,6 +170,27 @@ func _ready() -> void:
# screen directly, which is what makes an unattended run cheap -- it does not
# sit through 137 s of intro to press a d-pad.
_play = args.has("play") or args.has("menu")
# 🔴 `--boot --script=…` PARSED, WAS STORED, AND DID NOTHING. The script only
# ever starts at `_menu_enter`, and a `--boot` run without `--play` never
# enters a menu: it holds on the title and quits. So the run completed, exit
# 0, no menu line, no press -- a clean-looking result to a question that was
# never asked.
#
# This file already warns about exactly this shape 600 lines up, where
# `--capture` used to photograph the first frame of a scripted run: "a flag
# combination that silently photographs the wrong instant is worse than one
# that errors". Same class, found again by running the port as a player would
# rather than by reading it.
#
# Refusing rather than implying `--play`: the two runs differ by 157 seconds
# of intro, and quietly choosing that for someone is its own surprise.
if not _script.is_empty() and not _play:
push_error("--script needs a live menu. `--boot` alone ends on the title "
+ "and quits, so the script would never run. Use `--boot --play "
+ "--script=…` to walk from power-on (157 s of intro), or `--menu=<screen> "
+ "--script=…` to start on a screen.")
get_tree().quit(2)
return
if _play:
_menu = MenuFlow.new()
if not _menu.configure(_flow):
@@ -769,11 +790,24 @@ func _menu_enter(name: String, fresh: bool) -> void:
_menu.stack[_menu.stack.size() - 1]["focus"] = _force_focus
view.focused_id = _menu.focus()
view.queue_redraw()
# AUTHORED, and the weakest thing in P6: HANDOFF Q10 says nothing on the disc
# names which track a menu plays, so `authored/audio.json` picks one. It
# starts when the menu becomes live and CARRIES ACROSS submenus -- `play_bed`
# is idempotent, because music that restarts every time you press (B) is the
# kind of wrong that reads as "the audio works".
# MEASURED, and this comment is a correction of itself. It read "AUTHORED, and
# the weakest thing in P6: HANDOFF Q10 says nothing on the disc [refuted] names
# which track a menu plays, so `authored/audio.json` picks one" -- which was
# true when written and was refuted the same week. `BGM_103` is measured from
# three independent legs: the phase handler `sub_821C5580` plays cue 1103,
# the bank's two waves are byte-for-byte what the XMA probe saw at the menu,
# and the disc census agrees. `authored/audio.json` CITES it; it does not
# choose it.
#
# 🔴 Left visible rather than swapped out, because this is the third instance
# of the drifted-comment trap in this project -- a correction lands in the
# code or the data and the sentence above it keeps describing the old world.
# Neither agent's checker looks at prose that contradicts the code under it.
#
# What is still true: the bed starts when the menu becomes live and CARRIES
# ACROSS submenus -- `play_bed` is idempotent, because music that restarts
# every time you press (B) is the kind of wrong that reads as "the audio
# works".
audio.play_bed("main_menu")
print(" menu on %s, focus %s" % [name, _focus_label(view.focused_id)])
if not _script.is_empty() and not _script_started:

View File

@@ -54,6 +54,7 @@ goes against the port
the capture turns out to determine it
COMPOSITED rather than standalone
structural limit, not an unrun experiment
HANDOFF Q10 says nothing on the disc
ROWS
)