formats: a UI element scales about its pivot, not its keyframe corner

The compositor read a keyframe as `top-left = (X,Y)`, `size = decoded · scale`
and ignored the declared pivot. That is right at 100 %, which is every element
the format was ever checked against — the pause menu, the ARSENAL chip ruler —
and wrong for every element that is scaled.

Measured against a framebuffer capture of Canary on the title screen.
`GP_TITLE.pak` build 7 element 13 is `ptbase2.t32`: 640x360, pivot (320,180),
one keyframe at (320,180) with scale 200 %. From the corner that is a 1280x720
rect at 320..1600 x 180..900 — a quarter-screen slab with the top-left quadrant
bare. Anchored at the pivot it is (0,0)..(1280,720), and the capture shows the
background art reaching all four edges. Normalised cross-correlation of the
composite against the capture, searched over +-40 px, peaks at (0,0): 0.90 on
the planet limb, 0.72 on the lower-left ship.

`ptcopyright.t32` calibrates the other half: unscaled, 694x20 at (293,655), and
the capture's glyph run is x 295..986 / y 700..718 once the 45 px of window
chrome is taken off. So the keyframe really is the top-left at 1:1.

Disc-wide this moves 865 of 5 130 resting placements. The pause menu's own
`pgpeff01` glow stops hanging off the menu frame to the bottom-right and
centres on it.

`ComposeOptions::backdrop` comes with it: the default dim slate stands in for
the PRMD dim-quad behind an in-mission screen, but comparing against a
framebuffer needs the black the game actually composites over, so
`screen render --black` can ask for it.
This commit is contained in:
Sylpheed RE agent
2026-08-18 16:20:26 +00:00
parent cb6205ccfb
commit e6a55b5fd4
3 changed files with 116 additions and 11 deletions

View File

@@ -153,6 +153,11 @@ enum ScreenCommands {
/// Draw `loop*` sprite animations
#[arg(long)]
animated: bool,
/// Start the canvas black instead of the default dim slate — what the
/// game composites over on a screen carrying its own background, and so
/// what a framebuffer capture must be compared against.
#[arg(long)]
black: bool,
},
}
@@ -297,8 +302,15 @@ async fn main() -> Result<()> {
Commands::Screen { cmd } => match cmd {
ScreenCommands::List { pak } => cmd_screen_list(&pak),
ScreenCommands::Info { pak, build } => cmd_screen_info(&pak, build),
ScreenCommands::Render { pak, output, build, focus, animated } => {
cmd_screen_render(&pak, &output, build, focus, animated)
ScreenCommands::Render {
pak,
output,
build,
focus,
animated,
black,
} => {
cmd_screen_render(&pak, &output, build, focus, animated, black)
}
},
Commands::Save { cmd } => match cmd {
@@ -433,6 +445,7 @@ fn cmd_screen_render(
want: Option<usize>,
focus: bool,
animated: bool,
black: bool,
) -> Result<()> {
use sylpheed_formats::ui_layout::{self, ComposeOptions};
let builds = screen_builds(pak)?;
@@ -445,6 +458,11 @@ fn cmd_screen_render(
ComposeOptions {
include_focus: focus,
include_animated: animated,
backdrop: if black {
[0, 0, 0, 255]
} else {
ComposeOptions::default().backdrop
},
},
None,
);