re: measure the boot-nav title gate — and withdraw my own diagnosis
Last iteration I said launch_mission died because skip_intro only tests for the title on a static frame, gated at rmse <= 1500, and that run logged 1503 and 1549 just above the cut. I also said the fix was NOT to nudge 1500 but to measure both signals through a boot first. Measured, and the diagnosis does not survive. boot_trace.sh logs the two signals skip_intro decides on -- frame-to-frame RMSE and the is_title.py green-glyph count -- through a clean boot with no presses at all. One run, 29 samples over 484 s: 8 samples had rmse <= 1500, so the gate OPENED eight times 0 samples had glyph > 0, so the title was never seen At t=145 s the RMSE was 1205, comfortably inside the threshold, and the glyph test was called and answered zero. A frame can be perfectly static without being the title -- the intro movie has long quiet stretches, three reading RMSE exactly 0. So 1503/1549 were almost certainly movie frames too, and raising the constant would have admitted two more of them. What is left is narrower and honest: the interactive title never appeared, rather than appearing and being missed by a threshold. The limitation is recorded rather than buried: the tracer intended 1 s sampling and achieved 16.9 s, because each iteration forks two screenshots, ImageMagick compare and a fresh Python. So this does NOT prove the title never appeared -- only that it was absent from 29 samples. A window shorter than ~17 s falls between them. The recorded next step is to make the tracer sample at the rate it claims before concluding anything stronger. Artifact: docs/re/captures/boot-signal-trace.tsv. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PMRJjbxLqZtsb5Vb7KunPE
This commit is contained in:
@@ -1033,25 +1033,31 @@ premise was wrong.**
|
||||
Candidates: the **7 `.embsec_` sections** (VAs 0x84D0000–0x86AC000, ~129 KB
|
||||
total, executable) or a hashed record in `hidden/MiscBin.pak`. **Finding it
|
||||
gives the actual per-phase clear condition for every stage.**
|
||||
* 🚧 **(2026-08-25) BLOCKED — the live test of the `reset_phase_threads` rename
|
||||
never got to run, because boot-nav cannot reach the title.** The experiment is
|
||||
ready and worth doing: built-in 100 clears the trigger container and frees
|
||||
every thread but the caller, so at a phase terminator **both**
|
||||
`[phase+272+20]` (triggers queued) and `[phase+216+8]` (coroutines alive)
|
||||
should collapse; if they climb straight through a phase boundary instead, the
|
||||
reading is wrong. `tools/re-capture/phase_watch.py` now prints both counters —
|
||||
**written this session but never yet exercised against a live guest.**
|
||||
**The blocker, measured:** `skip_intro.sh` only tests for the title when the
|
||||
frame is static, gated at `rmse <= 1500`. A 381 s run produced 18 "waiting it
|
||||
out" samples, and two of them — **1503 at 104 s and 1549 at 139 s** — sat just
|
||||
above the cut, so `is_title.py` was never even called and the single allowed
|
||||
press was never spent. The attract loop then restarts and the wait never ends.
|
||||
▶️ **First step, and it is NOT "raise the constant":** log `rmse` *and* the
|
||||
`is_title.py` glyph count every second through a boot, so the true-title and
|
||||
attract-demo distributions can be seen; only then decide the gate. Tuning 1500
|
||||
to make one run pass would be fitting to a single sample. Note the surrounding
|
||||
comments are emphatic that tapping through the movies is what breaks the
|
||||
title, so the press budget really is one.
|
||||
* ❌ **(2026-08-25) My own boot-nav diagnosis, MEASURED AND WITHDRAWN.** I said
|
||||
the run died because `skip_intro.sh` gates the title test at `rmse <= 1500`
|
||||
and the run logged 1503/1549, just above the cut. Measured over a clean
|
||||
no-press boot ([`boot-nav-title-gate.md`](boot-nav-title-gate.md),
|
||||
`captures/boot-signal-trace.tsv`): the gate **opened eight times** in 29
|
||||
samples, and at `t=145 s` the RMSE was **1205** — inside the threshold — with
|
||||
`is_title.py` answering **0 glyph pixels**. The glyph count was 0 in *all* 29
|
||||
samples over 484 s. A static frame is not the title; the intro movie has long
|
||||
quiet stretches, three of them reading RMSE exactly 0. So the gate is not what
|
||||
stopped it, and raising the constant would have admitted two more movie frames.
|
||||
🟡 Narrowed: **the interactive title never appeared**, rather than appearing and
|
||||
being missed. ❔ **But the run does not prove that** — the tracer intended 1 s
|
||||
sampling and achieved **16.9 s** (two screenshots + ImageMagick `compare` + a
|
||||
fresh Python per iteration), so a title window under ~17 s falls between
|
||||
samples. ▶️ First step: make the tracer sample at the rate it claims — one
|
||||
long-lived Python process computing both signals — then re-run. Second: check
|
||||
whether the fast 2026-08-25 run had a warm shader cache that this one, started
|
||||
after `rm -f /dev/shm/xenia_*`, did not.
|
||||
* 🚧 **(2026-08-25) STILL UNRUN — the live test of the `reset_phase_threads`
|
||||
rename.** Built-in 100 clears the trigger container and frees every thread but
|
||||
the caller, so at a phase terminator **both** `[phase+272+20]` (triggers
|
||||
queued) and `[phase+216+8]` (coroutines alive) should collapse; if they climb
|
||||
straight through a phase boundary instead, the reading is wrong.
|
||||
`tools/re-capture/phase_watch.py` prints both — **written, still never
|
||||
exercised against a live guest.** Blocked behind the boot-nav item above.
|
||||
* 🟡 **(2026-08-25) The legacy IDXD string-pool reader is wrong far more often
|
||||
than assumed** — now measurable for the first time, since the record table gives
|
||||
a ground truth. Verified by hand: `FCSRange` (the module doc's own canonical
|
||||
|
||||
85
docs/re/boot-nav-title-gate.md
Normal file
85
docs/re/boot-nav-title-gate.md
Normal file
@@ -0,0 +1,85 @@
|
||||
# The boot-nav title gate — my own diagnosis, measured and withdrawn
|
||||
|
||||
Status: ❌ **the previous diagnosis is WITHDRAWN.** 🟡 the real failure is
|
||||
narrowed but not settled. ❔ the measurement has a limitation that matters, and
|
||||
it is stated below rather than buried.
|
||||
|
||||
## What I claimed last iteration
|
||||
|
||||
`launch_mission.sh` failed to reach the mission: 381 s of `skip_intro.sh`
|
||||
printing "movie … waiting it out", 18 times, and the ready room never arrived.
|
||||
`skip_intro.sh` only tests for the interactive title when the frame is **static**,
|
||||
gated at `rmse <= 1500`, and that run logged **1503 at 104 s** and **1549 at
|
||||
139 s** — just above the cut. I wrote that the gate had therefore blocked the
|
||||
title test and eaten the run.
|
||||
|
||||
I also wrote, correctly, that the fix was *not* to nudge 1500, and that the first
|
||||
step was to measure both signals through a whole boot. That is what this is.
|
||||
|
||||
## The measurement
|
||||
|
||||
`tools/re-capture/boot_trace.sh` logs, without pressing anything, the two signals
|
||||
`skip_intro.sh` decides on: the frame-to-frame RMSE (two grabs 0.6 s apart) and
|
||||
the green-glyph pixel count from `is_title.py` — independent tests for "is this
|
||||
static" and "is this the interactive title".
|
||||
|
||||
One clean boot, no input at any point:
|
||||
[`captures/boot-signal-trace.tsv`](captures/boot-signal-trace.tsv).
|
||||
|
||||
```
|
||||
29 samples spanning 484 s
|
||||
8 samples with rmse <= 1500 (the gate OPENED eight times)
|
||||
0 samples with glyph > 0 (the title was never seen)
|
||||
```
|
||||
|
||||
The eight low-RMSE samples: `t=12, 29, 62` (rmse 0), `t=99` (1486), `t=115`
|
||||
(1350), `t=145` (**1205**), `t=316` (0), `t=484` (0).
|
||||
|
||||
## ❌ Why the old diagnosis is wrong
|
||||
|
||||
The gate is not what stopped it. It **opened eight times**, and at `t=145` the
|
||||
RMSE was **1205** — comfortably inside the threshold — so `is_title.py` was
|
||||
called and answered **0 glyph pixels**. A frame can be perfectly static and still
|
||||
not be the title; the intro movie has long quiet stretches, and three samples
|
||||
read an RMSE of exactly 0.
|
||||
|
||||
So `1503`/`1549` in the failing run were almost certainly *also* not the title,
|
||||
and raising the threshold to admit them would have admitted two more movie
|
||||
frames, not the title. The instinct to leave the constant alone was right; the
|
||||
reason I gave for it was wrong.
|
||||
|
||||
## 🟡 What the failure actually is
|
||||
|
||||
Narrowed, not settled: **the interactive title never appeared at all** in 484 s
|
||||
of a no-press boot — not "appeared and was missed by the gate". The glyph count
|
||||
was 0 in all 29 samples, including the last one at 484 s where the frame was
|
||||
completely static (rmse 0).
|
||||
|
||||
A static frame with no glyph at 484 s is consistent with the attract loop having
|
||||
returned to a title that, per `menu_draw_capture.sh`, "accepts nothing at all" —
|
||||
but this run never pressed anything, so nothing can have broken it. That points
|
||||
at the boot itself, not at the navigation.
|
||||
|
||||
## ❔ The limitation, stated plainly
|
||||
|
||||
**The intended sampling rate was 1 s; the achieved rate was 16.9 s.** Each
|
||||
iteration takes two screenshots, runs ImageMagick `compare` and starts a Python
|
||||
interpreter, and that costs ~15 s per loop in this container. So 29 samples cover
|
||||
484 s with big gaps between them.
|
||||
|
||||
That means this run does **not** prove the title never appeared — only that it
|
||||
was absent from every one of 29 samples. A title window shorter than ~17 s could
|
||||
fall between two samples. Before concluding anything stronger, the tracer needs
|
||||
to get cheap enough to sample at the rate it claims: keep one Python process
|
||||
alive and compute the RMSE and the glyph count in it, instead of forking
|
||||
`compare` and a fresh interpreter every second.
|
||||
|
||||
## What this does not settle
|
||||
|
||||
* Whether the title ever appears on this boot path at all.
|
||||
* Whether the earlier "readyroom at 9 s, IN FLIGHT at 37 s" run (recorded
|
||||
2026-08-25 in `BACKLOG.md`) used a warm shader cache that this run lacked —
|
||||
this one was started after `rm -f /dev/shm/xenia_*`, which the fast run may not
|
||||
have been.
|
||||
* The live test this was all for — whether the trigger container and the
|
||||
coroutine list both collapse at a phase terminator — remains unrun.
|
||||
30
docs/re/captures/boot-signal-trace.tsv
Normal file
30
docs/re/captures/boot-signal-trace.tsv
Normal file
@@ -0,0 +1,30 @@
|
||||
secs rmse glyph
|
||||
12 0 0
|
||||
29 0 0
|
||||
47 11264 0
|
||||
62 0 0
|
||||
80 3250 0
|
||||
99 1486 0
|
||||
115 1350 0
|
||||
130 1512 0
|
||||
145 1205 0
|
||||
159 2714 0
|
||||
171 2712 0
|
||||
189 34429 0
|
||||
201 30721 0
|
||||
218 10379 0
|
||||
234 12487 0
|
||||
252 14940 0
|
||||
273 23650 0
|
||||
293 12200 0
|
||||
308 35782 0
|
||||
316 0 0
|
||||
332 8713 0
|
||||
351 21680 0
|
||||
369 7567 0
|
||||
386 4642 0
|
||||
412 9647 0
|
||||
436 6524 0
|
||||
458 18867 0
|
||||
476 11594 0
|
||||
484 0 0
|
||||
|
41
tools/re-capture/boot_trace.sh
Executable file
41
tools/re-capture/boot_trace.sh
Executable file
@@ -0,0 +1,41 @@
|
||||
#!/usr/bin/env bash
|
||||
# Log the two signals `skip_intro.sh` decides on, every second, through a whole
|
||||
# boot -- WITHOUT pressing anything.
|
||||
#
|
||||
# Why: skip_intro only runs the title test when the frame is static, gated at
|
||||
# `rmse <= 1500`. A 381 s run measured 1503 and 1549 -- just above the cut -- so
|
||||
# the title test was never called and the one allowed press was never spent.
|
||||
# The fix is NOT to nudge 1500; a threshold moved to make one run pass is fitted
|
||||
# to a single sample. This produces the distribution the gate should be chosen
|
||||
# from: for every second, the frame-to-frame RMSE and the green-glyph count,
|
||||
# which are independent measurements of "is this a static frame" and "is this
|
||||
# the interactive title".
|
||||
#
|
||||
# Output is TSV on stdout: seconds, rmse, glyph_px.
|
||||
set -u
|
||||
export HOME=/sylph-home/re
|
||||
DISP="${DISPLAY:-:98}"
|
||||
SD="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
alive(){ ps -o pid=,stat= -C xenia_canary 2>/dev/null | awk '$2 !~ /^Z/ {print $1}'; }
|
||||
|
||||
until xdotool search --name "Xenia-canary" >/dev/null 2>&1; do
|
||||
[ -n "$(alive)" ] || { echo "# EMULATOR GONE before the window appeared" >&2; exit 4; }
|
||||
sleep 1
|
||||
done
|
||||
printf 'secs\trmse\tglyph\n'
|
||||
deadline=$(( SECONDS + ${1:-600} ))
|
||||
while [ $SECONDS -lt $deadline ]; do
|
||||
rm -f /tmp/bt1.png /tmp/bt2.png
|
||||
screenshot /tmp/bt1.png >/dev/null 2>&1; sleep 0.6
|
||||
screenshot /tmp/bt2.png >/dev/null 2>&1
|
||||
# Same liveness guard as skip_intro: two STALE files compare to a constant
|
||||
# non-zero RMSE, which reads exactly like a playing movie.
|
||||
if [ ! -s /tmp/bt1.png ] || [ ! -s /tmp/bt2.png ]; then
|
||||
echo "# SCREENSHOT FAILED at ${SECONDS}s" >&2; exit 5
|
||||
fi
|
||||
[ -n "$(alive)" ] || { echo "# EMULATOR GONE at ${SECONDS}s" >&2; exit 4; }
|
||||
d=$(compare -metric RMSE /tmp/bt1.png /tmp/bt2.png null: 2>&1 | sed 's/ .*//' | cut -d. -f1)
|
||||
g=$(python3 "$SD/is_title.py" /tmp/bt2.png 400 2>/dev/null | sed 's/[^0-9]*\([0-9]*\).*/\1/')
|
||||
printf '%s\t%s\t%s\n' "$SECONDS" "${d:-0}" "${g:-0}"
|
||||
sleep 1
|
||||
done
|
||||
Reference in New Issue
Block a user