Last iteration I blamed four failed runs on the probe sampling every ~41 s, slower than the title screen lasts, and withdrew three earlier conclusions on that basis. Building the fix tested the claim and killed it. The speedup is real and control-verified. One long-lived ffmpeg x11grab stream, raw RGB, glyph counted in numpy -- no per-sample process startup, no PNG encode, no convert -crop: wrapper `screenshot` 3.98 s per sample (emulator running) import -window root -> PPM 1.20 s long-lived x11grab stream 0.29 s 13.7x The counter is byte-identical to is_title.py: 753 on the committed title capture, 327 on the main menu. Pointed at a running game it says the opposite of what I expected: 332 frames in 85.3 s = 3.89 fps; max glyph 0 1674 frames in 420.0 s = 3.99 fps; max glyph 0 1674 consecutive samples over seven unbroken minutes, four per second, zero green-(A) pixels. Sampling rate was a real defect that happened not to be the cause. So "neither locale reaches the interactive title without a pad press" -- withdrawn last iteration for want of evidence -- is reinstated, now as a dense measurement, with its reach stated: a MID-RUN window only, silent about the boot title. Leading hypothesis, unconfirmed: the PRESS (A) plate appears only in the boot title window and the attract loop's title carries none, which is exactly what title_states_capture.sh was written to test. The experiment is to start the fast probe from t=0 rather than attach to a run already in progress. METHOD: fixing the instrument is how you test the explanation that blamed it -- a plausible mechanism is a hypothesis, and the fix is its experiment, not its proof.
35 lines
1.4 KiB
Python
Executable File
35 lines
1.4 KiB
Python
Executable File
"""Fast title probe: one long-lived x11grab stream, ~3.5 fps, glyph count per frame.
|
|
|
|
Replaces the two-`screenshot` polling loop, which costs ~4-11 s per grab while
|
|
xenia runs and so samples every ~41 s -- slower than the title screen lasts.
|
|
Counter is byte-identical to is_title.py (controlled: 753 on the committed title
|
|
capture, 327 on the main menu).
|
|
"""
|
|
import subprocess, sys, time
|
|
import numpy as np
|
|
W,H = 1280,720
|
|
LIMIT = float(sys.argv[1]) if len(sys.argv)>1 else 120
|
|
NEED = int(sys.argv[2]) if len(sys.argv)>2 else 400
|
|
p = 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)
|
|
n=W*H*3; t0=time.time(); frames=0; best=0; hits=0
|
|
while time.time()-t0 < LIMIT:
|
|
buf=p.stdout.read(n)
|
|
if len(buf)<n: break
|
|
a=np.frombuffer(buf,np.uint8).reshape(H,W,3).astype(int)
|
|
r,g,b=a[:,:,0],a[:,:,1],a[:,:,2]
|
|
c=int(((g>130)&(g-r>45)&(g-b>45)).sum())
|
|
frames+=1
|
|
if c>best:
|
|
best=c
|
|
if c>=NEED:
|
|
from PIL import Image
|
|
Image.fromarray(a.astype(np.uint8)).save("/sylph-home/re/shots/fast-title.png")
|
|
if c>=NEED:
|
|
hits+=1
|
|
if hits==1: print(f"TITLE at t={time.time()-t0:.1f}s glyph={c} (frame {frames})", flush=True)
|
|
p.kill()
|
|
dt=time.time()-t0
|
|
print(f"{frames} frames in {dt:.1f}s = {frames/dt:.2f} fps; max glyph {best}; hits {hits}")
|