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 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Jc4pciRArGHfxGGhEbwp5t
This commit is contained in:
sylph-decoder
2026-09-03 19:19:44 +00:00
parent d4efe551e0
commit f7cc0bd283
2 changed files with 112 additions and 0 deletions

View File

@@ -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<u32, usize> = BTreeMap::new();
let mut kind_parent: BTreeMap<(u32, bool), usize> = BTreeMap::new();
let mut examples: Vec<String> = 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);
}
}

View File

@@ -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.