diff --git a/crates/sylpheed-export/examples/record_population.rs b/crates/sylpheed-export/examples/record_population.rs new file mode 100644 index 00000000..920570d9 --- /dev/null +++ b/crates/sylpheed-export/examples/record_population.rs @@ -0,0 +1,48 @@ +//! Why do two "every pak, every timed record" scans disagree by 86 %? +//! +//! This port counts 1 781 timed nested records and reports `+0x08 == max t` at +//! 92.3 %. The Decoder counts 3 311 and reports 49.6 %. Both scans are described +//! the same way, so at least one of them is narrower than its own description -- +//! and the exactness figure this port has quoted repeatedly is a property of +//! whichever subset it actually walks. +//! +//! Counts the survivors at each filter, so the gap is located rather than +//! guessed at. +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 records, mut in_bounds, mut magic, mut parsed, mut timed) = (0, 0, 0, 0, 0); + for p in &paks { + 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 }; + for (_, &(o, s)) in &b.records { + records += 1; + if o + 12 > by.len() || o + s > by.len() { continue } + in_bounds += 1; + if &by[o..o + 4] != b"RATC" { continue } + magic += 1; + let Some(lb) = ui_layout::parse_build(&by[o..o + s]) else { continue }; + parsed += 1; + let maxt = lb.elements.iter() + .flat_map(|el| el.keyframes.iter().filter_map(|k| k.time)) + .max().unwrap_or(0); + if maxt == 0 { continue } + timed += 1; + } + } + } + println!(" records declared by parse_build : {records}"); + println!(" within the entry's bounds : {in_bounds}"); + println!(" carrying the RATC magic : {magic} <- {} dropped here", + in_bounds - magic); + println!(" parsing as a nested build : {parsed}"); + println!(" with at least one timed keyframe: {timed}"); +} diff --git a/crates/sylpheed-export/src/screen.rs b/crates/sylpheed-export/src/screen.rs index 003f56aa..a7a10416 100644 --- a/crates/sylpheed-export/src/screen.rs +++ b/crates/sylpheed-export/src/screen.rs @@ -122,7 +122,12 @@ pub struct Focus { /// /// Decoded by the Decoder (`07e93ce`, `docs/re/structures/ui-record-loop-length.md`, /// delivered in HANDOFF `27938aa`) and **re-run here before adoption**, with - /// their falsifier and their non-triviality control: + /// their falsifier and their non-triviality control (⚠️ the 92.3 % below is + /// "of records where the question is meaningful" -- 1 643 of the 1 781 with a + /// timed keyframe. 3 311 nested records exist; the other 1 530 have no + /// keyframe time at all, so `+0x08 == max t` is not a question there. Quoted + /// bare until 2026-09-01, which is a population-scoped statistic reported + /// without its population): /// `cargo run -p sylpheed-export --example record_loop_control`. Disc-wide /// 1 781 timed records, 92.3 % exact, 7.7 % hold, **0 declaring less than /// their own last pose**; on the eight records this port animates, seven diff --git a/docs/port/DECISIONS.md b/docs/port/DECISIONS.md index 15daa685..4250631b 100644 --- a/docs/port/DECISIONS.md +++ b/docs/port/DECISIONS.md @@ -9,7 +9,7 @@ dies, which is what this file is for. -308 sections. Search this before re-deriving anything. +309 sections. Search this before re-deriving anything. * [P0 — the exporter, 2026-08-28](#p0--the-exporter-2026-08-28) * [P1 — Godot draws the screen, 2026-08-28](#p1--godot-draws-the-screen-2026-08-28) @@ -319,6 +319,7 @@ dies, which is what this file is for. * [The backfill: 17 was 12, and 12 is now 0](#the-backfill-17-was-12-and-12-is-now-0) * [🔴 Their record layout was wrong and I had copied it — fourth relayed aside](#their-record-layout-was-wrong-and-i-had-copied-it--fourth-relayed-aside) * [🔴 My falsifier never identified the offset — the half I called a formality did](#my-falsifier-never-identified-the-offset--the-half-i-called-a-formality-did) +* [The 92.3 %-versus-49.6 % gap: same numerator, and their filter is not applied](#the-923--versus-496--gap-same-numerator-and-their-filter-is-not-applied) ## P0 — the exporter, 2026-08-28 @@ -15210,3 +15211,50 @@ regular structure usually is.** is worth keeping: **while clearing it, not while building the tool.** Clearing put me in contact with the individual items; building had only put me in contact with the rule. + +## The 92.3 %-versus-49.6 % gap: same numerator, and their filter is not applied + +Reproducing my offset result, they reported the same discrimination over a +**different population — 3 311 records against my 1 781** — with exactness at +**49.6 %** against my **92.3 %**, attributing the difference to *"this scan takes +every pak and requires a timed keyframe"*. Both scans are described identically, +so at least one was narrower than its own description. Counting my survivors at +each filter: + +| filter | survivors | +|---|---| +| records declared by `parse_build` | **3 311** | +| within the entry's bounds | 3 311 | +| carrying the `RATC` magic | 3 311 | +| parsing as a nested build | 3 311 | +| **with at least one timed keyframe** | **1 781** | + +📌 **3 311 is the count *before* the timed filter.** And the arithmetic closes it: + +``` +1643 / 1781 = 92.3 % (mine) +1643 / 3311 = 49.6 % (theirs, exactly) +``` + +**Same numerator.** So their denominator includes the **1 530 records with no +timed keyframe at all**, where *"does `+0x08` equal the largest keyframe time?"* +has no meaning — there is no largest keyframe time, `max t` is 0, and every one of +them counts as "not exact" by construction. + +🔴 **So their stated filter is not applied**, and the 49.6 % is not a weaker +version of my 92.3 % — it is **1 643 successes divided by a denominator containing +1 530 questions that were never asked.** + +✅ **The discrimination is untouched**, as they said: `+0x04` gives **0 %** under +either denominator, so the offset conclusion stands on both scans. + +⚠️ **And my number needs its own qualifier, which it did not carry.** 92.3 % is +*"of the records where the question is meaningful"*, not *"of nested records"*. +I have been quoting it bare since 2026-08-30, including into `screen.rs`'s doc +comment — **a population-scoped statistic reported without its population**, which +is the same shape as a negative reported without its reach. + +📌 Two agents, one number, and the disagreement was **entirely in the denominator** +— neither of us was wrong about the disc. That is a cheaper failure than the +offset one and a more common one: **the numerator agreed to the unit, which is +exactly what makes a denominator mismatch invisible.**