From 9c16fa78603b27f430bc492aaaceed7030d2946d Mon Sep 17 00:00:00 2001 From: Sylpheed RE agent Date: Sat, 29 Aug 2026 02:14:21 +0000 Subject: [PATCH] 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. --- docs/port/MISSION.md | 23 ++++++++++ docs/re/METHOD.md | 7 +++ docs/re/capture-harness-status.md | 56 ++++++++++++++++++++++++ tools/re-capture/title_probe_xchecked.py | 50 +++++++++++++++++++++ 4 files changed, 136 insertions(+) create mode 100755 tools/re-capture/title_probe_xchecked.py diff --git a/docs/port/MISSION.md b/docs/port/MISSION.md index a2bc702f..82aaf612 100644 --- a/docs/port/MISSION.md +++ b/docs/port/MISSION.md @@ -77,6 +77,29 @@ alongside it. | **Q10** | **What are a music bank's sub-waves?** `BGM_001.slb` is three sub-waves — 10 KB, 4.47 MB, 4.67 MB — and we currently **concatenate them blindly** into one 347 s track. Two near-equal halves could be intro + loop, or two variations, or two halves of one piece. A menu that loops its music needs to know which | The role of each sub-wave, established for at least the menu BGM. "Concatenate" is a decision, not a default — right now it is a default nobody chose | | **S1** | ~~**Ready Room probe.**~~ **DONE 2026-08-28 — [no-go](../re/ready-room-probe.md).** It is 2D and enumerates fine, but the pak is briefing/tactical-map content, not the Ready Room menu | ✅ go/no-go written | +## 🔴 Emulator-side questions are blocked — the title is not reachable here + +**Status 2026-08-29, instrument-verified.** Two open items need a running menu: +the gamma control behind [tone curve](../re/structures/ui-render-tone-curve.md), +and separating `8AX` from `ptbase` in +[8AX](../re/structures/ui-8ax-fullres-background.md). Both are blocked. + +✅ **The negative is now solid.** A probe that restarts its capture stream every +30 s and cross-checks itself against an independent grabber every 60 s +(9 checks, 8 agreeing to within 2 %) ran **560 continuous seconds from launch at +3.30 fps**: the interactive title's green Ⓐ plate never appeared, while the game +rendered throughout. The final frame correlates 0.0145 / −0.0047 / 0.0102 with +our title / main-menu / `EXTRAS` renders — attract-movie content. + +❔ **Why is unknown.** `live-title-press-a.png` (753 glyph pixels) proves it was +reachable from this container on 2026-08-28. Clearing the shader cache fixed a +*black surface* but not this. + +⚠️ Neither blocked item blocks the five menu screens. See +[capture-harness-status](../re/capture-harness-status.md) for the full trail, +including three earlier "the title never appears" claims that were withdrawn +because the instrument was broken each time. + ## 🟡 Needs one more run — a Japanese-locale capture Recorded rather than worked around, per "do not improvise around a blocker". diff --git a/docs/re/METHOD.md b/docs/re/METHOD.md index 6aff3faa..88a7c9fb 100644 --- a/docs/re/METHOD.md +++ b/docs/re/METHOD.md @@ -544,3 +544,10 @@ agent's loop prompt, i.e. nowhere durable. See [`README.md`](README.md) for the "2 391 frames, zero hits" reads as overwhelming and collapses to nothing once the frames may all be the same frame. Sample count is not evidence of coverage unless the samples are known to be independent. +* **A probe that cross-checks itself turns "no result" into a result.** Three + successive "the title never appears" claims were withdrawn because the + instrument was broken each time. The version that finally held prints its own + reading beside an independent grabber's every 60 s — 9 checks, 8 agreeing to + within 2 % — so the negative arrives with its own evidence that the frames were + live. Building the cross-check into the tool costs a few lines and is what + separates a measurement from a silence. diff --git a/docs/re/capture-harness-status.md b/docs/re/capture-harness-status.md index b1c69754..2d94b0e3 100644 --- a/docs/re/capture-harness-status.md +++ b/docs/re/capture-harness-status.md @@ -223,3 +223,59 @@ single A/B does not exclude run-to-run variation. The old caches are kept in ❔ **Still no title.** Even rendering, `max glyph 0` — but that number now comes from a stalling probe, so it establishes nothing either way. + +--- + +## ✅ A negative I can finally stand behind — measured with a self-validating probe + +**2026-08-29.** Three earlier "the title never appears" claims came from +instruments that were later found broken (a stale pixel oracle, a 41 s sampling +interval, a freezing stream). This run used +[`tools/re-capture/title_probe_xchecked.py`](../../tools/re-capture/title_probe_xchecked.py), +which restarts its stream every 30 s **and cross-checks itself against an +independent `import` grab every 60 s**, printing both numbers. + +``` +1851 frames in 560.2 s = 3.30 fps +cross-checks 9, disagreements 1 +max glyph 0 +``` + +| t | stream | import | | +|---|---|---|---| +| 62 s | 6.05 | 0.07 | disagree — a fade, the boot logos are mid-transition | +| 123 s | 7.40 | 7.49 | agree | +| 183 s | 8.18 | 8.29 | agree | +| 243 s | 0.23 | 0.10 | agree | +| 311 s | 89.68 | 89.51 | agree | +| 371 s | 80.97 | 81.58 | agree | +| 426 s | 117.43 | 117.72 | agree | +| 487 s | 77.71 | 76.25 | agree | +| 546 s | 70.43 | 70.55 | agree | + +Eight of nine agree to within 2 %, the fps held at 3.30 (no collapse to 1.60), +and the surface plainly moved through dark and bright phases. **The frames were +live and the negative is real:** + +> ✅ **Measured — over 560 continuous seconds from launch, sampled 3.3 times a +> second by a cross-validated probe, the interactive title's green Ⓐ plate never +> appears, while the game renders throughout.** + +The final frame correlates **0.0145** with our title render, **−0.0047** with the +main menu and **0.0102** with `EXTRAS` — it is attract-movie content, not a UI +screen at all. + +## ❔ Why, still unknown + +`live-title-press-a.png` is committed and carries 753 glyph pixels, so the title +*was* reachable from this container on 2026-08-28. Nothing found so far explains +the change. Clearing the shader cache fixed the *black surface* but not this. + +## Where this leaves the emulator-side questions + +The two open items that need a running menu — the gamma control +([tone curve](structures/ui-render-tone-curve.md)) and separating `8AX` from +`ptbase` ([8AX](structures/ui-8ax-fullres-background.md)) — remain blocked, now +on a well-characterised and instrument-verified failure rather than on a +suspicion. Neither blocks the five menu screens. **Returning to static work**; +the probe is committed for whoever picks this up. diff --git a/tools/re-capture/title_probe_xchecked.py b/tools/re-capture/title_probe_xchecked.py new file mode 100755 index 00000000..672ec0d8 --- /dev/null +++ b/tools/re-capture/title_probe_xchecked.py @@ -0,0 +1,50 @@ +"""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)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}")