ui-prm-primitives recorded that where a .prm paints on a screen without a measured order is unsolved. For the developer splash it is now measured. Every frame opens with two full-screen draws before any sprite. The second is untextured in all 212 frames with a constant vertex colour of FF000000 -- opaque black -- matching palogo_eff0.prm's declaration exactly: kind 0x10, pivot (640,360) -> 1280x720, one keyframe, a = 255. So the splash backdrop is an opaque black full-screen quad from the bundle itself, painted behind every sprite, which is why a splash render needs --black rather than the default backdrop. Not a general rule, and said so: the measured main-menu order puts pteff02.prm at position 4 and pteff00.prm LAST, the latter being the screen-transition fade. And a correction to an existing row. measured_paint_order returns [0, 2, 4, 6, 1, 3, 5] for the splash, described as "the .prm, then all three glows, then the three logos". But the glows and the logos never appear in the same frame -- 0 overlapping frames in 235 -- and two elements that never co-occur have no observable relative depth. Between those halves the vector records the order they were SEEN IN, not a front-to-back relationship. That does not make the render wrong, and element 0 is a real depth observation since the .prm co-occurs with everything. But the type of the claim matters: reading the vector as depth invites compositing all seven elements at once, which is exactly what does not reproduce the screen. METHOD: two things that never co-occur have no observable relative order; when recording an order, note which pairs actually appeared together.
11 KiB
.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:
- They carry no texture. Not one has a payload child; there is nothing in
the bundle for them to draw.
.prmis a primitive, not a sprite. - The format marks them.
kind & 0x10and the.prmextension 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. - 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.
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.
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.prmfirst — it is the black backdrop the logos sit on; - the title paints
pteff02.prmat slot 4, beneath the wordmark, andpteff00.prmlast, 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) 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+44is a button ordinal (1…5 on the menu's five buttons,0xffffffffeverywhere else). No key there. - the bundle carries no data at all for a primitive. The menu build declares
pteff00.prm,pteff02.prmandpteff05.t32and has zero RATC children for any of them — its 34 children are 21T8aDsprites and 13.ratrecords, 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: the 36 builds that
come out one colour are wiped by pzeff00.prm and pceff00.prm, whose positions
have never been measured, so they are not in the table.
What is not settled
- ❔ 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_DIALOGDIFFICULTY 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
0x7f000000is 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) is0x10plus0x3000, 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.
⚠️ And the splash's "measured paint order" is a SEQUENCE, not a depth order
measured_paint_order returns [0, 2, 4, 6, 1, 3, 5] for the splash, commented
"the full-screen .prm, then all three glows, then the three logos".
The draw capture shows the glows and the logos never appear in the same frame — 0 overlapping frames in 235 (group start time). Two elements that never co-occur have no observable relative depth. What that vector records between its glow half and its logo half is the temporal order they were seen in, not a front-to-back relationship.
🟡 This does not make the render wrong — compositing them in that order is
harmless, and the ✅ for element 0 (the .prm first) is a real depth
observation, since it co-occurs with everything. But the type of the claim
matters for a consumer: treating the vector as depth invites compositing all
seven elements at once, which is exactly what does not reproduce the screen.