//! Control for the new public accessor `ui_layout::sprite_blend_additive`. //! //! `blend_vs_t8ad_bit` established the field by reading the `T8aD` header inline. //! The exporter cannot do that — `Element` exposed nothing at `+0x04`, which is //! why a blend map keyed by SCREEN NAME had to be authored, and why the Japanese //! menus were being asserted-by-omission to blend differently from the English //! ones. This checks the accessor the exporter will actually call, against the //! same 35 oracle rows, so a later refactor cannot silently change the field. //! //! cargo run -p sylpheed-formats --example blend_api_check use std::path::PathBuf; 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), ]; 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" ); 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; } // 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)) .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; } 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"); } } } } 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(); 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" ); println!("PASS"); }