1 SPLASH ADDRESSING (was blocking P3). No content predicate exists: design size fails (every extra composable bundle sampled is 1280x720, like every screen) and element count fails (fragments run 2..15, the splash halves are 3 and 7). But GP_TITLE needs none -- `--all` adds exactly four bundles there and all four are real screens, with the --all index equal to the pak entry index 1:1. And there are TWO splash screens: 11/14 are the developer logos, 10/13 are the SQUARE ENIX publisher wordmark, which the port did not have and which the boot shows first. 2 FADE-OUT (was blocking P3). It is (a), and it is bigger than the fade quad. Every element ends on exactly ONE untimed keyframe, which rules out (b); that block is where the screen plays out -- quad to a=255, buttons/labels/glows to a=0, frames hold. (c) is refuted by a null test that discriminates: a black quad alone holds the button/background brightness ratio constant, and through the fade it falls 6.50 -> 1.94, 3.4x monotonic. 3 FOCUS (saves P5 rework). Over-vs-instead is unobservable -- the focused sprite covers the base at 100.0% of base-visible pixels on three pairs once aligned (true offset (7,7); the centre alignment reads a misleading 78-84%), and compositing both ways differs by RMSE 1.1 inside the button rect. The real defect is the focus record's SECOND element: ptbtn0Nf.rat declares ptbtneff01.t32 (a 42x46 glowing ring, focus only) plus the bright label, where the base declares one sprite. That ring is the marker the port draws nowhere. 5 GAMMA. The capture is not neutral: capture ~ 255*(render/255)^g, g ~ 1.34-1.49, and the chain says it is a ramp the GAME installed, not a capture artefact. So RMSE against captures has a floor. Reach stated: the flat patches are all dark (render ~0-60), so midtones and highlights are unconstrained. 4 ROTATION is a human's call and is recorded in MISSION, not acted on -- the port rotating while the reference renderer does not would make verify-screen report a large diff meaning "the port is right". The RE half is answered: rotation is about the declared pivot, measured against a GPU capture. The focus record's +20 element-count word is marked 🟡 not ✅ -- read on GP_TITLE's ten button records only; the disc-wide check is written and still running.
171 lines
8.0 KiB
Markdown
171 lines
8.0 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)).
|
||
|
||
```
|
||
$ tools/re-capture/fade_quads.py 4 5 6 # GP_TITLE
|
||
build 4 (title) pteff00.prm t=16 α=255 t=261 α=0 t=269 α=0 t=— α=255
|
||
build 5 (main menu) pteff00.prm t=12 α=255 t= 70 α=0 t= 80 α=0 t=— α=255
|
||
build 6 (EXTRAS) pteff00.prm t=12 α=255 t= 64 α=0 t= 74 α=0 t=— α=255
|
||
```
|
||
|
||
Under [Q1](ui-keyframe-time-unit.md)'s `1 unit = 1/60 s`: 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).
|
||
|
||
### ❔ 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.** Reading
|
||
`screen info --build 5 --geometry` for the main menu, all 16 elements end on a
|
||
single timeless block; none has two. So there is one unknown duration per screen,
|
||
not a chain of them — which rules out (b) outright. And that final block is not
|
||
idle: it is where the screen *plays out*.
|
||
|
||
| 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.
|
||
|