Two results and one retraction, all from the same session. REFUTED: 'an ~8-10 s idle returns to the title' does not apply to the main menu. Held untouched it stayed put for >= 60 s, correlation never leaving 0.9245-0.9249. That timer is real but belongs to the TITLE. It was the only reason 'B leaves the main menu' was classed as authored, so Q5's B rule is upgraded to measured-ordering: B is delivered (canary logs vk=5801) and is the only input in >= 100 s before the return. The PRESS (A) plate: the boot title presents build 4 WITHOUT the plate first -- green-glyph 154, against 159 on the committed no-plate capture and 753/977/1493 on plate titles -- and the plate arrives after. That is the port's third option. RETRACTED: four durations taken the same day. classify_array costs 1503 ms per frame; running it per frame against an 8 fps x11grab drained the pipe at 0.64 fps, so every classified frame was stale and increasingly so. It manufactured 'plate 24.66 s after the title art', 'B->title 15.58 s', 'B->title 25.60 s' and 'A->menu 20.26 s'. The tell: a transition, a press and a fade do not share a duration, and the two B figures GREW across a longer run. A backlog preserves ordering and destroys durations, which is why the sequence results above stand and every timing does not. The ring's period is unaffected and that was checked, not assumed -- ring_period ran at 15.03 fps against a requested 15. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KNR5Y79D1T4bBr6gJQaWFP
120 lines
4.9 KiB
Python
120 lines
4.9 KiB
Python
#!/usr/bin/env python3
|
|
"""One boot, two answers: when the PRESS (A) plate appears, and what (B) does.
|
|
|
|
Q(plate): the port's boot ends on GP_TITLE build 4, which carries no plate, and
|
|
(A) is the only way off it -- so it ships a screen that needs a press and does
|
|
not say so. Builds 2/3 are the plate. The open half is the SEQUENCE: build 4
|
|
alone, build 4 with the plate composited from the start, or build 4 and THEN
|
|
the plate after a delay. This logs the title-art correlation and the green-(A)
|
|
glyph count on EVERY frame from before the title appears, so the two crossings
|
|
are read off one trace rather than inferred.
|
|
|
|
Q(B): whether (B) leaves the main menu, timed against the idle alternative.
|
|
|
|
Controls, both pre-run on committed captures:
|
|
* screen identity -- screen_match.py, 8/8 including the movie frames that
|
|
broke the statistics oracle;
|
|
* the plate -- green-(A) glyph count: 753/977/1493 px on plate titles, 159 on
|
|
`live-title-build4-no-plate.png`, 327 on the main menu. Threshold 400.
|
|
|
|
Usage: title_plate_and_b_probe.py OUTDIR
|
|
"""
|
|
import os, subprocess, sys, time
|
|
import numpy as np
|
|
from PIL import Image
|
|
|
|
SD = os.path.dirname(os.path.abspath(__file__))
|
|
sys.path.insert(0, SD)
|
|
from screen_match import classify_array
|
|
|
|
W, H = 1280, 720
|
|
PLATE = 400
|
|
OUT = sys.argv[1] if len(sys.argv) > 1 else "/sylph-home/re/platecap"
|
|
os.makedirs(OUT, exist_ok=True)
|
|
|
|
|
|
def stream():
|
|
return subprocess.Popen(
|
|
["ffmpeg", "-loglevel", "error", "-f", "x11grab", "-draw_mouse", "0",
|
|
"-video_size", f"{W}x{H}", "-i", ":98", "-r", "8",
|
|
"-f", "rawvideo", "-pix_fmt", "rgb24", "-"],
|
|
stdout=subprocess.PIPE, bufsize=W * H * 3 * 2)
|
|
|
|
|
|
def glyph(a):
|
|
r, g, b = a[:, :, 0].astype(int), a[:, :, 1].astype(int), a[:, :, 2].astype(int)
|
|
return int(((g > 130) & (g - r > 45) & (g - b > 45)).sum())
|
|
|
|
|
|
def main():
|
|
p = stream(); n = W * H * 3
|
|
t0 = time.time(); seg = t0; prev = None
|
|
skipped = False; stage = 0; marks = {}
|
|
rows = []
|
|
while time.time() - t0 < 420:
|
|
if time.time() - seg > 25:
|
|
p.kill(); p = stream(); seg = time.time()
|
|
b = p.stdout.read(n)
|
|
if len(b) < n:
|
|
p.kill(); p = stream(); seg = time.time(); continue
|
|
a = np.frombuffer(b, np.uint8).reshape(H, W, 3)
|
|
el = time.time() - t0
|
|
c, sc = classify_array(a)
|
|
gl = glyph(a)
|
|
rows.append((el, c, sc["title"], sc["menu"], gl))
|
|
if c != prev:
|
|
print(f"t={el:7.2f}s screen={c:<6} title={sc['title']:+.3f} "
|
|
f"menu={sc['menu']:+.3f} glyph={gl}", flush=True)
|
|
prev = c
|
|
|
|
if not skipped and el > 45:
|
|
subprocess.run(["python3", f"{SD}/pad.py", "tap", "A", "0.3"], check=False)
|
|
skipped = True
|
|
print(f"t={el:7.2f}s one (A) to skip the intro movie", flush=True)
|
|
elif stage == 0 and c == "title":
|
|
marks["title_art"] = el; stage = 1
|
|
Image.fromarray(a).save(f"{OUT}/title-first-{el:07.2f}.png")
|
|
print(f"t={el:7.2f}s TITLE ART (glyph={gl}) — watching for the plate",
|
|
flush=True)
|
|
elif stage == 1 and gl >= PLATE:
|
|
marks["plate"] = el; stage = 2
|
|
Image.fromarray(a).save(f"{OUT}/title-plate-{el:07.2f}.png")
|
|
print(f"t={el:7.2f}s PLATE (glyph={gl}) — "
|
|
f"{el-marks['title_art']:.2f}s after the title art", flush=True)
|
|
elif stage == 2 and el > marks["plate"] + 6:
|
|
subprocess.run(["python3", f"{SD}/pad.py", "tap", "A", "0.3"], check=False)
|
|
marks["A"] = el; stage = 3
|
|
print(f"t={el:7.2f}s >>> (A) on the title", flush=True)
|
|
elif stage == 3 and c == "menu":
|
|
marks["menu"] = el; stage = 4
|
|
print(f"t={el:7.2f}s MENU — idling 25 s before (B)", flush=True)
|
|
elif stage == 4 and el > marks["menu"] + 25:
|
|
subprocess.run(["python3", f"{SD}/pad.py", "tap", "B", "0.3"], check=False)
|
|
marks["B"] = el; stage = 5
|
|
print(f"t={el:7.2f}s >>> (B) PRESSED", flush=True)
|
|
elif stage == 5 and c != "menu":
|
|
marks["left_menu"] = el
|
|
print(f"t={el:7.2f}s LEFT THE MENU -> {c}, "
|
|
f"{el-marks['B']:.2f}s after (B)", flush=True)
|
|
stage = 6
|
|
elif stage == 6 and el > marks["left_menu"] + 12:
|
|
break
|
|
|
|
p.kill()
|
|
with open(f"{OUT}/trace.tsv", "w") as f:
|
|
f.write("t_s\tscreen\tcorr_title\tcorr_menu\tglyph\n")
|
|
for r in rows:
|
|
f.write(f"{r[0]:.3f}\t{r[1]}\t{r[2]:.4f}\t{r[3]:.4f}\t{r[4]}\n")
|
|
print("\nmarks:", {k: round(v, 2) for k, v in marks.items()})
|
|
if "title_art" in marks and "plate" in marks:
|
|
print(f"PLATE DELAY: {marks['plate']-marks['title_art']:.2f}s "
|
|
f"after the title art first matched")
|
|
if "B" in marks and "left_menu" in marks:
|
|
print(f"(B) -> left the menu in {marks['left_menu']-marks['B']:.2f}s")
|
|
print(f"trace: {OUT}/trace.tsv")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|