Files
Sylpheed/crates/sylpheed-formats/examples/t8ad_word8_census.rs
sylph-decoder abab343d2e re: a .t32 element carries no blend/alpha mode -- undecodable, with reach
Answers the port's ask about ptframe1/ptframe2, whose residual is uniquely higher on
flat pixels than edges and signed one direction -- a body-intensity difference.

Prior work covers .prm primitives and a refuted T8aD +0x04 bit; neither covers a
.t32 element. All 15 words of the 60-byte declaration entry are read: 3 are the
name, 8 constant, the rest kind, focus index, position and pivot. The frames are
kind 0, identical to every other plain sprite.

One candidate found and refuted by myself: T8aD +0x08 is the only word where both
frames agree uniquely on that screen, at 0x8050 -- but 38 sprites carry it
disc-wide, only 8 named frame, and the high byte tracks the archive. It is an
atlas/format word, not a mode.

Also records a false positive of my own test: +0x00 and +0x08 first read as
'separating the frames' because those words are the name string.

So any blend the port picks is authored. Reach: not looked at the executable's draw
path, where a mode selected in code rather than data would live.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wuu56cE8vJGTBtn1ppsk8v
2026-08-31 05:13:27 +00:00

51 lines
2.4 KiB
Rust

//! Is T8aD `+0x08` a per-sprite MODE, or a texture FORMAT word?
//!
//! On the main menu the two frames share `+0x08 = 0x8050` and no other sprite has
//! it — a candidate for the blend/alpha mode `sylpheed-port` asked for. Before
//! offering it, refute it: if 0x8050 is common disc-wide on ordinary sprites, it
//! is not frame-specific and not a mode.
//!
//! cargo run -p sylpheed-formats --example t8ad_word8_census
use sylpheed_formats::{pak::PakArchive, ui_layout};
use std::collections::BTreeMap;
use std::path::PathBuf;
fn main() {
let root = PathBuf::from(std::env::var("SYLPHEED_DISC").expect("SYLPHEED_DISC"));
let mut paks: Vec<_> = std::fs::read_dir(root.join("dat"))
.expect("dat")
.filter_map(|e| e.ok().map(|e| e.path()))
.filter(|p| p.extension().map(|x| x == "pak").unwrap_or(false))
.collect();
paks.sort();
let mut hist: BTreeMap<u32, usize> = BTreeMap::new();
let mut frames_like: BTreeMap<u32, usize> = BTreeMap::new();
let mut examples: BTreeMap<u32, Vec<String>> = BTreeMap::new();
for p in &paks {
let Ok(ar) = PakArchive::open(p) else { continue };
for e in ar.entries() {
let Ok(by) = ar.read(e) else { continue };
let Some(b) = ui_layout::parse_build(&by) else { continue };
for (n, (off, size)) in &b.sprites {
let s = &by[*off..(*off + *size).min(by.len())];
if s.len() < 48 { continue }
let w = u32::from_be_bytes([s[8], s[9], s[10], s[11]]);
*hist.entry(w).or_default() += 1;
if n.contains("frame") { *frames_like.entry(w).or_default() += 1 }
let ex = examples.entry(w).or_default();
if ex.len() < 3 && !ex.contains(n) { ex.push(n.clone()) }
}
}
}
let total: usize = hist.values().sum();
println!("{total} sprites disc-wide; distinct +0x08 values: {}\n", hist.len());
println!("{:>10} {:>8} {:>10} examples", "value", "count", "of which 'frame'");
for (v, c) in hist.iter().filter(|(_, c)| **c >= 20) {
println!("{:>#10x} {c:>8} {:>10} {}", v, frames_like.get(v).copied().unwrap_or(0),
examples[v].join(", "));
}
println!("\n0x8050 specifically: {} sprites, {} of them named *frame*",
hist.get(&0x8050).copied().unwrap_or(0),
frames_like.get(&0x8050).copied().unwrap_or(0));
}