# `.prm` elements are untextured full-screen quads, and `kind` bit `0x10` says so **Status:** βœ… `CONFIRMED` statically across all 965 screen builds on the disc. πŸ”΄ **Drawing them at `Element::rest()` is refuted** β€” it would black out the title screen. ❔ the resting rule for a fade group is unsolved, and that is the blocker on actually compositing them. ## What they are Every composite the port produces is missing its `.prm` elements: they resolve to no sprite, so `compose` skips them. 369 of them exist. Swept over the disc: | | | |---|---| | `.prm` elements in screen builds | **369** | | …with a RATC child of their own name (a payload) | **0** | | …with `kind & 0x10` set | **369** | | non-`.prm` elements with `kind & 0x10` set | **0** | | …with `pivot Γ— 2 == 1280Γ—720` (the design space) | **361** | | …resting at scale 100 %, position (0,0) | 361 / 356 | Three things follow, none of them inferred: 1. **They carry no texture.** Not one has a payload child; there is nothing in the bundle for them to draw. `.prm` is a primitive, not a sprite. 2. **The format marks them.** `kind & 0x10` and the `.prm` extension agree with **zero exceptions in either direction**. A port can classify an element as a primitive from the declaration table alone, without parsing filenames β€” which is what a decoded field is for. 3. **They are screen-sized colour fills.** 361 of 369 are exactly the design space at 1:1 in the corner. The 8 that are not are small coloured quads (844Γ—600 at (291,60), and two degenerate 0Γ—720). ## Where the colour is The keyframe's `fade` word, ARGB β€” the field named for the alpha ramp it drives on a sprite. On a `.prm` there is no sprite to modulate, so it *is* the fill. Resting values across the disc: ``` 189 0x00000000 (transparent β€” nothing on screen) 102 0xff000000 (opaque black) 32 0x7f000000 6 0x60ff0000 (red) 6 0x0000ffd4 (cyan) 20 other blacks 6 0x008000ff (violet) 2 0xf0ffffff (near-white flash) ``` Overwhelmingly **black at some alpha**: these are the screen's fade-to-black, dim-behind-a-menu and flash layers. That matches the standing note on `ComposeOptions::backdrop`, which describes the compositor's dim slate as a stand-in for "the PRMD dim-quad" β€” this is that quad, and it is in the file. ## Drawing them (2026-08-19) `fill_quad` composites a primitive as a solid rectangle of the keyframe's `fade` colour, `pivot Γ— 2` in size, placed and scaled exactly as a sprite is. It is behind `ComposeOptions::include_primitives` / `screen render --primitives`, and **off by default** β€” for a reason that is itself the result of this iteration. On the title screen, whose paint order is ground truth, it is measurably right: | composite | mean luminance | vs capture | mean abs diff | |---|---|---|---| | primitives off | 76.30 | **+18 %** | 16.07 | | primitives on | **63.72** | **βˆ’1.3 %** | **13.08** | (capture mean 64.58). The composite was ~40 % too bright in the background regions; `pteff02.prm`, a 25 % black dim, is what was missing. The wordmark is *not* dimmed by it β€” the measured order paints that quad at slot 4, beneath the logo β€” and the fraction of wordmark pixels above 200 moves 29.9 % β†’ 28.2 % against the capture's 26.8 %. [The composite](../captures/ui-layout/title-composited-with-primitives.png). Edge correlation goes 0.9538 β†’ 0.9480, which sounds like a loss and is not informative: a uniform dim scales gradients uniformly, so a normalised edge correlation barely sees it. Brightness is the metric that discriminates here, and it moves 20 percentage points toward the capture. ## Refuted twice, and the second one is the blocker **Refuted: you cannot draw them at `Element::rest()`** β€” as the old longest-dwell rule computed it. The title's `pteff00.prm` runs opaque β†’ transparent β†’ transparent β†’ opaque, a screen transition whose resting pose is the transparent plateau, and it paints **last**; the old rule picked the opaque endpoint, i.e. the whole screen. That is fixed β€” [`ui-resting-pose.md`](ui-resting-pose.md). **Refuted: the derived paint order does not place them.** This is what keeps the flag off. A primitive has no `T8aD` header, so it has **no layer key**, and `derived_paint_order` sorts the keyless to the very end. `GP_DIALOG`'s `pzeff00.prm` is a *single* keyframe of opaque black at full screen; painted last it wipes the build. Measured: of the 125 builds that draw a visible primitive, **36 come out more than 99 % one colour** with the derived order. And no simple default fixes it, because the two screens read off the running game disagree with each other: * the developer-logo splash paints `palogo_eff0.prm` **first** β€” it is the black backdrop the logos sit on; * the title paints `pteff02.prm` at slot **4**, beneath the wordmark, and `pteff00.prm` **last**, as the fade-out. So a primitive's position is real, per-element, and not derivable from anything decoded so far. "Primitives first" would break the title's fade-out; "primitives last" wipes 36 builds; "keep declaration position" was checked against the title and fails there too β€” `pteff00.prm` is element 8, declared among the wordmarks, and the game paints it 23rd. A disc test measures the damage (36 / 125 / 0-by-default) rather than asserting the feature works, so the number stays honest and changes when the ordering is solved. Worth seeing anyway: the DIFFICULTY dialog ([capture](../captures/ui-layout/dialog-difficulty-with-primitives.png)) renders legibly with its 50 % dim β€” an early false alarm said 36 `GP_DIALOG` builds went "100 % black", which was a crude near-black pixel threshold, not a black screen. The genuinely wiped ones are a different set, wiped by an *opaque* quad. ## What is not settled ## Where a primitive paints β€” refuted in the file, measured in the game (2026-08-19) **πŸ”΄ Refuted: the key is not in the bundle.** Two places were checked and both are empty: * the **declaration entry**'s four unread words are constant across every element of all three measured screens β€” `+28` = 0, `+36` = `0xffffffff`, `+56` = 0, and `+44` is a button ordinal (1…5 on the menu's five buttons, `0xffffffff` everywhere else). No key there. * the **bundle carries no data at all** for a primitive. The menu build declares `pteff00.prm`, `pteff02.prm` and `pteff05.t32` and has **zero** RATC children for any of them β€” its 34 children are 21 `T8aD` sprites and 13 `.rat` records, none of them named for a primitive. So the layer a primitive draws on comes from the game's own code, not the file. **βœ… But it is consistent, which makes a per-name table honest.** Bracketing each unkeyed element between its measured neighbours' keys: | element | title screen | main menu | splash | |---|---|---|---| | `pteff05.t32` | β€” | (0x8010, 0x8040) | β€” | | `pteff04.t32` | (0x8010, 0x8040) | β€” | β€” | | `pteff02.prm` | **(0x8010, 0x8040)** | **(0x8010, 0x8040)** | β€” | | `pteff00.prm` | (0x8100, end) | (0x8110, end) | β€” | | `palogo_eff0.prm` | β€” | β€” | (start, 0xa100) | `pteff02.prm` falls in the **same interval on both** screens it appears on, the fade quad is past the maximum on both, and the splash backdrop is below the minimum. `ui_layout::implied_layer_key` records exactly these, and nothing more β€” an unlisted primitive keeps `u32::MAX` and still sorts last. With it, `derived_paint_order` produces **the same layer-key sequence as the order read off the running game on all three measured screens**, primitives included, and matches element-for-element on four of the five bundle instances (the fifth is the title, which differs only inside its tied groups β€” a separate open question). Pinned by `the_derived_order_puts_every_element_in_the_right_layer_group`. This does **not** make `include_primitives` safe by default β€” but the 36 builds that come out one colour are now explained: `pzeff00.prm` is **forced first** in 32 of 32 instances by the occlusion constraint, so they were wiped by *our own sort*, not by the game. [`ui-forced-backdrop.md`](ui-forced-backdrop.md). ## What is not settled * βœ… **Where an *unmeasured* primitive paints β€” PARTLY CLOSED 2026-08-29.** For **80** instances the file forces it: an element covering the screen and fully opaque at some instant cannot paint above anything visible then, and where that is *every* other element its position is first. It reproduces `palogo_eff0.prm` (measured FIRST, and named like an overlay, so a name-based rule fails it) and permits `pteff00.prm` on top (measured LAST). βœ… It also explains the 36 builds below that "come out one colour": `pzeff00.prm` is forced first in 32 of 32. See [`ui-forced-backdrop.md`](ui-forced-backdrop.md). * ❔ **Where a *partly*-opaque primitive paints.** Still open for the 50 the constraint does not bind β€” including `pteff00.prm`, whose place on top remains a measured per-name entry rather than a decoded one. * ~~❔ **Where an *unmeasured* primitive paints.** The blocker, unchanged for the ones not in the table.~~ It has no layer key and the two measured screens rule out every constant default. The cheapest next step is a third measured order from a screen that carries a primitive β€” the `GP_DIALOG` DIFFICULTY box is reachable from the main menu and has exactly one, so a runtime child-list read there would say whether its dim is first or last. * ❔ **Blend mode.** A dim quad at `0x7f000000` is presumably straight alpha over what is beneath, but the flash (`0xf0ffffff`) and the coloured ones (`0x60ff0000`) may well be additive. Nothing measured. * ❔ **The 8 non-full-screen ones**, including two with a zero dimension. * 🟑 `kind = 0x3010` (38 elements) is `0x10` plus `0x3000`, the button-record bits β€” a primitive that is part of a button. Unexamined. --- ## βœ… Measured on the splash: the `.prm` paints FIRST, full-screen, opaque black **2026-08-29.** This page recorded that where a `.prm` paints "on a screen without a measured order is unsolved". For the developer splash it is now measured, from the 235-frame draw capture. Every frame begins with two full-screen draws before any sprite: ``` f120: [1280x720] [1280x720] [499x241] … ^clear ^this one ``` The second is **untextured** in all **212** frames, with a constant vertex colour of **`FF000000`** β€” opaque black. That matches `palogo_eff0.prm`'s declaration exactly: | declared | observed | |---|---| | `kind = 0x10` (the primitive marker) | untextured draw | | pivot (640,360) β†’ **1280Γ—720** | 1280Γ—720 quad | | **one** keyframe, `a = 255` | constant across 212 frames | | β€” | vertex colour `FF000000` | So the splash's backdrop is an **opaque black full-screen quad from the bundle itself**, painted behind every sprite β€” not a clear colour, which is why our splash renders need `--black` to match. ⚠️ **Not a general rule.** The measured *main menu* order puts `pteff02.prm` at position 4 and `pteff00.prm` **last** β€” that one is the screen-transition fade. A `.prm` paints where its screen's order says; the splash's happens to be first. ## ❌ WITHDRAWN (2026-08-29, same day): "the splash's measured order is a sequence, not depth" This page briefly claimed that `measured_paint_order`'s `[0, 2, 4, 6, 1, 3, 5]` records, between its glow half and its logo half, the *temporal* order they were seen in rather than a depth order β€” on the grounds that the two halves never share a frame (0 overlap in 235, which is [measured and still true](../data/splash-phase-timeline.txt): glows f94–115, logos f116–211). **The measurement was right and the conclusion was wrong**, on two independent grounds, either of which is fatal: * **Wrong source.** The vector was never read off the draw capture. It is a read of the live screen object's **child array** β€” [`ui-screen-runtime.md`](ui-screen-runtime.md) records it literally as `paint order (child slots): 0 2 4 6 1 3 5`. A child list has a definite order whether or not any two of its children are ever drawn together, so a co-occurrence argument cannot touch it. The capture was the *cross-check*, not the source. That page had already stated the limit honestly for the title build: the `kind = 0x4` instances "draw at Ξ±=0 and so never appeared in the capture, but their placement in the list is consistent with the grouping". * **The order is in the file anyway.** `examples/paint_order_audit` on `GP_TITLE.pak` entry 11: `derived == measured : YES`, `inverted pairs: 0`, **`same-layer-key ties: 0`**. The glows and the logos carry *distinct* T8aD layer keys (`0xa100` vs `0xa110` β€” [key](ui-paint-order-key.md)), so the glow half sorts before the logo half **statically**, with no capture involved. The relationship I called unobservable is decoded. What survives is narrower and worth keeping: **a draw capture of this screen cannot cross-check the glow-vs-logo half of the order**, because the two halves never co-occur. The capture validates the order *within* each half only. So the line in [`ui-paint-order-key.md`](ui-paint-order-key.md) β€” "read off the live child list, checked against a draw capture" β€” has that reach limit on the splash. The order itself is not in doubt; the *capture's* power to confirm all of it is. The βœ… above for element 0 is unaffected: it is a direct draw observation.