# Where a keyless primitive paints, when the file forces it **Classification: decoded.** Derived from the keyframes and the element's own geometry, checked disc-wide, and validated against **both** primitives whose position was measured in the running game — one it must reproduce, one it must not disturb. ## The question A `.prm` / `.tbm` primitive carries **no layer key**: the key is read from a sprite's header, and a primitive has no sprite. [`ui-prm-primitives.md`](ui-prm-primitives.md) established that the key is not in the bundle either — the declaration entry's unread words are constant, and the bundle has no RATC child for a primitive — so `implied_layer_key` is a **measured per-name table**, and anything not in it keeps `u32::MAX` and sorts **last**. "❔ Where an *unmeasured* primitive paints" has been that page's standing blocker. The port hit it: `build_12` / `build_15` composited to solid black at every instant of their declared life, because `pgloading_eff00.prm` — a full-screen opaque quad — sorted on top. ## The rule > **An element that covers the screen and is fully opaque at some instant cannot > paint above anything visible at that instant.** Where the elements visible > during its opaque span are *all* of them, its position is forced to first. This is a constraint read off the file, not a preference, and it is not an analogy to a neighbouring screen. `pgloading_eff00.prm` is opaque for **39** instants, and **all 9** other elements on the loading screen are visible inside that span. **Forced first**, 4/4 instances. ## The controls — the rule has to survive both, and it does | primitive | measured in the game | opaque instants | forced below | rule says | |---|---|---|---|---| | `palogo_eff0.prm` (entry 11, developer) | **paints FIRST** | 211 | **6 of 6** | ✅ forced first | | `palogo_eff0.prm` (entry 10/13, publisher) | **paints FIRST** | 256 | 2 of 2 | ✅ forced first | | `pteff00.prm` (title) | **paints LAST** | 2 | 3 of 23 | ✅ permitted on top | | `pteff00.prm` (menu) | **paints LAST** | 2 | 7 of 15 | ✅ permitted on top | 🔴 **The first row is the one that matters.** `palogo_eff0.prm` is named like an overlay, and a rule keyed on the *name* would sort it last — against a measured order. Occlusion gets it right. `pteff00.prm` is opaque only for two instants, at its screen's entry and exit, so the constraint never binds it: it is the fade cover, and it belongs on top. ## The span, and what it costs to get wrong The rule quantifies over "every instant the primitive is opaque" and "every element visible then", so it depends on where a screen's timeline ends and on what an element does after its own last keyframe. The port asked, having got **256** opaque instants for `palogo_eff0.prm` where this page said 211. ⚠️ **That pair was a bundle mismatch, not a definitional one** — `palogo_eff0.prm` appears on both splashes, and the publisher (entries 10, 13) runs to t=255 while the developer (11, 14) runs to t=210. 256 and 211 are both right, for their own screen. The definitions already agreed. **The definition:** the span is `0 ..= max keyframe time over every element in the build`, and an element **holds its final pose** past its own last keyframe — which is what `pose_at` does, and it is decoded rather than assumed: a group holds at its last keyframe rather than looping ([`ui-keyframe-time-unit.md`](../ui-keyframe-time-unit.md)), and [`ui-record-loop-length.md`](ui-record-loop-length.md) shows the declared length never falls short of the last keyframe, the slack being exactly that hold. **How much rests on it — 130 keyless full-screen primitives with an opaque interval, and how many verdicts change:** | alternative convention | verdicts changed | |---|---| | span = the bundle header's declared `+0x08` | **0** | | span = the primitive's **own** last keyframe | 72 | | elements counted **gone** after their last keyframe (no hold) | **72** | 🔴 **The hold decides 55 % of the verdicts, and dropping it is refuted by a measured order.** `palogo_eff0.prm` is a *single* keyframe at t=0. Without the hold it would be opaque for one instant, no other element would be up yet, and the rule would call it **free** — against the order measured in the running game, which paints it first. Pinned by `the_hold_after_a_final_keyframe_is_required_by_a_measured_order`. ✅ **And the header length is interchangeable with the elements' maximum**: zero disagreements disc-wide. Either may be used. ✅ **The verdicts that matter are convention-independent.** `pgloading_eff00.prm` comes out **first** under all four conventions; `pteff00.prm` comes out **free** under all four. Only `palogo_eff0.prm` moves, and only under the convention its own measured order rules out. ## Disc-wide Keyless **full-screen** primitives with an opaque interval: | | count | |---|---| | position **forced first** | **80** | | constrained below some, not all | 50 | | occluding nothing | 0 | The split falls almost exactly along the names — every `*base*` is forced, every `*eff00*` is not — with three families crossing it: `palogo_eff0.prm`, `pgloading_eff00.prm` and `pzeff00.prm` are named like overlays and are forced first. **That is precisely why the name is not the rule.** ✅ **And it explains a symptom the corpus had recorded without a cause.** `ui-prm-primitives.md` notes 36 builds that "come out one colour ... wiped by `pzeff00.prm` and `pceff00.prm`, whose positions have never been measured". `pzeff00.prm` is forced first in **32 of 32** instances. Those builds were wiped by our own sort, not by the game. ## 🔴 The rule's real limit, found by its own test An earlier version applied to any element. Its disc-wide test asserted that no *keyed* element is ever forced — and that assertion failed, on **22** of them: `pneff01.t32` (key `0xd850`, paints #8 of 13) and `pbfriendly.t32` (key `0x9230`, #17 of 49). Both are `.t32` **sprites**, and that is the flaw: a sprite's *element* alpha being 255 says nothing about whether its **texture** covers the screen. Most of it may be transparent. The disagreements were the rule overreaching, not the keys being wrong. `forced_backdrop` is now restricted to elements with **no sprite** — untextured primitives, which are solid quads and do occlude what they cover. That is also the only case `derived_paint_order` consults it for. ## Reach ⚠️ **Assumes straight alpha-over blending.** Blend mode is ❔ on [`ui-prm-primitives.md`](ui-prm-primitives.md): an *additive* quad at alpha 255 would not occlude, and the rule would then be placing it wrongly. The `palogo_eff0.prm` control is evidence the assumption holds at least there. ⚠️ **It gives a lower bound, not an ordering.** It settles the 80 instances where occlusion forces the position, and says nothing about the 50 where the primitive is opaque only part of the time — including `pteff00.prm`, whose place on top is still a *measured* per-name entry, not a decoded one. ⚠️ **No new oracle measurement.** The two controls are orders measured previously; nothing here was captured from a running game. A draw capture of a loading screen would confirm it directly, and the loading screens are not reachable from the title path. ## Reproducing ```bash cargo run -p sylpheed-formats --example prm_forced_first cargo run -p sylpheed-formats --example prm_occlusion_check SYLPHEED_DISC=/disc cargo test -p sylpheed-formats --test ui_forced_backdrop_disc ```