diff --git a/crates/sylpheed-formats/examples/kind_bit0_census.rs b/crates/sylpheed-formats/examples/kind_bit0_census.rs new file mode 100644 index 00000000..89cb3c36 --- /dev/null +++ b/crates/sylpheed-formats/examples/kind_bit0_census.rs @@ -0,0 +1,52 @@ +//! Disc-wide test of one bit: does `kind & 1` mean "this element has a parent"? +//! +//! The port is blocked on what `0x3003` is, having only `0x3002` in its rule. +//! `0x3002` and `0x3003` differ in bit 0 alone, and the struct doc claims bit 0 +//! is "has a parent". That is a falsifiable claim over every element on the +//! disc, so it is tested here rather than argued from two screens. +//! +//! cargo run -p sylpheed-formats --example kind_bit0_census +use sylpheed_formats::{pak::PakArchive, ui_layout}; +use std::collections::BTreeMap; +use std::path::PathBuf; + +fn main() { + let root = PathBuf::from(std::env::var("SYLPHEED_DISC").expect("SYLPHEED_DISC")); + let mut agree = 0usize; + let mut disagree = 0usize; + let mut kinds: BTreeMap = BTreeMap::new(); + let mut kind_parent: BTreeMap<(u32, bool), usize> = BTreeMap::new(); + let mut examples: Vec = Vec::new(); + + 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().map(|x| x == "pak").unwrap_or(false)).collect(); + paks.sort(); + for p in &paks { + let Ok(ar) = PakArchive::open(p) else { continue }; + for (i, ent) in ar.entries().iter().enumerate() { + let Ok(by) = ar.read(ent) else { continue }; + let Some(b) = ui_layout::parse_build(&by) else { continue }; + for el in &b.elements { + let has_parent = el.parent.is_some(); + let bit0 = el.kind & 1 == 1; + *kinds.entry(el.kind).or_default() += 1; + *kind_parent.entry((el.kind, has_parent)).or_default() += 1; + if bit0 == has_parent { agree += 1 } else { + disagree += 1; + if examples.len() < 10 { + examples.push(format!("{} entry {i} {} kind={:#x} parent={:?}", + p.file_name().unwrap().to_string_lossy(), el.name, el.kind, el.parent)); + } + } + } + } + } + println!("kind&1 == has_parent : agree {agree} DISAGREE {disagree}"); + for e in &examples { println!(" counterexample: {e}"); } + println!("\nkind histogram (count, and how many of each have a parent):"); + for (k, n) in &kinds { + let wp = kind_parent.get(&(*k, true)).copied().unwrap_or(0); + println!(" {k:#06x} n={n:6} with parent {wp:6} without {:6}", n - wp); + } +} diff --git a/docs/re/ui-kind-bit0-is-has-parent.md b/docs/re/ui-kind-bit0-is-has-parent.md new file mode 100644 index 00000000..391dc118 --- /dev/null +++ b/docs/re/ui-kind-bit0-is-has-parent.md @@ -0,0 +1,60 @@ +# ✅ `kind` bit 0 is **"has a parent"** — so `0x3003` *is* `0x3002` + +**Question:** the port has a menu-item rule keyed on `kind == 0x3002`, and the +OPTIONS rows are `0x3003`. What is `0x3003`? + +**What the human looks at:** the OPTIONS rows move. Pass = the five settings rows +respond like main-menu rows. + +**What this does NOT cover:** bits `0x2`, `0x8`, `0x10`, and the `0x70000` bits — +none of those are decoded here. + +**Instrument:** ⟨disc⟩, every `.pak` in `dat/`. +Reproduce: `cargo run -p sylpheed-formats --example kind_bit0_census`. + +## Decoded + +`0x3002` and `0x3003` differ in **bit 0 alone**, and bit 0 is the parent flag: + +``` +kind&1 == has_parent : agree 15493 DISAGREE 0 +``` + +| kind | n | with parent | without | +|---|---|---|---| +| `0x0000` | 7459 | 0 | 7459 | +| `0x0001` | 1093 | **1093** | 0 | +| `0x0004` | 2964 | 0 | 2964 | +| `0x0005` | 282 | **282** | 0 | +| `0x3002` | 778 | 0 | 778 | +| **`0x3003`** | **192** | **192** | **0** | +| `0x73002` | 64 | 0 | 64 | +| `0x73003` | 96 | **96** | 0 | + +Every kind with bit 0 set is parented in **every** instance; every kind without +it is unparented in every instance. The flag is **exactly redundant** with the +`+32` parent field, across 15 493 elements and every bundle on the disc. + +**So `0x3003` is a `0x3002` button record that happens to be parented.** The bit +that differs carries no role information at all. + +## What this does and does not license + +It removes the *reason* to treat the OPTIONS rows differently: they are the same +record class as the main-menu buttons, distinguished only by parenting. The port +was right not to widen a kind rule on circumstantial grounds — this replaces the +circumstance with the field. + +⚠️ It does **not** decode "is a menu item". `0x3002`/`0x3003` is a record class; +that the class is what menus are built from is still the port's existing reading, +now applied consistently rather than extended. + +⚠️ **`0x73002` and `0x73003` exist too** — 160 elements with the same low bits and +an undecoded `0x70000` above them. Any mask-based rule decides about those +whether or not its author meant to. I have not looked at what the high bits mean. + +## Reach of the negative + +Bit 0 is decoded. Bits `0x2`, `0x8`, `0x10` and `0x70000` are not; the corpus +elsewhere has `0x4` (template instance) and `0x10` (pivot-sized), and warns +separately that `kind & 0x2` is **not** the blend field.