The port caught this with arithmetic off the disc, and it was right: build 2 has a keyframe group of its own, so "when build 4 has settled, wait 2.13 s, then composite build 2" puts the plate at settle + 2.13 + 3.97 s. Confirmed build 2's group here independently of their message: `ptbtn00.t32` reaches a=255 at t=238. The reconciliation needs no free parameter. Both builds run on ONE clock, started together, and the premise that fails is `rest.t`: rest.t is NOT when a screen settles. It is the last HOLD keyframe before the exit. ptlogo1 rests at t=251 and stops moving at t=42. The title's visible build-in ends at t=118, where pteff01, pteff02.prm and ptlogoall_eff end their ramps together. 238 - 118 = 120 units = 2.000 s, against a measured 2.138 and 2.132. So the interval the two runs agreed on to 6 ms was a DECLARED one and I handed over a wall-clock reading of it. That reading is 6.7% long, and the corpus already knew why: 120 units in 2.135 s is the game presenting at 28.06 / 28.14 fps against a nominal 30, and the idle title was independently measured at 28.5 fps before these runs. Corroborated from inside the same two runs -- first pixels -> settle is 1.643 s and 2.131 s, a 30% spread, while settle -> plate is 2.138 and 2.132. Frames are dropped during the build-in, not during the hold, which a change in the game's own timing could not do. So the port authors nothing here. What is unchanged: ScreenView still has to draw two builds at once and the boot's end state is still not plate-free. Not settled, and said so on the page: which reading of the keyframe times is right (it moves the plate by 2 units and I cannot separate them from these traces), and my settle landmark to better than +/-5 units. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014voBspJ6kFncNErZJuZcLw
214 lines
11 KiB
Markdown
214 lines
11 KiB
Markdown
# ✅ The boot title shows build 4 alone for **2.13 s**, then composites the plate
|
||
|
||
**Status:** ✅ **measured** — two independent boots of the real game in Xenia
|
||
Canary, 2026-08-29. Not on the disc as a delay: build 2 (the `PRESS Ⓐ BUTTON`
|
||
plate) is an overlay with no fade quad of its own, and nothing in either
|
||
bundle's keyframe group carries the gap between them.
|
||
|
||
**Question this closes:** the port asked which of three things the boot title is
|
||
— build 4 alone, build 4 with the plate composited from the start, or build 4
|
||
**then** the plate after a delay — because the third case is the only one where
|
||
`ScreenView` has to draw **two builds at once**, which it has never done. The
|
||
sequence was already answered (it is the third,
|
||
[`menu-idle-and-b-2026-08-29.md`](menu-idle-and-b-2026-08-29.md)); **the delay
|
||
was withdrawn the same day** and is what this page supplies.
|
||
|
||
## The number
|
||
|
||
| | run 1 | run 2 |
|
||
|---|---|---|
|
||
| title art first drawn (surface leaves black) | 201.617 s | 214.130 s |
|
||
| **title settled** — glyph counter first reads its no-plate value **154** | 203.260 s | 216.261 s |
|
||
| **plate first counted** — glyph leaves 154 | 205.398 s | 218.393 s |
|
||
| **settled → plate** | **2.138 s** | **2.132 s** |
|
||
| first drawn → plate | 3.781 s | 4.263 s |
|
||
|
||
**Take 2.13 s, measured from the moment build 4's own build-in animation
|
||
finishes.** The two runs agree to **6 ms**, which is under one sample interval.
|
||
|
||
⚠️ **Do not take "first drawn → plate".** It differs by 0.48 s between the two
|
||
runs because the build-in itself ran 1.64 s and 2.13 s — the emulator's frame
|
||
pacing during an animation is not the game's clock, and this is exactly the sort
|
||
of number that looks like a measurement and is really the harness.
|
||
|
||

|
||
|
||
Raw per-frame data, 8 fps, every frame of both runs:
|
||
[`data/plate-timing-run1.tsv`](data/plate-timing-run1.tsv) ·
|
||
[`data/plate-timing-run2.tsv`](data/plate-timing-run2.tsv).
|
||
|
||
## Why "then the plate", and not "the plate was pulsing all along too dim to see"
|
||
|
||
The plate's declared alpha never exceeds `0x50` (80/255,
|
||
[HANDOFF](../port/HANDOFF.md)), so a glyph counter with a hard threshold could in
|
||
principle miss its dim phase and produce a fake delay. It does not, on two
|
||
independent observables:
|
||
|
||
* the glyph count is **exactly 154** — the committed no-plate title's own value,
|
||
159 on `live-title-build4-no-plate.png` — for every frame of the plateau, with
|
||
**zero** variation, for 1.99 s (run 1) and 2.13 s (run 2). After onset the
|
||
same counter swings 714 ↔ 1520 continuously. A cycling plate cannot produce a
|
||
flat exact-154 plateau nearly one full period long;
|
||
* the **surface mean** is flat to ±0.03 across the plateau (61.09 → 61.15) and
|
||
then rises. A pulsing overlay moves the frame mean; the frame mean does not
|
||
move until onset.
|
||
|
||
## 🔴 The instruction below was WRONG, and the port refuted it — corrected 2026-08-29
|
||
|
||
**What stands:** every measurement on this page. **What was wrong:** what I told
|
||
the port to do with it.
|
||
|
||
The instruction was *"when build 4 has settled, wait 2.13 s, composite build 2"*.
|
||
The port implemented it literally, then pointed out with arithmetic off the disc
|
||
that it cannot be right: build 2 has a **group of its own**, and playing that
|
||
group from a start at "settle" puts the plate at settle + 2.13 + 3.97 s. The
|
||
3.97 s is real — `ptbtn00.rat` reaches `a=255` at `t=238`, confirmed here
|
||
independently of their message:
|
||
|
||
```
|
||
$ sylpheed-cli screen info --build 2 --geometry $SYLPHEED_DISC/dat/GP_TITLE.pak
|
||
build [2] 1280x720 1 elements
|
||
0 ptbtn00.t32 214: 383,560 a=0 236: 383,550 a=0
|
||
238: 383,550 a=255 244: 383,550 a=255 -: a=0
|
||
```
|
||
|
||
### The reconciliation: one clock, and my landmark is `t≈118`, not `t=261`
|
||
|
||
**Build 2's group runs on the same clock as build 4's, starting together.** Then
|
||
the plate's own keyframes say when it arrives and nothing needs authoring.
|
||
|
||
The port's premise that *"build 4 settles at `t=261` = 4.35 s"* is the part that
|
||
fails, and it is worth stating plainly because it will bite elsewhere:
|
||
🔴 **`rest.t` is not when a screen settles.** It is the last *hold* keyframe
|
||
before the exit. `ptlogo1` has `rest.t = 251` and stops moving at **`t=42`** —
|
||
after which it creeps 5 px over the next 209 units. The title's visible build-in
|
||
is over at **`t≈118`**, where three elements' ramps end together (`pteff01`,
|
||
`pteff02.prm`, `ptlogoall_eff`); the only later change is the copyright line and
|
||
the ™.
|
||
|
||
That closes the gap exactly, with no free parameter:
|
||
|
||
| | units |
|
||
|---|---|
|
||
| last build-in ramp ends (`pteff01` / `pteff02.prm` / `ptlogoall_eff`) | `t = 118` |
|
||
| `ptbtn00` reaches `a = 255` | `t = 238` |
|
||
| **difference** | **120 units = 2.000 s** at 1 unit = 1/60 s |
|
||
|
||
against a measured **2.138 s** and **2.132 s**. So the interval the two runs agree
|
||
on to 6 ms is a **declared** 120 units — the number was on the disc, and I handed
|
||
over a wall-clock reading of it.
|
||
|
||
### ⚠️ And the wall-clock reading is 6.7 % long, for a reason the corpus already knew
|
||
|
||
120 units in 2.135 s is **56.2 units/s**, i.e. the game presenting at **28.06 /
|
||
28.14 fps** against its nominal 30. The corpus independently measured the idle
|
||
title at **28.5 fps** ([`ui-keyframe-time-unit.md`](ui-keyframe-time-unit.md)) —
|
||
1.3 % from these two runs, established before and separately from them.
|
||
|
||
✅ Corroborating that it is presentation rate and not the game: within these runs
|
||
*first pixels → settle* is **1.643 s** and **2.131 s** — a 30 % spread — while
|
||
*settle → plate* is **2.138 s** and **2.132 s**. The build-in is where frames are
|
||
dropped; the static hold is not. A model in which the game's own timing varied
|
||
would have to move both.
|
||
|
||
## What the port should author — nothing
|
||
|
||
1. draw build 4 and build **2** on **one clock, started together**, and play both
|
||
groups from their own keyframes;
|
||
2. the plate then appears at its declared `t = 238` with no authored constant;
|
||
3. its pulse is the focus record `ptbtn00f`, measured here at **2.12 / 2.19 /
|
||
2.34 / 2.31 s** over four intervals, mean **2.24 s** — replicating the
|
||
corpus's ≈ 2.3 s rather than replacing it.
|
||
|
||
⚠️ **If you do author a gap anyway, author 120 units (2.00 s at 30 Hz), not my
|
||
2.13 s.** The 2.13 s is this emulator's presentation rate baked into a game
|
||
constant, and a port running at a true 30 Hz would be visibly late.
|
||
|
||
So yes: `ScreenView` needs two builds at once, and the boot's end state is
|
||
**not** plate-free. That part of the answer is unchanged.
|
||
|
||
### ❔ What this does not settle
|
||
|
||
* **Which reading of the keyframe times** — the current one or Q1's replicated
|
||
shift — is right. It barely matters here (the plate's `a=255` is `t=238`
|
||
unshifted and `t=236` shifted, 0.03 s apart), but the two make different
|
||
predictions for `ptcopyright`'s fade, and my traces contain **both** a 0.4 s
|
||
rise and a 1.1 s creep before the plate. Not separated; Q1's 🟡 stands.
|
||
* **My settle landmark to better than ±5 units.** At 56 units/s, 8 units is
|
||
0.14 s — about one sample. `t=118` is identified from the file (three ramps
|
||
ending together) and is *consistent with* the measurement, not pinned by it.
|
||
|
||
## What is NOT measured here — the press latencies, again
|
||
|
||
Both runs pressed Ⓐ on the plate and Ⓑ on the menu, and both runs contain a
|
||
**frozen frame** on the Ⓐ path that makes the Ⓐ→menu duration meaningless:
|
||
|
||
| | run 1 | run 2 |
|
||
|---|---|---|
|
||
| frames held at surface mean **26.626**, motion exactly 0 | 14 (1.53 s) | 12 (1.39 s) |
|
||
|
||
🔴 **This is not the instrument.** Run 1's freeze straddled an x11grab restart,
|
||
so it looked exactly like the documented stale-stream failure; run 2 was run with
|
||
restarts **disabled** for the whole measuring window and reproduced the same
|
||
freeze, at the **same** surface mean to six decimals, in the same place relative
|
||
to the press. Two independent runs cannot agree to 1e-6 on a stalled buffer.
|
||
It is the guest: after Ⓐ, the fade-out starts (mean 64.4 → 51.0 → 26.6), the
|
||
frame is then **re-presented unchanged for ~1.4 s**, the full title reappears at
|
||
mean 64.28, and only then does the fade run to completion. That is the shape of
|
||
a **load stall**, and the Ⓑ path — menu → title, nothing to load — has no freeze
|
||
at all.
|
||
|
||
**So the Ⓐ→menu latency is an emulator load time, not a game beat, and the port
|
||
must not bake it in.** The parts of the transition that are stall-free:
|
||
|
||
| | run 1 | run 2 |
|
||
|---|---|---|
|
||
| press → first visible change (Ⓐ) | — | 0.29–0.37 s |
|
||
| press → first visible change (Ⓑ) | — | 0.28–0.33 s |
|
||
| **pure black between the two screens** (Ⓐ path) | 0.14–0.30 s | 0.14–0.27 s |
|
||
| black → menu settled | ≈ 1.0 s | ≈ 1.0 s |
|
||
| Ⓑ path: menu fade-out to black | — | 0.50 s |
|
||
| Ⓑ path: black → title art | — | ≤ 0.27 s, and it is a **cut**, not a fade |
|
||
|
||
⚠️ The two "first visible change" figures are **upper bounds that include this
|
||
harness**: the press is a file the emulator polls (`--hid=file`), so an unknown
|
||
poll interval sits inside them. They are quoted only because they bracket the
|
||
black hold, and they do **not** refute
|
||
[`screen-transitions.md`](screen-transitions.md)'s 0.07 s, which was taken a
|
||
different way.
|
||
|
||
✅ **The black hold does check the port's authored constant.** The port ships
|
||
0.17–0.23 s; both runs put it in **0.14–0.30 s**. Consistent, at a sampling
|
||
resolution (0.125 s) that cannot do better — so the authored value stands and is
|
||
now bracketed by a measurement rather than only by the declared 12 units
|
||
(0.20 s).
|
||
|
||
## The instrument, and its controls
|
||
|
||
[`tools/re-capture/title_timing_probe.py`](../../tools/re-capture/title_timing_probe.py),
|
||
built because the four durations withdrawn on 2026-08-29 came from a classifier
|
||
costing **1503 ms per frame** draining an 8 fps stream at 0.64 fps.
|
||
|
||
* **8.7 ms of compute per frame** — measured, 173× cheaper. The saving is the
|
||
±8 px offset search: every committed capture aligns at exactly `dy=0 dx=0`
|
||
([`five-screens-acceptance.md`](five-screens-acceptance.md)), so the live path
|
||
decimates 4× and does one ZNCC per reference instead of 25 at full res.
|
||
* **Both runs sampled at 7.97 and 7.98 fps against a requested 8.** A backlog
|
||
preserves ordering and destroys durations; there was no backlog.
|
||
* `--control` **passed 9/9 content controls and 4/4 plate-detector controls**,
|
||
including the two committed movie frames that are the class this oracle exists
|
||
to reject.
|
||
* an **independent one-shot grab** every 20 s, through a separate process, is
|
||
logged beside the stream's own frame. On the static screens the two agree to
|
||
**0.000 / 0.001**; the large disagreements are all inside movies, where a
|
||
0.3 s difference in grab time is a different picture.
|
||
* and the plateau itself carries an internal clock check: the plate's ~2.2 s
|
||
pulse is visible in the same trace. A stalled stream cannot produce a periodic
|
||
signal.
|
||
|
||
## Reach
|
||
|
||
Two runs, English locale, one machine, Xenia Canary. It says nothing about the
|
||
**attract loop's** title (which the corpus records as accepting no input at all),
|
||
and nothing about the Japanese build 7.
|