The port's authored/flow.json stamped title/on_cancel_why = 'MEASURED, HANDOFF Q5' for a clause whose evidence cell in the source table reads 'none'. It did not invent that: HANDOFF's Q5 row opened with one **measured** covering six clauses of different strength, and HANDOFF is the document it authors against. Split per clause. Measured: initial focus varies; up/down move one item per press and wrap both ends; left/right do nothing; B on a submenu restores focus 4/4; B on the main menu goes to the title in <= 0.4 s with no loading screen. NOT measured, evidence cell empty: 'no auto-repeat at the durations tried', and 'B on the title -> nothing'. Both marked do-not-stamp. Also splits the source table's own up/down row, which bundled 'one item per press' (indirectly but soundly evidenced by the 4-press wrap count) with 'no auto-repeat' (nothing behind it, and the hedge was carrying the claim). METHOD entry: the failure is in summarising, not at either endpoint. The source table was honest and the consumer cited its source; flattening six claims into one adjective created a provenance nothing supports. A strength label is not distributive. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Wuu56cE8vJGTBtn1ppsk8v
145 lines
5.8 KiB
Python
Executable File
145 lines
5.8 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""Two empty evidence cells in one run: d-pad auto-repeat, and Ⓑ on a SETTLED title.
|
|
|
|
Both asked for by the port agent, and both are rows in
|
|
`menu-navigation-semantics.md` with nothing in the evidence column:
|
|
|
|
* **"no auto-repeat at the durations tried"** — the hedge is doing the work. Hold
|
|
⬇ for 2 s and count cursor moves.
|
|
* **"Ⓑ on the title → nothing"** — the previous run's second Ⓑ landed *during* the
|
|
title's build-in, so what followed was the build-in finishing. This one waits for
|
|
the plate pulse, which is the title's own settled signature
|
|
(`plate-pulse-measured.md`), before pressing.
|
|
|
|
⚠️ **The move counter is controlled before it is used**: a single 0.12 s tap must
|
|
produce exactly ONE frame-to-frame spike. If the control does not give 1, the hold
|
|
result means nothing and is not reported.
|
|
|
|
nav_repeat_and_b.py LOG OUTDIR [wait_s]
|
|
"""
|
|
import os
|
|
import re
|
|
import subprocess
|
|
import sys
|
|
import time
|
|
|
|
import numpy as np
|
|
from PIL import Image
|
|
|
|
LOG, OUT = sys.argv[1], sys.argv[2]
|
|
WAIT = float(sys.argv[3]) if len(sys.argv) > 3 else 520
|
|
W, H = 1280, 720
|
|
NEED, CEIL, HOLD_N = 500, 2500, 12
|
|
MENU_LO, MENU_HI, MENU_HOLD = 250, 420, 6
|
|
PAD = os.path.join(os.path.dirname(os.path.abspath(__file__)), "pad.py")
|
|
SPIKE = 0.004 # fraction of pixels that must change to count as a cursor move
|
|
|
|
|
|
def deliveries(vk):
|
|
pat = re.compile((r"vk=%s flags=0001" % vk).encode())
|
|
try:
|
|
return len(pat.findall(open(LOG, "rb").read()))
|
|
except FileNotFoundError:
|
|
return 0
|
|
|
|
|
|
def _open():
|
|
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 grab(p, n):
|
|
buf = p.stdout.read(n)
|
|
if len(buf) < n:
|
|
return None
|
|
return np.frombuffer(buf, np.uint8).reshape(H, W, 3).astype(float)
|
|
|
|
|
|
def glyph(a):
|
|
r, g, b = a[:, :, 0], a[:, :, 1], a[:, :, 2]
|
|
return int(((g > 130) & (g - r > 45) & (g - b > 45)).sum())
|
|
|
|
|
|
def count_moves(p, n, secs, label):
|
|
"""Frame-to-frame spikes over `secs`. Each cursor move repaints the highlight."""
|
|
prev, spikes, t0, series = None, 0, time.time(), []
|
|
while time.time() - t0 < secs:
|
|
a = grab(p, n)
|
|
if a is None:
|
|
continue
|
|
if prev is not None:
|
|
d = float((np.abs(a - prev).max(axis=2) > 12).mean())
|
|
series.append(round(d, 5))
|
|
if d > SPIKE:
|
|
spikes += 1
|
|
prev = a
|
|
print(f" {label}: {spikes} spike(s) over {secs:.1f}s diffs={series}", flush=True)
|
|
return spikes
|
|
|
|
|
|
T0 = time.time()
|
|
p, n, seg = _open(), W * H * 3, time.time()
|
|
phase, streak, mark, base = "wait", 0, None, None
|
|
res = open(f"{OUT}/result.txt", "w")
|
|
while True:
|
|
el = time.time() - T0
|
|
if el > WAIT:
|
|
print(f"TIMEOUT in {phase}", flush=True); break
|
|
if time.time() - seg > 30:
|
|
p.kill(); p = _open(); seg = time.time()
|
|
a = grab(p, n)
|
|
if a is None:
|
|
p.kill(); p = _open(); seg = time.time(); continue
|
|
c = glyph(a)
|
|
if phase == "wait":
|
|
streak = streak + 1 if NEED <= c <= CEIL else 0
|
|
if streak >= HOLD_N:
|
|
print(f"[{el:7.1f}s] TITLE", flush=True)
|
|
subprocess.run([sys.executable, PAD, "tap", "A", "0.5"], check=False)
|
|
phase, streak = "tomenu", 0
|
|
elif phase == "tomenu":
|
|
streak = streak + 1 if MENU_LO <= c <= MENU_HI else 0
|
|
if streak >= MENU_HOLD:
|
|
print(f"[{el:7.1f}s] MENU (glyph {c})", flush=True); time.sleep(2)
|
|
print(" CONTROL: one 0.12 s DOWN tap — must give exactly 1 spike", flush=True)
|
|
subprocess.run([sys.executable, PAD, "tap", "DOWN", "0.12"], check=False)
|
|
ctrl = count_moves(p, n, 3.0, "control")
|
|
time.sleep(1.5)
|
|
print(" TEST: hold DOWN for 2.0 s", flush=True)
|
|
subprocess.Popen([sys.executable, PAD, "hold", "press=DOWN", "2.0"])
|
|
test = count_moves(p, n, 4.0, "hold-2s")
|
|
res.write(f"control_tap_spikes\t{ctrl}\nhold_2s_spikes\t{test}\n")
|
|
res.flush()
|
|
print(f" => control {ctrl}, hold {test} — "
|
|
f"{'CONTROL FAILED, hold result void' if ctrl != 1 else ('AUTO-REPEAT' if test > 1 else 'NO AUTO-REPEAT')}",
|
|
flush=True)
|
|
time.sleep(1.5)
|
|
subprocess.run([sys.executable, PAD, "tap", "B", "0.5"], check=False)
|
|
print(f"[{time.time()-T0:7.1f}s] B pressed on the menu — waiting for the title to SETTLE", flush=True)
|
|
phase, streak = "resettle", 0
|
|
elif phase == "resettle":
|
|
streak = streak + 1 if NEED <= c <= CEIL else 0
|
|
if streak >= HOLD_N:
|
|
Image.fromarray(a.astype(np.uint8)).save(f"{OUT}/title-settled.png")
|
|
print(f"[{el:7.1f}s] TITLE SETTLED (plate pulse, glyph {c}) — pressing B", flush=True)
|
|
base = a.copy()
|
|
before = deliveries("5801")
|
|
subprocess.run([sys.executable, PAD, "tap", "B", "0.5"], check=False)
|
|
for _ in range(20):
|
|
time.sleep(0.25)
|
|
if deliveries("5801") > before:
|
|
print(" B delivered", flush=True); break
|
|
mark = time.time(); phase = "afterB"
|
|
elif phase == "afterB":
|
|
if time.time() - mark > 20:
|
|
Image.fromarray(a.astype(np.uint8)).save(f"{OUT}/after-b-on-settled-title.png")
|
|
d = float((np.abs(a - base).max(axis=2) > 12).mean())
|
|
print(f"[{el:7.1f}s] 20 s after Ⓑ on the settled title: {100*d:.1f}% of pixels differ "
|
|
f"from the moment of the press, glyph {c}", flush=True)
|
|
res.write(f"b_on_settled_title_diff_pct\t{100*d:.2f}\nb_on_settled_title_glyph\t{c}\n")
|
|
break
|
|
p.kill(); res.close()
|