diff --git a/docs/port/MISSION.md b/docs/port/MISSION.md index 416ccbf..a2bc702 100644 --- a/docs/port/MISSION.md +++ b/docs/port/MISSION.md @@ -120,8 +120,18 @@ afterwards is using canary's own persistence, not patching its code. two-grab polling loop therefore samples every **~41 s**, against a title screen this corpus documents as lasting *a few seconds*. Four runs β€” two locales, two launch paths, two gamma settings β€” were all blinking slower than the event. -Everything below is the trail that led there; the conclusions it draws about the -*game* are withdrawn. See [capture-harness-status](../re/capture-harness-status.md). +πŸ”΄ **…and that diagnosis is itself refuted (same day).** The faster probe was +built β€” 3.98 s β†’ **0.29 s** per sample, 13.7Γ—, control-verified at 753/327 β€” and +at 3.99 fps for **420 continuous seconds (1 674 samples)** the interactive title +*still* never appeared. Sampling rate was a real defect and not the cause. +βœ… So "the interactive title does not appear mid-run without a pad press" is +reinstated, now as a dense measurement. ⚠️ Its reach is a **mid-run** window; it +says nothing about the boot title. +🟑 Leading hypothesis, unconfirmed: the plate appears only in the **boot** title +window and the attract loop's title has none β€” which is precisely what +`title_states_capture.sh` was written to test. The experiment is to start the +fast probe from t=0, not attach to a run already in progress. +See [capture-harness-status](../re/capture-harness-status.md). **🟑 Still not settled β€” two runs, and the reason moved again.** diff --git a/docs/re/METHOD.md b/docs/re/METHOD.md index 7c537e5..37f7cdc 100644 --- a/docs/re/METHOD.md +++ b/docs/re/METHOD.md @@ -510,3 +510,11 @@ agent's loop prompt, i.e. nowhere durable. See [`README.md`](README.md) for the classifies movie-vs-static on an *absolute* rmse threshold β€” so the gamma change biased the very classifier the run depended on. Harness thresholds tuned on one output configuration are not portable to another. +* **Fixing the instrument is how you test the explanation that blamed it.** I + diagnosed four failed runs as "the probe samples slower than the event", + wrote it up, and withdrew three earlier conclusions on that basis. Building the + faster probe refuted it: at 100Γ— the sampling density, over 420 unbroken + seconds, the event still never occurred. The diagnosis was a real defect that + happened not to be the cause β€” the most seductive kind, because fixing it feels + like confirmation. A plausible mechanism that explains a failure is a + hypothesis, and the fix is its experiment, not its proof. diff --git a/docs/re/REFUTED.md b/docs/re/REFUTED.md index 5edf2ae..eec5872 100644 --- a/docs/re/REFUTED.md +++ b/docs/re/REFUTED.md @@ -436,3 +436,13 @@ neighbourhood, not just the line. costs 10.8 s while the emulator runs (0.117 s idle, 92Γ—). A title lasting a few seconds would be missed. The observations stand; the conclusions drawn from them do not. [`capture-harness-status.md`](capture-harness-status.md) +* "the boot harness fails because its polling loop samples every ~41 s, slower + than the title screen lasts" β†’ **mine, and refuted by my own fix.** The + sampling defect was real (3.98 s β†’ 0.29 s per sample, 13.7Γ—, control-verified + at 753/327), but a probe running at 3.99 fps for **420 continuous seconds β€” + 1 674 samples β€” still saw zero green-β’Ά pixels.** Sampling rate was not the + cause. [`capture-harness-status.md`](capture-harness-status.md) +* ~~"neither locale reaches the interactive title without a pad press"~~ β†’ + withdrawn last iteration for want of evidence, now **reinstated as a + measurement**: 1 674 dense samples over 420 s, English, zero glyph frames. + ⚠️ Reach: a mid-run window only; it says nothing about the boot title. diff --git a/docs/re/capture-harness-status.md b/docs/re/capture-harness-status.md index 6caa5a9..31947b9 100644 --- a/docs/re/capture-harness-status.md +++ b/docs/re/capture-harness-status.md @@ -57,7 +57,61 @@ inflates that difference and biases every frame toward "movie". **The capture harness's tuning is coupled to the display settings** β€” changing gamma and capture behaviour in one run confounds both. -## The fix, not applied here +## πŸ”΄ The fix works β€” and it REFUTES the diagnosis above + +Built and measured (`tools/re-capture/fast_title_probe.py`): one long-lived +`ffmpeg` x11grab stream, raw RGB frames, glyph counted in numpy. No per-sample +process startup, no PNG encode, no `convert -crop`. + +| probe | seconds per sample, emulator running | +|---|---| +| the wrapper `screenshot` | **3.98** | +| `import -window root` β†’ PPM | 1.20 | +| **long-lived x11grab stream** | **0.29** | + +**13.7Γ— faster**, and the counter is control-verified against the committed +frames β€” it returns **753** on `live-title-press-a.png` and **327** on +`live-main-menu.png`, byte-identical to `is_title.py`. + +Then it was pointed at a running game: + +``` +332 frames in 85.3 s = 3.89 fps; max glyph 0 +1674 frames in 420.0 s = 3.99 fps; max glyph 0 +``` + +**1 674 consecutive samples over seven unbroken minutes, four per second, and the +interactive title never appeared.** So the sampling rate was a real defect and +*not* the cause. The hypothesis on this page β€” that the harness was blinking +slower than the event β€” is **mine, and refuted by my own fix**. + +## What that restores + +Last iteration I withdrew three conclusions on the strength of that hypothesis. +The withdrawal was right at the time (15 samples at 41 s intervals cannot support +them) and is now **superseded by better evidence**: dense sampling says the +interactive title genuinely does not appear in a mid-run window. Reinstated as a +**measurement**, with its reach: + +* βœ… over **420 continuous seconds**, English, `gamma_type = 2`, ~13 minutes into + a run with no pad input, **zero** frames carried the green β’Ά glyph. +* ⚠️ Reach: this covers a **mid-run** window only. It says nothing about the + first minutes of boot. + +## 🟑 The leading hypothesis, not confirmed + +The corpus already suspects the answer. `title_states_capture.sh` exists to test +*"whether the interactive one draws `ptbtn00` (the PRESS β’Ά plate) and the other +does not"* β€” i.e. the title appears **twice**: once at the end of the boot +sequence, and again from the attract loop, and only the first may carry the +plate. If so, the plate's window is early and one-shot, and no amount of +mid-run sampling will ever find it. + +That is consistent with everything measured, and it is **not confirmed**. The +test is to start the fast probe *before* the boot title β€” from t=0 rather than +attaching to a run already in progress. + +## The original fix note, kept Make the probe cheap enough to sample faster than the title window: grab a small region rather than the full surface, drop the ImageMagick `convert` round trip, diff --git a/tools/re-capture/fast_title_probe.py b/tools/re-capture/fast_title_probe.py new file mode 100755 index 0000000..6c26e77 --- /dev/null +++ b/tools/re-capture/fast_title_probe.py @@ -0,0 +1,34 @@ +"""Fast title probe: one long-lived x11grab stream, ~3.5 fps, glyph count per frame. + +Replaces the two-`screenshot` polling loop, which costs ~4-11 s per grab while +xenia runs and so samples every ~41 s -- slower than the title screen lasts. +Counter is byte-identical to is_title.py (controlled: 753 on the committed title +capture, 327 on the main menu). +""" +import subprocess, sys, time +import numpy as np +W,H = 1280,720 +LIMIT = float(sys.argv[1]) if len(sys.argv)>1 else 120 +NEED = int(sys.argv[2]) if len(sys.argv)>2 else 400 +p = subprocess.Popen(["ffmpeg","-loglevel","error","-f","x11grab","-draw_mouse","0", + "-video_size",f"{W}x{H}","-i",":98","-r","4","-f","rawvideo","-pix_fmt","rgb24","-"], + stdout=subprocess.PIPE, bufsize=W*H*3*2) +n=W*H*3; t0=time.time(); frames=0; best=0; hits=0 +while time.time()-t0 < LIMIT: + buf=p.stdout.read(n) + if len(buf)130)&(g-r>45)&(g-b>45)).sum()) + frames+=1 + if c>best: + best=c + if c>=NEED: + from PIL import Image + Image.fromarray(a.astype(np.uint8)).save("/sylph-home/re/shots/fast-title.png") + if c>=NEED: + hits+=1 + if hits==1: print(f"TITLE at t={time.time()-t0:.1f}s glyph={c} (frame {frames})", flush=True) +p.kill() +dt=time.time()-t0 +print(f"{frames} frames in {dt:.1f}s = {frames/dt:.2f} fps; max glyph {best}; hits {hits}")