re: the main menu has no idle self-return -- and four of my own durations were a backlog
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
This commit is contained in:
87
tools/re-capture/menu_b_probe.py
Normal file
87
tools/re-capture/menu_b_probe.py
Normal file
@@ -0,0 +1,87 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Does (B) leave the main menu -- and does the menu self-return to the title?
|
||||
|
||||
HANDOFF downgraded "(B) on the main menu returns to the title" to authored,
|
||||
because the corpus also carries "an ~8-10 s idle returns to the title" and one
|
||||
unrecorded observation cannot separate the two causes. This separates them by
|
||||
ordering: hold the menu UNTOUCHED for an idle window several times longer than
|
||||
the claimed 8-10 s and timestamp what happens, THEN press (B) and timestamp
|
||||
again. If the idle window passes with the menu still up, the idle cause is
|
||||
gone and the (B) observation is unambiguous.
|
||||
|
||||
Screen identity comes from screen_match.py, whose control includes the movie
|
||||
frames that broke the statistics-based oracle.
|
||||
|
||||
Usage: menu_b_probe.py IDLE_SECONDS AFTER_SECONDS
|
||||
"""
|
||||
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
|
||||
IDLE = float(sys.argv[1]) if len(sys.argv) > 1 else 60.0
|
||||
AFTER = float(sys.argv[2]) if len(sys.argv) > 2 else 30.0
|
||||
OUT = "/sylph-home/re/ringcap"
|
||||
|
||||
|
||||
def stream():
|
||||
return 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)
|
||||
|
||||
|
||||
def main():
|
||||
p = stream(); n = W * H * 3
|
||||
t0 = time.time(); seg = t0; last = None; prev = None
|
||||
phase = "IDLE"; pressed_at = None
|
||||
log = []
|
||||
while True:
|
||||
el = time.time() - t0
|
||||
if phase == "IDLE" and el >= IDLE:
|
||||
subprocess.run(["python3", f"{SD}/pad.py", "tap", "B", "0.3"], check=False)
|
||||
pressed_at = time.time() - t0
|
||||
print(f"t={pressed_at:6.2f}s >>> (B) PRESSED", flush=True)
|
||||
phase = "AFTER"
|
||||
if phase == "AFTER" and el >= IDLE + AFTER:
|
||||
break
|
||||
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)
|
||||
last = a
|
||||
c, sc = classify_array(a)
|
||||
log.append((el, c, sc["title"], sc["menu"]))
|
||||
if c != prev:
|
||||
print(f"t={el:6.2f}s screen={c:<6} title={sc['title']:+.3f} "
|
||||
f"menu={sc['menu']:+.3f}", flush=True)
|
||||
Image.fromarray(a).save(f"{OUT}/b-{el:06.2f}-{c}.png")
|
||||
prev = c
|
||||
p.kill()
|
||||
with open(f"{OUT}/menu-b-trace.tsv", "w") as f:
|
||||
f.write("t_s\tscreen\tcorr_title\tcorr_menu\n")
|
||||
for r in log:
|
||||
f.write(f"{r[0]:.3f}\t{r[1]}\t{r[2]:.4f}\t{r[3]:.4f}\n")
|
||||
idle = [r for r in log if r[0] < IDLE]
|
||||
aft = [r for r in log if pressed_at and r[0] > pressed_at + 1.0]
|
||||
print(f"\nIDLE phase : {len(idle)} samples over {IDLE:.0f}s, "
|
||||
f"screens seen = {sorted(set(r[1] for r in idle))}")
|
||||
print(f"AFTER (B) : {len(aft)} samples, "
|
||||
f"screens seen = {sorted(set(r[1] for r in aft))}")
|
||||
first_title = next((r[0] for r in aft if r[1] == "title"), None)
|
||||
if first_title:
|
||||
print(f" first 'title' at t={first_title:.2f}s = "
|
||||
f"{first_title - pressed_at:.2f}s after the (B) press")
|
||||
print(f"trace: {OUT}/menu-b-trace.tsv")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
119
tools/re-capture/title_plate_and_b_probe.py
Normal file
119
tools/re-capture/title_plate_and_b_probe.py
Normal file
@@ -0,0 +1,119 @@
|
||||
#!/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())
|
||||
Reference in New Issue
Block a user