#!/usr/bin/env python3 """Park on the menu and record it, using the XMA probe log as the screen oracle. `menu-bgm-loop-not-yet-captured.md` records why the obvious rig fails: an audio tee plus rendering runs the guest at ~0.20x real time and never reaches the menu. `--gpu=null` is ~0.96x and gives a clean capture, but has no video — so the screen oracle has to come from somewhere else. It comes from the decoder. `bgm-two-stems.md` measured that sitting on the main menu decodes exactly `BGM_103`'s two declared waves, **3 876 864** and **3 930 112 B**, concurrently. `--xma_param_probe` logs every stream handed to the decoder, so those two byte_sizes appearing IS the menu — evidence about what is being *recorded*, which is better provenance for an audio question than a screenshot ever was. Presses are driven off the same log rather than a stopwatch: * `ADV`'s contexts (1 294 336 / 1 118 208 / 1 171 456) mean the intro is playing → one Ⓐ ends it (Q9); * then Ⓐ again for title → menu, retried at most RETRIES times; * the moment `BGM_103` appears, **stop pressing** — Ⓐ on the menu activates a button and leaves it. ⚠️ `ADV` reappearing after that would mean the attract loop took over, i.e. we are not parked. That is checked and reported, not assumed. menu_bgm_logdriven.py LOG OUTDIR [hold_s] """ import os import re import subprocess import sys import time LOG = sys.argv[1] OUT = sys.argv[2] HOLD = float(sys.argv[3]) if len(sys.argv) > 3 else 240 PAD = os.path.join(os.path.dirname(os.path.abspath(__file__)), "pad.py") ADV = {1_294_336, 1_118_208, 1_171_456} BGM103 = {3_876_864, 3_930_112} RETRIES = 4 SIZE = re.compile(rb"byte_size=(\d+)") def seen(path, since=0): """All byte_sizes in the log, and the byte offset reached.""" try: with open(path, "rb") as f: f.seek(since) data = f.read() return {int(m.group(1)) for m in SIZE.finditer(data)}, since + len(data) except FileNotFoundError: return set(), since def tap(): subprocess.run([sys.executable, PAD, "tap", "A", "0.12"], check=False) print(f"[{time.time()-T0:7.1f}s] tapped A", flush=True) T0 = time.time() off, taps, menu_at = 0, 0, None adv_seen = False ev = open(f"{OUT}/events.tsv", "w") ev.write("# t_s\tevent\n") while time.time() - T0 < 900: new, off = seen(LOG, off) if new & ADV and not adv_seen: adv_seen = True ev.write(f"{time.time()-T0:.2f}\tADV decoding (intro movie)\n"); ev.flush() print(f"[{time.time()-T0:7.1f}s] ADV contexts seen — intro is playing", flush=True) time.sleep(3) tap(); taps += 1 continue if new & BGM103 and menu_at is None: menu_at = time.time() ev.write(f"{menu_at-T0:.2f}\tBGM_103 decoding (MENU)\n"); ev.flush() print(f"[{menu_at-T0:7.1f}s] BGM_103 contexts — ON THE MENU. holding {HOLD}s, no more input", flush=True) if menu_at is None and adv_seen and taps <= RETRIES and (time.time() - T0) % 25 < 0.6: tap(); taps += 1 time.sleep(1) if menu_at and time.time() - menu_at > HOLD: print(f"[{time.time()-T0:7.1f}s] hold complete", flush=True) break time.sleep(0.5) if menu_at is None: print("NEVER REACHED THE MENU (BGM_103 never decoded)", flush=True) else: late, _ = seen(LOG, 0) ev.write(f"{time.time()-T0:.2f}\tend; taps={taps}\n") print("taps:", taps, flush=True) ev.close()