# 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. 🔴 **Gather the times from the TOP-LEVEL elements only — not from nested leaf records.** The port raised this after reproducing `[160, 236]` from its own export, and it is worth stating because the implementation reads correctly either way while only one is right. Including `GP_TITLE` build 4's `ptloop` leaves, whose cycles run to 600 and 720, gives **`[269, 540]`** instead — a "settled instant" that lies *past the end of every top-level element's timeline*, i.e. after the screen has exited. Verified here: top-level `[160, 236]` width 76, with leaves `[269, 540]` width 271. A leaf loops on its own clock and says nothing about when the screen stops changing. ✅ **Confirmed against the running game.** The mechanism this page decodes — five transient flashes that fire and vanish — is observed in the guest's own draw stream, with `ptlogo_back2eff1` drawn in exactly two frames at t = 54.0 against a decoded peak of t54–56: [`ui-title-buildin-measured.md`](ui-title-buildin-measured.md). ## 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 light arc: console, rest(), and --settle](../captures/title-builds/title-arc-rest-vs-settle.png) 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`](../captures/title-builds/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 = None` is byte-identical.** `compose` with no `at` produces 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`…`eff5` and 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.md`](ui-rotation-implemented.md) records the pose scan as **"Flat. No minimum."** over t = 0…600. The cause is now known: `ComposeOptions::at` was posing **leaves only**, so the scan moved the light sweeps and never touched the top-level flashes. No `t` could have helped. `at` now poses every element. * 🔴 The same document blames the residual on our renderer **"not drawing `ptlogo1` / `ptlogo2` at all (four elements)"**. That is **withdrawn**. Build 4 declares *six* ptlogo elements: indices 0 and 1 are kind `0x0` at (184,193) and (137,308), alpha 255, and are **drawn**; indices 2–5 are kind `0x4` ghost 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 --black` against 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`](title-residual-tone-vs-geometry.md) measures. The arc band is no longer where the error lives. ## Reproducing ```bash 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 ```