Files
Sylpheed/docs/re/structures/ui-button-focus-record.md
Sylpheed RE agent 9ca1eb50fd 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.
2026-08-29 08:19:23 +00:00

112 lines
5.2 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.
# ✅ 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.