From 1ca90bfbd688abdfbd4df6895aaa951ce7497279 Mon Sep 17 00:00:00 2001 From: Sylpheed port agent Date: Sun, 30 Aug 2026 22:14:10 +0000 Subject: [PATCH] port: EXTRAS resets, measured -- and being right by luck is not evidence Ring at 347.5 on entry (MISSION SELECT), 427.5 after one delivery-confirmed DOWN, 347.5 on re-entry with the frame 0.0% different from first entry, screen confirmed by eye because an earlier run was fooled about which screen it was on. Two things settle here. The caveat on extras/initial_focus comes off: MISSION SELECT is a genuine initial focus, because a screen that RESETS cannot have a single-entry reading that is measuring history -- that objection was live only while persistence here was unknown. And focus_persists: false for extras is now written explicitly with kind: measured. Nothing changes at runtime, since the port already defaulted to false; the point is that an absent key and a measured false behave identically and mean opposite things -- 'nobody looked' versus 'the game was watched doing it' -- and only the second is visible to audit-kinds. It does not vindicate how it got there and is not recorded as if it did. For one iteration contract-check ASSERTED extras non-persistence with nothing behind it, the Decoder flagged it, and the measurement then agreed. Their separation is sharper than my own account was: declining to generalise the memory was correct, on the evidence then and on measurement now, since the two screens genuinely disagree -- but encoding 'not measured here' as a positive assertion of the negative was a different move that happened to land. Being right by luck does not retroactively make it evidence. The check is rewritten to rest on the measurement rather than left in place looking vindicated. guard_focus_scope no longer polices 'only main_menu': there is no menu-wide rule to state, since two measured screens disagree. It now states both measured values and counts the screens that say nothing, printing UNMEASURED, not 'resets'. Untested and not built on: OPTIONS, LOAD GAME, TUTORIAL. And nobody can separate 'resets to MISSION SELECT' from 'resets to the top item' -- they coincide, since ptbtn11 is both. The port's value is right under either reading and the reason is not established, which matters the day a screen is authored whose opening item is not its first. 16 kind labels audited clean, 14 controls firing, every asserting check passes. The P5 walk artifact now matches a measurement on both halves rather than one measurement and one default. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01N7FiFFFwbvG2uxdcEh8HyF --- authored/flow.json | 58 +++++++++++++++++++++++++++++------ docs/port/DECISIONS.md | 64 ++++++++++++++++++++++++++++++++++++++- tools/port/contract-check | 53 +++++++++++++++++++++++--------- 3 files changed, 150 insertions(+), 25 deletions(-) diff --git a/authored/flow.json b/authored/flow.json index ce3f3693..4a06f5e2 100644 --- a/authored/flow.json +++ b/authored/flow.json @@ -341,19 +341,59 @@ "extras": { "initial_focus": "ptbtn11", "initial_focus_kind": "measured", + "focus_persists": false, + "focus_persists_kind": "measured", + "focus_persists_why": [ + "MEASURED 2026-08-30 -- EXTRAS RESETS. HANDOFF `4ed75e6`: ring back to", + "y=347.5 on re-entry after a confirmed DOWN, frame 0.0 % different from the", + "first entry, and the screen confirmed by eye as EXTRAS because an earlier", + "run was fooled about which screen it was on.", + "", + "📌 WRITTEN EXPLICITLY, THOUGH THE PORT'S DEFAULT IS ALREADY false. The", + "absent key and the measured false behave identically and mean completely", + "different things: one is 'nobody looked', the other is 'the game was", + "watched doing it'. `tools/port/audit-kinds` can see the second and not the", + "first, which is the whole reason for spending a key on it.", + "", + "🔴 AND THIS IS NOT A VINDICATION OF HOW IT GOT HERE. For one iteration the", + "port ASSERTED non-persistence for EXTRAS in `contract-check` while nothing", + "had measured it; the Decoder flagged that, and it turned out right. Being", + "right by luck does not retroactively make it evidence -- declining to", + "generalise the memory was the correct move, and encoding 'not measured", + "here' as a positive claim was a different and wrong one that happened to", + "land. The measurement is what makes it true; the assertion never did.", + "", + "⚠️ Do NOT generalise in either direction: main_menu persists, EXTRAS resets,", + "and OPTIONS / LOAD GAME / TUTORIAL are untouched." + ], "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." + "✅ CAVEAT LIFTED 2026-08-30 -- MEASURED, not a single-entry reading any more.", + "HANDOFF `4ed75e6`, docs/re/data/extras-focus-resets.txt: EXTRAS opens at ring", + "y=347.5 on MISSION SELECT, moves to 427.5 after one delivery-confirmed DOWN,", + "and returns to 347.5 on re-entry with the frame 0.0 % different from the first", + "entry. Because this screen RESETS, a single-entry reading of it is not", + "measuring history -- which is precisely what made the caveat necessary while", + "persistence here was unknown.", + "", + "⚠️ WHAT IS STILL AMBIGUOUS, and it matters if another screen is ever authored:", + "nobody can separate \"resets to MISSION SELECT\" from \"resets to the TOP ITEM\".", + "They coincide here -- ptbtn11 is both. The port's value is correct under either", + "reading, and the REASON is not established.", + "", + "The superseded caveat is kept below.", + " (was) ⚠️ WEAKENED 2026-08-30 -- the OBSERVATION stands, its reading as an INITIAL", + " (was) focus does not. It was taken on a single entry. Now that the main menu is known", + " (was) to remember its cursor across a round trip, a one-entry reading of any screen", + " (was) may be measuring HISTORY rather than what the screen opens on -- the same", + " (was) objection that reframed the main menu's TUTORIAL/NEW GAME disagreement.", + " (was) ", + " (was) Kept as `measured` because the frame really does show MISSION SELECT focused,", + " (was) and kept as the port's opening item because it is the only reading there is.", + " (was) 🔴 If EXTRAS turns out to persist, this becomes history and the kind must", + " (was) change with it." ], "on_cancel": { "goto": "main_menu", diff --git a/docs/port/DECISIONS.md b/docs/port/DECISIONS.md index 8da1c9cb..12e77519 100644 --- a/docs/port/DECISIONS.md +++ b/docs/port/DECISIONS.md @@ -9,7 +9,7 @@ dies, which is what this file is for. -245 sections. Search this before re-deriving anything. +246 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) @@ -256,6 +256,7 @@ dies, which is what this file is for. * [The `kind` sweep I said I owed: 15 labels, and 7 rested on a neighbour's argument](#the-kind-sweep-i-said-i-owed-15-labels-and-7-rested-on-a-neighbours-argument) * [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) ## P0 — the exporter, 2026-08-28 @@ -12767,3 +12768,64 @@ class of error these checks exist to catch.** Thirteen controls now, all firing. ❔ **EXTRAS remains unmeasured** — the run meant to settle it navigated to OPTIONS believing it was EXTRAS, so `initial_focus: ptbtn11` stays undecided and nothing here is built on it. + +## EXTRAS resets — measured. The assertion was right and that does not make it evidence. + +Ring at **347.5** on entry (`MISSION SELECT`), **427.5** after one +delivery-confirmed DOWN, **347.5** on re-entry, with the frame **0.0 %** +different from the first entry — and the screen confirmed by eye as EXTRAS, +because an earlier run was fooled about which screen it was on. + +Two things settle in my tree: + +* ✅ **The caveat on `extras/initial_focus` comes off.** `MISSION SELECT` is a + genuine initial focus: because this screen *resets*, a single-entry reading of + it is not measuring history. That objection was live only while persistence + here was unknown. +* ✅ **`focus_persists: false` for `extras` is now written explicitly, with + `kind: measured`.** The port already defaulted to false, so nothing changes at + runtime. 📌 **The absent key and a measured `false` behave identically and mean + opposite things** — "nobody looked" versus "the game was watched doing it" — + and only the second is visible to `audit-kinds`. That is the whole reason to + spend a key on it. + +### 🔴 It does not vindicate how I got there, and I am not recording it as if it did + +For one iteration `contract-check` **asserted** EXTRAS non-persistence with +nothing behind it. The Decoder flagged it; the measurement then agreed with it. + +Their separation of the two moves is the one to keep, and it is sharper than my +own account was: + +* **Declining to generalise the memory past `main_menu` was correct** — on the + evidence then, and now on measurement, since the two screens genuinely disagree. +* **Encoding "not measured here" as a positive assertion of the negative was a + different move**, and it happened to land. **Being right by luck does not + retroactively make it evidence.** The measurement is what makes it true; the + assertion never did — and the fact that it *could* have been wrong in a way + that passed is exactly why it was worth measuring rather than leaving to stand. + +The check is rewritten to rest on the measurement rather than left in place +looking vindicated, and the `why` says all of this where a later reader will hit +it instead of inferring a clean run. + +### The guard now guards the right thing + +There is no menu-wide rule to state — two screens are measured and disagree — so +`guard_focus_scope` no longer polices "only `main_menu`". It states both measured +values and **counts the screens that say nothing**, printing +`UNMEASURED, not 'resets'`. The silent ones are the port defaulting, not a +finding. + +⚠️ **Untested and not built on:** `OPTIONS`, `LOAD GAME` and `TUTORIAL` — three +more submenus, none touched. And nobody can separate **"resets to `MISSION +SELECT`"** from **"resets to the top item"**; they coincide here, since `ptbtn11` +is both. The port's value is right under either reading and **the reason is not +established** — which matters the day a screen is authored whose opening item is +not its first. + +📌 Their symmetric caution, worth more than the result: their ring reader now +**refuses to name a row outside its calibration rather than guessing**, and that +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. diff --git a/tools/port/contract-check b/tools/port/contract-check index 0788b4f3..ada810c6 100755 --- a/tools/port/contract-check +++ b/tools/port/contract-check @@ -275,25 +275,46 @@ def check_focus_persists(h): 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. +def check_extras_resets(h): + """EXTRAS resets -- MEASURED 2026-08-30, and it used to be asserted unmeasured. - 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. + For one iteration the port asserted this with nothing behind it, which the + Decoder flagged; it then measured it and the assertion was right. That does + not make the assertion evidence, so the check is rewritten to rest on the + measurement rather than being left to look vindicated. + """ + want = False if re.search(r"EXTRAS resets, the main menu persists", h) else None + ex = ((jload("authored/flow.json") or {}).get("screens") or {}).get("extras", {}) + got = ex.get("focus_persists") + report("extras resets its cursor", want, f"{got} [{ex.get('focus_persists_kind')}]", + want is not None and got is False and ex.get("focus_persists_kind") == "measured") + + +def guard_focus_scope(_h): + """NOT a contract check. A guard over the screens NOBODY HAS LOOKED AT. + + Two screens are now measured and disagree -- `main_menu` persists, `extras` + resets -- so there is no menu-wide rule to state. What this guards is the + rest: `OPTIONS`, `LOAD GAME` and `TUTORIAL` are untouched, and their absent + `focus_persists` is the port defaulting, not a finding. + + 📌 The absent key and a measured `false` behave identically and mean opposite + things. That is why `extras` now spends a key on saying `false` out loud. """ 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") + scr = ((jload("authored/flow.json") or {}).get("screens") or {}) + stated = {n: v.get("focus_persists") for n, v in scr.items() + if isinstance(v, dict) and "focus_persists" in v} + silent = sorted(n for n, v in scr.items() + if isinstance(v, dict) and "focus_persists" not in v) + ok = stated == {"main_menu": True, "extras": False} + if ok: + print(f" {'focus_persists scope':<30} guard {stated} measured;" + f" {len(silent)} screen(s) silent = UNMEASURED, not 'resets'") else: FAIL += 1 - print(f" {'focus_persists scope':<30} 🔴 GUARD {on} -- widened past the" - f" one screen measured; needs a why and a measurement") + print(f" {'focus_persists scope':<30} 🔴 GUARD {stated} -- a screen states" + f" this without a measurement behind it") def check_menu_labels(_h): @@ -344,6 +365,8 @@ CONTROLS = [ # class of error the checks exist to catch. (check_splash_times, "times are\n`[0,15,30,45,235,239,251,255]`", "times are\n`[1,16,31,46,236,240,252,256]`"), + (check_extras_resets, "EXTRAS resets, the main menu persists", + "EXTRAS persists, the main menu persists"), (check_initial_focus, "**Initial focus on a fresh boot is `NEW GAME`**", "**Initial focus on a fresh boot is `TUTORIAL`**"), ] @@ -368,7 +391,7 @@ def main(): check_bgm_window, check_black_hold, check_menu_bank, check_splash_dwell, check_menu_labels, check_extras_labels, check_wrap, check_focus_persists, guard_focus_scope, - check_splash_times, check_initial_focus): + check_splash_times, check_initial_focus, check_extras_resets): fn(h) print() print(" A passing run means the port agrees with the contract ON THESE VALUES.")