Closes the open half of Q3. `ui-paint-order-derived-check.md` bounded WHERE a wrong tie-break could show -- overlapping same-key pairs -- and said outright that nobody had measured how many change a pixel. At the instant the player sees, the answer is: at most 1 px at max channel difference 1, on the JAPANESE title only (`ptlogo2` x `ptlogo_tm`, 5 px of shared ink). Exactly 0 px on all five port screens. The earlier 24-pair bound was counted at `rest()`, and 10 of the title's 11 overlapping tied pairs are between `ptlogo_back2eff1`..`eff5` -- the five transient flashes from the settle-time finding, transparent on the settled screen. A tie between two invisible elements cannot cost a pixel. Not a knife-edge. Sweeping every keyframe time and every midpoint between keyframe times, the live-pair count is flat across the ENTIRE settle window: 1 on the EN title, 2 on the JP title, 0 on all four loading bundles -- whose tie is live only at t17..t33, during the build-in, which matters because their settle windows are narrow enough to deserve little trust otherwise. Controls: every entry reporting zero also swaps an overlapping DIFFERENT-key pair, which must and does move pixels (25 310 / 268 698 / ~765 000 px). Zeros are explained by shared-ink counts rather than asserted -- the `ptframe` pairs overlap by bounding box and share 0 px of ink. Entries 0/1/12/15 have NO live control and their zeros rest on keyframe data rather than a render; recorded as the weaker claim it is. Refutation attempt on the corpus's "24 overlapping pairs": it SURVIVES as a rest-pose count -- an independent recount reproduces entry 7's 16 exactly. What is overturned is its interpretation as the risk surface. `tie_break_pixel_cost` gains a settle-time case and an alpha/scale filter on its rect test; `tie_cost_over_time` is new. Also strips 611 bytes of captured cargo warnings from the head of the committed tie census. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QsEPXWVaEpyfudtR6re1Pd
308 lines
15 KiB
Markdown
308 lines
15 KiB
Markdown
# ✅ Does the derived paint order reproduce the measured ones? Mostly — and the gap is bounded
|
||
|
||
> ✅ **The open half of this page is closed (2026-08-29): the tie-break's pixel
|
||
> cost is measured.** At the instant the player sees, it is **at most one pixel at
|
||
> Δ1, on the Japanese title only, and exactly zero on all five port screens** —
|
||
> [`ui-tie-break-cost-at-settle.md`](ui-tie-break-cost-at-settle.md). The 24-pair
|
||
> bound below is a **`rest()` count** and survives as such (entry 7's 16
|
||
> reproduces exactly), but 10 of the title's 11 pairs are between elements that
|
||
> are *transparent* on the settled screen. Why the game orders ties as it does is
|
||
> still unknown, and now costs one pixel.
|
||
|
||
**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.
|