`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.
142 lines
7.2 KiB
Markdown
142 lines
7.2 KiB
Markdown
# ✅ `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 `8AX` **is 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 its `opt ` block. Decoded, with a disc-wide check, in
|
||
[`ratc-child-names.md`](ratc-child-names.md); `ratc::parse` now reads the
|
||
stated name and `screen render` draws 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.
|
||
|
||
<sub>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.</sub>
|
||
|
||
## 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`](../../../tools/re-capture/eightax_detail_test.py),
|
||
output at [`data/eightax-detail-test.txt`](../data/eightax-detail-test.txt).
|
||
|
||
Both candidates are first mapped into the capture's tone domain with the measured
|
||
gamma ([tone curve](ui-render-tone-curve.md)); 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.
|