Files
Sylpheed/docs/re/structures/ui-title-buildin-measured.md
sylph-decoder 69bc4cd1cd re: the game never draws ptlogo_back2eff3 -- reproduced, with three explanations
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
2026-08-29 20:19:04 +00:00

222 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 t5466
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 75105.
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 |
|---|---|---|
| 2105 | 35 | splashes, then the intro movie |
| **107** | **27** | a transition frame — see the caveat below |
| 109167 | 6 → 14 | the build-in |
| 1681217 | 1011 | the settled title |
| 1218 | 45 | back to the attract loop |
The settled screen is 1011 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 100260, 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` | **130131** | **54.0 56.3** | flash, peak **t5456** ✅ |
| `ptlogo_back2eff2` | **133** | **61.0** | flash, peak t5860 |
| `ptlogo_back2eff3` | **never** | — | flash, peak t6264 ⚠️ |
| `ptlogo_back2eff4` | **133135** | 61.0 65.7 | flash, peak **t~64** ✅ |
| `ptlogo_back2eff` / `eff5` | 134260 | 63.3 … | 255 at t6466, **holds** ✅ |
| `ptlogo_back2` | 134260 | 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 t6062**, 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=6062 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 131135 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 4953).
* 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 1191220 and 59427025). 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
36 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
```