`ratc::parse` named each child by scanning backwards for the last printable run of bytes before its magic. The format states the name explicitly instead, in an `opt ` block: `"opt " | BE32 len | name | NUL | 3 bytes | magic` -- the same block `ui_layout::opt_link` already read for a button's focus link. The scan agrees with it 17 918 times out of 17 942 and is wrong 24 times, every one the same failure: the 3 trailing payload bytes are themselves printable and beat the real name. For `pteff05.t32` those bytes are `38 41 58` = `8AX`, so the full-resolution background of all five menu screens registered under a name no element declares, resolved to no sprite, and `compose` dropped it through an early `continue` that -- unlike the two arms above it -- records nothing. The screen lost its background and `screen render` still reported "all resolved". `8AX` was never a name. Docs that treated it as one are corrected here. Disc-wide, and the control is the 17 918 the scan already got right: the `opt ` reading reproduces every one of them. Effect on the five screens is the signature of the same art at twice the resolution -- mean brightness unmoved, high-frequency detail x1.15..x1.30 -- which is what the separately-measured `ui-8ax-fullres-background` result said the game draws. Also closes a long-standing dangling reference: `pmbase.t32`, recorded as "on the disc nowhere", is the `GP_STAGE_CLEAR` child the scan called `8AX`. RATC sibling references now resolve 10 148 of 10 148. Verified: 114/114 sylpheed-formats unit tests (including two new ones pinning the `8AX` case byte for byte and the no-block fallback), and every disc-gated integration suite in sylpheed-formats/sylpheed-cli.
7.2 KiB
✅ pteff04 / pteff05 — the full-resolution background, once dropped as 8AX
Status: ✅ RESOLVED 2026-08-29. This page's two questions are both closed and it is kept for the evidence, not as an open item.
- Which of the two backgrounds does the game draw? — the full-resolution one. Measured against a capture; the section below stands unchanged.
- Why did ours drop it? — because
8AXis not a name. It is three bytes of the preceding record's payload (38 41 58) that happen to be printable ASCII, and our backwards printable-run scan preferred them to the name the format states in itsoptblock. Decoded, with a disc-wide check, inratc-child-names.md;ratc::parsenow reads the stated name andscreen renderdraws the background on all five screens.
⚠️ Read the rest of this page with that correction in mind. It was written
while 8AX was believed to be a name the game uses, and says so in several
places — "the texture is registered as 8AX", "the T8aD behind its opt link
is registered under the name 8AX". The opt link was never the problem; the
opt block was the answer, sitting unread three bytes away.
Original status line: 🟡 a real name-resolution gap, currently harmless to
look at, and the obvious fix would make the render worse. Found by auditing
what screen render silently omits on the port's five screens.
What screen render drops, and why
sylpheed-cli screen render reports its omissions. On the five screens the port
needs, every one is accounted for except two:
| screen | not drawn | reason |
|---|---|---|
| title (4) | ptlogo1×2, ptlogo2×2 |
kind & 0x4 ghost instances — the draw capture shows one quad per wordmark |
pteff00.prm, pteff02.prm |
.prm primitives, off without --primitives |
|
ptloop01.rat, ptloop02.rat |
loop* animations, off without --animated |
|
pteff04.t32 |
⚠️ unexplained — this page | |
main menu (5), EXTRAS (6) |
as above, plus pteff05.t32 |
⚠️ same |
| splash (10, 11) | palogo_eff0.prm |
primitive |
pteff04/pteff05 are kind = 0x0, one keyframe, rest a = 255, and pivot
(640,360) — i.e. a full-screen, fully opaque element. Exactly the shape of
the defect the port agent reported (an element the game draws that we omit).
The cause: the element's name and its texture's name differ
Both names occur twice per bundle, the same as pteff03.t32, which resolves
fine. The difference is what follows the opt link:
pteff03.t32 @0x4661a4 opt → T8aD ← resolves
pteff05.t32 @0x0e2035 opt → 8AX T8aD ← the sprite is named 8AX
Hex at 0x0e2035:
opt .... 70 74 65 66 66 30 35 2e 74 33 32 00 38 41 58 54 38 61 44
p t e f f 0 5 . t 3 2 \0 8 A X T 8 a D
So the declaration says pteff05.t32 while the T8aD that follows is registered
under 8AX. compose does build.sprites.get(sprite) and, on a miss, hits a
silent continue. 8AX is a 1280×720 texture and appears in all six
title-family bundles (a60fcb85, a715f485, a81c1d85 and the three JP twins);
it is a sprite in builds 4/5/6 and never an element.
✅ Why it does not currently show
The same artwork is already on screen. ptbase.t32 is 640×360 and its
element draws at 200 %, and the background our render produces is
pixel-identical to 8AX in every patch sampled:
| patch | capture | our render | 8AX |
|---|---|---|---|
| top-left | 4.5, 9.4, 19.5 | 17.7, 29.7, 46.7 | 17.7, 29.7, 46.7 |
| top-right | 4.4, 9.0, 18.2 | 17.2, 28.2, 43.8 | 17.1, 28.2, 43.8 |
| bottom-left | 3.5, 6.0, 11.1 | 13.6, 20.5, 31.2 | 13.6, 20.5, 31.2 |
Directly: ptbase upscaled 2× against 8AX is mean abs diff 2.05 (max 80),
not byte-identical. They are the same art at two resolutions — 8AX is the
full-res copy, ptbase the half-res one the element scales back up.
⚠️ So do not "fix" this by drawing it
Resolving pteff05 → 8AX and drawing it in addition to ptbase would
double-draw the background — an opaque full-screen layer over an identical
one. It would not be visible as a doubling, which is worse: it would silently
cost fill and hide any future change to either layer.
✅ And that is now the recommendation, not just an option: use 8AX at 1:1.
The detail test above says the game's background carries 8AX's full-resolution
detail, so upscaling the 640×360 ptbase 2× is wrong, not merely softer.
⚠️ Still do not draw both — an opaque full-screen layer over an identical one
costs fill and hides any later change to either. ptbase's element carries the
keyframes, so a consumer needs its timing with 8AX's pixels.
✅ Settled statically: the game draws the FULL-RES 8AX
This was parked as "needs a per-draw capture". It did not — the two candidates
carry the same art at two resolutions, so what separates them is the detail
8AX has that an upscale cannot. Compute 8AX − upscale(ptbase) and ask
whether the live capture contains it.
tools/re-capture/eightax_detail_test.py,
output at data/eightax-detail-test.txt.
Both candidates are first mapped into the capture's tone domain with the measured gamma (tone curve); without that the capture's residual is dominated by the tone difference and the test is blind.
| screen | corr | control (shift 7 px) | control (flip) | ceiling | % of ceiling |
|---|---|---|---|---|---|
| main menu | +0.0475 | +0.0032 | −0.0075 | 0.070 | 68 % |
| title | +0.0634 | +0.0095 | +0.0086 | 0.094 | 68 % |
The "ceiling" is what a clean "8AX is drawn" would give, sd(8AX-only detail) / sd(capture residual) — the 8AX-only detail is small (sd 1.9–4.1) against
everything else that differs (sd 27–43), which is why the absolute correlation is
small and why the controls matter more than the magnitude.
Two independent screens both land at 68 % of ceiling, 7–15× their matched controls. The controls preserve the spatial correlation structure and destroy only the alignment, so they are what "no signal" looks like here.
🔴 An earlier, cruder version of this test does NOT support the conclusion
A pixel-pair test (a 2×-nearest upscale has identical adjacent columns) gave
ratio 0.00 for nearest, 0.60–0.72 for bilinear/bicubic/lanczos, 0.98 for native
8AX, and 1.01 for the capture — apparently decisive. It is not: additive
noise raises both terms equally and pushes any ratio toward 1. Fitting a noise
term, both "native + noise ≈ 1.2" and "bilinear upscale + noise ≈ 1.8" reproduce
the observed numbers. Recorded because the number looks conclusive and is not.
⚠️ What it still does not show
Whether ptbase is also drawn underneath. 8AX is ~86 % opaque and carries
the same art, so it would hide it either way. The observable fact is that 8AX's
detail reaches the screen.
❔ Not established
- The capture is ~4× darker than the render in these patches (4.5 vs 17.7), and not by a constant ratio. That is a separate colour/gamma question, untouched here.