The census bounded WHERE a wrong tie-break could show and said outright that nobody had measured how many of those pairs change a pixel. Measured. `compose_with_order` renders a bundle in a caller-supplied order; the new example renders each screen twice, once derived and once with one tied pair swapped, and diffs. Same-key elements are contiguous under a stable sort on (key, i), so a swap paints nothing else in between. Controlled per entry: swapping an OVERLAPPING pair with DIFFERENT keys moves 36 305 to 771 479 px (max delta 254). Where no such pair is drawn the output says so rather than reporting an uninterpretable zero. * EXTRAS (entries 6/9) and the main menu (5/8): 0 px. The tied ptframe pairs ink ~3 600 px each and share NONE of them -- the 102x132 rect overlap was an artefact of approximating an element as pivot x 2. Blend-independent. * Across all 31 drawable overlapping tied pairs in GP_TITLE, the largest change any of them makes to any channel is 3/255. * Withdrawn: "a wrong tie-break can be wrong by a whole layer". That rested on ptlogo_back2eff5 geometrically containing two other glows. Rendered, the swap moves 6 390 px by max delta 2. Containment is not occlusion when the container is a near-transparent glow, and nobody had rendered it before asserting it. Reach: this measures our compositor's sensitivity to order, not the game's. The zero-shared-ink results hold under any per-pixel blend; the delta<=3 figures assume ours. 13 disc-gated ui_paint_order_disc tests and 114 lib tests pass unchanged. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QsEPXWVaEpyfudtR6re1Pd
299 lines
14 KiB
Markdown
299 lines
14 KiB
Markdown
# ✅ Does the derived paint order reproduce the measured ones? Mostly — and the gap is bounded
|
||
|
||
**Status:** ✅ **checked, with numbers.** `compose` uses a paint order *measured
|
||
from the running game* for the builds that have one and falls back to
|
||
`derived_paint_order` — a sort on each sprite's layer key — everywhere else. The
|
||
code's doc comment claimed the derived order "reproduces both measured orders up
|
||
to ties". That was a hedge with no measurement behind it, and it was stale:
|
||
there are **three** measured orders now, not two.
|
||
|
||
Tool: `cargo run -p sylpheed-formats --example paint_order_audit -- dat/GP_TITLE.pak`.
|
||
Output committed at [`data/paint-order-audit.txt`](../data/paint-order-audit.txt).
|
||
|
||
## 🔴 Refutation, 2026-08-29 — the published COST of the tie-break was understated
|
||
|
||
The port challenged HANDOFF's line *"eight candidates refuted; costs one
|
||
element's blend on one screen"* against its own census of 105 elements sharing a
|
||
key across 12 of 16 screens. Those two numbers are not comparable — the port
|
||
counts *elements*, this page counts overlapping *pairs*, and "one screen" was
|
||
scoped to five English menu screens without saying so. **So the objection is not
|
||
the contradiction it looked like. It is still right, and the line is corrected.**
|
||
|
||
`paint_order_audit` already reports overlapping ties per entry. Run over all 16
|
||
`GP_TITLE` entries — committed at
|
||
[`data/paint-order-ties-gp_title.txt`](../data/paint-order-ties-gp_title.txt):
|
||
|
||
| entry | screen | tied pairs | **overlapping** |
|
||
|---|---|---|---|
|
||
| 4, 5, 8, 11, 14 | title EN, main menu ×2, splash ×2 | — | **measured order is used; no tie risk** |
|
||
| 0, 1 | loading, plain | 2 | **1** each |
|
||
| 12, 15 | loading, dressed | 2 | **1** each |
|
||
| 6, 9 | `EXTRAS` EN / JP | 15 | **2** each |
|
||
| **7** | **title, Japanese** | **37** | **16** |
|
||
| 2, 3, 10, 13 | `PRESS Ⓐ` plate, `palogo_sqex` | 0 | 0 |
|
||
|
||
**24 overlapping tied pairs across 7 of the 11 entries that fall back to the
|
||
derived order** — not one element on one screen. The Japanese title alone carries
|
||
16, because it is the twin of the one build whose measured order exists and it
|
||
has no measured order of its own.
|
||
|
||
⚠️ **Overlap is an upper bound on the cost, not the cost.** Two elements that
|
||
overlap still show nothing if either is transparent at rest or one is opaque
|
||
where they meet. What the pair count bounds is *where a wrong tie-break could
|
||
show*, and the port is right that nobody has measured how many of those actually
|
||
change a pixel.
|
||
|
||
⚠️ This does not weaken the **rule**. The layer key is decoded and the derived
|
||
order reproduces every measured order it can be checked against, exactly, except
|
||
the title's eight tied pairs. What is corrected is only the advertised blast
|
||
radius of the unknown tie-break.
|
||
|
||
## The claim holds, and the exception is all ties
|
||
|
||
| build | derived == measured | inverted pairs | of which same-key ties |
|
||
|---|---|---|---|
|
||
| main menu (entries 5, 8) | **YES** | 0 | — |
|
||
| developer splash (11, 14) | **YES** | 0 | — |
|
||
| title (entry 4) | **NO** | 8 | **8 — every one** |
|
||
|
||
Four of the five measured bundles reproduce **exactly**. The title's eight
|
||
disagreements are all between elements that share a layer key, which the sort
|
||
cannot order and breaks by declaration index.
|
||
|
||
## ⚠️ Two of those ties are total occlusions
|
||
|
||
The tied family is the five `ptlogo_back2eff` glows, all key `32899`:
|
||
|
||
```
|
||
measured paints: 14, 15, 18, 16, 17
|
||
derived paints: 14, 15, 16, 17, 18
|
||
```
|
||
|
||
Two pairs flip, and they are not near-misses:
|
||
|
||
| pair | overlap | |
|
||
|---|---|---|
|
||
| `back2eff5` vs `back2eff3` | 82 824 px² | **100 % of the smaller** |
|
||
| `back2eff5` vs `back2eff4` | 152 047 px² | **100 % of the smaller** |
|
||
|
||
`back2eff5` is 1133×280 and **fully contains** both. Derived paints it on top of
|
||
two glows it completely covers; the game paints it underneath. ✅ The title is
|
||
unaffected in practice, because it has a measured order.
|
||
|
||
🔴 **"Not cosmetic — it can be wrong by a whole layer" is WITHDRAWN (2026-08-29).**
|
||
That sentence stood here on a geometric argument and nobody had rendered it. The
|
||
swap was then measured: `back2eff3` × `back2eff5` moves **6 390 px by a maximum
|
||
of Δ2 out of 255**, and `back2eff4` × `back2eff5` moves 5 516 px by **Δ1**. A
|
||
total occlusion by a near-transparent additive glow occludes nothing. See
|
||
[the pixel cost](#-what-the-tie-break-actually-costs-in-pixels-2026-08-29).
|
||
|
||
## ✅ The port's actual exposure is two element pairs
|
||
|
||
Per build, counting tied pairs and how many of them **overlap** (only those can
|
||
paint visibly differently):
|
||
|
||
| entry | the port's screen | order used | tied pairs | overlapping |
|
||
|---|---|---|---|---|
|
||
| 4 | title | **measured** | — | — |
|
||
| 5 | main menu | **measured** (derived agrees exactly) | 0 | 0 |
|
||
| **6** | **`EXTRAS`** | **derived** | 15 | **2** |
|
||
| 10 | publisher splash | derived | **0** | **0** |
|
||
| 11 | developer splash | **measured** | — | — |
|
||
|
||
So of the five screens, **one** rests on an unverified derived order, and its
|
||
risk is **two overlapping tied pairs** — not the 15 the raw tie count suggests.
|
||
The publisher splash's derived order is fully determined (no ties at all).
|
||
|
||
✅ **And that risk is now measured at zero pixels** — both pairs turn out to
|
||
share no ink at all. See
|
||
[the pixel cost](#-what-the-tie-break-actually-costs-in-pixels-2026-08-29).
|
||
|
||
🟡 For completeness, outside the port's set: entry 7 (the Japanese title) is the
|
||
worst on the disc at 37 tied pairs, 16 overlapping.
|
||
|
||
## Reach
|
||
|
||
* This checks the derived order against the orders **measured from the running
|
||
game**; it is not an independent derivation of what the game does. Where no
|
||
measured order exists, agreement cannot be checked at all — only the *tie
|
||
exposure* can, which is what the table above reports.
|
||
* Overlap uses `pivot × 2` as the element's size (documented as the sprite's own
|
||
dimensions for a `.t32`) at its resting placement, so scaled or rotated
|
||
elements are approximated.
|
||
|
||
---
|
||
|
||
## ❔ The tie-break is undecodable from the bundle — searched, with reach
|
||
|
||
The audit above leaves one question: the layer key orders elements, but what
|
||
orders elements that **share** a key? On the title that tie-break decides two
|
||
total occlusions, so it is not academic.
|
||
|
||
The game paints the five tied `ptlogo_back2eff` glows in the order
|
||
**eff1, eff2, eff5, eff3, eff4**. Three static structures were searched for
|
||
anything that reproduces it.
|
||
|
||
**1. The declaration table.** Entries 14–18 are *byte-identical* apart from the
|
||
pivot, which is just half the sprite's own size:
|
||
|
||
```
|
||
14 ptlogo_back2eff1 00000000 ffffffff ffffffff 00000000 ffffffff 0000004e 0000003c 00000000
|
||
15 ptlogo_back2eff2 00000000 ffffffff ffffffff 00000000 ffffffff 00000074 0000005b 00000000
|
||
16 ptlogo_back2eff3 00000000 ffffffff ffffffff 00000000 ffffffff 000000aa 0000005b 00000000
|
||
17 ptlogo_back2eff4 00000000 ffffffff ffffffff 00000000 ffffffff 00000149 0000005b 00000000
|
||
18 ptlogo_back2eff5 00000000 ffffffff ffffffff 00000000 ffffffff 000001fb 0000007e 00000000
|
||
```
|
||
|
||
**2. The `T8aD` headers.** All five carry identical `+0x04` (`0x8832`) and
|
||
identical `+0x08`/`+0x0A` (`32899`, the layer key itself). They differ only in
|
||
position and tile count. Searched **exhaustively**: every offset `0x00–0x7f`, at
|
||
u8, u16 and u32 width, sorted both ascending and descending —
|
||
|
||
| | |
|
||
|---|---|
|
||
| fields sorting to the **measured** order | **0** |
|
||
| fields sorting to the **declaration** order (control) | **64** |
|
||
|
||
The control matters: 64 fields *can* be found that reproduce a known ordering, so
|
||
the scan is capable of finding an ordering field when one exists. It finds none
|
||
for the order the game uses.
|
||
|
||
**3. The RATC child order** — the bundle's second element list, which is
|
||
genuinely a different permutation from the declaration table on other screens.
|
||
For this family it reads `eff1, eff2, eff3, eff4, eff5`: declaration order again.
|
||
|
||
### The answer
|
||
|
||
❔ **Undecodable from the bundle, with reach.** All three static orderings give
|
||
`eff1…eff5`; the game gives `eff1, eff2, eff5, eff3, eff4`. That is consistent
|
||
with what [ui-screen-runtime](ui-screen-runtime.md) already concluded from the
|
||
other direction — the game builds a **reordered child list at load time** and
|
||
paints that.
|
||
|
||
So Q3 resolves as: the layer key is ✅ **decoded** and orders 4 of the 5 measured
|
||
bundles exactly; the **tie-break within a key is ❔ undecodable**, and a consumer
|
||
must either use a measured order or accept declaration index as an arbitrary
|
||
stand-in.
|
||
|
||
---
|
||
|
||
## ✅ `EXTRAS`'s exposure narrows to ONE pair — and the capture is consistent with it
|
||
|
||
The 15 tied pairs on `EXTRAS` reduce twice.
|
||
|
||
**2 of the 15 overlap** (only an overlapping tie can paint differently):
|
||
|
||
```
|
||
[0] ptframe3.t32 x [1] ptframe4.t32 key 32848 overlap 102x132
|
||
[14] ptloop01.rat x [15] ptloop02.rat key 32784 overlap 400x180
|
||
```
|
||
|
||
**1 of those 2 can be drawn.** `ptloop01`/`ptloop02` are `loop*` sprite
|
||
animations, which `compose` skips unless `--animated` is passed, so their tie is
|
||
unreachable in a default render. The real exposure is `ptframe3` × `ptframe4`,
|
||
overlapping **102 × 132 px** at x 584–686, y 318–450.
|
||
|
||
### The capture is consistent with our order
|
||
|
||
Correlating our render against
|
||
[`live-extras.png`](../captures/title-builds/live-extras.png):
|
||
|
||
| region | correlation |
|
||
|---|---|
|
||
| **the contested overlap** (x 584–686, y 318–450) | **+0.9622** |
|
||
| `ptframe3` alone, outside the overlap | +0.8502 |
|
||
| `ptframe4` alone, outside the overlap | +0.9903 |
|
||
| whole frame | +0.9440 |
|
||
|
||
The contested region matches **better than the frame as a whole** and sits inside
|
||
the range of regions where order *cannot* matter. Were our tie-break wrong, that
|
||
region should correlate worse than its neighbours.
|
||
|
||
🟡 **Consistent with, not proof.** Correlation cannot see a swap if the two
|
||
frames happen to be locally similar. But it is a real check, and it removes the
|
||
"entirely unverified" character of the one tie the port can actually hit.
|
||
|
||
**So the chain is: 15 tied pairs → 2 overlapping → 1 drawable → consistent with
|
||
the capture.** That is the whole paint-order risk on the port's five screens.
|
||
|
||
---
|
||
|
||
## ✅ What the tie-break actually costs, in pixels (2026-08-29)
|
||
|
||
The section above bounds *where* a wrong tie-break could show and says outright
|
||
that "nobody has measured how many of those actually change a pixel". Measured
|
||
now, and the answer is **zero on every screen the port ships**.
|
||
|
||
Tool: `cargo run -p sylpheed-formats --example tie_break_pixel_cost -- dat/GP_TITLE.pak`,
|
||
output committed at [`data/paint-order-tie-pixel-cost.txt`](../data/paint-order-tie-pixel-cost.txt).
|
||
It renders each bundle twice — once in the derived order, once with one tied
|
||
pair swapped — and diffs. Elements sharing a key are contiguous in the derived
|
||
order (a stable sort on `(key, i)`), so a swap paints nothing else in between:
|
||
the diff is the tie-break's cost and nothing else.
|
||
|
||
### The instrument was controlled first, per entry
|
||
|
||
Every entry also swaps an **overlapping pair with different keys** — a pair whose
|
||
order the game demonstrably cares about. If that swap moves nothing, the
|
||
instrument cannot see a reorder on that screen and its zeros are worthless.
|
||
|
||
| entry | control swap | pixels moved |
|
||
|---|---|---|
|
||
| 4 | `pteff04` × `ptlogo_back2eff5` | 36 305 (max Δ **254**) |
|
||
| 5 / 8 | `ptbase` × `pteff05` | 764 030 / 771 479 (max Δ 67) |
|
||
| 6 / 9 | `ptbase` × `pteff05` | 761 600 / 768 159 (max Δ 67) |
|
||
| 7 | `ptlogo_eff3` × `pteff04` | 240 308 (max Δ 225) |
|
||
| 0, 1, 12, 15 | — | ⚠️ **no control**: on the loading bundles no overlapping different-key pair is drawn under default options. Their numbers below are non-zero, so they do not rest on a control; a *zero* there would have been uninterpretable |
|
||
|
||
### And every result explains itself
|
||
|
||
A bare "0 px differ" is ambiguous: it can mean the order genuinely does not
|
||
matter, or that the two elements never painted on the same pixel and the
|
||
"overlap" was an artefact of the rect approximation (pivot × 2 at the resting
|
||
placement). So the tool also reports each element's **ink** — the pixels that
|
||
move when that element alone is removed — and the **shared ink** between the
|
||
pair. That distinction is what makes the zeros below trustworthy.
|
||
|
||
### The port's screens: zero, and blend-independent
|
||
|
||
| entry | screen | tied pair | ink | **shared ink** | pixels changed |
|
||
|---|---|---|---|---|---|
|
||
| 5, 8 | main menu EN / JP | `ptframe1` × `ptframe2` | 4 783 / 5 297 px | **0** | **0** |
|
||
| **6, 9** | **`EXTRAS` EN / JP** | `ptframe3` × `ptframe4` | 3 646 / 3 584 px | **0** | **0** |
|
||
| 10 | publisher splash | — (no ties) | | | **0** |
|
||
| 4, 11 | title, developer splash | measured order used | | | **0** |
|
||
|
||
`ptframe3` and `ptframe4` each put ink on ~3 600 pixels and **share none of
|
||
them**. Their 102 × 132 bounding-box overlap is a rect artefact: the sprites
|
||
inside it are disjoint. ✅ This is stronger than the correlation check below it,
|
||
because it does not depend on the blend at all — when two layers never touch the
|
||
same pixel, their order cannot matter under *any* per-pixel compositing rule.
|
||
|
||
**So the whole Q3 tie-break risk on the five menu screens is zero pixels, not
|
||
"one drawable pair, consistent with a capture".**
|
||
|
||
### Where it is non-zero, it is invisible
|
||
|
||
| entry | screen | worst tied pair | px changed | **max Δ** |
|
||
|---|---|---|---|---|
|
||
| 4 | title EN (derived order forced) | `back2eff1` × `back2eff5` | 6 645 (0.72 %) | **3** |
|
||
| 7 | title JP | `back2eff1` × `back2eff5` | 1 061 (0.12 %) | **3** |
|
||
| 0, 1, 12, 15 | loading ×4 | `pgloading_eff01` × `eff02` | 1 761 (0.19 %) | **1** |
|
||
|
||
Across **31 drawable overlapping tied pairs on the whole of `GP_TITLE`**, 26
|
||
change at least one pixel and the largest change any of them makes to any
|
||
channel is **3/255**. The tied families are additive glows; they are nearly
|
||
transparent where they meet, which is why containment does not equal occlusion.
|
||
|
||
⚠️ **Reach.** This measures *our compositor's* sensitivity to the order, not the
|
||
game's. For the ptframe pairs that does not matter (zero shared ink is
|
||
blend-independent). For the glow pairs it does: the Δ ≤ 3 figures assume our
|
||
alpha blend, and a game using additive or premultiplied blending for these could
|
||
differ. What the numbers rule out is a *structural* error — a layer appearing or
|
||
disappearing — not a shading one.
|
||
|
||
⚠️ Entries 12 and 15 under `--focus --animated --primitives` report ink 0/0 for
|
||
the tied pair: with the primitives drawn, those two elements contribute no
|
||
visible pixels at all. That is why their control is dead in that configuration,
|
||
and it is self-consistent rather than a failure.
|