//! Does `forced_backdrop`'s verdict depend on how the screen's timeline ENDS? //! //! The rule quantifies over "every instant the primitive is opaque" and "every //! element visible then", so both halves depend on where the timeline stops and //! on what an element does after its own last keyframe. The port asked, and it is //! the right question: a verdict that flips with the convention is not a decode. //! //! Four conventions, all applied to the same disc: //! A span = max keyframe time over all elements; elements HOLD their last pose //! (what `forced_backdrop` does, and what the port implements) //! B span = the primitive's OWN last keyframe time; elements hold //! C span = the bundle header `+0x08` (the declared length); elements hold //! D span = max keyframe time; an element is GONE after its own last keyframe //! //! D is the one worth the most: it is the assumption the port flagged as "doing //! real work", and it strictly shrinks the visible set, so it can only turn //! `forced` into `not forced`. use sylpheed_formats::{pak, ratc, ui_layout}; fn last_t(el: &ui_layout::Element) -> u32 { el.keyframes .iter() .filter_map(|k| k.time) .max() .unwrap_or(0) } fn forced(b: &ui_layout::UiBuild, el: &ui_layout::Element, tmax: u32, hold: bool) -> Option { if el.sprite.is_some() { return None; } if (el.pivot_x * 2) < b.design_w || (el.pivot_y * 2) < b.design_h { return None; } if tmax == 0 { return None; } let alpha = |e: &ui_layout::Element, t: u32| -> u32 { if !hold && t > last_t(e) { return 0; } e.pose_at(t).map(|k| k.fade >> 24).unwrap_or(0) }; let op: Vec = (0..=tmax).filter(|&t| alpha(el, t) == 255).collect(); if op.is_empty() { return None; } let others: Vec<&ui_layout::Element> = b.elements.iter().filter(|o| o.index != el.index).collect(); if others.is_empty() { return None; } let below = others .iter() .filter(|o| op.iter().any(|&t| alpha(o, t) > 0)) .count(); Some(below == others.len()) } fn main() { let root = std::env::var("SYLPHEED_DISC").expect("set SYLPHEED_DISC to the extracted disc root"); let mut paks: Vec<_> = std::fs::read_dir(format!("{root}/dat")) .expect("dat/") .flatten() .map(|e| e.path()) .filter(|p| p.extension().and_then(|s| s.to_str()) == Some("pak")) .collect(); paks.sort(); let (mut n, mut a_true) = (0usize, 0usize); let mut flips = [0usize; 3]; let mut examples: Vec = Vec::new(); for p in &paks { let Ok(ar) = pak::PakArchive::open(p) else { continue; }; for (ei, e) in ar.entries().iter().enumerate() { let Ok(by) = ar.read(e) else { continue }; if !ratc::is_ratc(&by) { continue; } let Some(b) = ui_layout::parse_build(&by) else { continue; }; let tall = b .elements .iter() .flat_map(|el| el.keyframes.iter().filter_map(|k| k.time)) .max() .unwrap_or(0); let hdr = if by.len() >= 12 { u32::from_be_bytes(by[8..12].try_into().unwrap()) } else { 0 }; for el in &b.elements { let Some(va) = forced(&b, el, tall, true) else { continue; }; n += 1; if va { a_true += 1 } for (k, vb) in [ forced(&b, el, last_t(el), true), forced(&b, el, hdr, true), forced(&b, el, tall, false), ] .into_iter() .enumerate() { if vb != Some(va) { flips[k] += 1; if k == 2 && examples.len() < 6 { examples.push(format!( "{}:{} {} A={va} D={vb:?}", p.file_name().unwrap().to_string_lossy(), ei, el.name )); } } } } } } println!("keyless full-screen primitives with an opaque interval: {n}"); println!(" convention A (span = all elements' max, hold) -> forced first: {a_true}\n"); println!(" verdicts that CHANGE under:"); println!( " B span = the primitive's own last keyframe : {}", flips[0] ); println!( " C span = the header's declared length +0x08 : {}", flips[1] ); println!( " D elements GONE after their last keyframe : {}", flips[2] ); for e in &examples { println!(" {e}") } }