The port refuted the stronger half of the last claim and was right. I wrote that the units/frame drift explained the publisher splash's 4.1% error against its declared dwell. It explains the sign only. Their test verified exactly here: the publisher/developer dwell ratio is 1.2143 declared, 1.2784 as the corpus's three cold boots measure it, and 1.3678 as this container's drift predicts -- so the drift's direction is right and real evidence, but its magnitude is about 2.4x too strong. One refinement, because the means are being compared more finely than n=3 supports: the corpus's three boots individually give excesses of +0.89%, +8.24% and +6.79%, a spread of 7.3 percentage points -- WIDER than the 5.30 pp gap under test -- and boot 1's ratio (1.2251) is essentially the declared 1.2143. This run sits 2.3 sigma above their mean: suggestive, not established. Not closable without a frame log from the corpus's instrument, which was screenshot timing and has none. An attempt to give this side an n of 3 failed on tooling and is recorded: ARM=early loses its F10 about 40% of the time -- two of five runs logged "ARMED EARLY" and produced no draw log at all, with nothing in the session log distinguishing them. Also fences the 33% drift against a misreading the port flagged: it is PRESENTATION pacing and cannot reach keyframe_units_per_second = 60, which is the game's logical rate, decoded under Q1, and which a renderer converts through at its own frame rate. And records a cross-check neither side went looking for: the batch counts are 1 and 2 on the publisher against 3 and 6 on the developer, and the port reports a count restricted to SPRITE-BEARING elements reproduces that exactly from the export -- so palogo_eff0, the layerless forced backdrop, is not in the batched draw, confirmed from the file. Two instruments that disagreed about that element in every previous iteration now agree on which one it is. New tool splash_boundaries.py carries the corrected counting method. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QsEPXWVaEpyfudtR6re1Pd
162 lines
7.9 KiB
Markdown
162 lines
7.9 KiB
Markdown
# The boot splash dwells are declared on the disc — wall clock is the wrong unit
|
||
|
||
**Classification: decoded**, with an independent wall-clock check. The port asked
|
||
for two timestamps; the right answer is that timestamps are not the invariant.
|
||
|
||
## The answer
|
||
|
||
| splash | bundle | declared timeline | at 60 units/s | corpus wall clock, 3 cold boots | error |
|
||
|---|---|---|---|---|---|
|
||
| publisher | `GP_TITLE` entries 10, 13 | t = 0 … **255** | **4.250 s** | 4.30 / 4.60 / 4.37 | 4.1 % |
|
||
| developer | entries 11, 14 | t = 0 … **210** | **3.500 s** | 3.51 / 3.50 / 3.37 | 1.1 % |
|
||
|
||
The developer splash is the sharp one: **3.500 s declared against 3.51 and 3.50
|
||
measured** on two of three boots. **Author the units.**
|
||
|
||
## Why not seconds — this run is the argument
|
||
|
||
A fresh no-input boot with a frame→wall-clock map (below) puts the same two dwells
|
||
at **5.10 – 5.61 s** and **3.83 – 4.30 s** — **15–20 % longer** than both the
|
||
declared values and the corpus's three runs, on the same disc and the same
|
||
declared timeline.
|
||
|
||
**So the wall-clock dwell is an emulator-pacing artefact that varies run to run.**
|
||
Three separate measurements of this container's rate — 13.1 fps, ~28 fps, and this
|
||
one — say the same thing from another direction. A port that authors seconds is
|
||
authoring one run's pacing.
|
||
|
||
## 🔴 The boundaries — corrected, after the port refuted them
|
||
|
||
An earlier version of this page read element visibility off *which quads appear
|
||
in the log*. **That is wrong**, and the port caught it by arithmetic: the two
|
||
splash spans came out at 2.237 and 2.414 units/frame, 7.9 % apart on one boot of
|
||
one guest, which should be one number.
|
||
|
||
Their diagnosis was that the spans were anchored on different elements — the
|
||
publisher's on its wordmark, the developer's on its glows. The log says the cause
|
||
is worse: **the developer splash batches SIX quads into one draw and the log dumps
|
||
only the first two.** While the glows are alive they occupy that prefix, so the
|
||
three wordmarks are invisible to the log until the glows stop being submitted.
|
||
"Developer wordmarks first drawn at frame 140" was the **logging prefix shifting**,
|
||
not the game.
|
||
|
||
✅ **The fix is to count the batch, not the logged quads.** `indices / 4` is the
|
||
number of quads the game is submitting, and the 8-vertex cap cannot touch it. The
|
||
count changes exactly where the declared set of elements with alpha > 0 changes:
|
||
|
||
| splash | transition | frame | t |
|
||
|---|---|---|---|
|
||
| publisher | 1 → 2 quads, the wordmark joins the glow | 5.5 | **15** |
|
||
| | 2 → 1, the glow ends | 22.5 | **45** |
|
||
| | last drawn | 119.5 | **255** |
|
||
| developer | 3 → 6 quads, three wordmarks join three glows | 126.5 | **15** |
|
||
| | 6 → 3, the glows end | 139.5 | **45** |
|
||
| | last drawn | 209.5 | **210** |
|
||
|
||
## 🟡 And that PARTLY explains the 4.1 %: the rate drifts through the run
|
||
|
||
| segment | units | frames | units/frame |
|
||
|---|---|---|---|
|
||
| publisher, t = 15 → 45 | 30 | 17 | **1.765** |
|
||
| publisher, t = 45 → 255 | 210 | 97 | **2.165** |
|
||
| developer, t = 15 → 45 | 30 | 13 | **2.308** |
|
||
| developer, t = 45 → 210 | 165 | 70 | **2.357** |
|
||
|
||
**Within the developer splash the two independent segments agree to 2 %.** Across
|
||
the run the rate rises from ~1.76 in the first seconds to ~2.36 — a **33 % drift**.
|
||
|
||
That is one cause for the port's 7.9 % inconsistency, which is what they
|
||
predicted.
|
||
|
||
🔴 **But it does NOT close the 4.1 %, and the port refuted the stronger claim I
|
||
made.** Their test, verified here exactly: the publisher ÷ developer dwell ratio.
|
||
|
||
| | ratio | excess over declared |
|
||
|---|---|---|
|
||
| declared, 255 ÷ 210 | 1.2143 | — |
|
||
| corpus mean, 3 cold boots | 1.2784 | **+5.30 %** |
|
||
| this container's drift predicts | 1.3678 | **+12.64 %** |
|
||
|
||
The corpus ratio does sit above declared — the drift's *sign* is right and that is
|
||
real evidence — but my container's drift would inflate it about **2.4× too
|
||
strongly**. Drift of some size is doing the work; drift of *this* size is not.
|
||
|
||
⚠️ **One refinement, because the means are being compared more precisely than
|
||
n = 3 supports.** The corpus's three boots individually give excesses of **+0.89 %,
|
||
+8.24 %, +6.79 %** — a spread of **7.3 percentage points**, *wider* than the
|
||
5.30 pp gap between their mean and the declared value. Boot 1's ratio (1.2251) is
|
||
essentially the declared 1.2143. So this run sits **2.3 σ** above the corpus mean:
|
||
suggestive, not established, and "2.4×" is a precise statement about means that
|
||
are not individually that precise.
|
||
|
||
❔ **It does not close without a frame log from the corpus's own instrument**, and
|
||
that instrument was screenshot timing — there is no such log. An attempt to give
|
||
this side an n of 3 failed on tooling (see below).
|
||
|
||
## ⚠️ What the 33 % drift is NOT about
|
||
|
||
It is **presentation pacing** — how many of the guest's animation units pass per
|
||
frame Xenia presents. It says nothing about the game's logical rate of **60
|
||
units/second**, which is decoded (Q1) and which a renderer converts through at its
|
||
own frame rate. Guest pacing cannot reach that constant. The number is quotable
|
||
and the misreading would be easy, so it is fenced here as well as in the port's
|
||
`timing.json`.
|
||
|
||
## ✅ A cross-check neither side was looking for
|
||
|
||
The batch counts are **1 and 2** on the publisher against **3 and 6** on the
|
||
developer. The port reports that a count restricted to **sprite-bearing** elements
|
||
reproduces exactly that from the export — so `palogo_eff0`, the layerless forced
|
||
backdrop of
|
||
[`ui-forced-backdrop.md`](ui-forced-backdrop.md), is **not in the batched draw**,
|
||
confirmed from the file. Two instruments that disagreed about that element in every
|
||
previous iteration now agree on which one it is.
|
||
|
||
⚠️ It also means **no single units-per-frame figure describes a run here**, which
|
||
is the same conclusion as the dwell being emulator-paced, arriving from a third
|
||
direction.
|
||
|
||
## 🔴 The instrument's resolution is one buffer flush, not one frame
|
||
|
||
`tools/re-capture/frame_clock.sh` polls the growing draw log for its last frame
|
||
number. The capture writes through a C++ `ofstream`, so `tail` sees the file in
|
||
flush-sized bursts:
|
||
|
||
* **69 of 125 samples showed no advance at all**; the rest jumped **7–15 frames**
|
||
at once.
|
||
* Interpolating a frame's time *inside* a burst invents precision. Done naively it
|
||
made the apparent rate swing between **0.0164 and 0.0316 s/frame** — 61 fps to
|
||
31 fps — which is the flush, not the guest.
|
||
* **Frames 119 and 123 fall in the same burst**, so the black gap between the
|
||
splashes is **not separable by this instrument at all**. Its duration here comes
|
||
from frame counting, not from this clock.
|
||
|
||
So the table above is quoted as **brackets**: a frame's true time lies between the
|
||
last sample that had not reached it and the first that had. Sub-flush point
|
||
estimates are not available and were withdrawn before being reported.
|
||
|
||
## What is still open
|
||
|
||
🟡 The publisher's 4.1 % error against the developer's 1.1 % is **partly** explained
|
||
by the units/frame drift — the publisher splash runs during the first seconds,
|
||
where the rate is furthest from its later value — but the magnitude does not carry
|
||
across instruments (see above). Not closed.
|
||
|
||
🔴 **`ARM=early` does not reliably arm the capture.** Two of five attempts logged
|
||
`ARMED EARLY` and produced **no draw log at all**; the F10 keypress is lost. An
|
||
attempt to repeat this measurement three times in this container failed on it, so
|
||
this side still has n = 1.
|
||
|
||
⚠️ The intro-video boundary (frame 216) is 7 frames after the developer splash's
|
||
last draw, but those 7 frames span **1.77 s** by the naive map — deep inside the
|
||
flush artefact, and the movie is loading there. **The developer→intro gap is not
|
||
measured**, only bounded at 5.70 – 6.21 s end to end.
|
||
|
||
## Reproducing
|
||
|
||
```bash
|
||
GRACE=1 NOTAP=1 FRAMES=9000 ARM=early tools/re-capture/ui_draw_capture.sh 200 /tmp/cap &
|
||
tools/re-capture/frame_clock.sh /tmp/cap/xenia_re_ui_draws_01.log /tmp/cap/frameclock.tsv \
|
||
150 0.2 /tmp/cap/canary.stdout 400
|
||
```
|