Q3 was delivered as "decoded: a u16 layer key at +0x0A". The audit last
iteration showed the key does not fully order a screen -- elements
sharing a key are tied, and on the title that tie-break decides two total
occlusions. This searches for what breaks the tie, and closes it as a
negative with reach.
The game paints the five tied ptlogo_back2eff glows in the order
eff1, eff2, eff5, eff3, eff4. Three static structures were searched:
1. The declaration table. Entries 14-18 are byte-identical apart from the
pivot, which is only half the sprite's own size.
2. The T8aD headers. All five carry identical +0x04 (0x8832) and
identical +0x08/+0x0A (32899, the key itself), differing only in
position and tile count. Searched exhaustively -- every offset
0x00-0x7f, u8/u16/u32, ascending and descending:
fields sorting to the MEASURED order: 0
fields sorting to the DECLARATION order (control): 64
The control is the point: 64 fields can be found that reproduce a
known ordering, so the scan finds ordering fields when they exist. It
finds none for the order the game uses.
3. The RATC child order -- a genuinely different permutation on other
screens -- gives eff1..eff5 here, declaration order again.
All three static orderings give eff1..eff5; the game gives eff1,2,5,3,4.
That agrees with ui-screen-runtime's conclusion from the other direction:
the game builds a reordered child list at load time and paints that.
Q3 now reads honestly: 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 use a measured order or accept declaration index as an
arbitrary stand-in. The port's exposure remains 2 overlapping tied pairs
on EXTRAS.
METHOD: an exhaustive field search needs a positive control, or "found
nothing" is worthless.
129 lines
5.7 KiB
Markdown
129 lines
5.7 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).
|
||
|
||
## 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.
|