//! Do `+4` / `+8` = 180 mean MIRROR? //! //! The disc-wide census shows `+4` and `+8` are dominated by 180 and ±90, while //! `+12` (the decoded screen-plane rotation) takes 157 distinct values including //! odd ones. That shape says flips rather than free rotation. //! //! Structural test, no renderer involved: if 180 means "mirror", then the same //! sprite should appear both with the field 0 and with it 180 **within one //! build** -- a mirrored pair. Free-rotation semantics predicts no such pairing. //! //! CONTROL: the same search run on `+12`, which is decoded as a real rotation //! and should NOT show a 0/180 pairing pattern of the same strength. use sylpheed_formats::{pak, ui_layout}; use std::collections::{BTreeMap, BTreeSet}; fn main() { let dir = std::env::args().nth(1).expect("/dat"); let mut paks: Vec<_> = std::fs::read_dir(&dir).expect("dir") .filter_map(|e| e.ok().map(|e| e.path())) .filter(|p| p.extension().map(|x| x == "pak").unwrap_or(false)).collect(); paks.sort(); // field -> (pak, entry, sprite) -> set of values seen let mut seen: [BTreeMap<(String, usize, String), BTreeSet>; 3] = [BTreeMap::new(), BTreeMap::new(), BTreeMap::new()]; for p in &paks { let Ok(ar) = pak::PakArchive::open(p) else { continue }; let pn = p.file_name().unwrap().to_string_lossy().to_string(); for (i, e) in ar.entries().to_vec().iter().enumerate() { let Ok(bytes) = ar.read(e) else { continue }; let Some(b) = ui_layout::parse_build(&bytes) else { continue }; let mut groups: Vec<(String, Vec)> = b.elements.iter().map(|el| (el.name.clone(), el.keyframes.clone())).collect(); for el in &b.elements { if let Some(&(off, size)) = b.records.get(&el.name) { if let Some(lb) = ui_layout::parse_build(&bytes[off..off + size]) { for le in &lb.elements { groups.push((le.name.clone(), le.keyframes.clone())); } } } } for (nm, ks) in groups { for k in &ks { let key = (pn.clone(), i, nm.clone()); seen[0].entry(key.clone()).or_default().insert(k.unknown_4); seen[1].entry(key.clone()).or_default().insert(k.unknown_8); seen[2].entry(key).or_default().insert(k.rotation_deg); } } } } for (idx, label) in [(0, "+4"), (1, "+8"), (2, "+12 (rotation, CONTROL)")] { let m = &seen[idx]; let mut pair_0_180 = 0usize; // a sprite seen at BOTH 0 and 180 in one build let mut only_180 = 0usize; let mut multi = 0usize; // more than two distinct values for v in m.values() { if v.len() > 2 { multi += 1; } let has0 = v.contains(&0); let has180 = v.contains(&180) || v.contains(&-180); if has0 && has180 { pair_0_180 += 1; } else if has180 && !has0 { only_180 += 1; } } println!("{label}: {} sprite-instances", m.len()); println!(" both 0 and ±180 in one build : {pair_0_180}"); println!(" ±180 without any 0 : {only_180}"); println!(" more than 2 distinct values : {multi}"); } }