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.")