Files
Sylpheed/tools/re-capture/title_probe_xchecked.py
Sylpheed RE agent 9c16fa7860 re: a title negative that survives its own cross-check
Three earlier "the title never appears" claims came from instruments
later found broken -- a stale pixel oracle, a 41 s sampling interval, a
freezing stream. This one carries its own evidence.

title_probe_xchecked.py restarts its capture stream every 30 s AND prints
its reading beside an independent `import` grab every 60 s:

  1851 frames in 560.2 s = 3.30 fps
  cross-checks 9, disagreements 1
  max glyph 0

  t= 62s stream   6.05 | import   0.07  disagree (a fade, logos mid-transition)
  t=123s stream   7.40 | import   7.49  agree
  t=183s stream   8.18 | import   8.29  agree
  t=243s stream   0.23 | import   0.10  agree
  t=311s stream  89.68 | import  89.51  agree
  t=371s stream  80.97 | import  81.58  agree
  t=426s stream 117.43 | import 117.72  agree
  t=487s stream  77.71 | import  76.25  agree
  t=546s stream  70.43 | import  70.55  agree

Eight of nine agree within 2%, fps held at 3.30 with no collapse to 1.60,
and the surface moved through dark and bright phases. So the frames were
live: over 560 continuous seconds from launch, sampled 3.3 times a
second, the interactive title's green (A) plate never appears while the
game renders throughout. The final frame correlates 0.0145 / -0.0047 /
0.0102 with our title / main menu / EXTRAS renders -- attract-movie
content, not a UI screen.

Why remains unknown. live-title-press-a.png with its 753 glyph pixels
proves the title was reachable from this container on 2026-08-28, and
clearing the shader cache fixed the black surface but not this.

The two emulator-side questions (gamma control, 8AX vs ptbase) are
therefore blocked on a characterised failure rather than a suspicion.
Neither blocks the five menu screens, so I am returning to static work;
the probe is committed for whoever picks it up.

METHOD: a probe that cross-checks itself turns "no result" into a result.
2026-08-29 02:14:21 +00:00

51 lines
2.2 KiB
Python
Executable File

"""Fixed probe + a live cross-check against an independent grabber.
Stream is restarted every 30 s (a single long-lived one freezes). Every 60 s an
`import` grab of the same display is taken and compared: if the two disagree by
more than a few percent, the stream is lying and the run's numbers are void.
"""
import subprocess, sys, time, os
import numpy as np
from PIL import Image
W,H,SURF = 1280,720,45
LIMIT = float(sys.argv[1]) if len(sys.argv)>1 else 540
RESTART = 30
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 independent():
subprocess.run(["/usr/local/bin/screenshot","/tmp/xcheck.png"],capture_output=True)
try: return float(np.asarray(Image.open("/tmp/xcheck.png").convert("RGB")).astype(int)[SURF:].mean())
except Exception: return -1.0
p=_open(); n=W*H*3
t0=time.time(); seg=t0; lastx=t0; lastr=-1
frames=0; best=0; disagree=0; checks=0
while time.time()-t0 < LIMIT:
if time.time()-seg > RESTART:
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)
frames+=1
sm=float(a[SURF:].mean())
r,g,b=a[:,:,0],a[:,:,1],a[:,:,2]
c=int(((g>130)&(g-r>45)&(g-b>45)).sum())
if c>best:
best=c
if c>=400:
Image.fromarray(a[SURF:].astype(np.uint8)).save("/sylph-home/re/shots/probe3-title.png")
print(f"*** TITLE t={time.time()-t0:.1f}s glyph={c}",flush=True)
if time.time()-lastx > 60:
lastx=time.time(); checks+=1
iv=independent()
bad = iv>=0 and abs(iv-sm) > max(3.0, 0.15*max(iv,sm))
if bad: disagree+=1
print(f" t={int(time.time()-t0):4d}s stream {sm:7.2f} | import {iv:7.2f} "
f"{'<-- DISAGREE' if bad else 'agree'} glyph {c}",flush=True)
p.kill(); dt=time.time()-t0
print(f"{frames} frames in {dt:.1f}s = {frames/dt:.2f} fps")
print(f"cross-checks {checks}, disagreements {disagree}")
print(f"max glyph {best}")