From c5fe6a460cd400c26f43a13cbbfa1f6b76841c0a Mon Sep 17 00:00:00 2001 From: sylph-decoder Date: Sat, 29 Aug 2026 17:42:03 +0000 Subject: [PATCH] re: initial focus is TUTORIAL x3 / NEW GAME x3, and never anything else Two more data points for the Q5 instability, from today s drives. A run that pressed A with no d-pad movement ended in a tutorial mission, correlating +0.960 with the committed capture, so that boot opened on TUTORIAL. A later boot read NEW GAME from a focus detector on the first menu frame. Six boots on the same harness now: TUTORIAL three times, NEW GAME three times, and no other item ever observed. The distribution is not uniform over the five buttons -- only these two occur -- which is a real constraint on whatever selects initial focus and something an explanation will have to account for. Also records in METHOD a bug that cost a seven-minute driven boot: a value was clamped for readability BEFORE the comparison that used it. A focus detector printed a degenerate margin, so it was capped at 999; the cap ran before the vote-sorting step, two different votes compared equal, the stable sort kept the wrong one, and a correct NEW GAME became an out-of-range index and a refusal. The measurement was right throughout -- a cosmetic fix changed a decision. Clamp at the point of display, never upstream of a comparison. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01QsEPXWVaEpyfudtR6re1Pd --- docs/re/METHOD.md | 8 ++++++++ docs/re/menu-navigation-semantics.md | 14 +++++++++++++- tools/re-capture/focus_from_capture.py | 16 ++++++++++------ 3 files changed, 31 insertions(+), 7 deletions(-) diff --git a/docs/re/METHOD.md b/docs/re/METHOD.md index a6c7a876..4245ba4e 100644 --- a/docs/re/METHOD.md +++ b/docs/re/METHOD.md @@ -129,6 +129,14 @@ agent's loop prompt, i.e. nowhere durable. See [`README.md`](README.md) for the * **Raw grep cannot see inside compressed pak entries.** * **Commit messages go in a file** (`git commit -F`); a literal `|` in a table cell needs escaping; `git log --all -- ` can hang. +* 🔴 **Never clamp a value before something compares it.** A focus detector + printed a degenerate `margin=12359888888.89`, so it was capped at 999 to keep + the output readable. That cap ran *before* the vote-sorting step, so two + different votes compared **equal**, the stable sort kept the wrong one, and a + correct `NEW GAME` became an out-of-range index and a refusal — which aborted a + seven-minute driven boot. The measurement was right the whole time; a cosmetic + fix changed a decision. **Clamp at the point of display, never upstream of a + comparison that depends on the value.** * **`pkill -f PATTERN` / `pgrep -f PATTERN` match YOUR OWN command line.** Hit twice in one session: `pkill -9 -f adv_audio_cap.sh` killed the shell that ran it, and an `until ! pgrep -f "probe.py --run"` loop never exited because the diff --git a/docs/re/menu-navigation-semantics.md b/docs/re/menu-navigation-semantics.md index 68babcbc..04395664 100644 --- a/docs/re/menu-navigation-semantics.md +++ b/docs/re/menu-navigation-semantics.md @@ -130,12 +130,24 @@ So Q4's last row closes as **measured**: and `3 GP_LOAD` / `2 GP_SELECT_STORAGE` are name-match candidates and nothing more. -### Initial focus — a fourth data point, and it still varies +### Initial focus — six data points now, and it still varies This boot opened the main menu on **`NEW GAME`**. Running tally across four boots of the same harness: `TUTORIAL`, `TUTORIAL`, `NEW GAME`, `NEW GAME`. Unchanged conclusion: **do not hardcode it.** +✅ **Two more, 2026-08-29, and the split is now even.** A drive that pressed Ⓐ +with no d-pad movement ended in a **tutorial mission** (+0.960 against +`tutorial-mission-reached-then-crash.png`), so that boot opened on `TUTORIAL`. A +later boot read **`NEW GAME`** from a focus detector on the first menu frame. + +**Tally: `TUTORIAL` ×3, `NEW GAME` ×3.** Six boots, same harness, no other item +ever seen. ⚠️ It is *not* uniform across the five buttons — only these two occur — +which is a constraint on whatever selects it, and something a future explanation +has to account for. Still **do not hardcode it**, and note that +`newgame_path.sh`'s "NEW GAME is the first item so no d-pad is needed" is wrong +half the time — see [`s00a-drive-blocked-by-focus.md`](s00a-drive-blocked-by-focus.md). + ### The `NEW GAME` path completes — the `SELECT DATA` crash is state, not path A second run of the same path, 2026-08-28, **did not crash**: diff --git a/tools/re-capture/focus_from_capture.py b/tools/re-capture/focus_from_capture.py index 06a16934..82e2b64f 100755 --- a/tools/re-capture/focus_from_capture.py +++ b/tools/re-capture/focus_from_capture.py @@ -86,10 +86,13 @@ def classify(shot, A, B, ra, pitch): for ref, ref_idx in ((A, IDX_A), (B, IDX_B)): prof = row_profile(shot, ref) r, peak, med = peak_row(prof) - # A zero median makes this explode -- floor it at a level that is - # small but real, so a degenerate comparison reads as "huge" rather - # than 1e10, and cap it so the printed number stays meaningful. - margin = min(peak / max(med, 1.0), 999.0) + # A zero median makes this explode, so floor it. 🔴 Do NOT cap here: + # an earlier version capped at 999 to keep the printed number readable, + # which made two different votes compare EQUAL, and the stable sort then + # kept the wrong one -- turning a correct NEW GAME into an out-of-range + # index and a refusal. Cap at the point of DISPLAY, never before a + # comparison that depends on the value. + margin = peak / max(med, 1.0) idx = int(round((r - ra) / pitch)) votes.append((idx, margin, ref_idx)) # If the shot IS one of the references, that comparison is degenerate (all @@ -131,9 +134,10 @@ def main(): got, margin = classify(shot, A, B, ra, pitch) ok = got is not None and margin >= MIN_MARGIN if as_json: - print(json.dumps({"button": got, "margin": round(margin, 3), "decided": ok})) + print(json.dumps({"button": got, "margin": round(min(margin, 999.0), 3), + "decided": ok})) else: - print(f"{got if ok else 'UNDECIDED'} margin={margin:.2f}") + print(f"{got if ok else 'UNDECIDED'} margin={min(margin, 999.0):.2f}") return 0 if ok else 1 if __name__ == "__main__":