re: the title's light arc is five transient flashes, not a tone or rotation error

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
This commit is contained in:
sylph-decoder
2026-08-29 19:29:12 +00:00
parent f5aec1ce1b
commit 6f4ba93c5c
7 changed files with 264 additions and 0 deletions

View File

@@ -1,5 +1,13 @@
# ✅ At least two thirds of the title's disagreement is GEOMETRY, not tone
> ⚠️ **The 10.92 title baseline here could not be reproduced on 2026-08-29** —
> `screen render --build 4 --black` against the same capture gives 14.07 both at
> `formats-pin-2026-08-29c` and today. The *ratio* this document argues for may
> well survive; the absolute is in doubt. Separately, part of the title's residual
> is now known to be neither tone nor rotation but `rest()` posing five transient
> flashes at their peaks — [`ui-settle-time.md`](ui-settle-time.md) closes the
> light-arc band from 33.22 to 11.79.
**Classification: measured**, on committed captures. Answers a question the
pending rotation decision needs: *how much would drawing the rotation actually
buy?*

View File

@@ -1,5 +1,20 @@
# ✅ Option A implemented — the reference renderer rotates. ⚠️ It does not close the title.
> 🔴 **Two claims below are withdrawn (2026-08-29), and the baseline is
> unreproducible.** See [`ui-settle-time.md`](ui-settle-time.md).
>
> * **"Flat. No minimum."** is explained, not a property of rotation:
> `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 and the scan has a clear optimum.
> * **"our renderer still does not draw `ptlogo1` / `ptlogo2` at all"** is
> **wrong**. Both are drawn. The four elements the diagnostic reported are kind
> `0x4` ghost instances, skipped deliberately. Hiding the real ones makes the
> error *worse*.
> * ❔ The **10.92** baseline is **not reproducible** — the same command gives
> 14.07 at this document's own pre-change tag and 14.07 today — so the "1.7 %
> better" verdict rests on a recipe that was not recorded.
**Decision:** the human chose **Option A** (2026-08-29) — teach
`sylpheed-formats`' own renderer to draw `rotation_deg`, so it and the port stay
comparable and `verify-screen` keeps meaning *"someone is wrong"*.

View File

@@ -0,0 +1,153 @@
# 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 t5456** → 0 by t58 | **255** | 0 |
| `ptlogo_back2eff2.t32` | a=0 → **255 at t5860** → 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 t6466 → 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 95215, x 8301230). 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 25 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
```