Files
Sylpheed/crates/sylpheed-formats/examples/kind_census_five_screens.rs
MechaCat02 ccd49ac31f fix(lint): clear the clippy gate across examples and tests
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>
2026-09-12 16:42:41 +02:00

206 lines
7.9 KiB
Rust

//! Refutation check on `sylpheed-port`'s kind census: *"Every sprite decoration
//! on both screens is `0x0` — `ptframe1`…`ptframe4` included — and every button
//! is `0x3002`."*
//!
//! Their exporter decodes the field independently; this reads it from the other
//! side. The check is deliberately WIDER than their claim in two ways, because a
//! census that only looks where the claim looks cannot fail:
//!
//! * it covers every element, not only `.t32` sprites, so a decoration with an
//! unexpected kind cannot hide behind the word "sprite";
//! * it covers every build of `GP_TITLE`, not the two screens they checked, so
//! the claim's *reach* gets tested and not just its instances.
//!
//! It also cross-checks `kind` against the focus/nav index at `+0x2C`, which is
//! `-1` on anything that cannot take the cursor. That turns a census into a
//! decode: if the two fields agree everywhere, the bit that separates them is
//! identified rather than guessed. Run over EVERY UI pak on the disc, not just
//! `GP_TITLE`, so the claim is disc-wide.
//!
//! cargo run -p sylpheed-formats --example kind_census_five_screens
use std::collections::BTreeMap;
use std::path::PathBuf;
use sylpheed_formats::{pak::PakArchive, ui_layout};
fn suffix(n: &str) -> &str {
match n.rfind('.') {
Some(i) => &n[i..],
None => "(none)",
}
}
fn main() {
let root = PathBuf::from(std::env::var("SYLPHEED_DISC").expect("SYLPHEED_DISC"));
let ar = PakArchive::open(root.join("dat/GP_TITLE.pak")).expect("GP_TITLE");
let n = ar.entries().len();
// kind -> suffix -> count, and the exceptions we care about by name
let mut table: BTreeMap<u32, BTreeMap<String, usize>> = BTreeMap::new();
let mut t32_nonzero: Vec<(usize, String, u32)> = Vec::new();
let mut btn_nonstd: Vec<(usize, String, u32)> = Vec::new();
let mut builds = 0usize;
for e in 0..n {
let by = match ar.read(&ar.entries()[e]) {
Ok(b) => b,
Err(_) => continue,
};
let b = match ui_layout::parse_build(&by) {
Some(b) => b,
None => continue,
};
builds += 1;
for el in &b.elements {
*table
.entry(el.kind)
.or_default()
.entry(suffix(&el.name).to_string())
.or_default() += 1;
if el.name.ends_with(".t32") && !el.name.contains("btn") && el.kind != 0 {
t32_nonzero.push((e, el.name.clone(), el.kind));
}
if el.name.contains("btn") && el.kind != 0x3002 {
btn_nonstd.push((e, el.name.clone(), el.kind));
}
}
}
println!("GP_TITLE: {builds} parseable builds of {n} entries\n");
println!("{:<10} elements by file suffix", "kind");
for (k, m) in &table {
let s: Vec<String> = m.iter().map(|(sfx, c)| format!("{sfx}x{c}")).collect();
println!("0x{k:<8X} {}", s.join(" "));
}
println!(
"\nNON-BUTTON .t32 elements with kind != 0: {}",
t32_nonzero.len()
);
for (e, nm, k) in t32_nonzero.iter().take(30) {
println!(" entry {e:>2} {nm:<24} kind 0x{k:X}");
}
println!("\n*btn* elements with kind != 0x3002: {}", btn_nonstd.len());
for (e, nm, k) in btn_nonstd.iter().take(30) {
println!(" entry {e:>2} {nm:<24} kind 0x{k:X}");
}
// Is `kind` a bitfield, and does bit 0x2 mean "focusable"? The focus/nav
// index at +0x2C is -1 on everything that cannot take the cursor, so the two
// fields cross-check each other. Printed rather than asserted: this is the
// evidence for the reading, not the reading itself.
println!("\nkind vs the focus index at +0x2C (-1 = not focusable), GP_TITLE:");
// +0x2C is not in `Element`, so it is read straight out of the 60-byte
// declaration entry: table at 0x20, 0x14 = count, entry stride 60.
const AT: usize = 0x20;
const STRIDE: usize = 60;
let mut cross: BTreeMap<(u32, i32), usize> = BTreeMap::new();
for e in 0..n {
let by = match ar.read(&ar.entries()[e]) {
Ok(b) => b,
Err(_) => continue,
};
if ui_layout::parse_build(&by).is_none() {
continue;
}
if by.len() < 0x18 {
continue;
}
let count = u32::from_be_bytes([by[0x14], by[0x15], by[0x16], by[0x17]]) as usize;
for i in 0..count {
let at = AT + i * STRIDE;
if at + STRIDE > by.len() {
break;
}
let kind =
u32::from_be_bytes([by[at + 0x28], by[at + 0x29], by[at + 0x2A], by[at + 0x2B]]);
let foc =
i32::from_be_bytes([by[at + 0x2C], by[at + 0x2D], by[at + 0x2E], by[at + 0x2F]]);
*cross
.entry((kind, if foc < 0 { -1 } else { 1 }))
.or_default() += 1;
}
}
for ((k, f), c) in &cross {
println!(
" kind 0x{k:<6X} focus {:<10} {c:>4} elements",
if *f < 0 { "= -1" } else { ">= 0" }
);
}
// ── the same test, every UI pak on the disc ──────────────────────────────
let mut all: BTreeMap<(u32, i32), usize> = BTreeMap::new();
let mut paks = 0usize;
let mut violations: Vec<String> = Vec::new();
let mut dir: Vec<_> = std::fs::read_dir(root.join("dat"))
.expect("dat")
.filter_map(|d| d.ok())
.map(|d| d.path())
.collect();
dir.sort();
for path in dir {
let name = path.file_name().unwrap().to_string_lossy().to_string();
if !name.ends_with(".pak") {
continue;
}
let ar = match PakArchive::open(&path) {
Ok(a) => a,
Err(_) => continue,
};
let mut used = false;
for i in 0..ar.entries().len() {
let by = match ar.read(&ar.entries()[i]) {
Ok(b) => b,
Err(_) => continue,
};
if ui_layout::parse_build(&by).is_none() {
continue;
}
if by.len() < 0x18 {
continue;
}
used = true;
let count = u32::from_be_bytes([by[0x14], by[0x15], by[0x16], by[0x17]]) as usize;
for e in 0..count {
let at = AT + e * STRIDE;
if at + STRIDE > by.len() {
break;
}
let kind = u32::from_be_bytes([
by[at + 0x28],
by[at + 0x29],
by[at + 0x2A],
by[at + 0x2B],
]);
let foc = i32::from_be_bytes([
by[at + 0x2C],
by[at + 0x2D],
by[at + 0x2E],
by[at + 0x2F],
]);
let f = if foc < 0 { -1 } else { 1 };
*all.entry((kind, f)).or_default() += 1;
if ((kind & 0x2) != 0) != (f > 0) {
violations.push(format!(
"{name} entry {i} elem {e}: kind 0x{kind:X} focus {foc}"
));
}
}
}
if used {
paks += 1
}
}
let total: usize = all.values().sum();
println!("\nDISC-WIDE — {paks} UI paks, {total} declaration entries");
println!("{:<12} {:>12} {:>12}", "kind", "focus = -1", "focus >= 0");
let kinds: std::collections::BTreeSet<u32> = all.keys().map(|(k, _)| *k).collect();
for k in kinds {
println!(
"0x{k:<10X} {:>12} {:>12}",
all.get(&(k, -1)).copied().unwrap_or(0),
all.get(&(k, 1)).copied().unwrap_or(0)
);
}
println!("\nHYPOTHESIS: bit 0x2 of kind == (focus index >= 0)");
println!("violations: {} of {total}", violations.len());
for v in violations.iter().take(20) {
println!(" {v}");
}
}