Files
Sylpheed/docs/re/structures/ui-paint-order-derived-check.md
sylph-decoder 7b5a4aa041 re: the paint-order tie-break costs one pixel, on one screen we do not ship
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
2026-08-29 19:35:29 +00:00

15 KiB
Raw Blame History

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 screensui-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.

🔴 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:

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.

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.

🟡 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 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 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 584686, y 318450.

The capture is consistent with our order

Correlating our render against live-extras.png:

region correlation
the contested overlap (x 584686, y 318450) +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. 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.