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
49 lines
2.3 KiB
Rust
49 lines
2.3 KiB
Rust
//! Does a `.t32` sprite's own T8aD header carry a per-sprite blend/alpha mode?
|
|
//!
|
|
//! The declaration entry does not: `ptframe1`/`ptframe2` are kind 0, identical to
|
|
//! every other plain sprite on the menu. The remaining place a mode could live is
|
|
//! the sprite's own T8aD child. ⚠️ `REFUTED.md` already kills one reading of it —
|
|
//! "`T8aD +0x04` bit `0x02` selects an additive blend" — so this is not that
|
|
//! claim; it asks whether ANY header word separates the two frames from the
|
|
//! sprites the port measures as ordinary.
|
|
//!
|
|
//! cargo run -p sylpheed-formats --example t8ad_header_compare
|
|
use sylpheed_formats::{pak::PakArchive, ui_layout};
|
|
use std::path::PathBuf;
|
|
|
|
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 by = ar.read(&ar.entries()[5]).expect("entry 5");
|
|
let b = ui_layout::parse_build(&by).expect("build");
|
|
let mut names: Vec<&String> = b.sprites.keys().collect();
|
|
names.sort();
|
|
println!("{:<20} {:>8} first 12 header words", "sprite", "size");
|
|
let mut rows: Vec<(String, Vec<u32>)> = Vec::new();
|
|
for n in names {
|
|
let (off, size) = b.sprites[n];
|
|
let s = &by[off..(off + size).min(by.len())];
|
|
if s.len() < 48 { continue }
|
|
let ws: Vec<u32> = (0..12)
|
|
.map(|k| u32::from_be_bytes([s[k*4], s[k*4+1], s[k*4+2], s[k*4+3]]))
|
|
.collect();
|
|
let mark = if n.starts_with("ptframe") { " <- FRAME" } else { "" };
|
|
println!("{n:<20} {size:>8} {}{mark}",
|
|
ws.iter().map(|v| format!("{v:08X}")).collect::<Vec<_>>().join(" "));
|
|
rows.push((n.clone(), ws));
|
|
}
|
|
// which words take a value the two frames share and nobody else does?
|
|
let fr: Vec<&(String, Vec<u32>)> = rows.iter().filter(|(n, _)| n.starts_with("ptframe")).collect();
|
|
if fr.len() == 2 {
|
|
println!("\nwords where BOTH frames agree and no other sprite has that value:");
|
|
let mut any = false;
|
|
for w in 0..12 {
|
|
let a = fr[0].1[w]; let bb = fr[1].1[w];
|
|
if a != bb { continue }
|
|
if rows.iter().any(|(n, v)| !n.starts_with("ptframe") && v[w] == a) { continue }
|
|
println!(" word {w} (+0x{:02X}) = {a:08X}", w * 4); any = true;
|
|
}
|
|
if !any { println!(" NONE"); }
|
|
}
|
|
}
|