re: land the loop-point correction where the claim actually lives

The port found its exporter still shipping 'no loop-point field has been
identified anywhere' in the field manifest.json concatenates, days after the
correction existed in other fields. Auditing this corpus the same way found the
same failure here: the refuted sentence was still standing untouched in
bgm-two-stems.md -- where anyone looking up BGM behaviour arrives -- and in
HANDOFF.md, the one page the port is told to read. My correction had gone into a
NEW page only.

Both fixed in place, each naming the refutation rather than quietly deleting the
old claim, and each carrying the measured window [9.44, 71.31] s at 61.87 s.

METHOD entry: writing a correction down is not landing it. Grep the corpus for
the CLAIM, not for the file you were working in. Plus the port's trap in doing
that audit -- a replacement that quotes the refuted sentence in order to name it
will match a substring search from inside the paragraph saying it is false.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wuu56cE8vJGTBtn1ppsk8v
This commit is contained in:
sylph-decoder
2026-08-30 10:20:04 +00:00
parent e1f2a7f714
commit 12159be61f
4 changed files with 135 additions and 8 deletions

View File

@@ -1727,9 +1727,13 @@ discovered it after re-exporting. ❔ The sweeps may simply be a small term, and
layer — 🟡 which of those is unsettled, and `ChannelMask` is `0x0002` on both,
so the file will not say. Today's 347 s concatenation plays the piece twice,
the second time as a bass-less stem.
**And it is not a seamless loop**: `BGM_001` fades out at 167.663 s and is
followed by 6.15 s of silence, with no loop-point field identified. A menu loop
is authored.
🔴 **"not a seamless loop … no loop-point field identified … a menu loop is
authored" — REFUTED 2026-08-30, and this bullet kept saying it for days after
the correction existed elsewhere.** The fade and the 6.15 s of silence are real,
but there **is** a loop point — in the **XMA decoder context**, set at runtime by
`XMASetLoopData`. For `BGM_103` it is **`[9.44 s, 71.31 s]`, cycling every
61.87 s**, watched over three wraps. The game never reaches the fade, which is
why the stored tail looked unusable. See the 2026-08-30 entries at the top.
**The menu's music is `BGM_103`.** The cue *table* cannot say — its BGM
entries are numeric — but `GamePart_Title`'s `sub_821C5580` plays **cue 1103**,
and `BGM_103.slb`'s two declared waves (3 876 864 / 3 930 112 B) are

View File

@@ -1431,3 +1431,31 @@ too — a span of zero cannot occur. Both cases point the same way:
**The tell is in the rows that agree with you.** Scrutinising the disconfirming half
harder is the intuitive discipline and it is the wrong half — a broken instrument
and an easy control both fail *silently on the side you were hoping for*.
## A correction that does not reach the artifact a consumer reads has not been made
The port agent found that after correcting `loop_end_why` and `loop_start_why`, the
field its exporter actually concatenates into `manifest.json``loop_why` — was
**still shipping** "AUDIBLY WRONG AT THE SEAM" and "no loop-point field has been
identified anywhere". Both refuted days earlier. Its corrections existed, were
accurate, and were in the wrong fields.
**And the same audit against this corpus found the same failure here.** The claim
*"no loop-point field has been identified in the XMA header, so a menu loop is
authored"* was refuted on 2026-08-30 — in a **new page**. The sentence stayed
untouched in [`bgm-two-stems.md`](structures/bgm-two-stems.md), which is where
anyone looking up BGM behaviour arrives, **and** in `HANDOFF.md`, which is the one
page the port is told to read. A reader following either would have got the dead
answer with nothing to warn them.
⚠️ **Writing the correction down is not the same as landing it.** A new page records
that you learned something; it does not stop the old sentence being read. When
something is refuted, **grep the corpus for the claim, not for the file you were
working in**, and fix it where it is stated — especially in `HANDOFF.md`, whose
whole purpose is to be the page somebody reads instead of the rest.
📌 And a trap in doing that audit, also the port's: its first verification reported
the stale text **still present**, because the replacement *quotes* the refuted
sentences in order to name them — so a substring search matches them inside the
paragraph saying they are false. The match was real and meant the opposite of what
the search implied. This is "the tell is in the rows that agree" arriving as a grep.

View File

@@ -117,11 +117,22 @@ transcribing one.
* a music bank is **two waves that play together**, not one track and not a
sequence — do not concatenate;
* both are full length; align them at sample 0;
* **the track is not a seamless loop.** `BGM_001` fades out and is followed by
**6.15 s of silence** (last sound 167.663 s of 173.809 s; the final second
before silence is at RMS 168 against 4 788 at the head). Looping the wave as
stored gives a fade-out and six seconds of nothing every cycle. No loop-point
field has been identified in the XMA header, so a menu loop is **authored**.
* **the track is not a seamless loop *as stored*.** `BGM_001` fades out and is
followed by **6.15 s of silence** (last sound 167.663 s of 173.809 s; the final
second before silence is at RMS 168 against 4 788 at the head). Looping the wave
end-to-end gives a fade-out and six seconds of nothing every cycle.
🔴 **But "no loop-point field has been identified … so a menu loop is
authored" is REFUTED (2026-08-30), and this bullet said it for days after.**
There is no loop point in the **file header**; there is one in the **XMA decoder
context**, written at runtime via `XMASetLoopData`, and Xenia already logs it.
For `BGM_103` it is **`[9.44 s, 71.31 s]`, cycling every 61.87 s** — measured by
watching three wraps, so the game never reaches the fade at all. That is *why*
the stored tail looks unusable: it is never played.
[`menu-bgm-loop-fields-conflict.md`](menu-bgm-loop-fields-conflict.md)
⚠️ Reach: measured on `BGM_103` only. Whether every bank carries loop bounds is
untested — but "the format has nowhere to put one" is dead.
## ✅ Confirmed at runtime — two stereo streams decode at once

View File

@@ -0,0 +1,84 @@
#!/usr/bin/env python3
"""Reach a submenu that carries a `.tbm` and capture it.
`ui-forced-backdrop.md` leaves 24 of its 62 deciding verdicts on `.tbm` elements
whose pixels this corpus cannot locate: not in the bundle, not a file, not a pak
entry, and **not in our composite** — `compose` skips an element with no resolvable
sprite, so our renderer draws *nothing* for a `.tbm`. The open question is whether
the game draws anything either. If it does not, those verdicts are inert rather
than correct.
⚠️ **No focus detector is needed, and that is deliberate.**
`s00a-drive-blocked-by-focus.md` records that a per-row brightness statistic
**failed its own control**, and that wrap-around makes counting presses useless.
But every main-menu destination except `EXTRAS` lands on an archive holding a
`.tbm` decider — `GP_SYSTEM` (`pqbase`), `GP_TUTORIAL` (`pubase`),
`GP_SAVE_LOAD` (`px_replay_base`), `GP_DIALOG` (`pcbase`). So pressing Ⓐ on
whatever happens to be focused is very likely to land somewhere useful, and the
screen is identified **afterwards, from the capture**, rather than chosen in
advance.
tbm_screen_capture.py OUTDIR [wait_s]
"""
import os
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 420
W, H = 1280, 720
NEED, CEIL, HOLD = 500, 2500, 12
PAD = os.path.join(os.path.dirname(os.path.abspath(__file__)), "pad.py")
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 tap(btn="A"):
subprocess.run([sys.executable, PAD, "tap", btn, "0.12"], check=False)
print(f"[{time.time()-T0:7.1f}s] tapped {btn}", flush=True)
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 = _open(), W * H * 3, time.time()
log = open(f"{OUT}/series.tsv", "w"); log.write("# t_s\tglyph\tmean\tphase\n")
phase, streak, mark = "wait", 0, None
while True:
el = time.time() - T0
if phase == "wait" and el > WAIT:
print("TITLE NEVER APPEARED", 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(int)
c = glyph(a)
log.write(f"{el:.3f}\t{c}\t{a.mean():.3f}\t{phase}\n"); log.flush()
if phase == "wait":
streak = streak + 1 if NEED <= c <= CEIL else 0
if streak >= HOLD:
tap(); mark = time.time(); phase = "menu"
elif phase == "menu" and time.time() - mark > 8:
Image.fromarray(a.astype(np.uint8)).save(f"{OUT}/menu.png")
print(f"[{el:7.1f}s] menu captured (glyph {c}) — pressing A into a submenu", flush=True)
tap(); mark = time.time(); phase = "submenu"
elif phase == "submenu" and time.time() - mark > 10:
Image.fromarray(a.astype(np.uint8)).save(f"{OUT}/submenu.png")
print(f"[{el:7.1f}s] submenu captured (glyph {c}, mean {a.mean():.1f})", flush=True)
break
p.kill(); log.close()