Files
Sylpheed/tools/re-capture/f3_sting_probe.py
sylph-decoder 3d7138892c
Some checks failed
CI / Native — linux (pull_request) Failing after 34m43s
CI / WASM — Web (pull_request) Successful in 31m12s
CI / Formatting (pull_request) Failing after 1m11s
re: F3 sting half closed -- no sting, measured with a working positive control
Continues the static lead from two iterations ago
(f3-title-sting-mechanism-found-not-value.md) with the dynamic half it
named as the next step. Booted with --xma_param_probe=true (the same
census menu-audio-cues.md used for the menu's SE cues), no pad input,
recording continuously from window-open: a glyph time series (not a
threshold trigger) and every newly-seen XMA-PARAM stream, stamped on
arrival since Xenia's own log carries no timestamps.

Positive control, and a real one: the probe caught the title's two BGM
stems starting at t=147.6s, matching f3-title-plays-bgm-102-and-103.md's
already-established finding exactly -- the instrument finds a real stream
before being asked to find nothing.

From the plate's first visible activity through 68 seconds of build-in
plus fully-settled pulsing (killed at t=220.1s), zero new XMA streams
appeared beyond the two BGM stems and three unidentified early ones (boot
splash, not this question). No SE-range stream, no second BGM, nothing --
measured, not the prior static reach limit.

Refutation attempt this iteration, recorded either way: my first read of
the fine-grained glyph series said BGM and the plate's build-in start at
"essentially the same moment". Checking the raw per-sample data instead of
a coarse table refutes that -- first non-zero glyph reading is ~0.67s after
BGM onset, and immediately noisy rather than a clean climb. Corrected in
the doc rather than left as an overclaim for someone chasing frame-accurate
sync later.

Reference data: docs/re/data/f3-sting-{glyph-timeseries,xma-param-arrivals}.tsv
-- derived numeric/log-line data, not a capture of rendered game content.
2026-09-12 12:40:34 +00:00

152 lines
5.2 KiB
Python

#!/usr/bin/env python3
"""F3, the sting half -- watch for a NEW XMA stream during the title's
build-in, with NO input, aligned against a continuous glyph time series so
"when did the plate reach full alpha" is measured, not assumed from a
threshold crossing.
Why continuous, not a threshold trigger: this container's own boot gate
(nav_repeat_and_b.py, f1_hold_capture.py) waits for the glyph count to HOLD
in [500,2500] for 12 samples before calling it "TITLE" -- which could
already be past the build-in's interesting part. This script starts
recording both streams (glyph count, XMA-PARAM arrivals) from the moment
Canary's window exists, so the whole rise from 0 can be read back, not just
the plateau.
Positive control, per R4: BGM cues 1102/1103 are already known to play on
the title (f3-title-plays-bgm-102-and-103.md) via this exact probe
mechanism (menu-audio-cues.md). If this run logs zero XMA-PARAM lines at
all, the probe found nothing INCLUDING the thing it's supposed to find, and
the run is void -- not a negative about a sting.
f3_sting_probe.py OUTDIR [duration_s]
"""
import os
import re
import subprocess
import sys
import time
import numpy as np
OUT = sys.argv[1]
DURATION = float(sys.argv[2]) if len(sys.argv) > 2 else 200.0
os.makedirs(OUT, exist_ok=True)
W, H = 1280, 720
env = dict(os.environ)
env["HOME"] = "/sylph-home/re"
env["SDL_AUDIODRIVER"] = "dummy"
env["DISPLAY"] = ":98"
env["XENIA_PAD_FILE"] = os.path.join(OUT, "pad.txt")
def glyph(a):
r, g, b = a[:, :, 0], a[:, :, 1], a[:, :, 2]
return int(((g > 130) & (g - r > 45) & (g - b > 45)).sum())
def _open():
return subprocess.Popen(
["ffmpeg", "-loglevel", "error", "-f", "x11grab", "-draw_mouse", "0",
"-video_size", f"{W}x{H}", "-i", ":98", "-r", "6",
"-f", "rawvideo", "-pix_fmt", "rgb24", "-"],
stdout=subprocess.PIPE, bufsize=W * H * 3 * 4)
def grab(p, n):
buf = p.stdout.read(n)
if len(buf) < n:
return None
return np.frombuffer(buf, np.uint8).reshape(H, W, 3).astype(float)
def main():
with open(env["XENIA_PAD_FILE"], "w"):
pass
subprocess.run(["xsetroot", "-solid", "black"], env=env, check=False)
xuid = os.environ.get("SYLPH_XUID", "")
if not xuid:
content = "/sylph-home/re/.local/share/Xenia/content"
entries = os.listdir(content) if os.path.isdir(content) else []
xuid = entries[0] if entries else ""
if not xuid:
print("FATAL: no profile signed in -- run: run-canary "
"--create_profile_if_none=Tag, wait ~5s, kill it", flush=True)
return
canary_log_path = os.path.join(OUT, "canary.stdout")
canary_log = open(canary_log_path, "w")
proc = subprocess.Popen(
["run-canary", f"--logged_profile_slot_0_xuid={xuid}",
"--xma_param_probe=true", "--log_level=2"],
cwd=OUT, env=env, stdout=canary_log, stderr=subprocess.STDOUT)
print(f"canary pid={proc.pid}, xma_param_probe=true, waiting for window",
flush=True)
T0 = time.time()
while not subprocess.run(
["xdotool", "search", "--name", "Xenia-canary"],
capture_output=True, text=True).stdout.strip():
if time.time() - T0 > 60:
print("FATAL: no window after 60s", flush=True)
return
time.sleep(1)
print(f"[{time.time()-T0:6.1f}s] window exists, recording", flush=True)
glyph_out = open(os.path.join(OUT, "glyph-timeseries.tsv"), "w")
glyph_out.write("# t_s\tglyph\n")
xma_seen = set()
xma_out = open(os.path.join(OUT, "xma-param-arrivals.tsv"), "w")
xma_out.write("# t_s\tline\n")
XMA_RE = re.compile(rb"XMA-PARAM.*")
p, n = _open(), W * H * 3
seg = time.time()
log_pos = 0
while time.time() - T0 < DURATION:
el = time.time() - T0
if time.time() - seg > 30:
p.kill(); p = _open(); seg = time.time()
a = grab(p, n)
if a is not None:
g = glyph(a)
glyph_out.write(f"{el:.2f}\t{g}\n")
glyph_out.flush()
else:
p.kill(); p = _open(); seg = time.time()
# Drain any new XMA-PARAM lines that arrived since last check --
# stamped on ARRIVAL (Xenia's own log lines carry no timestamp),
# same technique xma_readoff_trace.py already uses.
try:
with open(canary_log_path, "rb") as f:
f.seek(log_pos)
chunk = f.read()
log_pos = f.tell()
except FileNotFoundError:
chunk = b""
for line in chunk.splitlines():
if XMA_RE.search(line):
key = line
if key not in xma_seen:
xma_seen.add(key)
xma_out.write(f"{el:.2f}\t{line.decode('utf-8','replace')}\n")
xma_out.flush()
print(f"[{el:7.1f}s] NEW {line.decode('utf-8','replace')}",
flush=True)
p.kill()
glyph_out.close()
xma_out.close()
print(f"[{time.time()-T0:7.1f}s] killing emulator, "
f"{len(xma_seen)} distinct XMA-PARAM lines seen", flush=True)
proc.terminate()
try:
proc.wait(timeout=10)
except subprocess.TimeoutExpired:
proc.kill()
canary_log.close()
if __name__ == "__main__":
main()