Files
Sylpheed/crates/sylpheed-formats/examples/focus_alpha_census.rs
sylph-decoder 4c74e579a0 re: 210 focus records pulse, not 2 -- and 8 of them fail in a way that looks right
The port censused focus-record alpha over its own export -- 34 elements, 2
varying, both `ptbtn00f` -- and concluded there is nothing to fix. That is
correct and correctly scoped. This asks the same question of the whole disc.

1 130 focus records, 2 664 timed elements, 210 with a varying alpha. 202 have
`rest()` returning the PEAK, the `ui-settle-time` pathology. By pak:
PILOTLOG 116, MOVIE_THEATER 54, HANGAR_ARSENAL 30, LEADERBOARD 8, GP_TITLE 2.

So the port's 2 is right because GP_TITLE has 2. The scope was load-bearing
and was not stated as a limit -- "only 2 have a varying alpha" reads as a fact
about the format and is a fact about one pak. The pathology is concentrated in
exactly the screens a wider port reaches next.

The 8 LEADERBOARD ones are the worse mode. `py_ranking_btn01f` swings
255->127->255 with no two adjacent keyframes equal, so `rest()` falls through
to its longest-dwell rule and returns 244 -- neither the peak nor the trough.
A glow stuck at its peak is visibly wrong; one stuck at 244 of a 127..255
range looks entirely plausible and nothing reports it.

Verified rather than asserted: two hits dumped keyframe by keyframe, and a
control on `ptbtn01f`, which is genuinely constant across its cycle and is
correctly NOT flagged. `py_ranking_btn01f` also confirms the loop-length
decode independently -- its ramp ends at t=90 inside a declared 120-unit
cycle, holding bright for 30 units.

Reach stated: 210 is a floor. Focus records are matched by the `Xf.rat` name
rule, and elements with constant alpha but varying scale, rotation or
position have the same problem and are not counted.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QsEPXWVaEpyfudtR6re1Pd
2026-08-29 19:45:17 +00:00

66 lines
3.4 KiB
Rust

//! Which focus records have a VARYING alpha — disc-wide, not export-wide?
//!
//! `rest()` returns an element's last hold keyframe. For a constant-alpha
//! element that is harmless. For one that pulses it returns the PEAK, which is
//! the `ui-settle-time.md` pathology: the plate's `ptbtn00f` ramps 0→80→0 and
//! `rest()` reports 80, its maximum.
//!
//! The port censused this over its own export (34 records, 2 varying) and
//! concluded there is nothing to fix. That conclusion is only as wide as the
//! export. This asks the same question of the whole disc.
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 n_rec, mut n_elem, mut varying) = (0usize, 0usize, 0usize);
let (mut at_peak, mut mid_ramp) = (0usize, 0usize);
let mut by_pak: std::collections::BTreeMap<String, usize> = Default::default();
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 };
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 };
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 };
n_rec += 1;
for el in &lb.elements {
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 }
varying += 1;
let rest = el.rest().map(|k| k.fade >> 24).unwrap_or(0);
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 { "" }));
}
}
}
}
println!("focus records disc-wide : {n_rec}");
println!(" their timed elements : {n_elem}");
println!(" with a VARYING alpha : {varying}");
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}") }
println!("\nevery varying one:");
hits.sort(); hits.dedup();
for h in &hits { println!(" {h}") }
println!("\n({} distinct)", hits.len());
}