From 040008244fd2fa39c3cd4eab621abf413d307131 Mon Sep 17 00:00:00 2001 From: sylph-decoder Date: Thu, 3 Sep 2026 19:19:44 +0000 Subject: [PATCH] re: decode kind bit 0 as "has a parent" -- 0x3003 IS 0x3002, parented The port is blocked on what 0x3003 is, having only 0x3002 in its menu-item rule. The two differ in bit 0 alone. Disc-wide over every .pak: kind&1 == has_parent agrees on 15493 elements with 0 counterexamples -- 192/192 for 0x3003, 778/778 for 0x3002, and the same for every other kind. The flag is exactly redundant with the +32 parent field. So the bit that differs carries no role information: 0x3003 is a 0x3002 button record that is parented. That replaces the port's circumstantial case with the field, which is what they asked for rather than a widened rule. Flags what this does NOT license: it is not a decode of "is a menu item", and 0x73002/0x73003 (160 elements) carry an undecoded 0x70000 that any mask-based rule silently decides about. Adds examples/kind_bit0_census.rs. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01Jc4pciRArGHfxGGhEbwp5t --- .../examples/kind_bit0_census.rs | 52 ++++++++++++++++ docs/re/ui-kind-bit0-is-has-parent.md | 60 +++++++++++++++++++ 2 files changed, 112 insertions(+) create mode 100644 crates/sylpheed-formats/examples/kind_bit0_census.rs create mode 100644 docs/re/ui-kind-bit0-is-has-parent.md 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.