# ✅ 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). ## 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. So a tie-break by declaration index is not cosmetic — where it is wrong, it can be wrong by a whole layer. ✅ The title is unaffected in practice, because it has a measured order. ## ✅ 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). 🟡 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.