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.
51 lines
2.2 KiB
Python
Executable File
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}")
|