This branch predates CI on `main`. `cargo fmt --all` only; no behaviour. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
139 lines
4.6 KiB
Rust
139 lines
4.6 KiB
Rust
//! An OPAQUE full-screen primitive cannot paint on top of elements that are
|
|
//! visible at the same time — the screen would be blank.
|
|
//!
|
|
//! That is a constraint read off the file, not a preference. For each keyless
|
|
//! primitive this computes the interval over which it is opaque, and the interval
|
|
//! over which any OTHER element is visible, and reports the overlap.
|
|
//!
|
|
//! The falsifier: `pteff00.prm` is MEASURED painting last on the title and the
|
|
//! main menu. If any of its instances is opaque while content is up, the
|
|
//! constraint is wrong and this whole line is dead.
|
|
use sylpheed_formats::{pak, ratc, ui_layout};
|
|
|
|
/// Interval(s) where alpha >= `thr`, sampled at every half unit.
|
|
fn opaque_span(el: &ui_layout::Element, thr: u32, tmax: u32) -> Vec<(f64, f64)> {
|
|
let mut out = Vec::new();
|
|
let mut cur: Option<f64> = None;
|
|
let mut t = 0.0;
|
|
while t <= tmax as f64 {
|
|
let a = el.pose_at(t as u32).map(|k| k.fade >> 24).unwrap_or(0);
|
|
if a >= thr {
|
|
if cur.is_none() {
|
|
cur = Some(t)
|
|
}
|
|
} else if let Some(s) = cur.take() {
|
|
out.push((s, t));
|
|
}
|
|
t += 0.5;
|
|
}
|
|
if let Some(s) = cur {
|
|
out.push((s, tmax as f64))
|
|
}
|
|
out
|
|
}
|
|
|
|
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 want = [
|
|
"pteff00.prm",
|
|
"pgloading_eff00.prm",
|
|
"palogo_eff0.prm",
|
|
"pfbase.tbm",
|
|
"pteff02.prm",
|
|
"pzeff00.prm",
|
|
"pceff00.prm",
|
|
"pdeff00.prm",
|
|
];
|
|
println!(
|
|
"{:>22} {:>5} {:>16} {:>18} overlap",
|
|
"primitive", "entry", "opaque while", "content visible"
|
|
);
|
|
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 tmax = b
|
|
.elements
|
|
.iter()
|
|
.flat_map(|el| el.keyframes.iter().filter_map(|k| k.time))
|
|
.max()
|
|
.unwrap_or(0);
|
|
if tmax == 0 {
|
|
continue;
|
|
}
|
|
for el in &b.elements {
|
|
if !want.contains(&el.name.as_str()) {
|
|
continue;
|
|
}
|
|
if ui_layout::sprite_layer_key(&b, &by, el).is_some() {
|
|
continue;
|
|
}
|
|
let op = opaque_span(el, 255, tmax);
|
|
if op.is_empty() {
|
|
continue;
|
|
}
|
|
// when is any OTHER element visible?
|
|
let mut cmin = f64::MAX;
|
|
let mut cmax = f64::MIN;
|
|
for o in &b.elements {
|
|
if o.index == el.index {
|
|
continue;
|
|
}
|
|
for (s, t) in opaque_span(o, 1, tmax) {
|
|
cmin = cmin.min(s);
|
|
cmax = cmax.max(t)
|
|
}
|
|
}
|
|
if cmin > cmax {
|
|
continue;
|
|
}
|
|
// overlap of the primitive's opaque span with the content span
|
|
let ov: f64 = op
|
|
.iter()
|
|
.map(|&(s, t)| (t.min(cmax) - s.max(cmin)).max(0.0))
|
|
.sum();
|
|
let opd: String = op
|
|
.iter()
|
|
.map(|&(s, t)| format!("{s:.0}-{t:.0}"))
|
|
.collect::<Vec<_>>()
|
|
.join(",");
|
|
let flag = if ov > 2.0 {
|
|
" 🔴 CANNOT BE ON TOP"
|
|
} else {
|
|
""
|
|
};
|
|
println!(
|
|
"{:>22} {:>5} {:>16} {:>18} {ov:6.1}{flag}",
|
|
el.name,
|
|
format!(
|
|
"{}:{}",
|
|
p.file_name()
|
|
.unwrap()
|
|
.to_string_lossy()
|
|
.trim_end_matches(".pak")
|
|
.trim_start_matches("GP_"),
|
|
ei
|
|
),
|
|
opd,
|
|
format!("{cmin:.0}-{cmax:.0}")
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|