diff --git a/docs/port/MISSION.md b/docs/port/MISSION.md index a2bc702..82aaf61 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 6aff3fa..88a7c9f 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 b1c6975..2d94b0e 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 0000000..672ec0d --- /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}")