Records the settle-time decode, and corrects two claims it overturns.
docs/re/structures/ui-settle-time.md is the finding: a settled screen is one
instant every element is posed at, the disc names it (the midpoint of the
longest keyframe-free interval), and the title's arc closes from 33.22 to
11.79 with the clipped-pixel count landing on the console's 1459 within 0.5%.
Includes the disc-wide reach -- only 30% of bundles have a window wide enough
to trust -- and a three-way figure.
Withdrawn in ui-rotation-implemented.md:
* "Flat. No minimum." was not a property of rotation. `at` posed LEAVES ONLY,
so the scan moved the light sweeps and never touched the top-level flashes.
* "our renderer does not draw ptlogo1/ptlogo2 at all" is wrong. Both are
drawn; the four elements the diagnostic named are kind-0x4 ghosts sharing
their template's name. Hiding the real ones makes the error WORSE by
+5.20 and +7.47.
* Its 10.92 baseline is not reproducible -- 14.07 at its own pre-change tag
and 14.07 today -- so the "1.7% better" verdict rests on an unrecorded
recipe. Flagged in title-residual-tone-vs-geometry.md too.
METHOD.md gains two traps: a shared CARGO_TARGET_DIR makes a `git worktree`
build silently replace the binary you run next (it cost three renders here,
caught only because a missing flag was a hard error rather than a wrong
number); and an aggregate computed per-element is not a state of the system.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QsEPXWVaEpyfudtR6re1Pd
7.6 KiB
The settled screen is one instant, not one hold per element
Classification: decoded. The value comes from the keyframe table alone — no capture is consulted to compute it — and it is checked disc-wide. The verification against the console capture is a test of the decode, not its source.
The claim
Element::rest() returns an element's last hold keyframe, chosen for that
element independently of every other element. A composite built from rest() is
therefore not a screen at any moment in time; it is a per-element maximum.
For an element that ends the screen settled, that is the same thing. For a
transient it is exactly wrong — a flash's last hold is the flash peak, so
rest() leaves it burning forever.
The settled screen is instead one instant that every element is posed at, and the disc says which instant: gather every keyframe time in the build and take the longest interval containing none of them. Inside that gap nothing has an inflection, so every element is either holding or midway along a single linear ramp. That is what "the screen has stopped changing" means, expressed in the only vocabulary the file has.
UiBuild::settle_time() returns the midpoint of that interval;
UiBuild::settle_window() returns the interval, whose width is how much
confidence the midpoint deserves.
The case that found it
GP_TITLE build 4. Seven elements share the light-arc band behind the logo:
| element | timeline | at rest | at t=198 |
|---|---|---|---|
ptlogo_back2eff1.t32 |
a=0 → 255 at t54–56 → 0 by t58 | 255 | 0 |
ptlogo_back2eff2.t32 |
a=0 → 255 at t58–60 → 0 by t62 | 255 | 0 |
ptlogo_back2eff3.t32 |
staggered, same shape | 255 | 0 |
ptlogo_back2eff4.t32 |
staggered, same shape | 255 | 0 |
ptlogo_back2eff5.t32 |
255 at t64–66 → 192 at t74 → 0 by t110 | 255 | 0 |
ptlogo_back2eff.t32 |
255 at t66, holds to t238, exits t244 | 255 | 255 |
ptlogo_back2.t32 |
255 at t80, holds to t243, exits t249 | 255 | 255 |
The five numbered ones are a single light sweep travelling left to right across
the logo, drawn as five staggered two-frame flashes. They are all extinguished by
t110. rest() draws them simultaneously and permanently, and five stacked
white glows (ptlogo_back2eff5.t32 decodes to a mean opaque RGB of exactly
255,255,255) drive the arc to saturation.
What it looks like
The console's arc is a thin white outline with a pink hooked tail. rest()
renders a fat solid white blob that swallows the tail completely — five white
glows stacked. --settle reproduces the console.
⚠️ A measurement trap worth recording: sampling the brightest 3 % of that band
gives (252,245,239) for the console against (255,255,255) for rest() — nearly
neutral, and it reads as "no hue difference". That statistic samples the white
outline in both and never touches the pink tail. What it did expose was the
count: at a 97th-percentile threshold the console has 1 459 pixels above it and
rest() has 8 581, i.e. a saturated plateau. The pixel count carried the
signal that the mean colour hid.
The measurement
Against
live-title-build4-no-plate.png,
whole frame and over the arc band (y 95–215, x 830–1230). The third column counts
pixels at or above the band's 97th-percentile luminance — a saturation statistic,
independent of the error being minimised, and not fitted.
| mean abs diff | arc band | clipped px | |
|---|---|---|---|
| console capture | — | — | 1 459 |
rest() — the default |
14.07 | 33.22 | 8 581 |
--at 198 — predicted from the disc |
12.06 | 11.79 | 1 452 |
--at 100 — control, before the window |
19.13 | 20.46 | 1 468 |
--at 358 — control, past every exit |
28.80 | 53.37 | 1 447 |
t=198 was computed before the render was scored. settle_window() on build 4
returns [160, 236]; 198 is its midpoint. A separate sweep of t in 40…300 finds a
flat optimum over t ∈ [180, 238] at 12.06 / 11.79 / 1 452, which contains the
prediction.
⚠️ The clipped-pixel count discriminates rest() from any single instant — the
two controls also land near 1 459 — so it identifies the blow-out, not the
time. The time is identified by the mean, and by the disc.
The controls
- ✅
at = Noneis byte-identical.composewith noatproduces the same bytes before and after this change (cmp, exact). The default path is untouched. - ✅ The change did not regress the resting composite. Rendering build 4 at
formats-pin-2026-08-29c(pre-rotation, pre-leaf) and at the current tree both give 14.07 / 33.22. - ✅ A hand-picked visibility list reaches the same answer. Leave-one-out over
all 24 elements, then hiding exactly
eff1…eff5and keeping the two holders, gives 12.06 / 11.79 / 1 452 — identical to--at 198. The principled rule reproduces the hand-picked one with nothing hand-picked. - ✅ Disc-wide self-consistency. For every bundle with a window, no element has
a keyframe strictly inside it, and the midpoint lies within it —
tests/ui_settle_time_disc.rs.
🔴 Reach: this does not apply to every bundle
Of the 1 758 composable bundles carrying two or more keyframe times:
| count | share | |
|---|---|---|
| settle window ≥ 30 units (0.5 s) | 524 | 30 % |
| settle window < 10 units | 731 | 42 % |
| mean window | 49 units | — |
The 42 % are mostly loop* animation fragments, which are meant to be in
motion and have no settled pose to find. Check the window width before trusting
the midpoint. A narrow window is the data saying "this bundle never settles",
not a settle time with a small error bar.
What this corrects elsewhere in the corpus
- 🔴
ui-rotation-implemented.mdrecords the pose scan as "Flat. No minimum." over t = 0…600. The cause is now known:ComposeOptions::atwas posing leaves only, so the scan moved the light sweeps and never touched the top-level flashes. Notcould have helped.atnow poses every element. - 🔴 The same document blames the residual on our renderer "not drawing
ptlogo1/ptlogo2at all (four elements)". That is withdrawn. Build 4 declares six ptlogo elements: indices 0 and 1 are kind0x0at (184,193) and (137,308), alpha 255, and are drawn; indices 2–5 are kind0x4ghost instances at (−116,−7) and (437,508), alpha 0, and are skipped deliberately. Hiding element 0 makes the error worse by +5.20 whole-frame and +7.61 in the band; element 1 by +7.47. They are drawn and correctly placed. - ❔ The 10.92 baseline in that document's table is not reproducible.
screen render --build 4 --blackagainst that capture gives 14.07 at the pre-change tag and 14.07 now. Some element of that recipe was not recorded. Conclusions resting on 10.92 — including the "1.7 % better" verdict on rotation — should be treated as unverified until the recipe is recovered.
What is not settled
❔ The remaining 12.06. It is broad and level-like rather than localised,
which is consistent with the tone term that
title-residual-tone-vs-geometry.md
measures. The arc band is no longer where the error lives.
Reproducing
cargo run -q -p sylpheed-cli -- screen render --build 4 --black --at 198 \
"$SYLPHEED_DISC/dat/GP_TITLE.pak" /tmp/title.png
SYLPHEED_DISC=/disc cargo test -p sylpheed-formats --test ui_settle_time_disc
