ruled out
Last iteration I recorded eff3's absence as unexplained after withdrawing a bad
explanation for it. The previous capture survived on disk with 6907 frames, and
the attract loop returns to the title, so it contains a SECOND build-in at
frames 5942..7025. eff3 is absent there too.
Three alternative explanations tested and failed:
* sampling phase -- eff3 is non-zero for t in (58,64), SIX units, against a
2.23-unit step. A window wider than the step cannot be missed; frames 133
(t=60.1) and 134 (t=62.3) sit inside it and draw eff2 and eff4 instead.
* a draw the log cannot see -- exactly 2 draws per frame carry no geometry,
on all 932 settled title frames, always the same full-screen-triangle
shader, and present on frames where no wipe element is active.
* a bad position guess -- dropping position entirely, ZERO quads anywhere on
screen in either build-in window have a width within +-30 of 408. The width
spectrum jumps straight from 262 to 748.
Draw counts across both entries: eff1 4, eff2 3, eff3 0, eff4 6, against ~5
expected each. The four are a right-aligned wipe (938+258, 788+408, 447+749,
64+1133, all ending at x~1196) -- a left-growing reveal in four widths, of
which the game draws three.
Why is NOT established: nothing in eff3's element record differs from its
neighbours. Classified measured, not decoded, and the port is told that
dropping eff3 means authoring a behaviour I cannot derive from the file.
Two further corrections, both to my own earlier claims:
* "frame 107 is the title composited once" was an over-read. It binds NO
texture and only 4 of its 27 draws log geometry. The second title entry
has no such frame.
* the two build-ins are NOT frame-identical. I had that from a coincidentally
aligned pair of rows; aligned properly only 4 of 46 frames match. They are
the same animation at different sampling phases -- which is precisely what
makes the eff3 result robust.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QsEPXWVaEpyfudtR6re1Pd
222 lines
11 KiB
Markdown
222 lines
11 KiB
Markdown
# The title's build-in, measured in the guest's own draw stream
|
||
|
||
**Classification: measured.** Xenia Canary, `ui_draw_capture.sh ARM=early
|
||
FRAMES=9000`, 2026-08-29. Evidence:
|
||
[`title-glow-alpha-per-frame.csv`](../data/title-glow-alpha-per-frame.csv) — the
|
||
per-frame series the numbers below come from. (The 7 MB raw draw log is a scratch
|
||
capture and is not committed.)
|
||
|
||
## What this tests
|
||
|
||
[`ui-settle-time.md`](ui-settle-time.md) decodes the title's light arc as **five
|
||
staggered two-frame flashes** (`ptlogo_back2eff1`…`eff5`) that fire around t54–66
|
||
and are gone by t110, with `ptlogo_back2eff` and `ptlogo_back2` holding for the
|
||
rest of the screen. That decode was confirmed only against a **settled** frame:
|
||
posing at the settle time matched the console, which shows the *end state* is
|
||
right and says nothing about whether the flashes ever happen.
|
||
|
||
[`ui-record-loop-length.md`](ui-record-loop-length.md) decodes the `PRESS Ⓐ`
|
||
glow as a **120-unit cycle** containing a 105-unit ramp that peaks at **alpha 80**.
|
||
|
||
Both are predictions about the running game. This is the run.
|
||
|
||
## 🔴 First, the trap that nearly produced a false negative
|
||
|
||
The obvious instrument — match a bound texture's dimensions to a decoded sprite's
|
||
— **does not work, and fails silently in both directions.**
|
||
|
||
A first pass reported that none of the five flashes are ever drawn. It also
|
||
reported `ptbase2` (640×360) and `pteff04` (1280×720) drawn during frames 75–105.
|
||
Both were wrong. Those frames are the **intro movie**: four full-screen quads per
|
||
frame sampling a pair of 640×360 planes and a 1280×720 target — a video decode
|
||
whose plane sizes happen to collide with two sprite sizes. And the flashes were
|
||
missing because the title's sprites **sample large shared texture pages**, so the
|
||
bound texture identifies a page, not an element.
|
||
|
||
Canary's own capture code says so in a comment, and the corpus had already
|
||
recorded that the settled title's textures are all 1280×768. **The identity of a
|
||
2D draw here is its vertex geometry, not its texture.** Re-run against the quad
|
||
rects, everything appears.
|
||
|
||
## The structure of the title in the draw stream
|
||
|
||
| frames | draws/frame | what |
|
||
|---|---|---|
|
||
| 2–105 | 3–5 | splashes, then the intro movie |
|
||
| **107** | **27** | a transition frame — see the caveat below |
|
||
| 109–167 | 6 → 14 | the build-in |
|
||
| 168–1217 | 10–11 | the settled title |
|
||
| 1218– | 4–5 | back to the attract loop |
|
||
|
||
The settled screen is 10–11 draws that never name a sprite. **A capture armed at
|
||
the title therefore sees nothing** — which is why `ARM=early` exists.
|
||
|
||
⚠️ **What frame 107 is, is NOT established.** An earlier version of this page called
|
||
it "the title composited, once". It is a 27-draw spike sitting between the movie's
|
||
last frame (105) and the title's first (109), it **binds no texture at all**, and
|
||
only 4 of its 27 draws log any geometry. It marks the transition; calling it the
|
||
composite was an over-read. (The regression below independently puts t=0 at frame
|
||
106.1, which is consistent with the title's clock starting here — but that is a fit
|
||
landing nearby, not evidence about what the 27 draws do.)
|
||
|
||
## The flashes are real, and they are transient
|
||
|
||
Matching quads by design-space rect over frames 100–260, and converting frames to
|
||
keyframe units with **2.346 units/frame — derived from the glow's period alone,
|
||
a different element**, taking the composite spike (frame 107) as t=0:
|
||
|
||
| element | drawn in frames | → t units | decoded |
|
||
|---|---|---|---|
|
||
| `ptlogo_back2eff1` | **130–131** | **54.0 – 56.3** | flash, peak **t54–56** ✅ |
|
||
| `ptlogo_back2eff2` | **133** | **61.0** | flash, peak t58–60 |
|
||
| `ptlogo_back2eff3` | **never** | — | flash, peak t62–64 ⚠️ |
|
||
| `ptlogo_back2eff4` | **133–135** | 61.0 – 65.7 | flash, peak **t~64** ✅ |
|
||
| `ptlogo_back2eff` / `eff5` | 134–260 | 63.3 – … | 255 at t64–66, **holds** ✅ |
|
||
| `ptlogo_back2` | 134–260 | 63.3 – … | 255 at t80, **holds** ✅ |
|
||
| `ptlogo1` | **125**–148 | **42.2** | stops moving at **t42** ✅ |
|
||
|
||
**The flashes fire inside a six-frame window and are absent from every one of the
|
||
other 155 frames sampled.** The two holders are present continuously from frame
|
||
134 onward. That is the decoded mechanism, observed.
|
||
|
||
## 🔴 The game does not draw `ptlogo_back2eff3`
|
||
|
||
**Reproduced over two title entries, 5 800 frames apart, in one run.** The disc
|
||
declares `ptlogo_back2eff3` — alpha 0 at t58, **255 at t60–62**, 0 at t64, at
|
||
(788, 117), sprite 408×203 — and the running game never submits it.
|
||
|
||
The wipe family is right-aligned: `eff2` 938+258, `eff3` 788+408, `eff4` 447+749,
|
||
`eff5` 64+1133 all end at x≈1196. It is a left-growing wipe in four widths, and
|
||
**the game draws three of them.**
|
||
|
||
| element | width | draws across both build-ins | expected at ~2.23 units/frame |
|
||
|---|---|---|---|
|
||
| `eff1` | 167 | 4 | ~5 |
|
||
| `eff2` | 258 | 3 | ~5 |
|
||
| **`eff3`** | **408** | **0** | **~5** |
|
||
| `eff4` | 749 | 6 | ~5 |
|
||
|
||
⚠️ An earlier version of this page called this a **sub-frame phase effect**. That
|
||
was wrong and is withdrawn. Three explanations were tested and all fail:
|
||
|
||
* **Sampling phase.** `eff3`'s non-zero window is t ∈ (58, 64) — **six units**,
|
||
against a step of **2.23 units per presented frame**. A window wider than the
|
||
step *cannot* be missed: at least two frames must land inside it, and frames 133
|
||
(t = 60.1) and 134 (t = 62.3) do. Both draw `eff2` and `eff4`; neither draws
|
||
`eff3`.
|
||
* **A draw the log cannot see.** Exactly **2** draws per frame carry no geometry,
|
||
on all 932 settled title frames — always the same full-screen-triangle shader
|
||
(`prim=8 indices=3`, `vs=0x72CBCAA6A7984111`), present on frames where no wipe
|
||
element is active at all. `eff3` cannot be one of them.
|
||
* **A bad position guess on my side.** Dropping the position entirely and
|
||
searching *every* quad submitted anywhere on screen during both build-in
|
||
windows: **zero** have a width within ±30 of 408. The width spectrum jumps
|
||
straight from 262 to 748.
|
||
|
||
❔ **Why the game skips it is not established.** Nothing in its element record
|
||
distinguishes it — same kind (`0x0`), same keyframe shape, same `u4`/`u8`, same
|
||
scale, as `eff2` and `eff4` either side of it.
|
||
|
||
🔴 **For the port: you draw `eff3` at t=60–62 and the console does not.** That is
|
||
one extra frame of wipe, at the widest-but-one step. It is small, and it is real.
|
||
|
||
🔴 **A second thing this page previously claimed, also withdrawn: that a port
|
||
drawing all five flashes shows "more sweep than the console".** There is no
|
||
evidence for that. The port draws them sequentially at their declared times, never
|
||
more than two at once, which is what frames 131–135 above show the game doing.
|
||
The pile-up worth warning about was the `rest()` bug, and that is fixed.
|
||
|
||
⚠️ **What a frame-by-frame comparison of the build-in WILL show, and it is not a
|
||
defect on either side.** The game's timeline is 60 units/s against a 30 Hz
|
||
present, so 2 units per submitted frame; this capture ran at **2.231 units per
|
||
presented frame**. A 2-unit flash peak therefore gets about one frame here and
|
||
would get more on a faster present. **A build-in compared frame-by-frame against
|
||
this capture will disagree about which flashes appear in which frame**, and
|
||
neither side is wrong. The settled comparison is unaffected — at t=198 none of the
|
||
five is drawn.
|
||
|
||
## The glow's ramp, read out of the guest
|
||
|
||
The plate glow's quad carries a **per-vertex colour whose alpha is the element's
|
||
fade alpha**, so the ramp can be read directly rather than inferred from pixels.
|
||
|
||
* **Observed alpha range: 0 … 80. Decoded peak: 80.** Exact, and not fitted.
|
||
* ⚠️ **This identity holds for the glow and does NOT generalise.** Read the same
|
||
way, `eff4` gives 255 at frame 133, 127 at 134 and 254 at 135 — non-monotonic,
|
||
so a per-vertex alpha is not simply the element's fade alpha for every element.
|
||
The glow's agreement (peak exactly 80) is evidence for the glow, not a decoded
|
||
rule about vertex colour.
|
||
* **Period: 51.158 presented frames**, from the first to the last of **20
|
||
consecutive cycle starts** (individual periods 49–53).
|
||
* The draw is **omitted entirely** while the glow is dark — which is what the
|
||
11↔10 draw alternation in the settled title is.
|
||
|
||
Fitting the decoded 8-keyframe ramp to the 972 measured frames, with **one
|
||
disclosed free parameter** (a constant phase offset):
|
||
|
||
| curve | RMS residual, alpha levels |
|
||
|---|---|
|
||
| **the decoded ramp**, phase +2.25 units (= **+0.96 frames**, sub-frame) | **13.16** |
|
||
| the decoded ramp, no phase fit | 13.68 |
|
||
| a symmetric triangle of the same period and peak | 15.73 |
|
||
| flat at the mean | 31.13 |
|
||
| 🔴 **the decoded ramp REVERSED** | **38.18** |
|
||
|
||
The reversal control is the one that matters: if the shape carried no
|
||
information, forwards and backwards would fit equally. They differ by **2.9×**, so
|
||
the measured ramp has the decoded ramp's asymmetry — fast rise, slow fall — in the
|
||
decoded direction.
|
||
|
||
⚠️ The +0.96-frame phase is the expected bias, not a correction: a cycle start is
|
||
detected at the first frame with a non-zero alpha, and the decoded curve leaves
|
||
zero part-way through a frame.
|
||
|
||
## Two entries, different phases
|
||
|
||
The attract loop returns to the title, so one capture contains **two** build-ins
|
||
(frames 119–1220 and 5942–7025). They are the same animation and are **not**
|
||
frame-aligned: aligning them on `eff1`'s first frame, only **4 of 46** frames have
|
||
an identical quad list, and the draw-count sequences drift by a frame partway
|
||
through.
|
||
|
||
That is what makes the `eff3` result robust rather than weaker. Two independent
|
||
samplings of the same animation, at different phases, both miss it.
|
||
|
||
⚠️ Only the **first** entry has the 27-draw frame; the second goes straight from
|
||
3–6 draws into the build-in. Whatever frame 107 is, the re-entry does not repeat it.
|
||
|
||
## Reach and what is not settled
|
||
|
||
⚠️ **One run, one machine.** ~2.23 units per presented frame is *this run's*
|
||
pacing, not a property of the game — the game's own quantum is **2 units per
|
||
submitted frame** ([`ui-keyframe-time-unit.md`](../ui-keyframe-time-unit.md)). The
|
||
internal ratios are what transfer.
|
||
|
||
✅ **A validation worth stating: the fit recovers t=0 on its own.** Regressing the
|
||
observed frame of five events against their declared keyframe times (residuals
|
||
≤ 0.9 frames over t = 42…138) gives a slope of 2.231 units/frame and an intercept
|
||
at **frame 106.1** — and the composite spike, which was not part of the fit, is
|
||
frame **107**.
|
||
|
||
❔ **The glow's period implies a 114-unit cycle at that slope**, against a declared
|
||
120. The dark-fraction test below settles 120 against 105 without any calibration,
|
||
but the 5 % gap in the period is unexplained.
|
||
|
||
❔ **The absolute frame rate of this run was not measured** — Canary logged no fps
|
||
and the log has no timestamps — so nothing here is stated in seconds. It did not
|
||
need to be: every comparison above is a ratio of measured quantities.
|
||
|
||
❔ **`ptlogo_tm` was never matched.** At 37×17 it is below the rect tolerance used;
|
||
absence here is not evidence.
|
||
|
||
⚠️ **`eff5` and `ptlogo_back2eff` are the same rect at the same position and were
|
||
not separated.** Distinguishing them needs the per-vertex alpha, which the decode
|
||
says differs — not attempted.
|
||
|
||
## Reproducing
|
||
|
||
```bash
|
||
FRAMES=9000 MAXDRAWS=5000000 ARM=early tools/re-capture/ui_draw_capture.sh 900 /tmp/uicap
|
||
python3 tools/re-capture/buildin_timeline.py /tmp/uicap/xenia_re_ui_draws_01.log
|
||
```
|