Cross-checked the instrument built last iteration against an independent grabber while both watched the same screen, and it fails. A single long-lived ffmpeg x11grab stream degrades and then freezes: 862 frames in 540.1 s = 1.60 fps (it starts at 3.98) t=450/480/510/540 s: surface mean 5.21, identical every time At that same moment `import` read surface mean 125.65, and a freshly started ffmpeg stream read 122.43 -- agreeing with import to 3%. So the acquisition was broken, not the analysis: the stream replayed a stale frame while the screen was 24x brighter. That withdraws last iteration's headline. "2391 frames over 600 s from t=0, max glyph 0" cannot distinguish "the title never appeared" from "the stream froze early and repeated one frame 2391 times". Its 3.98 fps was measured over the first 20 s, before the degradation. Sample count is not coverage unless the samples are known independent. Fixed: the stream is now torn down and restarted every 30 s. Startup is ~0.3 s, cheap against the title's window, and it guarantees live frames. Separately, the cache hypothesis was tested and is SUPPORTED. cache, cache0, cache1, cache_host moved aside (to /tmp/xenia-cache-aside, not deleted) and the surface renders again: import reads mean 54.8 and 68.6 with 100% non-black warm content, against 0.07 and 0.08% non-black in the black run; 773 of 862 probe frames had >2% non-black. One run each side and many kill -9s before the black one, so it is supported, not proven -- the old caches are kept for reproduction. Still no title, but that number now comes from a stalling probe and establishes nothing either way. METHOD: validating a probe on static images tests its analysis, not its acquisition -- cross-check against an independent grabber during a run.
53 lines
2.4 KiB
Python
Executable File
53 lines
2.4 KiB
Python
Executable File
"""Fast title probe: a PERIODICALLY RESTARTED x11grab stream + glyph count.
|
|
|
|
🔴 DO NOT run one x11grab stream for the whole session. Measured 2026-08-29: a
|
|
single long-lived stream degrades from 3.98 fps to 1.60 fps and then FREEZES,
|
|
reporting a stale frame indefinitely. Cross-checked at one moment against an
|
|
independent `import` grab: the stream said surface mean 5.21 while `import` said
|
|
125.65, and the stream repeated that same value for four consecutive 30 s marks.
|
|
Any negative result from a stalled stream is worthless -- an earlier "2391
|
|
frames, zero hits" claim came from exactly that failure.
|
|
|
|
So the stream is torn down and restarted every RESTART_S seconds. Startup costs
|
|
~0.3 s, which is cheap against the title's few-second window and buys a
|
|
guarantee that the frames are live.
|
|
|
|
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
|
|
RESTART_S = 30 # tear down and restart the stream this often
|
|
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)
|
|
p=_open()
|
|
n=W*H*3; t0=time.time(); frames=0; best=0; hits=0; seg=time.time()
|
|
while time.time()-t0 < LIMIT:
|
|
if time.time()-seg > RESTART_S: # a stalled stream reports stale frames forever
|
|
p.kill(); p=_open(); seg=time.time()
|
|
buf=p.stdout.read(n)
|
|
if len(buf)<n:
|
|
p.kill(); p=_open(); seg=time.time(); continue
|
|
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}")
|