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:
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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(" ")
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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}");
|
||||
|
||||
@@ -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});
|
||||
|
||||
@@ -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}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
""
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 ---");
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
@@ -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!();
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
""
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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)");
|
||||
|
||||
@@ -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" }
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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()?;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
@@ -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}"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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"));
|
||||
|
||||
@@ -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(", "));
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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)]
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 };
|
||||
|
||||
@@ -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()
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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 };
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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:?}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 × {
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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}");
|
||||
|
||||
@@ -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
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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(),
|
||||
};
|
||||
|
||||
@@ -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)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) ---");
|
||||
}
|
||||
|
||||
@@ -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(" "));
|
||||
}
|
||||
|
||||
@@ -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(" "));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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}")
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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}")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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) ---");
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
@@ -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 { "" }
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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!();
|
||||
}
|
||||
|
||||
@@ -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) ---");
|
||||
}
|
||||
|
||||
@@ -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%");
|
||||
}
|
||||
|
||||
@@ -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) ---");
|
||||
}
|
||||
|
||||
@@ -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 ---");
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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" }
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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)"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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}");
|
||||
}
|
||||
|
||||
@@ -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}");
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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"); }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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(®istry);
|
||||
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}");
|
||||
|
||||
@@ -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) ---");
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 {
|
||||
""
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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}");
|
||||
|
||||
@@ -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(®istry);
|
||||
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" }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,7 +66,11 @@ fn main() {
|
||||
let ids = sylpheed_formats::movie_voice::registry_voice_ids(®istry);
|
||||
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 ®ions {
|
||||
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 ®ions {
|
||||
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}, \
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user