re: the boot splash dwells are declared -- and wall clock is the wrong unit
The port asked for two wall-clock timestamps across the boot splashes. Measured, and the measurement's own result is that timestamps are not the invariant. The dwells are the bundles' own declared timelines: publisher t=0..255 = 4.250 s at 60 units/s, developer t=0..210 = 3.500 s. The corpus's independent screenshot timing over three cold boots gives 4.30/4.60/4.37 and 3.51/3.50/3.37 -- the developer agreeing to 1.1%, two of its three runs to 0.3%. A fresh no-input boot with a frame->wall-clock map 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 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, and a port authoring seconds is authoring one run's pacing. Boundaries from the draw stream, read per quad: publisher glow frame 1, wordmark 6-119, three frames with NO sprite drawn, developer glows 123, wordmarks 140-209, intro video 216. The 3-frame gap replicates the earlier 4-frame measurement within the +-1 both are quantised to. New tool frame_clock.sh, and its limitation found by its own control: it resolves to one BUFFER FLUSH, not one frame. The capture writes through a C++ ofstream, so tail sees the log in bursts -- 69 of 125 samples showed no advance and the rest jumped 7-15 frames. Naive interpolation inside a burst made the apparent rate swing between 0.0164 and 0.0316 s/frame, which is the flush and not the guest. Frames 119 and 123 fall in one burst, so the inter-splash gap is not separable by this clock at all. Everything is quoted as brackets and the point estimates were withdrawn before being reported. palogo_anima never appears in the log and is NOT reported as undrawn: the developer bundle batches 7 elements into one draw and only the first two quads are logged. That is the trap that produced the eff3 false negative, so it is named rather than claimed. Also records the port's correction: ptcopyright has 105 instants with alpha >= 1 (t=139..243) against 105.89 units of span; I had quoted the rounded span. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QsEPXWVaEpyfudtR6re1Pd
This commit is contained in:
@@ -2341,3 +2341,48 @@ predate it.
|
||||
|
||||
Detail: [`docs/re/structures/ui-clock-freezes-at-settle.md`](../re/structures/ui-clock-freezes-at-settle.md).
|
||||
|
||||
## 2026-08-29 — the splash dwells: author units, not seconds
|
||||
|
||||
You asked for two wall-clock timestamps. I measured them, and the measurement's
|
||||
own result is that **timestamps are the wrong thing to author.**
|
||||
|
||||
✅ **The dwells are declared on the disc:**
|
||||
|
||||
| splash | declared | at 60 units/s | corpus wall clock, 3 cold boots |
|
||||
|---|---|---|---|
|
||||
| publisher (entries 10, 13) | t = 0…**255** | **4.250 s** | 4.30 / 4.60 / 4.37 |
|
||||
| developer (entries 11, 14) | t = 0…**210** | **3.500 s** | **3.51 / 3.50** / 3.37 |
|
||||
|
||||
The developer splash agrees to **1.1 %**, two of its three runs to 0.3 %.
|
||||
|
||||
🔴 **And my fresh boot is the argument against seconds.** With a frame→wall-clock
|
||||
map it puts the same two dwells at **5.10–5.61 s** and 3.83–4.30 s — 15–20 %
|
||||
longer than the declared values *and* than the corpus's three runs, same disc,
|
||||
same declared timeline. Three independent measurements of this container's rate
|
||||
(13.1 fps, ~28 fps, this one) say the same thing. **A seconds figure is one run's
|
||||
emulator pacing.** So: 255 and 210 units, and your instinct not to scale anything
|
||||
by a ratio from one screen was right for the same reason.
|
||||
|
||||
**Boundaries from the draw stream** (frames, this boot): publisher wordmark 6–119;
|
||||
**3 frames with no sprite drawn**; developer glows 123, wordmarks 140–209; intro
|
||||
video 216. The 3-frame gap replicates the earlier 4-frame one within the ±1 both
|
||||
are quantised to.
|
||||
|
||||
🔴 **What I could NOT measure, and why you should not read the fine numbers off
|
||||
this run.** `frame_clock.sh` resolves to one **buffer flush**, not one frame: 69 of
|
||||
125 samples showed no advance, the rest jumped 7–15 frames. Interpolating inside a
|
||||
burst made the apparent rate swing 0.0164–0.0316 s/frame — the flush, not the
|
||||
guest. **Frames 119 and 123 fall in the same burst, so the inter-splash gap is not
|
||||
separable by this clock at all**; its ~9 units come from frame counting instead.
|
||||
Everything above is quoted as brackets, and I withdrew the point estimates.
|
||||
|
||||
❔ Still open: the publisher's 4.1 % error against its declared 4.250 s, where the
|
||||
developer's is 1.1 %. And the developer→intro gap is only bounded (5.70–6.21 s end
|
||||
to end) because the movie loads inside a flush burst.
|
||||
|
||||
📌 Your quibble on `ptcopyright` is right: **105 instants** with alpha ≥ 1
|
||||
(t=139…243), against 105.89 units of span. I quoted the rounded span; the instant
|
||||
count is the better number and the argument runs on either.
|
||||
|
||||
Detail: [`docs/re/structures/boot-splash-dwells-are-declared.md`](../re/structures/boot-splash-dwells-are-declared.md).
|
||||
|
||||
|
||||
@@ -176,3 +176,4 @@ files, which is how the same ground got covered twice.
|
||||
| [`structures/ui-forced-backdrop.md`](structures/ui-forced-backdrop.md) | Where a keyless primitive paints, when the file forces it | ✅ **decoded**, partly closing `ui-prm-primitives.md`'s standing blocker: **an element covering the screen and fully opaque at some instant cannot paint above anything visible then**, and where that set is *every* other element its position is **forced first**. Disc-wide **80** instances forced, 50 constrained but not forced, 0 unconstrained. ✅ **Two controls, both measured orders from the running game**: it reproduces `palogo_eff0.prm` = FIRST (opaque 211 instants, below 6/6) — which a **name**-based rule gets wrong, since it is named like an overlay — and permits `pteff00.prm` on top (opaque 2 instants, below 3/23), which is where it is measured. ✅ Answers the port's `build_12`/`build_15` blank-screen contradiction: `pgloading_eff00.prm` is forced first, 4/4. ✅ Explains 36 builds the corpus recorded as "one colour" with no cause — `pzeff00.prm` forced first 32/32, so **our own sort wiped them**. 🔴 The rule's limit was found by its own test failing: applied to `.t32` sprites it claimed 22 must sort first against their own keys (`pneff01` 0xd850 at #8/13, `pbfriendly` 0x9230 at #17/49) — a sprite's *element* alpha says nothing about its *texture*'s coverage, so it is now restricted to untextured primitives. ⚠️ Assumes straight alpha-over; blend mode is still ❔. ⚠️ A lower bound, not an ordering. ⚠️ No new oracle run — the controls are prior measurements |
|
||||
| [`structures/ui-forced-backdrop.md`](structures/ui-forced-backdrop.md) *(span sensitivity)* | How much of the forced-backdrop rule rests on the timeline convention | ✅ **decoded**: the span is `0..=max keyframe time over every element`, and an element **holds** its final pose — decoded, not assumed ([`ui-keyframe-time-unit.md`](ui-keyframe-time-unit.md), [`ui-record-loop-length.md`](structures/ui-record-loop-length.md)). Sensitivity over the 130 keyless full-screen primitives with an opaque interval: using the header's declared **`+0x08`** instead changes **0** verdicts (interchangeable); using the primitive's **own** last keyframe changes **72**; counting elements **gone** after their last keyframe changes **72**. 🔴 So the hold decides **55 %** of verdicts — and dropping it is **refuted by a measured order**: `palogo_eff0.prm` is a single keyframe at t=0, so without the hold it is opaque for one instant, nothing else is up, and the rule calls it *free* against a game measured painting it first. ✅ The verdicts that matter are convention-independent — `pgloading_eff00.prm` is FIRST under all four, `pteff00.prm` FREE under all four. ⚠️ The port's **256 vs 211** was a **bundle mismatch, not a definitional one**: `palogo_eff0.prm` runs to t=255 on the publisher splash (entries 10/13) and t=210 on the developer (11/14) |
|
||||
| [`structures/ui-clock-freezes-at-settle.md`](structures/ui-clock-freezes-at-settle.md) | The top-level clock stops at the settle point — observed in the running game | ✅ **measured**: `GP_TITLE` build 4 declares `t = 0…269`, about 120 presented frames at this run's pacing, and the dwell lasted **~1 100**. `ptcopyright` declares alpha≥1 for **106 units** (t=138…244) and is **drawn for 1 050 frames**; `ptlogo1` declares an exit at t=264 and is drawn for 1 095. Both vanish within three frames of the dwell ending. **The clock advances through the build-in, stops inside the settle window `[160,236]`, and holds; the exit ramp plays when the screen leaves, not on a timer** — [`ui-settle-time.md`](structures/ui-settle-time.md)'s decode observed from the other side. A nested record keeps looping on its own clock throughout. 🔴 **This closes the 114-vs-120 gap, and it was my arithmetic**: 2.231 units/frame was regressed over *build-in* events (the only stretch the top-level clock advances) and applied to a period measured during the freeze — two different clocks. The declared **120** was never in doubt from the calibration-free dark-fraction test. ✅ The 51.158-frame period is now confirmed by a **second independent estimator** (autocorrelation, lag 51 with harmonics at 102/154) — ⚠️ whose first version **failed its control**, returning 48, because it indexed by sample position where the log's frame numbers have gaps. ❔ The **sweeps'** period stays unmeasured: the same validated estimator disagrees between two dwells of one screen (515 vs 452 frames). 🔴 **Blocker: a single Ⓐ on the title faults the guest** — 3 attempts, 2 register dumps of 223 MB and 519 MB, against 3 no-input runs that all completed; bounds menu-side dynamic RE here, and any scripted button press needs a `canary.stdout` size guard |
|
||||
| [`structures/boot-splash-dwells-are-declared.md`](structures/boot-splash-dwells-are-declared.md) | How long each boot splash is shown | ✅ **decoded**: the dwells are the bundles' own declared timelines — publisher **t=0…255 = 4.250 s**, developer **t=0…210 = 3.500 s** at 60 units/s. The corpus's independent screenshot timing over 3 cold boots gives 4.30/4.60/4.37 and **3.51/3.50/3.37** — the developer agreeing to **1.1 %**, two of its three runs to 0.3 %. 🔴 **Wall clock is the wrong unit to author**: a fresh no-input boot measured 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 runs, on the same disc — so a seconds figure is one run's emulator pacing. Boundaries from the draw stream: publisher wordmark frames 6–119, **3 frames with no sprite drawn**, developer glows 123, wordmarks 140–209, intro video 216. 🔴 **The frame→wall-clock instrument resolves to one BUFFER FLUSH, not one frame** — 69 of 125 samples showed no advance and the rest jumped 7–15 frames, making the apparent rate swing 0.0164–0.0316 s/frame; frames 119 and 123 fall in one burst, so the inter-splash gap is **not separable** by it. Quoted as brackets; sub-flush estimates withdrawn before reporting. ⚠️ `palogo_anima` never appears — almost certainly the 8-vertex cap (7 elements batched, 2 logged), the same trap as the `eff3` false negative, so it is named not reported. ❔ the publisher's 4.1 % error vs the developer's 1.1 % is unexplained |
|
||||
|
||||
87
docs/re/structures/boot-splash-dwells-are-declared.md
Normal file
87
docs/re/structures/boot-splash-dwells-are-declared.md
Normal file
@@ -0,0 +1,87 @@
|
||||
# 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, from the draw stream
|
||||
|
||||
Read with the per-quad parser, this boot:
|
||||
|
||||
| event | frame |
|
||||
|---|---|
|
||||
| `palogo_sqex_eff` — the publisher's glow | 1 |
|
||||
| `palogo_sqex` — the wordmark | **6** – 119 |
|
||||
| no sprite drawn at all | 120 – 122 |
|
||||
| `gamearts_eff` + `seta_eff` — the developer's glows | **123** – 139 |
|
||||
| `palogo_gamearts` + `palogo_seta` — the wordmarks | 140 – 209 |
|
||||
| intro video (a 640×360 pair per frame) | **216** – 563 |
|
||||
|
||||
The 3-frame black gap replicates the 4-frame gap measured in the earlier boot,
|
||||
within the ±1 frame both are quantised to — consistent with the ~9 units in
|
||||
[`boot-splash-gap-measured.md`](boot-splash-gap-measured.md).
|
||||
|
||||
⚠️ **`palogo_anima` never appears**, and that is almost certainly the 8-vertex cap
|
||||
rather than a fact about the game: the developer bundle has 7 elements batched into
|
||||
one draw, and only the first two quads are logged. This is the trap that produced
|
||||
the `eff3` false negative, so it is named rather than reported.
|
||||
|
||||
## 🔴 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 its declared 4.250 s is larger than the
|
||||
developer's 1.1 %, across only three corpus boots plus this one. Whether that is
|
||||
run-to-run pacing or something the publisher splash does differently is not
|
||||
settled.
|
||||
|
||||
⚠️ 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
|
||||
```
|
||||
43
tools/re-capture/frame_clock.sh
Executable file
43
tools/re-capture/frame_clock.sh
Executable file
@@ -0,0 +1,43 @@
|
||||
#!/usr/bin/env bash
|
||||
# Sample (wall clock, last captured frame) while a UI draw capture runs.
|
||||
#
|
||||
# The `xenia_re_ui_draws_NN.log` carries frame NUMBERS and no timestamps, and
|
||||
# Canary logs no fps, so a draw capture can say "4 frames of black" and not how
|
||||
# long that is. Two runs measured 13.1 and ~28 presented fps, so a nominal rate
|
||||
# cannot be assumed either.
|
||||
#
|
||||
# This polls the growing log and writes `epoch frame` pairs, which invert to give
|
||||
# any frame's wall-clock time.
|
||||
#
|
||||
# 🔴 RESOLUTION IS ONE BUFFER FLUSH, NOT ONE FRAME. The capture writes through a
|
||||
# C++ ofstream, so `tail` sees the log in flush-sized bursts: measured, 69 of 125
|
||||
# samples showed NO advance and the rest jumped 7-15 frames at once. Interpolating
|
||||
# a frame's time *inside* a burst invents precision -- it made the apparent rate
|
||||
# swing between 0.016 and 0.032 s/frame, which is the flush, not the guest.
|
||||
#
|
||||
# Use BRACKETS: a frame's true time lies between the last sample that had not
|
||||
# reached it and the first that had. Two frames inside one burst (the 3-frame
|
||||
# black gap between the boot splashes) are not separable at all.
|
||||
#
|
||||
# It also GUARDS canary.stdout: a guest fault dumps registers without bound
|
||||
# (223 MB and 519 MB observed on a filesystem at 91 %), so the run is killed if
|
||||
# stdout passes the cap.
|
||||
#
|
||||
# frame_clock.sh <draws.log> <out.tsv> [seconds] [interval] [stdout-file] [cap-MB]
|
||||
set -u
|
||||
LOG="$1"; OUT="$2"; DUR="${3:-150}"; IVAL="${4:-0.25}"; SOUT="${5:-}"; CAP="${6:-400}"
|
||||
: > "$OUT"
|
||||
end=$(( $(date +%s) + DUR ))
|
||||
while [ "$(date +%s)" -lt "$end" ]; do
|
||||
f=$(tail -c 400000 "$LOG" 2>/dev/null | grep -oE '^--- frame [0-9]+' | tail -1 | awk '{print $3}')
|
||||
[ -n "$f" ] && printf '%s\t%s\n' "$(date +%s.%N)" "$f" >> "$OUT"
|
||||
if [ -n "$SOUT" ] && [ -f "$SOUT" ]; then
|
||||
mb=$(( $(stat -c %s "$SOUT") / 1048576 ))
|
||||
if [ "$mb" -gt "$CAP" ]; then
|
||||
echo "STDOUT ${mb}MB > ${CAP}MB cap — guest is dumping registers, killing" >&2
|
||||
pkill -9 -x xenia_canary; exit 3
|
||||
fi
|
||||
fi
|
||||
sleep "$IVAL"
|
||||
done
|
||||
echo "sampled $(wc -l < "$OUT") points"
|
||||
Reference in New Issue
Block a user