re: a nested record's +0x08 is its loop length -- the plate's period is 120, not 105
Answers the question the port agent asked: does the `PRESS A` plate's pulse group loop from its start, or hold at alpha 0 between cycles? It holds. A nested record is itself a RATC bundle with its own header, and that header's `+0x08` is the loop length -- the same field ui_header_time_disc already tests as an animation length at the top level. Its keyframes need not fill it, and the slack is a hold at the final pose. `ptbtn00f` is 105 units of ramp inside a 120-unit cycle, so the glow rests dark for 15 units between pulses. The five main-menu focus records fill their 120 exactly, which is what shows the slack belongs to this record rather than to the format. Disc-wide over 1 781 timed nested records: 92.3% declare exactly their last keyframe time, 7.7% declare more, and 0 declare less. That last row is the falsifier -- a cycle cannot restart before its own last pose -- and it never fires; the 7.7% is what keeps the reading from being an unfalsifiable relabelling of the keyframes. Falsification against the running game, using a pacing factor measured INDEPENDENTLY on the main menu's focus ring (declared 120 units, measured 2.177 s, factor 1.0885): to reach the corpus's four measurements of the plate pulse (2.12/2.19/2.34/2.31 s), a 105-unit period needs a factor of 1.211-1.337, which EXCLUDES the ring's; a 120-unit period needs 1.060-1.170, which CONTAINS it. Predicted 2.177 s against a measured 2.12-2.34. The two elements are in different bundles and were measured in separate runs; the only thing tying them together is that both declare 120. So the port should stop shipping 105. Its 123-vs-129 ambiguity straddled the right answer without containing it, and 129 only fitted because it was 105 + the exit_ramp_units constant it has since correctly deleted. Reach is stated: this says where a cycle ends, not which records cycle, and the TOP-level +0x08 is a different field left untouched -- every GP_TITLE entry declares 300 while its elements end at 244-269. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QsEPXWVaEpyfudtR6re1Pd
This commit is contained in:
@@ -169,3 +169,4 @@ files, which is how the same ground got covered twice.
|
||||
| [`menu-idle-and-b-2026-08-29.md`](menu-idle-and-b-2026-08-29.md) | The main menu does not idle back to the title — and four durations that were a pipeline | ✅ **refuted**: no self-return in **≥ 60 s** untouched; the ~8–10 s idle belongs to the **title**. 🟡 Ⓑ→title ordering measured, latency not. 🔴 `classify_array` at **1503 ms/frame** drained an 8 fps stream at 0.64 fps and manufactured four latencies (24.66 s / 15.58 s / 25.60 s / 20.26 s) — all withdrawn; a backlog preserves ordering and destroys durations |
|
||||
| [`structures/ui-settle-time.md`](structures/ui-settle-time.md) | Which instant a "settled screen" composite depicts | ✅ **decoded**: a settled screen is **one instant every element is posed at**, and the disc names it — the midpoint of the **longest keyframe-free interval** in the build (`UiBuild::settle_time` / `settle_window`). 🔴 `rest()` is *not* that: it picks each element's last hold **independently**, so a two-frame flash holds at its **peak** and burns forever. `GP_TITLE` build 4 has five staggered flashes (`ptlogo_back2eff1`…`eff5`, all extinguished by t110) that `rest()` draws simultaneously and permanently, saturating the light arc. Predicted t=198 from `[160,236]` **before scoring**: arc band **33.22 → 11.79**, clipped pixels **8 581 → 1 452** against the console's **1 459** (an unfitted statistic), whole frame 14.07 → 12.06; controls at t=100 and t=358 are far worse, and a hand-picked visibility list reaches the identical 12.06/11.79/1 452. Controls: `at=None` byte-identical (`cmp`), pre- and post-rotation tags both 14.07, 13 paint-order tests green. ⚠️ **Reach**: of 1 758 bundles with ≥2 keyframe times only **30 %** have a window ≥ 30 units and **42 %** under 10 — mostly `loop*` fragments that never settle; check the width. 🔴 Withdraws two claims in [`ui-rotation-implemented.md`](structures/ui-rotation-implemented.md) — its "Flat. No minimum." (`at` posed **leaves only**) and its "Reborn does not draw `ptlogo1`/`ptlogo2`" (both **are** drawn; only kind-`0x4` ghosts are skipped, and hiding the real ones makes the error *worse* by +5.20/+7.47). ❔ its **10.92** baseline is unreproducible — 14.07 at both tags |
|
||||
| [`structures/ui-tie-break-cost-at-settle.md`](structures/ui-tie-break-cost-at-settle.md) | What the unknown paint-order tie-break costs, in pixels | ✅ **decoded**, closing the open half of Q3: at the settled instant the tie-break costs **at most 1 px at Δ1**, on the **Japanese title only** (`ptlogo2`×`ptlogo_tm`, 5 px shared ink); **exactly 0 px on all five port screens**. The earlier 24-pair bound was a `rest()` count — and 10 of the title's 11 tied pairs are between `ptlogo_back2eff1`…`eff5`, five transient flashes that are **transparent** on the settled screen ([`ui-settle-time.md`](structures/ui-settle-time.md)). Live pairs at settle: entry 4 → **1**, entry 7 → **2**, the four loading bundles → **0**. ✅ Not a knife-edge — sweeping every keyframe time and midpoint, the count is **flat across the whole settle window**, and the loading bundles' tie is live only at t17–t33. ✅ Controls: an overlapping *different*-key swap moves 25 310 / 268 698 / ~765 000 px on the entries reporting zero; zeros are explained by shared-ink counts (the `ptframe` pairs share **0 px** of ink). ⚠️ Entries 0/1/12/15 have **no live control** — their zeros rest on keyframe data, not a render. 🟡 Refutation attempt on the corpus's "24 pairs": **survives** as a rest-pose bound, 16/16 on entry 7. ❔ *Why* ties order as they do is still unknown — and now worth one pixel |
|
||||
| [`structures/ui-record-loop-length.md`](structures/ui-record-loop-length.md) | Where a looping record's cycle restarts — and the `PRESS Ⓐ` plate's real period | ✅ **decoded**: a nested record is itself a RATC bundle and its header **`+0x08` is the loop length**; its keyframes need not fill it, and the slack is a hold at the final pose. Disc-wide over **1 781** timed nested records: 92.3 % declare exactly their last keyframe time, **7.7 % declare more**, and **0 declare less** — the falsifier (a cycle cannot restart before its own last pose) never fires. 🔴 **The plate's `ptbtn00f` is 105 units of ramp inside a 120-unit cycle, so it holds dark for 15 units** — the port was shipping **105**, and the answer is **120**. ✅ Falsification test against the running game, using a pacing factor measured *independently* on the focus ring (declared 120 → **2.177 s**, factor **1.0885**): to reach the corpus's measured 2.12–2.34 s, 105 units needs a factor of **1.211–1.337** (🔴 excludes the ring's) while 120 needs **1.060–1.170** (✅ contains it). Different elements, different bundles, separate runs — tied only by both declaring 120. ⚠️ Says where a cycle *ends*, not which records cycle. ❔ the **top-level** `+0x08` (300 on every `GP_TITLE` entry, elements ending at 244–269) is a different question, untouched |
|
||||
|
||||
20
docs/re/data/record-loop-length-census.txt
Normal file
20
docs/re/data/record-loop-length-census.txt
Normal file
@@ -0,0 +1,20 @@
|
||||
nested records with timed keyframes : 1781
|
||||
+08 == max keyframe time (exact) : 1643 (92.3%)
|
||||
+08 > max keyframe time (a hold) : 138 (7.7%)
|
||||
+08 < max keyframe time 🔴 : 0 (0.00%) <- the falsifier
|
||||
|
||||
slack (+08 - max t) distribution, most common first:
|
||||
slack 0 : 1643
|
||||
slack 40 : 32
|
||||
slack 10 : 20
|
||||
slack 4 : 13
|
||||
slack 54 : 12
|
||||
slack 30 : 8
|
||||
slack 1 : 6
|
||||
slack 6 : 6
|
||||
slack 9 : 6
|
||||
slack 36 : 6
|
||||
slack 405 : 6
|
||||
slack 16 : 5
|
||||
slack 58 : 5
|
||||
slack 80 : 5
|
||||
106
docs/re/structures/ui-record-loop-length.md
Normal file
106
docs/re/structures/ui-record-loop-length.md
Normal file
@@ -0,0 +1,106 @@
|
||||
# A nested record's `+0x08` is its loop length — and the plate holds dark for 15 units
|
||||
|
||||
**Classification: decoded.** The field is read from the disc and checked
|
||||
disc-wide (1 781 records, 0 violations). The consequence for the `PRESS Ⓐ` plate
|
||||
is then tested against the corpus's existing measurements of the running game,
|
||||
which it passes and the previous reading fails.
|
||||
|
||||
## The question
|
||||
|
||||
The port asked it directly: the plate's pulse group runs t=0 → t=105 with alpha 0
|
||||
at both ends. **Does it loop from its start, or hold at alpha 0 between cycles?**
|
||||
|
||||
It matters because a 105-unit period is **1.750 s**, and the corpus timed the real
|
||||
pulse four times at **2.12 / 2.19 / 2.34 / 2.31 s** — about 17 % longer. The port
|
||||
shipped 105 anyway, saying the disc's number was wrong, because the alternative
|
||||
(129) was built on an `exit_ramp_units` constant that a decode had just deleted.
|
||||
|
||||
## The answer: it holds, and the period is 120
|
||||
|
||||
A nested record is **itself a RATC bundle, with its own header**, and that
|
||||
header's `+0x08` is a frame count — the same field
|
||||
[`ui-header-time-disc`](../../crates/sylpheed-formats/tests/ui_header_time_disc.rs)
|
||||
already tests as an animation length at the top level. Read it on the record and
|
||||
the answer is immediate:
|
||||
|
||||
| record | `+0x08` | largest keyframe | slack |
|
||||
|---|---|---|---|
|
||||
| `ptbtn00f.rat` — the plate glow | **120** | 105 | **15** |
|
||||
| `ptbtn01f` … `ptbtn05f` — main menu focus | 120 | 120 | 0 |
|
||||
| `ptloop01.rat` | 600 | 600 | 0 |
|
||||
| `ptloop02.rat` | 720 | 720 | 0 |
|
||||
|
||||
**The glow ramps 0 → 80 → 0 over 105 units inside a 120-unit cycle, so it rests
|
||||
dark for 15 units between pulses.** The five main-menu focus records fill their
|
||||
cycle exactly, which is what shows the slack is a property of *this record* rather
|
||||
than of the format.
|
||||
|
||||
## Disc-wide
|
||||
|
||||
[`record-loop-length-census.txt`](../data/record-loop-length-census.txt) — every
|
||||
nested record on the disc that has a timed keyframe:
|
||||
|
||||
| | count | share |
|
||||
|---|---|---|
|
||||
| nested records with timed keyframes | 1 781 | — |
|
||||
| `+0x08 == ` largest keyframe time | 1 643 | 92.3 % |
|
||||
| `+0x08 > ` largest keyframe time (a hold) | 138 | 7.7 % |
|
||||
| **`+0x08 < ` largest keyframe time** | **0** | **0.00 %** |
|
||||
|
||||
The last row is the falsifier and it never fires: **no record declares a cycle
|
||||
that would restart before its own last pose.** The 7.7 % is what keeps the reading
|
||||
from being an unfalsifiable relabelling of the keyframes — if every record
|
||||
declared exactly its own last keyframe time, the field would carry nothing.
|
||||
|
||||
## The falsification test against the running game
|
||||
|
||||
Both hypotheses are periods in declared units, and both have to be converted by
|
||||
the same emulator pacing factor. That factor is measured **independently**, on the
|
||||
main menu's focus ring: declared 120 units, measured **2.177 s**
|
||||
([`focus-ring-spin-measured.md`](../focus-ring-spin-measured.md)), so the factor
|
||||
is **1.0885** against a nominal 60 units/s.
|
||||
|
||||
| plate period | nominal | factor it would need to reach 2.12–2.34 s | verdict |
|
||||
|---|---|---|---|
|
||||
| 105 units | 1.750 s | **1.211 … 1.337** | 🔴 **excludes** the ring's 1.0885 |
|
||||
| **120 units** | 2.000 s | **1.060 … 1.170** | ✅ **contains** the ring's 1.0885 |
|
||||
|
||||
At 120 units and the ring's own factor the plate should pulse every **2.177 s**,
|
||||
against a measured 2.12–2.34 s. **105 cannot reach the measured range under any
|
||||
pacing factor that the ring also satisfies.**
|
||||
|
||||
This is a genuine test rather than a fit: the ring and the plate are different
|
||||
elements in different bundles, measured in separate runs, and the only thing tying
|
||||
them together is that both declare a 120-unit cycle.
|
||||
|
||||
## What this replaces
|
||||
|
||||
* 🔴 **105 is wrong** and the port should stop shipping it. The number is **120**,
|
||||
and it comes from the disc — not from `exit_ramp_units`, the deleted constant
|
||||
whose 129 happened to fit.
|
||||
* ✅ **The port's ambiguity is genuinely resolved**, just not the way it read: the
|
||||
old 123-vs-129 pair straddled the right answer without containing it.
|
||||
* ⚠️ **The 2.24 s mean is still ~3 % above the 2.177 s prediction.** That is inside
|
||||
the spread of the four measurements (2.12–2.34) and is not evidence of a further
|
||||
hold; it is what a four-sample wall-clock measurement of a ~2 s period in this
|
||||
emulator looks like.
|
||||
|
||||
## Reach
|
||||
|
||||
⚠️ **This says where a cycle ends, not that every record cycles.** 92.3 % of
|
||||
records declare no slack at all, and a record whose element is not in a repeating
|
||||
state (a button's base record, a one-shot build-in) has a length that is simply
|
||||
its own duration. Nothing here establishes *which* records the game restarts —
|
||||
only that when one does, `+0x08` is where.
|
||||
|
||||
❔ **The top-level `+0x08` is not the same thing.** Every `GP_TITLE` entry declares
|
||||
300 there while its elements end at 244–269, and no screen visibly repeats every
|
||||
5 s. Whether the top-level field is a loop length, a budget or something else is
|
||||
untouched by this.
|
||||
|
||||
## Reproducing
|
||||
|
||||
```bash
|
||||
cargo run -p sylpheed-formats --example record_loop_length
|
||||
SYLPHEED_DISC=/disc cargo test -p sylpheed-formats --test ui_record_loop_length_disc
|
||||
```
|
||||
Reference in New Issue
Block a user