This repository has been archived on 2026-09-16. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
Syplheed-Reborn/crates/sylpheed-formats/examples/rat_leaf_placement.rs
Sylpheed RE agent 7eeae3006a re(ui): the focus ring SPINS, the game draws it, and the leaf owns the f record
Three things, all from parsing ptbtn0Nf.rat as a build.

1. THE RING SPINS. Its two keyframes differ in exactly one field: rotation_deg
ramps 0 -> 360 with position, scale, alpha and tint all constant. A spin in
place, the same shape as the GP_BUNK example already recorded.

2. THE ORACLE CONFIRMS THE GAME RENDERS IT. In the OPTIONS-focused capture the
ring's bright head sits in a completely different angular position from the
sprite's own -- caught mid-spin. This is a SECOND independent confirmation that
rotation_deg is drawn, now on a different screen and a different element from the
ptloop sweeps, and it raises rotation's priority: it is not a title-only concern
that sits off-screen at rest, it is the main menu's focus marker.
NO ANGLE IS QUOTED. A brightest-region centroid says ~250 deg, but the control
refuses that precision -- rotating the sprite by a known 30/90/180/270 and
re-measuring gives errors up to 19.8 deg. What survives the error bar is that a
<=20 deg error cannot manufacture a ~250 deg displacement.

3. WHICH PLACEMENT WINS -- correcting this page's own earlier caveat, which said
to use the leaf only for elements the parent does not declare. Right for a BASE
record, wrong for an f record: the parent declares NO element for ptbtn0Nf.rat at
all (zero of build 5's 16), so the f record's placement comes from its leaf for
BOTH elements, label included. The label's (-7,-7) is load-bearing -- the f
sprite is 13px larger per axis and -7 keeps them concentric (535+96/2 = 583 vs
542+83/2 = 583.5). Corroborated against the oracle: the focused-minus-unfocused
region is x 505..703, and the leaf predicts a right edge near 707 where the
parent reading predicts 714.

Also exposes UiBuild::records (name -> (offset, size) of a nested .rat leaf).
Nested records were parsed into a PRIVATE map, so a consumer holding a UiBuild
could not locate a leaf's bytes at all -- which is exactly what blocked the port
from reaching the ring.
2026-08-29 08:46:53 +00:00

72 lines
3.1 KiB
Rust

//! Does a `.rat` leaf record decode with the same reader as a whole bundle?
//!
//! The port needs the position of `ptbtneff01.t32`, the focus ring, which is
//! declared *inside* the nested `ptbtn0Nf.rat` leaf and is therefore invisible
//! to anything that walks only a bundle's top-level elements.
//!
//! The leaf's first 32 bytes have the same shape as a bundle header --
//! `"RATC"`, `0x3c` declaration-entry size at `+4`, element count at `+20`,
//! design size at `+24`/`+28` -- so the hypothesis is that `parse_build` reads
//! it unchanged. The control is the BASE record `ptbtn0N.rat`, whose single
//! element's position is already known independently: the parent screen's
//! `screen info` reports `ptbtn01.rat` resting at (542,162).
//!
//! cargo run -p sylpheed-formats --example rat_leaf_placement -- <GP_TITLE.pak>
use sylpheed_formats::{pak, ratc, ui_layout};
fn main() {
let path = std::env::args().nth(1).expect("usage: … <pak>");
let ar = pak::PakArchive::open(&path).expect("open pak");
let entries: Vec<_> = ar.entries().to_vec();
for (ei, e) in entries.iter().enumerate() {
let Ok(bytes) = ar.read(e) else { continue };
let Some(kids) = ratc::parse(&bytes) else { continue };
// Only the title-family bundles carry ptbtn records.
if !kids.iter().any(|c| c.name == "ptbtn01f.rat") {
continue;
}
println!("=== pak entry {ei}");
for c in &kids {
if c.kind != "RATC" || !c.name.starts_with("ptbtn") {
continue;
}
let leaf = &bytes[c.offset..c.offset + c.size];
match ui_layout::parse_build(leaf) {
None => println!(" {:16} …parse_build says no", c.name),
Some(b) => {
println!(
" {:16} {}x{} {} element(s), fallback={}",
c.name,
b.design_w,
b.design_h,
b.elements.len(),
b.from_fallback
);
for el in &b.elements {
let r = el.rest();
println!(
" [{}] {:18} pivot ({:4},{:4}) rest ({:5},{:5}) kf {}",
el.index,
el.name,
el.pivot_x,
el.pivot_y,
r.map(|k| k.x).unwrap_or(-1),
r.map(|k| k.y).unwrap_or(-1),
el.keyframes.len()
);
for (i, k) in el.keyframes.iter().enumerate() {
println!(
" kf{i} t={:?} pos=({},{}) scale={}%,{}% a={} rot={} tint={:#010x}",
k.time, k.x, k.y, k.scale_x, k.scale_y,
k.fade >> 24, k.rotation_deg, k.tint
);
}
}
}
}
}
println!();
}
}