No Canary patch was needed: UpdateLoopStatus already logs loop_start/loop_end, they just need the Apu category (--log_mask=13 --log_level=3). Decoded, from the menu, 8734 records all after BGM_103's contexts appear: ctx0 (wave 3876864) loop_start 3605682 loop_end 25640423 loop_count 255 ctx1 (wave 3930112) loop_start 3539158 loop_end 26216351 loop_count 255 The movie's three ADV streams log NO loop records -- they do not loop. Semantics visible in the trajectory: read_offset runs from 32 upward and 20 % of samples sit below loop_start, so the stream plays from the beginning and loop_start is where it returns AFTER loop_end. No wrap was observed -- the 45 s hold ended with read_offset at 17 M against a loop_end of 25.6 M. Two registered predictions REFUTED. loop_start is not ~0 but 11.6 % in. And a linear bits-to-seconds conversion is invalid: it gives 62.34 s and 63.29 s for two stems that must play sample-synchronously, which is impossible, so the data refutes the assumption on its own. That leaves a conflict I am not resolving: the field implies a cycle of roughly [10 s, 72 s]; my audio tracking reported offsets 0.25..57.18 s. Recorded as contested, with the likely weak link named as mine -- that locator's control used slices cut from the wave itself, exact copies, which is an easier problem than matching a real capture, and a control easier than the measurement does not bound its error. The port is told to change nothing: its trimmed 61.93 s loop is verified in its own output, and the length survives better than the placement. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Wuu56cE8vJGTBtn1ppsk8v
115 lines
5.3 KiB
Markdown
115 lines
5.3 KiB
Markdown
# ✅ The menu BGM loops at **61.93 s**, not at the wave's 87.744 s — and there is no seam
|
||
|
||
**Classification: measured.** Xenia Canary, 2026-08-30, one boot, **240 s parked on
|
||
the main menu**. Replaces the authored loop the port was shipping, and it is wrong
|
||
in both directions it could be.
|
||
|
||
## Getting there — the log as the screen oracle
|
||
|
||
[`menu-bgm-loop-not-yet-captured.md`](menu-bgm-loop-not-yet-captured.md) recorded
|
||
why the obvious rig fails: an audio tee plus rendering runs at ~0.20× real time and
|
||
never reaches the menu. The route written down there was to drop video and let the
|
||
**XMA probe** say what screen we are on. It works, and the difference is not
|
||
marginal:
|
||
|
||
| | video rig | log oracle |
|
||
|---|---|---|
|
||
| menu reached | **never**, in 378 s | **26.8 s** |
|
||
| guest speed | ~0.20× | ~0.92× |
|
||
| capture silence | (broken pipe) | **0.08 %** |
|
||
|
||
`BGM_103`'s two waves decoding *is* the menu, and **no `ADV` context appears after
|
||
that point**, so the attract loop never took over — the hold is verified, not
|
||
assumed. 0.08 % silence is cleaner than the 0.31 % the recipe page records for its
|
||
own best run.
|
||
|
||
## 🔴 Result 1 — there is no seam
|
||
|
||
**Zero** runs of ≥0.3 s below (median − 18 dB) in 232 s of menu audio. The **3.4 s
|
||
near-silence** the port measured is a property of its authored loop, not of the
|
||
game.
|
||
|
||
## 🔴 Result 2 — it does not loop at the wave length
|
||
|
||
| lag | autocorrelation r |
|
||
|---|---|
|
||
| **87.750 s** (the wave) | **−0.009** |
|
||
| 61.909 s | **+0.533** |
|
||
| 123.819 s (2×) | +0.269 |
|
||
|
||
r at the wave length is **zero**, on four independent windows (−0.0090 / −0.0061 /
|
||
−0.0067 / −0.0055). The estimator recovers known periods exactly on synthetic
|
||
controls (87.750 → 87.750, 60.000 → 60.000).
|
||
|
||
## ✅ Result 3 — a second instrument, and the loop bounds
|
||
|
||
`BGM_103`'s two waves were dumped from `sound.pak`, decoded and **summed** (they
|
||
play together), then 30 s slices of the capture were located inside that mix by
|
||
envelope correlation. **Control: slices cut from the wave itself at 10 / 45 / 70 s
|
||
are found at 10.00 / 45.00 / 70.00 s.**
|
||
|
||
Playback advances **exactly +5.00 s per 5 s** of wall clock — 1:1, no resampling —
|
||
and wraps:
|
||
|
||
| | | |
|
||
|---|---|---|
|
||
| t=80 → 90 | 54.09 → 2.16 | 54.09 + 10 − **61.93** = 2.16 |
|
||
| t=145 → 150 | 57.18 → 0.25 | 57.18 + 5 − **61.93** = 0.25 |
|
||
| t=205 → 215 | 55.27 → 3.35 | 55.27 + 10 − **61.93** = 3.34 |
|
||
|
||
**Loop length 61.93 s**, from three independent wraps, agreeing with the
|
||
autocorrelation's 61.909 s produced by a completely different method.
|
||
|
||
⚠️ **Two points in the series are mis-locked, and they flag themselves**: the
|
||
slices at t=85 and t=210 straddle the wrap, so they half-match two places and
|
||
return the **two lowest scores in the table** (0.266, 0.272 against a 0.33–0.50
|
||
field). Read across them; do not fit to them.
|
||
|
||
### Where the loop sits in the wave
|
||
|
||
Observed offsets span **0.25 … 57.18 s** of an **87.744 s** wave. With a 61.93 s
|
||
period the region is **[≈0, 61.93)** — so the final **~25.8 s of the wave is never
|
||
played**.
|
||
|
||
✅ **And that explains the missing seam.** [`bgm-two-stems.md`](bgm-two-stems.md)
|
||
found that these tracks *fade out and are followed by seconds of silence*, and
|
||
concluded that looping the wave as stored "gives a fade-out and six seconds of
|
||
nothing every cycle". **The game never reaches the fade.** It loops before it.
|
||
|
||
## What the port should do
|
||
|
||
* **Loop at 61.93 s, not 87.75.** ⚠️ *Measured*, so it is authored on your side —
|
||
but it is now authored from an observation rather than from the file's length.
|
||
* **Expect no silence at the seam.** If your loop has one, that is your loop.
|
||
* Play **both waves summed**, aligned at 0, unchanged.
|
||
|
||
## 🔴 Contested (2026-08-30, later) — read the XMA context page before using the offsets
|
||
|
||
Reading `loop_start` / `loop_end` out of the running decoder
|
||
([`menu-bgm-loop-fields-conflict.md`](menu-bgm-loop-fields-conflict.md)) puts the
|
||
cycle's **start** at ~11.6 % into the wave, where this page's locator put the
|
||
observed offsets at 0.25 s. **Both cannot be right.**
|
||
|
||
⚠️ **And the weak link is likely this page's control.** It located slices **cut from
|
||
the wave itself** — exact copies, a far easier problem than matching a real capture
|
||
that differs by decoder, gain and mix. A control easier than the measurement does not
|
||
bound the measurement's error. The clean +5.00 s stepping shows the locator is
|
||
*self-consistent*; it does not show it locked to the right phrase, and music with
|
||
repeated sections is where a locator aliases.
|
||
|
||
**The 61.93 s period survives better than the placement**: it is corroborated by an
|
||
autocorrelation that used no wave at all, and the port's trimmed loop plays without
|
||
a seam. Treat the *length* as measured and the *start* as open.
|
||
|
||
## Reach
|
||
|
||
⚠️ **One boot, one screen, one bank.** `BGM_103` on the main menu.
|
||
⚠️ **61.93 s is the period, and the loop START is inferred** from offsets reaching
|
||
0.25 s and the period being 61.93 — not from watching the wrap at 5 s resolution
|
||
near zero. A loop of `[0.0, 61.93)` and one of `[0.25, 62.18)` are not separated
|
||
here.
|
||
⚠️ **Where the value lives is unknown.** `bgm-two-stems.md` says no loop-point field
|
||
was found in the *file* header, and that stands — XMA carries loop bounds in the
|
||
**decoder context** the game fills at runtime, which is where to look next. Not
|
||
looked at.
|