The port needed ptbtneff01.t32's placement and was about to author it from an eyeballed PNG measurement. It does not have to: a `.rat` leaf needs no new reader. Its first 32 bytes have a bundle header's shape -- "RATC", 0x3c declaration-entry size at +4, element count at +20, design 1280x720 at +24/+28 -- so ui_layout::parse_build reads it unchanged. The control is the base record, whose position is known independently: the parent screen reports ptbtn01.rat resting at (542,162), and parsing the leaf alone returns ptbtn01.t32 at (542,162). It reproduces all five buttons. Positions are absolute design-space top-left. The ring rests at (500, 156/236/ 316/396/476) for buttons 1-5 -- a uniform (-42,-6) from each button's own rest, identical in the Japanese bundle. The bright label is a uniform (-7,-7). Two things recorded rather than smoothed over: a leaf's placement DUPLICATES the parent's rather than being relative to it, and the two copies are not always byte-equal (ptbtn04's parent says y=401, its leaf says 402) -- the parent is what compose honours, so the leaf is the source only for elements the parent does not declare, which is exactly the ring. And `screen render --focus` is blind to the ring for the same reason the port's exporter was: el.focused is name-based on top-level elements and neither walks into the leaf.
7.5 KiB
✅ 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:
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:
+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:
| 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
against
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) 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.
✅ Where the ring is placed — DECODED 2026-08-29, no authoring needed
This page previously said the per-element placement inside a .rat leaf was not
decoded and that a consumer should eyeball it off a capture. That was wrong by
omission: a leaf needs no new reader. Its first 32 bytes have the same shape as
a bundle header — "RATC", 0x3c declaration-entry size at +4, element count
at +20, design size 1280x720 at +24/+28 — so ui_layout::parse_build
reads it unchanged.
The control is the base record, whose position is known independently: the
parent screen reports ptbtn01.rat resting at (542,162), and parsing the leaf
on its own returns ptbtn01.t32 at (542,162). It reproduces all five.
Positions are absolute design-space top-left, not offsets
(examples/rat_leaf_placement.rs):
| button | base | ring ptbtneff01.t32 |
Δ | label ptbtn0Nf.t32 |
Δ |
|---|---|---|---|---|---|
ptbtn01 |
(542,162) | (500,156) | (−42,−6) | (535,155) | (−7,−7) |
ptbtn02 |
(542,242) | (500,236) | (−42,−6) | (535,235) | (−7,−7) |
ptbtn03 |
(542,322) | (500,316) | (−42,−6) | (535,315) | (−7,−7) |
ptbtn04 |
(542,402) | (500,396) | (−42,−6) | (535,395) | (−7,−7) |
ptbtn05 |
(542,482) | (500,476) | (−42,−6) | (535,475) | (−7,−7) |
The offset is uniform: (−42,−6) for the ring and (−7,−7) for the label on
every button, and identical in the Japanese bundle (pak entry 8).
⚠️ One 1-unit disagreement, and it is real. ptbtn04's parent element rests
at y 401 while its own leaf says y 402 (and in the JP bundle the leaf says
401 against a parent 401). So a leaf's placement duplicates the parent's
rather than being relative to it, and the two copies are not always byte-equal.
The parent's is what compose honours; treat the leaf's as the source only for
elements the parent does not declare — which is exactly the ring's case.
🟡 The ring carries 2 keyframes where the label carries 1, so it animates. What it does between them is not decoded here.
⚠️ screen render --focus is blind to the ring, and so was this page
el.focused is name-based on top-level elements, and a screen's buttons are
.rat records whose focused twin is not itself a top-level element — so
rendering build 5 with and without --focus produces an identical image. The
reference renderer has the same blind spot the port reported, for the same
reason: neither walks into the leaf. Fixing it is a renderer change, not a
format question; the format is decoded above.
❔ Not established
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_TITLEonly.