Files
Sylpheed/tools/re-capture/jp_title_capture.py
sylph-decoder 77b4ccecc4 re: the Japanese title at rest -- the capture MISSION has wanted since 2026-08-29
Asked for by the port: its title_jp row drifted, localized to a 350x396 block at
(405,74) -- the logo stack -- and with no JP capture in the corpus it could say
the renderers moved apart but not which one moved.

Three earlier attempts failed to reach the interactive title in either locale.
The reason is now known and was never the locale: A at the title needs a
signed-in profile, and no run had one.

Locale set through canary's own persisted XConfig and restored afterwards,
verified back at language=1. INDEPENDENT confirmation it took: the XMA probe
logged a different voice-context set from every English run (ja 1112064 /
1150976 / 1177600 against en 1294336 / 1118208 / 1171456), so the switch reached
the guest rather than being a menu-language cosmetic.

'At rest' is demonstrated rather than assumed. Five frames ~1.5 s apart after the
plate pulse says the screen has settled: the port's ROI is byte-identical across
all of them, max |delta| 0 over 138 600 px, while the WHOLE FRAME moves 39 584 to
71 927 px -- the plate pulse and sweeps. That contrast is the control: the
instrument can see motion and the ROI still shows none.

The capture shows what the English title does not -- the katakana subtitle, and a
crystalline burst behind the wordmark, the ptlogo3a/b/c + ptlogo_back2eff* stack
that this corpus records as transparent at rest in English. Exactly the region
the port's drift is localized to.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wuu56cE8vJGTBtn1ppsk8v
2026-08-30 11:37:35 +00:00

89 lines
3.5 KiB
Python
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
"""Capture the JAPANESE title (`GP_TITLE` build 7) at rest.
Asked for by the port agent: its `title_jp` row drifted and it can say the two
renderers moved apart but not which moved, because **no capture of the JP title
exists in this corpus**. `MISSION.md` has carried this as "needs one more run"
the locale route is `set_console_language.py`, and the reason the earlier runs
failed (Ⓐ needs a signed-in profile) is now known.
"At rest" is **demonstrated, not assumed**: after the plate pulse says the title
has settled, five frames are taken ~1.5 s apart and the port's own region of
interest — 350×396 at (405, 74) in design space — is compared across them. If the
logo stack is still moving, the frames will say so.
⚠️ The frames are display-space 1280×720 with the game surface at y=45
(`tbm-submenu-reached.txt`), so the design-space box is offset by that.
jp_title_capture.py OUTDIR [wait_s]
"""
import subprocess
import sys
import time
import numpy as np
from PIL import Image
OUT = sys.argv[1]
WAIT = float(sys.argv[2]) if len(sys.argv) > 2 else 480
W, H = 1280, 720
NEED, CEIL, HOLD = 500, 2500, 12
SURF_Y = 45
ROI = (405, 74, 350, 396) # design-space x, y, w, h — the port's drifting block
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 glyph(a):
r, g, b = a[:, :, 0], a[:, :, 1], a[:, :, 2]
return int(((g > 130) & (g - r > 45) & (g - b > 45)).sum())
T0 = time.time()
p, n, seg, streak = _open(), W * H * 3, time.time(), 0
frames = []
while True:
el = time.time() - T0
if el > WAIT:
print("TIMEOUT — the title never settled", flush=True); break
if time.time() - seg > 30:
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(np.uint8)
c = glyph(a.astype(int))
streak = streak + 1 if NEED <= c <= CEIL else 0
if streak >= HOLD:
print(f"[{el:7.1f}s] TITLE SETTLED (plate pulse, glyph {c}) — taking 5 frames", flush=True)
for k in range(5):
t0 = time.time()
while time.time() - t0 < 1.5:
b2 = p.stdout.read(n)
if len(b2) < n:
p.kill(); p = _open(); break
a = np.frombuffer(b2, np.uint8).reshape(H, W, 3).astype(np.uint8)
Image.fromarray(a).save(f"{OUT}/jp-title-{k}.png")
frames.append(a.astype(int))
print(f" frame {k}: glyph {glyph(a.astype(int))}", flush=True)
break
p.kill()
if len(frames) == 5:
x, y, w, h = ROI
rois = [f[SURF_Y + y:SURF_Y + y + h, x:x + w] for f in frames]
print("\nROI stability — the port's 350x396 block at (405,74), design space:")
for i in range(1, 5):
d = np.abs(rois[i] - rois[0])
print(f" frame {i} vs 0: max |Δ| {d.max():3d}, pixels differing >8: "
f"{int((d.max(axis=2) > 8).sum()):6d} / {d.shape[0]*d.shape[1]}", flush=True)
full = [np.abs(frames[i] - frames[0]).max(axis=2) for i in range(1, 5)]
print(" whole frame, for contrast (the plate pulses, so this SHOULD move):")
for i, d in enumerate(full, 1):
print(f" frame {i} vs 0: pixels differing >8: {int((d > 8).sum()):7d}", flush=True)