diff --git a/crates/sylpheed-formats/examples/forced_backdrop_key_source.rs b/crates/sylpheed-formats/examples/forced_backdrop_key_source.rs new file mode 100644 index 00000000..f8a5571a --- /dev/null +++ b/crates/sylpheed-formats/examples/forced_backdrop_key_source.rs @@ -0,0 +1,71 @@ +//! Of the forced instances the rule merely CONFIRMS, how many have a key READ +//! FROM THE FILE, and how many an IMPLIED key that is itself a measurement? +//! +//! `forced_backdrop_necessity.rs` asked only whether an element had *a* key, +//! collapsing `sprite_layer_key` (a `u16` read out of the `T8aD` header — decoded) +//! with `implied_layer_key` (this crate's per-name table of positions **measured +//! in the running game**). For counting whether the rule moves anything that is +//! the right question. For describing what a confirmation is *made of*, it is not: +//! "the file already settles it" and "another measurement already settles it" are +//! different claims, and a reader who sees "own key" will take the first. +//! +//! Raised by the port agent 2026-08-30. +//! +//! cargo run -p sylpheed-formats --example forced_backdrop_key_source + +use std::path::PathBuf; + +use sylpheed_formats::{pak::PakArchive, ui_layout}; + +fn main() { + let root = PathBuf::from(std::env::var("SYLPHEED_DISC").expect("SYLPHEED_DISC")); + let mut paks: Vec = std::fs::read_dir(root.join("dat")) + .expect("dat/") + .filter_map(|e| e.ok().map(|e| e.path())) + .filter(|p| p.extension().is_some_and(|x| x == "pak")) + .collect(); + paks.sort(); + + let (mut read, mut implied, mut none) = (0usize, 0usize, 0usize); + println!("# archive entry element key_source key"); + for pak in &paks { + let Ok(ar) = PakArchive::open(pak) else { continue }; + let name = pak.file_name().unwrap().to_string_lossy().to_string(); + for (i, e) in ar.entries().iter().enumerate() { + let Ok(by) = ar.read(e) else { continue }; + let Some(b) = ui_layout::parse_build(&by) else { + continue; + }; + for el in &b.elements { + if !ui_layout::forced_backdrop(&b, el) { + continue; + } + let (src, key) = match ui_layout::sprite_layer_key(&b, &by, el) { + Some(k) => { + read += 1; + ("read_T8aD", Some(k)) + } + None => match ui_layout::implied_layer_key(&el.name) { + Some(k) => { + implied += 1; + ("implied_MEASURED", Some(k)) + } + None => { + none += 1; + ("none", None) + } + }, + }; + println!( + " {name} {i} {} {src} {}", + el.name, + key.map(|k| format!("0x{k:08X}")).unwrap_or("-".into()) + ); + } + } + } + println!("\n# forced instances by key source:"); + println!("# read from the T8aD header (decoded): {read}"); + println!("# implied — this crate's MEASURED name table: {implied}"); + println!("# none — only forced_backdrop can speak: {none}"); +} diff --git a/docs/re/data/forced-backdrop-key-source.txt b/docs/re/data/forced-backdrop-key-source.txt new file mode 100644 index 00000000..d95ba03f --- /dev/null +++ b/docs/re/data/forced-backdrop-key-source.txt @@ -0,0 +1,100 @@ +# What kind of key does each of the 80 forced instances have? +# +# Produced by: cargo run -p sylpheed-formats --example forced_backdrop_key_source +# 2026-08-30, SYLPHEED_DISC=/disc, every dat/*.pak. +# +# forced_backdrop_necessity.rs collapsed sprite_layer_key (a u16 READ from the +# T8aD header -- decoded) with implied_layer_key (this crate's per-name table of +# positions MEASURED in the running game). Raised by the port agent; splitting +# them gives a stronger result than either of us stated. +# +# read from the T8aD header: 0 <-- NOT ONE, anywhere on the disc +# implied (measured): 14 10x pfbase.tbm, 4x palogo_eff0.prm +# nothing at all: 66 62 the rule decides, 4 inert +# +# archive entry element key_source key + GP_BUNK.pak 0 px_bunk_base.tbm none - + GP_BUNK.pak 2 px_bunk_base.tbm none - + GP_BUNK.pak 4 pvbase.tbm none - + GP_BUNK.pak 6 pvbase.tbm none - + GP_DEBRIEFING_PILOTLOG.pak 118 px_deb_base.tbm none - + GP_DEBRIEFING_PILOTLOG.pak 130 px_deb_base.tbm none - + GP_DEBRIEFING_PILOTLOG.pak 131 pjbgbase2.tbm none - + GP_DEBRIEFING_PILOTLOG.pak 134 px_deb_base.tbm none - + GP_DEBRIEFING_PILOTLOG.pak 150 pjbgbase2.tbm none - + GP_DEBRIEFING_PILOTLOG.pak 165 px_deb_base.tbm none - + GP_DIALOG.pak 2 pcbase.tbm none - + GP_DIALOG.pak 3 pcbase.tbm none - + GP_DIALOG.pak 9 pzeff00.prm none - + GP_DIALOG.pak 10 pzeff00.prm none - + GP_DIALOG.pak 11 pzeff00.prm none - + GP_DIALOG.pak 12 pzeff00.prm none - + GP_DIALOG.pak 13 pzeff00.prm none - + GP_DIALOG.pak 14 pzeff00.prm none - + GP_DIALOG.pak 15 pzeff00.prm none - + GP_DIALOG.pak 16 pzeff00.prm none - + GP_DIALOG.pak 17 pzeff00.prm none - + GP_DIALOG.pak 18 pzeff00.prm none - + GP_DIALOG.pak 19 pzeff00.prm none - + GP_DIALOG.pak 20 pzeff00.prm none - + GP_DIALOG.pak 21 pzeff00.prm none - + GP_DIALOG.pak 22 pzeff00.prm none - + GP_DIALOG.pak 23 pzeff00.prm none - + GP_DIALOG.pak 24 pzeff00.prm none - + GP_DIALOG.pak 25 pzeff00.prm none - + GP_DIALOG.pak 26 pzeff00.prm none - + GP_DIALOG.pak 28 pzeff00.prm none - + GP_DIALOG.pak 29 pzeff00.prm none - + GP_DIALOG.pak 30 pzeff00.prm none - + GP_DIALOG.pak 31 pzeff00.prm none - + GP_DIALOG.pak 32 pzeff00.prm none - + GP_DIALOG.pak 33 pzeff00.prm none - + GP_DIALOG.pak 34 pzeff00.prm none - + GP_DIALOG.pak 35 pzeff00.prm none - + GP_DIALOG.pak 36 pzeff00.prm none - + GP_DIALOG.pak 37 pzeff00.prm none - + GP_DIALOG.pak 38 pzeff00.prm none - + GP_DIALOG.pak 39 pzeff00.prm none - + GP_DIALOG.pak 40 pzeff00.prm none - + GP_DIALOG.pak 41 pzeff00.prm none - + GP_DIALOG.pak 86 esrb_base.prm none - + GP_DIALOG.pak 130 esrb_base.prm none - + GP_GAMEOVER.pak 4 pnbase.tbm none - + GP_GAMEOVER.pak 7 pnbase.tbm none - + GP_MISSION_SELECT.pak 3 px_mission_base.tbm none - + GP_MISSION_SELECT.pak 5 px_mission_base.tbm none - + GP_MOVIE_THEATER.pak 0 px_movie_base.tbm none - + GP_MOVIE_THEATER.pak 1 px_movie_base.tbm none - + GP_OPTIONS.pak 0 po_menu_base.tbm none - + GP_OPTIONS.pak 0 po_menu_base.tbm none - + GP_OPTIONS.pak 2 po_menu_base.tbm none - + GP_OPTIONS.pak 2 po_menu_base.tbm none - + GP_SAVE_LOAD.pak 37 pfbase.tbm implied_MEASURED 0x00000000 + GP_SAVE_LOAD.pak 37 pfbase.tbm implied_MEASURED 0x00000000 + GP_SAVE_LOAD.pak 40 pfbase.tbm implied_MEASURED 0x00000000 + GP_SAVE_LOAD.pak 40 pfbase.tbm implied_MEASURED 0x00000000 + GP_SAVE_LOAD.pak 46 pgloading_eff00.prm none - + GP_SAVE_LOAD.pak 58 pfbase.tbm implied_MEASURED 0x00000000 + GP_SAVE_LOAD.pak 69 pgloading_eff00.prm none - + GP_SAVE_LOAD.pak 78 pfbase.tbm implied_MEASURED 0x00000000 + GP_SAVE_LOAD.pak 89 px_replay_base.tbm none - + GP_SAVE_LOAD.pak 93 pfbase.tbm implied_MEASURED 0x00000000 + GP_SAVE_LOAD.pak 93 pfbase.tbm implied_MEASURED 0x00000000 + GP_SAVE_LOAD.pak 98 px_replay_base.tbm none - + GP_SAVE_LOAD.pak 99 pfbase.tbm implied_MEASURED 0x00000000 + GP_SAVE_LOAD.pak 99 pfbase.tbm implied_MEASURED 0x00000000 + GP_SYSTEM.pak 0 pqbase.tbm none - + GP_SYSTEM.pak 1 pqbase.tbm none - + GP_TITLE.pak 10 palogo_eff0.prm implied_MEASURED 0x00000000 + GP_TITLE.pak 11 palogo_eff0.prm implied_MEASURED 0x00000000 + GP_TITLE.pak 12 pgloading_eff00.prm none - + GP_TITLE.pak 13 palogo_eff0.prm implied_MEASURED 0x00000000 + GP_TITLE.pak 14 palogo_eff0.prm implied_MEASURED 0x00000000 + GP_TITLE.pak 15 pgloading_eff00.prm none - + GP_TUTORIAL.pak 0 pubase.tbm none - + GP_TUTORIAL.pak 1 pubase.tbm none - + +# forced instances by key source: +# read from the T8aD header (decoded): 0 +# implied — this crate's MEASURED name table: 14 +# none — only forced_backdrop can speak: 66 diff --git a/docs/re/structures/ui-forced-backdrop.md b/docs/re/structures/ui-forced-backdrop.md index f1064463..9a8319ed 100644 --- a/docs/re/structures/ui-forced-backdrop.md +++ b/docs/re/structures/ui-forced-backdrop.md @@ -218,14 +218,46 @@ instrument: `crates/sylpheed-formats/examples/forced_backdrop_necessity.rs`. is the rule behaving as designed: it is a fallback, and it only ever fires where nothing else can speak. +### 🔴 Not one of the 80 has a key read from the file + +The necessity probe asked whether an element had *a* key, collapsing +`sprite_layer_key` — a `u16` read out of the `T8aD` header, **decoded** — with +`implied_layer_key` — this crate's per-name table, whose doc comment says outright +*"only names whose position has actually been measured"*. The port agent pointed out +that for describing what a *confirmation is made of* those are different claims, and +splitting them gives a sharper answer than either of us had +([`../data/forced-backdrop-key-source.txt`](../data/forced-backdrop-key-source.txt)): + +| key source | instances | +|---|---| +| read from the `T8aD` header (decoded) | **0** | +| `implied_layer_key` (measured in the running game) | 14 | +| nothing at all | 66 | + +**Zero.** There is no instance anywhere on the disc where a forced element also +carries a file-read layer key — which means **this rule has never been checked +against a decoded field, because there is no case in which both can speak.** That is +not a defect of the rule; it is what a fallback for keyless elements necessarily +looks like. But it removes a check a reader would reasonably assume exists, and the +page should not have let "own key" stand for it. + +The 14 are two names: `pfbase.tbm` ×10 (`GP_SAVE_LOAD`) and `palogo_eff0.prm` ×4 +(`GP_TITLE`), both implied `0x00000000`. + The 18 that merely agree split two ways, and the distinction matters: -* **14 have a key of their own** — `pfbase.tbm` (10) and `palogo_eff0.prm` (4). Here - the sort already had the answer and the rule reproduces it. ⚠️ **This is the part - that is not independent support.** `palogo_eff0.prm` being forced first agrees - with its measured order, but its `implied_layer_key` is `0x00000000` and would - have put it first anyway — so that agreement is the rule reproducing the crate, - not the game confirming the rule. +* **14 have a key of their own, and it is an IMPLIED one** — `pfbase.tbm` (10) and + `palogo_eff0.prm` (4). ⚠️ Two questions come apart here and an earlier version of + this page ran them together: + * *Does the rule change the composite?* **No** — the sort already had the key, so + these contribute nothing to the necessity count. That much was right. + * *Does the rule get the right answer?* **Yes, and this is the only place that can + be asked.** `implied_layer_key` is a position measured in the running game, so + the rule forcing these 14 first is the rule agreeing with the **oracle**. + 🔴 So "none of the 18 is evidence for the rule in any direction" — said to the port + agent on 2026-08-30 and corrected here — **was wrong about the 14**. They are not + a file-side check, and they are not independent of the sort, but they are the + rule's *only* external corroboration and there are 14 of them. * **4 are keyless but inert** — `po_menu_base.tbm`, twice in each of two 2-element builds. *Every* element on those screens is forced, so all keys collapse to the same value and the declaration-index tie-break gives the identical order either