re: build the fast probe -- and it refutes the diagnosis that motivated it

Last iteration I blamed four failed runs on the probe sampling every
~41 s, slower than the title screen lasts, and withdrew three earlier
conclusions on that basis. Building the fix tested the claim and killed
it.

The speedup is real and control-verified. One long-lived ffmpeg x11grab
stream, raw RGB, glyph counted in numpy -- no per-sample process startup,
no PNG encode, no convert -crop:

  wrapper `screenshot`            3.98 s per sample (emulator running)
  import -window root -> PPM      1.20 s
  long-lived x11grab stream       0.29 s          13.7x

The counter is byte-identical to is_title.py: 753 on the committed title
capture, 327 on the main menu.

Pointed at a running game it says the opposite of what I expected:

  332 frames in  85.3 s = 3.89 fps; max glyph 0
  1674 frames in 420.0 s = 3.99 fps; max glyph 0

1674 consecutive samples over seven unbroken minutes, four per second,
zero green-(A) pixels. Sampling rate was a real defect that happened not
to be the cause.

So "neither locale reaches the interactive title without a pad press" --
withdrawn last iteration for want of evidence -- is reinstated, now as a
dense measurement, with its reach stated: a MID-RUN window only, silent
about the boot title.

Leading hypothesis, unconfirmed: the PRESS (A) plate appears only in the
boot title window and the attract loop's title carries none, which is
exactly what title_states_capture.sh was written to test. The experiment
is to start the fast probe from t=0 rather than attach to a run already
in progress.

METHOD: fixing the instrument is how you test the explanation that blamed
it -- a plausible mechanism is a hypothesis, and the fix is its
experiment, not its proof.
This commit is contained in:
Sylpheed RE agent
2026-08-29 01:34:40 +00:00
parent 924d4953d5
commit e492cee8b9
5 changed files with 119 additions and 3 deletions

View File

@@ -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 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 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. 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 🔴 **…and that diagnosis is itself refuted (same day).** The faster probe was
*game* are withdrawn. See [capture-harness-status](../re/capture-harness-status.md). 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.** **🟡 Still not settled — two runs, and the reason moved again.**

View File

@@ -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 classifies movie-vs-static on an *absolute* rmse threshold — so the gamma
change biased the very classifier the run depended on. Harness thresholds tuned change biased the very classifier the run depended on. Harness thresholds tuned
on one output configuration are not portable to another. 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.

View File

@@ -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 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 seconds would be missed. The observations stand; the conclusions drawn from
them do not. [`capture-harness-status.md`](capture-harness-status.md) 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.

View File

@@ -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 harness's tuning is coupled to the display settings** — changing gamma and
capture behaviour in one run confounds both. 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 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, region rather than the full surface, drop the ImageMagick `convert` round trip,

View File

@@ -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)<n: break
a=np.frombuffer(buf,np.uint8).reshape(H,W,3).astype(int)
r,g,b=a[:,:,0],a[:,:,1],a[:,:,2]
c=int(((g>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}")