re: no per-element field on the disc separates the four too-dark frames
The port measures four elements as rendering too dark against the capture -- ptframe1/2 on the main menu, ptframe3/4 on EXTRAS -- with the shortfall correlating +0.77/+0.80 with the BACKGROUND and only +0.24 with the element's own contribution. That is the signature of a blend that scales what is already there. It asked whether the disc selects one. Three examples, one negative, wider than the one I gave last iteration: * frame_alpha_census -- every T8aD sprite on builds 5 and 6 by alpha. It REFUTES the port's own sharpener: 'neither frame has a single fully-opaque pixel, against ptbase's 99.1 %' is true, and pteff10 (max alpha 130, 100 % partial, no opaque pixel) is measured by the port as NEARLY EXACT. So being wholly semi-transparent is not what makes the frames special. * frame_vs_accurate_words -- all 12 T8aD header words for both screens, plus a per-BIT sweep of +0x04 and +0x08. NO word and NO bit puts the four frames on one side and pteff10 on the other. It also kills my own remaining candidate a second time: +0x08 = 0x8050 is shared with pteff21/22/23 on EXTRAS. * frame_keyframe_unknowns -- the keyframe record's fade, tint, rotation and its two unexplained signed words. unknown_4 and unknown_8 are ZERO on every keyframe of both screens, so they carry nothing here; no frame takes a value of any field that another element does not. Reach: the 60-byte declaration entry, the T8aD header word-wise and bit-wise, and the keyframe record. Four elements, two screens. Not the executable's draw path, which is the next commit. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Wuu56cE8vJGTBtn1ppsk8v
This commit is contained in:
57
crates/sylpheed-formats/examples/frame_alpha_census.rs
Normal file
57
crates/sylpheed-formats/examples/frame_alpha_census.rs
Normal file
@@ -0,0 +1,57 @@
|
||||
//! The main menu's sprites, by their ALPHA channel — is `ptframe1`/`ptframe2`'s
|
||||
//! "no fully-opaque pixel" a property of the artwork, and does any alpha value
|
||||
//! look like a scale the game expands (e.g. 0..128) rather than 0..255?
|
||||
//!
|
||||
//! The port measures both frames as rendering too DARK against the capture, with
|
||||
//! the shortfall correlating with the BACKGROUND. Two different causes predict
|
||||
//! that: a background-scaling blend selected in code, or an alpha that is too
|
||||
//! LOW in our decode. This example tests the second, which is on the disc.
|
||||
//!
|
||||
//! cargo run -p sylpheed-formats --example frame_alpha_census
|
||||
use sylpheed_formats::{pak::PakArchive, t8ad, ui_layout};
|
||||
use std::path::PathBuf;
|
||||
|
||||
fn census(name: &str, img: &t8ad::T8adImage) {
|
||||
let n = (img.width * img.height) as usize;
|
||||
let mut hist = [0usize; 256];
|
||||
for p in 0..n {
|
||||
hist[img.rgba[p * 4 + 3] as usize] += 1;
|
||||
}
|
||||
let zero = hist[0];
|
||||
let full = hist[255];
|
||||
let max = (0..256).rev().find(|&a| hist[a] > 0).unwrap_or(0);
|
||||
let nonzero = n - zero;
|
||||
// the top five alpha values that actually occur, by population
|
||||
let mut top: Vec<(usize, usize)> = (1..256).map(|a| (hist[a], a)).filter(|&(c, _)| c > 0).collect();
|
||||
top.sort_unstable_by(|a, b| b.0.cmp(&a.0));
|
||||
let top5: Vec<String> = top.iter().take(5).map(|&(c, a)| format!("{a}x{c}")).collect();
|
||||
println!(
|
||||
"{name:<16} {}x{:<4} px={n:<8} a=0:{:5.1}% a=255:{:5.1}% max={max:<3} \
|
||||
partial(1..254)/nonzero={:5.1}% top:[{}]",
|
||||
img.width, img.height,
|
||||
100.0 * zero as f64 / n as f64,
|
||||
100.0 * full as f64 / n as f64,
|
||||
if nonzero > 0 { 100.0 * (nonzero - full) as f64 / nonzero as f64 } else { 0.0 },
|
||||
top5.join(" ")
|
||||
);
|
||||
}
|
||||
|
||||
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");
|
||||
for build in [5usize, 6] {
|
||||
let by = ar.read(&ar.entries()[build]).expect("entry");
|
||||
let b = ui_layout::parse_build(&by).expect("build");
|
||||
println!("=== GP_TITLE build {build} ===");
|
||||
let mut names: Vec<&String> = b.sprites.keys().collect();
|
||||
names.sort();
|
||||
for n in names {
|
||||
let (off, size) = b.sprites[n];
|
||||
let s = &by[off..(off + size).min(by.len())];
|
||||
match t8ad::parse(s) {
|
||||
Some(img) => census(n, &img),
|
||||
None => println!("{n:<16} (not a T8aD / failed to parse)"),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
46
crates/sylpheed-formats/examples/frame_keyframe_unknowns.rs
Normal file
46
crates/sylpheed-formats/examples/frame_keyframe_unknowns.rs
Normal file
@@ -0,0 +1,46 @@
|
||||
//! The keyframe record's two unexplained words (`+4`, `+8`) and the fade/tint —
|
||||
//! do any of them separate the four elements the port measures as rendering too
|
||||
//! dark (`ptframe1`/`2`, `ptframe3`/`4`) from the ones it measures as accurate?
|
||||
//!
|
||||
//! The T8aD header does not: no word and no bit of `+0x04`/`+0x08` puts the four
|
||||
//! frames on one side and `pteff10` (max alpha 130, wholly semi-transparent, and
|
||||
//! rendered nearly exact) on the other. The keyframe is the other place a
|
||||
//! per-element draw mode could live.
|
||||
//!
|
||||
//! cargo run -p sylpheed-formats --example frame_keyframe_unknowns
|
||||
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");
|
||||
println!("{:<22} {:>5} {:>3} {:>10} {:>10} {:>8} {:>8} {:>8}",
|
||||
"element", "build", "kf", "unknown_4", "unknown_8", "fade", "tint", "rot");
|
||||
let mut frame_sets: Vec<(String, i32, i32, u32, u32, i32)> = Vec::new();
|
||||
let mut other_sets: Vec<(String, i32, i32, u32, u32, i32)> = Vec::new();
|
||||
for build in [5usize, 6] {
|
||||
let by = ar.read(&ar.entries()[build]).expect("entry");
|
||||
let b = ui_layout::parse_build(&by).expect("build");
|
||||
for e in &b.elements {
|
||||
for (i, k) in e.keyframes.iter().enumerate() {
|
||||
println!("{:<22} {build:>5} {i:>3} {:>10} {:>10} {:08X} {:08X} {:>8}",
|
||||
e.name, k.unknown_4, k.unknown_8, k.fade, k.tint, k.rotation_deg);
|
||||
let row = (e.name.clone(), k.unknown_4, k.unknown_8, k.fade, k.tint, k.rotation_deg);
|
||||
if e.name.contains("frame") { frame_sets.push(row) } else { other_sets.push(row) }
|
||||
}
|
||||
}
|
||||
}
|
||||
println!("\nframe keyframes: {} other keyframes: {}", frame_sets.len(), other_sets.len());
|
||||
for (label, get) in [
|
||||
("unknown_4", 0usize), ("unknown_8", 1), ("fade", 2), ("tint", 3), ("rotation", 4),
|
||||
] {
|
||||
let val = |r: &(String, i32, i32, u32, u32, i32)| -> i64 {
|
||||
match get { 0 => r.1 as i64, 1 => r.2 as i64, 2 => r.3 as i64, 3 => r.4 as i64, _ => r.5 as i64 }
|
||||
};
|
||||
let fv: std::collections::BTreeSet<i64> = frame_sets.iter().map(val).collect();
|
||||
let ov: std::collections::BTreeSet<i64> = other_sets.iter().map(val).collect();
|
||||
let only_frames: Vec<&i64> = fv.iter().filter(|v| !ov.contains(v)).collect();
|
||||
println!("{label:<10} frames take {:?} others take {} distinct values; frame-only values: {:?}",
|
||||
fv, ov.len(), only_frames);
|
||||
}
|
||||
}
|
||||
63
crates/sylpheed-formats/examples/frame_vs_accurate_words.rs
Normal file
63
crates/sylpheed-formats/examples/frame_vs_accurate_words.rs
Normal file
@@ -0,0 +1,63 @@
|
||||
//! Which T8aD header word, if any, separates the FOUR elements the port measures
|
||||
//! as rendering too dark (`ptframe1`/`2` on the main menu, `ptframe3`/`4` on
|
||||
//! `EXTRAS`) from the elements on the same two screens it measures as accurate?
|
||||
//!
|
||||
//! The control that matters: `pteff10` has max alpha 130 and no fully-opaque
|
||||
//! pixel — the same "wholly semi-transparent" property the port proposed as the
|
||||
//! reason the frames are special — and it renders nearly exact. So the separator
|
||||
//! must put `pteff10` on the ACCURATE side.
|
||||
//!
|
||||
//! cargo run -p sylpheed-formats --example frame_vs_accurate_words
|
||||
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 mut rows: Vec<(String, usize, Vec<u32>)> = Vec::new();
|
||||
for build in [5usize, 6] {
|
||||
let by = ar.read(&ar.entries()[build]).expect("entry");
|
||||
let b = ui_layout::parse_build(&by).expect("build");
|
||||
let mut names: Vec<&String> = b.sprites.keys().collect();
|
||||
names.sort();
|
||||
for n in names {
|
||||
let (off, size) = b.sprites[n];
|
||||
let s = &by[off..(off + size).min(by.len())];
|
||||
if s.len() < 48 || &s[0..4] != b"T8aD" { 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();
|
||||
rows.push((n.clone(), build, ws));
|
||||
}
|
||||
}
|
||||
let is_frame = |n: &str| n.starts_with("ptframe");
|
||||
println!("{:<16} {:>5} +0x04 +0x08 +0x1C +0x2C", "sprite", "build");
|
||||
for (n, b, w) in &rows {
|
||||
println!("{n:<16} {b:>5} {:08X} {:08X} {:08X} {:08X}{}",
|
||||
w[1], w[2], w[7], w[11], if is_frame(n) { " <- TOO DARK" } else { "" });
|
||||
}
|
||||
println!("\nwords where every ptframe* agrees and NO other sprite takes that value:");
|
||||
let frames: Vec<&(String, usize, Vec<u32>)> = rows.iter().filter(|(n,_,_)| is_frame(n)).collect();
|
||||
let mut any = false;
|
||||
for k in 0..12 {
|
||||
let v = frames[0].2[k];
|
||||
if !frames.iter().all(|r| r.2[k] == v) { continue }
|
||||
if rows.iter().any(|(n,_,w)| !is_frame(n) && w[k] == v) { continue }
|
||||
println!(" word {k} (+0x{:02X}) = {v:08X}", k*4); any = true;
|
||||
}
|
||||
if !any { println!(" NONE — no header word separates the four frames from the rest"); }
|
||||
println!("\nper-bit check on +0x04 and +0x08 (a bit that is 1 on all frames, 0 on all others):");
|
||||
let mut anyb = false;
|
||||
for &k in &[1usize, 2] {
|
||||
for bit in 0..32 {
|
||||
let on = |v: u32| (v >> bit) & 1 == 1;
|
||||
if frames.iter().all(|r| on(r.2[k])) && rows.iter().all(|(n,_,w)| is_frame(n) || !on(w[k])) {
|
||||
println!(" +0x{:02X} bit {bit} (0x{:X})", k*4, 1u32 << bit); anyb = true;
|
||||
}
|
||||
if frames.iter().all(|r| !on(r.2[k])) && rows.iter().all(|(n,_,w)| is_frame(n) || on(w[k])) {
|
||||
println!(" +0x{:02X} bit {bit} (0x{:X}) INVERTED", k*4, 1u32 << bit); anyb = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if !anyb { println!(" NONE"); }
|
||||
}
|
||||
Reference in New Issue
Block a user