//! Which screens does `forced_backdrop` DECIDE, and which does it merely agree with? //! //! Every check this corpus has run on the rule measured its **stability** — that //! no verdict moved when something else changed. That is a different property //! from **necessity**: an element whose position is already fixed by a read or an //! implied key is confirmed by the rule, not decided by it. //! //! So: compute `derived_paint_order` with the rule, and again with the //! `forced_backdrop` fallback removed, and report every entry whose order moves. //! Where nothing moves, the rule is decorative on that screen; where it moves, //! the rule is the only thing holding the order up. //! //! Raised by the port agent 2026-08-30. Reach note in //! `docs/re/structures/ui-forced-backdrop.md`. //! //! cargo run -p sylpheed-formats --example forced_backdrop_necessity -- [pak...] //! //! 🔴 With no argument this used to default to `GP_TITLE` alone, so a bare run //! reported **6 instances, not 80** — a thirteenth of the census, printed in the //! same format and reading like the whole thing. The port agent hit it and nearly //! filed the discrepancy back at me. It now walks every `dat/*.pak` by default and //! says on stderr how many archives it opened, because "I ran your instrument" has //! to mean the same thing to both of us. use std::path::PathBuf; use sylpheed_formats::{pak::PakArchive, ui_layout}; fn order_without_rule(build: &ui_layout::UiBuild, bundle: &[u8]) -> Vec { let mut idx: Vec = (0..build.elements.len()).collect(); idx.sort_by_key(|&i| { let el = &build.elements[i]; ( ui_layout::sprite_layer_key(build, bundle, el) .or_else(|| ui_layout::implied_layer_key(&el.name)) .unwrap_or(u32::MAX), i, ) }); idx } fn main() { let root = PathBuf::from(std::env::var("SYLPHEED_DISC").expect("SYLPHEED_DISC")); let (mut total_decides, mut total_agrees) = (0usize, 0usize); let mut paks: Vec = std::env::args().skip(1).map(PathBuf::from).collect(); if paks.is_empty() { let mut all: Vec = std::fs::read_dir(root.join("dat")) .expect("dat/") .filter_map(|e| e.ok().map(|e| e.path())) .filter(|p| p.extension().is_some_and(|x| x == "pak")) .collect(); all.sort(); paks = all; } eprintln!("# scanning {} archive(s)", paks.len()); for path in &paks { let ar = PakArchive::open(path).expect("pak"); println!("# {}", path.display()); println!("# entry forced decides elements note"); let mut decides = Vec::new(); let mut agrees = Vec::new(); #[allow(unused)] let _ = (&decides, &agrees); for (i, e) in ar.entries().iter().enumerate() { let Ok(by) = ar.read(e) else { continue }; let Some(b) = ui_layout::parse_build(&by) else { continue; }; let with = ui_layout::derived_paint_order(&b, &by); let without = order_without_rule(&b, &by); // Which elements does the rule fire on, and of those, which have no key // of their own to fall back on? let mut forced = Vec::new(); let mut keyless = Vec::new(); for el in &b.elements { if !ui_layout::forced_backdrop(&b, el) { continue; } forced.push(el.name.clone()); let own = ui_layout::sprite_layer_key(&b, &by, el) .or_else(|| ui_layout::implied_layer_key(&el.name)); if own.is_none() { keyless.push(el.name.clone()); } } if forced.is_empty() { continue; } let moved = with != without; if moved { decides.push(i); } else { agrees.push(i); } println!( " {i:>5} {:>6} {:>7} {:>8} forced=[{}] keyless=[{}]", forced.len(), if moved { "YES" } else { "no" }, b.elements.len(), forced.join(","), keyless.join(","), ); } println!("# rule DECIDES the order on entries {decides:?}"); println!("# rule merely AGREES on entries {agrees:?}\n"); total_decides += decides.len(); total_agrees += agrees.len(); } println!( "# TOTAL over {} archive(s): {total_decides} deciding entries, \ {total_agrees} agreeing", paks.len() ); }