diff --git a/docs/port/HANDOFF.md b/docs/port/HANDOFF.md index bc15beec..c67012e8 100644 --- a/docs/port/HANDOFF.md +++ b/docs/port/HANDOFF.md @@ -2529,6 +2529,39 @@ whatever it returns β€” the same reason a truncated log and a t=0 render both lo fine from inside. Template matching against the exported cue with a bed-only control has no such knob, which is the right fix rather than a better threshold. +## 🟑 2026-08-30 (later) β€” the loop IS a runtime field. **Change nothing yet.** + +`loop_start` / `loop_end` live in the **XMA decoder context**, set by +`XMASetLoopData`, and Xenia already logs them β€” no patch needed. Read from the menu: + +| ctx | wave | loop_start | loop_end | loop_count | +|---|---|---|---|---| +| 0 | 3 876 864 B | 3 605 682 | 25 640 423 | 255 | +| 1 | 3 930 112 B | 3 539 158 | 26 216 351 | 255 | + +βœ… Also decoded: the stream plays from offset **32**, and `loop_start` is where it +returns *after* `loop_end` β€” so the first pass is longer than later cycles. And the +movie's three `ADV` streams log **no** loop records at all: they do not loop. + +πŸ”΄ **But two of my predictions are refuted and there is a conflict I cannot +resolve**, so **do not re-author `loop_end_s` from this**: + +* `loop_start` is **not ~0** β€” it is 11.6 % into the stream. +* A linear bitsβ†’seconds conversion gives **62.34 s** and **63.29 s** for two stems + that must stay sample-synchronous. 0.95 s apart is impossible, so the conversion + is invalid β€” XMA frames are variable-length in bits. +* That implies a cycle of roughly **[10 s, 72 s]**, against the **0.25 … 57.18 s** + my audio tracking reported. Both cannot be right. + +⚠️ **The weak link is probably my own earlier control**: it located slices *cut from +the wave itself* β€” exact copies, an easier problem than matching your capture. A +control easier than the measurement does not bound its error. + +**The 61.93 s length survives better than the placement** β€” it has an +autocorrelation behind it that used no wave at all, and your trimmed loop has no +seam in your own output. Keep what you shipped. +[`menu-bgm-loop-fields-conflict.md`](../re/structures/menu-bgm-loop-fields-conflict.md) + ## βœ… 2026-08-30 β€” the menu BGM loops at **61.93 s**, and there is **no seam** **Measured, 240 s parked on the menu.** Your authored loop is wrong in both diff --git a/docs/re/data/menu-bgm-xma-loop-fields.txt b/docs/re/data/menu-bgm-xma-loop-fields.txt new file mode 100644 index 00000000..6c40af31 --- /dev/null +++ b/docs/re/data/menu-bgm-xma-loop-fields.txt @@ -0,0 +1,40 @@ +# The XMA context's loop fields for the menu bed, read from the running game. +# +# 2026-08-30. run-canary --gpu=null --xma_param_probe=true --log_mask=13 +# --log_level=3 (log_mask DISABLES categories: 13 = Kernel+Cpu+Gpu off, APU ON, +# and XELOGAPU is debug level, hence level 3). No Canary patch was needed -- +# UpdateLoopStatus already logs these. +# +# Menu reached by the log oracle in 26.8 s; 45 s hold; 8 734 'Looped Data' +# lines, ALL of them after BGM_103's contexts appear. The movie's three ADV +# streams produce NONE, i.e. loop_count = 0 on them. +# +# ctx -> wave, from the probe's own byte_size: +# ctx0 packets=1893 byte_size=3876864 (wave 0) +# ctx1 packets=1919 byte_size=3930112 (wave 1) +# +# LOOP FIELDS (bit offsets; loop_count 255 = infinite): +# ctx0 loop_start = 3 605 682 loop_end = 25 640 423 len = 22 034 741 +# ctx1 loop_start = 3 539 158 loop_end = 26 216 351 len = 22 677 193 +# +# READ-OFFSET TRAJECTORY over the 45 s hold: +# ctx0 min 32 max 16 944 845 ctx1 min 32 max 17 165 054 +# 20.0 % of samples are BELOW loop_start in both. +# Neither reaches loop_end, so NO WRAP was observed in this run. +# +# => playback starts at offset 32 (the first packet header) and runs forward; +# loop_start is where it returns AFTER loop_end. The first pass is longer +# than the cycles that follow. +# +# πŸ”΄ A LINEAR bits->seconds conversion is INVALID. Applied to each stem with +# its own byte_size it gives: +# ctx0 62.34 s ctx1 63.29 s +# Two stems that play sample-synchronously cannot have loop durations 0.95 s +# apart, so the linearity assumption is refuted by the data itself. XMA frames +# are variable-length in bits. +# +# πŸ”΄ AND IT CONFLICTS with the audio measurement. loop_start at 3.6 M of +# 31.0 M bits is 11.6 % in; linearly that is ~10 s, so the cycle would be +# roughly [10 s, 72 s] of the wave. The audio wave-offset tracking +# (menu-bgm-loop-measured.txt) put the observed offsets at 0.25 .. 57.18 s. +# Both cannot be right. Unresolved. diff --git a/docs/re/structures/menu-bgm-loop-fields-conflict.md b/docs/re/structures/menu-bgm-loop-fields-conflict.md new file mode 100644 index 00000000..25157746 --- /dev/null +++ b/docs/re/structures/menu-bgm-loop-fields-conflict.md @@ -0,0 +1,80 @@ +# 🟑 The loop point IS a runtime field β€” and reading it contradicts the audio measurement + +**Classification: decoded** (the fields and their values) **plus an unresolved +conflict** (what they mean in seconds). Xenia Canary, 2026-08-30, one boot, 45 s on +the menu. **The port should change nothing on the strength of this page.** + +## βœ… The loop point is not absent from the format + +[`bgm-two-stems.md`](bgm-two-stems.md) says *"no loop-point field has been +identified in the XMA header, so a menu loop is authored"*. That is true of the +**file header** and it left the wrong impression. The loop lives in the **XMA +decoder context**, set at runtime by `XMASetLoopData`, and Xenia's +`UpdateLoopStatus` already logs it β€” **no patch was needed**, only the Apu log +category (`--log_mask=13 --log_level=3`). + +| ctx | wave | `loop_start` | `loop_end` | `loop_count` | +|---|---|---|---|---| +| 0 | 3 876 864 B | **3 605 682** | **25 640 423** | 255 (infinite) | +| 1 | 3 930 112 B | **3 539 158** | **26 216 351** | 255 | + +Bit offsets. **8 734 records, every one after `BGM_103`'s contexts appear** β€” the +movie's three `ADV` streams log none at all, i.e. they do not loop. + +βœ… **And the semantics are visible in the trajectory.** `input_buffer_read_offset` +runs from **32** (the first packet header) 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`. The first pass is longer than the cycles after it. + +⚠️ **No wrap was observed.** Max read offset was 16.9 M / 17.2 M against a +`loop_end` of 25.6 M / 26.2 M β€” the 45 s hold was too short. The jump itself is +inferred from the field semantics, not watched. + +## πŸ”΄ Two things I predicted are refuted + +**1. "`loop_start` β‰ˆ 0" β€” no.** It is ~3.5–3.6 M bits, 11.4–11.6 % into the stream. +I registered that prediction before the run and it is wrong. + +**2. A linear bitsβ†’seconds conversion β€” invalid, and the data proves it.** Applied +to each stem with its own `byte_size`: + +| | linear loop duration | +|---|---| +| ctx0 | **62.34 s** | +| ctx1 | **63.29 s** | + +Two stems that play **sample-synchronously** cannot have loop durations 0.95 s +apart β€” they would drift a second per cycle. So the assumption fails on its own +output. XMA frames are variable-length in bits, which is exactly why. + +## πŸ”΄ The conflict, stated rather than resolved + +`loop_start` at 11.6 % of the stream implies a cycle of roughly **[10 s, 72 s]** of +the 87.744 s wave. But +[`menu-bgm-loop-measured.md`](menu-bgm-loop-measured.md) put the observed wave +offsets at **0.25 … 57.18 s**. **Both cannot be true.** + +⚠️ **And the weakness is probably mine.** That page's locator control used slices +**cut from the wave itself**, which are exact copies β€” a far easier matching problem +than a real capture, which differs by decoder, gain and mix. **A control that is +easier than the measurement does not bound the measurement's error**, and music +with repeated phrases is exactly where a locator aliases. The clean +5.00 s stepping +shows the locator is *self-consistent*; it does not show it locked to the right +phrase. + +So the honest position is: + +| claim | status | +|---|---| +| the loop is a runtime field, with these values | βœ… decoded | +| the movie streams do not loop | βœ… decoded | +| the cycle is **61.93 s** | 🟑 measured from audio, and its control was too easy | +| where the cycle *starts* in the wave | πŸ”΄ **contested** β€” 0.25 s from audio, ~10 s from the field | +| bits β†’ seconds | ❔ needs an XMA frame walk; not done | + +## What the port should do: nothing yet + +Its shipped loop β€” trim to 61.930 s β€” is verified in its own output and has no seam. +**Do not re-author it from this page.** What would settle it is walking the XMA +frame headers to convert `loop_start`/`loop_end` honestly, and a hold long enough to +**watch a wrap** rather than infer it. Neither is done. diff --git a/docs/re/structures/menu-bgm-loop-measured.md b/docs/re/structures/menu-bgm-loop-measured.md index 67f077cf..5215c68c 100644 --- a/docs/re/structures/menu-bgm-loop-measured.md +++ b/docs/re/structures/menu-bgm-loop-measured.md @@ -83,6 +83,24 @@ nothing every cycle". **The game never reaches the fade.** It loops before it. * **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.