80 findings, not the 14 the first run showed -- clippy stops at the first failing compilation unit, so `--keep-going` is what makes the list complete. 60 were machine-applicable (`cargo clippy --fix`). The rest by hand: * five descending `sort_by` -> `sort_by_key(Reverse(..))` * `chunks_exact(4)` on both sides of four zips, so the compared items stay `[u8; 4]` rather than one array against one slice * three `type` aliases for the census maps and the captured-quad tuple * `&PathBuf` -> `&Path` in two disc tests * two range loops; one of them keeps `#[allow(needless_range_loop)]` with the reason -- the index is into a map's value, which changes each iteration * the module doc list in `invert_capture` re-indented to markdown's rules * `blit`'s eight arguments get `#[allow(too_many_arguments)]`, not a struct One dead `let off = b.len();` in a `ratc` test is dropped rather than renamed. The sibling test at :162 is the one that asserts an offset; if this one was meant to as well, that is a test change and not a lint fix. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
145 lines
4.9 KiB
Rust
145 lines
4.9 KiB
Rust
//! 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<bool> {
|
|
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<u32> = (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").unwrap_or_else(|_| "/disc".into());
|
|
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<String> = 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}")
|
|
}
|
|
}
|