From b3da5c1d4853edcce5cacf4c8ddbf0ae6daece08 Mon Sep 17 00:00:00 2001 From: Sylpheed port agent Date: Sun, 30 Aug 2026 21:39:08 +0000 Subject: [PATCH] port: correct a check that asserted an absence of measurement as a finding The pair I shipped this iteration -- focus_persists on for main_menu, off everywhere else -- reported both halves as agreement with the contract. Nothing measured that extras does not persist. The corpus has EXTRAS' opening item from one entry and (B) restoring the PARENT's focus 4/4; neither says what a submenu's own cursor does on re-entry. Caught by the Decoder. It is the mirror of the trap it was written to avoid. I refused to let a derived menu-wide rule overwrite a measured value, then let 'not measured here' become a positive assertion of the negative. Both treat a gap in the corpus as if it carried information and differ only in which direction they fill it. And the failure mode was the bad one: if the game does persist EXTRAS, the check holds the port to the wrong behaviour and passes while doing it. check_focus_persists now asserts only the measured half. The scope became a separate guard with its own outcome word -- 'only main_menu, AUTHORED DEFAULT, unmeasured elsewhere' -- which still fails if widened, since that should be a deliberate edit, but can no longer be read as the game being known to reset. focus_persists_why records the correction rather than being rewritten. It also weakens a label. EXTRAS' initial_focus is marked measured and was taken on a single entry; now that the main menu is known to remember its cursor, a one-entry reading of any screen may be measuring history rather than what the screen opens on -- the same objection that reframed the TUTORIAL/NEW GAME disagreement. The observation stands, its reading as an initial focus does not. Caveat attached, kind left as measured with a note that it changes if EXTRAS turns out to persist. Not building on the non-persistence half until their EXTRAS re-entry run returns. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01N7FiFFFwbvG2uxdcEh8HyF --- authored/flow.json | 34 +++++++++++++++++++++-- docs/port/DECISIONS.md | 58 ++++++++++++++++++++++++++++++++++++++- tools/port/contract-check | 47 +++++++++++++++++++++++++------ 3 files changed, 127 insertions(+), 12 deletions(-) diff --git a/authored/flow.json b/authored/flow.json index e24559d9..5ae4cdc7 100644 --- a/authored/flow.json +++ b/authored/flow.json @@ -144,7 +144,24 @@ "", "⚠️ WHAT IS NOT KNOWN: whether the memory survives a return to the BOOT", "(as opposed to the title), and whether any other screen has it. Ask before", - "widening this." + "widening this.", + "", + "🔴 CORRECTED 2026-08-30, SAME DAY, by the Decoder: the paragraph above argued", + "the scope from `extras` having a MEASURED initial focus that a remembered", + "cursor would override. That is a good reason to be CAUTIOUS and NOT a finding", + "that `extras` resets. Nothing has measured what a submenu's own cursor does on", + "re-entry: the corpus has EXTRAS' opening item from ONE entry, and (B) restoring", + "the PARENT's focus 4/4, and neither answers it.", + "", + "So `focus_persists: false` everywhere else is THE PORT'S DEFAULT, not the", + "game's behaviour. It invents the least and it preserves the one measurement", + "there is. `tools/port/contract-check` asserts only the main-menu half against", + "the contract and reports the scope as a GUARD, because for one iteration it", + "asserted non-persistence as though it had been measured -- which would have", + "held the port to the wrong behaviour and passed while doing it.", + "", + "❔ The Decoder is measuring EXTRAS re-entry now. Do not build on the", + "non-persistence half until it returns." ], "initial_focus_why": [ "AUTHORED, standing in for HANDOFF Q5, which measured that initial focus is NOT STABLE: four boots of the same harness opened on TUTORIAL, TUTORIAL, NEW GAME, NEW GAME. A port has to open on something. ptbtn01 (NEW GAME) is picked because it is one of the two states actually observed and it is the top item, so a reader can predict it. It is a CHOICE. Delete this the day the RE agent finds what selects it. CORROBORATED 2026-08-29, and still not decoded: the committed capture live-main-menu.png has NEW GAME focused. Identified by rendering all five focus states and taking the minimum difference -- 531 differing pixels against 6080-7094 for the others, an 11.5x margin -- with the method controlled on live-main-menu-options-focused.png, whose answer is in its filename and which it picks by 4.7x. That means the port's choice matches the state of one committed frame. It does NOT make focus stable: Q5's four boots gave TUTORIAL, TUTORIAL, NEW GAME, NEW GAME, and this identifies one frame rather than a rule. Delete this entry the day something says what SELECTS it. TIGHTENED 2026-08-29: Q5 now has SIX boots, and the shape is sharper than 'unstable' -- TUTORIAL x3, NEW GAME x3, and NO OTHER ITEM EVER OBSERVED. So it is not uniform over five buttons; whatever selects it has to explain a two-way split. That does not change this choice (NEW GAME remains one of exactly two observed states, and it is the state of the committed capture) but it does change what would REFUTE it: a boot opening on LOAD GAME, OPTIONS or EXTRAS would break the two-way shape, and a rule that predicts the split would delete this entry outright.", @@ -223,7 +240,20 @@ "extras": { "initial_focus": "ptbtn11", "initial_focus_kind": "measured", - "initial_focus_why": "MEASURED, unlike the main menu's: EXTRAS opens focused on MISSION SELECT (live-extras.png). It is authored here only because there is nowhere else to put a measurement -- it is not a choice.", + "initial_focus_why": [ + "MEASURED, unlike the main menu's: EXTRAS opens focused on MISSION SELECT (live-extras.png). It is authored here only because there is nowhere else to put a measurement -- it is not a choice.", + "", + "⚠️ WEAKENED 2026-08-30 -- the OBSERVATION stands, its reading as an INITIAL", + "focus does not. It was taken on a single entry. Now that the main menu is known", + "to remember its cursor across a round trip, a one-entry reading of any screen", + "may be measuring HISTORY rather than what the screen opens on -- the same", + "objection that reframed the main menu's TUTORIAL/NEW GAME disagreement.", + "", + "Kept as `measured` because the frame really does show MISSION SELECT focused,", + "and kept as the port's opening item because it is the only reading there is.", + "🔴 If EXTRAS turns out to persist, this becomes history and the kind must", + "change with it." + ], "on_cancel": { "goto": "main_menu", "goto_name": "TITLE_MENU", diff --git a/docs/port/DECISIONS.md b/docs/port/DECISIONS.md index 409c0c5f..edf4c6eb 100644 --- a/docs/port/DECISIONS.md +++ b/docs/port/DECISIONS.md @@ -9,7 +9,7 @@ dies, which is what this file is for. -241 sections. Search this before re-deriving anything. +242 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) @@ -252,6 +252,7 @@ dies, which is what this file is for. * [The `+0x08` ask came back answered — and is not consumable yet](#the-0x08-ask-came-back-answered--and-is-not-consumable-yet) * [The pin moves to `formats-pin-2026-08-30b`, and the port stops owning `+0x08`](#the-pin-moves-to-formats-pin-2026-08-30b-and-the-port-stops-owning-0x08) * [The menu remembers its cursor — a measured P5 defect, fixed and scoped](#the-menu-remembers-its-cursor--a-measured-p5-defect-fixed-and-scoped) +* [🔴 Correction, same day: I encoded an absence of measurement as a finding](#correction-same-day-i-encoded-an-absence-of-measurement-as-a-finding) ## P0 — the exporter, 2026-08-28 @@ -12577,3 +12578,58 @@ settling it.** If focus persists, an "initial focus" reading not taken on a fres boot's first menu entry is measuring history — so the records that disagreed need not disagree about the game. My `NEW GAME` stays **authored**, on its own reasoning. Nothing here confirms it. + +## 🔴 Correction, same day: I encoded an absence of measurement as a finding + +The check I shipped this iteration asserted a **pair** — `focus_persists` on for +`main_menu`, off everywhere else — and called both halves agreement with the +contract. The Decoder caught it: **nothing measured that `extras` does not +persist.** The corpus has EXTRAS' opening item from *one entry* and Ⓑ restoring +the *parent's* focus 4/4. Neither says what a submenu's own cursor does on +re-entry. + +📌 **It is the exact mirror of the trap I had just congratulated myself on +avoiding.** I refused to let a derived menu-wide rule overwrite a measured value +— and then let *"not measured here"* become a positive assertion of the negative. +Both errors treat a gap in the corpus as if it carried information; they only +differ in which direction they fill it. + +And the failure mode was the bad one: **if the game does persist EXTRAS, the +check holds the port to the wrong behaviour and passes while doing it.** A wrong +assertion that fails is a nuisance; a wrong assertion that passes is a +manufactured fact. + +### What changed + +* `check_focus_persists` now asserts **only the measured half** against the + contract. +* The scope is a separate **`guard`** with its own outcome word, printing + `only main_menu -- AUTHORED DEFAULT, unmeasured elsewhere`. It still fails if + someone widens it, because that should be a deliberate edit with a `why` — but + a passing run can no longer be read as the game being known to reset. +* `focus_persists_why` records the correction rather than being rewritten, and + says the non-persistence half is **the port's default, not the game's + behaviour**. + +### And it weakens a `kind` label I had been leaning on + +EXTRAS' `initial_focus: ptbtn11` is marked `measured`, and the same objection +applies to it: **it was taken on a single entry.** Now that the main menu is known +to remember its cursor, a one-entry reading of any screen may be measuring +*history* rather than what the screen opens on — the same argument that reframed +the main menu's TUTORIAL-versus-NEW-GAME disagreement this morning. + +The observation stands; its *reading* as an initial focus does not. Left as +`measured` with the caveat attached, because the frame really does show MISSION +SELECT focused and it is the only reading there is — flagged so that if EXTRAS +turns out to persist, the label changes with it. + +⚠️ **Not building further on the non-persistence half** until their EXTRAS +re-entry run comes back. + +📌 Their sharpening of the pins point is the general form of all of this: **the +disciplines that fail this way are the ones that never visibly failed.** +Delivery-confirmation went to Ⓐ and Ⓑ because those broke once; the d-pad had +always quietly worked, so nothing directed attention at it. `kind: "measured"` is +the same shape — it has never visibly failed, so nothing has been checking what +each instance of it actually rests on. diff --git a/tools/port/contract-check b/tools/port/contract-check index 850011b0..36bb8817 100755 --- a/tools/port/contract-check +++ b/tools/port/contract-check @@ -219,17 +219,46 @@ def flow_buttons(screen): def check_focus_persists(h): - """The menu remembers its cursor -- measured on the MAIN MENU, one screen. + """The menu remembers its cursor -- MEASURED, on the main menu, one screen. - Checked as a pair: the behaviour must be on where it was measured and off - everywhere else. A one-sided check would pass a port that had quietly made it - a menu-wide rule, which would override `extras`' measured initial focus. + 🔴 This checked a PAIR until 2026-08-30: on for `main_menu`, off everywhere + else. The second half asserted that `extras` does NOT persist, and **nothing + measured that**. What the corpus has is EXTRAS' initial focus from a single + entry and Ⓑ restoring the PARENT's focus 4/4 — neither says what a submenu's + own cursor does on re-entry. So one measured behaviour and one absence of a + measurement were being reported identically, and if the game does persist + EXTRAS the check would have held the port to the wrong behaviour AND PASSED. + + The mirror of the trap it was written to avoid: refusing to let a derived + rule overwrite a measured value, then letting "not measured here" become a + positive assertion of the negative. Now only the measured half is asserted + against the contract; the scope is a guard, below. """ want = bool(re.search(r"the main menu remembers its cursor; re-entry is not a reset", h)) - scr = (jload("authored/flow.json") or {}).get("screens") or {} - on = [n for n, v in scr.items() if isinstance(v, dict) and v.get("focus_persists")] - report("menu remembers its cursor", want or None, on == ["main_menu"] and on, - want and on == ["main_menu"]) + got = (((jload("authored/flow.json") or {}).get("screens") or {}) + .get("main_menu", {}).get("focus_persists")) + report("menu remembers its cursor", want or None, got, want and got is True) + + +def guard_focus_scope(_h): + """NOT a contract check. A regression guard on an AUTHORED DEFAULT. + + No screen but `main_menu` persists its cursor in this port, and that is the + port's choice, not a finding: it preserves EXTRAS' measured opening item and + invents the least. Guarded so that widening it is a deliberate edit with a + `why`, and labelled so a passing run cannot be read as the game being known + to reset. + """ + global FAIL + on = sorted(n for n, v in ((jload("authored/flow.json") or {}).get("screens") or {}).items() + if isinstance(v, dict) and v.get("focus_persists")) + if on == ["main_menu"]: + print(f" {'focus_persists scope':<30} guard only main_menu" + f" -- AUTHORED DEFAULT, unmeasured elsewhere") + else: + FAIL += 1 + print(f" {'focus_persists scope':<30} 🔴 GUARD {on} -- widened past the" + f" one screen measured; needs a why and a measurement") def check_menu_labels(_h): @@ -295,7 +324,7 @@ def main(): for fn in (check_fade_quads, check_fade_out, check_plate_period, check_bgm_window, check_black_hold, check_menu_bank, check_splash_dwell, check_menu_labels, check_extras_labels, - check_wrap, check_focus_persists): + check_wrap, check_focus_persists, guard_focus_scope): fn(h) print() print(" A passing run means the port agrees with the contract ON THESE VALUES.")