# The paint order comes from a layer key in the T8aD sprite header **Status:** ✅ `CONFIRMED` on both screens whose paint order has been measured — the key is **non-decreasing in paint order on every element that has a sprite**, 20 of 24 on one and 6 of 7 on the other. 🟡 ties are not explained. ❔ the field's full meaning (it looks like flags, not a plain depth). ## The field Every `T8aD` sprite begins with a 44-byte header. The word at **`+0x08`** — untouched by this project's decoder, which reads width/height/tile-count at `+0x14`/`+0x18`/`+0x1c` — sorts the screen. Read in the order the game paints them (`ui-screen-runtime.md` has how that order was measured — off the live child list, checked against a draw capture): **`GP_TITLE` build 4, the title screen** | paint slot | element | sprite | `+0x08` | |---|---|---|---| | 0 | 9 | `ptbase2` | `0x8000` | | 3 | 10 | `pteff04` | `0x8020` | | 5 | 6 | `pteff01` | `0x8040` | | 6 | 20 | `ptlogo_back2eff` | `0x8081` | | 7 | 19 | `ptlogo_back2` | `0x8082` | | 8–12 | 14,15,18,16,17 | `back2eff1…5` | `0x8083` ×5 | | 13–19 | 0,2,4,7,1,3,5 | `ptlogo1`/`tm`/`ptlogo2` | `0x80a0` ×7 | | 20 | 22 | `ptlogoall_eff` | `0x80a8` | | 21 | 23 | `ptlogoall_eff2` | `0x80a9` | | 22 | 21 | `ptcopyright` | `0x8100` | **`GP_TITLE` entries 11/14, the developer-logo splash** | paint slot | sprite | `+0x08` | |---|---|---| | 1–3 | the three `_eff` glows | `0x a100` ×3 | | 4–6 | the three base logos | `0x a110` ×3 | Both are **ascending, with no inversion anywhere**. On the splash it explains the whole permutation — the glows sort before their logos because `0xa100 < 0xa110`, which is why the declaration table's interleaving (base, glow, base, glow) is not what you see. ## Why this matters It is the first **file-derivable** account of the paint order. Everything checked before it failed: declaration order and its reverse, the placement region, the RATC child order, keyframe start and rest times, resting Y, the runtime element record's fields, and every other build's table. This is a per-sprite value the port can read directly. ## What is not settled * **Ties.** Two groups share a key (`0x8083` ×5 and `0x80a0` ×7) and the game paints them in an order that is *not* the declaration order — `14,15,18,16,17` and `0,2,4,7,1,3,5`. Something breaks those ties and it is not known. For the port it may not matter (tied elements are same-layer, and the three `ptlogo1`/`ptlogo2` instances are the ghosts that are not drawn at rest), but it is unmeasured, not proven harmless. * **The field's meaning.** `0x8000`, `0x8020`, `0x8040`, `0x8081`… `0x8100` on one screen and `0xa100`/`0xa110` on another look like flag words with a layer in some of the bits rather than a plain integer depth. Sorting on the whole word works on both screens; which bits actually carry the layer is unknown. * **Two screens is two screens.** A third measured permutation would either promote this to a rule or break it. The cheapest one available is any screen whose object is resident at the same time as the title's. ## Landed in the compositor (2026-08-19) `ui_layout::compose` now paints in the **derived** order — a stable sort of the elements by `sprite_layer_key` — for every build except the two whose measured order is hard-coded, which stay as the ground truth they are. Elements with no sprite (the `.prm` primitives) have no key; they keep their declaration position among themselves and `compose` skips them anyway. Verified three ways rather than by a green build: * a disc-gated test asserts the measured orders are **non-decreasing** in the key and that the composite's key sequence comes out sorted — and it was checked both ways: reading the word from `+0x0c` instead of `+0x08` makes it fail; * the title still composites identically (its measured order is used); * a screen nobody has captured — `GP_MISSION_SELECT` — now composites cleanly ([capture](../captures/mission-select-derived-order.png)). **Found on the way, and worth its own line:** the developer-logo splash bundle has no `.rat` child, so `ui_layout::is_build` rejects it and the **default** `screen render` never sees it. ⚠️ **Corrected 2026-08-28:** the sentence that used to end this paragraph — "the splash cannot be rendered by `screen render` at all" — is **wrong**. It can: `screen render --all` widens the enumeration past `is_build` and the splash composites fine, both halves. [`../ui-title-build-map.md`](../ui-title-build-map.md) has the renders. What is true is narrower: it is invisible to the *default* listing, so anyone who does not pass `--all` will conclude it is missing. ## What the change did to the screens that were already verified (2026-08-19) The derived order is applied to **every** build on the disc on the strength of two measured screens, so the first thing owed to it is a check of what it did to the screens the corpus had already validated against the running game. Two exist: the tutorial PAUSE menu and the title main menu (`../captures/ui-layout/pause-tutorial-real-vs-rebuilt.png`). Rendered both ways — `compose` as committed, then with `compose` temporarily reverted to declaration order — and diffed: | screen | pixels differing | RMSE | max per-channel delta | |---|---|---|---| | tutorial PAUSE | 35 162 / 921 600 (3.8 %) | 0.52 % | 45 / 255 | | title main menu | 9 911 / 921 600 (1.1 %) | 0.36 % | 34 / 255 | **No layout regression.** Side by side the two renders are indistinguishable: every panel, label and glyph is in the same place at the same size. What moved is confined to pixels where translucent sprites overlap — the glows around PAUSE, the OBJECTIVE / DEFEAT CONDITION / HINT header bars, the menu underlines — i.e. the order the blends compose in, which is exactly what a paint-order change is supposed to touch and nothing else. 🟡 **Which of the two is more faithful on these two screens is NOT settled.** A difference of ≤45/255 on a few per cent of pixels is not decidable against the committed side-by-side oracle, and there is no fresh framebuffer capture of either screen to diff at that magnitude. The derived order is kept because it is the rule measured off the game on the two screens where the order *is* known, not because it was shown to be better here. If a capture of the PAUSE menu is ever taken, this is the first thing to check it against. ### Corpus-wide, and not a no-op A disc-gated test (`every_composite_paints_in_layer_key_order`) composes every build on the disc and asserts the draw list is strictly increasing in `(layer key, declaration index)`. It also counts how far the rule reaches: > **341 of 965 builds (35 %) are reordered** by it. That matters for how much credit the rule gets. Had it been a near-no-op, the two measured screens would be the entire evidence base; instead a third of the disc's screens now composite in an order no capture has checked. The test asserts the share stays above a quarter, so a future change that quietly collapses the rule back to declaration order fails here instead of passing silently. ## A third measured permutation — the main menu (2026-08-19) Read off the running game with `tools/re-capture/screen_children.py`, and identified in the file by its pivot signature: **`GP_TITLE.pak` ratc-index 8**, the NEW GAME / LOAD GAME / TUTORIAL / OPTIONS / EXTRAS screen, 16 elements. ``` paint order (child slots): 1 3 4 2 5 8 9 6 7 15 10 11 12 13 14 0 ``` | slot | element | | slot | element | |---|---|---|---|---| | 0 | 1 `ptbase.t32` | | 8 | 7 `ptframe2` | | 1 | 3 `ptloop01` | | 9 | 15 `ptmsg` | | 2 | 4 `ptloop02` | | 10–14 | 10–14 `ptbtn01`…`ptbtn05` | | 3 | 2 `pteff05` | | 15 | **0 `pteff00.prm`** | | 4 | **5 `pteff02.prm`** | | | | | 5–7 | 8 `pteff10`, 9 `pteff12`, 6 `ptframe1` | | | | **This is the first measured screen with TWO primitives**, and they land in different places — which is the point. `pteff02.prm` (the 25 % black dim) paints 4th, beneath the whole UI; `pteff00.prm` (the screen-transition fade, resting transparent) paints **last**. Both match their positions on the title screen exactly, where `pteff02.prm` is also slot 4 and `pteff00.prm` is also last. So a primitive's place is **per-element and stable by role** across screens: | primitive | role | measured position | |---|---|---| | `palogo_eff0.prm` | opaque black backdrop | **first** (splash) | | `pteff02.prm` | 25 % dim under the UI | **slot 4** (title *and* menu) | | `pteff00.prm` | screen-transition fade | **last** (title *and* menu) | It is still not *derived* — a primitive has no `T8aD` header and so no layer key — but there are now three permutations to test a candidate against instead of two, and the candidate has to live in the 60-byte declaration entry. ### Verified against the live capture The order is wired into `measured_paint_order` (keyed by element names, so both language builds get it). Composited with `--primitives` and edge-correlated against a framebuffer capture taken in the same session ([capture](../captures/main-menu-oracle.png), [composite](../captures/ui-layout/main-menu-composited.png)): > **0.9591 at shift (0, 0)** That is a *third* screen confirming the whole stack at once — paint order, resting pose, `fade` alpha and primitives — on a capture this project had not seen before. ### Not settled * ❔ `ptframe1`/`ptframe2` rest at `0x00ffffff` (alpha 0) and are therefore not drawn, but the capture shows the menu frame plainly. Either the resting rule picks the wrong plateau for them or the frame is drawn by something else. * ❔ The derivation for primitives. Three permutations now, still no rule. ## The tie-break: still unsolved, and now measured down to the pixel (2026-08-19) With the layer key and the primitives' implied keys in place, the tie-break — how the game orders elements that **share** a key — is the only thing left between the derived order and ground truth. Three measured screens now constrain it. **On the menu and the splash it does not bite.** Every tied group there comes out in declaration order, which is what the stable sort already gives: | screen | tied group | measured | |---|---|---| | menu | `0x8010` ×2 | 3, 4 | | menu | `0x8050` ×2 | 6, 7 | | menu | `0x8110` ×5 | 10, 11, 12, 13, 14 | | splash | `0xa100` ×3 | 2, 4, 6 | | splash | `0xa110` ×3 | 1, 3, 5 | **The title is the one screen that discriminates**, and nothing predicts it: ``` 0x8083 ×5 measured 14, 15, 18, 16, 17 (eff1, eff2, eff5, eff3, eff4) 0x80a0 ×7 measured 0, 2, 4, 7, 1, 3, 5 (logo1×3, tm, logo2×3) ``` ### Refuted | candidate | result | |---|---| | declaration order | interleaves the logos (`0,1,2,3,4,5,7`); wrong | | **RATC child order** | groups the logos correctly but puts `tm` last, and leaves `0x8083` in `eff1..eff5`; **exact on the menu and splash, wrong on the title** | | first keyframe time | `0x8083` times are 52, 56, **62**, 58, 60 — the measured order is not sorted by them | | resting keyframe time | same shape, same failure | | resting X or Y | `0x8083` rests at x = 938, 938, 64, 788, 447 — unsorted either way | | `T8aD` header `+0x00`, `+0x04`, `+0x0c`, `+0x10` | identical within a group, or unsorted | Child order deserves a note: it is a *strict improvement* over declaration order (7 misplaced positions on the title instead of 9, and it recovers the logo1×3 / logo2×3 grouping), and it is exactly right on the two other screens. It was **not** adopted, because on the only screen that can tell the two apart it is still wrong, and a rule that is wrong there buys nothing a stable sort does not already give. ### What it costs, exactly `order_disagreements_that_change_pixels_are_pinned` measures the damage rather than assuming it. Across all three measured screens: * **3** disagreeing pairs of *drawn* elements (repeat instances and faded-out elements are skipped, so they cannot count); * all 3 have overlapping bounding boxes; * **2** actually share opaque pixels — `ptlogo_back2eff5` against `eff3` (22 568 px) and against `eff4` (32 395 px). The game paints `eff5` third in the group, the sort paints it fifth, and those blended glow pixels differ. The third pair is the instructive one. `ptlogo2` and `ptlogo_tm` overlap by two columns — the wordmark decodes 992 px wide and reaches x=1129, the trademark starts at x=1127 — but the wordmark is **fully transparent** there, so the order cannot matter. A bounding-box test called this a defect; reading the alpha says it is not. That is why the test reads pixels. So the residual error in the derived order is confined to **one element's blend on one screen**, and it is pinned: a change that makes it worse fails the test. ## Two more measured orders — and the first independent confirmation (2026-08-19) The three orders the derived rule was built from all live in `GP_TITLE.pak`, so they cannot confirm it: the rule was fitted to them. These two are from `GP_SAVE_LOAD.pak`, read off the running game after the Canary threading fix made the main menu reachable ([capture](../captures/load-game-oracle.png)). ### ✅ The slot-list header — exact, without being told anything `GP_SAVE_LOAD` ratc-indices 1/4/5/21/27, 9 elements: ``` measured 7 8 0 1 2 3 4 5 6 derived 7 8 0 1 2 3 4 5 6 EXACT, on all 6 instances ``` | element | key | |---|---| | 7 `pffocus1.rat`, 8 `pfslot1f_scr.rat` | `0xb102` **tied ×2** | | 0 `pftitlebase` | `0xb200` | | 1–5 `pfeff11`…`pftitle1` | `0xb210` **tied ×5** | | 6 `pfeff00.prm` | none → last | Both tied groups come out in **declaration order**, which is what the stable sort gives, and the unkeyed fade quad lands last. Nothing about this screen was fed into the rule. It is the first evidence that the layer key is a real ordering mechanism rather than a description of three screens in one pak. ### 🟡 The save/load frame — differs in exactly the two known ways `GP_SAVE_LOAD` ratc-index 18/24, 13 elements (`is_build` rejects it, so the compositor never sees it — but it is just as good as *evidence*): ``` measured 0 1 2 3 4 5 6 7 10 11 8 12 9 derived 2 3 4 5 6 7 8 10 11 12 9 0 1 ``` 1. **Unkeyed elements paint first, not last.** Elements 0 and 1 are `pfbase.tbm` — a `.tbm`, not a `.prm`, with no sprite and no key — and the game paints them before everything else. Exactly the splash's `palogo_eff0.prm` behaviour in a different file type, so `implied_layer_key` now covers it and the layer-key sequences agree. 2. **A tied group in a non-declaration order.** `0xb100` holds 8 `pfwin`, 10 `pfbtn0`, 11 `pfbtn1`, 12 `pfmsg`, and the game paints **10, 11, 8, 12**. That second point is the tie-break question again — and it is now on a *second* screen, which is what it needed. ### 🔴 Refuted: the tie-break is not `kind` The new group is suggestive: 10 and 11 are `kind = 0x2002` and 8 and 12 are `0x0000`, so "descending `kind`, then declaration index" reproduces `10, 11, 8, 12` exactly. It does not survive the title: * `0x8083` ×5 — every element is `kind = 0x0000`, so the rule predicts declaration order `14,15,16,17,18`; the game paints `14,15,18,16,17`. * `0x80a0` ×7 — kinds are `0x0` and `0x4`, so the rule predicts `2,3,4,5` then `0,1,7`; the game paints `0,2,4,7,1,3,5`. So `kind` is not it either, and the count of refuted candidates is now seven: declaration order, RATC child order, first keyframe time, resting time, resting X/Y, the `T8aD` header words, and `kind`. ## The corpus-wide census (2026-08-26) `tools/re-capture/paint_key_census.py`, output in [`../data/paint-key-census.txt`](../data/paint-key-census.txt). **All 21 184 `T8aD` sprites on the disc** — pak entries *and* `RATC` children. ### ⚠️ The first version of this section counted 4 525 sprites, and was retracted It filtered pak entries whose own first four bytes are `T8aD`. But a sprite is usually a **child of a `RATC` bundle**, and a bundle entry's magic is `RATC`, so a top-level magic filter cannot see one: | | sprites | distinct keys | |---|---|---| | top-level `T8aD` entries (what was counted) | 4 525 | 45 | | `T8aD` inside `RATC` bundles (what was missed) | **16 659** | **204** | | both | **21 184** | **216** | **171 of the 216 keys exist only inside bundles.** The sharpest way to put it: that census never saw **`GP_TITLE.pak` at all** — the pak holding both screens this page's evidence comes from. Every number it produced described a fifth of the corpus, chosen by an accident of container nesting. Retracted from it: "45 values", "the keys are pak-local", and "each auxiliary pak occupies its own narrow high-byte band". What survives is below. ### ✅ The field is a `u16` at `+0x0A`, not a word at `+0x08` The upper half of the 32-bit word this page reads is **zero in 21 184 / 21 184** sprites. Nothing above changes — `0x00008100` sorts identically to `0x8100` — but the port should read two bytes, and a value with the high half set would mean something has been misread rather than that the layer got deeper. ### ✅ It is an enumeration: 216 values for 21 184 sprites Not a per-sprite depth. The commonest key (`0x8000`) covers 1 768 sprites, `0x8100` another 1 524. ### 🔴 Refuted: the keys are **not** partitioned by pak This was the previous section's headline and the fuller population kills it. | | 4 525-sprite version | **all 21 184** | |---|---|---| | keys crossing a pak family | 4 / 45 = 9 % | **68 / 216 = 31 %** | | keys confined to `GP_MAIN_GAME_2D` | 33 / 45 | **52 / 216** | And the per-pak *ranges overlap heavily* rather than banding: `GP_BUNK` `0x8000`–`0xa110`, `GP_CHALLENGE` `0x8000`–`0x9200`, `GP_HANGAR_ARSENAL` `0x8000`–`0x9412`, `GP_LEADERBOARD` `0x8000`–`0xf100`, `GP_TITLE` `0x8000`–`0xc150`. The tidy "each pak owns a high-byte band" picture was an artifact of seeing one or two keys per pak. Only three paks are actually narrow (`GP_GAMEOVER` `0xd840`–`0xd862`, `GP_MISSION_LOG` `0xa400`–`0xa420`, `GP_SYSTEM` `0xa010`–`0xa112`). So the key is closer to a **shared vocabulary** than to a per-screen depth — the opposite of what the under-sampled census said. ❔ Which bits carry the layer remains unknown, and is now a question about 216 values rather than 45. ### ✅ The language paks are one screen set six times The six `GP_MAIN_GAME_*2D.pak` have **byte-for-byte identical key sets**. This is why counting paks rather than pak *families* first reported "37 of 45 keys appear in more than one pak" — a statement about localisation, not about the format. ### The recurring failure, stated once Three published numbers on this page were wrong the same way: each was computed over a population I had not checked was the population in question — the six language copies, then the top-level-only sprites, then the size of that gap. The analysis was never the problem. **Check the sampling frame before the statistic.** ## `EXTRAS` — the derived order against a capture nobody had taken (2026-08-28) The five orders above are all **measured**: the derived rule is checked against a live child list. This one is the other direction — a screen with **no measured order at all**, composited purely from the derived rule and scored against a framebuffer capture taken the same day ([capture](../captures/title-builds/live-extras.png), [composite](../captures/ui-layout/extras-composited.png)). `GP_TITLE.pak` build 6, 18 elements, two `.prm` primitives: ``` sylpheed-cli screen render --build 6 --black --primitives GP_TITLE.pak extras.png align_to_capture.py live-extras.png extras.png --region 150 100 1150 600 ``` | composite | best shift | score | |---|---|---| | **build 6, `--black --primitives`** | **(0, 0)** | **0.9620** | | build 6, no primitives | (0, 0) | 0.9576 | | *control:* main menu vs `main-menu-oracle.png`, same tooling | (0, 0) | 0.9657 | The control was run first and reproduces the screen this rule was fitted to, so the harness is known-good before the new screen is scored. `EXTRAS` lands within 0.004 of it, at zero shift, with the primitives helping rather than hurting. ### ⚠️ What this does and does not show **It does show** that every other part of the stack transfers to an unfitted screen: element enumeration, resting pose, pivots, `fade` alpha, and that the two primitives land somewhere harmless under the derived `implied_layer_key`. **It does not show that the paint order is right.** `align_to_capture.py` correlates *gradient magnitude* — edges — and a paint-order change moves blend values, not edges. The earlier derived-vs-declaration A/B on the pause menu and main menu measured exactly that: 1.1–3.8 % of pixels differing by ≤45/255, in translucent overlaps. A score computed this way cannot see a difference that size, so **0.9620 is evidence for placement and only consistent with the order.** **The sharp test, not run.** Composite build 6 twice — the committed derived order, and `compose` temporarily reverted to declaration order — and diff both against the capture over the pixels where they disagree. That needs a rebuild of `sylpheed-formats`, which is why it is named here rather than done; it is the same A/B this page already ran on the two screens that had oracles, and `EXTRAS` would be the first screen where the two orders can be judged against ground truth rather than declared indistinguishable. ## 🟡 A second flag in the same header: `+0x04`, bit `0x02` (2026-08-28) This page reads the layer key at `+0x0A`. The word at **`+0x04`** is a different flag word, and on the title screen it splits the sprites exactly along **effect versus normal**: | `+0x04` | title sprites | |---|---| | **`0x8832`** | `pteff01`, `pteff03a`, `ptlogo_back2eff1…5`, `ptlogoall_eff`, `ptlogoall_eff2` | | **`0x8830`** | `ptlogo1`, `ptlogo2`, `ptlogo_tm`, `ptbase2`, `ptlogo_back2`, `ptcopyright`, `ptlogo_back2eff` | One bit apart: **`0x02`**. **Disc-wide** (19 216 `T8aD` sprites, top-level and `RATC`-nested): 18 distinct values, and bit `0x02` is set in **27.1 %**. It varies independently of the rest of the word — `0x8830`/`0x8832`, `0x0830`/`0x0832`, `0x0810`/`0x0812` and `0x0030`/`0x0032` all occur — so it is a genuine flag, not part of a larger enumeration. **Why it matters:** the title's swoosh renders opaque white where the game draws a thin coloured stroke, and every other candidate is eliminated — position and size come from the texture and match, `fade` is white-with-alpha, `tint` is white, and the texture itself is blue-leaning ([`../ui-title-build-map.md`](../ui-title-build-map.md)). A per-sprite **blend mode** is what is left, and this is the only per-sprite field found that distinguishes the elements involved. ### 🔴 Tested — it does NOT select an additive blend Blending bit-`0x02` sprites additively and re-correlating against the title capture moved **every** measure the wrong way: | | alpha-over | additive | |---|---|---| | whole-frame mean diff | **+0.55** | +1.04 | | swoosh-band mean diff | **+1.83** | +3.98 | | swoosh-band edge-corr | **0.6971** | 0.5578 | So the bit is real and independent, but **additive is refuted**. `T8adImage` now carries the word as `flags`, documented and *not* acted on. ⚠️ **What remains is a correlation, not a decode.** The original test was against [`../captures/title-builds/live-title-build4-no-plate.png`](../captures/title-builds/live-title-build4-no-plate.png). Worth noting `ptlogo_back2eff` carries `0x8830` **despite** having `eff` in its name — so the split is the field's, not a naming pattern's. What the bit *does* mean is still unknown. --- ## 🟡 The `+0x04` bit `0x02`: two candidate meanings killed, attribution now sound **2026-08-29.** This page recorded the bit as a real independent field (set in 27.1 % of 19 216 sprites) with its "additive blend" reading refuted and its meaning ❔ *not diagnosed*, noting that `ptlogo_back2eff` is `0x8830` "despite its name". Two things here: that note rested on a **size match**, and the size is ambiguous. ### ✅ First, a sound way to attribute a header to a name `T8aD` headers appear in the bundle **in RATC child order**. Verified on `GP_TITLE` build 4 by an independent property — each header's decoded dimensions against the dimensions the named child should have: **18 of 18 match, 0 mismatches.** That matters because two of the eighteen share a size (`ptlogo_back2eff` and `ptlogo_back2eff5`, both 1133×280), so a size-keyed lookup cannot tell them apart — the trap [METHOD.md](../METHOD.md) already records for this corpus. Ordering resolves them: index 12 is `ptlogo_back2eff5` (`0x8832`, bit **set**), index 14 is `ptlogo_back2eff` (`0x8830`, bit **clear**). So the documented counterexample is **real and correctly attributed** — now on evidence rather than on a guess between two same-sized sprites. ### 🔴 Two candidate meanings, both refuted | hypothesis | verdict | |---|---| | bit ⟺ the name contains `eff` | **refuted** — `ptlogo_back2eff` is an `eff` name with the bit clear | | bit ⇒ the name contains `eff` (the surviving one-way reading) | **also refuted, disc-wide — see below.** It held 10/10 on build 4 and fails on **2 657 of 4 995** bit-set sprites across the disc | | bit ⟺ the element is transient (gone before the screen settles) | **refuted** — `pteff03`/`pteff03a` carry the bit and run to `t=250`, ramping to `a=255` and holding, i.e. they persist | ### 🟡 What the exception looks like up close `ptlogo_back2eff` and `ptlogo_back2eff5` are the same size and the same artwork, and differ in **two** header words: ``` ptlogo_back2eff5 +0x04 00008832 +0x08 00008083 (layer key 32899, bit SET) ptlogo_back2eff +0x04 00008830 +0x08 00008081 (layer key 32897, bit clear) ``` ⚠️ They are **not** duplicates: pixel-compared, `max abs diff 21`. Their alpha summaries are identical to one decimal (4.5 % opaque, 86.7 % clear, mean 19.3), which is precisely why the summaries were not trusted — two different renditions of one image at one size. So the bit tracks something that also distinguishes two versions of the same artwork, alongside a different layer key. ❔ **Still not diagnosed**, but the search space is smaller by two and the attribution underneath it is now sound. ### 🔴 The one-way implication is refuted too — build 4 was a local pattern Having fixed the attribution, the surviving reading was *"bit set ⇒ the name contains `eff`"*, true 10/10 on `GP_TITLE` build 4. Checked disc-wide with [`tools/re-capture/eff_bit_census.py`](../../../tools/re-capture/eff_bit_census.py) ([data](../data/eff-bit-census.txt)) over 14 709 sprites whose preceding name resolves: | | count | |---|---| | bit SET & name has `eff` | 2 338 | | **bit SET & name lacks `eff`** | **2 657** | | bit clear & name has `eff` | 1 399 | | bit clear & name lacks `eff` | 8 315 | `P(eff | set) = 0.468` against `P(eff | clear) = 0.144`. **The implication fails more often than it holds.** What survives is an *association* — the bit is 3.3× enriched for `eff` names — and one build's 10/10 was a local naming habit, not a rule. 🟡 The counterexamples are the interesting part: `pv_loading_ring0`, `pv_loading_light0`–`3`, `pv_loading_line`, `px_bunk_line`, `px_top_extra` — rings, glows, lights and thin lines. **Effect-like artwork that does not carry the `eff` naming convention.** Consistent with the bit marking effect sprites by authoring intent rather than by name, which is a description, not a decode. ⚠️ Name caveat: these names come from the string **immediately preceding** each `T8aD`, validated 17/18 on build 4 against the child order. The one mismatch is the known `pteff04.t32` → registered as `8AX` case, so this is the **element** (`opt `) name, not the sprite's registered name. ### 🔴 Premultiplied alpha — a fourth candidate, refuted A per-sprite flag controlling **premultiplied vs straight alpha** would matter enormously to a port (it changes the blend equation) and has a sharp static signature: a premultiplied texture has `RGB ≤ A` everywhere. [`tools/re-capture/eff_bit_alpha_test.py`](../../../tools/re-capture/eff_bit_alpha_test.py), over the 170 decoded `GP_TITLE` textures that pair to a flag word ([data](../data/eff-bit-alpha-test.txt)): | group | n | mean %(RGB>A) | median | |---|---|---|---| | bit SET | 61 | **55.5** | 52.5 | | bit clear | 109 | **33.7** | 30.2 | Premultiplied requires ~0 % for the flagged group. Both groups are far from it, and the flagged group violates **more** — the opposite of the hypothesis. **Refuted.** 🟡 What is left is a weak association: flagged sprites carry more bright-RGB / low-alpha pixels, which is what glow art looks like. But the distributions overlap badly — the best single threshold classifies **76.5 %** against a **64.1 %** base rate, a 12-point lift. That is a tendency, not a rule, and nothing to build on. ### ⚠️ One re-test worth remembering "`0x02` selects an additive blend" was refuted by *"blending those sprites additively worsens every measure against the capture"* — but that ran against a title render which has since been fixed twice (the `rest_plateau` bug, and the `8AX` background our composer drops). The refutation may still stand; it was simply measured through a renderer with known other errors. If `ui_layout::blit` ever gains additive blending, it is worth one re-run. ### 🛑 Parking this field Four candidate meanings are now dead — additive blend, `eff` name (both directions), transient element, premultiplied alpha — and none of the four searches produced a positive account. The bit blocks nothing: the port's screens composite at 0.947 correlation against a capture without it. **Parked**, with the search space narrowed and the attribution method (child order, not size) left sound for whoever picks it up. ## The reach of the draw-capture cross-check (2026-08-29) The orders above are read off the **live child list**; the draw captures confirm them. That confirmation is partial, and this is how partial — counted by [`order_crosscheck_reach.py`](../../../tools/re-capture/order_crosscheck_reach.py), data in [`order-crosscheck-reach.txt`](../data/order-crosscheck-reach.txt): | screen | captured frames | draws/frame | quads/frame | order length | positions the capture confirms | |---|---|---|---|---|---| | title (entry 4) | 3 + 2 | 8 | 12 | 24 | **7** named in [`ui-screen-runtime.md`](ui-screen-runtime.md) | | main menu (entry 8) | 2 | 9 / 7 | 16 / 9 | 16 | ≤ the richest frame | | splash (11/14) | 235 | — | — | 7 | within each half only | Three things this makes explicit, none of which changes an order: * **Count quads, not draws.** The menu's draw 9 is `indices=24` — **six** quads batched out of one texture. A per-draw count reads 9 where 16 are on screen, and an earlier pass of this analysis "found" three quads for six declarations that way and briefly concluded elements were missing. They were batched. * **The title capture is stable**: 8 draws / 12 quads / 5 textures, byte-for-byte the same census in every one of the five captured frames across two logs. So everything it shows co-occurs, and the 7 confirmed positions are mutually ordered by observation. The other 17 rest on the child-list read alone — which is the ground truth here, but it is one source, not two. (The census counts 8 draws where that page names 7 confirmed elements, so the capture carried a little more than it was read for — not chased, and not a claim either way.) * **The menu capture is not stable**: texture `0x11C30000` is in frame 0 and gone by frame 3. Its order is still confirmed as far as any single frame goes. * **The splash capture cannot cross-check its middle.** Glows (f94–115) and logos (f116–211) never share a frame, so no capture of this screen orders one against the other. The static layer key does (`0xa100` < `0xa110`), which is why the order stands — see the ❌ withdrawn section in [`ui-prm-primitives.md`](ui-prm-primitives.md).