A disc-wide census of the ARGB that keyless elements carry. Every full-screen *eff00* PRIMITIVE is pure black at its various alphas (ff000000, 7f000000, 40000000, b2000000, cc000000, d4000000, 00000000). Black at alpha a over content is exactly an alpha-over dim or fade, and an additive black quad would be a no-op nobody would author -- so this narrows the open blend question a long way. The only non-black primitive on the disc is pbafc.prm, RGB 00e8e0 cyan at alphas up to ff, and it is 844x600, NOT full-screen, so it sits outside forced_backdrop's geometry guard. It is now the sole additive candidate. The census also refutes my own argument for nearly half its verdicts. Of the 80 forced-first instances only 42 are .prm; 38 are .tbm carrying fade ffffffff. A SOLID white quad at alpha 255 painted first would make the screen white, and no screen is white -- so a .tbm is a white modulation on a texture, and element alpha does not establish its coverage. That is the .t32 error one file extension further out. I guarded that with el.sprite.is_some(), which fixed the symptom and not the cause: an element's alpha is not its texture's opacity, and only an untextured primitive makes the two the same fact. So 42 verdicts stay decoded and 38 drop to inferred -- still almost certainly right, since all are named *base*, all are full-screen, and pfbase.tbm's first position is measured in the running game, but that is a name-and-role argument which this page elsewhere calls the weaker kind. The code is deliberately unchanged. Restricting forced_backdrop to .prm would send eleven screens' backgrounds back to u32::MAX -- last -- which is the blank-screen bug the rule was written to fix. Downgrading the status is honest; reverting the position would be wrong. The 42/38 split is pinned by a test so anyone tightening the rule sees what it costs. Separately, on the port's black_hold_units ask: four more no-input boots yielded one usable log, which armed late and missed the publisher splash, so the sample is still two runs spanning 3 and 4 frames. Their 6.5-9.2 range stands. And a reason it may not be resolvable this way: the draw log DROPS frame numbers -- in the 3-frame run, frames 121 and 124 are absent entirely, so "frames with no sprite" and "span of frame numbers" are different quantities. Their statistical correction is taken: at n=3 the sample SD (3.893) is the estimator, not the population SD (3.179), making my run 1.88 sigma from the corpus mean rather than 2.31. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QsEPXWVaEpyfudtR6re1Pd
200 lines
9.9 KiB
Markdown
200 lines
9.9 KiB
Markdown
# 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.
|
||
|
||
## 🔴 Self-refutation: the argument is sound for only 42 of the 80
|
||
|
||
A disc-wide census of the *colour* these elements carry breaks the rule's premise
|
||
for nearly half its verdicts.
|
||
|
||
| the 80 forced-first instances | count | fade ARGB |
|
||
|---|---|---|
|
||
| `.prm` — untextured solid quads | **42** | pure black (`ff000000`, `7f000000`, `40000000`, …) |
|
||
| `.tbm` | **38** | **`ffffffff`** — white at full alpha |
|
||
|
||
**A solid white quad at alpha 255 painted first would make the screen white.** No
|
||
screen is white. So a `.tbm` is not a solid quad: `ffffffff` is a white
|
||
*modulation* on a texture, which is exactly what a background bitmap carries.
|
||
|
||
🔴 **And that means element alpha does not establish coverage for them** — the same
|
||
error the `.t32` guard already caught, one file extension further out. I fixed that
|
||
symptom (`el.sprite.is_some()`) rather than its cause: **an element's alpha is not
|
||
its texture's opacity, and only an untextured primitive makes the two the same
|
||
thing.**
|
||
|
||
**What this does and does not change:**
|
||
|
||
* ✅ The **42 `.prm`** verdicts stand as decoded. For a solid colour quad the fade
|
||
*is* the pixel, so opacity and coverage are the same fact.
|
||
* 🟡 The **38 `.tbm`** verdicts are **not** decoded. They are almost certainly still
|
||
right — every one is named `*base*`, is full-screen, and one of them
|
||
(`pfbase.tbm`) has its first position **measured in the running game** — but that
|
||
is a name-and-role argument, which this page elsewhere argues is the weaker kind.
|
||
* ⚠️ **The code is deliberately unchanged.** Restricting `forced_backdrop` to
|
||
`.prm` would send `pcbase`, `pnbase`, `pqbase`, `pubase`, `pvbase`, `pjbgbase2`,
|
||
`po_menu_base` and the four `px_*_base` back to `u32::MAX` — last — which is the
|
||
blank-screen bug this rule was written to fix. Downgrading their *status* is
|
||
honest; reverting their *position* would be wrong.
|
||
|
||
## 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.
|
||
|
||
✅ **The colour census narrows this a long way.** Every full-screen `*eff00*`
|
||
primitive on the disc is **pure black** at its various alphas — `ff000000`,
|
||
`7f000000`, `40000000`, `b2000000`, `cc000000`, `d4000000`, `00000000`. Black at
|
||
alpha *a* over content is exactly what an alpha-over dim or fade looks like, and an
|
||
*additive* black quad would be a no-op, so a designer would not author one. The
|
||
single non-black primitive on the disc is **`pbafc.prm`**, RGB `00e8e0` (cyan) at
|
||
alphas up to `ff` — and it is **844×600, not full-screen**, so it is outside this
|
||
rule's geometry guard entirely. ❔ Whether *it* is additive is still open, and it is
|
||
now the only candidate.
|
||
|
||
⚠️ **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
|
||
```
|