formats/cli: draw the .prm primitives, opt-in, because their paint order is unsolved

fill_quad composites an untextured primitive as a solid rectangle of the
keyframes fade colour, pivot x 2 in size, placed and scaled exactly as a sprite
is. Behind ComposeOptions::include_primitives and screen render --primitives.

On the title screen, whose paint order is ground truth, it is measurably right:
mean luminance 76.30 -> 63.72 against the captures 64.58, i.e. from +18% to
-1.3%, and mean absolute difference 16.07 -> 13.08. The background was ~40% too
bright; pteff02.prm, a 25% black dim, was what was missing. The wordmark is not
dimmed by it because the measured order paints that quad at slot 4, beneath the
logo. Edge correlation moves 0.9538 -> 0.9480, which is not informative here: a
uniform dim scales gradients uniformly so a normalised edge score barely sees it.

OFF BY DEFAULT, and that is the finding. A primitive has no T8aD header, so no
layer key, and derived_paint_order sorts the keyless to the end. GP_DIALOGs
pzeff00.prm is a single keyframe of opaque black at full screen; painted last it
wipes the build. Of the 125 builds that draw a visible primitive, 36 come out
>99% one colour with the derived order.

No constant default works, because the two screens read off the running game
disagree: the splash paints its primitive FIRST (the black backdrop) while the
title paints one at slot 4 and another LAST (the fade-out). Declaration order
fails the title too. A disc test measures the damage rather than asserting the
feature works, so the number moves when the ordering is solved.

Also records a false alarm worth keeping: a first pass reported 36 GP_DIALOG
builds at "100% black", which was a crude near-black pixel threshold and not a
black screen - those dialogs are dimmed 50% and perfectly legible. The genuinely
wiped builds are a different set.
This commit is contained in:
Sylpheed RE agent
2026-08-19 07:21:32 +00:00
parent 6b65f7ea91
commit eb86142e4f
6 changed files with 243 additions and 32 deletions

View File

@@ -181,6 +181,12 @@ enum ScreenCommands {
/// what a framebuffer capture must be compared against.
#[arg(long)]
black: bool,
/// Draw the untextured `.prm` primitives (fade / dim / flash quads).
/// Off by default: they are decoded, but where they paint on a screen
/// without a measured order is unsolved — see
/// `docs/re/structures/ui-prm-primitives.md`.
#[arg(long)]
primitives: bool,
/// Widen the list from screen builds to **every composable bundle** —
/// including the ones with no `.rat` layout child, such as the
/// developer-logo splash. 2 859 RATC bundles on the disc compose; only
@@ -346,9 +352,10 @@ async fn main() -> Result<()> {
animated,
black,
all,
} => {
cmd_screen_render(&pak, &output, build, focus, animated, black, all)
}
primitives,
} => cmd_screen_render(
&pak, &output, build, focus, animated, black, all, primitives,
),
},
Commands::Save { cmd } => match cmd {
SaveCommands::Info { file, all } => cmd_save_info(&file, all),
@@ -568,6 +575,7 @@ fn cmd_screen_render(
animated: bool,
black: bool,
all: bool,
primitives: bool,
) -> Result<()> {
use sylpheed_formats::ui_layout::{self, ComposeOptions};
let builds = screen_builds(pak, all)?;
@@ -585,6 +593,7 @@ fn cmd_screen_render(
} else {
ComposeOptions::default().backdrop
},
include_primitives: primitives,
},
None,
);