formats: a settled screen is one instant, not one hold per element
`Element::rest()` picks each element's last hold keyframe independently of every other element, so a composite built from it is not the screen at any moment in time -- it is a per-element maximum. For a transient that is exactly wrong: a two-frame flash's last hold IS the flash peak, so it burns forever. GP_TITLE build 4 is the case. `ptlogo_back2eff1`..`eff5` are five staggered two-frame flashes -- one light sweep drawn as five frames, all extinguished by t110 -- that `rest()` draws simultaneously and permanently. Five stacked white glows saturate the light arc behind the logo. The disc names the right instant: the midpoint of the longest interval containing no keyframe of any element. `UiBuild::settle_time()` and `settle_window()`; `screen render --settle` applies it and prints the window, whose width is how much the midpoint is worth. Predicted t=198 from [160,236] BEFORE scoring. Against the console capture, the arc band goes 33.22 -> 11.79 and pixels at the clipping level 8581 -> 1452, where the console has 1459 -- an unfitted statistic. Whole frame 14.07 -> 12.06. Controls at t=100 and t=358 are far worse, and a hand-picked visibility list reaches the identical numbers. `ComposeOptions::at` now poses every element rather than leaves only, which is why the earlier rotation pose scan was flat: it moved the sweeps and never touched the top-level flashes. `at = None` is byte-identical (cmp), the pre-rotation tag renders identically at rest, and the 13 paint-order tests plus the keyframe/focus/opt-link disc tests are green. Also fixes the diagnostic that caused a wrong finding to be sent to the port agent: `not drawn` listed bare names, and a kind-0x4 ghost carries its template's name, so four ghosts printed as `ptlogo1.t32`/`ptlogo2.t32` and read as "the logo is missing". It now prints index, name and reason. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QsEPXWVaEpyfudtR6re1Pd
This commit is contained in:
@@ -196,13 +196,22 @@ enum ScreenCommands {
|
||||
#[arg(long)]
|
||||
all: bool,
|
||||
/// Pose every element at this KEYFRAME TIME instead of at its resting
|
||||
/// pose (60 units = 1 second). The resting pose is the last *hold*
|
||||
/// keyframe, which is the settled screen — wrong for anything still
|
||||
/// moving. The title's two light sweeps hold off the right edge, so a
|
||||
/// resting composite omits them; `--at 358` puts them where a capture
|
||||
/// taken mid-sweep has them.
|
||||
#[arg(long)]
|
||||
/// pose (60 units = 1 second). The resting pose is each element's last
|
||||
/// *hold* keyframe, picked independently of every other element — so it
|
||||
/// is not the screen at any one moment, and it is wrong twice over: it
|
||||
/// omits anything still moving (the title's light sweeps hold off the
|
||||
/// right edge), and it freezes a transient at its PEAK (the title's five
|
||||
/// two-frame flashes burn forever). Prefer `--settle`.
|
||||
#[arg(long, conflicts_with = "settle")]
|
||||
at: Option<u32>,
|
||||
/// Pose every element at the instant the screen is SETTLED, derived from
|
||||
/// the disc: the midpoint of the longest interval containing no keyframe
|
||||
/// of any element. Prints the window it used, whose width is how much the
|
||||
/// midpoint is worth — a narrow one means the bundle never settles (42 %
|
||||
/// of them, mostly `loop*` fragments). See
|
||||
/// `docs/re/structures/ui-settle-time.md`.
|
||||
#[arg(long)]
|
||||
settle: bool,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -362,8 +371,9 @@ async fn main() -> Result<()> {
|
||||
all,
|
||||
primitives,
|
||||
at,
|
||||
settle,
|
||||
} => cmd_screen_render(
|
||||
&pak, &output, build, focus, animated, black, all, primitives, at,
|
||||
&pak, &output, build, focus, animated, black, all, primitives, at, settle,
|
||||
),
|
||||
},
|
||||
Commands::Save { cmd } => match cmd {
|
||||
@@ -591,12 +601,39 @@ fn cmd_screen_render(
|
||||
all: bool,
|
||||
primitives: bool,
|
||||
at: Option<u32>,
|
||||
settle: bool,
|
||||
) -> Result<()> {
|
||||
use sylpheed_formats::ui_layout::{self, ComposeOptions};
|
||||
let builds = screen_builds(pak, all)?;
|
||||
let idx = pick_build(&builds, want)?;
|
||||
let bytes = &builds[idx].1;
|
||||
let b = ui_layout::parse_build(bytes).context("build did not parse")?;
|
||||
let at = if settle {
|
||||
match (b.settle_window(), b.settle_time()) {
|
||||
(Some((lo, hi)), Some(t)) => {
|
||||
// Report the width, not just the answer. A 4-unit window and a
|
||||
// 190-unit one give the same kind of number and mean entirely
|
||||
// different things.
|
||||
println!(
|
||||
"settle window [{lo}, {hi}] = {} units ({:.2} s) -> posing at t={t}{}",
|
||||
hi - lo,
|
||||
(hi - lo) as f64 / 60.0,
|
||||
if hi - lo < 30 {
|
||||
" ⚠️ narrow — this bundle may never settle"
|
||||
} else {
|
||||
""
|
||||
}
|
||||
);
|
||||
Some(t)
|
||||
}
|
||||
_ => {
|
||||
println!("no settle window (fewer than two distinct keyframe times) — using rest()");
|
||||
None
|
||||
}
|
||||
}
|
||||
} else {
|
||||
at
|
||||
};
|
||||
let screen = ui_layout::compose(
|
||||
&b,
|
||||
bytes,
|
||||
@@ -632,14 +669,35 @@ fn cmd_screen_render(
|
||||
if !screen.missing.is_empty() {
|
||||
println!(" sprites that did not resolve/decode: {:?}", screen.missing);
|
||||
}
|
||||
let undrawn: Vec<&str> = b
|
||||
// 🔴 Report the INDEX, the KIND and WHY, not just the name. A kind-`0x4`
|
||||
// ghost instance carries its template's name, so a bare name list shows
|
||||
// `ptlogo1.t32` twice and reads as "the logo is missing" when what is
|
||||
// skipped is two motion-trail ghosts sitting at alpha 0 off-screen. That
|
||||
// misreading cost this project a wrong finding sent to another agent.
|
||||
let undrawn: Vec<String> = b
|
||||
.elements
|
||||
.iter()
|
||||
.filter(|e| !screen.drawn.contains(&e.index))
|
||||
.map(|e| e.name.as_str())
|
||||
.map(|e| {
|
||||
let why = if e.name.ends_with(".prm") {
|
||||
"untextured primitive, needs --primitives"
|
||||
} else if e.name.ends_with(".rat") {
|
||||
"animation, needs --animated"
|
||||
} else if e.kind == 0x4 {
|
||||
"kind 0x4 ghost instance"
|
||||
} else if e.rest().map(|k| k.fade >> 24) == Some(0) {
|
||||
"transparent at its pose"
|
||||
} else {
|
||||
"no reason established"
|
||||
};
|
||||
format!("[{}] {} ({why})", e.index, e.name)
|
||||
})
|
||||
.collect();
|
||||
if !undrawn.is_empty() {
|
||||
println!(" not drawn ({}): {undrawn:?}", undrawn.len());
|
||||
println!(" not drawn ({}):", undrawn.len());
|
||||
for u in &undrawn {
|
||||
println!(" {u}");
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user