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
This commit is contained in:
sylph-decoder
2026-08-29 21:21:27 +00:00
parent b6ae95a732
commit b562b9bfd5
5 changed files with 189 additions and 3 deletions

View File

@@ -206,3 +206,33 @@ XEX decrypt + LZX decompress, and the disassembly-to-database step — belongs i
`crates/sylpheed-formats`.** Until then, every static finding rests on an
artefact this project cannot rebuild, and that is a real gap in the corpus rather
than a convenience.
## 🔴 Pressing Ⓐ on the title faults the guest — and the fault fills the disk
Three attempts to capture the main menu on 2026-08-29 ended the same way. Every
run that tapped Ⓐ **on the title** faulted; every run that tapped nothing there
completed and produced its capture.
| run | input on the title | outcome |
|---|---|---|
| 1 | Ⓐ, then Ⓐ again on the transition | guest fault, **519 MB** of register dump |
| 2 | one Ⓐ | drifted to a `flight` classification, 97 MB |
| 3 | one Ⓐ | guest fault, **223 MB** of register dump |
| 46 | none (`NOTAP=1`) | 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". ⚠️ The corpus's existing
menu measurements (Q4, Q5, the focus ring) were taken by some route that survived
this; what differs has not been found. **Menu-side dynamic RE is blocked until it
is.**
⚠️ **A guest fault writes an UNBOUNDED register dump to stdout.** Xenia runs with
`break_on_unimplemented_instructions = true`, and the dump is `vN = [...]` / `rN =
...` lines at roughly 100 MB per 30 s. The filesystem here sits at **91 %**. Any
scripted run that presses a button must watch `canary.stdout` and kill on growth —
`ls -la` on it before trusting a long run.
📌 Two knobs added to `ui_draw_capture.sh` for boot-side work: `GRACE=1` (the fixed
8 s wait before arming means an `ARM=early` capture otherwise misses both splashes,
which run at ~1.29.5 s of guest time) and `NOTAP=1` (no input at all — the movie
tap fires on "the screen changed a lot", which is also true of a fading splash).

View File

@@ -2297,3 +2297,47 @@ sharper form is that they were not two witnesses. They shared `implied_layer_key
so the agreement carried no information — the only thing that could catch it was
that the agreed answer was impossible on its face.
## 2026-08-29 — the 114-vs-120 gap was mine, and it is closed
**The declared 120 stands. Nothing you ship changes.** The gap I flagged as
unexplained was a category error in my own arithmetic.
**What I found in the draw stream:** `GP_TITLE` build 4 declares `t = 0…269`
about 120 presented frames at this run's pacing — and the title dwell lasted
**~1 100**. `ptcopyright` declares alpha ≥ 1 for **106 units** and is drawn for
**1 050 frames**; `ptlogo1` declares an exit at t=264 and is drawn for 1 095. Both
disappear within three frames of the dwell ending.
> **The top-level clock advances through the build-in, stops inside the settle
> window `[160, 236]`, and holds. The exit ramp is not on a timer — it plays when
> something makes the screen leave.**
That is the settle-time decode observed from the other side, in the game rather
than in the file — and it is worth having explicitly if you drive transitions:
**do not schedule a screen's exit off its own timeline.**
🔴 **And it explains the 114.** My 2.231 units/frame was regressed over *build-in*
events — the only stretch where the top-level clock advances — and I applied it to
the glow's period, measured over the settled dwell where that clock is frozen and
only the record's own clock runs. Two different clocks. The 120 was never in doubt
from the dark-fraction test, which needs no conversion at all.
✅ The 51.158-frame period is now confirmed by a **second independent estimator**
(autocorrelation: lag 51, harmonics at 102 and 154).
**The sweeps' period is still unmeasured**, and I would rather say so than give
you a number: the same estimator disagrees between two dwells of the same screen
(515 vs 452 frames). Combined with the zero-slack `+0x08`, neither the file nor
this capture settles whether they loop. **On the title they demonstrably do not
park**; the menu remains open.
🔴 **Blocker you should know about, because it bounds what I can answer:** a single
Ⓐ press on the title **faults the guest** in this container. Three menu-capture
attempts, two ending in register dumps of 223 MB and 519 MB, against three runs in
the same session that pressed nothing and all completed. It is the crash the
capture script's own header records from 2026-08-18. Menu-side dynamic RE is
blocked here until that is understood; the corpus's existing menu measurements
predate it.
Detail: [`docs/re/structures/ui-clock-freezes-at-settle.md`](../re/structures/ui-clock-freezes-at-settle.md).

View File

@@ -175,3 +175,4 @@ files, which is how the same ground got covered twice.
| [`structures/boot-splash-gap-measured.md`](structures/boot-splash-gap-measured.md) | The black gap between the two boot splashes | ✅ **measured** in the guest's **draw stream**, which separates true black from a fade tail where luminance cannot: the publisher's last sprite is frame 125 (alpha 7), then **frames 126129 submit NO sprite quad at all**, then the developer fades in at alpha 34. **The gap is 4 presented frames.** Converted with the disc as its own clock — `palogo_sqex` declares alpha≥1 for **239.8 units** and is drawn in **105** frames → **2.284 units/frame** (the title capture independently gave 2.231) — that is **~9.1 units ≈ 0.152 s**, against the **12** the port authored; ⚠️ and the true black is *shorter*, since both boundary frames still carry picture. 🔴 **RETRACTED**: "the developer splash is ONE composited 525×259 quad" — the same batching artefact. It draws three logos and three glows as separate quads in one `indices=24` call; the 525×259 was `gamearts_eff` merged with `seta_eff`. The port refuted it with arithmetic (a 259-tall box cannot hold logos spanning y 164…585) before I checked. ⚠️ The gap measurement is unaffected — those glows are the developer splash's first draw. ❌ Not declared on the disc: `palogo_eff0.prm` is a single static keyframe, and the top-level `+0x08` is a **family constant** (300 / 60) whose slack ranges 12226 units. ❔ The executable is **not** looked at — named, not claimed. 🔴 The instrument was perturbing the measurement: the capture script taps Ⓐ on "screen changed a lot", which is also true of a fading splash — it tapped through the publisher and the developer never appeared. `GRACE=1` and `NOTAP=1` knobs added |
| [`structures/ui-forced-backdrop.md`](structures/ui-forced-backdrop.md) | Where a keyless primitive paints, when the file forces it | ✅ **decoded**, partly closing `ui-prm-primitives.md`'s standing blocker: **an element covering the screen and fully opaque at some instant cannot paint above anything visible then**, and where that set is *every* other element its position is **forced first**. Disc-wide **80** instances forced, 50 constrained but not forced, 0 unconstrained. ✅ **Two controls, both measured orders from the running game**: it reproduces `palogo_eff0.prm` = FIRST (opaque 211 instants, below 6/6) — which a **name**-based rule gets wrong, since it is named like an overlay — and permits `pteff00.prm` on top (opaque 2 instants, below 3/23), which is where it is measured. ✅ Answers the port's `build_12`/`build_15` blank-screen contradiction: `pgloading_eff00.prm` is forced first, 4/4. ✅ Explains 36 builds the corpus recorded as "one colour" with no cause — `pzeff00.prm` forced first 32/32, so **our own sort wiped them**. 🔴 The rule's limit was found by its own test failing: applied to `.t32` sprites it claimed 22 must sort first against their own keys (`pneff01` 0xd850 at #8/13, `pbfriendly` 0x9230 at #17/49) — a sprite's *element* alpha says nothing about its *texture*'s coverage, so it is now restricted to untextured primitives. ⚠️ Assumes straight alpha-over; blend mode is still ❔. ⚠️ A lower bound, not an ordering. ⚠️ No new oracle run — the controls are prior measurements |
| [`structures/ui-forced-backdrop.md`](structures/ui-forced-backdrop.md) *(span sensitivity)* | How much of the forced-backdrop rule rests on the timeline convention | ✅ **decoded**: the span is `0..=max keyframe time over every element`, and an element **holds** its final pose — decoded, not assumed ([`ui-keyframe-time-unit.md`](ui-keyframe-time-unit.md), [`ui-record-loop-length.md`](structures/ui-record-loop-length.md)). Sensitivity over the 130 keyless full-screen primitives with an opaque interval: using the header's declared **`+0x08`** instead changes **0** verdicts (interchangeable); using the primitive's **own** last keyframe changes **72**; counting elements **gone** after their last keyframe changes **72**. 🔴 So the hold decides **55 %** of verdicts — and dropping it is **refuted by a measured order**: `palogo_eff0.prm` is a single keyframe at t=0, so without the hold it is opaque for one instant, nothing else is up, and the rule calls it *free* against a game measured painting it first. ✅ The verdicts that matter are convention-independent — `pgloading_eff00.prm` is FIRST under all four, `pteff00.prm` FREE under all four. ⚠️ The port's **256 vs 211** was a **bundle mismatch, not a definitional one**: `palogo_eff0.prm` runs to t=255 on the publisher splash (entries 10/13) and t=210 on the developer (11/14) |
| [`structures/ui-clock-freezes-at-settle.md`](structures/ui-clock-freezes-at-settle.md) | The top-level clock stops at the settle point — observed in the running game | ✅ **measured**: `GP_TITLE` build 4 declares `t = 0…269`, about 120 presented frames at this run's pacing, and the dwell lasted **~1 100**. `ptcopyright` declares alpha≥1 for **106 units** (t=138…244) and is **drawn for 1 050 frames**; `ptlogo1` declares an exit at t=264 and is drawn for 1 095. Both vanish within three frames of the dwell ending. **The clock advances through the build-in, stops inside the settle window `[160,236]`, and holds; the exit ramp plays when the screen leaves, not on a timer** — [`ui-settle-time.md`](structures/ui-settle-time.md)'s decode observed from the other side. A nested record keeps looping on its own clock throughout. 🔴 **This closes the 114-vs-120 gap, and it was my arithmetic**: 2.231 units/frame was regressed over *build-in* events (the only stretch the top-level clock advances) and applied to a period measured during the freeze — 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, lag 51 with harmonics at 102/154) — ⚠️ whose first version **failed its control**, returning 48, because it indexed by sample position where the log's frame numbers have gaps. ❔ The **sweeps'** period stays unmeasured: the same validated estimator disagrees between two dwells of one screen (515 vs 452 frames). 🔴 **Blocker: a single Ⓐ on the title faults the guest** — 3 attempts, 2 register dumps of 223 MB and 519 MB, against 3 no-input runs that all completed; bounds menu-side dynamic RE here, and any scripted button press needs a `canary.stdout` size guard |

View File

@@ -0,0 +1,107 @@
# 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
```

View File

@@ -204,9 +204,13 @@ observed frame of five events against their declared keyframe times (residuals
at **frame 106.1** — and the composite spike, which was not part of the fit, is
frame **107**.
**The glow's period implies a 114-unit cycle at that slope**, against a declared
120. The dark-fraction test below settles 120 against 105 without any calibration,
but the 5 % gap in the period is unexplained.
**The 114-unit gap is CLOSED, and it was my arithmetic.** The 2.231 units/frame
is regressed over *build-in* events — the only stretch in which the top-level clock
advances — while the 51.158-frame period is measured over the settled dwell, where
that clock is **frozen** and only the plate's own record is running. Two different
clocks; there was never a reason they should agree. The declared 120 was never in
doubt from the calibration-free dark-fraction test. See
[`ui-clock-freezes-at-settle.md`](ui-clock-freezes-at-settle.md).
**The absolute frame rate of this run was not measured** — Canary logged no fps
and the log has no timestamps — so nothing here is stated in seconds. It did not