diff --git a/crates/sylpheed-formats/examples/dialog_pair_identity.rs b/crates/sylpheed-formats/examples/dialog_pair_identity.rs new file mode 100644 index 00000000..71da3a29 --- /dev/null +++ b/crates/sylpheed-formats/examples/dialog_pair_identity.rs @@ -0,0 +1,47 @@ +//! Are `GP_DIALOG` entries 0/1 and 2/3 a LANGUAGE PAIR or a DUPLICATE? +//! +//! They are the only two adjacent pairs in that archive with identical element +//! sets; every other adjacent pair is two unrelated dialogs. Left open as +//! "untested" — identical element names are equally consistent with a language +//! pair (same layout, different glyphs baked into the textures) and with a +//! byte-for-byte duplicate. +//! +//! The bytes decide it: identical entries are a duplicate; entries that share +//! every element name but differ in payload are a language pair. +//! +//! CONTROL: entries 10/11, known to be two DIFFERENT dialogs (stage 10 vs stage +//! 02), must come out as differing — and by a lot. A comparator that cannot +//! separate two unrelated dialogs cannot judge two similar ones. +//! +//! cargo run -p sylpheed-formats --example dialog_pair_identity +use sylpheed_formats::pak::PakArchive; +use std::path::PathBuf; + +fn cmp(ar: &PakArchive, a: usize, b: usize, what: &str) { + let (Ok(x), Ok(y)) = (ar.read(&ar.entries()[a]), ar.read(&ar.entries()[b])) else { + println!("{what}: unreadable"); + return; + }; + let same_len = x.len() == y.len(); + let n = x.len().min(y.len()); + let diff = (0..n).filter(|&i| x[i] != y[i]).count(); + let first = (0..n).find(|&i| x[i] != y[i]); + println!("{what}"); + println!(" sizes {} / {} ({})", x.len(), y.len(), + if same_len { "equal" } else { "DIFFER" }); + println!(" differing bytes over the common prefix: {diff} / {n} ({:.2}%)", + 100.0 * diff as f64 / n as f64); + match first { + None if same_len => println!(" => BYTE-IDENTICAL — a duplicate"), + None => println!(" => one is a prefix of the other"), + Some(o) => println!(" => first difference at offset 0x{o:X}"), + } +} + +fn main() { + let root = PathBuf::from(std::env::var("SYLPHEED_DISC").expect("SYLPHEED_DISC")); + let ar = PakArchive::open(root.join("dat/GP_DIALOG.pak")).expect("GP_DIALOG"); + cmp(&ar, 10, 11, "CONTROL: entries 10/11 — known two different dialogs"); + cmp(&ar, 0, 1, "entries 0/1"); + cmp(&ar, 2, 3, "entries 2/3 — the DIFFICULTY build"); +} diff --git a/docs/re/REFUTED.md b/docs/re/REFUTED.md index 23193f8a..211336b8 100644 --- a/docs/re/REFUTED.md +++ b/docs/re/REFUTED.md @@ -837,3 +837,23 @@ screen. Checked against the disc, with the main menu as a control **The claim survives**, and the control confirms the ordering rule reproduces a screen whose answer is independently known. + +### Refutation attempt on `sylpheed-port`'s `BGM_103` exclusion — ✅ SURVIVES, and is stronger (2026-08-31) + +**Target:** their `audio.json` rests P6's most important value on three legs, two +of which they found to be a single disc-to-runtime comparison. The legs stand only +if the disc census **excludes alternatives** — they measured *"of 32 readable +`BGM_*` banks, exactly one carries waves of that size"*. + +**My attempt:** re-derive the exclusion from the committed census +(`data/bgm-wave-census.txt`, produced by my own tool rather than their reader). + +| | | +|---|---| +| census rows | 32 | +| banks carrying **both** 3 876 864 and 3 930 112 | **1** — `BGM_103.slb` | +| banks carrying **either** size | **1** — `BGM_103.slb` | + +**The claim survives and the exclusion is tighter than they stated**: no other bank +carries *either* wave size, not merely not both. Their third leg is genuinely +discriminating. diff --git a/docs/re/data/dialog-0-1-is-a-duplicate.txt b/docs/re/data/dialog-0-1-is-a-duplicate.txt new file mode 100644 index 00000000..c4a7d327 --- /dev/null +++ b/docs/re/data/dialog-0-1-is-a-duplicate.txt @@ -0,0 +1,37 @@ +# Are GP_DIALOG entries 0/1 and 2/3 language pairs or duplicates? 2026-08-31. +# ✅ ANSWERED, and the two cases SPLIT. +# +# They are the only two adjacent pairs in GP_DIALOG with identical element sets; +# every other adjacent pair is two unrelated dialogs +# (difficulty-is-a-dialog.txt). Identical element NAMES are equally consistent +# with a language pair and with a byte-for-byte duplicate, so it was left +# untested. The bytes decide it. +# +# ✅ CONTROL: entries 10/11 are known to be two DIFFERENT dialogs (stage 10 vs +# stage 02). They differ in 54.90 % of the common prefix and in size. A +# comparator that cannot separate two unrelated dialogs cannot judge two similar +# ones -- this one can. +# +# entries 10/11 sizes 7 072 448 / 6 788 820 54.90 % of bytes differ +# entries 0/1 sizes 59 810 / 59 810 0.00 % -- BYTE-IDENTICAL +# entries 2/3 sizes 8 136 936 / 8 124 856 2.77 % differ, first at 0x1BB +# +# ✅ ENTRIES 0/1 ARE A DUPLICATE. Same size, zero differing bytes. Not a language +# pair -- the same 59 810 bytes stored twice. +# +# 🟡 ENTRIES 2/3 -- THE DIFFICULTY BUILD -- ARE NOT. Different sizes, 2.77 % of +# bytes differing from offset 0x1BB, while sharing EVERY element name. That is +# what a language pair looks like: one layout, one element set, and a small +# fraction of the payload differing where the glyphs live. +# +# ⚠️ SUPPORTED, NOT PROVEN, and the untested step is nameable: I have not +# captured DIFFICULTY in `ja`. What is established is "two builds, same element +# names, ~97.2 % identical bytes"; that they are ENGLISH and JAPANESE rests on the +# disc's pattern of shipping screens twice per language, not on a capture of this +# screen. +# +# 📌 This partially restores a claim I withdrew. I called 2/3 "an EN/JP pair" as a +# bare assertion, and withdrew it when sylpheed-port showed adjacent GP_DIALOG +# entries are generally unrelated. The withdrawal was right -- I had no evidence +# then. This is the evidence, and it is weaker than the original phrasing: a pair +# by structure, a LANGUAGE pair by inference from a disc-wide convention.