Answers the question the port agent asked: does the `PRESS A` plate's pulse group loop from its start, or hold at alpha 0 between cycles? It holds. A nested record is itself a RATC bundle with its own header, and that header's `+0x08` is the loop length -- the same field ui_header_time_disc already tests as an animation length at the top level. Its keyframes need not fill it, and the slack is a hold at the final pose. `ptbtn00f` is 105 units of ramp inside a 120-unit cycle, so the glow rests dark for 15 units between pulses. The five main-menu focus records fill their 120 exactly, which is what shows the slack belongs to this record rather than to the format. Disc-wide over 1 781 timed nested records: 92.3% declare exactly their last keyframe time, 7.7% declare more, and 0 declare less. That last row is the falsifier -- a cycle cannot restart before its own last pose -- and it never fires; the 7.7% is what keeps the reading from being an unfalsifiable relabelling of the keyframes. Falsification against the running game, using a pacing factor measured INDEPENDENTLY on the main menu's focus ring (declared 120 units, measured 2.177 s, factor 1.0885): to reach the corpus's four measurements of the plate pulse (2.12/2.19/2.34/2.31 s), a 105-unit period needs a factor of 1.211-1.337, which EXCLUDES the ring's; a 120-unit period needs 1.060-1.170, which CONTAINS it. Predicted 2.177 s against a measured 2.12-2.34. The two elements are in different bundles and were measured in separate runs; the only thing tying them together is that both declare 120. So the port should stop shipping 105. Its 123-vs-129 ambiguity straddled the right answer without containing it, and 129 only fitted because it was 105 + the exit_ramp_units constant it has since correctly deleted. Reach is stated: this says where a cycle ends, not which records cycle, and the TOP-level +0x08 is a different field left untouched -- every GP_TITLE entry declares 300 while its elements end at 244-269. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QsEPXWVaEpyfudtR6re1Pd
39 lines
1.8 KiB
Rust
39 lines
1.8 KiB
Rust
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();
|
|
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 };
|
|
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(" "));
|
|
}
|
|
}
|
|
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]) {
|
|
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(" "));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|