diff --git a/crates/sylpheed-export/examples/dialog_pairs.rs b/crates/sylpheed-export/examples/dialog_pairs.rs new file mode 100644 index 00000000..45d44e52 --- /dev/null +++ b/crates/sylpheed-export/examples/dialog_pairs.rs @@ -0,0 +1,71 @@ +//! Test the Decoder's UNTESTED reading of a residual they recorded as odd. +//! +//! `GP_DIALOG` holds 140 entries against a 70-record dialog table — a 2:1 ratio +//! that would make the id→entry join an ordering question. It does not hold: +//! adjacent pairing gives identical element-name sets on **2 of 65** pairs, +//! halves pairing on **0**. In `GP_TITLE` a language pair shares its element set +//! exactly, so identical sets are the signature there and almost nothing matches +//! here. +//! +//! The residual: the only two adjacent pairs that DO match are entries `0/1` and +//! `2/3` — and `2/3` is the DIFFICULTY build. Their plausible reading is that +//! dialog text is baked into language-specific sprites, so EN/JP entries differ +//! by construction. ⚠️ **They flagged it as untested and did not assert it**, and +//! it has a hole they named themselves: it would explain the 63 that differ and +//! leave the 2 that match needing their own explanation. +//! +//! This prints what the differences actually look like, so the reading is judged +//! against the names rather than accepted as plausible. +use sylpheed_formats::{pak, ratc, ui_layout}; +use std::collections::BTreeSet; + +fn main() { + let root = std::env::var("SYLPHEED_DISC").unwrap_or_else(|_| "/disc".into()); + let ar = pak::PakArchive::open(format!("{root}/dat/GP_DIALOG.pak")).expect("GP_DIALOG.pak"); + let sets: Vec>> = ar.entries().iter().map(|e| { + let by = ar.read(e).ok()?; + if !ratc::is_ratc(&by) { return None } + let b = ui_layout::parse_build(&by)?; + Some(b.elements.iter().map(|el| el.name.clone()).collect()) + }).collect(); + + let (mut same, mut diff, mut pairs) = (0usize, 0usize, 0usize); + let mut shown = 0; + for i in (0..sets.len().saturating_sub(1)).step_by(2) { + let (Some(a), Some(b)) = (&sets[i], &sets[i + 1]) else { continue }; + pairs += 1; + if a == b { + same += 1; + println!(" entries {i:>3}/{:<3} IDENTICAL sets, {} element(s)", i + 1, a.len()); + continue; + } + diff += 1; + if shown < 3 { + shown += 1; + let only_a: Vec<_> = a.difference(b).cloned().collect(); + let only_b: Vec<_> = b.difference(a).cloned().collect(); + println!(" entries {i:>3}/{:<3} differ: {} only-in-first, {} only-in-second", + i + 1, only_a.len(), only_b.len()); + println!(" first : {:?}", &only_a[..only_a.len().min(4)]); + println!(" second : {:?}", &only_b[..only_b.len().min(4)]); + } + } + // 🔴 THE DECISIVE DETAIL, not the impressionistic one. Two languages of one + // dialog cannot differ in BUTTON COUNT. If adjacent entries do, they are + // different dialogs and the whole adjacent-pairing premise is wrong -- which + // is a stronger statement than "the language reading is untested". + let btns = |s: &Option>| -> usize { + s.as_ref().map_or(0, |x| x.iter().filter(|n| n.contains("btn")).count()) + }; + let mut mismatched = 0; + for i in (0..sets.len().saturating_sub(1)).step_by(2) { + if sets[i].is_none() || sets[i + 1].is_none() { continue } + if btns(&sets[i]) != btns(&sets[i + 1]) { mismatched += 1 } + } + println!("\n adjacent pairs whose BUTTON COUNTS differ: {mismatched}"); + println!(" A language pair cannot. Every one of these is two different dialogs."); + println!("\n {pairs} adjacent pair(s): {same} identical, {diff} differing"); + println!(" Their reading -- text baked into language-specific sprites -- predicts"); + println!(" the differing names look SYSTEMATIC (a locale suffix, a parallel set)."); + println!(" Judge it against the names above rather than against its plausibility."); +} diff --git a/docs/port/DECISIONS.md b/docs/port/DECISIONS.md index 57405c26..bea4fb45 100644 --- a/docs/port/DECISIONS.md +++ b/docs/port/DECISIONS.md @@ -9,7 +9,7 @@ dies, which is what this file is for. -288 sections. Search this before re-deriving anything. +289 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) @@ -299,6 +299,7 @@ dies, which is what this file is for. * [DIFFICULTY is a dialog, and the count-match it weakens was one I had recorded](#difficulty-is-a-dialog-and-the-count-match-it-weakens-was-one-i-had-recorded) * [Their note about instruments applies to me more than to them](#their-note-about-instruments-applies-to-me-more-than-to-them) * [The reach I recorded as theirs closed, and re-running it with a broader filter held](#the-reach-i-recorded-as-theirs-closed-and-re-running-it-with-a-broader-filter-held) +* [Refuted: their language-sprite reading of the `GP_DIALOG` residual](#refuted-their-language-sprite-reading-of-the-gp_dialog-residual) ## P0 — the exporter, 2026-08-28 @@ -14437,3 +14438,48 @@ agreed."* produced the scan.** Agreement would have ended it; so would a challenge to the whole claim. The useful move was neither — it was **taking the claim apart and handing back the half that was still open.** + +## Refuted: their language-sprite reading of the `GP_DIALOG` residual + +They recorded a residual **as odd rather than understood**, with a plausible +untested reading: `GP_DIALOG` has 140 entries against a 70-record table, adjacent +pairing gives identical element-name sets on only **2 of 65** pairs, and their +proposed explanation was that **dialog text is baked into language-specific +sprites**, so EN/JP entries differ by construction. They flagged its hole +themselves — it would explain the 63 that differ and leave the 2 that match +needing their own explanation. + +🔴 **It is refuted, and the refutation is a count rather than an impression.** + +**26 of 65 adjacent pairs differ in BUTTON COUNT.** Two languages of one dialog +cannot: a locale changes the glyphs on a button, not how many there are. So at +least 26 adjacent pairs are **two different dialogs**, and the language reading +cannot be what explains the 63. + +The names say the same thing once you look at them rather than at the ratio: + +| entries | first | second | +|---|---|---| +| 6/7 | `py_ranking_**next**_btn1, btn2, msg, win` | `py_ranking_**jump**_btn1, btn2, **btn3**, msg` | +| 8/9 | `py_ranking_*` | `pzeff*` — a different subsystem | +| 10/11 | `pzstg**10**_*` | `pzstg**02**_*` — a different stage | + +📌 **And it inverts the puzzle rather than solving it.** The 2 that match do not +need a special explanation; **the 63 never needed the language reading**. Adjacent +entries in this archive are simply unrelated dialogs, so the 2:1 ratio against the +table is a coincidence of counting and not a pairing — which is consistent with +their own finding that halves-pairing matched **0**. + +⚠️ **What I am not claiming.** That entries `0/1` and `2/3` *are* EN/JP pairs is +**not** established by this scan. Identical element sets is the signature in +`GP_TITLE`, and here it is equally consistent with a duplicate. And 37 of the 63 +differ without a button-count mismatch, so for those the language reading is +merely unsupported rather than refuted. **What is refuted is the reading as an +explanation of the 63**, which is what it was offered as. + +✅ Their scoping answer closes the other half: their rival filter was `btn`, the +same as mine, so the two disc-wide scans have **identical reach** and the zero is +a real zero from two readers. Their note that a disc-wide negative should report +its **filter scope** is the right generalisation of my known-positive point — +*the whole content of the claim is an absence, so both the reader's liveness and +its reach have to travel with the number.*