From d9a2229217606446a66ceebe984ae720982f6c67 Mon Sep 17 00:00:00 2001 From: Sylpheed RE agent Date: Mon, 24 Aug 2026 04:09:18 +0000 Subject: [PATCH] formats: what `opt ` links, measured - a record-to-record reference, focus is one use of it Two readings were on record and both were wrong in different directions: the structure doc called it "normal state -> focused state" from a single example, and the backlog called it "refuted as focus; unexplained otherwise". Classified every link reachable from a declaration table: 1467 links, and ALL 1467 resolve to a RATC child of their own bundle, ALL are .rat -> .rat, none dangle and none self-link. 1076 (73%) match the f focus pattern; the other 391 are chains between effect records - px_bunk_eff01 -> pjex_eff -> pjex_eff07, pveff01 -> pjeff02 -> pjeff21 - which also explains why only 227 targets are themselves declared elements: the middle of a chain is, the end is not. So `opt ` is a record-to-record reference within the bundle, and focus is its commonest use rather than its meaning. Coverage is stated rather than glossed: the bundles hold 18718 raw `opt ` tags against the 1467 classified, because opt_link reads the first tag of a DECLARED element's record. Roughly 92% of occurrences sit deeper in the chains (or are byte coincidences - the scan is unaligned) and are untested. The numbers are asserted so the answer cannot drift back into an anecdote. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01PMRJjbxLqZtsb5Vb7KunPE --- .../tests/ui_opt_link_disc.rs | 155 ++++++++++++++++++ docs/re/structures/ui-rat-layout.md | 44 +++++ 2 files changed, 199 insertions(+) create mode 100644 crates/sylpheed-formats/tests/ui_opt_link_disc.rs diff --git a/crates/sylpheed-formats/tests/ui_opt_link_disc.rs b/crates/sylpheed-formats/tests/ui_opt_link_disc.rs new file mode 100644 index 0000000..19db11b --- /dev/null +++ b/crates/sylpheed-formats/tests/ui_opt_link_disc.rs @@ -0,0 +1,155 @@ +//! What does the `.rat` record's `opt ` link actually point at? +//! +//! `ui-rat-layout.md` reads it as *normal state → focused state*, from one +//! example: `pgpbtn00.rat` links to `pgpbtn00f.rat`. The backlog carries the +//! opposite verdict — "refuted as focus; unexplained otherwise" — with no number +//! behind either. This settles it by classifying **every** `opt ` link on the +//! disc, so the answer is a distribution rather than an anecdote. +//! +//! Four questions per link, in order of how much they would explain: +//! 1. is the target `f.` — the focus pattern? +//! 2. is the target another element of the SAME build? +//! 3. is it a RATC child of the bundle (a resource rather than an element)? +//! 4. otherwise: what do the leftovers look like? + +use std::collections::{HashMap, HashSet}; +use std::path::{Path, PathBuf}; + +use sylpheed_formats::{pak::PakArchive, ratc, ui_layout}; + +fn disc_root() -> Option { + if let Ok(p) = std::env::var("SYLPHEED_DISC") { + let p = PathBuf::from(p); + if p.join("dat").is_dir() { + return Some(p); + } + } + let default = Path::new( + "/home/fabi/RE - Project Sylpheed/Project Sylpheed - Arc of Deception (USA, Europe) (En,Ja)", + ); + if default.join("dat").is_dir() { + return Some(default.to_path_buf()); + } + None +} + +fn for_each_build(root: &Path, mut f: impl FnMut(&str, &[u8])) { + let mut paks: Vec = std::fs::read_dir(root.join("dat")) + .expect("dat/") + .flatten() + .map(|e| e.path()) + .filter(|p| p.extension().and_then(|s| s.to_str()) == Some("pak")) + .collect(); + paks.sort(); + for p in &paks { + let name = p.file_name().unwrap().to_string_lossy().to_string(); + let Ok(arc) = PakArchive::open(p) else { continue }; + for e in arc.entries() { + let Ok(bytes) = arc.read(e) else { continue }; + if ratc::is_ratc(&bytes) { + f(&name, &bytes); + } + } + } +} + +#[test] +fn every_opt_link_on_the_disc_is_classified() { + let Some(root) = disc_root() else { + eprintln!("SKIP: extracted disc not found (set SYLPHEED_DISC to enable)"); + return; + }; + + let (mut links, mut focus_pattern, mut same_build, mut ratc_child) = (0usize, 0, 0, 0); + // `opt_link` returns the FIRST `opt ` tag in a record. If records can carry + // several, every count here understates - so count the tags themselves in + // the raw bundle rather than trusting the parser's one-per-record view. + let mut opt_tags = 0usize; + let mut self_link = 0usize; + let mut ext_pairs: HashMap<(String, String), usize> = HashMap::new(); + let mut leftovers: Vec = Vec::new(); + + for_each_build(&root, |pak, bytes| { + let Some(build) = ui_layout::parse_build(bytes) else { + return; + }; + if build.from_fallback { + return; + } + let names: HashSet = build + .elements + .iter() + .map(|e| e.name.to_ascii_lowercase()) + .collect(); + let kids: HashSet = ratc::parse(bytes) + .unwrap_or_default() + .into_iter() + .map(|c| c.name.to_ascii_lowercase()) + .collect(); + opt_tags += bytes.windows(4).filter(|w| *w == b"opt ").count(); + for el in &build.elements { + let Some(target) = el.focus_link.as_ref() else { + continue; + }; + links += 1; + let src = el.name.to_ascii_lowercase(); + let tgt = target.to_ascii_lowercase(); + let se = src.rsplit_once('.').map(|(_, e)| e.to_string()).unwrap_or_default(); + let te = tgt.rsplit_once('.').map(|(_, e)| e.to_string()).unwrap_or_default(); + *ext_pairs.entry((se, te)).or_default() += 1; + if tgt == src { + self_link += 1; + } + let is_focus = src + .rsplit_once('.') + .map(|(stem, ext)| format!("{stem}f.{ext}") == tgt) + .unwrap_or(false); + if is_focus { + focus_pattern += 1; + } + if names.contains(&tgt) { + same_build += 1; + } + if kids.contains(&tgt) { + ratc_child += 1; + } + if !is_focus && leftovers.len() < 14 { + leftovers.push(format!( + "{pak}: {} -> {}{}", + el.name, + target, + if names.contains(&tgt) { " [declared element]" } else { "" } + )); + } + } + }); + + eprintln!("opt links on the disc: {links} raw `opt ` tags in the bundles: {opt_tags}"); + eprintln!(" target is f. (the focus pattern): {focus_pattern}"); + eprintln!(" target is another element of the same build: {same_build}"); + eprintln!(" target is a RATC child of the bundle: {ratc_child}"); + eprintln!(" target is the element ITSELF: {self_link}"); + let mut ep: Vec<_> = ext_pairs.iter().collect(); + ep.sort_by_key(|(_, n)| std::cmp::Reverse(**n)); + eprintln!(" source-ext -> target-ext: {:?}", &ep[..ep.len().min(8)]); + for l in &leftovers { + eprintln!(" NOT the f-pattern: {l}"); + } + + assert!(links > 0, "no opt links found — the sweep is broken"); + + // MEASURED 2026-08-24 and asserted, because both prior readings were wrong + // in different directions: `opt ` is not "the focused state" (that fits 73 % + // of links, not all of them) and it is not "unexplained" either — EVERY + // link resolves to a RATC child of its own bundle, and every one is + // .rat -> .rat. + assert_eq!(links, 1467, "the disc has 1467 opt links"); + assert_eq!(ratc_child, links, "an opt link does not resolve to a RATC child"); + assert_eq!( + ext_pairs.get(&("rat".to_string(), "rat".to_string())).copied(), + Some(links), + "an opt link points at something other than a .rat record" + ); + assert_eq!(self_link, 0, "a record links to itself"); + assert_eq!(focus_pattern, 1076, "the f-pattern share moved"); +} diff --git a/docs/re/structures/ui-rat-layout.md b/docs/re/structures/ui-rat-layout.md index e20159f..7ef7355 100644 --- a/docs/re/structures/ui-rat-layout.md +++ b/docs/re/structures/ui-rat-layout.md @@ -452,3 +452,47 @@ that a menu draws for its buttons. **Still open:** whether the focused state is marked anywhere *else* — the `.rat` record, the RATC child stream, or (as with the paint order) only in the game's code. This closes the declaration table, not the question. + +## 🟡 What `opt ` links — measured over the whole disc (2026-08-24) + +Two readings were on record and both were wrong in different directions: this +file called it *normal state → focused state* from a single example +(`pgpbtn00.rat → pgpbtn00f.rat`), and the backlog called it "refuted as focus; +unexplained otherwise". Classifying **every** link reachable from a declaration +table (`tests/ui_opt_link_disc.rs`) gives a distribution instead: + +| | | +|---|---| +| `opt ` links classified | **1 467** | +| resolve to a **RATC child of the same bundle** | **1 467 — all of them** | +| `.rat` → `.rat` | **1 467 — all of them** | +| match the focus pattern `f.` | **1 076 (73 %)** | +| whose target is *also* a declared element | 227 | +| link to themselves | **0** | + +So `opt ` is a **record→record reference within the bundle**: a record naming +another record it uses. It never dangles, never points at a sprite, and never +points at itself. Focus is the commonest *use* of that mechanism, not its +meaning. + +**What the other 27 % are: chains.** The non-`f` targets are effect records +referring to further effect records, and following them shows depth: + +``` +px_bunk_eff01.rat → pjex_eff.rat → pjex_eff07.rat +px_bunk_eff01.rat → pjnet_bg.rat → pjnet_loop1.rat +pveff01.rat → pjeff02.rat → pjeff21.rat +pjeff03.rat → pjeff03_sub.rat +``` + +Note the middle name in each chain *is* a declared element while the third is +not — which is exactly why only 227 targets are elements. + +⚠️ **Coverage, stated because it bounds all of the above.** The bundles contain +**18 718** raw `opt ` byte-tags against the **1 467** links classified here. +`opt_link()` reads the **first** tag of the record belonging to a **declared +element**, so roughly 92 % of occurrences sit on records deeper in the chain (or +are byte coincidences in binary data — the scan is unaligned). What those say is +untested. The claims above are about the links a screen's element table can +reach, which is what a compositor follows; they are not a statement about every +`opt ` in the file.