decides 55% of verdicts The port implemented the forced-backdrop rule and reported a discrepancy: palogo_eff0.prm at 256 opaque instants against this corpus's 211. There is no discrepancy. palogo_eff0.prm appears on BOTH splashes -- the publisher (entries 10, 13) runs to t=255, giving 256 instants; the developer (11, 14) runs to t=210, giving 211. Same definition, different bundle. The page now names the entries so it cannot recur. The definition, stated: 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 which is decoded rather than assumed (a group holds at its last keyframe rather than looping; the declared +0x08 never falls short of the last keyframe, the slack being that hold). The port's instinct that the hold was load-bearing was right. Over the 130 keyless full-screen primitives with an opaque interval: * span = the header's declared +0x08 -> 0 verdicts change * span = the primitive's own last keyframe -> 72 change * elements GONE after their last keyframe -> 72 change So the hold decides 55% of verdicts -- and dropping it is REFUTED by a measured order. palogo_eff0.prm is a single keyframe at t=0: without the hold it is opaque for one instant, no other element is up yet, and the rule calls it free, against a game measured painting it first. Pinned by a new test that spells out the counterfactual rather than importing it. The verdicts that matter are convention-independent: pgloading_eff00.prm is FIRST under all four conventions and pteff00.prm FREE under all four. And the header length is interchangeable with the elements' maximum -- zero disagreements disc-wide. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QsEPXWVaEpyfudtR6re1Pd
156 lines
7.3 KiB
Markdown
156 lines
7.3 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.
|
|
|
|
## 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
|
|
```
|