Canonical pair turn2->turn3: the complete block set, the three heap payloads no previous capture could read (route [272], list-10 [1728], the 24-byte Population body), the three AI client seeds, and both output autosaves -- byte-identical to the published oracle AND to this lane's own hooks=off control, so the stream and the save come from the same run and the instrument did not change the turn it recorded. Creation turn turn1->turn2: three runs. Pinning the AI client seeds to the values an earlier run observed made a DIFFERENT process reproduce that run's block -- including the research pick that varies -- and its autosave byte for byte. The workload three lanes could not reproduce is reproducible given the seeds. Two corrections to lane L4's list-23 reading (no trailing int; the body is not turn-dependent) and one to my own list-5 record, the latter found by lane RB while consuming this capture. Format: JSON (raw words are ground truth, decoded is a typing) plus lane RB's own .tcb grammar with the heap payloads filled in, so RB's reader consumes it unchanged. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ARBgSooAfokKUy6wKUKEyZ
31 lines
1.4 KiB
Python
31 lines
1.4 KiB
Python
#!/usr/bin/env python3
|
|
"""Poll VM146's framebuffer until the main menu's red buttons are up. Never sleep-and-click:
|
|
the >60 s startup has cost this campaign at least five wasted runs, and it is on the board four
|
|
times. A run that clicks into the Kerberos intro reads exactly like a run whose clicks did nothing.
|
|
"""
|
|
import subprocess, sys, time, glob, os
|
|
from PIL import Image
|
|
|
|
DEADLINE = float(sys.argv[1]) if len(sys.argv) > 1 else 240.0
|
|
t0 = time.time()
|
|
while time.time() - t0 < DEADLINE:
|
|
subprocess.run(["uv", "run", "--with", "pillow", "python3", "tools/vmshot.py",
|
|
"--ssh", "--one", "146"], capture_output=True, check=False)
|
|
shots = sorted(glob.glob("dumps/vmshot/vm146-*.png"), key=os.path.getmtime)
|
|
if shots:
|
|
im = Image.open(shots[-1]).convert("RGB")
|
|
# The button column: Profiles..Exit sit at x 455-570, y 405-695, and are a saturated red
|
|
# only when the menu has actually rendered.
|
|
red = 0
|
|
for y in range(405, 695, 3):
|
|
for x in range(455, 570, 3):
|
|
r, g, b = im.getpixel((x, y))
|
|
if r > 110 and r > g * 2 and r > b * 2:
|
|
red += 1
|
|
print(f"t+{time.time()-t0:5.0f}s red={red} {os.path.basename(shots[-1])}", flush=True)
|
|
if red > 400:
|
|
print("MENU UP")
|
|
sys.exit(0)
|
|
time.sleep(6)
|
|
print("MENU NOT SEEN -- do not click")
|
|
sys.exit(1)
|