cli/formats: let the screen commands reach bundles with no .rat child

The developer-logo splash declares its sprites directly and has no .rat layout
child, so is_build rejected it and no screen command could render it — despite
it being one of only two screens whose paint order has been read off the running
game, and the one where the layer key explains the whole permutation.

Adds ui_layout::is_composable (a declaration table plus at least one element
resolving to a T8aD the bundle carries) and an opt-in --all on screen
list/info/render. Measured on the disc: 2859 RATC bundles, 965 pass is_build,
2751 pass is_composable, and 0 pass is_build without passing it — a strict
superset. It is opt-in because the 1786 extra bundles are mostly two-element
fragments (a button and its glow), and because widening the default would
renumber --build for every pak, invalidating the build indices the corpus's
notes cite by number.

The splash now renders 6/7 elements, painting its glows first in the order
measured off the game; a disc test pins that order.
This commit is contained in:
Sylpheed RE agent
2026-08-19 06:03:52 +00:00
parent b28763db0b
commit a1c370e810
5 changed files with 210 additions and 10 deletions

View File

@@ -0,0 +1,86 @@
# A screen build is not the only thing `compose` can draw
**Status:**`CONFIRMED` by measurement over the disc, with the artifact to
show for it. 🟡 the wider set is not "all screens" — most of what it adds are
fragments. ❔ what distinguishes a screen from a fragment in the file is unknown.
## The gap
`ui_layout::is_build` — the predicate every screen command and every disc test
enumerates with — requires a `.rat` layout child:
```rust
ratc::is_ratc(bundle) && kids.iter().any(|c| c.name.ends_with(".rat"))
```
The **developer-logo splash** has none. Its elements name their `T8aD` sprites
directly (`palogo_gamearts.t32`), so there is nothing for a `.rat` record to
place, and `parse_build` handles it perfectly well — it was simply never reached.
That was worse than a missing screen. The splash is **one of only two screens
whose paint order has been read off the running game**
([`ui-paint-order-key.md`](ui-paint-order-key.md)), and it is the one where the
layer key explains the *whole* permutation. Its measurement could not be checked
against a render, because there was no way to produce one.
## The measurement
| | count |
|---|---|
| RATC bundles on the disc | 2 859 |
| pass `is_build` | 965 |
| pass `is_composable` (declaration table + a resolvable `T8aD`) | 2 751 |
| passing `is_build` but **not** `is_composable` | **0** |
So the new predicate is a strict superset, not a rival rule.
**But the 1 786 extra bundles are not 1 786 screens.** They are dominated by
two-element fragments — a button beside its glow:
```
GP_READY_ROOM.pak +792 GP_MAIN_GAME_*2D.pak +108 each (six languages)
GP_DEBRIEFING_PILOTLOG.pak +164 GP_HANGAR_ARSENAL.pak +96
e.g. 2 els: ["pvbtnnew.t32", "pvbtnneweff.t32"]
2 els: ["py_menu_new.t32", "py_menu_new_eff.t32"]
```
That is why `is_build` stays the default. Widening it would also **renumber
`--build`** for every pak, and the corpus's notes cite build indices by number
(`GP_TITLE` build 4, entries 11/14, `GP_OPTIONS` build 11) — silently shifting
them would invalidate written-down evidence.
## What landed
`ui_layout::is_composable`, and an opt-in `--all` on `screen list`, `screen info`
and `screen render`. Default behaviour and default numbering are unchanged.
```
$ sylpheed-cli screen render dat/GP_TITLE.pak splash.png --all --build 11 --black
build [11]: drew 6/7 elements
not drawn (1): ["palogo_eff0.prm"]
```
![the splash](../captures/ui-layout/developer-logo-splash-composed.png)
All three logos with their glows behind them. The seventh element is a `.prm`
primitive, which has no sprite and is skipped as everywhere else. A disc test
pins the draw order to `[2,4,6,1,3,5]` — the glows first — which is the order
measured off the running game, so the measurement is now checkable rather than
merely recorded.
Note this render also depends on the `_eff` fix
([`ui-focus-and-effect-elements.md`](ui-focus-and-effect-elements.md)): before
it, the three glows were dropped as "focused-state records" and the splash would
have rendered as three bare logos.
## What is not settled
***What makes a bundle a screen.** `is_composable` answers "can this be
drawn", not "is this a screen the game shows". Element count is a crude proxy
(fragments are 25 elements) and has not been checked against anything.
***The `.prm` primitives.** `palogo_eff0.prm` is a full-screen element with
pivot (640,360) and a single keyframe; nothing decodes `.prm` yet, so every
composite is missing whatever they draw. On the splash the measured paint order
puts it **first**, i.e. it is the backdrop.
* 🟡 The splash render has not been diffed against a framebuffer capture. It is
now *possible* to, which it was not before.