Files
Sylpheed/docs/re/structures/ui-record-loop-length.md
sylph-decoder 07e93ce041 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
2026-08-29 19:40:23 +00:00

5.0 KiB
Raw Blame History

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 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
ptbtn01fptbtn05f — 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 — 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), so the factor is 1.0885 against a nominal 60 units/s.

plate period nominal factor it would need to reach 2.122.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.122.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.122.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 244269, 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

cargo run -p sylpheed-formats --example record_loop_length
SYLPHEED_DISC=/disc cargo test -p sylpheed-formats --test ui_record_loop_length_disc