Files
Sylpheed/docs/port/plate-arrival-halves.md
MechaCat02 a23c321831 port: land the play-tested work, and only that
Takes the port branch up to 77320d5e -- the state the human play-tested on
2026-09-02 -- for SOURCE paths only. Not a branch merge: `auto/port-p6-audio`
is 366 commits and 938 files, and most of that must not land.

WHAT COMES IN (76 files, all human-confirmed working):
  * the logo splash animation. 08ed3dd1 found it: `pose_at` ASSIGNED the settle
    instant instead of clamping to it, so the splash never animated at all --
    and the same bug manufactured a passing harness result, because the harness
    photographed t past the settle. Confirmed by play-test: "cannot notice any
    obvious difference from the actual game."
  * gamepad input -- (A)/(B) bound additively (`ui_accept` ships with NO joypad
    binding), stick latched with hysteresis at the game's own 61% digitise
    threshold. This is what made (A), video-skip and Extras work at all.
  * menu navigation and flow, menu audio, the exporter, the authored
    declarations, and 23 verification tools under tools/port/.

WHAT IS DELIBERATELY LEFT ON THE BRANCH:
  * everything after c0ae460a -- the F5/F6 title-timing investigation, whose own
    tip commit calls itself a "hand-off for one-minute human checks". Unchecked
    by definition; it goes through the new review gate like anything else.
  * the OPTIONS menu work of 2026-09-03. Real, probably good, NOT play-tested.
  * the F1 repeat mechanism, which its own commit calls "deliberately inert".

WHAT MUST NOT LAND, AND WHY THE .gitignore CHANGED:
  545 MB of extracted game content was committed on that branch -- 850 sprite,
  audio and transcoded video files under `export-probe/` and `export-probe2/`,
  plus 246 MB of loose .wav and .tsv at the repo root. This repository's own
  rule, in this file, is "never game content".

  The rule was not missing. It was written, and it was tightened on that very
  branch, with a careful comment explaining why BOTH `export/` and `data/base/`
  had to be listed -- while the exporter was writing to a third name that
  nobody had thought to list. Enumerating names is the thing that failed. So
  the ignore rules now describe the SHAPE: any top-level `export*/`, game media
  by extension, and loose capture output at the root. Verified both ways -- it
  catches all four offenders and ignores nothing currently tracked.

Verified: `cargo check --workspace` clean; all nine GDScript files parse in
project context, with a positive control (an injected syntax error is detected,
3 lines) so the clean result means something. `tools/port/check-all` was NOT
run -- it needs the container, the export tree and a display.
2026-09-04 16:17:14 +02:00

459 lines
25 KiB
Markdown
Raw Permalink 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.
# H3 — the `PRESS Ⓐ` plate: which half the lateness is in
**Status:** ✅ **both halves answered, and TWO of my own conclusions on this page
were refuted within the hour** — the `5 units/frame` reading and the claim that
`clock: "shared"` collapses. Both are struck in place rather than deleted,
because both were confident and both were wrong for reasons worth keeping.
The answers are the Decoder's `h3-units-per-frame-measured.md`,
`origin/auto/frame-blend-draw-path` at `a482d9a`.
Written 2026-09-01 by the Port, against `export/` regenerated from this
checkout, `HEAD` = `6db49f5` (merge of `origin/main` `1af103d` and
`origin/human/r1-retro-tick`). HANDOFF on this branch answers `9ca1eb5`.
The [play-test](../agents/PLAYTEST-2026-09-01.md) finding 3 says the plate
arrives late and names three candidate causes: the **unit→seconds constant**
(the Decoder's), the **clock origin** and **`rest.t`** (both ours). The brief
asks which half it is, and how that was established. This page is the answer.
> **Short version, after the answer landed.** `rest.t` and the clock origin are
> eliminated and stay eliminated — those were the port's half and the evidence
> below still holds. **Units per *frame* is 2, not the 5 I inferred**; my
> derivation was sound arithmetic on an unsound premise and is struck below.
> **The anchor is t=160**, as I suspected. And my consequent claim that
> `clock: "shared"` therefore collapses is **withdrawn** — I computed the
> collapse against the one number now known to be in dispute. What is left open
> is **units per *second***, which is `2 × guest fps`, and whether the guest is
> 30 Hz or 60 Hz is not settled by anyone. At 60 Hz the plate lands at 1.97 s
> instead of 3.93 s, which is the size of what the human reported.
## What the export actually declares
`export/screens/title/press_start.json`, build 2, one element `ptbtn00`:
| t | alpha | note |
|---|---|---|
| 0 | `0x00` | invisible, y = 560 |
| 214 | `0x00` | still invisible, y = 550 — it has drifted up unseen |
| **236** | `0xff` | **full** |
| 238 | `0xff` | last opaque frame |
| 244 | `0x00` | gone again |
`rest.t = 236`. `settle_window = [214, 236, 225]`.
🔴 **The corpus says the plate reaches `a=255` at `t=238`. It reaches it at
`t=236`.** 238 is the last frame at full alpha, not the arrival. Every
derivation quoting `238 − 118 = 120 units = 2.000 s` is really `236 − 118 =
118 units = 1.967 s`. The port has been printing both halves of the
contradiction in one sentence on every boot — *"plate reaches full alpha at
t=236 … 120 units after …"* — which is what an unchecked literal beside a
computed value looks like. Corrected in `boot.gd` and in `authored/flow.json`.
It moves the reconciliation by 0.033 s and overturns nothing.
## Candidate 1 — `rest.t`. **Eliminated.**
`rest.t` cannot set the plate's arrival, because the arrival is a **declared
keyframe**, not a rest pose. `ptbtn00` is transparent until `t=214` and opaque
at `t=236` under its own ramp; `rest.t = 236` only chooses where `holding`
parks it afterwards, and 236 *is* that ramp's own peak. Setting `rest.t` to any
other value moves where the plate stops, never when it starts.
Confirmed against the running port rather than by reading: see the film below,
where the plate's onset is bracketed by the declared `t=214` with `rest.t`
untouched.
⚠️ This does **not** rehabilitate `rest.t` generally. It is still wrong for
transients — `ptlogo_back2eff1` is a two-frame flash (0 at t52, `0xff` at
t54–56, 0 by t58) and its `rest.t = 54` is the flash *peak*, so `rest()` leaves
five of these burning at once. It is simply not in this defect's causal path.
## Candidate 2 — the clock origin. **Eliminated, and measured.**
Two things had to be true and both are:
**(a) The port's two builds share one clock exactly.** `_advance` sets
`view.time_units = 0.0` and raises the overlay in the same call
(`_overlay_due = _elapsed`), and the sequence path assigns
`overlay.time_units = view.time_units` outright rather than integrating a second
delta. Verified over a filmed boot: **85 of 85 title frames have
`view_units == overlay_units` to three decimals**, from 7.812 to 679.182. There
is no drift and no offset to find.
**(b) The title step begins when the previous step ends.** The film puts the
video's end and the title's start at the same logged instant (7.86 s / 7.92 s
across two runs), with no black hold between them.
## Candidate 3 — the unit→seconds constant. **NOT eliminated. It is the live one.**
🔴 **This section said "eliminated by sign" and that was wrong.** The argument
was: for the port to be late the constant must be *larger* than 60 units/s,
while the oracle presents at ~28.1 fps and the corpus measured the idle title at
28.5 fps — both slower than nominal, so the game's units run slower, not faster.
**That conflates two different quantities.** A presentation rate converts a
*measured wall-clock duration* into units. It says nothing about **how many
units elapse per game frame**, which is the constant in question
(`keyframe_units_per_second`, and `keyframe_time_unit` is listed as `unresolved`
on every screen this export emits). The correction is recorded rather than
edited away because the elimination it produced was confident and wrong.
And the Decoder's splash draw capture, landed the same day on
`origin/auto/frame-blend-draw-path` at `3cc1b51`
(`docs/re/data/splash-quad-timeline.txt`), gives an **independent handle on that
constant** — from a screen with no bearing on the plate:
| interval, publisher splash | guest, observed | this export, declared | units/frame |
|---|---|---|---|
| companion (`Q7`) onset → sharp logo (`Q0`) onset | f4 → f7, **3 frames** | t=0 → t=15, **15 units** | **5.0** |
| companion onset → its own peak | f4 → f7 (a=240), **3 frames** | t=0 → t=15 (a=255), **15 units** | **5.0** |
| sharp logo onset → full alpha | f7 → f10, **3 frames** | t=15 → t=30, **15 units** | **5.0** |
Three intervals, three ways, **5 units per guest frame** — against the declared
**2**.
## 🔴 STRUCK. It is 2 units per frame, and my 5 was an artefact of two things
Measured by the Decoder on `ptbtn00` itself — the plate, on the title, not a
splash — in `h3-units-per-frame-measured.md` (`a482d9a`), against a
pre-registration committed before the capture was read:
```
label 5372 5373 5374 5375 [5376] 5377 5378 [5379] 5380
alpha 46 69 92 115 — 197 220 — 255
step +23 +23 +23
```
`255 × 2 / 22 = 23.18`, and the plate's declared ramp is `t=214 → 236`, i.e.
`T = 22`. **Three consecutive gap-free steps of exactly 23 is 2.0 units per
frame on the nose.** Their prediction was 11 frames for the ramp and it measured
10, inside a stated ±1. My 4.4-frame prediction is excluded by more than 2×.
**Why my three intervals all read 5, and why the agreement between them was
worthless.** Two independent errors, both mine to have made:
1. **An alpha step is not a clock rate.** For a linear segment,
`Δα per frame = 255 × (units per frame) / T`. Two elements with different
declared segment lengths `T` show different `Δα` at an *identical* clock.
Splash B's quads step 34 with `T=15`; the plate steps 23 with `T=22`; both
are 2 units/frame. Reading a step as a rate is what produced the 2.7×.
2. **My "onsets" were not onsets.** I took each quad's *first submission* as its
`α = 0` time. On splash A, `Q7` and `Q0` are both already at **α = 85** when
first submitted. So all three intervals started late — and by a *different*
amount per element, because the bias scales with `T`. That the three then
agreed with each other is not corroboration; they share the error.
The three "independent" intervals were one measurement made three times with the
same two biases. That is the shape I should have checked for and did not.
⚠️ The Decoder records the published `splash-quad-timeline.txt` having no `T`
column as their own defect, now fixed. That does not make the inference mine any
less: **the arithmetic was sound and the premise was not, and a premise handed
to me is still a premise I used.**
⚠️ **Two caveats, and they are why this is asked rather than acted on.** The
capture's frame index **skips** — 5, 8 and 12 are absent from Q7's eight draws —
and whether those are frames where the quad was genuinely not submitted or
presents the logger dropped changes the arithmetic. And the present rate during
a splash is not established.
### 🔴 And it collides with the oracle's own plate measurement
The two captures cannot both be read at face value:
| capture | implies |
|---|---|
| `title-plate-delay-measured.md` — 118 units in 2.135 s | **~55 units/s** |
| `splash-quad-timeline.txt` — 15 units in 3 frames | **~150 units/s** at 30 fps |
A factor of **2.7** between two measurements taken off the same game by the same
agent, on two different screens. Either the two screens' keyframes are in
different units, or one of the two readings has an anchor wrong — which is the
same shape as the `t=118` / `t=160` question below. **Both are oracle
measurements and neither is the port's to resolve.** Asked in
[`BLOCKED.md`](BLOCKED.md) H3.
## The film — and the instrument had to be fixed first
`--film` scheduled frame `n` for `n × interval` and reported nothing. One
1280×720 `save_png` under llvmpipe costs ~0.24 s, so **a request for 0.05 s
delivered 247 frames in 60 s where 1 200 were asked for** — an achieved 4.1 fps
against a requested 20 — and the deficit accumulated silently into the frame
index. `f_071.png` still looked exactly like the frame that was meant to be
3.55 s in.
[`TEMPORAL-VERIFICATION.md`](../agents/TEMPORAL-VERIFICATION.md) §1 is explicit
that this is not a slow capture but a *different* one, and that an instrument
which cannot report its own completeness may not be trusted (R3). So `--film`
now writes `<film>_frames.tsv` — one row per frame carrying the elapsed second
it was **actually** taken at, the second it was **requested** for, the lag
between them, and both builds' clocks — appended and flushed as it goes, so a
run killed by `timeout` still leaves a complete index. It prints the achieved
rate against the requested one every 40 frames. The schedule is deliberately
**not** rebased onto `_elapsed`: catching up would hide the shortfall, which is
the defect.
The run this page rests on: requested 20 fps, **achieved 15.9 fps for the first
120 frames and 11.3 fps by frame 200**, lag reaching 8.7 s. Stated because the
numbers below are quoted against `view_units` — the port's own timeline, which
the index records per frame — and never against a wall-clock instant.
### Pre-registered, before looking (R2)
> Filming the boot, measured from the frame the title step begins, the plate
> region stays at its background floor until **t=214**, rises over **22 units**,
> and plateaus at **t=236**. Accept ±2 frames at each end.
### What it did
Region = the plate's own quad, x 127–640, y 525–575 (`pos [383,550]`,
`pivot [256,25]`, sprite 513×50). Mean of the crop, one row per filmed frame:
| `view_units` | region mean | |
|---|---|---|
| 7.8 → 143.8 | 0.13765, **flat to 5 decimals** | nothing there |
| 151.9 → 207.8 | 0.13773 → 0.14341 | a slow 4 % drift, build 4's own |
| **207.8 → 215.3** | 0.14341 → **0.15348** | ⬅ the step; brackets the declared **214** |
| 215.3 → 239.8 | → 0.21254 | the ramp |
| 239.8 → 279.3 | → 0.24766 | plate full at 236; the rest is `ptbtn00f` |
The onset is bracketed between 207.8 and 215.3 at a frame spacing of ~7.5 units,
i.e. **within one frame of the declared 214**, and `rest.t` was never touched.
Prediction held.
After 236 the region keeps oscillating — peaks near `u` 279 and 407, troughs
near 343 and 479, a period of **~128 units** against `ptbtn00f`'s declared
`loop_length_units = 120`. That is the focus glow looping, not the plate, and it
is an ordering-and-period check that survives the drifting capture rate.
## So where the port lands against the oracle
`docs/re/title-plate-delay-measured.md` is **not in this checkout** — it is on
`origin/auto/no-disc-and-menu-captures`, added at `fb536df` and cited by
`authored/flow.json` at `5b0a6e6`. Read there:
| | run 1 | run 2 | **the port** |
|---|---|---|---|
| settled → plate | 2.138 s | 2.132 s | **1.967 s** (118 units at 60/s) |
| first drawn → plate | 3.781 s | 4.263 s | **3.933 s** (236 units) |
On the interval the oracle measured deliberately, the port is **0.17 s early**.
On the interval the oracle explicitly says not to use, the port sits **between
the two runs**. No number anyone has taken makes this port's plate late.
**The human watched both and says late, and that observation stands.** At
60 units/s the port reproduces every figure the plate capture carries — which is
the point: *reproducing that capture is not the same as being right*, because
the splash capture says the unit is 2.5× off and would put the port 2.4 s late.
The two cannot both hold. The human's eye agrees with the splash capture.
## 🔴 WITHDRAWN: my claim that the t=160 anchor collapses `clock: "shared"`
The section below asked which anchor the glyph counter corresponds to and said,
of the t=160 answer, *"the shared-clock premise does not reproduce the
measurement at all and `clock: "shared"` is open"*. **The Decoder answered
t≈160–176 — candidate B — and then repeated my consequence back to me**, noting
correctly that what it costs the port's model is the port's problem and not
evidence against their measurement.
**They were right to report it and I was wrong to have claimed it.** I computed
the collapse by comparing the declared 76 units against **2.135 s**, and 2.135 s
is the single number the same page identifies as in dispute — their two captures
disagree about frames→seconds by ~2.9×. *Falsifying a model with the quantity
that is itself under dispute* is the error, and it is the second time on this
page I reasoned confidently from a premise I had not checked.
Against the **new** capture, in its own labels rather than in seconds,
shared-clock holds:
| declared, shared clock | capture | at the plate ramp's own measured 2.2 units/label |
|---|---|---|
| `ptcopyright` full (t=160) → plate α=0 (t=214) = **54 units** | 5350 → 5370, **20 labels** | 44 units |
| `ptcopyright` full (t=160) → plate α=255 (t=236) = **76 units** | 5350 → 5380, **30 labels** | 66 units |
| the plate's own ramp (t=214 → 236) = **22 units** | 5370 → 5380, **10 labels** | — (this is the calibration) |
Both intervals come out **short of declared by 13–19 %, in the same direction**,
against a claimed collapse of ~1.7×. And the residual has a named candidate the
Decoder supplies: **empty labels advance the clock by more than one step**, they
are about one label in five, and the two intervals contain different fractions of
them (3 of 20, and 6 of 30). Modelled with empties carrying ~4 units the two
reconstruct at 46 and 72 against 54 and 76.
**So `clock: "shared"` is not falsified and the port keeps it.** It is not
*confirmed* to better than ~20 % either, and this page does not claim that. What
it is no longer is "collapsed on the port's own account".
## A second anchor question, also the Decoder's — ✅ ANSWERED: it is t=160
**What does the oracle's "title settled" correspond to on the declared
timeline?** It is defined operationally — *"glyph counter first reads its
no-plate value 154"* — and our export offers **two** anchors, 42 units apart:
| anchor | what it is | settled → plate | vs measured 2.135 s |
|---|---|---|---|
| **t = 118** | `pteff01`, `pteff02`, `ptlogoall_eff` end their ramps together | 118 units = 1.967 s | −8 % |
| **t = 160** | `ptcopyright` reaches full alpha — the **last** element to finish building in, and the only one made of **glyphs** | 76 units = 1.267 s | −41 % |
The port's reconciliation picked 118. The port's own `settle_time()` returns
**160** and the boot prints `settles at t=160` — so the two notions disagree
inside one binary. And the oracle's anchor is a *glyph* counter, while the thing
that finishes at 160 is a *line of text*.
If the anchor is 160, the shared-clock premise does not reproduce the
measurement at all and `clock: "shared"` in `authored/flow.json` — which the
port authored from arithmetic and nobody has ever measured — is open. If it is
118, the reconciliation stands and H3 is not a timing defect.
**Answered from the draw stream** (`a482d9a`): `ptcopyright` reaches α=255 at
label 5350, which calibrates to **t ≈ 168** on the plate's own ramp (t ≈ 176 at a
flat 2.0/label). Candidate B is 8–16 units away; candidate A is 50–58. It is B.
📌 And a caution from the same stream that this port should hold onto: **the
sweep leaves never settle.** The two off-screen-wide quads translate
monotonically through every label examined and are still moving when the plate
arrives. *"The title has settled"* can only ever mean **the build-in elements
have finished**, never *the screen has stopped changing* — which is the same
distinction that `rest.t` keeps getting wrong.
### 🔴 ALL FOUR NAMED CAUSES ARE NOW DEAD, AND THE HUMAN'S OBSERVATION IS NOT
**The rate is measured: 56.8 units per guest second** (`units-per-second-measured.md`,
control passing at 1.15 %, two elements agreeing at one clock — `ptbtn00` at
657.9 α/s and `ptcopyright` at 650.4 α/s, which puts `ptcopyright`'s segment at
`T = 22.25`, a round declared length nobody fitted). **30 and 120 are both
excluded.**
At 56.8 units/s the plate's `t = 236` lands at **4.15 s** after clock zero,
against the port's **3.93 s**. The port is fractionally **early**. So:
| candidate | verdict |
|---|---|
| `rest.t` | eliminated — the arrival is a declared keyframe |
| clock origin | eliminated — 85/85 frames share one clock |
| the anchor (t=118 vs t=160) | answered: t=160, and `clock: "shared"` survives it |
| **the unit→seconds constant** | **eliminated — 56.8 measured; the port is early, not late** |
**Nothing named in the play-test explains what the human saw, and this page says
so rather than quietly closing H3 green.** The observation stands and is now
unattributed. Two things it could still be, neither of them the plate's own
timing and neither established here:
* **what "late" was measured against.** Every number above is relative to the
title's clock zero. A player experiences the plate relative to the *boot*, and
the port's boot reaches the title at 7.86 s only because `--skip-at` presses Ⓐ.
🔴 **On the play-test build Ⓐ was not bound to the pad at all**, so that human
could not skip the 137 s intro — the run they judged is not the run any of
these measurements describe.
* **the splash dwells.** The rate's reach is the **title**. The splashes are a
different `GamePart` and nothing yet shows they tick at 56.8; the Decoder has
reading their `T` off the disc as a next item.
### The route that is dead regardless
🔴 **`units = 2 × frames` must not be used anywhere.** The same animation takes
21 frame labels in one capture and 33 in another, and a splash logo steps
`+136,+34` in one run and `+17,+51,+34,+34,+17,+17` in the other; a fixed
per-frame increment cannot do that. The 2 was one run's frame pacing.
✅ **Audited, and the port never did this.** `boot.gd` advances
`time_units += delta * units_per_second` off delta time. The retirement cost this
port a *justification* in `authored/timing.json`, not a behaviour — and the
justification's second leg (12 declared units against a 0.14–0.30 s measured
black plateau, 40–86 units/s, no frames in the chain) never depended on it.
### What was still open, before the rate landed
`units/second = units/frame × guest fps`. The first factor is now **2**. The
second is not established: **2 × 30 = 60** (what this port uses) and
**2 × 60 = 120** (which puts the plate at **1.97 s** instead of 3.93 s — the size
of what the human reported). The Decoder's capture ran at 27.2 labels/s, which is
Canary's presentation rate and cannot separate a 30 Hz guest at full speed from a
60 Hz guest at half. **They asked the port not to change 60 units/s on their
account yet, and it has not.** Asked in [`BLOCKED.md`](BLOCKED.md) H3.
## Refutation attempts this iteration
| claim | whose | outcome |
|---|---|---|
| the title's settle window is `[160, 236]` | Decoder, `5b0a6e6` | ✅ **survived** — our exporter still computes `[160, 236, 198]` under the corrected record layout |
| *"`ptlogo1` rests at t=251 and stops moving at t=42"* | Decoder, `5b0a6e6`, and the headline evidence for `rest.t ≠ settle` | ❌ **refuted on its evidence** — in the current export `ptlogo1.rest.t` is **42**, equal to when it stops moving. The record-layout fix repaired exactly this element. The *conclusion* survives on other elements (`ptlogo_back2eff1`, `pteff00`); the example no longer supports it |
| *"`ptbtn00` reaches a=255 at t=238"* → `120 units` | the Port's own | ❌ **refuted** — 236, so 118 units |
| *"build 4 is still fading up from black until t=261; `pteff00` is 7 % opaque at 243"* | the Port's own | ❌ **refuted** — `pteff00` is opaque at t=0, clear by **t=16**, transparent until 261, then fades **to** black by 269. At t=243 it is 0 % opaque. The comment had the direction backwards |
## What this does not settle
* Whether the human's "late" is a real offset the corpus has not measured. It
is not reconciled by anything here, and no measurement contradicts it either.
* The `t=118` / `t=160` anchor. Asked, not guessed.
* Whether the plate **pulses** after arrival. The port holds it; the corpus
measures a ~2.24 s pulse. Filed already in `flow.json`'s `no_pulse_why`.
* Finding 4 (the splash fade/blur) is the Decoder's this iteration — it said so
on the message channel and is on `auto/frame-blend-draw-path`.
---
# The splash blur is an ASSET, not a pass — and this port already draws it
Added the same iteration, after the Decoder answered play-test finding 4 from
GPU state (*no post-process on either splash*, `auto/frame-blend-draw-path` at
`3cc1b51`) and the human added the observation that **the logos go from blurred
to clear/sharp, in about one second**.
Those two are not in tension. They resolve each other:
> **Every logo ships a second, pre-blurred copy of itself, ~21 × 20 px larger
> and concentric, which is drawn alone first and crossfades out as the sharp
> logo fades in.** That is "blurred → sharp" with no post-process pass, no
> blur shader and no second render target.
Measured off `export/sprites/title/`:
| logo | sharp | companion | position offset | concentric? |
|---|---|---|---|---|
| `palogo_gamearts` | 500 × 71 | 521 × 91 | (−11, −10) | ✅ |
| `palogo_seta` | 240 × 89 | 261 × 110 | (−10, −11) | ✅ |
| `palogo_anima` | 388 × 136 | 407 × 156 | (−11, −9) | ✅ |
| `palogo_sqex` | 666 × 68 | 686 × 89 | (−10, −11) | ✅ |
## 🔴 "The port applies no blur at all" is false, and it came from this page's own side
[`BLOCKED.md`](BLOCKED.md) H2 and the play-test both record *"The port draws the
splash from the declared keyframe alphas only. It applies **no blur at all**."*
The port does not apply a post-process blur — but it draws all seven quads of
the developer splash including the three blurred companions, and it has been
doing so all along. A frozen sweep across the build-in, one deterministic
capture every 3 units (the human's "take a series of quick screenshots within
the short animation span", done without a clock at all):
| `t` | units | drawn |
|---|---|---|
| 0.00 s | 0 | background only |
| 0.05 – 0.25 s | 3 – 15 | background + **the three blurred companions alone** |
| 0.30 – 0.70 s | 18 – 42 | **all seven** — the crossfade |
| 0.75 – 0.80 s | 45 – 48 | background + the three sharp logos |
Blurred first, then both, then sharp. The mechanism is reproduced. The claim
that it was not came from describing the renderer instead of running it.
## Cross-check of the Decoder's quad mapping — ✅ survived, independently
`splash-quad-timeline.txt` names its quads by NDC rectangle off the guest's
vertex stream. This export's declared rectangles, converted independently:
| | this export | the guest | agreement |
|---|---|---|---|
| `palogo_sqex` | x[−0.517, +0.523] y[−0.106, +0.083] | `Q0` x[−0.520, +0.520] y[−0.100, +0.080] | **~2 px** |
| `palogo_sqex_eff` | x[−0.533, +0.539] y[−0.133, +0.114] | `Q7` x[−0.530, +0.540] y[−0.130, +0.120] | **~4 px** |
Two decoders, two paths — a `.pak` read statically and a vertex stream logged
live — agreeing to a few pixels on both the sharp quad and the blurred one.
That is corroboration of the mapping and of `Q7` being the companion.
## What is still open on the splash
* **The extent.** The rectangles agree; the *alphas* are where the 5-units-per-
frame discrepancy above came from, and until that is settled it is not
possible to say whether the game holds the blurred copy longer than we do.
That is the same question as H3 and is asked once, there.
* Whether `palogo_eff0` (kind `0x10`, a full-screen quad, like the title's
`pteff00`) is the splash's fade veil. Not examined this iteration.