style: rustfmt sweep — 107 files the lint gate never saw

This branch predates CI on `main`. `cargo fmt --all` only; no behaviour.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
MechaCat02
2026-09-12 16:34:40 +02:00
parent 891c38e87e
commit d394ba6aed
107 changed files with 3489 additions and 1280 deletions

View File

@@ -655,7 +655,9 @@ fn cmd_screen_render(
Some(t)
}
_ => {
println!("no settle window (fewer than two distinct keyframe times) — using rest()");
println!(
"no settle window (fewer than two distinct keyframe times) — using rest()"
);
None
}
}

View File

@@ -1,35 +1,89 @@
use sylpheed_formats::{pak, ui_layout};
fn main(){
let mut a=std::env::args().skip(1);
let pk=a.next().unwrap(); let i:usize=a.next().unwrap().parse().unwrap();
let ar=pak::PakArchive::open(pk).unwrap();
let by=ar.read(&ar.entries()[i]).unwrap();
let b=ui_layout::parse_build(&by).unwrap();
println!("entry {i}: {} elements, {} records, {} sprites",
b.elements.len(), b.records.len(), b.sprites.len());
let mut rk:Vec<&String>=b.records.keys().collect(); rk.sort();
fn main() {
let mut a = std::env::args().skip(1);
let pk = a.next().unwrap();
let i: usize = a.next().unwrap().parse().unwrap();
let ar = pak::PakArchive::open(pk).unwrap();
let by = ar.read(&ar.entries()[i]).unwrap();
let b = ui_layout::parse_build(&by).unwrap();
println!(
"entry {i}: {} elements, {} records, {} sprites",
b.elements.len(),
b.records.len(),
b.sprites.len()
);
let mut rk: Vec<&String> = b.records.keys().collect();
rk.sort();
println!(" records: {:?}", rk);
for (rn,&(o,sz)) in &b.records {
if o+sz>by.len() { continue }
let Some(lb)=ui_layout::parse_build(&by[o..o+sz]) else { continue };
for (rn, &(o, sz)) in &b.records {
if o + sz > by.len() {
continue;
}
let Some(lb) = ui_layout::parse_build(&by[o..o + sz]) else {
continue;
};
println!(" RECORD {rn}: {} elements", lb.elements.len());
for le in &lb.elements {
let lts:Vec<String>=le.keyframes.iter()
.map(|k|format!("t{}a{}",k.time.map(|v|v as i64).unwrap_or(-1),k.fade>>24)).collect();
println!(" [{}] {:<22} kind=0x{:<6x} {}", le.index, le.name, le.kind, lts.join(" "));
let lts: Vec<String> = le
.keyframes
.iter()
.map(|k| {
format!(
"t{}a{}",
k.time.map(|v| v as i64).unwrap_or(-1),
k.fade >> 24
)
})
.collect();
println!(
" [{}] {:<22} kind=0x{:<6x} {}",
le.index,
le.name,
le.kind,
lts.join(" ")
);
}
}
for e in &b.elements {
let ts:Vec<String>=e.keyframes.iter()
.map(|k|format!("t{}a{}",k.time.map(|v|v as i64).unwrap_or(-1),k.fade>>24)).collect();
println!(" [{}] {:<24} kind=0x{:<6x} {}", e.index, e.name, e.kind, ts.join(" "));
if let Some(&(o,s))=b.records.get(&e.name) {
if o+s<=by.len() {
if let Some(lb)=ui_layout::parse_build(&by[o..o+s]) {
let ts: Vec<String> = e
.keyframes
.iter()
.map(|k| {
format!(
"t{}a{}",
k.time.map(|v| v as i64).unwrap_or(-1),
k.fade >> 24
)
})
.collect();
println!(
" [{}] {:<24} kind=0x{:<6x} {}",
e.index,
e.name,
e.kind,
ts.join(" ")
);
if let Some(&(o, s)) = b.records.get(&e.name) {
if o + s <= by.len() {
if let Some(lb) = ui_layout::parse_build(&by[o..o + s]) {
for le in &lb.elements {
let lts:Vec<String>=le.keyframes.iter()
.map(|k|format!("t{}a{}",k.time.map(|v|v as i64).unwrap_or(-1),k.fade>>24)).collect();
println!(" leaf {:<20} kind=0x{:<6x} {}", le.name, le.kind, lts.join(" "));
let lts: Vec<String> = le
.keyframes
.iter()
.map(|k| {
format!(
"t{}a{}",
k.time.map(|v| v as i64).unwrap_or(-1),
k.fade >> 24
)
})
.collect();
println!(
" leaf {:<20} kind=0x{:<6x} {}",
le.name,
le.kind,
lts.join(" ")
);
}
}
}

View File

@@ -1,21 +1,27 @@
use sylpheed_formats::{pak, ui_layout};
fn main(){
let mut a=std::env::args().skip(1);
let pk=a.next().unwrap();
let ar=pak::PakArchive::open(pk).unwrap();
fn main() {
let mut a = std::env::args().skip(1);
let pk = a.next().unwrap();
let ar = pak::PakArchive::open(pk).unwrap();
for t in a {
let i:usize=t.parse().unwrap();
let by=ar.read(&ar.entries()[i]).unwrap();
let b=ui_layout::parse_build(&by).unwrap();
let i: usize = t.parse().unwrap();
let by = ar.read(&ar.entries()[i]).unwrap();
let b = ui_layout::parse_build(&by).unwrap();
println!("=== entry {i} ===");
let order=ui_layout::derived_paint_order(&b,&by);
let order = ui_layout::derived_paint_order(&b, &by);
for e in &b.elements {
let k=ui_layout::sprite_layer_key(&b,&by,e);
let pos=order.iter().position(|&x|x==e.index);
println!(" [{}] {:<24} kind=0x{:<5x} sprite={:<24} key={:<12} paint#{:?}",
e.index, e.name, e.kind,
e.sprite.clone().unwrap_or_else(||"<none>".into()),
k.map(|v|format!("0x{v:08x}")).unwrap_or_else(||"NONE".into()), pos);
let k = ui_layout::sprite_layer_key(&b, &by, e);
let pos = order.iter().position(|&x| x == e.index);
println!(
" [{}] {:<24} kind=0x{:<5x} sprite={:<24} key={:<12} paint#{:?}",
e.index,
e.name,
e.kind,
e.sprite.clone().unwrap_or_else(|| "<none>".into()),
k.map(|v| format!("0x{v:08x}"))
.unwrap_or_else(|| "NONE".into()),
pos
);
}
}
}

View File

@@ -1,36 +1,84 @@
use sylpheed_formats::{pak, ratc, ui_layout};
fn main(){
let root=std::env::var("SYLPHEED_DISC").unwrap_or_else(|_|"/disc".into());
let ar=pak::PakArchive::open(format!("{root}/dat/GP_READY_ROOM.pak")).unwrap();
for (i,e) in ar.entries().iter().enumerate() {
let Ok(by)=ar.read(e) else { continue };
if !ratc::is_ratc(&by) { continue }
let Some(b)=ui_layout::parse_build(&by) else { continue };
let Some(p)=b.elements.iter().find(|el|el.name=="pbafc.prm") else { continue };
fn main() {
let root = std::env::var("SYLPHEED_DISC").unwrap_or_else(|_| "/disc".into());
let ar = pak::PakArchive::open(format!("{root}/dat/GP_READY_ROOM.pak")).unwrap();
for (i, e) in ar.entries().iter().enumerate() {
let Ok(by) = ar.read(e) else { continue };
if !ratc::is_ratc(&by) {
continue;
}
let Some(b) = ui_layout::parse_build(&by) else {
continue;
};
let Some(p) = b.elements.iter().find(|el| el.name == "pbafc.prm") else {
continue;
};
println!("=== entry {i}: {} elements ===", b.elements.len());
println!(" pbafc.prm pivot=({},{}) -> {}x{}", p.pivot_x,p.pivot_y,p.pivot_x*2,p.pivot_y*2);
println!(
" pbafc.prm pivot=({},{}) -> {}x{}",
p.pivot_x,
p.pivot_y,
p.pivot_x * 2,
p.pivot_y * 2
);
for k in &p.keyframes {
println!(" t={:<5} fade={:08x} a={:<4} xy=({},{}) s={}/{}",
k.time.map(|v|v as i64).unwrap_or(-1), k.fade, k.fade>>24, k.x,k.y,k.scale_x,k.scale_y);
println!(
" t={:<5} fade={:08x} a={:<4} xy=({},{}) s={}/{}",
k.time.map(|v| v as i64).unwrap_or(-1),
k.fade,
k.fade >> 24,
k.x,
k.y,
k.scale_x,
k.scale_y
);
}
// what does it cover, and is anything visible while it is opaque?
let rest=p.rest().unwrap();
let (px,py,pw,ph)=(rest.x, rest.y, (p.pivot_x*2) as i32, (p.pivot_y*2) as i32);
let rest = p.rest().unwrap();
let (px, py, pw, ph) = (
rest.x,
rest.y,
(p.pivot_x * 2) as i32,
(p.pivot_y * 2) as i32,
);
println!(" its rect at rest: ({px},{py}) {pw}x{ph}");
let tmax=b.elements.iter().flat_map(|e|e.keyframes.iter().filter_map(|k|k.time)).max().unwrap_or(0);
let op:Vec<u32>=(0..=tmax).filter(|&t|p.pose_at(t).map(|k|k.fade>>24)==Some(255)).collect();
println!(" opaque at {} instants (t={:?}..{:?}) of 0..{tmax}", op.len(), op.first(), op.last());
let mut cov=0; let mut vis=0;
let tmax = b
.elements
.iter()
.flat_map(|e| e.keyframes.iter().filter_map(|k| k.time))
.max()
.unwrap_or(0);
let op: Vec<u32> = (0..=tmax)
.filter(|&t| p.pose_at(t).map(|k| k.fade >> 24) == Some(255))
.collect();
println!(
" opaque at {} instants (t={:?}..{:?}) of 0..{tmax}",
op.len(),
op.first(),
op.last()
);
let mut cov = 0;
let mut vis = 0;
for o in &b.elements {
if o.index==p.index { continue }
let Some(ok)=o.rest() else { continue };
let (ow,oh)=((o.pivot_x*2) as i32,(o.pivot_y*2) as i32);
let overlap=(px+pw).min(ok.x+ow)-px.max(ok.x)>0 && (py+ph).min(ok.y+oh)-py.max(ok.y)>0;
if !overlap { continue }
cov+=1;
if op.iter().any(|&t| o.pose_at(t).map(|k|k.fade>>24).unwrap_or(0)>0) {
vis+=1;
if vis<=6 { println!(" covered AND visible while opaque: {}", o.name); }
if o.index == p.index {
continue;
}
let Some(ok) = o.rest() else { continue };
let (ow, oh) = ((o.pivot_x * 2) as i32, (o.pivot_y * 2) as i32);
let overlap = (px + pw).min(ok.x + ow) - px.max(ok.x) > 0
&& (py + ph).min(ok.y + oh) - py.max(ok.y) > 0;
if !overlap {
continue;
}
cov += 1;
if op
.iter()
.any(|&t| o.pose_at(t).map(|k| k.fade >> 24).unwrap_or(0) > 0)
{
vis += 1;
if vis <= 6 {
println!(" covered AND visible while opaque: {}", o.name);
}
}
}
println!(" elements its rect covers: {cov}; visible while it is opaque: {vis}");

View File

@@ -1,24 +1,35 @@
use sylpheed_formats::{pak, ui_layout};
fn main(){
let mut a=std::env::args().skip(1);
let pk=a.next().unwrap();
let ar=pak::PakArchive::open(pk).unwrap();
fn main() {
let mut a = std::env::args().skip(1);
let pk = a.next().unwrap();
let ar = pak::PakArchive::open(pk).unwrap();
for t in a {
let i:usize=t.parse().unwrap();
let by=ar.read(&ar.entries()[i]).unwrap();
let Some(b)=ui_layout::parse_build(&by) else { continue };
let top=u32::from_be_bytes(by[8..12].try_into().unwrap());
let i: usize = t.parse().unwrap();
let by = ar.read(&ar.entries()[i]).unwrap();
let Some(b) = ui_layout::parse_build(&by) else {
continue;
};
let top = u32::from_be_bytes(by[8..12].try_into().unwrap());
println!("entry {i:2} TOP +08 = {top}");
let mut ks:Vec<&String>=b.records.keys().collect(); ks.sort();
let mut ks: Vec<&String> = b.records.keys().collect();
ks.sort();
for rn in ks {
let &(o,s)=b.records.get(rn).unwrap();
if o+16>by.len() { continue }
let magic=&by[o..o+4];
let h4=u32::from_be_bytes(by[o+4..o+8].try_into().unwrap());
let h8=u32::from_be_bytes(by[o+8..o+12].try_into().unwrap());
let maxt=ui_layout::parse_build(&by[o..o+s])
.map(|lb|lb.elements.iter().flat_map(|e|e.keyframes.iter()
.filter_map(|k|k.time)).max().unwrap_or(0)).unwrap_or(0);
let &(o, s) = b.records.get(rn).unwrap();
if o + 16 > by.len() {
continue;
}
let magic = &by[o..o + 4];
let h4 = u32::from_be_bytes(by[o + 4..o + 8].try_into().unwrap());
let h8 = u32::from_be_bytes(by[o + 8..o + 12].try_into().unwrap());
let maxt = ui_layout::parse_build(&by[o..o + s])
.map(|lb| {
lb.elements
.iter()
.flat_map(|e| e.keyframes.iter().filter_map(|k| k.time))
.max()
.unwrap_or(0)
})
.unwrap_or(0);
println!(" {rn:<24} magic={:?} +04={:08x}({:.1}) +08={h8:<6} max keyframe t={maxt} ratio={:.4}",
String::from_utf8_lossy(magic), h4, h4 as f64/65536.0,
if maxt>0 {h8 as f64/maxt as f64} else {0.0});

View File

@@ -8,19 +8,34 @@ fn main() {
println!("# Before that change EVERY row below was drawn alpha-over by our renderer,");
println!("# which is why `verify-screen` was structurally incapable on these screens.");
for pak in ["GP_TITLE", "GP_OPTIONS"] {
let Ok(ar) = PakArchive::open(root.join(format!("dat/{pak}.pak"))) else { continue };
let Ok(ar) = PakArchive::open(root.join(format!("dat/{pak}.pak"))) else {
continue;
};
let n = ar.entries().len();
for e in 0..n {
let Ok(by) = ar.read(&ar.entries()[e]) else { continue };
let Some(b) = ui_layout::parse_build(&by) else { continue };
let mut add: Vec<&String> = b.sprites.keys()
let Ok(by) = ar.read(&ar.entries()[e]) else {
continue;
};
let Some(b) = ui_layout::parse_build(&by) else {
continue;
};
let mut add: Vec<&String> = b
.sprites
.keys()
.filter(|s| ui_layout::blend_additive_by_name(&b, &by, s) == Some(true))
.collect();
if add.is_empty() { continue }
if add.is_empty() {
continue;
}
add.sort();
println!("\n{pak} entry {e} -- {} of {} sprites additive",
add.len(), b.sprites.len());
for s in add { println!(" {s}"); }
println!(
"\n{pak} entry {e} -- {} of {} sprites additive",
add.len(),
b.sprites.len()
);
for s in add {
println!(" {s}");
}
}
}
}

View File

@@ -28,7 +28,11 @@ fn main() {
let (start, end) =
media::resolve_movie_voice_region(&src, "ADV", VoiceLang::English).expect("region");
println!("resolver says {start}..{end} ({} B)", end - start);
println!("decoder wants {:?} = {} B payload\n", WANT, WANT.iter().sum::<usize>());
println!(
"decoder wants {:?} = {} B payload\n",
WANT,
WANT.iter().sum::<usize>()
);
for back_packets in [0usize, 100, 200, 237, 238, 239, 300, 400] {
let back = (back_packets * 2048) as u64;
@@ -47,7 +51,11 @@ fn main() {
"-{back_packets:4} packets (start {s}): {} chunk(s) {:?}{}",
riffs.len(),
sizes,
if hit { " <== MATCHES THE DECODER" } else { "" }
if hit {
" <== MATCHES THE DECODER"
} else {
""
}
);
}
}

View File

@@ -21,8 +21,8 @@ fn main() {
let src = DirectorySource::new(std::path::PathBuf::from(&disc));
std::fs::create_dir_all(&out).expect("outdir");
let (start, end) = media::resolve_movie_voice_region(&src, &movie, VoiceLang::English)
.expect("voice region");
let (start, end) =
media::resolve_movie_voice_region(&src, &movie, VoiceLang::English).expect("voice region");
let bytes = src
.read_segment_range("dat/sound", start, (end - start) as usize)
.expect("region");
@@ -35,7 +35,10 @@ fn main() {
std::fs::write(&p, r).expect("write");
// the probe reports `byte_size` = RIFF total - 60; print both so the
// dump can be tied to a specific XMA context by its own number
println!(" chunk {i}: {} B byte_size-equivalent {} -> {p}",
r.len(), r.len() as i64 - 60);
println!(
" chunk {i}: {} B byte_size-equivalent {} -> {p}",
r.len(),
r.len() as i64 - 60
);
}
}

View File

@@ -30,7 +30,11 @@ fn main() {
" stream {i}: payload {payload} B ({} packets) declared {rate} B/s \
=> {:.3} s",
payload / 2048,
if rate > 0 { payload as f64 / rate as f64 } else { 0.0 }
if rate > 0 {
payload as f64 / rate as f64
} else {
0.0
}
);
}
}

View File

@@ -6,7 +6,9 @@ use sylpheed_formats::media::{self, DirectorySource};
use sylpheed_formats::slb;
fn main() {
let name = std::env::args().nth(1).unwrap_or_else(|| "BGM_103.slb".into());
let name = std::env::args()
.nth(1)
.unwrap_or_else(|| "BGM_103.slb".into());
let out = std::env::args().nth(2).expect("OUTDIR");
let disc = std::env::var("SYLPHEED_DISC").expect("SYLPHEED_DISC");
let src = DirectorySource::new(std::path::PathBuf::from(&disc));
@@ -17,6 +19,10 @@ fn main() {
for (i, r) in slb::to_xma_riffs(&bytes).iter().enumerate() {
let p = format!("{out}/{}_{i}.xma", name.trim_end_matches(".slb"));
std::fs::write(&p, r).expect("write");
println!(" wave {i}: {} B (byte_size {}) -> {p}", r.len(), r.len() - 60);
println!(
" wave {i}: {} B (byte_size {}) -> {p}",
r.len(),
r.len() - 60
);
}
}

View File

@@ -17,17 +17,21 @@
//! bundles before its disc-wide numbers mean anything.
//!
//! cargo run -p sylpheed-formats --example black_backdrop_predicate
use sylpheed_formats::{pak::PakArchive, ui_layout};
use std::io::Write;
use std::path::PathBuf;
use sylpheed_formats::{pak::PakArchive, ui_layout};
/// A screen declares its own backdrop if some `.prm` primitive holds
/// `fade == 0xff000000` at t = 0: full alpha over black.
fn has_black_backdrop(b: &ui_layout::UiBuild) -> Option<String> {
for el in &b.elements {
if !el.name.ends_with(".prm") { continue }
if !el.name.ends_with(".prm") {
continue;
}
if let Some(k) = el.keyframes.iter().find(|k| k.time == Some(0)) {
if k.fade == 0xff00_0000 { return Some(el.name.clone()) }
if k.fade == 0xff00_0000 {
return Some(el.name.clone());
}
}
}
None
@@ -40,39 +44,63 @@ fn main() {
let ar = PakArchive::open(root.join("dat/GP_TITLE.pak")).expect("GP_TITLE.pak");
let (mut y, mut n) = (0, 0);
for e in 0..16usize {
let Ok(by) = ar.read(&ar.entries()[e]) else { continue };
let Some(b) = ui_layout::parse_build(&by) else { continue };
let Ok(by) = ar.read(&ar.entries()[e]) else {
continue;
};
let Some(b) = ui_layout::parse_build(&by) else {
continue;
};
match has_black_backdrop(&b) {
Some(nm) => { y += 1; println!(" entry {e:>2} YES {nm}") }
None => { n += 1; println!(" entry {e:>2} no") }
Some(nm) => {
y += 1;
println!(" entry {e:>2} YES {nm}")
}
None => {
n += 1;
println!(" entry {e:>2} no")
}
}
}
println!(" -> {y} with, {n} without\n");
println!("== DISC-WIDE, over every screen build");
let mut paks: Vec<PathBuf> = std::fs::read_dir(root.join("dat")).expect("dat/")
let mut paks: Vec<PathBuf> = std::fs::read_dir(root.join("dat"))
.expect("dat/")
.filter_map(|e| e.ok().map(|e| e.path()))
.filter(|p| p.extension().is_some_and(|x| x == "pak")).collect();
.filter(|p| p.extension().is_some_and(|x| x == "pak"))
.collect();
paks.sort();
let (mut tot, mut with) = (0usize, 0usize);
for pak in &paks {
let Ok(ar) = PakArchive::open(pak) else { continue };
let Ok(ar) = PakArchive::open(pak) else {
continue;
};
let name = pak.file_name().unwrap().to_string_lossy().to_string();
let (mut t, mut w) = (0usize, 0usize);
for e in ar.entries() {
let Ok(by) = ar.read(e) else { continue };
if !ui_layout::is_build(&by) { continue }
let Some(b) = ui_layout::parse_build(&by) else { continue };
if !ui_layout::is_build(&by) {
continue;
}
let Some(b) = ui_layout::parse_build(&by) else {
continue;
};
t += 1;
if has_black_backdrop(&b).is_some() { w += 1 }
if has_black_backdrop(&b).is_some() {
w += 1
}
}
if t > 0 {
println!("{name:30} {w:4} / {t:<4} declare a black backdrop");
std::io::stdout().flush().ok();
}
tot += t; with += w;
tot += t;
with += w;
}
println!("\n{with} of {tot} screen builds disc-wide declare an opaque-black backdrop \
({:.1} %)", 100.0 * with as f64 / tot as f64);
println!(
"\n{with} of {tot} screen builds disc-wide declare an opaque-black backdrop \
({:.1} %)",
100.0 * with as f64 / tot as f64
);
println!("--- END ---");
}

View File

@@ -14,51 +14,94 @@ use sylpheed_formats::{pak::PakArchive, ui_layout};
/// (build entry, sprite, measured additive?) — from `data/blend-bit-vs-oracle.txt`,
/// every label an `RB_BLENDCONTROL0` value read out of the guest command stream.
const MEASURED: &[(usize, &str, bool)] = &[
(4, "ptbase2.t32", false), (4, "ptlogo1.t32", false), (4, "ptlogo2.t32", false),
(4, "ptlogo_tm.t32", false), (4, "ptcopyright.t32", false),
(4, "ptlogo_back2.t32", false), (4, "ptlogo_back2eff.t32", false),
(2, "ptbtn00.t32", false), (2, "ptbtn00f.t32", true),
(5, "ptbase.t32", false), (5, "ptmsg.t32", false), (5, "ptbtn01f.t32", false),
(5, "ptbtneff01.t32", false), (5, "pteff10.t32", true), (5, "pteff12.t32", true),
(6, "pteff21.t32", true), (6, "pteff22.t32", true), (6, "pteff23.t32", true),
(6, "ptframe3.t32", true), (6, "ptframe4.t32", true),
(6, "pteff03.t32", true), (6, "pteff03a.t32", true),
(4, "ptbase2.t32", false),
(4, "ptlogo1.t32", false),
(4, "ptlogo2.t32", false),
(4, "ptlogo_tm.t32", false),
(4, "ptcopyright.t32", false),
(4, "ptlogo_back2.t32", false),
(4, "ptlogo_back2eff.t32", false),
(2, "ptbtn00.t32", false),
(2, "ptbtn00f.t32", true),
(5, "ptbase.t32", false),
(5, "ptmsg.t32", false),
(5, "ptbtn01f.t32", false),
(5, "ptbtneff01.t32", false),
(5, "pteff10.t32", true),
(5, "pteff12.t32", true),
(6, "pteff21.t32", true),
(6, "pteff22.t32", true),
(6, "pteff23.t32", true),
(6, "ptframe3.t32", true),
(6, "ptframe4.t32", true),
(6, "pteff03.t32", true),
(6, "pteff03a.t32", true),
];
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 ok, mut bad, mut missing) = (0, 0, 0);
println!("{:<7} {:<22} {:<10} {:<10} {}", "entry", "sprite", "expected", "accessor", "");
println!(
"{:<7} {:<22} {:<10} {:<10} {}",
"entry", "sprite", "expected", "accessor", ""
);
for e in [2usize, 4, 5, 6] {
let by = ar.read(&ar.entries()[e]).expect("entry");
let b = ui_layout::parse_build(&by).expect("build");
for &(oe, name, additive) in MEASURED {
if oe != e { continue }
if oe != e {
continue;
}
// Prefer the Element accessor; fall back to the by-name one for
// focused variants, which are reached through `opt ` and carry no
// top-level element of their own.
let got = b.elements.iter().find(|x| x.sprite.as_deref() == Some(name))
let got = b
.elements
.iter()
.find(|x| x.sprite.as_deref() == Some(name))
.and_then(|el| ui_layout::sprite_blend_additive(&b, &by, el))
.or_else(|| ui_layout::blend_additive_by_name(&b, &by, name));
if got.is_none() { println!("{e:<7} {name:<22} {additive:<10} {:<10} MISSING", "-"); missing += 1; continue }
if got.is_none() {
println!("{e:<7} {name:<22} {additive:<10} {:<10} MISSING", "-");
missing += 1;
continue;
}
match got {
Some(g) if g == additive => { ok += 1;
println!("{e:<7} {name:<22} {additive:<10} {g:<10} OK"); }
other => { bad += 1;
println!("{e:<7} {name:<22} {additive:<10} {other:?} MISMATCH"); }
Some(g) if g == additive => {
ok += 1;
println!("{e:<7} {name:<22} {additive:<10} {g:<10} OK");
}
other => {
bad += 1;
println!("{e:<7} {name:<22} {additive:<10} {other:?} MISMATCH");
}
}
}
}
println!("\n{ok} agree, {bad} mismatched, {missing} not found (of {})", MEASURED.len());
println!(
"\n{ok} agree, {bad} mismatched, {missing} not found (of {})",
MEASURED.len()
);
// The control that removes the test's own subject: the accessor must also
// report a MIX. An accessor stuck at one value would pass every `false` row.
let by = ar.read(&ar.entries()[6]).expect("entry");
let b = ui_layout::parse_build(&by).expect("build");
let add = b.elements.iter().filter(|e| ui_layout::sprite_blend_additive(&b, &by, e) == Some(true)).count();
let over = b.elements.iter().filter(|e| ui_layout::sprite_blend_additive(&b, &by, e) == Some(false)).count();
let add = b
.elements
.iter()
.filter(|e| ui_layout::sprite_blend_additive(&b, &by, e) == Some(true))
.count();
let over = b
.elements
.iter()
.filter(|e| ui_layout::sprite_blend_additive(&b, &by, e) == Some(false))
.count();
println!("control -- entry 6 must report BOTH values: additive={add} alpha-over={over}");
assert!(add > 0 && over > 0, "accessor is not discriminating");
assert_eq!(bad, 0, "the public accessor disagrees with the committed oracle");
assert_eq!(
bad, 0,
"the public accessor disagrees with the committed oracle"
);
println!("PASS");
}

View File

@@ -15,31 +15,46 @@
//!
//! cargo run -p sylpheed-formats --example blend_prediction_splash
//! cargo run -p sylpheed-formats --example blend_prediction_splash -- GP_OPTIONS 0 1 2
use sylpheed_formats::{pak::PakArchive, ui_layout};
use std::path::PathBuf;
use sylpheed_formats::{pak::PakArchive, ui_layout};
fn main() {
let root = PathBuf::from(std::env::var("SYLPHEED_DISC").expect("SYLPHEED_DISC"));
let args: Vec<String> = std::env::args().skip(1).collect();
let pak = args.iter().find(|a| a.parse::<usize>().is_err())
.cloned().unwrap_or_else(|| "GP_TITLE".to_string());
let pak = args
.iter()
.find(|a| a.parse::<usize>().is_err())
.cloned()
.unwrap_or_else(|| "GP_TITLE".to_string());
let ar = PakArchive::open(root.join(format!("dat/{pak}.pak"))).expect("pak");
let builds: Vec<usize> = args.iter().filter_map(|a| a.parse().ok()).collect();
let builds = if builds.is_empty() { (0..ar.entries().len()).collect() } else { builds };
let builds = if builds.is_empty() {
(0..ar.entries().len()).collect()
} else {
builds
};
for e in builds {
let Ok(by) = ar.read(&ar.entries()[e]) else { continue };
let Some(b) = ui_layout::parse_build(&by) else { continue };
let Ok(by) = ar.read(&ar.entries()[e]) else {
continue;
};
let Some(b) = ui_layout::parse_build(&by) else {
continue;
};
println!("=== {pak} entry {e} ===");
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() < 8 || &s[0..4] != b"T8aD" { continue }
if s.len() < 8 || &s[0..4] != b"T8aD" {
continue;
}
let w = u32::from_be_bytes([s[4], s[5], s[6], s[7]]);
println!("{n:<26} +0x04 = {w:08X} bit 0x02 {} PREDICT {}",
if w & 2 != 0 { "SET " } else { "clear" },
if w & 2 != 0 { "ADDITIVE" } else { "alpha-over" });
println!(
"{n:<26} +0x04 = {w:08X} bit 0x02 {} PREDICT {}",
if w & 2 != 0 { "SET " } else { "clear" },
if w & 2 != 0 { "ADDITIVE" } else { "alpha-over" }
);
}
println!();
}

View File

@@ -15,9 +15,9 @@
//! `data/ui-blend-extras-complete.txt`.
//!
//! cargo run -p sylpheed-formats --example blend_vs_t8ad_bit
use sylpheed_formats::{pak::PakArchive, ui_layout};
use std::collections::BTreeMap;
use std::path::PathBuf;
use sylpheed_formats::{pak::PakArchive, ui_layout};
/// (build entry, sprite, measured additive?) — the oracle's verdicts, verbatim.
const MEASURED: &[(usize, &str, bool)] = &[
@@ -40,7 +40,7 @@ const MEASURED: &[(usize, &str, bool)] = &[
(5, "pteff12.t32", true),
(5, "ptframe1.t32", true),
(5, "ptframe2.t32", true),
(5, "pteff03.t32", true), // the rotated sweep strips, via ptloop01/02
(5, "pteff03.t32", true), // the rotated sweep strips, via ptloop01/02
(5, "pteff03a.t32", true),
// --- entry 6, EXTRAS ------------------------------------------------------
(6, "ptbase.t32", false),
@@ -62,7 +62,10 @@ const MEASURED: &[(usize, &str, bool)] = &[
];
fn main() {
if std::env::args().any(|a| a == "decl") { decl_rivals(); return }
if std::env::args().any(|a| a == "decl") {
decl_rivals();
return;
}
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 hdr: BTreeMap<(usize, String), u32> = BTreeMap::new();
@@ -71,16 +74,25 @@ fn main() {
let b = ui_layout::parse_build(&by).expect("build");
for (n, &(off, size)) in &b.sprites {
let s = &by[off..(off + size).min(by.len())];
if s.len() < 8 || &s[0..4] != b"T8aD" { continue }
if s.len() < 8 || &s[0..4] != b"T8aD" {
continue;
}
hdr.insert((e, n.clone()), u32::from_be_bytes([s[4], s[5], s[6], s[7]]));
}
}
println!("{:<10} {:<22} {:<10} {:>10} {}", "entry", "sprite", "+0x04", "bit 0x02", "measured blend");
println!(
"{:<10} {:<22} {:<10} {:>10} {}",
"entry", "sprite", "+0x04", "bit 0x02", "measured blend"
);
let (mut tp, mut tn, mut fp, mut fnn, mut missing) = (0, 0, 0, 0, 0);
for &(e, n, additive) in MEASURED {
let Some(&w) = hdr.get(&(e, n.to_string())) else {
println!("{e:<10} {n:<22} {:<10} {:>10} {}", "MISSING", "-",
if additive { "ADDITIVE" } else { "alpha-over" });
println!(
"{e:<10} {n:<22} {:<10} {:>10} {}",
"MISSING",
"-",
if additive { "ADDITIVE" } else { "alpha-over" }
);
missing += 1;
continue;
};
@@ -91,20 +103,31 @@ fn main() {
(true, false) => fp += 1,
(false, true) => fnn += 1,
}
println!("{e:<10} {n:<22} {:08X} {:>10} {}{}", w, bit,
if additive { "ADDITIVE" } else { "alpha-over" },
if bit == additive { "" } else { " <== DISAGREES" });
println!(
"{e:<10} {n:<22} {:08X} {:>10} {}{}",
w,
bit,
if additive { "ADDITIVE" } else { "alpha-over" },
if bit == additive {
""
} else {
" <== DISAGREES"
}
);
}
println!("\nbit set & additive {tp}");
println!("bit clear & alpha-over {tn}");
println!("bit set & alpha-over {fp} <- false positives");
println!("bit clear & additive {fnn} <- false negatives");
println!("sprite not found {missing}");
println!("\n{}", if fp == 0 && fnn == 0 && missing == 0 {
"PERFECT PARTITION on every element whose blend was measured."
} else {
"THE BIT DOES NOT PREDICT THE MEASURED BLEND."
});
println!(
"\n{}",
if fp == 0 && fnn == 0 && missing == 0 {
"PERFECT PARTITION on every element whose blend was measured."
} else {
"THE BIT DOES NOT PREDICT THE MEASURED BLEND."
}
);
// ── THE CONTROL THAT MATTERS ────────────────────────────────────────────
// A perfect partition is worthless if half the header partitions equally
@@ -119,10 +142,17 @@ fn main() {
let b = ui_layout::parse_build(&by).expect("build");
for (n, &(off, size)) in &b.sprites {
let s = &by[off..(off + size).min(by.len())];
if s.len() < 48 || &s[0..4] != b"T8aD" { continue }
words.insert((e, n.clone()), (0..12)
.map(|k| u32::from_be_bytes([s[k*4], s[k*4+1], s[k*4+2], s[k*4+3]]))
.collect());
if s.len() < 48 || &s[0..4] != b"T8aD" {
continue;
}
words.insert(
(e, n.clone()),
(0..12)
.map(|k| {
u32::from_be_bytes([s[k * 4], s[k * 4 + 1], s[k * 4 + 2], s[k * 4 + 3]])
})
.collect(),
);
}
}
for w in 0..12 {
@@ -131,10 +161,20 @@ fn main() {
let mut set_seen = false;
let mut clear_seen = false;
for &(e, n, additive) in MEASURED {
let Some(v) = words.get(&(e, n.to_string())) else { ok = false; break };
let Some(v) = words.get(&(e, n.to_string())) else {
ok = false;
break;
};
let on = (v[w] >> bit) & 1 == 1;
if on { set_seen = true } else { clear_seen = true }
if on != additive { ok = false; break }
if on {
set_seen = true
} else {
clear_seen = true
}
if on != additive {
ok = false;
break;
}
}
// A constant bit trivially "agrees" with nothing; require both sides.
if ok && set_seen && clear_seen {
@@ -144,11 +184,16 @@ fn main() {
}
println!("\nRIVAL FIELDS — other bits of the first 12 header words that separate");
println!("the same 35 elements with zero errors: {}", rivals.len());
for r in &rivals { println!(" {r}"); }
for r in &rivals {
println!(" {r}");
}
if rivals.len() == 1 {
println!(" -> the sample singles out ONE field. Nothing else in the header does it.");
} else {
println!(" -> the sample does NOT single out a field; {} candidates tie.", rivals.len());
println!(
" -> the sample does NOT single out a field; {} candidates tie.",
rivals.len()
);
}
}
@@ -171,17 +216,33 @@ fn decl_rivals() {
const STRIDE: usize = 60;
let mut decl: BTreeMap<(usize, String), Vec<u32>> = BTreeMap::new();
for e in [2usize, 4, 5, 6] {
let Ok(by) = ar.read(&ar.entries()[e]) else { continue };
if by.len() < 0x18 { continue }
let Ok(by) = ar.read(&ar.entries()[e]) else {
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 }
if at + STRIDE > by.len() {
break;
}
let end = by[at..at + 12].iter().position(|&c| c == 0).unwrap_or(12);
let name = String::from_utf8_lossy(&by[at..at + end]).to_string();
decl.insert((e, name), (0..15)
.map(|k| u32::from_be_bytes([by[at+k*4], by[at+k*4+1], by[at+k*4+2], by[at+k*4+3]]))
.collect());
decl.insert(
(e, name),
(0..15)
.map(|k| {
u32::from_be_bytes([
by[at + k * 4],
by[at + k * 4 + 1],
by[at + k * 4 + 2],
by[at + k * 4 + 3],
])
})
.collect(),
);
}
}
let mut missing: Vec<String> = Vec::new();
@@ -191,23 +252,37 @@ fn decl_rivals() {
}
}
println!("\n=== DECLARATION-ENTRY RIVAL SWEEP ===");
println!("measured elements with NO declaration entry of their own: {} of {}",
missing.len(), MEASURED.len());
for m in &missing { println!(" {m}"); }
println!(
"measured elements with NO declaration entry of their own: {} of {}",
missing.len(),
MEASURED.len()
);
for m in &missing {
println!(" {m}");
}
if !missing.is_empty() {
println!(" -> no declaration field can select the blend for these, because they");
println!(" have no declaration entry. The header is the only per-sprite home.");
}
let labelled: Vec<&(usize, &str, bool)> = MEASURED.iter()
.filter(|(e, n, _)| decl.contains_key(&(*e, n.to_string()))).collect();
let labelled: Vec<&(usize, &str, bool)> = MEASURED
.iter()
.filter(|(e, n, _)| decl.contains_key(&(*e, n.to_string())))
.collect();
let mut rivals = 0;
for w in 0..15 {
for bit in 0..32 {
let (mut ok, mut s, mut c) = (true, false, false);
for &&(e, n, additive) in &labelled {
let on = (decl[&(e, n.to_string())][w] >> bit) & 1 == 1;
if on { s = true } else { c = true }
if on != additive { ok = false; break }
if on {
s = true
} else {
c = true
}
if on != additive {
ok = false;
break;
}
}
if ok && s && c {
println!(" RIVAL: declaration +0x{:02X} bit {bit}", w * 4);
@@ -215,6 +290,8 @@ fn decl_rivals() {
}
}
}
println!("declaration bits that separate the {} labellable elements: {rivals}",
labelled.len());
println!(
"declaration bits that separate the {} labellable elements: {rivals}",
labelled.len()
);
}

View File

@@ -12,8 +12,8 @@
//! separates exactly those two is a candidate; one that does not, is not.
//!
//! cargo run -p sylpheed-formats --example decl_entry_diff
use sylpheed_formats::{pak::PakArchive, ui_layout};
use std::path::PathBuf;
use sylpheed_formats::{pak::PakArchive, ui_layout};
const AT: usize = 0x20;
const N: usize = 60;
@@ -24,8 +24,12 @@ fn main() {
let by = ar.read(&ar.entries()[5]).expect("entry 5");
let b = ui_layout::parse_build(&by).expect("build");
let names: Vec<String> = b.elements.iter().map(|e| e.name.clone()).collect();
let frames: Vec<usize> = names.iter().enumerate()
.filter(|(_, n)| n.starts_with("ptframe")).map(|(i, _)| i).collect();
let frames: Vec<usize> = names
.iter()
.enumerate()
.filter(|(_, n)| n.starts_with("ptframe"))
.map(|(i, _)| i)
.collect();
println!("{} elements; frames at indices {:?}", names.len(), frames);
let word = |i: usize, w: usize| -> u32 {
@@ -36,13 +40,26 @@ fn main() {
for w in 0..N / 4 {
let fv: Vec<u32> = frames.iter().map(|&i| word(i, w)).collect();
let same_in_frames = fv.windows(2).all(|p| p[0] == p[1]);
let others: Vec<u32> = (0..names.len()).filter(|i| !frames.contains(i))
.map(|i| word(i, w)).collect();
let others: Vec<u32> = (0..names.len())
.filter(|i| !frames.contains(i))
.map(|i| word(i, w))
.collect();
let unique = same_in_frames && !others.contains(&fv[0]);
let distinct = { let mut v: Vec<u32> = (0..names.len()).map(|i| word(i, w)).collect();
v.sort_unstable(); v.dedup(); v.len() };
println!(" +0x{:02X} frames {:?} distinct values {distinct:2}{}",
w * 4, fv.iter().map(|v| format!("{v:08X}")).collect::<Vec<_>>(),
if unique { " <- SEPARATES THE FRAMES" } else { "" });
let distinct = {
let mut v: Vec<u32> = (0..names.len()).map(|i| word(i, w)).collect();
v.sort_unstable();
v.dedup();
v.len()
};
println!(
" +0x{:02X} frames {:?} distinct values {distinct:2}{}",
w * 4,
fv.iter().map(|v| format!("{v:08X}")).collect::<Vec<_>>(),
if unique {
" <- SEPARATES THE FRAMES"
} else {
""
}
);
}
}

View File

@@ -8,8 +8,8 @@
//! distinct values: +0x28 (3) and +0x2C (6). This prints who has what.
//!
//! cargo run -p sylpheed-formats --example decl_flag_words
use sylpheed_formats::{pak::PakArchive, ui_layout};
use std::path::PathBuf;
use sylpheed_formats::{pak::PakArchive, ui_layout};
const AT: usize = 0x20;
const N: usize = 60;
@@ -23,10 +23,23 @@ fn main() {
let o = AT + i * N + off;
u32::from_be_bytes([by[o], by[o + 1], by[o + 2], by[o + 3]])
};
println!("{:<22} {:>10} {:>10} {:>10} {:>10}", "element", "+0x28", "+0x2C", "+0x34", "kind");
println!(
"{:<22} {:>10} {:>10} {:>10} {:>10}",
"element", "+0x28", "+0x2C", "+0x34", "kind"
);
for (i, e) in b.elements.iter().enumerate() {
let mark = if e.name.starts_with("ptframe") { " <- FRAME" } else { "" };
println!("{:<22} {:>10} {:>10} {:>10} {:>#10x}{mark}",
e.name, w(i, 0x28), w(i, 0x2C) as i32, w(i, 0x34), e.kind);
let mark = if e.name.starts_with("ptframe") {
" <- FRAME"
} else {
""
};
println!(
"{:<22} {:>10} {:>10} {:>10} {:>#10x}{mark}",
e.name,
w(i, 0x28),
w(i, 0x2C) as i32,
w(i, 0x34),
e.kind
);
}
}

View File

@@ -10,44 +10,62 @@
//! This replicates the scan through the public RATC API and counts.
//!
//! cargo run -p sylpheed-formats --example design_size_fallback
use sylpheed_formats::{pak::PakArchive, ratc, ui_layout};
use std::io::Write;
use std::path::PathBuf;
use sylpheed_formats::{pak::PakArchive, ratc, ui_layout};
fn be32(b: &[u8], o: usize) -> u32 {
if o + 4 > b.len() { return 0 }
if o + 4 > b.len() {
return 0;
}
u32::from_be_bytes([b[o], b[o + 1], b[o + 2], b[o + 3]])
}
fn main() {
let root = PathBuf::from(std::env::var("SYLPHEED_DISC").expect("SYLPHEED_DISC"));
let mut paks: Vec<PathBuf> = std::fs::read_dir(root.join("dat")).expect("dat/")
let mut paks: Vec<PathBuf> = std::fs::read_dir(root.join("dat"))
.expect("dat/")
.filter_map(|e| e.ok().map(|e| e.path()))
.filter(|p| p.extension().is_some_and(|x| x == "pak")).collect();
.filter(|p| p.extension().is_some_and(|x| x == "pak"))
.collect();
paks.sort();
let (mut read, mut fell_back, mut nonstd) = (0usize, 0usize, 0usize);
for pak in &paks {
let Ok(ar) = PakArchive::open(pak) else { continue };
let Ok(ar) = PakArchive::open(pak) else {
continue;
};
let name = pak.file_name().unwrap().to_string_lossy().to_string();
let (mut r, mut f) = (0usize, 0usize);
for e in ar.entries() {
let Ok(by) = ar.read(e) else { continue };
if !ui_layout::is_build(&by) { continue }
let Some(kids) = ratc::parse(&by) else { continue };
if !ui_layout::is_build(&by) {
continue;
}
let Some(kids) = ratc::parse(&by) else {
continue;
};
// the same predicate ui_layout uses, over the same records
// ⚠️ A first version took EVERY RATC child and failed its control:
// it reported all 965 builds stating a non-1280x720 size, where
// `screen list` prints 1280x720 for every one. `records` in
// ui_layout is the `.rat` children only; a T8aD sprite header read
// at +0x18 is garbage that passes the range test.
let found = kids.iter().filter(|k| k.kind == "RATC" || k.name.ends_with(".rat")).find_map(|k| {
let rec = &by[k.offset..(k.offset + k.size).min(by.len())];
let (w, h) = (be32(rec, 0x18), be32(rec, 0x1c));
(w > 0 && h > 0 && w <= 8192 && h <= 8192).then_some((w, h))
});
let found = kids
.iter()
.filter(|k| k.kind == "RATC" || k.name.ends_with(".rat"))
.find_map(|k| {
let rec = &by[k.offset..(k.offset + k.size).min(by.len())];
let (w, h) = (be32(rec, 0x18), be32(rec, 0x1c));
(w > 0 && h > 0 && w <= 8192 && h <= 8192).then_some((w, h))
});
match found {
Some((w, h)) => { r += 1; if (w, h) != (1280, 720) { nonstd += 1;
println!(" {name} : a build states a NON-standard design size {w}x{h}"); } }
Some((w, h)) => {
r += 1;
if (w, h) != (1280, 720) {
nonstd += 1;
println!(" {name} : a build states a NON-standard design size {w}x{h}");
}
}
None => f += 1,
}
}
@@ -55,7 +73,8 @@ fn main() {
println!("{name:30} {r:5} read {f:5} FABRICATED");
std::io::stdout().flush().ok();
}
read += r; fell_back += f;
read += r;
fell_back += f;
}
println!("\n{read} builds state a design size, {fell_back} get the 1280x720 FALLBACK");
println!("{nonstd} builds state something other than 1280x720");

View File

@@ -10,12 +10,16 @@
//! is independently known to place them (162/242/322/401/482, spacing 80).
//!
//! cargo run -p sylpheed-formats --example dialog_button_rows
use sylpheed_formats::{pak::PakArchive, ui_layout};
use std::path::PathBuf;
use sylpheed_formats::{pak::PakArchive, ui_layout};
fn rows(ar: &PakArchive, entry: usize, what: &str) {
let Ok(by) = ar.read(&ar.entries()[entry]) else { return };
let Some(b) = ui_layout::parse_build(&by) else { return };
let Ok(by) = ar.read(&ar.entries()[entry]) else {
return;
};
let Some(b) = ui_layout::parse_build(&by) else {
return;
};
let mut v: Vec<(i32, String)> = b
.elements
.iter()
@@ -23,7 +27,12 @@ fn rows(ar: &PakArchive, entry: usize, what: &str) {
let n = &e.name;
(n.starts_with("pcbtn") || n.starts_with("ptbtn")) && !n.contains('f')
})
.map(|e| (e.rest().map(|k| k.y).unwrap_or(e.pivot_y as i32), e.name.clone()))
.map(|e| {
(
e.rest().map(|k| k.y).unwrap_or(e.pivot_y as i32),
e.name.clone(),
)
})
.collect();
v.sort_by_key(|r| r.0);
println!("\n{what} (entry {entry}):");
@@ -39,7 +48,11 @@ fn rows(ar: &PakArchive, entry: usize, what: &str) {
fn main() {
let root = PathBuf::from(std::env::var("SYLPHEED_DISC").expect("SYLPHEED_DISC"));
let t = PakArchive::open(root.join("dat/GP_TITLE.pak")).expect("GP_TITLE");
rows(&t, 5, "CONTROL: GP_TITLE main menu (must be 162/242/322/401/482)");
rows(
&t,
5,
"CONTROL: GP_TITLE main menu (must be 162/242/322/401/482)",
);
let d = PakArchive::open(root.join("dat/GP_DIALOG.pak")).expect("GP_DIALOG");
rows(&d, 2, "GP_DIALOG candidate for DLG_SELECT_DIFFICULTY");
rows(&d, 3, "GP_DIALOG entry 3 (the pair)");

View File

@@ -11,8 +11,8 @@
//! CONTROL: entries 2/3 (identical element sets) must come out as "buttons match".
//!
//! cargo run -p sylpheed-formats --example dialog_pair_37
use sylpheed_formats::{pak::PakArchive, ui_layout};
use std::path::PathBuf;
use sylpheed_formats::{pak::PakArchive, ui_layout};
fn sig(ar: &PakArchive, i: usize) -> Option<(Vec<(String, i32)>, usize)> {
let by = ar.read(&ar.entries()[i]).ok()?;
@@ -21,7 +21,12 @@ fn sig(ar: &PakArchive, i: usize) -> Option<(Vec<(String, i32)>, usize)> {
.elements
.iter()
.filter(|e| e.name.contains("btn"))
.map(|e| (e.name.clone(), e.rest().map(|k| k.y).unwrap_or(e.pivot_y as i32)))
.map(|e| {
(
e.name.clone(),
e.rest().map(|k| k.y).unwrap_or(e.pivot_y as i32),
)
})
.collect();
v.sort();
let n = v.len();
@@ -35,13 +40,17 @@ fn main() {
let (mut match_btn, mut differ_btn, mut ctrl) = (0, 0, false);
let mut examples = 0;
for k in (0..n).step_by(2) {
let (Some((a, na)), Some((b, nb))) = (sig(&ar, k), sig(&ar, k + 1)) else { continue };
let (Some((a, na)), Some((b, nb))) = (sig(&ar, k), sig(&ar, k + 1)) else {
continue;
};
if na != nb {
continue; // the 26 already settled
}
if a == b {
match_btn += 1;
if k == 2 { ctrl = true }
if k == 2 {
ctrl = true
}
} else {
differ_btn += 1;
if examples < 5 {
@@ -54,5 +63,8 @@ fn main() {
}
println!("\nequal-button-count pairs whose button NAMES+ROWS match : {match_btn}");
println!("equal-button-count pairs whose buttons DIFFER : {differ_btn}");
println!("control (entries 2/3 counted as matching): {}", if ctrl { "PASSED" } else { "FAILED" });
println!(
"control (entries 2/3 counted as matching): {}",
if ctrl { "PASSED" } else { "FAILED" }
);
}

View File

@@ -7,8 +7,8 @@
//! re-derives that with my own reader before I record the refutation.
//!
//! cargo run -p sylpheed-formats --example dialog_pair_button_counts
use sylpheed_formats::{pak::PakArchive, ui_layout};
use std::path::PathBuf;
use sylpheed_formats::{pak::PakArchive, ui_layout};
fn btns(ar: &PakArchive, i: usize) -> Option<usize> {
let by = ar.read(&ar.entries()[i]).ok()?;

View File

@@ -6,9 +6,9 @@
//! language pair should differ in the SAME slots with locale-marked names.
//!
//! cargo run -p sylpheed-formats --example dialog_pair_diffs
use sylpheed_formats::{pak::PakArchive, ui_layout};
use std::collections::BTreeSet;
use std::path::PathBuf;
use sylpheed_formats::{pak::PakArchive, ui_layout};
fn names(ar: &PakArchive, i: usize) -> Option<BTreeSet<String>> {
let by = ar.read(&ar.entries()[i]).ok()?;
@@ -22,7 +22,9 @@ fn main() {
let n = ar.entries().len();
let mut shown = 0;
for k in (0..n).step_by(2) {
let (Some(a), Some(b)) = (names(&ar, k), names(&ar, k + 1)) else { continue };
let (Some(a), Some(b)) = (names(&ar, k), names(&ar, k + 1)) else {
continue;
};
if a == b {
continue;
}
@@ -35,8 +37,14 @@ fn main() {
continue;
}
if shown < 6 {
println!("entries {k:3}/{:<3} shared {} only-in-{k}: {:?} only-in-{}: {:?}",
k + 1, a.intersection(&b).count(), oa, k + 1, ob);
println!(
"entries {k:3}/{:<3} shared {} only-in-{k}: {:?} only-in-{}: {:?}",
k + 1,
a.intersection(&b).count(),
oa,
k + 1,
ob
);
shown += 1;
}
}

View File

@@ -14,8 +14,8 @@
//! separate two unrelated dialogs cannot judge two similar ones.
//!
//! cargo run -p sylpheed-formats --example dialog_pair_identity
use sylpheed_formats::pak::PakArchive;
use std::path::PathBuf;
use sylpheed_formats::pak::PakArchive;
fn cmp(ar: &PakArchive, a: usize, b: usize, what: &str) {
let (Ok(x), Ok(y)) = (ar.read(&ar.entries()[a]), ar.read(&ar.entries()[b])) else {
@@ -27,10 +27,16 @@ fn cmp(ar: &PakArchive, a: usize, b: usize, what: &str) {
let diff = (0..n).filter(|&i| x[i] != y[i]).count();
let first = (0..n).find(|&i| x[i] != y[i]);
println!("{what}");
println!(" sizes {} / {} ({})", x.len(), y.len(),
if same_len { "equal" } else { "DIFFER" });
println!(" differing bytes over the common prefix: {diff} / {n} ({:.2}%)",
100.0 * diff as f64 / n as f64);
println!(
" sizes {} / {} ({})",
x.len(),
y.len(),
if same_len { "equal" } else { "DIFFER" }
);
println!(
" differing bytes over the common prefix: {diff} / {n} ({:.2}%)",
100.0 * diff as f64 / n as f64
);
match first {
None if same_len => println!(" => BYTE-IDENTICAL — a duplicate"),
None => println!(" => one is a prefix of the other"),
@@ -41,7 +47,12 @@ fn cmp(ar: &PakArchive, a: usize, b: usize, what: &str) {
fn main() {
let root = PathBuf::from(std::env::var("SYLPHEED_DISC").expect("SYLPHEED_DISC"));
let ar = PakArchive::open(root.join("dat/GP_DIALOG.pak")).expect("GP_DIALOG");
cmp(&ar, 10, 11, "CONTROL: entries 10/11 — known two different dialogs");
cmp(
&ar,
10,
11,
"CONTROL: entries 10/11 — known two different dialogs",
);
cmp(&ar, 0, 1, "entries 0/1");
cmp(&ar, 2, 3, "entries 2/3 — the DIFFICULTY build");
}

View File

@@ -9,9 +9,9 @@
//! sets are the signature of a language pair.
//!
//! cargo run -p sylpheed-formats --example dialog_pairing
use sylpheed_formats::{pak::PakArchive, ui_layout};
use std::collections::BTreeSet;
use std::path::PathBuf;
use sylpheed_formats::{pak::PakArchive, ui_layout};
fn names(ar: &PakArchive, i: usize) -> Option<BTreeSet<String>> {
let by = ar.read(&ar.entries()[i]).ok()?;
@@ -27,7 +27,12 @@ fn main() {
for k in (0..n).step_by(2) {
match (names(&ar, k), names(&ar, k + 1)) {
(Some(a), Some(b)) => {
if a == b { adj_same += 1; println!(" identical pair: entries {k}/{}", k+1) } else { adj_diff += 1 }
if a == b {
adj_same += 1;
println!(" identical pair: entries {k}/{}", k + 1)
} else {
adj_diff += 1
}
}
_ => skipped += 1,
}
@@ -38,10 +43,16 @@ fn main() {
for k in 0..n / 2 {
match (names(&ar, k), names(&ar, k + n / 2)) {
(Some(a), Some(b)) => {
if a == b { h_same += 1 } else { h_diff += 1 }
if a == b {
h_same += 1
} else {
h_diff += 1
}
}
_ => h_skip += 1,
}
}
println!("HALVES pairing (i, i+70): identical {h_same} differing {h_diff} unreadable {h_skip}");
println!(
"HALVES pairing (i, i+70): identical {h_same} differing {h_diff} unreadable {h_skip}"
);
}

View File

@@ -6,8 +6,8 @@
//! join would be positional — this checks.
//!
//! cargo run -p sylpheed-formats --example dialog_pak_shape
use sylpheed_formats::{pak::PakArchive, ui_layout};
use std::path::PathBuf;
use sylpheed_formats::{pak::PakArchive, ui_layout};
fn main() {
let root = PathBuf::from(std::env::var("SYLPHEED_DISC").expect("SYLPHEED_DISC"));

View File

@@ -10,8 +10,8 @@
//!
//! cargo run -p sylpheed-formats --example element_records -- GP_TITLE ptbtn00
//! cargo run -p sylpheed-formats --example element_records -- --census
use sylpheed_formats::{pak::PakArchive, ui_layout};
use std::path::PathBuf;
use sylpheed_formats::{pak::PakArchive, ui_layout};
fn main() {
let root = PathBuf::from(std::env::var("SYLPHEED_DISC").expect("SYLPHEED_DISC"));
@@ -19,22 +19,35 @@ fn main() {
if argv.iter().any(|a| a == "--census") {
let (mut els, mut linked, mut screens_with) = (0usize, 0usize, 0usize);
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();
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();
for p in &paks {
let Ok(ar) = PakArchive::open(p) else { continue };
let Ok(ar) = PakArchive::open(p) else {
continue;
};
for ent in ar.entries() {
let Ok(by) = ar.read(ent) else { continue };
let Some(b) = ui_layout::parse_build(&by) else { continue };
let Some(b) = ui_layout::parse_build(&by) else {
continue;
};
let n = b.elements.iter().filter(|e| e.focus_link.is_some()).count();
els += b.elements.len(); linked += n;
if n > 0 { screens_with += 1 }
els += b.elements.len();
linked += n;
if n > 0 {
screens_with += 1
}
}
}
println!("elements disc-wide : {els}");
println!("with a focus_link record : {linked} ({:.1}%)", 100.0*linked as f64/els as f64);
println!(
"with a focus_link record : {linked} ({:.1}%)",
100.0 * linked as f64 / els as f64
);
println!("builds containing at least 1: {screens_with}");
println!("\nEach of those carries a SECOND record whose keyframes are invisible");
println!("to anyone who looks up the leaf by name and stops.");
@@ -46,22 +59,49 @@ fn main() {
let ar = PakArchive::open(root.join(format!("dat/{pak}.pak"))).expect("pak");
for (i, ent) in ar.entries().iter().enumerate() {
let Ok(by) = ar.read(ent) else { continue };
let Some(b) = ui_layout::parse_build(&by) else { continue };
let Some(b) = ui_layout::parse_build(&by) else {
continue;
};
for el in &b.elements {
if let Some(w) = &want { if !el.name.starts_with(w.as_str()) { continue } }
if el.focus_link.is_none() && want.is_none() { continue }
if let Some(w) = &want {
if !el.name.starts_with(w.as_str()) {
continue;
}
}
if el.focus_link.is_none() && want.is_none() {
continue;
}
println!("entry {i}: {}", el.name);
let stem = el.name.trim_end_matches(".rat");
for (tag, rec) in [("leaf", format!("{stem}.rat")),
("focus", el.focus_link.clone().unwrap_or_default())] {
if rec.is_empty() { continue }
for (tag, rec) in [
("leaf", format!("{stem}.rat")),
("focus", el.focus_link.clone().unwrap_or_default()),
] {
if rec.is_empty() {
continue;
}
match b.records.get(rec.as_str()) {
Some(&(lo, ls)) => {
let bytes = &by[lo..(lo + ls).min(by.len())];
let loop_u = ui_layout::loop_length_units(bytes);
let kf: Vec<String> = ui_layout::parse_build(bytes).map(|lb| lb.elements.iter()
.map(|e| format!("{} [{} keys, peak a{}]", e.name, e.keyframes.len(),
e.keyframes.iter().map(|k| k.fade >> 24).max().unwrap_or(0))).collect())
let kf: Vec<String> = ui_layout::parse_build(bytes)
.map(|lb| {
lb.elements
.iter()
.map(|e| {
format!(
"{} [{} keys, peak a{}]",
e.name,
e.keyframes.len(),
e.keyframes
.iter()
.map(|k| k.fade >> 24)
.max()
.unwrap_or(0)
)
})
.collect()
})
.unwrap_or_default();
println!(" {tag:<6} {rec:<20} loop {:?} {}", loop_u, kf.join(", "));
}

View File

@@ -11,12 +11,16 @@
//! known screen it cannot be trusted on an unknown one.
//!
//! cargo run -p sylpheed-formats --example extras_button_order
use sylpheed_formats::{pak::PakArchive, ui_layout};
use std::path::PathBuf;
use sylpheed_formats::{pak::PakArchive, ui_layout};
fn report(ar: &PakArchive, entry: usize, what: &str) {
let Ok(by) = ar.read(&ar.entries()[entry]) else { return };
let Some(b) = ui_layout::parse_build(&by) else { return };
let Ok(by) = ar.read(&ar.entries()[entry]) else {
return;
};
let Some(b) = ui_layout::parse_build(&by) else {
return;
};
let mut rows: Vec<(i32, String, u32)> = b
.elements
.iter()

View File

@@ -13,8 +13,8 @@
//! is independently known (entries 6/9, buttons ptbtn11/12/13).
//!
//! cargo run -p sylpheed-formats --example find_difficulty_build
use sylpheed_formats::{pak::PakArchive, ui_layout};
use std::path::PathBuf;
use sylpheed_formats::{pak::PakArchive, ui_layout};
fn main() {
let root = PathBuf::from(std::env::var("SYLPHEED_DISC").expect("SYLPHEED_DISC"));
@@ -28,10 +28,14 @@ fn main() {
let mut found_extras = false;
for p in &paks {
let Ok(ar) = PakArchive::open(p) else { continue };
let Ok(ar) = PakArchive::open(p) else {
continue;
};
for (i, e) in ar.entries().iter().enumerate() {
let Ok(by) = ar.read(e) else { continue };
let Some(b) = ui_layout::parse_build(&by) else { continue };
let Some(b) = ui_layout::parse_build(&by) else {
continue;
};
let btns: Vec<&String> = b
.records
.keys()
@@ -51,8 +55,11 @@ fn main() {
if name != "GP_TITLE.pak" {
let mut names: Vec<String> = btns.iter().map(|s| (*s).clone()).collect();
names.sort();
println!(" {name:28} entry {i:3} {} buttons {:?}", btns.len(),
&names[..names.len().min(8)]);
println!(
" {name:28} entry {i:3} {} buttons {:?}",
btns.len(),
&names[..names.len().min(8)]
);
}
}
}

View File

@@ -14,8 +14,8 @@
//! proves nothing unless it can find something.
//!
//! cargo run -p sylpheed-formats --example find_difficulty_names
use sylpheed_formats::{pak::PakArchive, ui_layout};
use std::path::PathBuf;
use sylpheed_formats::{pak::PakArchive, ui_layout};
const WANTED: &[&str] = &["easy", "normal", "hard", "diff", "level", "rank"];
@@ -31,17 +31,16 @@ fn main() {
let mut control = false;
let mut hits = 0usize;
for p in &paks {
let Ok(ar) = PakArchive::open(p) else { continue };
let Ok(ar) = PakArchive::open(p) else {
continue;
};
let pname = p.file_name().unwrap().to_string_lossy().to_string();
for (i, e) in ar.entries().iter().enumerate() {
let Ok(by) = ar.read(e) else { continue };
let Some(b) = ui_layout::parse_build(&by) else { continue };
let all: Vec<String> = b
.sprites
.keys()
.chain(b.records.keys())
.cloned()
.collect();
let Some(b) = ui_layout::parse_build(&by) else {
continue;
};
let all: Vec<String> = b.sprites.keys().chain(b.records.keys()).cloned().collect();
if pname == "GP_TITLE.pak" && all.iter().any(|n| n.contains("ptbtn11")) {
control = true;
}
@@ -61,6 +60,9 @@ fn main() {
}
}
}
println!("\ncontrol (found ptbtn11 in GP_TITLE): {}", if control { "PASSED" } else { "FAILED" });
println!(
"\ncontrol (found ptbtn11 in GP_TITLE): {}",
if control { "PASSED" } else { "FAILED" }
);
println!("{hits} build(s) carried a difficulty-ish name");
}

View File

@@ -37,7 +37,9 @@ fn all_descriptors(buf: &[u8]) -> Vec<(usize, u32)> {
fn main() {
let mut args = std::env::args().skip(1);
let disc = args.next().expect("usage: find_stream_by_size <disc> <bytes>…");
let disc = args
.next()
.expect("usage: find_stream_by_size <disc> <bytes>…");
let wanted: Vec<usize> = args.filter_map(|a| a.parse().ok()).collect();
assert!(!wanted.is_empty(), "give at least one payload size");
// A `to_xma_riffs` chunk is the payload plus a 60-byte RIFF wrapper.

View File

@@ -12,9 +12,12 @@ use sylpheed_formats::{pak, ratc, ui_layout};
fn main() {
let root = std::env::var("SYLPHEED_DISC").unwrap_or_else(|_| "/disc".into());
let mut paks: Vec<_> = std::fs::read_dir(format!("{root}/dat")).expect("dat/")
.flatten().map(|e| e.path())
.filter(|p| p.extension().and_then(|s| s.to_str()) == Some("pak")).collect();
let mut paks: Vec<_> = std::fs::read_dir(format!("{root}/dat"))
.expect("dat/")
.flatten()
.map(|e| e.path())
.filter(|p| p.extension().and_then(|s| s.to_str()) == Some("pak"))
.collect();
paks.sort();
let (mut n_rec, mut n_elem, mut varying) = (0usize, 0usize, 0usize);
let (mut at_peak, mut mid_ramp) = (0usize, 0usize);
@@ -22,31 +25,55 @@ fn main() {
let mut hits: Vec<String> = Vec::new();
for p in &paks {
let pn = p.file_name().unwrap().to_string_lossy().to_string();
let Ok(ar) = pak::PakArchive::open(p) else { continue };
let Ok(ar) = pak::PakArchive::open(p) else {
continue;
};
for (ei, e) in ar.entries().iter().enumerate() {
let Ok(by) = ar.read(e) else { continue };
if !ratc::is_ratc(&by) { continue }
let Some(b) = ui_layout::parse_build(&by) else { continue };
if !ratc::is_ratc(&by) {
continue;
}
let Some(b) = ui_layout::parse_build(&by) else {
continue;
};
for (rn, &(o, s)) in &b.records {
// A focus record is one whose name is another record's plus `f`.
let Some(stem) = rn.strip_suffix("f.rat") else { continue };
if !b.records.contains_key(&format!("{stem}.rat")) { continue }
if o + s > by.len() { continue }
let Some(lb) = ui_layout::parse_build(&by[o..o + s]) else { continue };
let Some(stem) = rn.strip_suffix("f.rat") else {
continue;
};
if !b.records.contains_key(&format!("{stem}.rat")) {
continue;
}
if o + s > by.len() {
continue;
}
let Some(lb) = ui_layout::parse_build(&by[o..o + s]) else {
continue;
};
n_rec += 1;
for el in &lb.elements {
if el.keyframes.is_empty() { continue }
if el.keyframes.is_empty() {
continue;
}
n_elem += 1;
let a: Vec<u32> = el.keyframes.iter().map(|k| k.fade >> 24).collect();
let (lo, hi) = (*a.iter().min().unwrap(), *a.iter().max().unwrap());
if lo == hi { continue }
if lo == hi {
continue;
}
varying += 1;
let rest = el.rest().map(|k| k.fade >> 24).unwrap_or(0);
if rest == hi { at_peak += 1 } else { mid_ramp += 1 }
if rest == hi {
at_peak += 1
} else {
mid_ramp += 1
}
*by_pak.entry(pn.clone()).or_default() += 1;
hits.push(format!(
"{pn} [{ei}] {rn}::{} alpha {lo}..{hi} rest()={rest}{}",
el.name, if rest == hi { " 🔴 == PEAK" } else { "" }));
el.name,
if rest == hi { " 🔴 == PEAK" } else { "" }
));
}
}
}
@@ -57,9 +84,14 @@ fn main() {
println!(" of which rest() == the PEAK : {at_peak} <- burns bright forever");
println!(" of which rest() is MID-RAMP : {mid_ramp} <- neither extreme; looks plausible");
println!("\nby pak:");
for (k, v) in &by_pak { println!(" {k:<34} {v}") }
for (k, v) in &by_pak {
println!(" {k:<34} {v}")
}
println!("\nevery varying one:");
hits.sort(); hits.dedup();
for h in &hits { println!(" {h}") }
hits.sort();
hits.dedup();
for h in &hits {
println!(" {h}")
}
println!("\n({} distinct)", hits.len());
}

View File

@@ -71,8 +71,13 @@ fn main() {
let without = order_without_rule(&b, &by);
let a = ui_layout::compose_with_order(&b, &by, opts.clone(), None, Some(&with));
let c = ui_layout::compose_with_order(&b, &by, opts, None, Some(&without));
println!("\n== GP_TITLE entry {entry}{label} ({}x{})", a.width, a.height);
println!(" threshold | RGB>t with rule | A>t with rule | RGB>t WITHOUT | A>t WITHOUT");
println!(
"\n== GP_TITLE entry {entry}{label} ({}x{})",
a.width, a.height
);
println!(
" threshold | RGB>t with rule | A>t with rule | RGB>t WITHOUT | A>t WITHOUT"
);
for t in [0u8, 1, 2, 4, 8, 16] {
let (r1, a1) = counts(&a.rgba, t);
let (r0, a0) = counts(&c.rgba, t);

View File

@@ -29,7 +29,9 @@ fn main() {
let (mut read, mut implied, mut none) = (0usize, 0usize, 0usize);
println!("# archive entry element key_source key");
for pak in &paks {
let Ok(ar) = PakArchive::open(pak) else { continue };
let Ok(ar) = PakArchive::open(pak) else {
continue;
};
let name = pak.file_name().unwrap().to_string_lossy().to_string();
for (i, e) in ar.entries().iter().enumerate() {
let Ok(by) = ar.read(e) else { continue };

View File

@@ -55,60 +55,63 @@ fn main() {
}
eprintln!("# scanning {} archive(s)", paks.len());
for path in &paks {
let ar = PakArchive::open(path).expect("pak");
println!("# {}", path.display());
println!("# entry forced decides elements note");
let ar = PakArchive::open(path).expect("pak");
println!("# {}", path.display());
println!("# entry forced decides elements note");
let mut decides = Vec::new();
let mut agrees = Vec::new();
#[allow(unused)]
let _ = (&decides, &agrees);
for (i, e) in ar.entries().iter().enumerate() {
let Ok(by) = ar.read(e) else { continue };
let Some(b) = ui_layout::parse_build(&by) else {
continue;
};
let with = ui_layout::derived_paint_order(&b, &by);
let without = order_without_rule(&b, &by);
let mut decides = Vec::new();
let mut agrees = Vec::new();
#[allow(unused)]
let _ = (&decides, &agrees);
for (i, e) in ar.entries().iter().enumerate() {
let Ok(by) = ar.read(e) else { continue };
let Some(b) = ui_layout::parse_build(&by) else {
continue;
};
let with = ui_layout::derived_paint_order(&b, &by);
let without = order_without_rule(&b, &by);
// Which elements does the rule fire on, and of those, which have no key
// of their own to fall back on?
let mut forced = Vec::new();
let mut keyless = Vec::new();
for el in &b.elements {
if !ui_layout::forced_backdrop(&b, el) {
// Which elements does the rule fire on, and of those, which have no key
// of their own to fall back on?
let mut forced = Vec::new();
let mut keyless = Vec::new();
for el in &b.elements {
if !ui_layout::forced_backdrop(&b, el) {
continue;
}
forced.push(el.name.clone());
let own = ui_layout::sprite_layer_key(&b, &by, el)
.or_else(|| ui_layout::implied_layer_key(&el.name));
if own.is_none() {
keyless.push(el.name.clone());
}
}
if forced.is_empty() {
continue;
}
forced.push(el.name.clone());
let own = ui_layout::sprite_layer_key(&b, &by, el)
.or_else(|| ui_layout::implied_layer_key(&el.name));
if own.is_none() {
keyless.push(el.name.clone());
let moved = with != without;
if moved {
decides.push(i);
} else {
agrees.push(i);
}
println!(
" {i:>5} {:>6} {:>7} {:>8} forced=[{}] keyless=[{}]",
forced.len(),
if moved { "YES" } else { "no" },
b.elements.len(),
forced.join(","),
keyless.join(","),
);
}
if forced.is_empty() {
continue;
}
let moved = with != without;
if moved {
decides.push(i);
} else {
agrees.push(i);
}
println!(
" {i:>5} {:>6} {:>7} {:>8} forced=[{}] keyless=[{}]",
forced.len(),
if moved { "YES" } else { "no" },
b.elements.len(),
forced.join(","),
keyless.join(","),
);
println!("# rule DECIDES the order on entries {decides:?}");
println!("# rule merely AGREES on entries {agrees:?}\n");
total_decides += decides.len();
total_agrees += agrees.len();
}
println!("# rule DECIDES the order on entries {decides:?}");
println!("# rule merely AGREES on entries {agrees:?}\n");
total_decides += decides.len();
total_agrees += agrees.len();
}
println!("# TOTAL over {} archive(s): {total_decides} deciding entries, \
{total_agrees} agreeing", paks.len());
println!(
"# TOTAL over {} archive(s): {total_decides} deciding entries, \
{total_agrees} agreeing",
paks.len()
);
}

View File

@@ -64,7 +64,9 @@ fn main() {
println!("# archive entry element changed_px total_px ink_px(control) pct");
let (mut decided, mut zero_cost, mut blind) = (0usize, 0usize, 0usize);
for pak in &paks {
let Ok(ar) = PakArchive::open(pak) else { continue };
let Ok(ar) = PakArchive::open(pak) else {
continue;
};
let name = pak.file_name().unwrap().to_string_lossy().to_string();
for (i, e) in ar.entries().iter().enumerate() {
let Ok(by) = ar.read(e) else { continue };

View File

@@ -9,8 +9,8 @@
//! cannot find the incumbent proves nothing.
//!
//! cargo run -p sylpheed-formats --example four_button_row_rivals
use sylpheed_formats::{pak::PakArchive, ui_layout};
use std::path::PathBuf;
use sylpheed_formats::{pak::PakArchive, ui_layout};
const WANT: [i32; 4] = [259, 329, 399, 469];
@@ -24,11 +24,15 @@ fn main() {
paks.sort();
let (mut incumbent, mut rivals) = (0, 0);
for p in &paks {
let Ok(ar) = PakArchive::open(p) else { continue };
let Ok(ar) = PakArchive::open(p) else {
continue;
};
let pname = p.file_name().unwrap().to_string_lossy().to_string();
for (i, e) in ar.entries().iter().enumerate() {
let Ok(by) = ar.read(e) else { continue };
let Some(b) = ui_layout::parse_build(&by) else { continue };
let Some(b) = ui_layout::parse_build(&by) else {
continue;
};
let mut ys: Vec<i32> = b
.elements
.iter()
@@ -43,13 +47,21 @@ fn main() {
let close = ys.iter().zip(WANT.iter()).all(|(a, b)| (a - b).abs() <= 6);
if close {
let is_inc = pname == "GP_DIALOG.pak" && (i == 2 || i == 3);
if is_inc { incumbent += 1 } else { rivals += 1 }
println!(" {}{pname:24} entry {i:4} rows {ys:?}",
if is_inc { "INCUMBENT " } else { "RIVAL " });
if is_inc {
incumbent += 1
} else {
rivals += 1
}
println!(
" {}{pname:24} entry {i:4} rows {ys:?}",
if is_inc { "INCUMBENT " } else { "RIVAL " }
);
}
}
}
println!("\ncontrol: found {incumbent} incumbent build(s) (want 2) — {}",
if incumbent == 2 { "PASSED" } else { "FAILED" });
println!(
"\ncontrol: found {incumbent} incumbent build(s) (want 2) — {}",
if incumbent == 2 { "PASSED" } else { "FAILED" }
);
println!("{rivals} rival build(s) elsewhere on the disc");
}

View File

@@ -8,8 +8,8 @@
//! 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;
use sylpheed_formats::{pak::PakArchive, t8ad, ui_layout};
fn census(name: &str, img: &t8ad::T8adImage) {
let n = (img.width * img.height) as usize;
@@ -22,16 +22,28 @@ fn census(name: &str, img: &t8ad::T8adImage) {
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();
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();
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,
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 },
if nonzero > 0 {
100.0 * (nonzero - full) as f64 / nonzero as f64
} else {
0.0
},
top5.join(" ")
);
}
@@ -39,16 +51,23 @@ fn census(name: &str, img: &t8ad::T8adImage) {
fn main() {
let root = PathBuf::from(std::env::var("SYLPHEED_DISC").expect("SYLPHEED_DISC"));
let argv: Vec<String> = std::env::args().skip(1).collect();
let pak = argv.iter().find(|a| a.parse::<usize>().is_err())
.cloned().unwrap_or_else(|| "GP_TITLE".to_string());
let pak = argv
.iter()
.find(|a| a.parse::<usize>().is_err())
.cloned()
.unwrap_or_else(|| "GP_TITLE".to_string());
let ar = PakArchive::open(root.join(format!("dat/{pak}.pak"))).expect("pak");
// Builds default to the two the port ships and can be overridden, so the
// same census serves the title (4) and the `PRESS (A)` plate (2).
let args: Vec<usize> = argv.iter().filter_map(|a| a.parse().ok()).collect();
let builds: Vec<usize> = if args.is_empty() { vec![5, 6] } else { args };
for build in builds {
let Ok(by) = ar.read(&ar.entries()[build]) else { continue };
let Some(b) = ui_layout::parse_build(&by) else { continue };
let Ok(by) = ar.read(&ar.entries()[build]) else {
continue;
};
let Some(b) = ui_layout::parse_build(&by) else {
continue;
};
println!("=== {pak} build {build} ===");
let mut names: Vec<&String> = b.sprites.keys().collect();
names.sort();

View File

@@ -8,14 +8,16 @@
//! 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;
use sylpheed_formats::{pak::PakArchive, ui_layout};
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");
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] {
@@ -23,24 +25,55 @@ fn main() {
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!(
"{:<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());
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),
("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 }
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);
println!(
"{label:<10} frames take {:?} others take {} distinct values; frame-only values: {:?}",
fv,
ov.len(),
only_frames
);
}
}

View File

@@ -8,8 +8,8 @@
//! 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;
use sylpheed_formats::{pak::PakArchive, ui_layout};
fn main() {
let root = PathBuf::from(std::env::var("SYLPHEED_DISC").expect("SYLPHEED_DISC"));
@@ -23,41 +23,74 @@ fn main() {
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 }
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]]))
.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");
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!(
"{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 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 !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):");
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})", 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 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"); }
if !anyb {
println!(" NONE");
}
}

View File

@@ -6,15 +6,17 @@
//! GP_TITLE, then NEW GAME loads nothing external and that reading is wrong.
//!
//! cargo run -p sylpheed-formats --example gp_title_buttons
use sylpheed_formats::{pak::PakArchive, ui_layout};
use std::path::PathBuf;
use sylpheed_formats::{pak::PakArchive, ui_layout};
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.pak");
for (i, e) in ar.entries().iter().enumerate() {
let Ok(by) = ar.read(e) else { continue };
let Some(b) = ui_layout::parse_build(&by) else { continue };
let Some(b) = ui_layout::parse_build(&by) else {
continue;
};
let mut btns: Vec<String> = b
.records
.keys()

View File

@@ -1,14 +1,24 @@
use sylpheed_formats::{pak::PakArchive, ui_layout};
use std::path::PathBuf;
fn main(){
let root=PathBuf::from(std::env::var("SYLPHEED_DISC").unwrap());
let ar=PakArchive::open(root.join("dat/GP_TITLE.pak")).unwrap();
for (i,e) in ar.entries().iter().enumerate(){
let Ok(by)=ar.read(e) else { println!("{i:2} <unreadable>"); continue };
use sylpheed_formats::{pak::PakArchive, ui_layout};
fn main() {
let root = PathBuf::from(std::env::var("SYLPHEED_DISC").unwrap());
let ar = PakArchive::open(root.join("dat/GP_TITLE.pak")).unwrap();
for (i, e) in ar.entries().iter().enumerate() {
let Ok(by) = ar.read(e) else {
println!("{i:2} <unreadable>");
continue;
};
let names: Vec<String> = ui_layout::parse_build(&by)
.map(|b| b.sprites.keys().take(2).cloned().collect()).unwrap_or_default();
.map(|b| b.sprites.keys().take(2).cloned().collect())
.unwrap_or_default();
let rec: Vec<String> = ui_layout::parse_build(&by)
.map(|b| b.records.keys().take(2).cloned().collect()).unwrap_or_default();
println!("{i:2} {} B sprites {:?} records {:?}", by.len(), names, rec);
.map(|b| b.records.keys().take(2).cloned().collect())
.unwrap_or_default();
println!(
"{i:2} {} B sprites {:?} records {:?}",
by.len(),
names,
rec
);
}
}

View File

@@ -10,12 +10,14 @@
//! (the PRESS Ⓐ plate) is byte-identical in size and is the control.
//!
//! cargo run -p sylpheed-formats --example gp_title_pair_check
use sylpheed_formats::{pak::PakArchive, ui_layout};
use std::collections::BTreeSet;
use std::path::PathBuf;
use sylpheed_formats::{pak::PakArchive, ui_layout};
fn sprites(ar: &PakArchive, i: usize) -> BTreeSet<String> {
let Ok(by) = ar.read(&ar.entries()[i]) else { return BTreeSet::new() };
let Ok(by) = ar.read(&ar.entries()[i]) else {
return BTreeSet::new();
};
ui_layout::parse_build(&by)
.map(|b| b.sprites.keys().cloned().collect())
.unwrap_or_default()
@@ -24,19 +26,32 @@ fn sprites(ar: &PakArchive, i: usize) -> BTreeSet<String> {
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.pak");
let pairs = [(0, 1, "loading plain"), (2, 3, "PRESS (A) plate [CONTROL]"),
(4, 7, "title art"), (5, 8, "main menu"), (6, 9, "EXTRAS"),
(10, 13, "publisher splash"), (11, 14, "developer splash"),
(12, 15, "loading dressed")];
let pairs = [
(0, 1, "loading plain"),
(2, 3, "PRESS (A) plate [CONTROL]"),
(4, 7, "title art"),
(5, 8, "main menu"),
(6, 9, "EXTRAS"),
(10, 13, "publisher splash"),
(11, 14, "developer splash"),
(12, 15, "loading dressed"),
];
for (a, b, what) in pairs {
let (sa, sb) = (sprites(&ar, a), sprites(&ar, b));
let only_a: Vec<_> = sa.difference(&sb).cloned().collect();
let only_b: Vec<_> = sb.difference(&sa).cloned().collect();
let shared = sa.intersection(&sb).count();
let verdict = if only_a.is_empty() && only_b.is_empty() { "IDENTICAL SET" }
else { "DIFFERS" };
let verdict = if only_a.is_empty() && only_b.is_empty() {
"IDENTICAL SET"
} else {
"DIFFERS"
};
println!("\n{a:2}/{b:<2} {what:26} {shared:3} shared {verdict}");
if !only_a.is_empty() { println!(" only in {a}: {only_a:?}"); }
if !only_b.is_empty() { println!(" only in {b}: {only_b:?}"); }
if !only_a.is_empty() {
println!(" only in {a}: {only_a:?}");
}
if !only_b.is_empty() {
println!(" only in {b}: {only_b:?}");
}
}
}

View File

@@ -15,45 +15,62 @@
//! later by `if tmax == 0 { return false; }`, so 0 is handled, not assumed.)
//!
//! cargo run -p sylpheed-formats --example inrange_fallback_count
use sylpheed_formats::{pak::PakArchive, ui_layout};
use std::io::Write;
use std::path::PathBuf;
use sylpheed_formats::{pak::PakArchive, ui_layout};
fn main() {
let root = PathBuf::from(std::env::var("SYLPHEED_DISC").expect("SYLPHEED_DISC"));
let mut paks: Vec<PathBuf> = std::fs::read_dir(root.join("dat")).expect("dat/")
let mut paks: Vec<PathBuf> = std::fs::read_dir(root.join("dat"))
.expect("dat/")
.filter_map(|e| e.ok().map(|e| e.path()))
.filter(|p| p.extension().is_some_and(|x| x == "pak")).collect();
.filter(|p| p.extension().is_some_and(|x| x == "pak"))
.collect();
paks.sort();
let (mut kf, mut untimed, mut builds) = (0u64, 0u64, 0u64);
let (mut queries, mut none_at) = (0u64, 0u64);
for pak in &paks {
let Ok(ar) = PakArchive::open(pak) else { continue };
let Ok(ar) = PakArchive::open(pak) else {
continue;
};
for e in ar.entries() {
let Ok(by) = ar.read(e) else { continue };
if !ui_layout::is_build(&by) { continue }
let Some(b) = ui_layout::parse_build(&by) else { continue };
if !ui_layout::is_build(&by) {
continue;
}
let Some(b) = ui_layout::parse_build(&by) else {
continue;
};
builds += 1;
// (a) :1681 -- how many poses carry no time?
for el in &b.elements {
for k in &el.keyframes {
kf += 1;
if k.time.is_none() { untimed += 1 }
if k.time.is_none() {
untimed += 1
}
}
}
// (b) :1010 -- ask every element for a pose at every time that any
// element declares, which is the set the occlusion test draws from.
let mut times: Vec<u32> = b.elements.iter()
.flat_map(|e| e.keyframes.iter().filter_map(|k| k.time)).collect();
times.sort_unstable(); times.dedup();
let mut times: Vec<u32> = b
.elements
.iter()
.flat_map(|e| e.keyframes.iter().filter_map(|k| k.time))
.collect();
times.sort_unstable();
times.dedup();
for el in &b.elements {
for &t in &times {
queries += 1;
if el.pose_at(t).is_none() { none_at += 1 }
if el.pose_at(t).is_none() {
none_at += 1
}
}
}
}
print!("."); std::io::stdout().flush().ok();
print!(".");
std::io::stdout().flush().ok();
}
println!();
println!("{builds} builds, {kf} keyframes");
@@ -67,15 +84,23 @@ fn main() {
let mut out_queries = 0u64;
let mut none_out = 0u64;
for pak in &paks {
let Ok(ar) = PakArchive::open(pak) else { continue };
let Ok(ar) = PakArchive::open(pak) else {
continue;
};
for e in ar.entries() {
let Ok(by) = ar.read(e) else { continue };
if !ui_layout::is_build(&by) { continue }
let Some(b) = ui_layout::parse_build(&by) else { continue };
if !ui_layout::is_build(&by) {
continue;
}
let Some(b) = ui_layout::parse_build(&by) else {
continue;
};
for el in &b.elements {
for &t in &[u32::MAX, 1_000_000u32] {
out_queries += 1;
if el.pose_at(t).is_none() { none_out += 1 }
if el.pose_at(t).is_none() {
none_out += 1
}
}
}
}

View File

@@ -10,14 +10,16 @@
//!
//! 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 sylpheed_formats::{pak, ui_layout};
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")
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();
.filter(|p| p.extension().map(|x| x == "pak").unwrap_or(false))
.collect();
paks.sort();
// field -> (pak, entry, sprite) -> set of values seen
@@ -25,13 +27,20 @@ fn main() {
[BTreeMap::new(), BTreeMap::new(), BTreeMap::new()];
for p in &paks {
let Ok(ar) = pak::PakArchive::open(p) else { continue };
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();
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]) {
@@ -54,15 +63,20 @@ fn main() {
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 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
let mut multi = 0usize; // more than two distinct values
for v in m.values() {
if v.len() > 2 { multi += 1; }
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; }
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}");

View File

@@ -1,19 +1,37 @@
use sylpheed_formats::{pak, ui_layout};
fn main(){
let mut a=std::env::args().skip(1);
let pk=a.next().unwrap(); let bi:usize=a.next().unwrap().parse().unwrap();
let ar=pak::PakArchive::open(pk).unwrap();
let by=ar.read(&ar.entries()[bi]).unwrap();
let b=ui_layout::parse_build(&by).unwrap();
fn main() {
let mut a = std::env::args().skip(1);
let pk = a.next().unwrap();
let bi: usize = a.next().unwrap().parse().unwrap();
let ar = pak::PakArchive::open(pk).unwrap();
let by = ar.read(&ar.entries()[bi]).unwrap();
let b = ui_layout::parse_build(&by).unwrap();
for t in a {
let i:usize=t.parse().unwrap();
let e=&b.elements[i];
println!("[{i}] {} kind=0x{:x} parent={:?} pivot=({},{}) kfs={}",
e.name, e.kind, e.parent, e.pivot_x, e.pivot_y, e.keyframes.len());
let i: usize = t.parse().unwrap();
let e = &b.elements[i];
println!(
"[{i}] {} kind=0x{:x} parent={:?} pivot=({},{}) kfs={}",
e.name,
e.kind,
e.parent,
e.pivot_x,
e.pivot_y,
e.keyframes.len()
);
for k in &e.keyframes {
println!(" t={:<5} a={:<4} xy=({},{}) s={}/{} rot={} u4={} u8={} tint={:08x}",
k.time.map(|v|v as i64).unwrap_or(-1), k.fade>>24, k.x,k.y,
k.scale_x,k.scale_y,k.rotation_deg,k.unknown_4,k.unknown_8,k.tint);
println!(
" t={:<5} a={:<4} xy=({},{}) s={}/{} rot={} u4={} u8={} tint={:08x}",
k.time.map(|v| v as i64).unwrap_or(-1),
k.fade >> 24,
k.x,
k.y,
k.scale_x,
k.scale_y,
k.rotation_deg,
k.unknown_4,
k.unknown_8,
k.tint
);
}
}
}

View File

@@ -5,8 +5,8 @@
//! reading is that the three together are rotations about three axes, "not tied
//! to an observed rotation". This censuses them across every UI pak on the disc
//! so the reading can be argued with rather than assumed.
use sylpheed_formats::{pak, ui_layout};
use std::collections::BTreeMap;
use sylpheed_formats::{pak, ui_layout};
fn main() {
let dir = std::env::args().nth(1).expect("usage: <disc>/dat");
@@ -24,20 +24,28 @@ fn main() {
let (mut kfs, mut builds) = (0usize, 0usize);
for p in &paks {
let Ok(ar) = pak::PakArchive::open(p) else { continue };
let Ok(ar) = pak::PakArchive::open(p) else {
continue;
};
let name = 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 Some(b) = ui_layout::parse_build(&bytes) else {
continue;
};
builds += 1;
// parents and leaves alike
let mut groups: Vec<(String, Vec<ui_layout::Keyframe>)> = b
.elements.iter().map(|el| (el.name.clone(), el.keyframes.clone())).collect();
.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((format!("{}->{}", el.name, le.name), le.keyframes.clone()));
groups
.push((format!("{}->{}", el.name, le.name), le.keyframes.clone()));
}
}
}
@@ -49,8 +57,10 @@ fn main() {
*h8.entry(k.unknown_8).or_default() += 1;
*h12.entry(k.rotation_deg).or_default() += 1;
if k.unknown_4 != 0 || k.unknown_8 != 0 {
both_nz.push(format!("{name} e{i} {nm} +4={} +8={} +12={}",
k.unknown_4, k.unknown_8, k.rotation_deg));
both_nz.push(format!(
"{name} e{i} {nm} +4={} +8={} +12={}",
k.unknown_4, k.unknown_8, k.rotation_deg
));
}
}
}
@@ -59,11 +69,17 @@ fn main() {
println!("{builds} builds, {kfs} keyframes (parents + leaves)\n");
for (nm, h) in [("+4", &h4), ("+8", &h8), ("+12 (rotation)", &h12)] {
let nz: usize = h.iter().filter(|(k, _)| **k != 0).map(|(_, v)| *v).sum();
println!("{nm}: {} distinct values, {} non-zero keyframes ({:.4}%)",
h.len(), nz, 100.0 * nz as f64 / kfs as f64);
println!(
"{nm}: {} distinct values, {} non-zero keyframes ({:.4}%)",
h.len(),
nz,
100.0 * nz as f64 / kfs as f64
);
let mut top: Vec<_> = h.iter().filter(|(k, _)| **k != 0).collect();
top.sort_by_key(|(_, v)| std::cmp::Reverse(**v));
for (k, v) in top.iter().take(6) { println!(" {k:>8} x{v}"); }
for (k, v) in top.iter().take(6) {
println!(" {k:>8} x{v}");
}
}
println!("\nkeyframes with a non-zero +4 or +8: {}", both_nz.len());
// Per-pak, so a reader can ask whether a pak they have a CAPTURE of is
@@ -74,10 +90,14 @@ fn main() {
*per.entry(pak).or_default() += 1;
}
println!(" by pak:");
for (k, v) in &per { println!(" {k:<28} {v}"); }
for (k, v) in &per {
println!(" {k:<28} {v}");
}
println!(" paks with NONE: (any UI pak not listed above)");
if let Ok(f) = std::env::var("KF_SHOW") {
println!("\n all lines for {f}:");
for l in both_nz.iter().filter(|l| l.starts_with(&f)) { println!(" {l}"); }
for l in both_nz.iter().filter(|l| l.starts_with(&f)) {
println!(" {l}");
}
}
}

View File

@@ -1,38 +1,61 @@
//! What do 0x3002/0x3003 elements actually look like disc-wide?
//! The port's menu-item rule keys on this class; this asks whether the class is
//! uniformly menu-row-shaped or whether it contains other things too.
use sylpheed_formats::{pak::PakArchive, ui_layout};
use std::collections::BTreeMap;
use std::path::PathBuf;
use sylpheed_formats::{pak::PakArchive, ui_layout};
fn main() {
let root = PathBuf::from(std::env::var("SYLPHEED_DISC").expect("SYLPHEED_DISC"));
let mut names: BTreeMap<String, usize> = BTreeMap::new();
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();
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();
for p in &paks {
let Ok(ar)=PakArchive::open(p) else { continue };
let Ok(ar) = PakArchive::open(p) else {
continue;
};
for ent in ar.entries() {
let Ok(by)=ar.read(ent) else { continue };
let Some(b)=ui_layout::parse_build(&by) else { continue };
let Ok(by) = ar.read(ent) else { continue };
let Some(b) = ui_layout::parse_build(&by) else {
continue;
};
for el in &b.elements {
if el.kind==0x3002 || el.kind==0x3003 {
if el.kind == 0x3002 || el.kind == 0x3003 {
let stem = el.name.split('.').next().unwrap_or("");
let cls: String = stem.trim_end_matches(|c:char| c.is_ascii_digit()).to_string();
*names.entry(cls).or_default()+=1;
let cls: String = stem
.trim_end_matches(|c: char| c.is_ascii_digit())
.to_string();
*names.entry(cls).or_default() += 1;
}
}
}
}
let total: usize = names.values().sum();
println!("0x3002/0x3003 elements disc-wide: {total}, {} name-stems", names.len());
println!(
"0x3002/0x3003 elements disc-wide: {total}, {} name-stems",
names.len()
);
let mut v: Vec<_> = names.into_iter().collect();
v.sort_by_key(|(_,n)| std::cmp::Reverse(*n));
let btn: usize = v.iter().filter(|(k,_)| k.contains("btn")).map(|(_,n)| n).sum();
println!("stems containing \"btn\": {btn} of {total} ({:.1}%)", 100.0*btn as f64/total as f64);
v.sort_by_key(|(_, n)| std::cmp::Reverse(*n));
let btn: usize = v
.iter()
.filter(|(k, _)| k.contains("btn"))
.map(|(_, n)| n)
.sum();
println!(
"stems containing \"btn\": {btn} of {total} ({:.1}%)",
100.0 * btn as f64 / total as f64
);
println!("\ntop stems:");
for (k,n) in v.iter().take(14) { println!(" {n:5} {k}"); }
for (k, n) in v.iter().take(14) {
println!(" {n:5} {k}");
}
println!("\nNON-btn stems (the ones a menu-item rule would also claim):");
for (k,n) in v.iter().filter(|(k,_)| !k.contains("btn")).take(12) { println!(" {n:5} {k}"); }
for (k, n) in v.iter().filter(|(k, _)| !k.contains("btn")).take(12) {
println!(" {n:5} {k}");
}
}

View File

@@ -6,9 +6,9 @@
//! disc, so it is tested here rather than argued from two screens.
//!
//! cargo run -p sylpheed-formats --example kind_bit0_census
use sylpheed_formats::{pak::PakArchive, ui_layout};
use std::collections::BTreeMap;
use std::path::PathBuf;
use sylpheed_formats::{pak::PakArchive, ui_layout};
fn main() {
let root = PathBuf::from(std::env::var("SYLPHEED_DISC").expect("SYLPHEED_DISC"));
@@ -18,35 +18,54 @@ fn main() {
let mut kind_parent: BTreeMap<(u32, bool), usize> = BTreeMap::new();
let mut examples: Vec<String> = Vec::new();
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();
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();
for p in &paks {
let Ok(ar) = PakArchive::open(p) else { continue };
let Ok(ar) = PakArchive::open(p) else {
continue;
};
for (i, ent) in ar.entries().iter().enumerate() {
let Ok(by) = ar.read(ent) else { continue };
let Some(b) = ui_layout::parse_build(&by) else { continue };
let Some(b) = ui_layout::parse_build(&by) else {
continue;
};
for el in &b.elements {
let has_parent = el.parent.is_some();
let bit0 = el.kind & 1 == 1;
*kinds.entry(el.kind).or_default() += 1;
*kind_parent.entry((el.kind, has_parent)).or_default() += 1;
if bit0 == has_parent { agree += 1 } else {
if bit0 == has_parent {
agree += 1
} else {
disagree += 1;
if examples.len() < 10 {
examples.push(format!("{} entry {i} {} kind={:#x} parent={:?}",
p.file_name().unwrap().to_string_lossy(), el.name, el.kind, el.parent));
examples.push(format!(
"{} entry {i} {} kind={:#x} parent={:?}",
p.file_name().unwrap().to_string_lossy(),
el.name,
el.kind,
el.parent
));
}
}
}
}
}
println!("kind&1 == has_parent : agree {agree} DISAGREE {disagree}");
for e in &examples { println!(" counterexample: {e}"); }
for e in &examples {
println!(" counterexample: {e}");
}
println!("\nkind histogram (count, and how many of each have a parent):");
for (k, n) in &kinds {
let wp = kind_parent.get(&(*k, true)).copied().unwrap_or(0);
println!(" {k:#06x} n={n:6} with parent {wp:6} without {:6}", n - wp);
println!(
" {k:#06x} n={n:6} with parent {wp:6} without {:6}",
n - wp
);
}
}

View File

@@ -18,12 +18,15 @@
//! `GP_TITLE`, so the claim is disc-wide.
//!
//! cargo run -p sylpheed-formats --example kind_census_five_screens
use sylpheed_formats::{pak::PakArchive, ui_layout};
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)" }
match n.rfind('.') {
Some(i) => &n[i..],
None => "(none)",
}
}
fn main() {
@@ -36,11 +39,21 @@ fn main() {
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 };
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;
*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));
}
@@ -55,10 +68,17 @@ fn main() {
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!(
"\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}"); }
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
@@ -71,65 +91,115 @@ fn main() {
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 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;
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" });
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();
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 };
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 }
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]]);
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}"));
violations.push(format!(
"{name} entry {i} elem {e}: kind 0x{kind:X} focus {foc}"
));
}
}
}
if used { paks += 1 }
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!(
"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}"); }
for v in violations.iter().take(20) {
println!(" {v}");
}
}

View File

@@ -28,8 +28,16 @@ fn main() {
if let Some(el) = build.elements.iter().find(|x| x.name == name) {
println!(" PARENT keyframes (t, alpha, scale, rot, x,y):");
for k in &el.keyframes {
println!(" t={:<5} a={:<4} scale=({},{}) rot={:<5} ({},{})",
k.time.map(|t| t as i64).unwrap_or(-1), alpha_of(k), k.scale_x, k.scale_y, k.rotation_deg, k.x, k.y);
println!(
" t={:<5} a={:<4} scale=({},{}) rot={:<5} ({},{})",
k.time.map(|t| t as i64).unwrap_or(-1),
alpha_of(k),
k.scale_x,
k.scale_y,
k.rotation_deg,
k.x,
k.y
);
}
}
match build.records.get(name) {
@@ -40,9 +48,16 @@ fn main() {
for le in &lb.elements {
println!(" LEAF element {:?}:", le.name);
for k in &le.keyframes {
println!(" t={:<5} a={:<4} scale=({},{}) rot={:<5} ({},{})",
k.time.map(|t| t as i64).unwrap_or(-1), alpha_of(k), k.scale_x, k.scale_y,
k.rotation_deg, k.x, k.y);
println!(
" t={:<5} a={:<4} scale=({},{}) rot={:<5} ({},{})",
k.time.map(|t| t as i64).unwrap_or(-1),
alpha_of(k),
k.scale_x,
k.scale_y,
k.rotation_deg,
k.x,
k.y
);
}
}
}

View File

@@ -3,15 +3,31 @@
//! cargo run -p sylpheed-formats --example leaf_dump -- <pak> <entry> <name>
use sylpheed_formats::{pak, ui_layout};
fn a(k: &ui_layout::Keyframe) -> u32 { k.fade >> 24 }
fn t(k: &ui_layout::Keyframe) -> i64 { k.time.map(|v| v as i64).unwrap_or(-1) }
fn a(k: &ui_layout::Keyframe) -> u32 {
k.fade >> 24
}
fn t(k: &ui_layout::Keyframe) -> i64 {
k.time.map(|v| v as i64).unwrap_or(-1)
}
fn show(tag: &str, els: &[ui_layout::Element]) {
for e in els {
println!(" {tag} {:?} pivot=({},{}) kind={:#x}", e.name, e.pivot_x, e.pivot_y, e.kind);
println!(
" {tag} {:?} pivot=({},{}) kind={:#x}",
e.name, e.pivot_x, e.pivot_y, e.kind
);
for k in &e.keyframes {
println!(" t={:<5} a={:<4} scale=({},{}) rot={:<5} pos=({},{}) tint={:#010x}",
t(k), a(k), k.scale_x, k.scale_y, k.rotation_deg, k.x, k.y, k.tint);
println!(
" t={:<5} a={:<4} scale=({},{}) rot={:<5} pos=({},{}) tint={:#010x}",
t(k),
a(k),
k.scale_x,
k.scale_y,
k.rotation_deg,
k.x,
k.y,
k.tint
);
}
}
}
@@ -24,8 +40,18 @@ fn main() {
let ar = pak::PakArchive::open(&path).expect("open");
let bytes = ar.read(&ar.entries()[entry]).expect("read");
let b = ui_layout::parse_build(&bytes).expect("build");
println!("entry {entry}: {} elements, design {}x{}", b.elements.len(), b.design_w, b.design_h);
let els: Vec<_> = b.elements.iter().filter(|e| e.name.contains(&want)).cloned().collect();
println!(
"entry {entry}: {} elements, design {}x{}",
b.elements.len(),
b.design_w,
b.design_h
);
let els: Vec<_> = b
.elements
.iter()
.filter(|e| e.name.contains(&want))
.cloned()
.collect();
show("PARENT", &els);
for e in &els {
match b.records.get(&e.name) {

View File

@@ -1,8 +1,8 @@
//! Dump the keyframes of any nested `.rat` leaf by name.
//!
//! cargo run -p sylpheed-formats --example leaf_keyframes -- GP_TITLE ptbtn00.rat 2 3 4
use sylpheed_formats::{pak::PakArchive, ui_layout};
use std::path::PathBuf;
use sylpheed_formats::{pak::PakArchive, ui_layout};
fn main() {
let root = PathBuf::from(std::env::var("SYLPHEED_DISC").expect("SYLPHEED_DISC"));
@@ -12,19 +12,32 @@ fn main() {
let builds: Vec<usize> = argv[2..].iter().filter_map(|a| a.parse().ok()).collect();
let ar = PakArchive::open(root.join(format!("dat/{pak}.pak"))).expect("pak");
for e in builds {
let Ok(by) = ar.read(&ar.entries()[e]) else { continue };
let Some(b) = ui_layout::parse_build(&by) else { continue };
let Some(&(lo, ls)) = b.records.get(rec.as_str()) else { continue };
let Ok(by) = ar.read(&ar.entries()[e]) else {
continue;
};
let Some(b) = ui_layout::parse_build(&by) else {
continue;
};
let Some(&(lo, ls)) = b.records.get(rec.as_str()) else {
continue;
};
let leaf = &by[lo..(lo + ls).min(by.len())];
println!("=== {pak} entry {e}{rec} (leaf {ls} bytes) ===");
if let Some(u) = ui_layout::loop_length_units(leaf) { println!(" declared loop: {u} units"); }
if let Some(u) = ui_layout::loop_length_units(leaf) {
println!(" declared loop: {u} units");
}
if let Some(lb) = ui_layout::parse_build(leaf) {
for el in &lb.elements {
println!(" {}{} keyframes", el.name, el.keyframes.len());
for (i, k) in el.keyframes.iter().enumerate() {
println!(" kf{i:<2} t={:<5} x={:<6} y={:<6} fade={:08X} (alpha {:3})",
println!(
" kf{i:<2} t={:<5} x={:<6} y={:<6} fade={:08X} (alpha {:3})",
k.time.map(|t| t.to_string()).unwrap_or_else(|| "-".into()),
k.x, k.y, k.fade, k.fade >> 24);
k.x,
k.y,
k.fade,
k.fade >> 24
);
}
}
}

View File

@@ -1,19 +1,32 @@
use sylpheed_formats::{pak, ui_layout};
fn main(){
let ar=pak::PakArchive::open(std::env::args().nth(1).unwrap()).unwrap();
let by=ar.read(&ar.entries()[4]).unwrap();
let b=ui_layout::parse_build(&by).unwrap();
for n in ["ptloop01.rat","ptloop02.rat"] {
let el=b.elements.iter().find(|e| e.name==n).unwrap();
println!("{n}: kind={:#x} animated={} rec={:?}", el.kind, el.animated,
b.records.get(n).map(|&(o,s)|(o,s)));
if let Some(&(o,s))=b.records.get(n) {
match ui_layout::parse_build(&by[o..o+s]) {
Some(lb)=>for le in &lb.elements {
println!(" leaf {:?} sprite={:?} in_sprites={}", le.name, le.sprite,
le.sprite.as_ref().map(|x| b.sprites.contains_key(x)).unwrap_or(false));
},
None=>println!(" leaf parse FAILED"),
fn main() {
let ar = pak::PakArchive::open(std::env::args().nth(1).unwrap()).unwrap();
let by = ar.read(&ar.entries()[4]).unwrap();
let b = ui_layout::parse_build(&by).unwrap();
for n in ["ptloop01.rat", "ptloop02.rat"] {
let el = b.elements.iter().find(|e| e.name == n).unwrap();
println!(
"{n}: kind={:#x} animated={} rec={:?}",
el.kind,
el.animated,
b.records.get(n).map(|&(o, s)| (o, s))
);
if let Some(&(o, s)) = b.records.get(n) {
match ui_layout::parse_build(&by[o..o + s]) {
Some(lb) => {
for le in &lb.elements {
println!(
" leaf {:?} sprite={:?} in_sprites={}",
le.name,
le.sprite,
le.sprite
.as_ref()
.map(|x| b.sprites.contains_key(x))
.unwrap_or(false)
);
}
}
None => println!(" leaf parse FAILED"),
}
}
}

View File

@@ -1,26 +1,36 @@
// Leave-one-out over the elements that touch a band, dumping raw RGBA each time.
use sylpheed_formats::{pak, ui_layout};
fn main(){
let mut a=std::env::args().skip(1);
let pk=a.next().unwrap();
let bi:usize=a.next().unwrap().parse().unwrap();
let dir=a.next().unwrap();
let ar=pak::PakArchive::open(pk).unwrap();
let by=ar.read(&ar.entries()[bi]).unwrap();
let b=ui_layout::parse_build(&by).unwrap();
let o=ui_layout::ComposeOptions{ backdrop:[0,0,0,255], ..Default::default() };
let n=b.elements.len();
let base=ui_layout::compose(&b,&by,o.clone(),None);
std::fs::write(format!("{dir}/base.raw"),&base.rgba).unwrap();
fn main() {
let mut a = std::env::args().skip(1);
let pk = a.next().unwrap();
let bi: usize = a.next().unwrap().parse().unwrap();
let dir = a.next().unwrap();
let ar = pak::PakArchive::open(pk).unwrap();
let by = ar.read(&ar.entries()[bi]).unwrap();
let b = ui_layout::parse_build(&by).unwrap();
let o = ui_layout::ComposeOptions {
backdrop: [0, 0, 0, 255],
..Default::default()
};
let n = b.elements.len();
let base = ui_layout::compose(&b, &by, o.clone(), None);
std::fs::write(format!("{dir}/base.raw"), &base.rgba).unwrap();
println!("canvas {}x{} elements {n}", base.width, base.height);
for i in 0..n {
let e=&b.elements[i];
let Some(kf)=e.rest() else { continue };
let e = &b.elements[i];
let Some(kf) = e.rest() else { continue };
// only bother with elements whose rest pose can touch the band
let mut v=vec![true;n]; v[i]=false;
let c=ui_layout::compose(&b,&by,o.clone(),Some(&v));
std::fs::write(format!("{dir}/wo-{i}.raw"),&c.rgba).unwrap();
println!("{i}\t{}\t{}\t({},{})\ta={}", e.name,
e.sprite.clone().unwrap_or_default(), kf.x, kf.y, kf.fade>>24);
let mut v = vec![true; n];
v[i] = false;
let c = ui_layout::compose(&b, &by, o.clone(), Some(&v));
std::fs::write(format!("{dir}/wo-{i}.raw"), &c.rgba).unwrap();
println!(
"{i}\t{}\t{}\t({},{})\ta={}",
e.name,
e.sprite.clone().unwrap_or_default(),
kf.x,
kf.y,
kf.fade >> 24
);
}
}

View File

@@ -9,8 +9,8 @@
//! This checks that from my own reader before I correct the page.
//!
//! cargo run -p sylpheed-formats --example loop_length_offset_discriminates
use sylpheed_formats::{pak::PakArchive, ui_layout};
use std::path::PathBuf;
use sylpheed_formats::{pak::PakArchive, ui_layout};
fn be32(b: &[u8], o: usize) -> Option<u32> {
(b.len() >= o + 4).then(|| u32::from_be_bytes([b[o], b[o + 1], b[o + 2], b[o + 3]]))
@@ -29,16 +29,22 @@ fn main() {
let mut stat = [(0usize, 0usize, 0usize); 3];
let offsets = [0x04usize, 0x08, 0x0c];
for p in &paks {
let Ok(ar) = PakArchive::open(p) else { continue };
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 };
let Some(b) = ui_layout::parse_build(&by) else {
continue;
};
for (_, (off, size)) in &b.records {
let rec = &by[*off..(*off + *size).min(by.len())];
if rec.len() < 0x10 || &rec[0..4] != b"RATC" {
continue;
}
let Some(leaf) = ui_layout::parse_build(rec) else { continue };
let Some(leaf) = ui_layout::parse_build(rec) else {
continue;
};
let max_t = leaf
.elements
.iter()
@@ -48,21 +54,33 @@ fn main() {
// sylpheed-port's reconciliation: max_t == 0 makes "does the word
// equal the largest keyframe time?" vacuous, and those records were
// silently in my denominator. Filter them and the counts must meet.
if std::env::var("MEANINGFUL_ONLY").is_ok() && max_t == 0 { continue }
if std::env::var("MEANINGFUL_ONLY").is_ok() && max_t == 0 {
continue;
}
for (i, o) in offsets.iter().enumerate() {
if let Some(w) = be32(rec, *o) {
stat[i].0 += 1;
if w < max_t { stat[i].1 += 1 }
if w == max_t { stat[i].2 += 1 }
if w < max_t {
stat[i].1 += 1
}
if w == max_t {
stat[i].2 += 1
}
}
}
}
}
}
println!("{:>8} {:>9} {:>12} {:>14}", "offset", "records", "violations", "exact == max_t");
println!(
"{:>8} {:>9} {:>12} {:>14}",
"offset", "records", "violations", "exact == max_t"
);
for (i, o) in offsets.iter().enumerate() {
let (n, v, x) = stat[i];
println!(" +0x{o:02X} {n:>9} {v:>12} ({:>5.1}%) {x:>8} ({:>5.1}%)",
100.0 * v as f64 / n.max(1) as f64, 100.0 * x as f64 / n.max(1) as f64);
println!(
" +0x{o:02X} {n:>9} {v:>12} ({:>5.1}%) {x:>8} ({:>5.1}%)",
100.0 * v as f64 / n.max(1) as f64,
100.0 * x as f64 / n.max(1) as f64
);
}
}

View File

@@ -9,22 +9,29 @@
//! readings are printed so the mapping is not assumed.
//!
//! cargo run -p sylpheed-formats --example main_menu_element_extents
use sylpheed_formats::{pak::PakArchive, ui_layout};
use std::path::PathBuf;
use sylpheed_formats::{pak::PakArchive, ui_layout};
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");
println!("{:<26} {:>6} {:>6} {:>7} {:>7} {}", "element", "x", "y", "pivot_x", "pivot_y", "sprite");
println!(
"{:<26} {:>6} {:>6} {:>7} {:>7} {}",
"element", "x", "y", "pivot_x", "pivot_y", "sprite"
);
let mut rows: Vec<(i32, i32, String, String)> = b
.elements
.iter()
.map(|e| {
let k = e.rest();
(k.map(|k| k.x).unwrap_or(0), k.map(|k| k.y).unwrap_or(0),
e.name.clone(), e.sprite.clone().unwrap_or_default())
(
k.map(|k| k.x).unwrap_or(0),
k.map(|k| k.y).unwrap_or(0),
e.name.clone(),
e.sprite.clone().unwrap_or_default(),
)
})
.collect();
rows.sort_by_key(|r| (r.1, r.0));
@@ -39,8 +46,18 @@ fn main() {
(false, true) => " <- hot in CAPTURE space",
_ => "",
};
println!("{n:<26} {x:>6} {y:>6} {:>7} {:>7} {s}{mark}",
b.elements.iter().find(|e| &e.name == n).map(|e| e.pivot_x).unwrap_or(0),
b.elements.iter().find(|e| &e.name == n).map(|e| e.pivot_y).unwrap_or(0));
println!(
"{n:<26} {x:>6} {y:>6} {:>7} {:>7} {s}{mark}",
b.elements
.iter()
.find(|e| &e.name == n)
.map(|e| e.pivot_x)
.unwrap_or(0),
b.elements
.iter()
.find(|e| &e.name == n)
.map(|e| e.pivot_y)
.unwrap_or(0)
);
}
}

View File

@@ -14,7 +14,9 @@ fn main() {
.collect();
assert!(!wanted.is_empty(), "give hashes in hex");
let langs = ["eng", "jpn", ""];
let dirs = ["", "Movie", "etc", "Voice", "Sound", "BGM", "bgm", "se", "SE"];
let dirs = [
"", "Movie", "etc", "Voice", "Sound", "BGM", "bgm", "se", "SE",
];
let mut tried = 0usize;
let mut check = |name: String, tried: &mut usize| {
*tried += 1;

View File

@@ -11,9 +11,9 @@
//! are not necessarily the same object.
//!
//! cargo run -p sylpheed-formats --example ordinal_entry_map
use sylpheed_formats::{pak::PakArchive, ui_layout};
use std::io::Write;
use std::path::PathBuf;
use sylpheed_formats::{pak::PakArchive, ui_layout};
/// One decompression pass per entry, both predicates applied to it: reading the
/// archive twice doubled the cost on `GP_READY_ROOM` (902 entries) for nothing.
@@ -21,37 +21,66 @@ fn maps(ar: &PakArchive) -> (Vec<usize>, Vec<usize>) {
let (mut d, mut a) = (Vec::new(), Vec::new());
for (i, e) in ar.entries().iter().enumerate() {
let Ok(by) = ar.read(e) else { continue };
if ui_layout::is_build(&by) { d.push(i) }
if ui_layout::is_composable(&by) { a.push(i) }
if ui_layout::is_build(&by) {
d.push(i)
}
if ui_layout::is_composable(&by) {
a.push(i)
}
}
(d, a)
}
fn main() {
let root = PathBuf::from(std::env::var("SYLPHEED_DISC").expect("SYLPHEED_DISC"));
let mut paks: Vec<PathBuf> = std::fs::read_dir(root.join("dat")).expect("dat/")
let mut paks: Vec<PathBuf> = std::fs::read_dir(root.join("dat"))
.expect("dat/")
.filter_map(|e| e.ok().map(|e| e.path()))
.filter(|p| p.extension().is_some_and(|x| x == "pak")).collect();
.filter(|p| p.extension().is_some_and(|x| x == "pak"))
.collect();
paks.sort();
let (mut clean, mut div, mut allshift) = (0usize, 0usize, 0usize);
for pak in &paks {
let Ok(ar) = PakArchive::open(pak) else { continue };
let Ok(ar) = PakArchive::open(pak) else {
continue;
};
let name = pak.file_name().unwrap().to_string_lossy().to_string();
let (d, a) = maps(&ar);
if d.is_empty() && a.is_empty() { continue }
if d.is_empty() && a.is_empty() {
continue;
}
let bad = d.iter().enumerate().find(|(o, &e)| *o != e).map(|(o, _)| o);
// does `--all` renumber? compare the entry each ordinal resolves to
let shift = (0..d.len().min(a.len())).find(|&o| d[o] != a[o]);
let tag = match bad {
None => { clean += 1; format!("{:4} builds ordinal == entry throughout", d.len()) }
Some(o) => { div += 1;
let t: Vec<String> = d.iter().enumerate().skip(o).take(5)
.map(|(x, &y)| format!("[{x}]->{y}")).collect();
format!("{:4} builds 🔴 diverges at ordinal {o}: {}", d.len(), t.join(" ")) }
None => {
clean += 1;
format!("{:4} builds ordinal == entry throughout", d.len())
}
Some(o) => {
div += 1;
let t: Vec<String> = d
.iter()
.enumerate()
.skip(o)
.take(5)
.map(|(x, &y)| format!("[{x}]->{y}"))
.collect();
format!(
"{:4} builds 🔴 diverges at ordinal {o}: {}",
d.len(),
t.join(" ")
)
}
};
let s = match shift {
Some(o) => { allshift += 1;
format!(" ⚠️ --all renumbers from [{o}]: entry {} -> {}", d[o], a[o]) }
Some(o) => {
allshift += 1;
format!(
" ⚠️ --all renumbers from [{o}]: entry {} -> {}",
d[o], a[o]
)
}
None if a.len() != d.len() => format!(" (--all appends {} more)", a.len() - d.len()),
None => String::new(),
};

View File

@@ -6,35 +6,60 @@
//! element genuinely HOLDS, and `rest_plateau()` returning it is correct. Only the
//! fallback is the unsound path.
//! cargo run -p sylpheed-formats --example palogo_eff_check
use sylpheed_formats::{pak::PakArchive, ui_layout};
use std::path::PathBuf;
use sylpheed_formats::{pak::PakArchive, ui_layout};
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 (i, e) in ar.entries().iter().enumerate() {
let Ok(by) = ar.read(e) else { continue };
let Some(b) = ui_layout::parse_build(&by) else { continue };
let Some(b) = ui_layout::parse_build(&by) else {
continue;
};
for el in &b.elements {
if !el.name.starts_with("palogo") || !el.name.contains("eff") { continue }
if !el.name.starts_with("palogo") || !el.name.contains("eff") {
continue;
}
// A single-keyframe element has no gap to maximise, so neither path
// applies and `rest()` trivially returns the only pose. Excluding it
// here matches the census, which filters `len < 2`.
if el.keyframes.len() < 2 { continue }
if el.keyframes.len() < 2 {
continue;
}
let plateau = el.keyframes.windows(2).position(|w| {
w[0].x == w[1].x && w[0].y == w[1].y && w[0].scale_x == w[1].scale_x
&& w[0].scale_y == w[1].scale_y && w[0].fade == w[1].fade
w[0].x == w[1].x
&& w[0].y == w[1].y
&& w[0].scale_x == w[1].scale_x
&& w[0].scale_y == w[1].scale_y
&& w[0].fade == w[1].fade
});
let ks: Vec<String> = el.keyframes.iter().map(|k| format!(
"{}:a{} {},{} {}%",
k.time.map(|v| v.to_string()).unwrap_or("-".into()),
(k.fade >> 24) & 0xff, k.x, k.y, k.scale_x)).collect();
let ks: Vec<String> = el
.keyframes
.iter()
.map(|k| {
format!(
"{}:a{} {},{} {}%",
k.time.map(|v| v.to_string()).unwrap_or("-".into()),
(k.fade >> 24) & 0xff,
k.x,
k.y,
k.scale_x
)
})
.collect();
let r = el.rest();
println!("e{i:<3} {:24} kf=[{}]", el.name, ks.join(" "));
println!(" plateau at pair {:?} -> path: {} rest a={} t={:?}",
plateau,
if plateau.is_some() { "PLATEAU (sound: the pose is held)" } else { "DWELL FALLBACK (unsound)" },
r.map(|k| (k.fade >> 24) & 0xff).unwrap_or(0),
r.and_then(|k| k.time));
println!(
" plateau at pair {:?} -> path: {} rest a={} t={:?}",
plateau,
if plateau.is_some() {
"PLATEAU (sound: the pose is held)"
} else {
"DWELL FALLBACK (unsound)"
},
r.map(|k| (k.fade >> 24) & 0xff).unwrap_or(0),
r.and_then(|k| k.time)
);
}
}
}

View File

@@ -11,80 +11,123 @@
//! explanation and something else is wrong.
//!
//! cargo run -p sylpheed-formats --example plateau_choice
use sylpheed_formats::{pak::PakArchive, ui_layout};
use std::path::PathBuf;
use sylpheed_formats::{pak::PakArchive, ui_layout};
fn main() {
let root = PathBuf::from(std::env::var("SYLPHEED_DISC").expect("SYLPHEED_DISC"));
let mut paks: Vec<PathBuf> = std::fs::read_dir(root.join("dat")).expect("dat/")
let mut paks: Vec<PathBuf> = std::fs::read_dir(root.join("dat"))
.expect("dat/")
.filter_map(|e| e.ok().map(|e| e.path()))
.filter(|p| p.extension().is_some_and(|x| x == "pak")).collect();
.filter(|p| p.extension().is_some_and(|x| x == "pak"))
.collect();
paks.sort();
let (mut one_cov, mut one_agree) = (0usize, 0usize); // control
let (mut one_cov, mut one_agree) = (0usize, 0usize); // control
let (mut multi_cov, mut multi_agree, mut multi_wrongrun) = (0usize, 0usize, 0usize);
for pak in &paks {
let Ok(ar) = PakArchive::open(pak) else { continue };
let Ok(ar) = PakArchive::open(pak) 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 };
let Some((lo, hi)) = b.settle_window() else { continue };
if hi - lo < 10 { continue }
let Some(b) = ui_layout::parse_build(&by) else {
continue;
};
let Some((lo, hi)) = b.settle_window() else {
continue;
};
if hi - lo < 10 {
continue;
}
let st = lo + (hi - lo) / 2;
for el in &b.elements {
let k = &el.keyframes;
if k.len() < 2 { continue }
if k.len() < 2 {
continue;
}
let same = |a: &ui_layout::Keyframe, c: &ui_layout::Keyframe| {
a.fade == c.fade && a.scale_x == c.scale_x && a.scale_y == c.scale_y
&& a.tint == c.tint && a.x == c.x && a.y == c.y
a.fade == c.fade
&& a.scale_x == c.scale_x
&& a.scale_y == c.scale_y
&& a.tint == c.tint
&& a.x == c.x
&& a.y == c.y
};
// enumerate maximal runs of length >= 2, with their time spans
let mut runs: Vec<(usize, usize)> = Vec::new();
let mut i = 0usize;
while i < k.len() {
let mut j = i;
while j + 1 < k.len() && same(&k[j], &k[j + 1]) { j += 1 }
if j - i + 1 >= 2 { runs.push((i, j)) }
while j + 1 < k.len() && same(&k[j], &k[j + 1]) {
j += 1
}
if j - i + 1 >= 2 {
runs.push((i, j))
}
i = j + 1;
}
if runs.is_empty() { continue }
if runs.is_empty() {
continue;
}
let covers = |&(a, c): &(usize, usize)| match (k[a].time, k[c].time) {
(Some(t0), Some(t1)) => t0 <= st && st <= t1,
_ => false,
};
let covering: Vec<_> = runs.iter().filter(|r| covers(r)).collect();
if covering.is_empty() { continue }
let (Some(r), Some(s)) = (el.rest(), el.pose_at(st)) else { continue };
let agree = r.fade == s.fade && r.x == s.x && r.y == s.y
&& r.scale_x == s.scale_x && r.scale_y == s.scale_y;
if covering.is_empty() {
continue;
}
let (Some(r), Some(s)) = (el.rest(), el.pose_at(st)) else {
continue;
};
let agree = r.fade == s.fade
&& r.x == s.x
&& r.y == s.y
&& r.scale_x == s.scale_x
&& r.scale_y == s.scale_y;
if runs.len() == 1 {
one_cov += 1;
if agree { one_agree += 1 }
if agree {
one_agree += 1
}
} else {
multi_cov += 1;
if agree { multi_agree += 1 }
else {
if agree {
multi_agree += 1
} else {
// did rest() land on a run that does NOT cover settle?
let on_covering = covering.iter().any(|&&(a, c)| {
(a..=c).any(|idx| {
let kk = &k[idx];
kk.fade == r.fade && kk.x == r.x && kk.y == r.y
&& kk.scale_x == r.scale_x && kk.scale_y == r.scale_y
kk.fade == r.fade
&& kk.x == r.x
&& kk.y == r.y
&& kk.scale_x == r.scale_x
&& kk.scale_y == r.scale_y
})
});
if !on_covering { multi_wrongrun += 1 }
if !on_covering {
multi_wrongrun += 1
}
}
}
}
}
}
println!("CONTROL — exactly ONE plateau, and it covers the settle instant:");
println!(" {one_cov} elements, rest() and pose_at(settle) agree on {one_agree} ({:.1} %)",
100.0 * one_agree as f64 / one_cov.max(1) as f64);
println!(
" {one_cov} elements, rest() and pose_at(settle) agree on {one_agree} ({:.1} %)",
100.0 * one_agree as f64 / one_cov.max(1) as f64
);
println!("\nTEST — MORE THAN ONE plateau, at least one covering the settle instant:");
println!(" {multi_cov} elements, agree on {multi_agree} ({:.1} %)",
100.0 * multi_agree as f64 / multi_cov.max(1) as f64);
println!(" of the {} disagreements, rest() landed on a run that does NOT cover",
multi_cov - multi_agree);
println!(
" {multi_cov} elements, agree on {multi_agree} ({:.1} %)",
100.0 * multi_agree as f64 / multi_cov.max(1) as f64
);
println!(
" of the {} disagreements, rest() landed on a run that does NOT cover",
multi_cov - multi_agree
);
println!(" the settle instant: {multi_wrongrun}");
println!("\n--- END (if this line is missing, the run did not finish) ---");
}

View File

@@ -11,27 +11,40 @@
//! ⚠️ The rule was invented AFTER seeing three of those answers, so it is fitted
//! on them and only `pfbase.tbm` is out of sample. This prints all four plus a
//! disc-wide census, so the fit and its reach are visible together.
use sylpheed_formats::{pak, ratc, ui_layout};
use std::collections::BTreeMap;
use sylpheed_formats::{pak, ratc, ui_layout};
fn main() {
let root = std::env::var("SYLPHEED_DISC").unwrap_or_else(|_| "/disc".into());
let mut paks: Vec<_> = std::fs::read_dir(format!("{root}/dat")).expect("dat/")
.flatten().map(|e| e.path())
.filter(|p| p.extension().and_then(|s| s.to_str()) == Some("pak")).collect();
let mut paks: Vec<_> = std::fs::read_dir(format!("{root}/dat"))
.expect("dat/")
.flatten()
.map(|e| e.path())
.filter(|p| p.extension().and_then(|s| s.to_str()) == Some("pak"))
.collect();
paks.sort();
// name -> (count, set of t=0 alphas, set of "starts at max" flags)
let mut byname: BTreeMap<String, (usize, BTreeMap<u32, usize>)> = BTreeMap::new();
for p in &paks {
let Ok(ar) = pak::PakArchive::open(p) else { continue };
let Ok(ar) = pak::PakArchive::open(p) else {
continue;
};
for e in ar.entries() {
let Ok(by) = ar.read(e) else { continue };
if !ratc::is_ratc(&by) { continue }
let Some(b) = ui_layout::parse_build(&by) else { continue };
if !ratc::is_ratc(&by) {
continue;
}
let Some(b) = ui_layout::parse_build(&by) else {
continue;
};
for el in &b.elements {
// primitives and the keyless: anything with no layer key
if ui_layout::sprite_layer_key(&b, &by, el).is_some() { continue }
let Some(k0) = el.keyframes.first() else { continue };
if ui_layout::sprite_layer_key(&b, &by, el).is_some() {
continue;
}
let Some(k0) = el.keyframes.first() else {
continue;
};
let a0 = k0.fade >> 24;
let ent = byname.entry(el.name.clone()).or_default();
ent.0 += 1;
@@ -39,11 +52,25 @@ fn main() {
}
}
}
println!("{:>28} {:>7} alpha at t=0 (count)", "keyless element", "n");
let known = ["palogo_eff0.prm", "pfbase.tbm", "pteff02.prm", "pteff00.prm"];
println!(
"{:>28} {:>7} alpha at t=0 (count)",
"keyless element", "n"
);
let known = [
"palogo_eff0.prm",
"pfbase.tbm",
"pteff02.prm",
"pteff00.prm",
];
for (n, (c, a)) in &byname {
let tag = if known.contains(&n.as_str()) { " <- IN THE MEASURED TABLE" } else { "" };
if *c < 4 && tag.is_empty() { continue }
let tag = if known.contains(&n.as_str()) {
" <- IN THE MEASURED TABLE"
} else {
""
};
if *c < 4 && tag.is_empty() {
continue;
}
let al: Vec<String> = a.iter().map(|(k, v)| format!("{k}x{v}")).collect();
println!(" {n:>26} {c:>7} {}{tag}", al.join(" "));
}

View File

@@ -3,35 +3,50 @@
//! `ui-prm-primitives.md` leaves blend mode open, and `forced_backdrop` assumes
//! straight alpha-over. A quad whose ARGB would tint the whole screen a colour no
//! screen shows is evidence against alpha-over for that quad.
use sylpheed_formats::{pak, ratc, ui_layout};
use std::collections::BTreeMap;
fn main(){
let root=std::env::var("SYLPHEED_DISC").unwrap_or_else(|_|"/disc".into());
let mut paks:Vec<_>=std::fs::read_dir(format!("{root}/dat")).expect("dat/")
.flatten().map(|e|e.path())
.filter(|p|p.extension().and_then(|s|s.to_str())==Some("pak")).collect();
use sylpheed_formats::{pak, ratc, ui_layout};
fn main() {
let root = std::env::var("SYLPHEED_DISC").unwrap_or_else(|_| "/disc".into());
let mut paks: Vec<_> = std::fs::read_dir(format!("{root}/dat"))
.expect("dat/")
.flatten()
.map(|e| e.path())
.filter(|p| p.extension().and_then(|s| s.to_str()) == Some("pak"))
.collect();
paks.sort();
let mut m:BTreeMap<String,BTreeMap<String,usize>>=BTreeMap::new();
let mut m: BTreeMap<String, BTreeMap<String, usize>> = BTreeMap::new();
for p in &paks {
let Ok(ar)=pak::PakArchive::open(p) else { continue };
let Ok(ar) = pak::PakArchive::open(p) else {
continue;
};
for e in ar.entries() {
let Ok(by)=ar.read(e) else { continue };
if !ratc::is_ratc(&by) { continue }
let Some(b)=ui_layout::parse_build(&by) else { continue };
let Ok(by) = ar.read(e) else { continue };
if !ratc::is_ratc(&by) {
continue;
}
let Some(b) = ui_layout::parse_build(&by) else {
continue;
};
for el in &b.elements {
if el.sprite.is_some() { continue }
if el.sprite.is_some() {
continue;
}
for k in &el.keyframes {
*m.entry(el.name.clone()).or_default()
.entry(format!("{:08x}", k.fade)).or_default()+=1;
*m.entry(el.name.clone())
.or_default()
.entry(format!("{:08x}", k.fade))
.or_default() += 1;
}
}
}
}
println!("{:>26} fade ARGB values (count)", "keyless element");
for (n,v) in &m {
let tot:usize=v.values().sum();
if tot<4 { continue }
let s:Vec<String>=v.iter().map(|(k,c)|format!("{k}x{c}")).collect();
for (n, v) in &m {
let tot: usize = v.values().sum();
if tot < 4 {
continue;
}
let s: Vec<String> = v.iter().map(|(k, c)| format!("{k}x{c}")).collect();
println!(" {n:>24} {}", s.join(" "));
}
}

View File

@@ -15,37 +15,72 @@ use sylpheed_formats::{pak, ratc, ui_layout};
fn main() {
let root = std::env::var("SYLPHEED_DISC").unwrap_or_else(|_| "/disc".into());
let mut paks: Vec<_> = std::fs::read_dir(format!("{root}/dat")).expect("dat/")
.flatten().map(|e| e.path())
.filter(|p| p.extension().and_then(|s| s.to_str()) == Some("pak")).collect();
let mut paks: Vec<_> = std::fs::read_dir(format!("{root}/dat"))
.expect("dat/")
.flatten()
.map(|e| e.path())
.filter(|p| p.extension().and_then(|s| s.to_str()) == Some("pak"))
.collect();
paks.sort();
let (mut forced, mut partial, mut free) = (0usize, 0usize, 0usize);
let mut names: std::collections::BTreeMap<String, (usize, usize)> = Default::default();
for p in &paks {
let Ok(ar) = pak::PakArchive::open(p) else { continue };
let Ok(ar) = pak::PakArchive::open(p) else {
continue;
};
for e in ar.entries() {
let Ok(by) = ar.read(e) else { continue };
if !ratc::is_ratc(&by) { continue }
let Some(b) = ui_layout::parse_build(&by) else { continue };
let tmax = b.elements.iter().flat_map(|el| el.keyframes.iter().filter_map(|k| k.time))
.max().unwrap_or(0);
if tmax == 0 { continue }
if !ratc::is_ratc(&by) {
continue;
}
let Some(b) = ui_layout::parse_build(&by) else {
continue;
};
let tmax = b
.elements
.iter()
.flat_map(|el| el.keyframes.iter().filter_map(|k| k.time))
.max()
.unwrap_or(0);
if tmax == 0 {
continue;
}
for el in &b.elements {
if ui_layout::sprite_layer_key(&b, &by, el).is_some() { continue }
if ui_layout::sprite_layer_key(&b, &by, el).is_some() {
continue;
}
// full-screen only: a quad that does not cover cannot occlude
if el.pivot_x * 2 < 1280 || el.pivot_y * 2 < 720 { continue }
if el.pivot_x * 2 < 1280 || el.pivot_y * 2 < 720 {
continue;
}
let op: Vec<u32> = (0..=tmax)
.filter(|&t| el.pose_at(t).map(|k| k.fade >> 24) == Some(255)).collect();
if op.is_empty() { continue }
.filter(|&t| el.pose_at(t).map(|k| k.fade >> 24) == Some(255))
.collect();
if op.is_empty() {
continue;
}
let others: Vec<&ui_layout::Element> =
b.elements.iter().filter(|o| o.index != el.index).collect();
if others.is_empty() { continue }
let below = others.iter().filter(|o|
op.iter().any(|&t| o.pose_at(t).map(|k| k.fade >> 24).unwrap_or(0) > 0)).count();
if others.is_empty() {
continue;
}
let below = others
.iter()
.filter(|o| {
op.iter()
.any(|&t| o.pose_at(t).map(|k| k.fade >> 24).unwrap_or(0) > 0)
})
.count();
let ent = names.entry(el.name.clone()).or_default();
ent.1 += 1;
if below == others.len() { forced += 1; ent.0 += 1 }
else if below > 0 { partial += 1 } else { free += 1 }
if below == others.len() {
forced += 1;
ent.0 += 1
} else if below > 0 {
partial += 1
} else {
free += 1
}
}
}
}
@@ -54,5 +89,7 @@ fn main() {
println!(" forced below SOME but not all : {partial}");
println!(" occludes nothing : {free}");
println!("\nby name — instances forced first / total:");
for (n, (f, t)) in &names { println!(" {n:>24} {f:>4} / {t}"); }
for (n, (f, t)) in &names {
println!(" {n:>24} {f:>4} / {t}");
}
}

View File

@@ -18,55 +18,120 @@ fn opaque_span(el: &ui_layout::Element, thr: u32, tmax: u32) -> Vec<(f64, f64)>
while t <= tmax as f64 {
let a = el.pose_at(t as u32).map(|k| k.fade >> 24).unwrap_or(0);
if a >= thr {
if cur.is_none() { cur = Some(t) }
if cur.is_none() {
cur = Some(t)
}
} else if let Some(s) = cur.take() {
out.push((s, t));
}
t += 0.5;
}
if let Some(s) = cur { out.push((s, tmax as f64)) }
if let Some(s) = cur {
out.push((s, tmax as f64))
}
out
}
fn main() {
let root = std::env::var("SYLPHEED_DISC").unwrap_or_else(|_| "/disc".into());
let mut paks: Vec<_> = std::fs::read_dir(format!("{root}/dat")).expect("dat/")
.flatten().map(|e| e.path())
.filter(|p| p.extension().and_then(|s| s.to_str()) == Some("pak")).collect();
let mut paks: Vec<_> = std::fs::read_dir(format!("{root}/dat"))
.expect("dat/")
.flatten()
.map(|e| e.path())
.filter(|p| p.extension().and_then(|s| s.to_str()) == Some("pak"))
.collect();
paks.sort();
let want = ["pteff00.prm", "pgloading_eff00.prm", "palogo_eff0.prm",
"pfbase.tbm", "pteff02.prm", "pzeff00.prm", "pceff00.prm", "pdeff00.prm"];
println!("{:>22} {:>5} {:>16} {:>18} overlap", "primitive", "entry", "opaque while", "content visible");
let want = [
"pteff00.prm",
"pgloading_eff00.prm",
"palogo_eff0.prm",
"pfbase.tbm",
"pteff02.prm",
"pzeff00.prm",
"pceff00.prm",
"pdeff00.prm",
];
println!(
"{:>22} {:>5} {:>16} {:>18} overlap",
"primitive", "entry", "opaque while", "content visible"
);
for p in &paks {
let Ok(ar) = pak::PakArchive::open(p) else { continue };
let Ok(ar) = pak::PakArchive::open(p) else {
continue;
};
for (ei, e) in ar.entries().iter().enumerate() {
let Ok(by) = ar.read(e) else { continue };
if !ratc::is_ratc(&by) { continue }
let Some(b) = ui_layout::parse_build(&by) else { continue };
let tmax = b.elements.iter().flat_map(|el| el.keyframes.iter().filter_map(|k| k.time))
.max().unwrap_or(0);
if tmax == 0 { continue }
if !ratc::is_ratc(&by) {
continue;
}
let Some(b) = ui_layout::parse_build(&by) else {
continue;
};
let tmax = b
.elements
.iter()
.flat_map(|el| el.keyframes.iter().filter_map(|k| k.time))
.max()
.unwrap_or(0);
if tmax == 0 {
continue;
}
for el in &b.elements {
if !want.contains(&el.name.as_str()) { continue }
if ui_layout::sprite_layer_key(&b, &by, el).is_some() { continue }
let op = opaque_span(el, 255, tmax);
if op.is_empty() { continue }
// when is any OTHER element visible?
let mut cmin = f64::MAX; let mut cmax = f64::MIN;
for o in &b.elements {
if o.index == el.index { continue }
for (s, t) in opaque_span(o, 1, tmax) { cmin = cmin.min(s); cmax = cmax.max(t) }
if !want.contains(&el.name.as_str()) {
continue;
}
if ui_layout::sprite_layer_key(&b, &by, el).is_some() {
continue;
}
let op = opaque_span(el, 255, tmax);
if op.is_empty() {
continue;
}
// when is any OTHER element visible?
let mut cmin = f64::MAX;
let mut cmax = f64::MIN;
for o in &b.elements {
if o.index == el.index {
continue;
}
for (s, t) in opaque_span(o, 1, tmax) {
cmin = cmin.min(s);
cmax = cmax.max(t)
}
}
if cmin > cmax {
continue;
}
if cmin > cmax { continue }
// overlap of the primitive's opaque span with the content span
let ov: f64 = op.iter()
.map(|&(s, t)| (t.min(cmax) - s.max(cmin)).max(0.0)).sum();
let opd: String = op.iter().map(|&(s,t)| format!("{s:.0}-{t:.0}")).collect::<Vec<_>>().join(",");
let flag = if ov > 2.0 { " 🔴 CANNOT BE ON TOP" } else { "" };
println!("{:>22} {:>5} {:>16} {:>18} {ov:6.1}{flag}",
el.name, format!("{}:{}", p.file_name().unwrap().to_string_lossy()
.trim_end_matches(".pak").trim_start_matches("GP_"), ei),
opd, format!("{cmin:.0}-{cmax:.0}"));
let ov: f64 = op
.iter()
.map(|&(s, t)| (t.min(cmax) - s.max(cmin)).max(0.0))
.sum();
let opd: String = op
.iter()
.map(|&(s, t)| format!("{s:.0}-{t:.0}"))
.collect::<Vec<_>>()
.join(",");
let flag = if ov > 2.0 {
" 🔴 CANNOT BE ON TOP"
} else {
""
};
println!(
"{:>22} {:>5} {:>16} {:>18} {ov:6.1}{flag}",
el.name,
format!(
"{}:{}",
p.file_name()
.unwrap()
.to_string_lossy()
.trim_end_matches(".pak")
.trim_start_matches("GP_"),
ei
),
opd,
format!("{cmin:.0}-{cmax:.0}")
);
}
}
}

View File

@@ -18,54 +18,105 @@
use sylpheed_formats::{pak, ratc, ui_layout};
fn last_t(el: &ui_layout::Element) -> u32 {
el.keyframes.iter().filter_map(|k| k.time).max().unwrap_or(0)
el.keyframes
.iter()
.filter_map(|k| k.time)
.max()
.unwrap_or(0)
}
fn forced(b: &ui_layout::UiBuild, el: &ui_layout::Element, tmax: u32, hold: bool) -> Option<bool> {
if el.sprite.is_some() { return None }
if (el.pivot_x * 2) < b.design_w as u32 || (el.pivot_y * 2) < b.design_h as u32 { return None }
if tmax == 0 { return None }
if el.sprite.is_some() {
return None;
}
if (el.pivot_x * 2) < b.design_w as u32 || (el.pivot_y * 2) < b.design_h as u32 {
return None;
}
if tmax == 0 {
return None;
}
let alpha = |e: &ui_layout::Element, t: u32| -> u32 {
if !hold && t > last_t(e) { return 0 }
if !hold && t > last_t(e) {
return 0;
}
e.pose_at(t).map(|k| k.fade >> 24).unwrap_or(0)
};
let op: Vec<u32> = (0..=tmax).filter(|&t| alpha(el, t) == 255).collect();
if op.is_empty() { return None }
let others: Vec<&ui_layout::Element> = b.elements.iter().filter(|o| o.index != el.index).collect();
if others.is_empty() { return None }
let below = others.iter().filter(|o| op.iter().any(|&t| alpha(o, t) > 0)).count();
if op.is_empty() {
return None;
}
let others: Vec<&ui_layout::Element> =
b.elements.iter().filter(|o| o.index != el.index).collect();
if others.is_empty() {
return None;
}
let below = others
.iter()
.filter(|o| op.iter().any(|&t| alpha(o, t) > 0))
.count();
Some(below == others.len())
}
fn main() {
let root = std::env::var("SYLPHEED_DISC").unwrap_or_else(|_| "/disc".into());
let mut paks: Vec<_> = std::fs::read_dir(format!("{root}/dat")).expect("dat/")
.flatten().map(|e| e.path())
.filter(|p| p.extension().and_then(|s| s.to_str()) == Some("pak")).collect();
let mut paks: Vec<_> = std::fs::read_dir(format!("{root}/dat"))
.expect("dat/")
.flatten()
.map(|e| e.path())
.filter(|p| p.extension().and_then(|s| s.to_str()) == Some("pak"))
.collect();
paks.sort();
let (mut n, mut a_true) = (0usize, 0usize);
let mut flips = [0usize; 3];
let mut examples: Vec<String> = Vec::new();
for p in &paks {
let Ok(ar) = pak::PakArchive::open(p) else { continue };
let Ok(ar) = pak::PakArchive::open(p) else {
continue;
};
for (ei, e) in ar.entries().iter().enumerate() {
let Ok(by) = ar.read(e) else { continue };
if !ratc::is_ratc(&by) { continue }
let Some(b) = ui_layout::parse_build(&by) else { continue };
let tall = b.elements.iter().flat_map(|el| el.keyframes.iter().filter_map(|k| k.time))
.max().unwrap_or(0);
let hdr = if by.len() >= 12 { u32::from_be_bytes(by[8..12].try_into().unwrap()) } else { 0 };
if !ratc::is_ratc(&by) {
continue;
}
let Some(b) = ui_layout::parse_build(&by) else {
continue;
};
let tall = b
.elements
.iter()
.flat_map(|el| el.keyframes.iter().filter_map(|k| k.time))
.max()
.unwrap_or(0);
let hdr = if by.len() >= 12 {
u32::from_be_bytes(by[8..12].try_into().unwrap())
} else {
0
};
for el in &b.elements {
let Some(va) = forced(&b, el, tall, true) else { continue };
n += 1; if va { a_true += 1 }
for (k, vb) in [forced(&b, el, last_t(el), true),
forced(&b, el, hdr, true),
forced(&b, el, tall, false)].into_iter().enumerate() {
let Some(va) = forced(&b, el, tall, true) else {
continue;
};
n += 1;
if va {
a_true += 1
}
for (k, vb) in [
forced(&b, el, last_t(el), true),
forced(&b, el, hdr, true),
forced(&b, el, tall, false),
]
.into_iter()
.enumerate()
{
if vb != Some(va) {
flips[k] += 1;
if k == 2 && examples.len() < 6 {
examples.push(format!("{}:{} {} A={va} D={vb:?}",
p.file_name().unwrap().to_string_lossy(), ei, el.name));
examples.push(format!(
"{}:{} {} A={va} D={vb:?}",
p.file_name().unwrap().to_string_lossy(),
ei,
el.name
));
}
}
}
@@ -75,8 +126,19 @@ fn main() {
println!("keyless full-screen primitives with an opaque interval: {n}");
println!(" convention A (span = all elements' max, hold) -> forced first: {a_true}\n");
println!(" verdicts that CHANGE under:");
println!(" B span = the primitive's own last keyframe : {}", flips[0]);
println!(" C span = the header's declared length +0x08 : {}", flips[1]);
println!(" D elements GONE after their last keyframe : {}", flips[2]);
for e in &examples { println!(" {e}") }
println!(
" B span = the primitive's own last keyframe : {}",
flips[0]
);
println!(
" C span = the header's declared length +0x08 : {}",
flips[1]
);
println!(
" D elements GONE after their last keyframe : {}",
flips[2]
);
for e in &examples {
println!(" {e}")
}
}

View File

@@ -8,35 +8,53 @@
//! export; this checks that against the disc.
//!
//! cargo run -p sylpheed-formats --example ptloop_leaf_extent
use sylpheed_formats::{pak::PakArchive, ui_layout};
use std::path::PathBuf;
use sylpheed_formats::{pak::PakArchive, ui_layout};
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.pak");
for entry in [4usize, 5, 7] {
let Ok(by) = ar.read(&ar.entries()[entry]) else { continue };
let Some(b) = ui_layout::parse_build(&by) else { continue };
let Ok(by) = ar.read(&ar.entries()[entry]) else {
continue;
};
let Some(b) = ui_layout::parse_build(&by) else {
continue;
};
println!("\n######## GP_TITLE entry {entry} ########");
for parent in ["ptloop01", "ptloop02"] {
let Some(el) = b.elements.iter().find(|e| e.name.starts_with(parent)) else {
println!(" {parent}: not present in this build"); continue };
println!(" {parent}: not present in this build");
continue;
};
let Some(&(off, size)) = b.records.get(&el.name) else {
println!(" {}: no nested record", el.name); continue };
println!(" {}: no nested record", el.name);
continue;
};
let span = u32::from_be_bytes(by[off + 8..off + 12].try_into().unwrap());
let Some(lb) = ui_layout::parse_build(&by[off..off + size]) else {
println!(" {}: leaf will not parse", el.name); continue };
println!(" {} parent rest ({},{}) nested cycle span {span}",
el.name, el.keyframes.last().map(|k| k.x).unwrap_or(0),
el.keyframes.last().map(|k| k.y).unwrap_or(0));
println!(" {}: leaf will not parse", el.name);
continue;
};
println!(
" {} parent rest ({},{}) nested cycle span {span}",
el.name,
el.keyframes.last().map(|k| k.x).unwrap_or(0),
el.keyframes.last().map(|k| k.y).unwrap_or(0)
);
for le in &lb.elements {
let (mut lo, mut hi) = (i64::MAX, i64::MIN);
let (mut sxs, mut sys) = (Vec::new(), Vec::new());
for t in 0..=span {
if let Some(k) = le.pose_at(t) {
lo = lo.min(k.x as i64); hi = hi.max(k.x as i64);
if !sxs.contains(&k.scale_x) { sxs.push(k.scale_x) }
if !sys.contains(&k.scale_y) { sys.push(k.scale_y) }
lo = lo.min(k.x as i64);
hi = hi.max(k.x as i64);
if !sxs.contains(&k.scale_x) {
sxs.push(k.scale_x)
}
if !sys.contains(&k.scale_y) {
sys.push(k.scale_y)
}
}
}
// A CYCLE LENGTH IS NOT A MOTION DURATION. Find the last t at
@@ -46,15 +64,24 @@ fn main() {
let mut prev = None;
for t in 0..=span {
if let Some(k) = le.pose_at(t) {
if prev.map_or(false, |p| p != k.x) { last_move = t }
if prev.map_or(false, |p| p != k.x) {
last_move = t
}
prev = Some(k.x);
}
}
let w = (le.pivot_x * 2) as i64;
println!(" leaf {:<12} pivot {}x{} quad w={w} x track {lo} .. {hi} \
println!(
" leaf {:<12} pivot {}x{} quad w={w} x track {lo} .. {hi} \
(centre {} .. {}) scale_x {:?} scale_y {:?}",
le.name, le.pivot_x, le.pivot_y,
lo + le.pivot_x as i64, hi + le.pivot_x as i64, sxs, sys);
le.name,
le.pivot_x,
le.pivot_y,
lo + le.pivot_x as i64,
hi + le.pivot_x as i64,
sxs,
sys
);
println!(" motion ends at t={last_move} of a {span}-unit cycle -> {:.3} px/unit over the MOVING span (vs {:.3} over the cycle)",
(hi - lo) as f64 / last_move.max(1) as f64,
(hi - lo) as f64 / span as f64);

View File

@@ -7,8 +7,8 @@
//! number from their export, so it is worth deriving independently.
//!
//! cargo run -p sylpheed-formats --example ptloop_leaf_keyframes
use sylpheed_formats::{pak::PakArchive, ui_layout};
use std::path::PathBuf;
use sylpheed_formats::{pak::PakArchive, ui_layout};
fn main() {
let root = PathBuf::from(std::env::var("SYLPHEED_DISC").expect("SYLPHEED_DISC"));
@@ -16,9 +16,15 @@ fn main() {
let by = ar.read(&ar.entries()[4]).expect("entry 4");
let b = ui_layout::parse_build(&by).expect("parse");
for parent in ["ptloop01", "ptloop02"] {
let Some(el) = b.elements.iter().find(|e| e.name.starts_with(parent)) else { continue };
let Some(&(off, size)) = b.records.get(&el.name) else { continue };
let Some(lb) = ui_layout::parse_build(&by[off..off + size]) else { continue };
let Some(el) = b.elements.iter().find(|e| e.name.starts_with(parent)) else {
continue;
};
let Some(&(off, size)) = b.records.get(&el.name) else {
continue;
};
let Some(lb) = ui_layout::parse_build(&by[off..off + size]) else {
continue;
};
for le in &lb.elements {
println!("\n== {} -> leaf {}", el.name, le.name);
let ks: Vec<_> = le.keyframes.iter().collect();
@@ -27,8 +33,14 @@ fn main() {
match (a.time, c.time) {
(Some(t0), Some(t1)) if t1 > t0 => println!(
" t {t0:>4} -> {t1:<4} x {:>6} -> {:<6} = {:+.4} px/unit",
a.x, c.x, (c.x - a.x) as f64 / (t1 - t0) as f64),
_ => println!(" t {:?} -> {:?} x {} -> {} (no rate)", a.time, c.time, a.x, c.x),
a.x,
c.x,
(c.x - a.x) as f64 / (t1 - t0) as f64
),
_ => println!(
" t {:?} -> {:?} x {} -> {} (no rate)",
a.time, c.time, a.x, c.x
),
}
}
}

View File

@@ -41,7 +41,10 @@ fn main() {
// CYCLE LENGTH, and its keyframes need not fill it. This is why two
// captures of the same settled title do not share a sweep phase.
let loop_len = u32::from_be_bytes(by[off + 8..off + 12].try_into().unwrap());
println!("\n-- {} nested record: loop length (+0x08) = {loop_len}", el.name);
println!(
"\n-- {} nested record: loop length (+0x08) = {loop_len}",
el.name
);
let Some(lb) = ui_layout::parse_build(&by[off..off + size]) else {
eprintln!("{}: leaf will not parse", el.name);
continue;

View File

@@ -6,19 +6,30 @@
//! alpha, and this prints it.
//!
//! cargo run -p sylpheed-formats --example ptloop_parent_keyframes -- GP_TITLE 5
use sylpheed_formats::{pak::PakArchive, ui_layout};
use std::path::PathBuf;
use sylpheed_formats::{pak::PakArchive, ui_layout};
fn main() {
let root = PathBuf::from(std::env::var("SYLPHEED_DISC").expect("SYLPHEED_DISC"));
let argv: Vec<String> = std::env::args().skip(1).collect();
let pak = argv.iter().find(|a| a.parse::<usize>().is_err())
.cloned().unwrap_or_else(|| "GP_TITLE".to_string());
let pak = argv
.iter()
.find(|a| a.parse::<usize>().is_err())
.cloned()
.unwrap_or_else(|| "GP_TITLE".to_string());
let ar = PakArchive::open(root.join(format!("dat/{pak}.pak"))).expect("pak");
let builds: Vec<usize> = argv.iter().filter_map(|a| a.parse().ok()).collect();
for e in if builds.is_empty() { vec![5usize] } else { builds } {
let Ok(by) = ar.read(&ar.entries()[e]) else { continue };
let Some(b) = ui_layout::parse_build(&by) else { continue };
for e in if builds.is_empty() {
vec![5usize]
} else {
builds
} {
let Ok(by) = ar.read(&ar.entries()[e]) else {
continue;
};
let Some(b) = ui_layout::parse_build(&by) else {
continue;
};
println!("=== {pak} entry {e}: {} elements ===", b.elements.len());
for el in &b.elements {
for (i, k) in el.keyframes.iter().enumerate() {

View File

@@ -16,54 +16,98 @@
//! * The distribution must not be trivial: if every record had exactly
//! `+0x08 == max t`, the field would carry nothing and "loop length" would be
//! an unfalsifiable relabelling of the keyframes.
use sylpheed_formats::{pak, ratc, ui_layout};
use std::collections::BTreeMap;
use sylpheed_formats::{pak, ratc, ui_layout};
fn main() {
let root = std::env::var("SYLPHEED_DISC").unwrap_or_else(|_| "/disc".into());
let mut paks: Vec<_> = std::fs::read_dir(format!("{root}/dat")).expect("dat/")
.flatten().map(|e| e.path())
.filter(|p| p.extension().and_then(|s| s.to_str()) == Some("pak")).collect();
let mut paks: Vec<_> = std::fs::read_dir(format!("{root}/dat"))
.expect("dat/")
.flatten()
.map(|e| e.path())
.filter(|p| p.extension().and_then(|s| s.to_str()) == Some("pak"))
.collect();
paks.sort();
let (mut total, mut exact, mut holds, mut violations) = (0usize, 0usize, 0usize, 0usize);
let mut hold_hist: BTreeMap<i64, usize> = BTreeMap::new();
let mut worst: Vec<(i64, String)> = Vec::new();
for p in &paks {
let Ok(ar) = pak::PakArchive::open(p) else { continue };
let Ok(ar) = pak::PakArchive::open(p) else {
continue;
};
for e in ar.entries() {
let Ok(by) = ar.read(e) else { continue };
if !ratc::is_ratc(&by) { continue }
let Some(b) = ui_layout::parse_build(&by) else { continue };
if !ratc::is_ratc(&by) {
continue;
}
let Some(b) = ui_layout::parse_build(&by) else {
continue;
};
for (rn, &(o, s)) in &b.records {
if o + 12 > by.len() || o + s > by.len() { continue }
if &by[o..o + 4] != b"RATC" { continue }
if o + 12 > by.len() || o + s > by.len() {
continue;
}
if &by[o..o + 4] != b"RATC" {
continue;
}
let len = u32::from_be_bytes(by[o + 8..o + 12].try_into().unwrap()) as i64;
let Some(lb) = ui_layout::parse_build(&by[o..o + s]) else { continue };
let maxt = lb.elements.iter()
let Some(lb) = ui_layout::parse_build(&by[o..o + s]) else {
continue;
};
let maxt = lb
.elements
.iter()
.flat_map(|el| el.keyframes.iter().filter_map(|k| k.time))
.max().unwrap_or(0) as i64;
if maxt == 0 { continue } // a static record declares no cycle
.max()
.unwrap_or(0) as i64;
if maxt == 0 {
continue;
} // a static record declares no cycle
total += 1;
let slack = len - maxt;
*hold_hist.entry(slack).or_default() += 1;
if slack == 0 { exact += 1 } else if slack > 0 { holds += 1 } else {
if slack == 0 {
exact += 1
} else if slack > 0 {
holds += 1
} else {
violations += 1;
if worst.len() < 12 {
worst.push((slack, format!("{}:{rn} len={len} maxt={maxt}",
p.file_name().unwrap().to_string_lossy())));
worst.push((
slack,
format!(
"{}:{rn} len={len} maxt={maxt}",
p.file_name().unwrap().to_string_lossy()
),
));
}
}
}
}
}
println!("nested records with timed keyframes : {total}");
println!(" +08 == max keyframe time (exact) : {exact} ({:.1}%)", 100.0*exact as f64/total as f64);
println!(" +08 > max keyframe time (a hold) : {holds} ({:.1}%)", 100.0*holds as f64/total as f64);
println!(" +08 < max keyframe time 🔴 : {violations} ({:.2}%) <- the falsifier",
100.0*violations as f64/total as f64);
println!(
" +08 == max keyframe time (exact) : {exact} ({:.1}%)",
100.0 * exact as f64 / total as f64
);
println!(
" +08 > max keyframe time (a hold) : {holds} ({:.1}%)",
100.0 * holds as f64 / total as f64
);
println!(
" +08 < max keyframe time 🔴 : {violations} ({:.2}%) <- the falsifier",
100.0 * violations as f64 / total as f64
);
println!("\nslack (+08 - max t) distribution, most common first:");
let mut h: Vec<_> = hold_hist.iter().collect();
h.sort_by_key(|&(_, n)| std::cmp::Reverse(*n));
for (k, n) in h.iter().take(14) { println!(" slack {k:>6} : {n}"); }
if !worst.is_empty() { println!("\nviolations:"); for (s, w) in &worst { println!(" {s:>6} {w}"); } }
for (k, n) in h.iter().take(14) {
println!(" slack {k:>6} : {n}");
}
if !worst.is_empty() {
println!("\nviolations:");
for (s, w) in &worst {
println!(" {s:>6} {w}");
}
}
}

View File

@@ -10,54 +10,83 @@
//! anything cannot be trusted to return the right one.
//!
//! cargo run -p sylpheed-formats --example record_loop_length_api
use sylpheed_formats::{pak::PakArchive, ui_layout};
use std::path::PathBuf;
use sylpheed_formats::{pak::PakArchive, ui_layout};
fn main() {
// ---- controls -------------------------------------------------------
assert_eq!(ui_layout::loop_length_units(b"NOTR\x00\x00\x00\x00\x00\x00\x00\x78"), None,
"control FAILED: accepted a non-RATC slice");
assert_eq!(ui_layout::loop_length_units(b"RATC\x00\x00"), None,
"control FAILED: accepted a slice too short for +0x08");
assert_eq!(ui_layout::loop_length_units(b"RATC\x00\x00\x00\x00\x00\x00\x00\x78"), Some(120),
"control FAILED: did not read +0x08 big-endian");
assert_eq!(
ui_layout::loop_length_units(b"NOTR\x00\x00\x00\x00\x00\x00\x00\x78"),
None,
"control FAILED: accepted a non-RATC slice"
);
assert_eq!(
ui_layout::loop_length_units(b"RATC\x00\x00"),
None,
"control FAILED: accepted a slice too short for +0x08"
);
assert_eq!(
ui_layout::loop_length_units(b"RATC\x00\x00\x00\x00\x00\x00\x00\x78"),
Some(120),
"control FAILED: did not read +0x08 big-endian"
);
println!("controls pass: rejects non-RATC, rejects short, reads BE at +0x08");
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.pak");
// The records the finding names, with their published values.
let expect: &[(&str, u32)] = &[("ptbtn00f.rat", 120), ("ptloop01.rat", 600),
("ptloop02.rat", 720)];
let expect: &[(&str, u32)] = &[
("ptbtn00f.rat", 120),
("ptloop01.rat", 600),
("ptloop02.rat", 720),
];
let mut seen = 0usize;
let (mut recs, mut viol) = (0usize, 0usize);
for (ei, e) in ar.entries().iter().enumerate() {
let Ok(by) = ar.read(e) else { continue };
let Some(b) = ui_layout::parse_build(&by) else { continue };
let Some(b) = ui_layout::parse_build(&by) else {
continue;
};
for (name, (off, size)) in &b.records {
let rec = &by[*off..(*off + *size).min(by.len())];
let Some(len) = ui_layout::loop_length_units(rec) else { continue };
let Some(len) = ui_layout::loop_length_units(rec) else {
continue;
};
recs += 1;
// the disc-wide invariant the finding rests on
let largest = ui_layout::parse_build(rec)
.map(|l| l.elements.iter()
.flat_map(|el| el.keyframes.iter().filter_map(|k| k.time))
.max().unwrap_or(0))
.map(|l| {
l.elements
.iter()
.flat_map(|el| el.keyframes.iter().filter_map(|k| k.time))
.max()
.unwrap_or(0)
})
.unwrap_or(0);
if len < largest { viol += 1; }
if len < largest {
viol += 1;
}
for (want_name, want) in expect {
if name == want_name && seen < 16 {
seen += 1;
let ok = if len == *want { "OK" } else { "MISMATCH" };
println!(" entry {ei:2} {name:16} +0x08 = {len:4} \
(published {want}) largest kf {largest:4} {ok}");
println!(
" entry {ei:2} {name:16} +0x08 = {len:4} \
(published {want}) largest kf {largest:4} {ok}"
);
assert_eq!(len, *want, "{name} disagrees with the published value");
}
}
}
}
println!("\n{recs} records read through the public fn; \
{viol} violate +0x08 >= largest keyframe time");
assert!(seen > 0, "found none of the named records — the check proved nothing");
println!(
"\n{recs} records read through the public fn; \
{viol} violate +0x08 >= largest keyframe time"
);
assert!(
seen > 0,
"found none of the named records — the check proved nothing"
);
}

View File

@@ -16,14 +16,16 @@
//! * last alpha == 0 -> it fades out; a visible rest is a transient's peak
//!
//! cargo run -p sylpheed-formats --example rest_fallback_audit
use sylpheed_formats::{pak::PakArchive, ui_layout};
use std::path::PathBuf;
use sylpheed_formats::{pak::PakArchive, ui_layout};
fn main() {
let root = PathBuf::from(std::env::var("SYLPHEED_DISC").expect("SYLPHEED_DISC"));
let mut paks: Vec<PathBuf> = std::fs::read_dir(root.join("dat")).expect("dat/")
let mut paks: Vec<PathBuf> = std::fs::read_dir(root.join("dat"))
.expect("dat/")
.filter_map(|e| e.ok().map(|e| e.path()))
.filter(|p| p.extension().is_some_and(|x| x == "pak")).collect();
.filter(|p| p.extension().is_some_and(|x| x == "pak"))
.collect();
paks.sort();
let (mut fires, mut vis, mut ends_visible, mut ends_zero, mut at_peak) = (0, 0, 0, 0, 0);
// ⚠️ The port agent's exit-ramp finding applies to THIS split too: if a
@@ -32,26 +34,53 @@ fn main() {
// elements before using it on the 1 697.
let (mut all_el, mut all_end_zero) = (0usize, 0usize);
for pak in &paks {
let Ok(ar) = PakArchive::open(pak) else { continue };
let Ok(ar) = PakArchive::open(pak) 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 };
let Some(b) = ui_layout::parse_build(&by) else {
continue;
};
for el in &b.elements {
if el.keyframes.len() < 2 { continue }
if el.keyframes.len() < 2 {
continue;
}
all_el += 1;
if (el.keyframes.last().unwrap().fade >> 24) & 0xff == 0 { all_end_zero += 1 }
if el.keyframes.windows(2).any(|w| w[0].x == w[1].x && w[0].y == w[1].y
&& w[0].scale_x == w[1].scale_x && w[0].scale_y == w[1].scale_y
&& w[0].fade == w[1].fade) { continue }
if (el.keyframes.last().unwrap().fade >> 24) & 0xff == 0 {
all_end_zero += 1
}
if el.keyframes.windows(2).any(|w| {
w[0].x == w[1].x
&& w[0].y == w[1].y
&& w[0].scale_x == w[1].scale_x
&& w[0].scale_y == w[1].scale_y
&& w[0].fade == w[1].fade
}) {
continue;
}
fires += 1;
let Some(r) = el.rest() else { continue };
let a = (r.fade >> 24) & 0xff;
if a == 0 { continue }
if a == 0 {
continue;
}
vis += 1;
let last = (el.keyframes.last().unwrap().fade >> 24) & 0xff;
if last > 0 { ends_visible += 1 } else { ends_zero += 1 }
let peak = el.keyframes.iter().map(|k| (k.fade >> 24) & 0xff).max().unwrap_or(0);
if a == peak { at_peak += 1 }
if last > 0 {
ends_visible += 1
} else {
ends_zero += 1
}
let peak = el
.keyframes
.iter()
.map(|k| (k.fade >> 24) & 0xff)
.max()
.unwrap_or(0);
if a == peak {
at_peak += 1
}
}
}
}
@@ -62,7 +91,9 @@ fn main() {
println!(" rest alpha == the element's MAX {at_peak}");
println!("\nCONTROL on the split itself — is 'ends at a=0' near-universal?");
println!(" all elements with >= 2 keyframes {all_el}");
println!(" of those, last keyframe alpha = 0 {all_end_zero} ({:.1} %)",
100.0 * all_end_zero as f64 / all_el as f64);
println!(
" of those, last keyframe alpha = 0 {all_end_zero} ({:.1} %)",
100.0 * all_end_zero as f64 / all_el as f64
);
println!("\n--- END (if this line is missing, the run did not finish) ---");
}

View File

@@ -14,8 +14,8 @@
//!
//! cargo run -p sylpheed-formats --example rest_fallback_census
use sylpheed_formats::{pak::PakArchive, ui_layout};
use std::path::PathBuf;
use sylpheed_formats::{pak::PakArchive, ui_layout};
fn main() {
let root = PathBuf::from(std::env::var("SYLPHEED_DISC").expect("SYLPHEED_DISC"));
@@ -30,22 +30,33 @@ fn main() {
let mut worst: Vec<(u32, String, String)> = Vec::new();
let mut per_pak: std::collections::BTreeMap<String, (usize, usize)> = Default::default();
for pak in &paks {
let Ok(ar) = PakArchive::open(pak) else { continue };
let Ok(ar) = PakArchive::open(pak) else {
continue;
};
let name = pak.file_name().unwrap().to_string_lossy().to_string();
for (i, e) in ar.entries().iter().enumerate() {
let Ok(by) = ar.read(e) else { continue };
let Some(b) = ui_layout::parse_build(&by) else { continue };
let Some(b) = ui_layout::parse_build(&by) else {
continue;
};
for el in &b.elements {
if el.keyframes.len() < 2 { continue }
if el.keyframes.len() < 2 {
continue;
}
elements += 1;
// a plateau is two ADJACENT poses that are equal — the same test
// the plateau path makes before the fallback can run
let has_plateau = el.keyframes.windows(2).any(|w| {
w[0].x == w[1].x && w[0].y == w[1].y
&& w[0].scale_x == w[1].scale_x && w[0].scale_y == w[1].scale_y
w[0].x == w[1].x
&& w[0].y == w[1].y
&& w[0].scale_x == w[1].scale_x
&& w[0].scale_y == w[1].scale_y
&& w[0].fade == w[1].fade
});
if has_plateau { plateau += 1; continue }
if has_plateau {
plateau += 1;
continue;
}
fallback += 1;
per_pak.entry(name.clone()).or_default().0 += 1;
let Some(r) = el.rest() else { continue };
@@ -58,13 +69,16 @@ fn main() {
}
}
}
println!("POPULATION: {elements} elements with >= 2 keyframes, over {} archives", paks.len());
println!(
"POPULATION: {elements} elements with >= 2 keyframes, over {} archives",
paks.len()
);
println!("COVERAGE: {plateau} have a plateau (fallback never runs)");
println!(" {fallback} have NONE -> the dwell fallback decides");
println!(" {fb_visible} of those rest at alpha > 0 -- i.e. VISIBLE\n");
println!("PER ARCHIVE — fallback fires / of those, rests VISIBLE:");
let mut rows: Vec<_> = per_pak.into_iter().collect();
rows.sort_by(|a, b| b.1.1.cmp(&a.1.1));
rows.sort_by(|a, b| b.1 .1.cmp(&a.1 .1));
for (pak, (fires, vis)) in &rows {
println!(" {pak:34} {fires:5} fires {vis:5} visible");
}

View File

@@ -1,27 +1,49 @@
//! Which `GP_TITLE` elements does the resting-pose dwell fallback decide, and does
//! it hand back a visible pose? The disc-wide census says 5 fires / 4 visible here.
//! cargo run -p sylpheed-formats --example rest_fallback_title
use sylpheed_formats::{pak::PakArchive, ui_layout};
use std::path::PathBuf;
use sylpheed_formats::{pak::PakArchive, ui_layout};
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 (i, e) in ar.entries().iter().enumerate() {
let Ok(by) = ar.read(e) else { continue };
let Some(b) = ui_layout::parse_build(&by) else { continue };
let Some(b) = ui_layout::parse_build(&by) else {
continue;
};
for el in &b.elements {
if el.keyframes.len() < 2 { continue }
if el.keyframes.windows(2).any(|w| w[0].x == w[1].x && w[0].y == w[1].y
&& w[0].scale_x == w[1].scale_x && w[0].scale_y == w[1].scale_y
&& w[0].fade == w[1].fade) { continue }
if el.keyframes.len() < 2 {
continue;
}
if el.keyframes.windows(2).any(|w| {
w[0].x == w[1].x
&& w[0].y == w[1].y
&& w[0].scale_x == w[1].scale_x
&& w[0].scale_y == w[1].scale_y
&& w[0].fade == w[1].fade
}) {
continue;
}
let Some(r) = el.rest() else { continue };
let a = (r.fade >> 24) & 0xff;
let ks: Vec<String> = el.keyframes.iter()
.map(|k| format!("{}:a{}", k.time.map(|v| v.to_string()).unwrap_or("-".into()), (k.fade >> 24) & 0xff))
let ks: Vec<String> = el
.keyframes
.iter()
.map(|k| {
format!(
"{}:a{}",
k.time.map(|v| v.to_string()).unwrap_or("-".into()),
(k.fade >> 24) & 0xff
)
})
.collect();
println!("entry {i:2} {:24} rest a={a:3} t={:?} [{}]{}",
el.name, r.time, ks.join(" "),
if a > 0 { " <== VISIBLE" } else { "" });
println!(
"entry {i:2} {:24} rest a={a:3} t={:?} [{}]{}",
el.name,
r.time,
ks.join(" "),
if a > 0 { " <== VISIBLE" } else { "" }
);
}
}
}

View File

@@ -6,29 +6,56 @@
//! nothing like its stored size.
//!
//! cargo run -p sylpheed-formats --example rest_scale_of -- 5 6 4
use sylpheed_formats::{pak::PakArchive, ui_layout};
use std::path::PathBuf;
use sylpheed_formats::{pak::PakArchive, ui_layout};
fn main() {
let root = PathBuf::from(std::env::var("SYLPHEED_DISC").expect("SYLPHEED_DISC"));
let argv: Vec<String> = std::env::args().skip(1).collect();
let pak = argv.iter().find(|a| a.parse::<usize>().is_err())
.cloned().unwrap_or_else(|| "GP_TITLE".to_string());
let pak = argv
.iter()
.find(|a| a.parse::<usize>().is_err())
.cloned()
.unwrap_or_else(|| "GP_TITLE".to_string());
let ar = PakArchive::open(root.join(format!("dat/{pak}.pak"))).expect("pak");
let builds: Vec<usize> = argv.iter().filter_map(|a| a.parse().ok()).collect();
for build in if builds.is_empty() { vec![5usize, 6] } else { builds } {
let Ok(by) = ar.read(&ar.entries()[build]) else { continue };
let Some(b) = ui_layout::parse_build(&by) else { continue };
for build in if builds.is_empty() {
vec![5usize, 6]
} else {
builds
} {
let Ok(by) = ar.read(&ar.entries()[build]) else {
continue;
};
let Some(b) = ui_layout::parse_build(&by) else {
continue;
};
println!("=== {pak} entry {build} ===");
println!("{:<22} {:>10} {:>7} {:>7} {:>12} {}", "element", "pivot(w,h)", "sx%", "sy%", "drawn px", "at 1x/2x of pivot*2");
println!(
"{:<22} {:>10} {:>7} {:>7} {:>12} {}",
"element", "pivot(w,h)", "sx%", "sy%", "drawn px", "at 1x/2x of pivot*2"
);
for e in &b.elements {
let k = match e.rest() { Some(k) => k, None => continue };
let k = match e.rest() {
Some(k) => k,
None => continue,
};
let w = e.pivot_x * 2;
let h = e.pivot_y * 2;
let dw = w as f64 * k.scale_x as f64 / 100.0;
let dh = h as f64 * k.scale_y as f64 / 100.0;
println!("{:<22} {:>4},{:<5} {:>7} {:>7} {:>6.1}x{:<5.1} {:>6.1}x{:<5.1}",
e.name, w, h, k.scale_x, k.scale_y, dw, dh, dw * 2.0, dh * 2.0);
println!(
"{:<22} {:>4},{:<5} {:>7} {:>7} {:>6.1}x{:<5.1} {:>6.1}x{:<5.1}",
e.name,
w,
h,
k.scale_x,
k.scale_y,
dw,
dh,
dw * 2.0,
dh * 2.0
);
}
println!();
}

View File

@@ -20,14 +20,16 @@
//! averaged over.
//!
//! cargo run -p sylpheed-formats --example rest_vs_settle
use sylpheed_formats::{pak::PakArchive, ui_layout};
use std::path::PathBuf;
use sylpheed_formats::{pak::PakArchive, ui_layout};
fn main() {
let root = PathBuf::from(std::env::var("SYLPHEED_DISC").expect("SYLPHEED_DISC"));
let mut paks: Vec<PathBuf> = std::fs::read_dir(root.join("dat")).expect("dat/")
let mut paks: Vec<PathBuf> = std::fs::read_dir(root.join("dat"))
.expect("dat/")
.filter_map(|e| e.ok().map(|e| e.path()))
.filter(|p| p.extension().is_some_and(|x| x == "pak")).collect();
.filter(|p| p.extension().is_some_and(|x| x == "pak"))
.collect();
paks.sort();
// control population (plateau) and test population (fallback), each split by
// whether the bundle settles at all
@@ -35,19 +37,36 @@ fn main() {
let (mut fb_n, mut fb_rest_vis, mut fb_settle_vis) = (0usize, 0usize, 0usize);
let mut narrow = 0usize;
for pak in &paks {
let Ok(ar) = PakArchive::open(pak) else { continue };
let Ok(ar) = PakArchive::open(pak) 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 };
let Some((lo, hi)) = b.settle_window() else { continue };
if hi - lo < 10 { narrow += 1; continue } // this bundle never settles
let Some(b) = ui_layout::parse_build(&by) else {
continue;
};
let Some((lo, hi)) = b.settle_window() else {
continue;
};
if hi - lo < 10 {
narrow += 1;
continue;
} // this bundle never settles
let st = lo + (hi - lo) / 2;
for el in &b.elements {
if el.keyframes.len() < 2 { continue }
let plateau = el.keyframes.windows(2).any(|w| w[0].x == w[1].x
&& w[0].y == w[1].y && w[0].scale_x == w[1].scale_x
&& w[0].scale_y == w[1].scale_y && w[0].fade == w[1].fade);
let (Some(r), Some(s)) = (el.rest(), el.pose_at(st)) else { continue };
if el.keyframes.len() < 2 {
continue;
}
let plateau = el.keyframes.windows(2).any(|w| {
w[0].x == w[1].x
&& w[0].y == w[1].y
&& w[0].scale_x == w[1].scale_x
&& w[0].scale_y == w[1].scale_y
&& w[0].fade == w[1].fade
});
let (Some(r), Some(s)) = (el.rest(), el.pose_at(st)) else {
continue;
};
let (ra, sa) = ((r.fade >> 24) & 0xff, (s.fade >> 24) & 0xff);
if plateau {
// ⚠️ The first version of this control compared EVERY plateau
@@ -60,9 +79,11 @@ fn main() {
// held interval actually CONTAINS the settle instant.
ctl_n += 1;
let covers = el.keyframes.windows(2).any(|w| {
let held = w[0].x == w[1].x && w[0].y == w[1].y
let held = w[0].x == w[1].x
&& w[0].y == w[1].y
&& w[0].scale_x == w[1].scale_x
&& w[0].scale_y == w[1].scale_y && w[0].fade == w[1].fade;
&& w[0].scale_y == w[1].scale_y
&& w[0].fade == w[1].fade;
match (w[0].time, w[1].time) {
(Some(a), Some(bb)) => held && a <= st && st <= bb,
_ => false,
@@ -70,13 +91,23 @@ fn main() {
});
if covers {
ctl_cov += 1;
if ra == sa && r.x == s.x && r.y == s.y
&& r.scale_x == s.scale_x && r.scale_y == s.scale_y { ctl_agree += 1 }
if ra == sa
&& r.x == s.x
&& r.y == s.y
&& r.scale_x == s.scale_x
&& r.scale_y == s.scale_y
{
ctl_agree += 1
}
}
} else {
fb_n += 1;
if ra > 0 { fb_rest_vis += 1 }
if sa > 0 { fb_settle_vis += 1 }
if ra > 0 {
fb_rest_vis += 1
}
if sa > 0 {
fb_settle_vis += 1
}
}
}
}
@@ -85,13 +116,19 @@ fn main() {
println!("CONTROL — elements where rest() takes the SOUND plateau path:");
println!(" {ctl_n} plateau elements in settling bundles");
println!(" {ctl_cov} of them HOLD ACROSS the settle instant — the fair control");
println!(" pose_at(settle) agrees with rest() on {ctl_agree} of those ({:.1} %)",
100.0 * ctl_agree as f64 / ctl_cov.max(1) as f64);
println!(
" pose_at(settle) agrees with rest() on {ctl_agree} of those ({:.1} %)",
100.0 * ctl_agree as f64 / ctl_cov.max(1) as f64
);
println!("\nTEST — elements where the unsound dwell fallback decides:");
println!(" {fb_n} elements");
println!(" rest() returns a VISIBLE pose on {fb_rest_vis} ({:.1} %)",
100.0 * fb_rest_vis as f64 / fb_n.max(1) as f64);
println!(" pose_at(settle) returns a VISIBLE pose on {fb_settle_vis} ({:.1} %)",
100.0 * fb_settle_vis as f64 / fb_n.max(1) as f64);
println!(
" rest() returns a VISIBLE pose on {fb_rest_vis} ({:.1} %)",
100.0 * fb_rest_vis as f64 / fb_n.max(1) as f64
);
println!(
" pose_at(settle) returns a VISIBLE pose on {fb_settle_vis} ({:.1} %)",
100.0 * fb_settle_vis as f64 / fb_n.max(1) as f64
);
println!("\n--- END (if this line is missing, the run did not finish) ---");
}

View File

@@ -3,8 +3,8 @@
//! `DECISIONS.md` records `ptlogo_eff2` at 125 % as "the single drawn element in
//! the whole export at a scale that is not a whole multiple of 100 %". That
//! census was over parents only -- leaves were never opened. This opens them.
use sylpheed_formats::{pak, ui_layout};
use std::collections::BTreeMap;
use sylpheed_formats::{pak, ui_layout};
fn main() {
let path = std::env::args().nth(1).expect("pak");
@@ -13,7 +13,9 @@ fn main() {
let mut leaves_opened = 0usize;
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 Some(b) = ui_layout::parse_build(&bytes) else {
continue;
};
for el in &b.elements {
for k in &el.keyframes {
hist.entry((k.scale_x, k.scale_y))
@@ -38,11 +40,21 @@ fn main() {
println!("{:>12} {:>7} examples", "scale", "count");
for (k, v) in &hist {
let mut ex: Vec<&String> = v.iter().collect();
ex.sort(); ex.dedup();
ex.sort();
ex.dedup();
let odd = k.0 % 100 != 0 || k.1 % 100 != 0;
println!("{}{:>5},{:<5} {:>7} {}", if odd { "* " } else { " " },
k.0, k.1, v.len(),
ex.iter().take(3).map(|s| s.as_str()).collect::<Vec<_>>().join(", "));
println!(
"{}{:>5},{:<5} {:>7} {}",
if odd { "* " } else { " " },
k.0,
k.1,
v.len(),
ex.iter()
.take(3)
.map(|s| s.as_str())
.collect::<Vec<_>>()
.join(", ")
);
}
println!("\n* = not a whole multiple of 100%");
}

View File

@@ -15,14 +15,16 @@
//! whose two endpoint poses DIFFER — it is interpolating, not held.
//!
//! cargo run -p sylpheed-formats --example settle_midramp_census
use sylpheed_formats::{pak::PakArchive, ui_layout};
use std::path::PathBuf;
use sylpheed_formats::{pak::PakArchive, ui_layout};
fn main() {
let root = PathBuf::from(std::env::var("SYLPHEED_DISC").expect("SYLPHEED_DISC"));
let mut paks: Vec<PathBuf> = std::fs::read_dir(root.join("dat")).expect("dat/")
let mut paks: Vec<PathBuf> = std::fs::read_dir(root.join("dat"))
.expect("dat/")
.filter_map(|e| e.ok().map(|e| e.path()))
.filter(|p| p.extension().is_some_and(|x| x == "pak")).collect();
.filter(|p| p.extension().is_some_and(|x| x == "pak"))
.collect();
paks.sort();
// buckets by settle-window width
let edges = [0u32, 10, 20, 30, 60, u32::MAX];
@@ -31,18 +33,29 @@ fn main() {
let mut mid = [0usize; 5];
let mut bundles = [0usize; 5];
for pak in &paks {
let Ok(ar) = PakArchive::open(pak) else { continue };
let Ok(ar) = PakArchive::open(pak) 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 };
let Some((lo, hi)) = b.settle_window() else { continue };
let Some(b) = ui_layout::parse_build(&by) else {
continue;
};
let Some((lo, hi)) = b.settle_window() else {
continue;
};
let w = hi - lo;
let bi = edges.windows(2).position(|p| w >= p[0] && w < p[1]).unwrap_or(4);
let bi = edges
.windows(2)
.position(|p| w >= p[0] && w < p[1])
.unwrap_or(4);
bundles[bi] += 1;
let st = lo + w / 2;
for el in &b.elements {
let k = &el.keyframes;
if k.len() < 2 { continue }
if k.len() < 2 {
continue;
}
els[bi] += 1;
// the interval containing the settle instant
let mut interpolating = false;
@@ -50,26 +63,49 @@ fn main() {
if let (Some(t0), Some(t1)) = (pair[0].time, pair[1].time) {
if t0 <= st && st <= t1 && t0 != t1 {
let same = pair[0].fade == pair[1].fade
&& pair[0].x == pair[1].x && pair[0].y == pair[1].y
&& pair[0].x == pair[1].x
&& pair[0].y == pair[1].y
&& pair[0].scale_x == pair[1].scale_x
&& pair[0].scale_y == pair[1].scale_y;
if !same && st != t0 && st != t1 { interpolating = true }
if !same && st != t0 && st != t1 {
interpolating = true
}
break;
}
}
}
if interpolating { mid[bi] += 1 }
if interpolating {
mid[bi] += 1
}
}
}
}
println!("{:8}{:>10}{:>10}{:>12}{:>10}", "window", "bundles", "elements", "mid-ramp", "share");
println!(
"{:8}{:>10}{:>10}{:>12}{:>10}",
"window", "bundles", "elements", "mid-ramp", "share"
);
for i in 0..5 {
if els[i] == 0 { continue }
println!("{:8}{:>10}{:>10}{:>12}{:>9.1}%", names[i], bundles[i], els[i], mid[i],
100.0 * mid[i] as f64 / els[i] as f64);
if els[i] == 0 {
continue;
}
println!(
"{:8}{:>10}{:>10}{:>12}{:>9.1}%",
names[i],
bundles[i],
els[i],
mid[i],
100.0 * mid[i] as f64 / els[i] as f64
);
}
let te: usize = els.iter().sum(); let tm: usize = mid.iter().sum();
println!("{:8}{:>10}{:>10}{:>12}{:>9.1}%", "ALL", bundles.iter().sum::<usize>(), te, tm,
100.0 * tm as f64 / te as f64);
let te: usize = els.iter().sum();
let tm: usize = mid.iter().sum();
println!(
"{:8}{:>10}{:>10}{:>12}{:>9.1}%",
"ALL",
bundles.iter().sum::<usize>(),
te,
tm,
100.0 * tm as f64 / te as f64
);
println!("\n--- END (if this line is missing, the run did not finish) ---");
}

View File

@@ -8,42 +8,73 @@
//! different and much smaller population. This computes both.
//!
//! cargo run -p sylpheed-formats --example settle_narrow_rate
use sylpheed_formats::{pak::PakArchive, ui_layout};
use std::path::PathBuf;
use sylpheed_formats::{pak::PakArchive, ui_layout};
fn main() {
let root = PathBuf::from(std::env::var("SYLPHEED_DISC").expect("SYLPHEED_DISC"));
let mut paks: Vec<PathBuf> = std::fs::read_dir(root.join("dat")).expect("dat/")
let mut paks: Vec<PathBuf> = std::fs::read_dir(root.join("dat"))
.expect("dat/")
.filter_map(|e| e.ok().map(|e| e.path()))
.filter(|p| p.extension().is_some_and(|x| x == "pak")).collect();
.filter(|p| p.extension().is_some_and(|x| x == "pak"))
.collect();
paks.sort();
// (with >=2 keyframe times, narrow) for each population
let (mut b2, mut bn) = (0usize, 0usize); // screen builds (is_build)
let (mut c2, mut cn) = (0usize, 0usize); // composable (is_composable)
let (mut b2, mut bn) = (0usize, 0usize); // screen builds (is_build)
let (mut c2, mut cn) = (0usize, 0usize); // composable (is_composable)
for pak in &paks {
let Ok(ar) = PakArchive::open(pak) else { continue };
let Ok(ar) = PakArchive::open(pak) else {
continue;
};
for e in ar.entries() {
let Ok(by) = ar.read(e) else { continue };
let is_b = ui_layout::is_build(&by);
let is_c = ui_layout::is_composable(&by);
if !is_b && !is_c { continue }
let Some(b) = ui_layout::parse_build(&by) else { continue };
let mut ts: Vec<u32> = b.elements.iter()
.flat_map(|el| el.keyframes.iter().filter_map(|k| k.time)).collect();
ts.sort_unstable(); ts.dedup();
if ts.len() < 2 { continue }
let narrow = match b.settle_window() { Some((lo, hi)) => hi - lo < 10, None => true };
if is_b { b2 += 1; if narrow { bn += 1 } }
if is_c { c2 += 1; if narrow { cn += 1 } }
if !is_b && !is_c {
continue;
}
let Some(b) = ui_layout::parse_build(&by) else {
continue;
};
let mut ts: Vec<u32> = b
.elements
.iter()
.flat_map(|el| el.keyframes.iter().filter_map(|k| k.time))
.collect();
ts.sort_unstable();
ts.dedup();
if ts.len() < 2 {
continue;
}
let narrow = match b.settle_window() {
Some((lo, hi)) => hi - lo < 10,
None => true,
};
if is_b {
b2 += 1;
if narrow {
bn += 1
}
}
if is_c {
c2 += 1;
if narrow {
cn += 1
}
}
}
}
println!("population n narrow (<10 u) share");
println!("SCREEN BUILDS (is_build, what `screen render` renders by default)");
println!(" {b2:5} {bn:9} {:.0} %",
100.0 * bn as f64 / b2.max(1) as f64);
println!(
" {b2:5} {bn:9} {:.0} %",
100.0 * bn as f64 / b2.max(1) as f64
);
println!("COMPOSABLE bundles (is_composable, what --all admits)");
println!(" {c2:5} {cn:9} {:.0} %",
100.0 * cn as f64 / c2.max(1) as f64);
println!(
" {c2:5} {cn:9} {:.0} %",
100.0 * cn as f64 / c2.max(1) as f64
);
println!("\nui-settle-time.md quotes 731 / 1758 = 42 % over composable bundles.");
println!("--- END ---");
}

View File

@@ -6,24 +6,42 @@
// instead one INSTANT that every element is posed at, and the instant to pick is
// inside the longest interval during which no element has a keyframe at all.
use sylpheed_formats::{pak, ui_layout};
fn main(){
let mut a=std::env::args().skip(1);
let pk=a.next().unwrap();
let ar=pak::PakArchive::open(&pk).unwrap();
let only:Option<usize>=a.next().and_then(|s|s.parse().ok());
for (i,e) in ar.entries().iter().enumerate() {
if let Some(o)=only { if o!=i { continue } }
let Ok(by)=ar.read(e) else { continue };
let Some(b)=ui_layout::parse_build(&by) else { continue };
if b.elements.len()<2 { continue }
let mut ts:Vec<u32>=b.elements.iter().flat_map(|el|
el.keyframes.iter().filter_map(|k|k.time)).collect();
if ts.len()<2 { continue }
ts.sort_unstable(); ts.dedup();
fn main() {
let mut a = std::env::args().skip(1);
let pk = a.next().unwrap();
let ar = pak::PakArchive::open(&pk).unwrap();
let only: Option<usize> = a.next().and_then(|s| s.parse().ok());
for (i, e) in ar.entries().iter().enumerate() {
if let Some(o) = only {
if o != i {
continue;
}
}
let Ok(by) = ar.read(e) else { continue };
let Some(b) = ui_layout::parse_build(&by) else {
continue;
};
if b.elements.len() < 2 {
continue;
}
let mut ts: Vec<u32> = b
.elements
.iter()
.flat_map(|el| el.keyframes.iter().filter_map(|k| k.time))
.collect();
if ts.len() < 2 {
continue;
}
ts.sort_unstable();
ts.dedup();
// longest gap between consecutive keyframe times
let (mut best,mut lo,mut hi)=(0u32,0u32,0u32);
let (mut best, mut lo, mut hi) = (0u32, 0u32, 0u32);
for w in ts.windows(2) {
if w[1]-w[0] > best { best=w[1]-w[0]; lo=w[0]; hi=w[1]; }
if w[1] - w[0] > best {
best = w[1] - w[0];
lo = w[0];
hi = w[1];
}
}
println!("{:>4} {:<28} times {:>3} span {:>4} settle window [{lo},{hi}] = {best} units ({:.2}s) -> t={}",
i, format!("{:08x}",e.name_hash), ts.len(), ts.last().unwrap(), best as f64/60.0, lo+best/2);

View File

@@ -2,26 +2,38 @@
//! publisher splash's widest keyframe-free gap as 190 units; `--settle` reports 8.
//! One of the two readings is wrong and the file settles it.
//! cargo run -p sylpheed-formats --example settle_window_check -- <build>
use sylpheed_formats::{pak::PakArchive, ui_layout};
use std::path::PathBuf;
use sylpheed_formats::{pak::PakArchive, ui_layout};
fn main() {
let b: usize = std::env::args().nth(1).unwrap_or("10".into()).parse().unwrap();
let b: usize = std::env::args()
.nth(1)
.unwrap_or("10".into())
.parse()
.unwrap();
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()[b]).expect("entry");
let build = ui_layout::parse_build(&by).expect("parse");
println!("entry {b}: {} elements", build.elements.len());
for el in &build.elements {
let ts: Vec<String> = el.keyframes.iter()
.map(|k| k.time.map(|v| v.to_string()).unwrap_or("-".into())).collect();
let ts: Vec<String> = el
.keyframes
.iter()
.map(|k| k.time.map(|v| v.to_string()).unwrap_or("-".into()))
.collect();
println!(" {:26} [{}]", el.name, ts.join(" "));
}
let mut ts: Vec<u32> = build.elements.iter()
.flat_map(|e| e.keyframes.iter().filter_map(|k| k.time)).collect();
ts.sort_unstable(); ts.dedup();
let mut ts: Vec<u32> = build
.elements
.iter()
.flat_map(|e| e.keyframes.iter().filter_map(|k| k.time))
.collect();
ts.sort_unstable();
ts.dedup();
println!("\nunion of all element keyframe times: {ts:?}");
let gaps: Vec<(u32,u32,u32)> = ts.windows(2).map(|w| (w[1]-w[0], w[0], w[1])).collect();
let mut g = gaps.clone(); g.sort_by(|a,b| b.0.cmp(&a.0));
let gaps: Vec<(u32, u32, u32)> = ts.windows(2).map(|w| (w[1] - w[0], w[0], w[1])).collect();
let mut g = gaps.clone();
g.sort_by(|a, b| b.0.cmp(&a.0));
println!("widest gaps: {:?}", &g[..g.len().min(4)]);
println!("settle_window() reports {:?}", build.settle_window());
}

View File

@@ -16,7 +16,10 @@ use sylpheed_formats::{idxd::IdxdObject, pak::PakArchive};
fn main() {
let a: Vec<String> = std::env::args().collect();
let root = a.get(1).cloned().unwrap_or_else(|| std::env::var("SYLPHEED_DISC").unwrap());
let root = a
.get(1)
.cloned()
.unwrap_or_else(|| std::env::var("SYLPHEED_DISC").unwrap());
let arc = PakArchive::open(format!("{root}/dat/tables.pak")).unwrap();
// Anything a gain would plausibly be called, plus the audio nouns.
@@ -26,15 +29,26 @@ fn main() {
for (i, e) in arc.entries().iter().enumerate() {
let Ok(b) = arc.read(e) else { continue };
let Ok(o) = IdxdObject::parse(&b) else { continue };
let Ok(o) = IdxdObject::parse(&b) else {
continue;
};
let t = o.tokens();
let up: Vec<String> = t.iter().map(|s| s.to_uppercase()).collect();
let is_audio = up.iter().any(|s| {
s.contains("SOUND") || s.contains("BANK_") || s.starts_with("SE_") || s.starts_with("BGM_")
s.contains("SOUND")
|| s.contains("BANK_")
|| s.starts_with("SE_")
|| s.starts_with("BGM_")
});
if !is_audio { continue }
if !is_audio {
continue;
}
audio_objs += 1;
println!("audio object #{i}: schema {:08x}, {} tokens", o.schema_hash, t.len());
println!(
"audio object #{i}: schema {:08x}, {} tokens",
o.schema_hash,
t.len()
);
for (j, tok) in up.iter().enumerate() {
if GAINY.iter().any(|g| tok.contains(g)) {
gain_hits.push((i, t[j].clone()));
@@ -56,10 +70,24 @@ fn main() {
let mut ctrl = 0usize;
for e in arc.entries() {
let Ok(b) = arc.read(e) else { continue };
let Ok(o) = IdxdObject::parse(&b) else { continue };
ctrl += o.tokens().iter().filter(|s| s.to_uppercase().contains("SE_UI")).count();
let Ok(o) = IdxdObject::parse(&b) else {
continue;
};
ctrl += o
.tokens()
.iter()
.filter(|s| s.to_uppercase().contains("SE_UI"))
.count();
}
println!("\nCONTROL — the same matcher looking for a token known to exist (\"SE_UI\"): {ctrl} hits");
println!(" {}", if ctrl > 0 { "PASS: the matcher finds tokens that are there" }
else { "FAIL: matcher is broken, the negative above means nothing" });
println!(
"\nCONTROL — the same matcher looking for a token known to exist (\"SE_UI\"): {ctrl} hits"
);
println!(
" {}",
if ctrl > 0 {
"PASS: the matcher finds tokens that are there"
} else {
"FAIL: matcher is broken, the negative above means nothing"
}
);
}

View File

@@ -33,8 +33,11 @@ fn main() {
println!("entry {entry}: not a build");
continue;
};
println!("\n## GP_TITLE entry {entry} -- {} elements, {} sprites",
b.elements.len(), b.sprites.len());
println!(
"\n## GP_TITLE entry {entry} -- {} elements, {} sprites",
b.elements.len(),
b.sprites.len()
);
// Every sprite the bundle carries, not only those a top-level element
// names: a focused variant is reached through `focus_link` and would
@@ -43,13 +46,24 @@ fn main() {
names.sort();
for n in names {
match ui_layout::blend_additive_by_name(&b, &by, n) {
Some(true) => { additive += 1;
println!(" {n:<26} word04=0x{:08X} ADDITIVE",
ui_layout::header_word_04_by_name(&b, &by, n).unwrap()); }
Some(false) => { alpha_over += 1;
println!(" {n:<26} word04=0x{:08X} alpha-over",
ui_layout::header_word_04_by_name(&b, &by, n).unwrap()); }
None => { no_header += 1; println!(" {n:<26} (no T8aD header)"); }
Some(true) => {
additive += 1;
println!(
" {n:<26} word04=0x{:08X} ADDITIVE",
ui_layout::header_word_04_by_name(&b, &by, n).unwrap()
);
}
Some(false) => {
alpha_over += 1;
println!(
" {n:<26} word04=0x{:08X} alpha-over",
ui_layout::header_word_04_by_name(&b, &by, n).unwrap()
);
}
None => {
no_header += 1;
println!(" {n:<26} (no T8aD header)");
}
}
}
}
@@ -59,8 +73,10 @@ fn main() {
if additive == 0 {
println!("PREDICTION HELD -- the bit agrees with the capture on both splashes");
} else {
println!("PREDICTION FAILED -- {additive} sprite(s) claim additive, \
the capture submits none");
println!(
"PREDICTION FAILED -- {additive} sprite(s) claim additive, \
the capture submits none"
);
}
// Control: this harness must be able to REPORT additive, or "additive=0"
@@ -68,8 +84,14 @@ fn main() {
// the oracle measures as mixed.
let by6 = ar.read(&ar.entries()[6]).expect("entry 6");
let b6 = ui_layout::parse_build(&by6).expect("build 6");
let c_add = b6.sprites.keys()
.filter(|n| ui_layout::blend_additive_by_name(&b6, &by6, n) == Some(true)).count();
println!("control -- entry 6 through the SAME code path reports additive={c_add} \
(must be > 0): {}", if c_add > 0 { "PASS" } else { "FAIL" });
let c_add = b6
.sprites
.keys()
.filter(|n| ui_layout::blend_additive_by_name(&b6, &by6, n) == Some(true))
.count();
println!(
"control -- entry 6 through the SAME code path reports additive={c_add} \
(must be > 0): {}",
if c_add > 0 { "PASS" } else { "FAIL" }
);
}

View File

@@ -26,13 +26,13 @@ use sylpheed_formats::{pak::PakArchive, t8ad, ui_layout};
const CAPTURED: &[(&str, f64, f64, f64, f64, usize)] = &[
// name, x0, x1, y0, y1, draws submitted in
("Q0", -0.520, 0.520, -0.100, 0.080, 111),
("Q1", -0.390, 0.390, 0.350, 0.550, 87),
("Q2", -0.190, 0.190, -0.120, 0.120, 87),
("Q1", -0.390, 0.390, 0.350, 0.550, 87),
("Q2", -0.190, 0.190, -0.120, 0.120, 87),
("Q3", -0.300, 0.300, -0.620, -0.250, 87),
("Q4", -0.410, 0.410, 0.320, 0.570, 21),
("Q5", -0.200, 0.210, -0.150, 0.150, 21),
("Q4", -0.410, 0.410, 0.320, 0.570, 21),
("Q5", -0.200, 0.210, -0.150, 0.150, 21),
("Q6", -0.320, 0.310, -0.650, -0.220, 21),
("Q7", -0.530, 0.540, -0.130, 0.120, 8),
("Q7", -0.530, 0.540, -0.130, 0.120, 8),
];
fn main() {
@@ -45,12 +45,20 @@ fn main() {
let by = ar.read(&ar.entries()[entry]).expect("entry");
let b = ui_layout::parse_build(&by).expect("build");
for el in &b.elements {
let Some(sprite) = el.sprite.as_ref() else { continue };
let Some(&(off, size)) = b.sprites.get(sprite) else { continue };
let Some(img) = t8ad::parse(&by[off..off + size]) else { continue };
let Some(sprite) = el.sprite.as_ref() else {
continue;
};
let Some(&(off, size)) = b.sprites.get(sprite) else {
continue;
};
let Some(img) = t8ad::parse(&by[off..off + size]) else {
continue;
};
// Declared placement is constant across every keyframe on these
// eight elements, so any keyframe gives the same rect; take the first.
let Some(kf) = el.keyframes.first() else { continue };
let Some(kf) = el.keyframes.first() else {
continue;
};
let (x, y) = (kf.x as f64, kf.y as f64);
let (w, h) = (img.width as f64, img.height as f64);
predicted.push((
@@ -64,15 +72,20 @@ fn main() {
}
let dist = |p: &(String, f64, f64, f64, f64), c: &(&str, f64, f64, f64, f64, usize)| {
(p.1 - c.1).abs().max((p.2 - c.2).abs())
.max((p.3 - c.3).abs()).max((p.4 - c.4).abs())
(p.1 - c.1)
.abs()
.max((p.2 - c.2).abs())
.max((p.3 - c.3).abs())
.max((p.4 - c.4).abs())
};
// The capture rounds to 0.01, so a correct prediction must land inside half
// a step plus the pixel grid: 0.01 NDC is 6.4 px in x, 3.6 px in y.
const TOL: f64 = 0.010;
println!("{:<26} {:<4} {:>8} {:>9} {:>7} {}",
"sprite (disc)", "quad", "max|d|", "runner-up", "draws", "verdict");
println!(
"{:<26} {:<4} {:>8} {:>9} {:>7} {}",
"sprite (disc)", "quad", "max|d|", "runner-up", "draws", "verdict"
);
let (mut ok, mut bad) = (0, 0);
let mut used: Vec<&str> = Vec::new();
for p in &predicted {
@@ -82,15 +95,31 @@ fn main() {
let (d0, best) = (ds[0].0, ds[0].1);
let d1 = ds[1].0;
let good = d0 <= TOL && !used.contains(&best.0);
if good { ok += 1; used.push(best.0) } else { bad += 1 }
println!("{:<26} {:<4} {:>8.4} {:>9.4} {:>7} {}",
p.0, best.0, d0, d1, best.5,
if good { "OK" } else { "MISMATCH" });
if good {
ok += 1;
used.push(best.0)
} else {
bad += 1
}
println!(
"{:<26} {:<4} {:>8.4} {:>9.4} {:>7} {}",
p.0,
best.0,
d0,
d1,
best.5,
if good { "OK" } else { "MISMATCH" }
);
}
println!("\n{ok} named, {bad} unmatched, of {} predicted / {} captured",
predicted.len(), CAPTURED.len());
println!("CONTROL: every runner-up above must be far outside the {TOL} tolerance; \
if it is not, the rects are not separable and the naming is luck.");
println!(
"\n{ok} named, {bad} unmatched, of {} predicted / {} captured",
predicted.len(),
CAPTURED.len()
);
println!(
"CONTROL: every runner-up above must be far outside the {TOL} tolerance; \
if it is not, the rects are not separable and the naming is luck."
);
}
/// The second question this data answers, appended as its own pass: what IS an
@@ -103,39 +132,63 @@ fn eff_is_a_concentric_outset_not_a_scale() {
let ar = PakArchive::open(root.join("dat/GP_TITLE.pak")).expect("GP_TITLE");
for (entry, pairs) in [
(10usize, &[("palogo_sqex", "palogo_sqex_eff")][..]),
(11, &[("palogo_gamearts", "palogo_gamearts_eff"),
("palogo_seta", "palogo_seta_eff"),
("palogo_anima", "palogo_anima_eff")][..]),
(
11,
&[
("palogo_gamearts", "palogo_gamearts_eff"),
("palogo_seta", "palogo_seta_eff"),
("palogo_anima", "palogo_anima_eff"),
][..],
),
] {
let by = ar.read(&ar.entries()[entry]).expect("entry");
let b = ui_layout::parse_build(&by).expect("build");
for (logo, eff) in pairs {
let g = |n: &str| {
let el = b.elements.iter()
.find(|e| e.sprite.as_deref() == Some(&format!("{n}.t32"))).unwrap();
let el = b
.elements
.iter()
.find(|e| e.sprite.as_deref() == Some(&format!("{n}.t32")))
.unwrap();
let &(off, size) = b.sprites.get(&format!("{n}.t32")).unwrap();
let img = t8ad::parse(&by[off..off + size]).unwrap();
let kf = el.keyframes.first().unwrap();
(kf.x as f64, kf.y as f64, img.width as f64, img.height as f64)
(
kf.x as f64,
kf.y as f64,
img.width as f64,
img.height as f64,
)
};
let (lx, ly, lw, lh) = g(logo);
let (ex, ey, ew, eh) = g(eff);
// concentric?
let dcx = (ex + ew / 2.0) - (lx + lw / 2.0);
let dcy = (ey + eh / 2.0) - (ly + lh / 2.0);
assert!(dcx.abs() <= 2.0 && dcy.abs() <= 2.0,
"{eff}: centres differ by ({dcx},{dcy}) -- not concentric");
assert!(
dcx.abs() <= 2.0 && dcy.abs() <= 2.0,
"{eff}: centres differ by ({dcx},{dcy}) -- not concentric"
);
// uniform outset, NOT a uniform scale?
let (ox, oy) = ((ew - lw) / 2.0, (eh - lh) / 2.0);
assert!((ox - oy).abs() <= 2.0,
"{eff}: outset ({ox},{oy}) is not uniform");
assert!(ox >= 8.0 && ox <= 12.0, "{eff}: outset {ox} px outside 8..12");
assert!(
(ox - oy).abs() <= 2.0,
"{eff}: outset ({ox},{oy}) is not uniform"
);
assert!(
ox >= 8.0 && ox <= 12.0,
"{eff}: outset {ox} px outside 8..12"
);
// and the scale reading it displaces
let (sx, sy) = (ew / lw, eh / lh);
assert!((sx - sy).abs() > 0.05,
"{eff}: scales {sx:.3}/{sy:.3} ARE uniform -- 'scaled larger' would stand");
println!("{eff:<26} outset {ox:.0}x{oy:.0} px, centre off by \
({dcx:.1},{dcy:.1}), scale {sx:.3}/{sy:.3} (NOT uniform)");
assert!(
(sx - sy).abs() > 0.05,
"{eff}: scales {sx:.3}/{sy:.3} ARE uniform -- 'scaled larger' would stand"
);
println!(
"{eff:<26} outset {ox:.0}x{oy:.0} px, centre off by \
({dcx:.1},{dcy:.1}), scale {sx:.3}/{sy:.3} (NOT uniform)"
);
}
}
}

View File

@@ -1,12 +1,21 @@
use sylpheed_formats::{pak, ui_layout, t8ad};
fn main(){
let mut a=std::env::args().skip(1);
let pk=a.next().unwrap(); let i:usize=a.next().unwrap().parse().unwrap();
let ar=pak::PakArchive::open(pk).unwrap();
let by=ar.read(&ar.entries()[i]).unwrap();
let b=ui_layout::parse_build(&by).unwrap();
let mut v:Vec<(String,u32,u32)>=b.sprites.iter().filter_map(|(n,&(o,s))|{
let im=t8ad::parse(&by[o..o+s])?; Some((n.clone(),im.width,im.height))}).collect();
use sylpheed_formats::{pak, t8ad, ui_layout};
fn main() {
let mut a = std::env::args().skip(1);
let pk = a.next().unwrap();
let i: usize = a.next().unwrap().parse().unwrap();
let ar = pak::PakArchive::open(pk).unwrap();
let by = ar.read(&ar.entries()[i]).unwrap();
let b = ui_layout::parse_build(&by).unwrap();
let mut v: Vec<(String, u32, u32)> = b
.sprites
.iter()
.filter_map(|(n, &(o, s))| {
let im = t8ad::parse(&by[o..o + s])?;
Some((n.clone(), im.width, im.height))
})
.collect();
v.sort();
for (n,w,h) in v { println!(" {w:>5} x {h:<5} {n}"); }
for (n, w, h) in v {
println!(" {w:>5} x {h:<5} {n}");
}
}

View File

@@ -1,9 +1,17 @@
use sylpheed_formats::{pak, ui_layout};
fn main(){
let ar=pak::PakArchive::open(std::env::args().nth(1).unwrap()).unwrap();
let b=ui_layout::parse_build(&ar.read(&ar.entries()[4]).unwrap()).unwrap();
for el in b.elements.iter().filter(|e| e.name.contains("ptloop")||e.name.contains("ptbtn")) {
println!("{:<18} sprite={:?} has_leaf={}", el.name, el.sprite,
b.records.contains_key(&el.name));
fn main() {
let ar = pak::PakArchive::open(std::env::args().nth(1).unwrap()).unwrap();
let b = ui_layout::parse_build(&ar.read(&ar.entries()[4]).unwrap()).unwrap();
for el in b
.elements
.iter()
.filter(|e| e.name.contains("ptloop") || e.name.contains("ptbtn"))
{
println!(
"{:<18} sprite={:?} has_leaf={}",
el.name,
el.sprite,
b.records.contains_key(&el.name)
);
}
}

View File

@@ -10,8 +10,8 @@
//! holding something the disc says animates.
//!
//! cargo run -p sylpheed-formats --example static_cycle_inert
use sylpheed_formats::{pak::PakArchive, ui_layout};
use std::path::PathBuf;
use sylpheed_formats::{pak::PakArchive, ui_layout};
fn main() {
let root = PathBuf::from(std::env::var("SYLPHEED_DISC").expect("SYLPHEED_DISC"));
@@ -19,13 +19,17 @@ fn main() {
let (mut total, mut static_cycle, mut multipose) = (0usize, 0usize, 0usize);
for e in ar.entries() {
let Ok(by) = ar.read(e) else { continue };
let Some(b) = ui_layout::parse_build(&by) else { continue };
let Some(b) = ui_layout::parse_build(&by) else {
continue;
};
for (name, (off, size)) in &b.records {
let rec = &by[*off..(*off + *size).min(by.len())];
if rec.len() < 0x10 || &rec[0..4] != b"RATC" {
continue;
}
let Some(leaf) = ui_layout::parse_build(rec) else { continue };
let Some(leaf) = ui_layout::parse_build(rec) else {
continue;
};
total += 1;
let times: Vec<u32> = leaf
.elements
@@ -37,10 +41,17 @@ fn main() {
continue;
}
static_cycle += 1;
let worst = leaf.elements.iter().map(|el| el.keyframes.len()).max().unwrap_or(0);
let worst = leaf
.elements
.iter()
.map(|el| el.keyframes.len())
.max()
.unwrap_or(0);
if worst > 1 {
multipose += 1;
println!(" 🔴 {name} declares {cycle} units and has an element with {worst} poses");
println!(
" 🔴 {name} declares {cycle} units and has an element with {worst} poses"
);
} else if name.starts_with("ptbtn1") {
println!(" {name:16} declares {cycle} units, max poses/element {worst}");
}

View File

@@ -6,8 +6,8 @@
//! well-formed and "not exact" is a real answer. This checks it.
//!
//! cargo run -p sylpheed-formats --example static_record_census
use sylpheed_formats::{pak::PakArchive, ui_layout};
use std::path::PathBuf;
use sylpheed_formats::{pak::PakArchive, ui_layout};
fn main() {
let root = PathBuf::from(std::env::var("SYLPHEED_DISC").expect("SYLPHEED_DISC"));
@@ -19,16 +19,22 @@ fn main() {
paks.sort();
let (mut untimed, mut all_zero, mut nonzero) = (0usize, 0usize, 0usize);
for p in &paks {
let Ok(ar) = PakArchive::open(p) else { continue };
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 };
let Some(b) = ui_layout::parse_build(&by) else {
continue;
};
for (_, (off, size)) in &b.records {
let rec = &by[*off..(*off + *size).min(by.len())];
if rec.len() < 0x10 || &rec[0..4] != b"RATC" {
continue;
}
let Some(leaf) = ui_layout::parse_build(rec) else { continue };
let Some(leaf) = ui_layout::parse_build(rec) else {
continue;
};
let times: Vec<u32> = leaf
.elements
.iter()
@@ -44,7 +50,10 @@ fn main() {
}
}
}
println!("nested RATC records that parse: {}", untimed + all_zero + nonzero);
println!(
"nested RATC records that parse: {}",
untimed + all_zero + nonzero
);
println!(" NO timed keyframe at all : {untimed}");
println!(" timed, every pose at t == 0 : {all_zero}");
println!(" timed, largest t > 0 : {nonzero}");

View File

@@ -8,21 +8,34 @@
//! whole answer.
//!
//! cargo run -p sylpheed-formats --example sweep_leaf_ramp -- GP_TITLE 5 6 4
use sylpheed_formats::{pak::PakArchive, ui_layout};
use std::path::PathBuf;
use sylpheed_formats::{pak::PakArchive, ui_layout};
fn main() {
let root = PathBuf::from(std::env::var("SYLPHEED_DISC").expect("SYLPHEED_DISC"));
let argv: Vec<String> = std::env::args().skip(1).collect();
let pak = argv.iter().find(|a| a.parse::<usize>().is_err())
.cloned().unwrap_or_else(|| "GP_TITLE".to_string());
let pak = argv
.iter()
.find(|a| a.parse::<usize>().is_err())
.cloned()
.unwrap_or_else(|| "GP_TITLE".to_string());
let ar = PakArchive::open(root.join(format!("dat/{pak}.pak"))).expect("pak");
let builds: Vec<usize> = argv.iter().filter_map(|a| a.parse().ok()).collect();
for e in if builds.is_empty() { vec![5usize, 6, 4] } else { builds } {
let Ok(by) = ar.read(&ar.entries()[e]) else { continue };
let Some(b) = ui_layout::parse_build(&by) else { continue };
for e in if builds.is_empty() {
vec![5usize, 6, 4]
} else {
builds
} {
let Ok(by) = ar.read(&ar.entries()[e]) else {
continue;
};
let Some(b) = ui_layout::parse_build(&by) else {
continue;
};
for name in ["ptloop01.rat", "ptloop02.rat"] {
let Some(&(lo, ls)) = b.records.get(name) else { continue };
let Some(&(lo, ls)) = b.records.get(name) else {
continue;
};
let leaf_bytes = &by[lo..(lo + ls).min(by.len())];
println!("=== {pak} entry {e}{name} (leaf {ls} bytes) ===");
if let Some(loop_units) = ui_layout::loop_length_units(leaf_bytes) {

View File

@@ -8,8 +8,8 @@
//! 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;
use sylpheed_formats::{pak::PakArchive, ui_layout};
fn main() {
let root = PathBuf::from(std::env::var("SYLPHEED_DISC").expect("SYLPHEED_DISC"));
@@ -23,26 +23,51 @@ fn main() {
for n in names {
let (off, size) = b.sprites[n];
let s = &by[off..(off + size).min(by.len())];
if s.len() < 48 { continue }
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]]))
.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(" "));
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();
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;
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");
}
if !any { println!(" NONE"); }
}
}

View File

@@ -6,9 +6,9 @@
//! 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;
use sylpheed_formats::{pak::PakArchive, ui_layout};
fn main() {
let root = PathBuf::from(std::env::var("SYLPHEED_DISC").expect("SYLPHEED_DISC"));
@@ -22,29 +22,51 @@ fn main() {
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 };
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 };
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 }
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 }
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()) }
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'");
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!(
"{:>#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));
println!(
"\n0x8050 specifically: {} sprites, {} of them named *frame*",
hist.get(&0x8050).copied().unwrap_or(0),
frames_like.get(&0x8050).copied().unwrap_or(0)
);
}

View File

@@ -138,7 +138,11 @@ fn cases() -> Vec<Case> {
]
}
fn tied_overlapping_pairs(build: &UiBuild, bytes: &[u8], at: Option<u32>) -> Vec<(usize, usize, u32)> {
fn tied_overlapping_pairs(
build: &UiBuild,
bytes: &[u8],
at: Option<u32>,
) -> Vec<(usize, usize, u32)> {
let keys: Vec<u32> = build
.elements
.iter()

View File

@@ -19,7 +19,9 @@ fn rect(e: &ui_layout::Element, t: u32) -> Option<(i32, i32, i32, i32)> {
return None;
}
let (w, h) = ((e.pivot_x * 2) as i32, (e.pivot_y * 2) as i32);
if w == 0 || h == 0 { return None; }
if w == 0 || h == 0 {
return None;
}
Some((kf.x, kf.y, w, h))
}
@@ -27,50 +29,92 @@ fn live_pairs(b: &UiBuild, bytes: &[u8], keys: &[u32], t: u32) -> usize {
let mut n = 0;
for a in 0..keys.len() {
for c in (a + 1)..keys.len() {
if keys[a] != keys[c] || keys[a] == u32::MAX { continue }
let (Some(ra), Some(rb)) = (rect(&b.elements[a], t), rect(&b.elements[c], t)) else { continue };
if keys[a] != keys[c] || keys[a] == u32::MAX {
continue;
}
let (Some(ra), Some(rb)) = (rect(&b.elements[a], t), rect(&b.elements[c], t)) else {
continue;
};
if (ra.0 + ra.2).min(rb.0 + rb.2) - ra.0.max(rb.0) > 0
&& (ra.1 + ra.3).min(rb.1 + rb.3) - ra.1.max(rb.1) > 0 { n += 1 }
&& (ra.1 + ra.3).min(rb.1 + rb.3) - ra.1.max(rb.1) > 0
{
n += 1
}
}
}
n
}
fn main() {
let path = std::env::args().nth(1).expect("usage: tie_cost_over_time <pak>");
let path = std::env::args()
.nth(1)
.expect("usage: tie_cost_over_time <pak>");
let ar = pak::PakArchive::open(&path).expect("open pak");
println!("# tied pairs that could cost a pixel, over time — {path}\n");
for (i, e) in ar.entries().to_vec().iter().enumerate() {
let Ok(by) = ar.read(e) else { continue };
let Some(b) = ui_layout::parse_build(&by) else { continue };
let keys: Vec<u32> = b.elements.iter()
.map(|el| ui_layout::sprite_layer_key(&b, &by, el).unwrap_or(u32::MAX)).collect();
let mut ts: Vec<u32> = b.elements.iter()
.flat_map(|el| el.keyframes.iter().filter_map(|k| k.time)).collect();
ts.sort_unstable(); ts.dedup();
if ts.len() < 2 { continue }
let Some(b) = ui_layout::parse_build(&by) else {
continue;
};
let keys: Vec<u32> = b
.elements
.iter()
.map(|el| ui_layout::sprite_layer_key(&b, &by, el).unwrap_or(u32::MAX))
.collect();
let mut ts: Vec<u32> = b
.elements
.iter()
.flat_map(|el| el.keyframes.iter().filter_map(|k| k.time))
.collect();
ts.sort_unstable();
ts.dedup();
if ts.len() < 2 {
continue;
}
let last = *ts.last().unwrap();
// sample every keyframe time AND every midpoint between them
let mut samples: Vec<u32> = ts.clone();
for w in ts.windows(2) { samples.push(w[0] + (w[1] - w[0]) / 2); }
samples.sort_unstable(); samples.dedup();
let counts: Vec<(u32, usize)> =
samples.iter().map(|&t| (t, live_pairs(&b, &by, &keys, t))).collect();
for w in ts.windows(2) {
samples.push(w[0] + (w[1] - w[0]) / 2);
}
samples.sort_unstable();
samples.dedup();
let counts: Vec<(u32, usize)> = samples
.iter()
.map(|&t| (t, live_pairs(&b, &by, &keys, t)))
.collect();
let peak = counts.iter().map(|&(_, n)| n).max().unwrap_or(0);
if peak == 0 { continue }
let Some((lo, hi)) = b.settle_window() else { continue };
if peak == 0 {
continue;
}
let Some((lo, hi)) = b.settle_window() else {
continue;
};
let st = b.settle_time().unwrap();
let at_settle = live_pairs(&b, &by, &keys, st);
// the whole plateau, not just its midpoint
let plateau: Vec<usize> =
(lo..=hi).step_by(((hi - lo).max(1) / 8).max(1) as usize)
.map(|t| live_pairs(&b, &by, &keys, t)).collect();
let plateau: Vec<usize> = (lo..=hi)
.step_by(((hi - lo).max(1) / 8).max(1) as usize)
.map(|t| live_pairs(&b, &by, &keys, t))
.collect();
let pmax = plateau.iter().copied().max().unwrap_or(0);
println!("entry {i:2} peak {peak} live pair(s) over t=0..{last} \
settle window [{lo},{hi}] at t={st}: {at_settle} ACROSS THE WHOLE WINDOW: max {pmax}");
let busy: Vec<String> = counts.iter().filter(|&&(_, n)| n > 0)
.map(|&(t, n)| format!("t{t}:{n}")).collect();
if busy.len() <= 24 { println!(" live only at {}", busy.join(" ")); }
else { println!(" live at {} of {} sampled instants", busy.len(), counts.len()); }
println!(
"entry {i:2} peak {peak} live pair(s) over t=0..{last} \
settle window [{lo},{hi}] at t={st}: {at_settle} ACROSS THE WHOLE WINDOW: max {pmax}"
);
let busy: Vec<String> = counts
.iter()
.filter(|&&(_, n)| n > 0)
.map(|&(t, n)| format!("t{t}:{n}"))
.collect();
if busy.len() <= 24 {
println!(" live only at {}", busy.join(" "));
} else {
println!(
" live at {} of {} sampled instants",
busy.len(),
counts.len()
);
}
}
}

View File

@@ -23,13 +23,21 @@ fn main() {
let src = DirectorySource::new(std::path::PathBuf::from(&disc));
let code = VoiceLang::English.code_pub();
let tpak = src.open_pak("dat/tables.pak").expect("tables.pak");
let manifest = tpak.entries().iter()
let manifest = tpak
.entries()
.iter()
.find_map(|e| tpak.read(e).ok().filter(|b| movie_manifest::is_manifest(b)))
.expect("manifest");
let marker = format!("{code}\\Movie\\VOICE_ADV.slb");
let registry = tpak.entries().iter().find_map(|e| {
tpak.read(e).ok().filter(|b| b.windows(marker.len()).any(|w| w == marker.as_bytes()))
}).expect("registry");
let registry = tpak
.entries()
.iter()
.find_map(|e| {
tpak.read(e)
.ok()
.filter(|b| b.windows(marker.len()).any(|w| w == marker.as_bytes()))
})
.expect("registry");
let ids = movie_voice::registry_voice_ids(&registry);
let stoc = src.read_file("dat/sound.pak").expect("sound.pak");
let entries = PakArchive::parse_toc(&stoc).expect("toc");
@@ -37,23 +45,47 @@ fn main() {
let (mut same, mut grew, mut extra, mut skip) = (0, 0, 0, 0);
for m in movie_manifest::parse(&manifest) {
let movie = m.movie;
let Some(token) = movie_manifest::voice_token(&manifest, &movie) else { skip += 1; continue };
let Some(&id) = ids.get(&token) else { skip += 1; continue };
let Some(anchor) = ["Movie","etc","Voice"].iter().find_map(|dir| {
let Some(token) = movie_manifest::voice_token(&manifest, &movie) else {
skip += 1;
continue;
};
let Some(&id) = ids.get(&token) else {
skip += 1;
continue;
};
let Some(anchor) = ["Movie", "etc", "Voice"].iter().find_map(|dir| {
let h = name_hash(&format!("{code}\\{dir}\\{token}.slb"));
entries.binary_search_by_key(&h, |e| e.name_hash).ok().map(|i| entries[i].offset as u64)
}) else { skip += 1; continue };
let win_start = anchor.saturating_sub(2*1024*1024) & !3;
let Ok(window) = src.read_segment_range("dat/sound", win_start, 8*1024*1024) else { skip += 1; continue };
let Some(end_local) = movie_voice::find_descriptor(&window, id) else { skip += 1; continue };
entries
.binary_search_by_key(&h, |e| e.name_hash)
.ok()
.map(|i| entries[i].offset as u64)
}) else {
skip += 1;
continue;
};
let win_start = anchor.saturating_sub(2 * 1024 * 1024) & !3;
let Ok(window) = src.read_segment_range("dat/sound", win_start, 8 * 1024 * 1024) else {
skip += 1;
continue;
};
let Some(end_local) = movie_voice::find_descriptor(&window, id) else {
skip += 1;
continue;
};
let end = win_start + end_local as u64;
let cand = movie_voice::find_descriptor(&window, id.wrapping_sub(1))
.or_else(|| movie_voice::find_descriptor_before(&window, end_local))
.map(|o| win_start + o as u64)
.filter(|&s| s < end);
let today = cand.filter(|&s| end - s < 1_500_000).unwrap_or(anchor);
let Some(proposed) = cand else { skip += 1; continue };
if today == proposed { same += 1; continue }
let Some(proposed) = cand else {
skip += 1;
continue;
};
if today == proposed {
same += 1;
continue;
}
let sizes = |s: u64| -> Vec<usize> {
src.read_segment_range("dat/sound", s, (end - s) as usize)
.map(|b| slb::to_xma_riffs(&b).iter().map(|r| r.len() - 60).collect())
@@ -62,9 +94,15 @@ fn main() {
let (a, b) = (sizes(today), sizes(proposed));
let tail_same = a.len() == b.len() && a.iter().skip(1).eq(b.iter().skip(1));
let verdict = if a.len() == b.len() && tail_same && b[0] > a[0] {
grew += 1; "first chunk GREW, tail identical"
} else if b.len() > a.len() { extra += 1; "EXTRA leading chunks" }
else { extra += 1; "changed otherwise" };
grew += 1;
"first chunk GREW, tail identical"
} else if b.len() > a.len() {
extra += 1;
"EXTRA leading chunks"
} else {
extra += 1;
"changed otherwise"
};
println!("{movie:10} today {a:?}\n{:10} prop {b:?} {verdict}", "");
}
println!("\nunchanged {same} fixed-cleanly {grew} would-break {extra} skipped {skip}");

View File

@@ -12,39 +12,60 @@
//!
//! cargo run -p sylpheed-formats --example voice_region_chunk_census
use std::collections::BTreeMap;
use sylpheed_formats::media::{self, DirectorySource, DiscSource};
use sylpheed_formats::movie_manifest;
use sylpheed_formats::slb::{self, VoiceLang};
use std::collections::BTreeMap;
fn main() {
let disc = std::env::var("SYLPHEED_DISC").expect("SYLPHEED_DISC");
let src = DirectorySource::new(std::path::PathBuf::from(&disc));
let tpak = src.open_pak("dat/tables.pak").expect("tables.pak");
let manifest = tpak
.entries().iter()
.entries()
.iter()
.find_map(|e| tpak.read(e).ok().filter(|b| movie_manifest::is_manifest(b)))
.expect("manifest");
let movies: Vec<String> = movie_manifest::parse(&manifest).into_iter().map(|m| m.movie).collect();
let movies: Vec<String> = movie_manifest::parse(&manifest)
.into_iter()
.map(|m| m.movie)
.collect();
let total = movies.len();
let mut hist: BTreeMap<usize, Vec<String>> = BTreeMap::new();
let (mut resolved, mut unresolved, mut unreadable) = (0, 0, 0);
for movie in movies {
let Some((start, end)) = media::resolve_movie_voice_region(&src, &movie, VoiceLang::English)
else { unresolved += 1; continue };
let Ok(b) = src.read_segment_range("dat/sound", start, (end - start) as usize)
else { unreadable += 1; continue };
let Some((start, end)) =
media::resolve_movie_voice_region(&src, &movie, VoiceLang::English)
else {
unresolved += 1;
continue;
};
let Ok(b) = src.read_segment_range("dat/sound", start, (end - start) as usize) else {
unreadable += 1;
continue;
};
resolved += 1;
hist.entry(slb::to_xma_riffs(&b).len()).or_default().push(movie);
hist.entry(slb::to_xma_riffs(&b).len())
.or_default()
.push(movie);
}
println!("POPULATION: {total} movies in the manifest");
println!("COVERAGE: {resolved} resolved and read, {unresolved} unresolved, {unreadable} unreadable");
println!(" {} accounted for\n", resolved + unresolved + unreadable);
println!(
" {} accounted for\n",
resolved + unresolved + unreadable
);
for (n, ms) in &hist {
println!(" {n} chunk(s): {:>3} region(s) {}", ms.len(),
ms.iter().cloned().collect::<Vec<_>>().join(" "));
println!(
" {n} chunk(s): {:>3} region(s) {}",
ms.len(),
ms.iter().cloned().collect::<Vec<_>>().join(" ")
);
}
println!("\nTHREE-CHUNK REGIONS: {}", hist.get(&3).map(|v| v.len()).unwrap_or(0));
println!(
"\nTHREE-CHUNK REGIONS: {}",
hist.get(&3).map(|v| v.len()).unwrap_or(0)
);
println!("--- END OF CENSUS (if this line is missing, the run did not finish) ---");
}

View File

@@ -66,7 +66,10 @@ fn main() {
"no leading region".to_string()
} else if hdr == Some(first_riff as usize) {
with_header += 1;
format!("BANK HEADER ({first_riff} B = {} packets exactly)", first_riff / 2048)
format!(
"BANK HEADER ({first_riff} B = {} packets exactly)",
first_riff / 2048
)
} else {
with_leading += 1;
*census.entry(first_riff as usize % 2048).or_default() += 1;

View File

@@ -24,17 +24,27 @@ const WANT: [usize; 3] = [1_294_336, 1_118_208, 1_171_456];
fn main() {
let disc = std::env::var("SYLPHEED_DISC").expect("SYLPHEED_DISC");
let src = DirectorySource::new(std::path::PathBuf::from(&disc));
for (label, start) in [("anchor (today)", ADV_ANCHOR), ("predecessor (proposed)", ADV_PRED)] {
for (label, start) in [
("anchor (today)", ADV_ANCHOR),
("predecessor (proposed)", ADV_PRED),
] {
let bytes = src
.read_segment_range("dat/sound", start, (ADV_END - start) as usize)
.expect("region");
let sizes: Vec<usize> = slb::to_xma_riffs(&bytes).iter().map(|r| r.len() - 60).collect();
let sizes: Vec<usize> = slb::to_xma_riffs(&bytes)
.iter()
.map(|r| r.len() - 60)
.collect();
let hit = sizes.len() == 3 && sizes.iter().zip(WANT.iter()).all(|(a, b)| a == b);
println!(
"{label:24} start {start} span {:>9} -> {:?}{}",
ADV_END - start,
sizes,
if hit { " <== MATCHES THE DECODER" } else { "" }
if hit {
" <== MATCHES THE DECODER"
} else {
""
}
);
}
}

View File

@@ -30,33 +30,58 @@ fn main() {
.map(|m| m.movie)
.collect();
println!("{:10} {:>8} {:>12} {:>10} {}", "movie", "chunks", "first chunk", "clip pkts", "verdict");
println!(
"{:10} {:>8} {:>12} {:>10} {}",
"movie", "chunks", "first chunk", "clip pkts", "verdict"
);
let (mut clipped, mut clean, mut skipped) = (0, 0, 0);
for movie in movies {
let Some((start, end)) = media::resolve_movie_voice_region(&src, &movie, VoiceLang::English)
else { skipped += 1; continue };
let Some((start, end)) =
media::resolve_movie_voice_region(&src, &movie, VoiceLang::English)
else {
skipped += 1;
continue;
};
// ONE read of the region plus a lead-in, then slide inside it. Re-reading
// several MB per step made this too slow to finish at all.
const LEAD: u64 = 600 * 2048;
let lead = LEAD.min(start);
let buf = match src.read_segment_range("dat/sound", start - lead, (end - (start - lead)) as usize) {
Ok(b) => b, Err(_) => { skipped += 1; continue }
let buf = match src.read_segment_range(
"dat/sound",
start - lead,
(end - (start - lead)) as usize,
) {
Ok(b) => b,
Err(_) => {
skipped += 1;
continue;
}
};
let at = |back: usize| -> Vec<Vec<u8>> {
let off = lead as usize - back * 2048;
slb::to_xma_riffs(&buf[off..])
};
let base_riffs = at(0);
if base_riffs.is_empty() { skipped += 1; continue }
if base_riffs.is_empty() {
skipped += 1;
continue;
}
let n0 = base_riffs.len();
let first0 = base_riffs[0].len() - 60;
let mut clip = 0usize;
for k in 1..=(lead as usize / 2048) {
if at(k).len() != n0 { break }
if at(k).len() != n0 {
break;
}
clip = k;
}
let verdict = if clip == 0 { clean += 1; "starts at a boundary" }
else { clipped += 1; "STARTS MID-STREAM" };
let verdict = if clip == 0 {
clean += 1;
"starts at a boundary"
} else {
clipped += 1;
"STARTS MID-STREAM"
};
println!("{movie:10} {n0:>8} {first0:>12} {clip:>10} {verdict}");
}
println!("\nclipped {clipped} clean {clean} skipped {skipped}");

View File

@@ -25,33 +25,55 @@ fn main() {
let code = VoiceLang::English.code_pub();
let tpak = src.open_pak("dat/tables.pak").expect("tables.pak");
let manifest = tpak
.entries().iter()
.entries()
.iter()
.find_map(|e| tpak.read(e).ok().filter(|b| movie_manifest::is_manifest(b)))
.expect("manifest");
let marker = format!("{code}\\Movie\\VOICE_ADV.slb");
let registry = tpak.entries().iter().find_map(|e| {
tpak.read(e).ok().filter(|b| b.windows(marker.len()).any(|w| w == marker.as_bytes()))
}).expect("registry");
let registry = tpak
.entries()
.iter()
.find_map(|e| {
tpak.read(e)
.ok()
.filter(|b| b.windows(marker.len()).any(|w| w == marker.as_bytes()))
})
.expect("registry");
let ids = movie_voice::registry_voice_ids(&registry);
let stoc = src.read_file("dat/sound.pak").expect("sound.pak");
let entries = PakArchive::parse_toc(&stoc).expect("toc");
println!("{:10} {:>6} {:>12} {:>12} {:>12} {:>10} {:>9} {}",
"movie","id","anchor","pred(id-1)","pred(before)","span","chosen","note");
println!(
"{:10} {:>6} {:>12} {:>12} {:>12} {:>10} {:>9} {}",
"movie", "id", "anchor", "pred(id-1)", "pred(before)", "span", "chosen", "note"
);
for m in movie_manifest::parse(&manifest) {
let movie = m.movie;
let Some(token) = movie_manifest::voice_token(&manifest, &movie) else { continue };
let Some(token) = movie_manifest::voice_token(&manifest, &movie) else {
continue;
};
let Some(&id) = ids.get(&token) else { continue };
let Some(anchor) = ["Movie","etc","Voice"].iter().find_map(|dir| {
let Some(anchor) = ["Movie", "etc", "Voice"].iter().find_map(|dir| {
let h = name_hash(&format!("{code}\\{dir}\\{token}.slb"));
entries.binary_search_by_key(&h, |e| e.name_hash).ok().map(|i| entries[i].offset as u64)
}) else { continue };
let win_start = anchor.saturating_sub(2*1024*1024) & !3;
let Ok(window) = src.read_segment_range("dat/sound", win_start, 8*1024*1024) else { continue };
let Some(end_local) = movie_voice::find_descriptor(&window, id) else { continue };
entries
.binary_search_by_key(&h, |e| e.name_hash)
.ok()
.map(|i| entries[i].offset as u64)
}) else {
continue;
};
let win_start = anchor.saturating_sub(2 * 1024 * 1024) & !3;
let Ok(window) = src.read_segment_range("dat/sound", win_start, 8 * 1024 * 1024) else {
continue;
};
let Some(end_local) = movie_voice::find_descriptor(&window, id) else {
continue;
};
let end = win_start + end_local as u64;
let p1 = movie_voice::find_descriptor(&window, id.wrapping_sub(1)).map(|o| win_start + o as u64);
let pb = movie_voice::find_descriptor_before(&window, end_local).map(|o| win_start + o as u64);
let p1 =
movie_voice::find_descriptor(&window, id.wrapping_sub(1)).map(|o| win_start + o as u64);
let pb =
movie_voice::find_descriptor_before(&window, end_local).map(|o| win_start + o as u64);
let cand = p1.or(pb);
// the resolver's own filter
let kept = cand.filter(|&s| s < end && end - s < 1_500_000);
@@ -62,9 +84,11 @@ fn main() {
(Some(_), Some(_)) => "predecessor kept",
(None, _) => "no predecessor found -> anchor",
};
println!("{movie:10} {id:>6} {anchor:>12} {:>12} {:>12} {span:>10} {:>9} {note}",
p1.map(|v| v.to_string()).unwrap_or("-".into()),
pb.map(|v| v.to_string()).unwrap_or("-".into()),
if chosen == anchor { "anchor" } else { "pred" });
println!(
"{movie:10} {id:>6} {anchor:>12} {:>12} {:>12} {span:>10} {:>9} {note}",
p1.map(|v| v.to_string()).unwrap_or("-".into()),
pb.map(|v| v.to_string()).unwrap_or("-".into()),
if chosen == anchor { "anchor" } else { "pred" }
);
}
}

View File

@@ -66,7 +66,11 @@ fn main() {
let ids = sylpheed_formats::movie_voice::registry_voice_ids(&registry);
let name_of: std::collections::HashMap<u32, String> =
ids.iter().map(|(n, &i)| (i, n.clone())).collect();
println!("registry: {} cue names, {} distinct ids", ids.len(), name_of.len());
println!(
"registry: {} cue names, {} distinct ids",
ids.len(),
name_of.len()
);
// The 17 regions that open with a headerless stream, from the manifest.
let movies: Vec<String> = {
@@ -76,7 +80,10 @@ fn main() {
.iter()
.find_map(|e| tpak.read(e).ok().filter(|b| movie_manifest::is_manifest(b)))
.expect("manifest");
movie_manifest::parse(&manifest).into_iter().map(|m| m.movie).collect()
movie_manifest::parse(&manifest)
.into_iter()
.map(|m| m.movie)
.collect()
};
let mut regions = Vec::new();
for m in &movies {
@@ -89,38 +96,57 @@ fn main() {
// Scan a window covering every region, with margin for cues either side.
let win_start = lo.saturating_sub(8 * 1024 * 1024) & !3;
let win_len = (hi - win_start + 8 * 1024 * 1024) as usize;
println!("scanning dat/sound {win_start}..{} ({:.1} MB)", win_start + win_len as u64,
win_len as f64 / 1e6);
println!(
"scanning dat/sound {win_start}..{} ({:.1} MB)",
win_start + win_len as u64,
win_len as f64 / 1e6
);
let buf = src
.read_segment_range("dat/sound", win_start, win_len)
.expect("stream window");
let descs = all_descriptors(&buf);
println!("{} trailer descriptors found\n", descs.len());
let named = descs.iter().filter(|(_, id)| name_of.contains_key(id)).count();
println!(" of those, {named} carry an id the registry names, {} do not\n",
descs.len() - named);
let named = descs
.iter()
.filter(|(_, id)| name_of.contains_key(id))
.count();
println!(
" of those, {named} carry an id the registry names, {} do not\n",
descs.len() - named
);
// For each stream-opening region, name the cue that OWNS the leading span:
// the cue whose [prev_desc .. desc] interval contains it.
println!("leading span -> owning cue\n");
let mut verdicts: std::collections::BTreeMap<&str, usize> = Default::default();
for (m, s, e) in &regions {
let Ok(bytes) = src.read_segment_range("dat/sound", *s, (e - s) as usize) else { continue };
let Some(fr) = bytes.windows(4).position(|w| w == b"RIFF") else { continue };
let Ok(bytes) = src.read_segment_range("dat/sound", *s, (e - s) as usize) else {
continue;
};
let Some(fr) = bytes.windows(4).position(|w| w == b"RIFF") else {
continue;
};
if fr == 0 || sylpheed_formats::slb::bank_header_len(&bytes) == Some(fr) {
continue; // bank-header case, not ours
}
let (a, b) = (*s, *s + fr as u64); // the leading span, global offsets
// Descriptors bracketing the MIDDLE of the leading span.
// Descriptors bracketing the MIDDLE of the leading span.
let mid = (a + b) / 2;
let mid_local = (mid - win_start) as usize;
let before = descs.iter().rev().find(|(o, _)| (*o as u64) < mid_local as u64);
let before = descs
.iter()
.rev()
.find(|(o, _)| (*o as u64) < mid_local as u64);
let after = descs.iter().find(|(o, _)| *o >= mid_local);
let owner = after.map(|(_, id)| *id);
let owner_name = owner
.and_then(|id| name_of.get(&id).cloned())
.unwrap_or_else(|| owner.map(|i| format!("<unnamed id {i}>")).unwrap_or("<none>".into()));
.unwrap_or_else(|| {
owner
.map(|i| format!("<unnamed id {i}>"))
.unwrap_or("<none>".into())
});
let kind = if owner_name.starts_with("VOICE_D_") {
"MISSION line"
} else if owner_name.starts_with("VOICE_") {
@@ -146,18 +172,33 @@ fn main() {
println!("\ncue span vs the 1.5 MB guard, and what the region actually starts at:\n");
let (mut over, mut under, mut over_is_stream, mut under_is_stream) = (0, 0, 0, 0);
for (m, s, e) in &regions {
let Ok(bytes) = src.read_segment_range("dat/sound", *s, (e - s) as usize) else { continue };
let Ok(bytes) = src.read_segment_range("dat/sound", *s, (e - s) as usize) else {
continue;
};
let fr = bytes.windows(4).position(|w| w == b"RIFF");
let is_stream = matches!(fr, Some(f) if f > 0
&& sylpheed_formats::slb::bank_header_len(&bytes) != Some(f));
// The true predecessor trailer for this cue, from the full descriptor list.
let end_local = (*e - win_start) as usize;
let prev = descs.iter().rev().find(|(o, _)| *o < end_local).map(|(o, _)| *o as u64 + win_start);
let prev = descs
.iter()
.rev()
.find(|(o, _)| *o < end_local)
.map(|(o, _)| *o as u64 + win_start);
let Some(prev) = prev else { continue };
let span = e - prev;
let guarded = span >= 1_500_000;
if guarded { over += 1; if is_stream { over_is_stream += 1 } }
else { under += 1; if is_stream { under_is_stream += 1 } }
if guarded {
over += 1;
if is_stream {
over_is_stream += 1
}
} else {
under += 1;
if is_stream {
under_is_stream += 1
}
}
if is_stream {
println!(
" {m:8} true cue span {span:8} B (> guard: {guarded}) region starts at {s}, \

View File

@@ -74,8 +74,10 @@ fn main() {
.expect("window");
let descs = all_descriptors(&buf);
println!("{:<14} {:>10} {:>10} {:>10} {:>7} {:>9} {:>9}",
"cue", "stream1", "stream2", "stream3", "s3/s2", "rate2", "rate3");
println!(
"{:<14} {:>10} {:>10} {:>10} {:>7} {:>9} {:>9}",
"cue", "stream1", "stream2", "stream3", "s3/s2", "rate2", "rate3"
);
let mut ratios: Vec<f64> = Vec::new();
let mut tiny = 0;
for w in descs.windows(2) {
@@ -101,8 +103,12 @@ fn main() {
println!(
"{:<14} {s1:>10} {s2:>10} {s3:>10} {r:>7.4} {:>9} {:>9}",
name.trim_start_matches("VOICE_"),
declared_rate(&riffs[1]).map(|v| v.to_string()).unwrap_or("-".into()),
declared_rate(&riffs[2]).map(|v| v.to_string()).unwrap_or("-".into()),
declared_rate(&riffs[1])
.map(|v| v.to_string())
.unwrap_or("-".into()),
declared_rate(&riffs[2])
.map(|v| v.to_string())
.unwrap_or("-".into()),
);
}
ratios.sort_by(|a, b| a.partial_cmp(b).unwrap());
@@ -113,7 +119,11 @@ fn main() {
let var = ratios.iter().map(|r| (r - mean).powi(2)).sum::<f64>() / n as f64;
println!(
" stream3/stream2 ratio: min {:.4} median {:.4} max {:.4} mean {:.4} sd {:.4}",
ratios[0], ratios[n / 2], ratios[n - 1], mean, var.sqrt()
ratios[0],
ratios[n / 2],
ratios[n - 1],
mean,
var.sqrt()
);
println!(" cues where stream 3 is less than HALF of stream 2: {tiny}");
let near = ratios.iter().filter(|r| (**r - 1.0).abs() < 0.15).count();

Some files were not shown because too many files have changed in this diff Show More