Files
Sylpheed/docs/re/structures/ui-clock-freezes-at-settle.md
sylph-decoder 78328c5022 re: the top-level clock freezes at the settle point -- closing the 114-vs-120 gap
Measured in the title draw capture, re-read with the per-quad parser.

GP_TITLE build 4 declares t = 0..269, about 120 presented frames at this run's
pacing. The title dwell lasted ~1100. ptcopyright declares alpha >= 1 for 106
units (t=138..244) and is drawn for 1050 frames; ptlogo1 declares an exit at
t=264 and is drawn for 1095. Both vanish within three frames of the dwell
ending.

So the top-level clock advances through the build-in, stops inside the settle
window [160,236], and holds. The exit ramp is not played on a timer -- it plays
when something makes the screen leave. That is ui-settle-time.md's decode seen
from the other side and observed in the running game rather than inferred from
the file. A nested record keeps looping on its own clock throughout.

This closes the 114-vs-120 gap, and it was my arithmetic rather than a
discrepancy in the decode. The 2.231 units/frame was regressed over BUILD-IN
events -- the only stretch in which the top-level clock advances -- and applied
to a period measured over the settled dwell, where that clock is frozen and
only the plate's own record is running. Two different clocks. The declared 120
was never in doubt from the calibration-free dark-fraction test.

The 51.158-frame period is now confirmed by a second independent estimator:
autocorrelation returns lag 51 with clean harmonics at 102 and 154. Its FIRST
version failed its control, returning 48 for a period known to be 51.158,
because it indexed by sample position where the log's frame numbers have gaps.
Recorded, because the failure is the reason the second version can be trusted.

Not settled: the sweeps' period. The same validated estimator disagrees between
two dwells of one screen -- 515 vs 452 frames for the same family -- and a 14%
disagreement within one screen is not a period. The +0x08 field cannot settle
it either, since ptloop01/ptloop02 have zero slack.

Blocker recorded in CONTAINER-NOTES: a single A press on the title faults the
guest. Three menu-capture attempts, two ending in register dumps of 223 MB and
519 MB, against three no-input runs in the same session that all completed. And
a guest fault writes an UNBOUNDED register dump to stdout on a filesystem at
91%, so any scripted button press needs a size guard.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QsEPXWVaEpyfudtR6re1Pd
2026-08-29 21:21:27 +00:00

108 lines
5.2 KiB
Markdown
Raw 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 top-level clock freezes at the settle point — and that closes the 114-vs-120 gap
**Classification: measured.** From the title draw capture of 2026-08-29
(`ui_draw_capture.sh ARM=early`), re-read with the per-quad parser.
## The observation
`GP_TITLE` build 4 declares a timeline of `t = 0 … 269`. At this run's build-in
pacing that is about 120 presented frames. **The title dwell lasted ~1 100.**
| element | declared alpha ≥ 1 | drawn in frames | span |
|---|---|---|---|
| `ptcopyright` | t = 138 … 244 (**106 units**) | 168 … 1217 | **1 050 frames** |
| `ptlogo1` | t = 26.6 … 264 (237 units) | 125 … 1220 | 1 095 frames |
If the top-level clock ran on to the end of its declared timeline, `ptcopyright`
would fade out at t=244 — around frame 216 — and `ptlogo1` at t=264. **Neither
does.** Both are drawn continuously until the dwell ends, and disappear within
three frames of it.
> **The top-level clock advances through the build-in, stops at the settle point,
> and holds there. The exit ramp is not played on a timer — it plays when
> something makes the screen leave.**
That is [`ui-settle-time.md`](ui-settle-time.md)'s decode seen from the other
side, and observed in the running game rather than inferred from the file. The
freeze lands inside the settle window `[160, 236]`: `ptcopyright` reaches full
alpha at its start (t=160) and never moves again.
Meanwhile the plate's focus record keeps looping throughout — a nested record runs
on **its own clock**, which does not stop when the parent's does.
## 🔴 What this closes: the 114-unit period, which was my error
[`ui-record-loop-length.md`](ui-record-loop-length.md) recorded an unexplained gap:
the plate glow's period of **51.158 presented frames**, multiplied by **2.231**
units/frame, gives **114** units against a declared **120**.
**The two numbers come from different clocks.** The 2.231 was regressed over five
*build-in* events — the only stretch in which the top-level clock advances. The
51.158 was measured over the settled dwell, where that clock is frozen and only
the record's own clock is running. Applying one to the other compares two
different stretches of wall time, and there was never a reason they should agree.
The 120 was never in doubt from evidence that needs no conversion: the
**dark-fraction test** (17.7 % measured, 14.4 % predicted by 120, 2.2 % by 105,
with a culling threshold of 1 read off the data) refutes 105 by a factor of eight
without any units/frame at all. **The gap was an artefact of my arithmetic, not a
discrepancy in the decode**, and the doc no longer carries it as open.
## ✅ The period itself, confirmed twice
51.158 frames was originally measured by detecting cycle starts. A second,
independent estimator — autocorrelation of the glow's alpha series — returns
**lag 51**, with clean harmonics at **102** and **154**.
⚠️ **And the first version of that estimator failed its control**, which is why it
is worth recording. Indexed by *sample position* it returned **48** for a period
known to be 51.158 — 6 % low, with the top three lags (47, 48, 49) not even
bracketing the truth. The log's frame numbers have gaps, so a lag of *n samples*
is not *n frames*. Indexed by frame number with missing frames masked, the control
passes.
## ❔ What this does not settle: the sweeps' period
The same validated estimator, applied to the two light sweeps, gives periods that
**disagree between two dwells of the same screen**:
| sweep | dwell 1 | dwell 2 |
|---|---|---|
| tall quad, h ≈ 1134 | **515** frames | **452** frames |
| tall quad, h ≈ 1303 | 549 frames | 538 frames |
A 14 % disagreement within one screen is not a period. The likely reason is that
the quad's x is a composite of the parent's placement with the leaf's rotation and
600800 % scale, so "the quad's left edge" is not a clean phase variable — but
that is a hypothesis, not a finding. **The sweeps' period is unmeasured**, and the
`+0x08` field cannot settle it either: `ptloop01`/`ptloop02` have zero slack, and a
zero-slack record cannot distinguish "loops at 600" from "runs once and stops".
## 🔴 Blocker: a single Ⓐ on the title faults the guest
Three attempts to capture the **main menu** ended the same way.
| run | input | outcome |
|---|---|---|
| 1 | Ⓐ on title, then Ⓐ again on the transition | guest fault, **519 MB** of register dump |
| 2 | one Ⓐ on title (guard added) | drifted to a `flight` classification, 97 MB |
| 3 | one Ⓐ on title | guest fault, **223 MB** of register dump |
Against three runs in the same session that tapped nothing on the title and all
completed normally. This is the crash `ui_draw_capture.sh`'s own header records
from 2026-08-18 — "a stray A there sends the guest into the save-data probe".
⚠️ **It bounds menu-side dynamic RE in this container**, and it is not a
contradiction of the corpus's existing menu measurements, which were taken before
and by some route that survived. What differs has not been found.
⚠️ **And a guest fault writes an unbounded register dump to stdout** — 223 MB and
519 MB here, on a filesystem at 91 %. Any scripted run that presses a button needs
a size guard on `canary.stdout`.
## Reproducing
```bash
python3 tools/re-capture/quads_per_frame.py <log> 200 1200
```