This branch predates CI on `main`. `cargo fmt --all` only; no behaviour. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
87 lines
3.5 KiB
Rust
87 lines
3.5 KiB
Rust
//! 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 std::collections::{BTreeMap, BTreeSet};
|
|
use sylpheed_formats::{pak, ui_layout};
|
|
|
|
fn main() {
|
|
let dir = std::env::args().nth(1).expect("<disc>/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<i32>>; 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<ui_layout::Keyframe>)> = 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}");
|
|
}
|
|
}
|