re(ui): answer four of the port's five asks -- splash, fade-out, focus, gamma
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.
This commit is contained in:
@@ -41,6 +41,76 @@ authored version can be deleted.
|
||||
|
||||
## Already settled — the port can rely on these today
|
||||
|
||||
### ⬅ Answers to the port's five asks (2026-08-29)
|
||||
|
||||
* **1 — how to recognise the splash.** ❔ **No content rule exists; you are
|
||||
authoring this.** Design size fails (every extra composable bundle sampled is
|
||||
1280×720, same as every screen) and element count fails (the fragments run
|
||||
2…15 elements, the splash halves have 3 and 7 — the ranges overlap).
|
||||
✅ **But `GP_TITLE` needs no rule.** There, `--all` adds exactly **four**
|
||||
bundles and all four are real screens — no fragments at all — and the `--all`
|
||||
index equals the pak **entry** index 1:1 across all 16, so addressing by entry
|
||||
index does not mean something different from elsewhere.
|
||||
🔴 **And there are TWO splash screens; you have one.** Entries **11/14** are the
|
||||
developer logos (GAME ARTS / SETA / studio anima). Entries **10/13** are the
|
||||
**SQUARE ENIX publisher** wordmark — the *first* thing the boot shows — and you
|
||||
do not have them. The pairs are region twins (`™` on 10, `®` on 13). All four
|
||||
draw every element they declare.
|
||||
[`ui-splash-addressing.md`](../re/ui-splash-addressing.md) ·
|
||||
[render grid](../re/captures/title-builds/splash-both-halves-rendered.png)
|
||||
|
||||
* **2 — the ~0.4 s fade-out is (a)**, and it is bigger than the fade quad.
|
||||
Every element of a screen ends on **exactly one** untimed keyframe — so there
|
||||
is one unknown duration per screen, which rules out (b). That final block is
|
||||
where the screen plays out: the quad goes `a=255` (black) while the buttons,
|
||||
`ptmsg` and the glows go `a=0` and the two frames hold. (c) is refuted by a
|
||||
null test on the capture: a black quad alone keeps the button÷background
|
||||
brightness **ratio constant**, and measured through the fade it falls
|
||||
**6.50 → 1.94, a 3.4× monotonic drop**. So: write one authored constant
|
||||
(~0.4 s / ~24 units) and **play the group to its end on every element** — do
|
||||
not fade a black rectangle over a frozen screen.
|
||||
[`screen-transitions.md`](../re/screen-transitions.md)
|
||||
|
||||
* **3 — focus: your choice is fine, and it is not your bug.** ✅ The focused
|
||||
sprite **completely covers** the base — `f` alpha ≥ base alpha at **100.0 %** of
|
||||
base-visible pixels on three pairs across both languages, once aligned properly
|
||||
(the true offset is **(7,7)**, and at the centre alignment it reads a
|
||||
misleading 78–84 %). Compositing both ways differs by **RMSE 1.1 inside the
|
||||
button rectangle**, max 12/255 on ~25 px — unmeasurable at frame level.
|
||||
🔴 **What you are actually missing is the focus record's SECOND element.**
|
||||
`ptbtn0Nf.rat` declares **two** sprites — `ptbtneff01.t32` (a 42×46 **glowing
|
||||
ring**, focus only) then `ptbtn0Nf.t32` (the bright label) — where the base
|
||||
record declares one. That ring is the marker you say you draw nowhere. ⚠️ Note
|
||||
the small dot-in-circle at each underline's left end is *not* it: that is on
|
||||
every button all the time, part of the base art.
|
||||
[`structures/ui-button-focus-record.md`](../re/structures/ui-button-focus-record.md)
|
||||
|
||||
* **4 — rotation: not mine to decide alone.** Raised with the human; see
|
||||
MISSION. What I can say without a decision: the two sub-questions are not
|
||||
equally open. Rotation is about the **declared pivot** — that anchor is
|
||||
*measured*, not assumed: the title's two `ptloop` sweeps scale 600 %/800 %
|
||||
vertically, where the pivot term is worth 450 and 630 px, and the GPU capture
|
||||
puts both quad centres at y **359.1**/**360.0** against the pivot formula's
|
||||
**360.0**; top-left anchoring predicts 810/990 and centre-as-position 270. So
|
||||
if you draw rotation, rotate about the declared pivot.
|
||||
⚠️ It changes nothing on your five screens **at rest** — they have zero
|
||||
top-level rotations, and the title's two nested ones sit entirely off-screen at
|
||||
rest. [`structures/ui-keyframe-rotation.md`](../re/structures/ui-keyframe-rotation.md)
|
||||
|
||||
* **5 — the capture is not gamma-neutral, and RMSE against it has a floor.**
|
||||
Measured on flat patches (16×16, both images `std < 8`):
|
||||
`capture ≈ 255·(render/255)^γ` with **γ ≈ 1.49** (main menu), **1.49**
|
||||
(`EXTRAS`), **1.34** (title). The chain says this is a ramp **the game
|
||||
installed**, not a capture-path artefact: canary's swap-path gamma stage is a
|
||||
pure 256-entry LUT that defaults to identity, and the game is measured calling
|
||||
`VdGetCurrentDisplayGamma` once at video init. ⚠️ **Reach: the flat patches are
|
||||
almost all dark (render ~0–60), so nothing here constrains midtones or
|
||||
highlights** — which is where γ 1.4 does its visible work. So: yes, there is a
|
||||
floor; a γ ≈ 1.4 darkening gets closer and is **authored**, best applied where
|
||||
it was measured rather than extrapolated. Do not chase RMSE below it.
|
||||
[`structures/ui-render-tone-curve.md`](../re/structures/ui-render-tone-curve.md)
|
||||
|
||||
|
||||
* **`GP_TITLE.pak` is eight screens, each shipped twice — English and Japanese.**
|
||||
Build 4 is the English title art and 7 its Japanese twin; **2/3 are the
|
||||
`PRESS Ⓐ BUTTON` plate, a build of their own** composited over the title and
|
||||
|
||||
@@ -208,6 +208,28 @@ settled and only multi-keyframe absolute timing is open; `rest()` differs from
|
||||
its alternative on **one** element across all five screens, and the current
|
||||
answer there is the defensible one.
|
||||
|
||||
## 🔵 Needs a human decision — rotation (raised 2026-08-29)
|
||||
|
||||
The port agent asks whether it should **render** `rotation_deg` (decoded at
|
||||
keyframe `+12`) when `sylpheed-cli screen render` deliberately does not. Its own
|
||||
framing is the reason this is not mine to settle: if the port rotates and the
|
||||
reference renderer does not, then `verify-screen` reports a large title diff that
|
||||
means *"the port is right"* — a silently inverted signal.
|
||||
|
||||
The RE half is answered and is in HANDOFF: rotation is about the **declared
|
||||
pivot** (measured against a GPU capture, not assumed), and it changes nothing on
|
||||
the five screens **at rest**.
|
||||
|
||||
What needs a decision is which way the divergence gets closed:
|
||||
|
||||
* teach `ui_layout::blit` a rotating path, so the two renderers stay comparable
|
||||
and the diff keeps meaning "someone is wrong" — costs work in the reference
|
||||
renderer, which is otherwise not on the port's critical path; or
|
||||
* let the port render rotation and mark the title as a known-divergent screen in
|
||||
`verify-screen`, accepting a check that no longer guards the title.
|
||||
|
||||
Recorded rather than chosen, per "do not improvise around a blocker".
|
||||
|
||||
## Known unknowns — say so, do not fill them in
|
||||
|
||||
Some of these may turn out to be undecodable. That is a valid, useful answer, and
|
||||
|
||||
BIN
docs/re/captures/title-builds/splash-both-halves-rendered.png
Normal file
BIN
docs/re/captures/title-builds/splash-both-halves-rendered.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 64 KiB |
BIN
docs/re/captures/ui-layout/button-base-focus-ring-sprites.png
Normal file
BIN
docs/re/captures/ui-layout/button-base-focus-ring-sprites.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 23 KiB |
@@ -115,3 +115,56 @@ screen's build-in is a separate, longer thing.
|
||||
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.
|
||||
|
||||
|
||||
111
docs/re/structures/ui-button-focus-record.md
Normal file
111
docs/re/structures/ui-button-focus-record.md
Normal file
@@ -0,0 +1,111 @@
|
||||
# ✅ A focused button is a **two-element record**, and the second element is the ring
|
||||
|
||||
**Status:** 🟡 `DECODED for GP_TITLE`, disc-wide check **not yet run**; the
|
||||
sprite contents and the ring are ✅ `MEASURED` against a live capture. Answers the
|
||||
port's *"focus: drawn over the base element, or instead of it?"*
|
||||
|
||||
⚠️ The element-count field at `+20` is read here on **`GP_TITLE`'s ten button
|
||||
records only** (five buttons × two language bundles). A disc-wide check —
|
||||
"does `+20` equal the number of resource names the leaf embeds, for every `.rat`
|
||||
leaf on the disc?" — is written and was still running when this was committed.
|
||||
Until it lands, treat `+20` as a **strong local reading, not a decoded field**:
|
||||
the *observable* claim below (the focus record carries a second sprite,
|
||||
`ptbtneff01.t32`, and the port must draw it) rests on the embedded names and the
|
||||
capture, not on that word.
|
||||
|
||||
**Short answer: it does not matter, and that is not the bug.** The focused sprite
|
||||
covers the base completely, so over-vs-instead is worth at most 12/255 on ~25
|
||||
pixels. What *is* missing is the focus record's **second element** — a glowing
|
||||
ring that only exists when focused.
|
||||
|
||||
## The record
|
||||
|
||||
A button's base and focused variants are both `.rat` leaves, and they are not the
|
||||
same shape:
|
||||
|
||||
```text
|
||||
ptbtn02.rat 164 B strings: RATC, ptbtn02.t32, opt ptbtn02f.rat
|
||||
ptbtn02f.rat 312 B strings: RATC, ptbtneff01.t32, ptbtn02f.t32, opt ptbtn02b.rat
|
||||
```
|
||||
|
||||
The header says so explicitly. A `.rat` leaf begins:
|
||||
|
||||
```text
|
||||
+0 "RATC"
|
||||
+16 u32 flags 0x00008110 (base) 0x00008112 (focused)
|
||||
+20 u32 ELEMENT COUNT 1 2
|
||||
+32 first element name
|
||||
```
|
||||
|
||||
So the focused record declares **two** elements — `ptbtneff01.t32` first, then
|
||||
`ptbtn0Nf.t32` — where the base declares one. Both `f` records of both language
|
||||
bundles read the same way, and the flag word differs only in bit `0x02`
|
||||
alongside the count.
|
||||
|
||||
⚠️ The `opt ` link on a `.rat` leaf is **not** simply "my focused variant". The
|
||||
chain runs `ptbtn01.rat → ptbtn01f.rat → ptbtn02.rat → ptbtn02f.rat →
|
||||
ptbtn02b.rat`, i.e. it threads base and focused records together in order. The
|
||||
corpus calls it the focus link and for a *base* record that reading works; do not
|
||||
generalise it to the `f` records.
|
||||
|
||||
## The three sprites
|
||||
|
||||
Decoded with `sylpheed-cli pak textures`, shown over a checkerboard in
|
||||
[`button-base-focus-ring-sprites.png`](../captures/ui-layout/button-base-focus-ring-sprites.png):
|
||||
|
||||
| sprite | size | what it is |
|
||||
|---|---|---|
|
||||
| `ptbtn02.t32` | 117×43 | the **dim** label, its underline, and a small dot-in-circle at the underline's left end |
|
||||
| `ptbtn02f.t32` | 130×56 | the **same label, bright and glowing** — a complete replacement, 13 px larger in each axis |
|
||||
| `ptbtneff01.t32` | 42×46 | a **glowing ring**, focus only |
|
||||
|
||||
The small dot-in-circle is on **every** button all the time; the large ring is
|
||||
the focus marker. They are different things and sit side by side on the focused
|
||||
row — visible in
|
||||
[`live-main-menu-options-focused.png`](../captures/title-builds/live-main-menu-options-focused.png)
|
||||
against
|
||||
[`live-main-menu.png`](../captures/title-builds/live-main-menu.png).
|
||||
|
||||
## Over or instead? Measured, and the answer is "unobservable"
|
||||
|
||||
Aligning base and focused by normalised cross-correlation of their alpha masks —
|
||||
the true offset is **(7,7)**, not the (6,6) that centring predicts —
|
||||
|
||||
| pair | base-visible px | `f` alpha ≥ base alpha there |
|
||||
|---|---|---|
|
||||
| `ptbtn02` / `f` (bundle `b58a0fe6`) | 1 444 | **100.0 %** |
|
||||
| `ptbtn01` / `f` (bundle `b58a0fe6`) | 1 898 | **100.0 %** |
|
||||
| `ptbtn02` / `f` (bundle `a715f485`) | 3 720 | **100.0 %** |
|
||||
|
||||
⚠️ **At the centre alignment it reads 78–84 %, and that number is an artefact.**
|
||||
A 1 px shift on strokes this thin manufactures a fifth of a sprite's worth of
|
||||
"the focused art is thinner here". Solve the alignment before trusting a coverage
|
||||
figure.
|
||||
|
||||
Coverage is not the same as hiding, though, because `f` is not fully opaque
|
||||
everywhere. Compositing both ways over the menu's own background colour:
|
||||
|
||||
| pair | max channel difference | px > 8/255 | RMSE over the button rect |
|
||||
|---|---|---|---|
|
||||
| `ptbtn02` | 12.5 | 23 | 1.09 |
|
||||
| `ptbtn01` | 12.2 | 27 | 1.08 |
|
||||
| `ptbtn02` (JP) | 12.2 | 27 | 1.05 |
|
||||
|
||||
So the two hypotheses differ by **~1.1 RMSE inside the button rectangle**, on a
|
||||
couple of dozen pixels — below the ≈ γ 1.4 tone gap
|
||||
([tone curve](ui-render-tone-curve.md)) and far below any frame-level RMSE this
|
||||
corpus can resolve. **Either choice is defensible; neither is measurable.**
|
||||
Replacing is the cheaper one and is what the file's structure suggests, since
|
||||
`ptbtn0Nf.t32` is a whole label rather than an overlay.
|
||||
|
||||
## ❔ Not established
|
||||
|
||||
* **Where `ptbtneff01.t32` is placed.** The record declares two elements; the
|
||||
per-element placement inside a `.rat` leaf was not decoded here. The capture
|
||||
shows the ring **left of** the label, roughly on the underline's left end.
|
||||
A port should take the offset from the record, and until it is decoded, from
|
||||
the capture.
|
||||
* **`ptbtn02b.t32`** — a third variant, `b`, exists for button 02 only, same size
|
||||
as the base. Not seen on any capture. Not chased.
|
||||
* Whether a **non-title** archive uses the same two-element convention. Checked
|
||||
on `GP_TITLE` only.
|
||||
70
docs/re/ui-splash-addressing.md
Normal file
70
docs/re/ui-splash-addressing.md
Normal file
@@ -0,0 +1,70 @@
|
||||
# ❔ How to address the developer-logo splash — no content rule, but `GP_TITLE` needs none
|
||||
|
||||
**Status:** ❔ **undecodable as a general predicate, with reach** — and ✅
|
||||
**exact for `GP_TITLE`**, which is the only archive in scope. Answers the port's
|
||||
*"I need a predicate, not an index."*
|
||||
|
||||
## The ask
|
||||
|
||||
The splash has no `.rat` layout child, so `is_build` rejects it and it is
|
||||
reachable only through `--all` / `is_composable`, which disc-wide also admits
|
||||
~1 786 non-screen bundles. The port wanted a rule that admits the splash and not
|
||||
those.
|
||||
|
||||
## ✅ In `GP_TITLE` the problem does not arise
|
||||
|
||||
```
|
||||
$ sylpheed-cli screen list GP_TITLE.pak 12 builds — entries 0-9, 12, 15
|
||||
$ sylpheed-cli screen list --all GP_TITLE.pak 16 bundles — entries 0-15
|
||||
```
|
||||
|
||||
`--all` adds exactly **four** bundles: entries **10, 11, 13, 14**. All four are
|
||||
real screens, and **not one fragment appears**. So within this archive
|
||||
`is_composable` *is* the predicate — it is exact, with no filtering needed.
|
||||
|
||||
⚠️ And the renumbering worry is moot here: in `GP_TITLE` the `--all` index equals
|
||||
the pak entry index **1:1 across all 16**. That is an accident of this archive
|
||||
having 16 composable bundles at entries 0…15, not a general property — but it
|
||||
means the port can address these by entry index without the index meaning
|
||||
something different from elsewhere.
|
||||
|
||||
## ✅ And there are TWO splash screens, not one
|
||||
|
||||
Rendered with `screen render --all --build <n> --primitives`
|
||||
([grid](captures/title-builds/splash-both-halves-rendered.png)):
|
||||
|
||||
| entry | elements | what it draws |
|
||||
|---|---|---|
|
||||
| **10** | 3 | **SQUARE ENIX** publisher wordmark, `™` |
|
||||
| **11** | 7 | **GAME ARTS** / **SETA** / **studio anima** developer logos |
|
||||
| **13** | 3 | SQUARE ENIX, `®` — the twin of 10 |
|
||||
| **14** | 7 | the twin of 11 |
|
||||
|
||||
⚠️ **The port has 11/14 and is missing 10/13** — the publisher half, which is the
|
||||
*first* thing the boot sequence shows. The two halves differ only by the
|
||||
trademark glyph (`™` on 10, `®` on 13), which is the region/language split this
|
||||
archive uses everywhere else.
|
||||
|
||||
All four draw every element they declare (3/3 and 7/7), so nothing is silently
|
||||
dropped.
|
||||
|
||||
## ❔ The general rule: looked for, not found
|
||||
|
||||
Two candidate predicates, both dead:
|
||||
|
||||
* **Design size.** Every extra composable bundle sampled is **1280×720** — the
|
||||
same as every screen. It does not separate anything.
|
||||
* **Element count.** The "two-element fragment" story holds in
|
||||
`GP_MISSION_SELECT` (10 extras, all 2 elements) and **fails elsewhere**:
|
||||
`GP_OPTIONS`'s extras have 5 and 15 elements, `GP_SAVE_LOAD`'s have 4, 5, 6, 9
|
||||
and 13. The splash's own halves have 3 and 7. The ranges overlap, so no
|
||||
threshold separates them.
|
||||
|
||||
Reach: three archives sampled beyond `GP_TITLE`, chosen because they have
|
||||
non-build composables. Not a disc-wide sweep. A rule may exist in a field not
|
||||
looked at — the `.prm` fade quad is one candidate
|
||||
([transitions](screen-transitions.md) shows overlays lack it), untested here.
|
||||
|
||||
**So: the port is authoring this.** Locating the splash by entry index is the
|
||||
honest description, and `name_source` should say it was located by index and not
|
||||
by a rule.
|
||||
Reference in New Issue
Block a user