Files
Sylpheed/tools/re-capture/menu_b_probe.py
sylph-decoder 7a4a74f8d7 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
2026-08-29 11:50:46 +00:00

88 lines
3.4 KiB
Python

#!/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())