Files
Sylpheed/docs/re/structures/ui-forced-backdrop.md
sylph-decoder b6ae95a732 re: the forced-backdrop span -- 256 vs 211 is a bundle mismatch, and the hold
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
2026-08-29 21:14:58 +00:00

7.3 KiB

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 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 onepalogo_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), and 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: 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

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