Files
Sylpheed/docs/re/structures/ui-8ax-fullres-background.md
Sylpheed RE agent 6c4e5d3d71 re(ui): settle 8AX vs ptbase statically -- the game draws the full-res one
I had parked this as "needs a per-draw capture recording texture base
addresses". It did not. 8AX (1280x720) and ptbase (640x360 at 200%) are
the SAME artwork at two resolutions, which is exactly why comparing
either against a capture is inconclusive -- and why comparing their
DIFFERENCE is not. Compute 8AX - upscale(ptbase), the detail only 8AX
has, and ask whether the capture contains it.

Both candidates are first mapped into the capture's tone domain with the
measured gamma; without that the residual is dominated by the tone
difference and the test is blind.

  main menu  corr +0.0475   controls +0.0032 shift, -0.0075 flip   68% of ceiling
  title      corr +0.0634   controls +0.0095 shift, +0.0086 flip   68% of ceiling

Two independent screens, both at 68% of the theoretical ceiling
(sd of the 8AX-only detail over sd of the capture residual), 7-15x their
matched controls. The controls preserve spatial correlation and destroy
only alignment, so they are what "no signal" looks like.

So the recommendation changes: resolve the name and draw 8AX at 1:1.
Upscaling ptbase 2x is wrong, not merely softer. Still do not draw both --
an opaque layer over an identical one costs fill and hides later changes,
and ptbase's element is the one carrying the keyframes, so a consumer
needs its timing with 8AX's pixels.

Also recorded and withdrawn: a cruder pixel-pair test gave 0.00-0.72 for
upscales, 0.98 native and 1.01 for the capture -- apparently decisive.
Additive noise raises both terms of that ratio equally and drives any
value toward 1; fitting a noise term, both "native + noise" and "bilinear
+ noise" reproduce the observed numbers. The conclusion is right, that
test does not establish it, and it is in REFUTED because the number looks
conclusive and is not.

Not shown: whether ptbase is also drawn underneath. 8AX is ~86% opaque
and carries the same art, so it would hide it either way.
2026-08-29 02:40:53 +00:00

124 lines
6.0 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.
# 🟡 `pteff04` / `pteff05` are dropped — the texture is registered as `8AX`
**Status:** 🟡 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`](../../../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.94.1) against
everything else that differs (sd 2743), 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, 715× 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.600.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.