sylpheed-port's BLOCKED ask #2. The gap was the one quantity in the transition with no rule: measured at 0, 3 and 2 frames across three transitions, and I had proposed it might be a load, which would make it emulator- and storage-dependent and unauthorable. Leg 1, bundle size runs the wrong way. If the gap were the incoming bundle arriving, the biggest bundle would gap longest. Build 4 is 12 278 666 B and gaps ZERO frames; build 5 is 6 977 437 B and gaps 3 and 2. Leg 2, ran title->menu a second time from cold. The outgoing ramp is byte-identical (63, 127, 191, 255) and the gap is 3 frames in BOTH runs. Leg 3, and the two runs are not a null comparison -- which is the objection leg 2 invites. The captures refute it themselves: press-to-first-change differs by ~12 frames between them (~25 against ~10). Something in this transition really is cache-sensitive and moved by 0.4 s, while the gap did not move at all. The control comes from inside the measurement rather than from an assumption that conditions differed. So the gap is deterministic to the frame and not a load. It is also not constant across transitions (0, 3, 2, 3) and not in the fade group -- the port reports 866 keyframes across 16 screens with 0 untimed. A deterministic game quantity with no rule found; black_hold_units stays 0, and "not a load" must not become a reason to author a constant. The load proposal in screen-transitions.md is marked refuted rather than deleted. Also fills in docs/game/navigation.md, which the standing brief asks me to keep and which I had not touched while measuring four transitions: a player-side section on what a screen change looks like, and three scripting traps -- that `pkill -f xenia_canary` kills the shell that ran it (cost a launch today, and the same trap is already in METHOD for pgrep), that screen_id.py reports `menu` during the attract loop, and that it cannot tell EXTRAS from the main menu. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Wuu56cE8vJGTBtn1ppsk8v
495 lines
25 KiB
Markdown
495 lines
25 KiB
Markdown
# What happens between two screens — a fade through black, and where its timing lives
|
||
|
||
**Status:** ✅ `CONFIRMED`. The quad and its ramp are **decoded** (a keyframe
|
||
group on the disc, with a disc-wide check); the wall-clock timings are
|
||
**measured** off the running game at 30 fps. One piece is **undecodable from this
|
||
field** and is called out below.
|
||
|
||
Answers [MISSION Q7](../port/MISSION.md).
|
||
|
||
## The mechanism — decoded
|
||
|
||
Every title-side screen carries a full-screen untextured primitive that is
|
||
**black, and paints last**: `pteff00.prm` in `GP_TITLE`, `pfeff00.prm` in
|
||
`GP_SAVE_LOAD`. That it sorts last was already established
|
||
([`structures/ui-paint-order-key.md`](structures/ui-paint-order-key.md)); what is
|
||
new here is that its **keyframe group is the transition**.
|
||
|
||
The group is always four blocks, and always this shape:
|
||
|
||
| block | alpha | meaning |
|
||
|---|---|---|
|
||
| 1 | `0xff` at `t = T0` | the screen starts **black** |
|
||
| 2 | `0x00` at `t = T1` | ramp to fully clear — the screen **fades in** |
|
||
| 3 | `0x00` at `t = T2` | clear; this is the resting pose, the quad is invisible |
|
||
| 4 | `0xff`, **no time** | ramp back to black — the screen **fades out** on exit |
|
||
|
||
Read with the corpus's rule that a keyframe is the *start* of a ramp
|
||
([`structures/ui-resting-pose.md`](structures/ui-resting-pose.md)).
|
||
|
||
🔴 **The table this section used to print was taken with a STALE READER, and both
|
||
its numbers were wrong (corrected 2026-08-30).** `fade_quads.py` read each pose's
|
||
time from `blk+36` — the *next* record's time word — so every time was shifted one
|
||
slot and the last pose came out untimed (`t=—`). That is the same association the
|
||
[record-layout fix](ui-keyframe-record-layout.md) retired in the crate; the Python
|
||
helper was never swept with it. Fixed, and controlled against the rebuilt
|
||
`screen info`, which prints `[0 12 70 80]` for the same element:
|
||
|
||
```
|
||
$ tools/re-capture/fade_quads.py 4 5 6 # GP_TITLE
|
||
build 4 (title) pteff00.prm t= 0 α=255 t= 16 α=0 t=261 α=0 t=269 α=255
|
||
build 5 (main menu) pteff00.prm t= 0 α=255 t= 12 α=0 t= 70 α=0 t= 80 α=255
|
||
build 6 (EXTRAS) pteff00.prm t= 0 α=255 t= 12 α=0 t= 64 α=0 t= 74 α=255
|
||
```
|
||
|
||
**Every pose is timed. There is no untimed keyframe**, and the fade-out ramp is on
|
||
the disc after all: `70 → 80` = 10 units for the main menu, `64 → 74` = 10 for
|
||
`EXTRAS`, `261 → 269` = **8** for the title.
|
||
|
||
⚠️ **And the fade-IN was mislabelled by the same shift.** This page used to say the
|
||
screen "holds black for **0.20 s**, then fades in over **0.87 s** (`EXTRAS`),
|
||
**0.97 s** (main menu) or **4.08 s** (the title)". Those spans are `T2 − T1` under
|
||
the stale pairing — the stretch where the quad sits at **α = 0**, i.e. the screen
|
||
fully visible and *not* fading at all. Read correctly the screen starts black at
|
||
`t = 0` and fades in over **12 units (0.20 s)** on the menu and `EXTRAS`, **16
|
||
units (0.27 s)** on the title. A port pacing its menu fade-in off the old number
|
||
would have run it **5× too slow**.
|
||
|
||
### ❔ The fade-OUT duration is not in this field
|
||
|
||
The fourth block has **no time** — a group's last block stops 4 bytes short and
|
||
that word is already the next group's element index
|
||
([`ui_layout.rs`](../../crates/sylpheed-formats/src/ui_layout.rs) documents the
|
||
packing). So the disc gives the ramp's *target* (black) and not its length. That
|
||
duration is **measured** below, and the port is authoring it.
|
||
|
||
### The disc-wide check, and what it shows about overlays
|
||
|
||
Over every `GP_*.pak`, counting bundles that have a `.prm` element **and** ≥8
|
||
elements (screen-sized rather than a two-element fragment):
|
||
|
||
> **40 of 97** carry a 255 → 0 → 255 quad; 56 of the 60 found disc-wide have
|
||
> exactly 4 keyframes.
|
||
|
||
41 % sounds weak until it is read per pak, where it is nearly all-or-nothing:
|
||
|
||
| pak | with quad / screen-sized |
|
||
|---|---|
|
||
| `GP_MISSION_SELECT`, `GP_MOVIE_THEATER`, `GP_OPTIONS`, `GP_TUTORIAL`, `GP_SYSTEM` | 2/2 each |
|
||
| `GP_BUNK`, `GP_CHALLENGE` | 6/6 |
|
||
| `GP_STAGE_CLEAR` | 4/4 |
|
||
| `GP_SAVE_LOAD` | 10/12 |
|
||
| **`GP_TITLE`** | **6/12** |
|
||
| `GP_DIALOG` | 0/133 |
|
||
| `GP_DEBRIEFING_PILOTLOG` | 4/102 |
|
||
|
||
**`GP_TITLE`'s 6 of 12 is the interesting row, and it is not a gap.** The six
|
||
that carry the quad are exactly the six *screen* builds — title, main menu and
|
||
`EXTRAS`, English and Japanese. The six that do not are exactly the six
|
||
**overlays**: the `PRESS Ⓐ BUTTON` plate and the two `DELTASABER` plates
|
||
([`ui-title-build-map.md`](ui-title-build-map.md)). An overlay composited onto a
|
||
screen has no transition of its own, so it has no fade quad — which is
|
||
independent corroboration that those builds are overlays rather than screens.
|
||
`GP_DIALOG`'s 0/133 says the same thing about dialog boxes.
|
||
|
||
## The timing — measured
|
||
|
||
Recorded with `ffmpeg -f x11grab -framerate 30` over the game surface, mean
|
||
frame luminance per frame; raw data in
|
||
[`captures/transitions/transition-luminance.csv`](captures/transitions/transition-luminance.csv),
|
||
filmstrip in
|
||
[`transition-filmstrip.png`](captures/transitions/transition-filmstrip.png).
|
||
|
||
| | main menu → `EXTRAS` (Ⓐ) | `EXTRAS` → main menu (Ⓑ) |
|
||
|---|---|---|
|
||
| press → first visible change | 0.07 s | 0.37 s |
|
||
| **fade-out to black** | **0.367 s** | **0.400 s** |
|
||
| **pure black** (luminance 0.02) | 0.233 s | 0.167 s |
|
||
| luminance rise until settled | 0.567 s | 1.467 s |
|
||
|
||
The fade-out is the number the file cannot give, and it comes out the same both
|
||
ways: **~0.4 s**, i.e. ~24 units under Q1's rule.
|
||
|
||
The black hold is *consistent with* the file's 12 units (0.20 s) but does not
|
||
confirm it — the plateau spans the tail of the outgoing screen's fade-out and the
|
||
head of the incoming screen's black, and this measurement cannot separate them.
|
||
|
||
### ⚠️ The luminance rise is **not** the quad's ramp
|
||
|
||
The two columns differ by 2.6× where the quad's declared ramps differ by only
|
||
1.12× (58 units vs 52). The filmstrip says why: the background reappears *first*
|
||
and the labels arrive after it, so what the luminance curve is timing is the
|
||
incoming screen's **own element animations**, not the fade quad. Quoting 1.47 s
|
||
as "the main menu's fade" would be wrong. The quad's ramp is decoded; the
|
||
screen's build-in is a separate, longer thing.
|
||
|
||
## For the port
|
||
|
||
* a screen change is: **fade the outgoing screen to black over ~0.4 s**, hold
|
||
black briefly, then **fade the incoming screen in over its own declared ramp**
|
||
while its elements play their own keyframes;
|
||
* the fade-in ramp is **read from the file** (`T1 − T0` on the screen's fade
|
||
quad);
|
||
* the ~0.4 s fade-out and the black hold are **authored from this page** — the
|
||
disc does not carry them.
|
||
|
||
## ✅ Which quantity the ~0.4 s is — measured 2026-08-29
|
||
|
||
Asked by the port: is 0.4 s **(a)** the ramp from the hold to the exit pose, i.e.
|
||
exactly the missing duration of that untimed keyframe, **(b)** several keyframes'
|
||
worth, or **(c)** something the game does independently of the group?
|
||
|
||
**It is (a)** — and it is bigger than the fade quad. Two facts.
|
||
|
||
🔴 **1. "There is exactly one untimed keyframe, and every element has it" — REFUTED
|
||
2026-08-30. It was a STALE BINARY.**
|
||
|
||
That claim came from `screen info`, and the copy of `sylpheed-cli` in this container
|
||
was built **2026-08-29 12:38**, before the keyframe-record-layout fix landed. The old
|
||
parser shifted every time by one slot and could not time a group's final pose, so it
|
||
printed a trailing `-`. Rebuilt, the same element reads:
|
||
|
||
```
|
||
stale pteff00.prm 4 kf rest t=70 [12:0,0 70:0,0 80:0,0 -:0,0]
|
||
fresh pteff00.prm 4 kf rest t=12 [ 0:0,0 12:0,0 70:0,0 80:0,0]
|
||
```
|
||
|
||
**Four timed poses. There is no untimed keyframe and no unknown duration**, so the
|
||
question this section was answering — *"is 0.4 s the missing duration of that
|
||
untimed keyframe"* — no longer has its subject. The argument below (the ratio test)
|
||
is untouched and still shows the content fading rather than a quad arriving; what is
|
||
dead is the framing around it.
|
||
|
||
✅ **And the number is now decoded.** `pteff00.prm`'s final ramp is **70 → 80 = 10
|
||
units ≈ 0.167 s**, not the ~24 units this page authored. That confirms the port
|
||
agent's reading; I tried to refute it against the bytes and could not.
|
||
|
||
🔴 **So the measured ~0.4 s is NOT the ramp alone** — 0.4 s is ~24 units against a
|
||
decoded 10. Something else occupies the other ~14 units.
|
||
|
||
## ✅ What the other ~14 units are — MEASURED 2026-08-30, and it is not a hold
|
||
|
||
This page previously guessed: *"that the remainder is exactly the black hold is
|
||
arithmetic that fits (14 units = 0.233 s), **not a measurement**"*. It has now been
|
||
measured, and **the guess was wrong**. There is no black hold inside the fade-out.
|
||
|
||
**Instrument.** [`fade_decompose.sh`](../../tools/re-capture/fade_decompose.sh)
|
||
boots to the main menu, arms the UI draw capture there, then presses Ⓑ, so one
|
||
260-frame window contains the whole screen change.
|
||
[`fade_envelope.py`](../../tools/re-capture/fade_envelope.py) reads the fade quad's
|
||
alpha per submitted frame. The quad is **identified, not guessed at**: a `.prm`
|
||
primitive carries no `tex[base=…]`, and this element paints last
|
||
([paint-order key](structures/ui-paint-order-key.md)), so it is the last
|
||
full-screen *untextured* quad of a frame. Taking merely the last full-screen quad
|
||
picks up textured backdrops and gives a different answer.
|
||
|
||
**Control.** The quad's ramp is *decoded* (10 units), so the instrument can be
|
||
checked before it is believed. At Q1's 2 units per rendered frame, 10 units is 5
|
||
frames. Measured: the quad is absent at frame 39 and `α=255` at frame 43 — **4
|
||
submitted-frame steps**, with one unlogged frame inside the span. Agreement to
|
||
within that one frame. An instrument that could not reproduce the decoded ramp
|
||
could not be trusted on the undecoded remainder.
|
||
|
||
**The measurement** ([`data/fade-envelope-menu-to-title.txt`](data/fade-envelope-menu-to-title.txt)):
|
||
|
||
```
|
||
frame 34 content elements begin fading (a 255 quad appears and decays)
|
||
35..41 255 223 207 175 95 31 15 the content fade
|
||
frame 40 the BLACK QUAD first appears, α=102
|
||
41 α=127
|
||
43 α=255 fully black
|
||
45 last frame the menu draws
|
||
frame 46 6 draws (vs 12) — ONE frame of black
|
||
47+ the title's build starts
|
||
```
|
||
|
||
✅ **The ~14 extra units are the content elements' own fade-outs, which start six
|
||
frames BEFORE the quad's ramp.** The blackout runs frame 34 → 43 = **9 submitted
|
||
frames ≈ 0.30 s at 30 Hz**, of which the quad's ramp is the last 4. That is the
|
||
same quantity the filmstrip measured as 0.367–0.400 s with coarser timing.
|
||
|
||
🔴 **"and overlap it" was WRONG, and is withdrawn (same day).** `sylpheed-port`
|
||
read the lead off the disc independently — content fade-outs start at `ptmsg` **58**
|
||
and `pteff10`/`pteff12`/`ptbtn05` **60**, against the quad's ramp at **70** — which
|
||
reproduces the six-frame lead exactly (12 units = 6 frames), but has content
|
||
*finishing* at 68, two units **before** the quad starts. So I checked which draws I
|
||
had actually been watching, and they were right:
|
||
|
||
```
|
||
frame TEXTURED sprite alphas untextured
|
||
34 [144,148,169,191,254,255] [64, 255, 64]
|
||
36 [ 42,119,145,149,159,255] [64, 207, 64]
|
||
37 [ 71, 95,145,149,191,255] [64, 175, 64]
|
||
39 [146,150,255] <- fading ones GONE
|
||
40 [146,151,255] [64, 31, 64, 102] <- black quad appears
|
||
```
|
||
|
||
The **content sprites are textured**, they finish at frame 39, and the quad appears
|
||
at 40 — **a one-frame gap, which is the port's two units.** What overlaps the quad
|
||
is a *different* thing: a full-screen **untextured** quad decaying 255→…→15 across
|
||
frames 34–41, still at α=31 and α=15 while the quad ramps. ⚠️ That element is
|
||
**unidentified**: build 5 declares only two primitives, `pteff00.prm` and a
|
||
single-keyframe `pteff02.prm`, and neither is a 255→15 decay. It is not accounted
|
||
for by the outgoing screen's own primitive list, and I am not going to name it from
|
||
one capture.
|
||
|
||
So the correct shape is **sequence, not overlap**: content fades out, finishes, and
|
||
one frame later the black quad ramps.
|
||
|
||
✅ **The inter-screen black is ONE frame** (46), not ~14 units. The draw count
|
||
collapses from 12 to 6 for exactly one frame and the incoming build starts at 47.
|
||
|
||
📌 **For the port:** a transition is *not* "ramp the black quad for 10 units, then
|
||
hold black for 14". It is "start the content elements fading, and 6 frames later
|
||
ramp the black quad over its declared 10 units on top of them". Authoring it as a
|
||
hold puts a sixth of a second of dead black in the middle of every screen change
|
||
that the game does not have.
|
||
|
||
## ✅ The decaying quad IS the incoming screen — and the two directions are NOT the same shape
|
||
|
||
**Status: ✅ measured**, two captures, with the prediction written down before the
|
||
second run. Data: [`data/fade-four-transitions.txt`](data/fade-four-transitions.txt).
|
||
|
||
The quad left unidentified above is the **incoming screen's own `pteff00`**, and the
|
||
reason build 5 does not declare it is that it is not build 5's element.
|
||
|
||
**The tell is that TWO quads arrive together.** A screen contributes both a
|
||
`pteff00` (255 at `t=0`, decaying) *and* a `pteff02` (64). On the settled menu the
|
||
untextured set is `[64]`; at frame 34 it becomes `[64, 255, 64]` — a 255 **and** a
|
||
second 64, which is exactly build 4's opening pair and cannot be explained by any
|
||
single element.
|
||
|
||
**The discriminator, predicted in advance.** A fit is not a measurement, and 8
|
||
frames matching build 4's declared 16 units is a fit. So: a transition whose
|
||
incoming screen declares something *else*. `title → menu` brings in build 5, whose
|
||
opening is `0→12` = 12 units = **6 frames** against build 4's 8.
|
||
|
||
| transition | incoming build | declared open | measured decay |
|
||
|---|---|---|---|
|
||
| menu → title (Ⓑ) | 4 | 16 units = 8 frames | **8** (frames 34–41) |
|
||
| title → menu (Ⓐ) | 5 | 12 units = 6 frames | **5** (frames 73–77) |
|
||
|
||
Different incoming screen, different decay length, in the predicted direction. That
|
||
kills "a fixed transition effect". ⚠️ The second came out **5 where 6 was
|
||
predicted** — one frame short, inside this capture's documented ±1 — so the
|
||
*direction* is measured and the *duration* agrees to a frame, which is as far as
|
||
one run reaches.
|
||
|
||
### ✅ And the fade-out ramp is exactly LINEAR — the alpha puzzle was a composite
|
||
|
||
The rise in the first capture (102, 127, 255) did not sit on a line, and this page
|
||
flagged that as unexplained. In the second capture the outgoing title's quad ramps
|
||
with **no other untextured quad present**:
|
||
|
||
```
|
||
frame 67 68 69 70
|
||
alpha 63 127 191 255 steps of exactly 64
|
||
```
|
||
|
||
Four frames, against build 4's declared fade-out `261→269` = 8 units = **4 frames**.
|
||
Exact, and exactly linear. The first capture's curve was the **composite of two
|
||
overlapping quads**, not a non-linear ramp — which is what `sylpheed-port` proposed
|
||
when they saw the numbers, and it is right.
|
||
|
||
### 🔴 The two directions have different structures, and this is the part to author
|
||
|
||
* **`title → menu` (Ⓐ) is SEQUENTIAL.** Outgoing content fades (58–67); the
|
||
outgoing quad ramps to black (67–70); frames 70–72 draw almost nothing (6 draws,
|
||
2 textured); the incoming screen's elements appear at **73**. There is a real
|
||
black interval — **fully black from frame 70 until the incoming quad first drops
|
||
below 255 at frame 75, i.e. 5 frames ≈ 10 units.**
|
||
* **`menu → title` (Ⓑ) is a CROSS-FADE.** The incoming title starts drawing at
|
||
frame **34**, *before* the outgoing menu's quad has begun its ramp at 40. Both
|
||
screens draw together for ~6 frames. There is no black interval at all: the
|
||
near-empty frame is a single one (46). ⚠️ **This is the outlier of three** — see
|
||
the EXTRAS test below. It is not a property of Ⓑ.
|
||
|
||
### 🔴 A third transition REFUTES "Ⓑ has no black interval" — it was one screen pair
|
||
|
||
`sylpheed-port` asked for exactly this test and declined to act on the asymmetry
|
||
without it, on the grounds that one transition is not a rule. **They were right not
|
||
to.** `EXTRAS → main menu`, also via Ⓑ:
|
||
|
||
```
|
||
frame untextured full-screen draws tex
|
||
33 [64] 9 7 EXTRAS settled
|
||
34 [64, 51] 8 4 outgoing quad starts
|
||
38 [64, 255] 8 4 fully black
|
||
39 [] 3 0 <- EMPTY
|
||
40 [] 3 0 <- EMPTY
|
||
41 [64, 169] 7 3 incoming menu's quad, decaying
|
||
45 [64, 21] 7 3
|
||
46 [64] 7 3 clear
|
||
```
|
||
|
||
**Two completely empty frames** — 3 draws, *zero* textured. That is a harder black
|
||
than either earlier capture showed. So Ⓑ does not imply a cross-fade; `menu →
|
||
title` is the outlier, and the thing I was one step from writing up as "Ⓑ has no
|
||
black" is **false**.
|
||
|
||
⚠️ The screen was **verified, not assumed**: `screen_id.py` cannot tell EXTRAS from
|
||
the main menu (both are dark blue `GP_TITLE` screens), so the armed frame was
|
||
checked with [`which_title_screen.py`](../../tools/re-capture/which_title_screen.py)
|
||
— `extras` 18.58 against `main_menu` 29.85, margin 11.27, inside the 9.9–11.7 band
|
||
its control establishes on four known captures.
|
||
|
||
### ✅ What three transitions agree on
|
||
|
||
| transition | outgoing ramp | declared | black gap | incoming decay | declared |
|
||
|---|---|---|---|---|---|
|
||
| menu → title (Ⓑ) | frames 40–43 | 10 u = 5 f | **none** | 34–41 = **8 f** | 16 u = 8 f |
|
||
| title → menu (Ⓐ) | 67–70 = **4 f** | 8 u = 4 f | 3 f | 73–77 = **5 f** | 12 u = 6 f |
|
||
| EXTRAS → menu (Ⓑ) | 34–38 = **5 f** | 10 u = 5 f | **2 f** | 41–45 = **5 f** | 12 u = 6 f |
|
||
|
||
✅ **The outgoing ramp is the declared final ramp — three for three**, against three
|
||
different declared values, and exactly linear where it is not composited (capture 2
|
||
steps of 64; capture 3 steps of ~51 = 255/5).
|
||
|
||
🟡 **The incoming decay is exact for build 4 (8 = 8) and one frame short for build 5,
|
||
twice** (5 against 6, in two independent runs — so it is reproducible, not noise).
|
||
Capture 3's *rate* settles what the count cannot: steps of −21, −42, −43, −42, i.e.
|
||
**255/6 per frame after a half-step start**, which is the declared 12 units exactly.
|
||
So build 5's opening ramp is confirmed at 12 units and the frame *count* is a phase
|
||
offset. ⚠️ Capture 2's decay (255, 127, 84, 63 → steps −64, −43, −21) does **not**
|
||
fit that, and I cannot explain it.
|
||
|
||
🔴 **The black gap is the quantity with no rule yet**: none, 3 frames, 2 frames
|
||
across three transitions. It is not a per-button property and not a constant.
|
||
|
||
## ✅ The black gap is NOT a load — measured 2026-08-30 (the port's ask #2)
|
||
|
||
**Status: ✅ measured.** Three legs, one of them a repeat run that carries its own
|
||
internal control. Data:
|
||
[`data/fade-four-transitions.txt`](data/fade-four-transitions.txt).
|
||
|
||
### Leg 1 — bundle size runs the wrong way
|
||
|
||
If the gap were the cost of bringing the incoming bundle in, the biggest bundle
|
||
would have the longest gap. It has **none**:
|
||
|
||
| incoming build | bytes | black gap |
|
||
|---|---|---|
|
||
| 4 (title) | **12 278 666** | **0 frames** |
|
||
| 5 (menu) | 6 977 437 | 3 frames |
|
||
| 5 (menu) | 6 977 437 | 2 frames |
|
||
|
||
The 12.3 MB screen arrives with no gap at all while the 7.0 MB one gaps twice.
|
||
|
||
### Leg 2 — the same transition, twice, and the gap does not move
|
||
|
||
`title → menu` via Ⓐ, run twice from cold:
|
||
|
||
| quantity | run 1 | run 2 |
|
||
|---|---|---|
|
||
| outgoing quad rise | 67–70: 63, 127, 191, 255 | 64–67: **63, 127, 191, 255** |
|
||
| frames fully black | 70, 71, 72 = **3** | 67, 68, 69 = **3** |
|
||
| incoming decay | 255, –, 127, 84, 63 | 255, 169, 127, 84, 42 |
|
||
| **press → first change** | ~25 frames | **~10 frames** |
|
||
|
||
✅ **The gap is 3 frames in both, and the outgoing ramp is byte-identical.**
|
||
|
||
### Leg 3 — and the runs are *not* a null comparison
|
||
|
||
The obvious objection to leg 2 is that two runs under the same conditions prove
|
||
nothing. **The captures refute that themselves**: the press-to-first-change latency
|
||
differs by **~12 frames** between them (~25 against ~10). Conditions demonstrably
|
||
were not identical — something in this transition *is* I/O- or cache-sensitive and
|
||
moved by 0.4 s — and the black gap did not move at all. That is the control the
|
||
comparison needs, and it comes from inside the measurement rather than from an
|
||
assumption about the machine.
|
||
|
||
### What this establishes, and what it does not
|
||
|
||
* ✅ **Not a load.** It does not scale with bundle size, and it does not move when
|
||
the transition's own latency moves by 12 frames.
|
||
* ✅ **Deterministic**, to the frame, across runs.
|
||
* ❔ **What it *is* remains open.** It is not in the fade quad's keyframes — the
|
||
port reports 866 keyframes across 16 screens with **0 untimed**, so there is no
|
||
hidden duration left in that group — and it is not constant across transitions
|
||
(0, 3, 2 frames). So: a deterministic quantity, not from the fade group, with no
|
||
rule yet. The port should keep `black_hold_units` at **0** rather than author a
|
||
constant; "not a load" removes the excuse for a machine-dependent number without
|
||
supplying a game-dependent one.
|
||
|
||
⚠️ **Reach:** two runs of one transition plus single runs of two others, all in
|
||
Xenia, all `GP_TITLE`. "Not a load" is measured against *this* emulator's variance;
|
||
a real console could differ, and nothing here reaches the other nine transitions.
|
||
|
||
🔴 **This section used to propose that the black interval was a LOAD.** That is
|
||
now **refuted** — see the three legs above. The ~25-frame press-to-change latency
|
||
is real and *is* variable (it measured ~10 in a second run), but it is a different
|
||
quantity from the gap, and the gap does not move with it.
|
||
|
||
### ⚠️ What this does to "sequence, not overlap"
|
||
|
||
Last iteration I withdrew an overlap claim and wrote "the shape is sequence, not
|
||
overlap". **Both halves of that were partly wrong, in opposite directions.** What
|
||
is true:
|
||
|
||
* the **outgoing** screen's content finishes one frame before its own quad starts —
|
||
the port's 2-unit gap, confirmed;
|
||
* the **incoming** screen genuinely overlaps all of it, on Ⓑ — so "cross-fade" was
|
||
right about the screens and wrong about which elements;
|
||
* and on Ⓐ neither overlap happens.
|
||
|
||
The lesson is not about either reading: it is that **one transition was being
|
||
generalised to "a transition"**, and the two directions in this archive do not
|
||
behave alike.
|
||
|
||
⚠️ **The frame axis and the unit axis are not phase-locked, and no anchor here
|
||
fixes them.** Aligning the capture's frames to the file's units two different ways
|
||
— content-start ↔ t=58, or ramp-start ↔ t=70 — differs by **two frames**, and
|
||
nothing in this run distinguishes them. The *durations* are robust (6-frame lead,
|
||
3–4-frame ramp, 1-frame gap, 1-frame inter-screen black); the absolute alignment is
|
||
not. Any total quoted as "N units" from this capture inherits that ±2 frames, so a
|
||
model total agreeing with a measured total to within one unit is **agreement at one
|
||
alignment**, not a confirmation. The quad's own alphas are a second reason for
|
||
caution: 102 at frame 40, 127 at 41, 255 at 43 do not sit on a linear 0→255 across
|
||
`t=70→80`, and that is unexplained.
|
||
|
||
⚠️ **Reach.** One transition (main menu → title, via Ⓑ), one run. The frame axis
|
||
has gaps — 232 `--- frame` headers over frames 3…260, so ~10 % of submitted frames
|
||
carry no UI draw — which is ±1 frame on any span quoted here and is why the ramp is
|
||
given as 4 steps rather than a duration to three digits. Whether the six-frame lead
|
||
is constant across screens, or is a property of *these* elements' keyframes, is
|
||
**not** measured.
|
||
|
||
| elements | final untimed block | what it does |
|
||
|---|---|---|
|
||
| `pteff00.prm` (the fade quad) | `a = 255` | goes **black** |
|
||
| `pteff10`, `pteff12`, `ptbtn01…05`, `ptmsg` | `a = 0` | **fade out** |
|
||
| `ptframe1`, `ptframe2` | `a = 255` | hold, and get covered |
|
||
| `ptbase`, `pteff05`, `ptloop*`, `pteff02.prm` | single keyframe | hold |
|
||
|
||
**2. The capture shows the content fading, not just a black quad arriving.** This
|
||
has a null hypothesis that discriminates: under (c) — the game blackens the frame
|
||
independently — every region is scaled by the same `1 − α`, so the **ratio**
|
||
between a button region and a background region is *constant* through the
|
||
fade-out. Under (a) it must fall, because the buttons ramp to `a = 0` while the
|
||
background elements hold at 255 and are only dimmed.
|
||
|
||
Measured on [`transition-filmstrip.png`](captures/transitions/transition-filmstrip.png),
|
||
button column ÷ upper-right background art, frame by frame through the fade-out:
|
||
|
||
```
|
||
frame 0 1 2 3 4 5
|
||
ratio 6.495 5.574 3.105 2.125 1.935 (black)
|
||
```
|
||
|
||
**A 3.4× monotonic fall.** Constant is refuted. The buttons really are fading
|
||
independently of the overall dim, exactly as their declared final block says.
|
||
(The incoming screen runs it in reverse, 2.22 → 3.47 over frames 7–12.)
|
||
|
||
⚠️ **Reach.** The filmstrip is downsampled and the "button" region unavoidably
|
||
contains some background, so the ratio is a direction, not a clean alpha
|
||
measurement. It refutes the constant-ratio null decisively; it does not by itself
|
||
pin the 0.4 s to ±0.05 s. And it is measured on **one** transition pair.
|
||
|
||
### For the port
|
||
|
||
Write **one** authored constant — the duration of the final untimed keyframe,
|
||
~0.4 s / ~24 units — and **play the group to its end on every element**. Do not
|
||
model the exit as a black rectangle fading over a frozen screen: the buttons and
|
||
labels ramp to transparent at the same time, and that difference is visible.
|
||
|