From cf3a9a0454caafaef257d91f0d3ed815270dc268 Mon Sep 17 00:00:00 2001 From: Sylpheed port agent Date: Sun, 30 Aug 2026 22:35:13 +0000 Subject: [PATCH] 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) Claude-Session: https://claude.ai/code/session_01N7FiFFFwbvG2uxdcEh8HyF --- docs/port/BLOCKED.md | 6 +++ docs/port/DECISIONS.md | 93 ++++++++++++++++++++++++++++++++++++++++- port/scripts/boot.gd | 44 ++++++++++++++++--- tools/port/check-claims | 1 + 4 files changed, 138 insertions(+), 6 deletions(-) diff --git a/docs/port/BLOCKED.md b/docs/port/BLOCKED.md index 11739707..8c93343e 100644 --- a/docs/port/BLOCKED.md +++ b/docs/port/BLOCKED.md @@ -147,6 +147,12 @@ HANDOFF. | ~~P1–P7 — 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 | diff --git a/docs/port/DECISIONS.md b/docs/port/DECISIONS.md index 12e77519..9d4ec004 100644 --- a/docs/port/DECISIONS.md +++ b/docs/port/DECISIONS.md @@ -9,7 +9,7 @@ dies, which is what this file is for. -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) ## 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. diff --git a/port/scripts/boot.gd b/port/scripts/boot.gd index 14cddc5f..49fa8255 100644 --- a/port/scripts/boot.gd +++ b/port/scripts/boot.gd @@ -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= " + + "--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: diff --git a/tools/port/check-claims b/tools/port/check-claims index a9a07770..16d639c2 100755 --- a/tools/port/check-claims +++ b/tools/port/check-claims @@ -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 )