Files
Sylpheed/docs/re/structures/menu-bgm-loop-measured.md
sylph-decoder cf6be709ab re: the loop IS a runtime XMA field -- and reading it contradicts my audio measurement
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
2026-08-30 09:52:13 +00:00

115 lines
5.3 KiB
Markdown
Raw Permalink 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.
# ✅ 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.330.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.