re(ui): the paint-order tie-break is undecodable from the bundle

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.
This commit is contained in:
Sylpheed RE agent
2026-08-29 02:26:19 +00:00
parent ba47bdebe8
commit f7f9b555f6
4 changed files with 69 additions and 1 deletions

View File

@@ -564,3 +564,10 @@ agent's loop prompt, i.e. nowhere durable. See [`README.md`](README.md) for the
elements paints identically either way. Reporting 15 would have overstated the
risk by 7×; the useful number is the one filtered by whether the difference can
reach a pixel.
* **An exhaustive field search needs a positive control, or "found nothing" is
worthless.** Scanning a header for a field that reproduces a measured ordering
returned zero hits — which could equally mean the field is absent or the scan
is broken. Running the same scan against a *known* ordering (declaration order)
returned **64** hits, proving the scan finds ordering fields when they exist.
Only then is the zero a finding. The control costs four extra lines and turns a
silence into a negative with reach.

View File

@@ -457,3 +457,10 @@ neighbourhood, not just the line.
repeating one stale frame; cross-checked, it read surface mean 5.21 where
`import` read 125.65 at the same moment. A dense negative from a frozen stream
is not a negative. [`capture-harness-status.md`](capture-harness-status.md)
* "the `T8aD` layer key fully determines a screen's paint order" → **refuted, and
the remainder is undecodable.** Elements sharing a key are tied; on the title
the game paints the five tied `ptlogo_back2eff` glows `1,2,5,3,4` while the
declaration table, the RATC child order and **every** field in the `T8aD`
header (exhaustive 0x000x7f, u8/u16/u32, both directions — 0 matches against
64 for the declaration-order control) all give `1,2,3,4,5`.
[`ui-paint-order-derived-check.md`](structures/ui-paint-order-derived-check.md)

View File

@@ -72,3 +72,57 @@ worst on the disc at 37 tied pairs, 16 overlapping.
* 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 1418 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 `0x000x7f`, 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.