re: kind bit 0x2 is the FOCUSABLE flag -- decoded, 0 violations in 15493 entries

The declaration entry's kind word (+0x28) and its focus/nav index (+0x2C) are the
same fact twice: kind & 0x2 is set iff the focus index is >= 0. Checked over 24
UI paks and every parseable build in each -- 1062 focusable elements, 14431 not,
zero exceptions. The test is two-sided, so it would fail if any focusable element
lacked the bit or any non-focusable element carried it.

Consequence: kind == 0x3002 is not the test for a button. It catches 778 of 1062
and misses 284 (26.7 %) at 0x2, 0x2002, 0x3003, 0x73002, 0x73003 -- including
ptbtn00.rat on GP_TITLE's PRESS (A) plate, which is 0x73002. And 0x3000, 817
elements, looks like a button and is not focusable.

This is also the refutation attempt on sylpheed-port's kind census. Their claim
-- every decoration 0x0, every button 0x3002 -- is exactly right on the two
screens they checked, reproduced here independently, and fails one build over on
the title they have not run yet.

The other kind bits are reported as observed structure and explicitly not
claimed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wuu56cE8vJGTBtn1ppsk8v
This commit is contained in:
sylph-decoder
2026-08-31 06:16:09 +00:00
parent 01025ab494
commit 880a4d562b
3 changed files with 314 additions and 0 deletions

View File

@@ -0,0 +1,135 @@
//! Refutation check on `sylpheed-port`'s kind census: *"Every sprite decoration
//! on both screens is `0x0` — `ptframe1`…`ptframe4` included — and every button
//! is `0x3002`."*
//!
//! Their exporter decodes the field independently; this reads it from the other
//! side. The check is deliberately WIDER than their claim in two ways, because a
//! census that only looks where the claim looks cannot fail:
//!
//! * it covers every element, not only `.t32` sprites, so a decoration with an
//! unexpected kind cannot hide behind the word "sprite";
//! * it covers every build of `GP_TITLE`, not the two screens they checked, so
//! the claim's *reach* gets tested and not just its instances.
//!
//! It also cross-checks `kind` against the focus/nav index at `+0x2C`, which is
//! `-1` on anything that cannot take the cursor. That turns a census into a
//! decode: if the two fields agree everywhere, the bit that separates them is
//! identified rather than guessed. Run over EVERY UI pak on the disc, not just
//! `GP_TITLE`, so the claim is disc-wide.
//!
//! cargo run -p sylpheed-formats --example kind_census_five_screens
use sylpheed_formats::{pak::PakArchive, ui_layout};
use std::collections::BTreeMap;
use std::path::PathBuf;
fn suffix(n: &str) -> &str {
match n.rfind('.') { Some(i) => &n[i..], None => "(none)" }
}
fn main() {
let root = PathBuf::from(std::env::var("SYLPHEED_DISC").expect("SYLPHEED_DISC"));
let ar = PakArchive::open(root.join("dat/GP_TITLE.pak")).expect("GP_TITLE");
let n = ar.entries().len();
// kind -> suffix -> count, and the exceptions we care about by name
let mut table: BTreeMap<u32, BTreeMap<String, usize>> = BTreeMap::new();
let mut t32_nonzero: Vec<(usize, String, u32)> = Vec::new();
let mut btn_nonstd: Vec<(usize, String, u32)> = Vec::new();
let mut builds = 0usize;
for e in 0..n {
let by = match ar.read(&ar.entries()[e]) { Ok(b) => b, Err(_) => continue };
let b = match ui_layout::parse_build(&by) { Some(b) => b, None => continue };
builds += 1;
for el in &b.elements {
*table.entry(el.kind).or_default().entry(suffix(&el.name).to_string()).or_default() += 1;
if el.name.ends_with(".t32") && !el.name.contains("btn") && el.kind != 0 {
t32_nonzero.push((e, el.name.clone(), el.kind));
}
if el.name.contains("btn") && el.kind != 0x3002 {
btn_nonstd.push((e, el.name.clone(), el.kind));
}
}
}
println!("GP_TITLE: {builds} parseable builds of {n} entries\n");
println!("{:<10} {}", "kind", "elements by file suffix");
for (k, m) in &table {
let s: Vec<String> = m.iter().map(|(sfx, c)| format!("{sfx}x{c}")).collect();
println!("0x{k:<8X} {}", s.join(" "));
}
println!("\nNON-BUTTON .t32 elements with kind != 0: {}", t32_nonzero.len());
for (e, nm, k) in t32_nonzero.iter().take(30) { println!(" entry {e:>2} {nm:<24} kind 0x{k:X}"); }
println!("\n*btn* elements with kind != 0x3002: {}", btn_nonstd.len());
for (e, nm, k) in btn_nonstd.iter().take(30) { println!(" entry {e:>2} {nm:<24} kind 0x{k:X}"); }
// Is `kind` a bitfield, and does bit 0x2 mean "focusable"? The focus/nav
// index at +0x2C is -1 on everything that cannot take the cursor, so the two
// fields cross-check each other. Printed rather than asserted: this is the
// evidence for the reading, not the reading itself.
println!("\nkind vs the focus index at +0x2C (-1 = not focusable), GP_TITLE:");
// +0x2C is not in `Element`, so it is read straight out of the 60-byte
// declaration entry: table at 0x20, 0x14 = count, entry stride 60.
const AT: usize = 0x20;
const STRIDE: usize = 60;
let mut cross: BTreeMap<(u32, i32), usize> = BTreeMap::new();
for e in 0..n {
let by = match ar.read(&ar.entries()[e]) { Ok(b) => b, Err(_) => continue };
if ui_layout::parse_build(&by).is_none() { continue }
if by.len() < 0x18 { continue }
let count = u32::from_be_bytes([by[0x14], by[0x15], by[0x16], by[0x17]]) as usize;
for i in 0..count {
let at = AT + i * STRIDE;
if at + STRIDE > by.len() { break }
let kind = u32::from_be_bytes([by[at+0x28], by[at+0x29], by[at+0x2A], by[at+0x2B]]);
let foc = i32::from_be_bytes([by[at+0x2C], by[at+0x2D], by[at+0x2E], by[at+0x2F]]);
*cross.entry((kind, if foc < 0 { -1 } else { 1 })).or_default() += 1;
}
}
for ((k, f), c) in &cross {
println!(" kind 0x{k:<6X} focus {:<10} {c:>4} elements",
if *f < 0 { "= -1" } else { ">= 0" });
}
// ── the same test, every UI pak on the disc ──────────────────────────────
let mut all: BTreeMap<(u32, i32), usize> = BTreeMap::new();
let mut paks = 0usize;
let mut violations: Vec<String> = Vec::new();
let mut dir: Vec<_> = std::fs::read_dir(root.join("dat")).expect("dat")
.filter_map(|d| d.ok()).map(|d| d.path()).collect();
dir.sort();
for path in dir {
let name = path.file_name().unwrap().to_string_lossy().to_string();
if !name.ends_with(".pak") { continue }
let ar = match PakArchive::open(&path) { Ok(a) => a, Err(_) => continue };
let mut used = false;
for i in 0..ar.entries().len() {
let by = match ar.read(&ar.entries()[i]) { Ok(b) => b, Err(_) => continue };
if ui_layout::parse_build(&by).is_none() { continue }
if by.len() < 0x18 { continue }
used = true;
let count = u32::from_be_bytes([by[0x14], by[0x15], by[0x16], by[0x17]]) as usize;
for e in 0..count {
let at = AT + e * STRIDE;
if at + STRIDE > by.len() { break }
let kind = u32::from_be_bytes([by[at+0x28], by[at+0x29], by[at+0x2A], by[at+0x2B]]);
let foc = i32::from_be_bytes([by[at+0x2C], by[at+0x2D], by[at+0x2E], by[at+0x2F]]);
let f = if foc < 0 { -1 } else { 1 };
*all.entry((kind, f)).or_default() += 1;
if ((kind & 0x2) != 0) != (f > 0) {
violations.push(format!("{name} entry {i} elem {e}: kind 0x{kind:X} focus {foc}"));
}
}
}
if used { paks += 1 }
}
let total: usize = all.values().sum();
println!("\nDISC-WIDE — {paks} UI paks, {total} declaration entries");
println!("{:<12} {:>12} {:>12}", "kind", "focus = -1", "focus >= 0");
let kinds: std::collections::BTreeSet<u32> = all.keys().map(|(k, _)| *k).collect();
for k in kinds {
println!("0x{k:<10X} {:>12} {:>12}",
all.get(&(k, -1)).copied().unwrap_or(0),
all.get(&(k, 1)).copied().unwrap_or(0));
}
println!("\nHYPOTHESIS: bit 0x2 of kind == (focus index >= 0)");
println!("violations: {} of {total}", violations.len());
for v in violations.iter().take(20) { println!(" {v}"); }
}

View File

@@ -0,0 +1,89 @@
# `kind` bit 0x2 is the FOCUSABLE flag -- decoded, disc-wide. 2026-08-31.
#
# cargo run -p sylpheed-formats --example kind_census_five_screens (SYLPHEED_DISC=/disc)
#
# The declaration entry's kind word (+0x28) and its focus/nav index (+0x2C) are
# two fields nobody had cross-checked. The index is -1 on anything that cannot
# take the cursor, so if the two agree everywhere, the bit that separates them is
# IDENTIFIED rather than guessed.
#
# bit 0x2 of kind == (focus index >= 0) 0 violations in 15493 entries
#
# 24 UI paks, every parseable build in each. 1062 focusable elements, 14431 not.
#
# WHY IT MATTERS: `kind == 0x3002` is NOT the test for a button. It catches 778
# of 1062 focusable elements and MISSES 284 -- 26.7 % -- at 0x2, 0x2002, 0x3003,
# 0x73002 and 0x73003. On GP_TITLE that is `ptbtn00.rat` on the PRESS (A) plate
# (entries 2 and 3), which is 0x73002. And 0x3000 (817 elements) resembles
# 0x3002 and is NOT focusable.
#
# The low bits look like independent flags -- 0x1 with a parent, 0x4 a repeated
# template instance, 0x10 a primitive -- and 0x2000/0x3000/0x70000 like a group
# in the high half. Only bit 0x2 is decoded here; the rest is observed structure
# and is NOT claimed.
GP_TITLE: 16 parseable builds of 16 entries
kind elements by file suffix
0x0 .ratx27 .t32x102
0x1 .t32x2
0x4 .t32x8
0x10 .prmx18
0x3000 .t32x3
0x3002 .ratx16
0x73002 .ratx2
NON-BUTTON .t32 elements with kind != 0: 13
entry 4 ptlogo1.t32 kind 0x4
entry 4 ptlogo2.t32 kind 0x4
entry 4 ptlogo1.t32 kind 0x4
entry 4 ptlogo2.t32 kind 0x4
entry 4 ptlogoall_eff.t32 kind 0x3000
entry 4 ptlogoall_eff2.t32 kind 0x3000
entry 7 ptlogo1.t32 kind 0x4
entry 7 ptlogo2.t32 kind 0x4
entry 7 ptlogo1.t32 kind 0x4
entry 7 ptlogo2.t32 kind 0x4
entry 7 ptlogo_all_eff.t32 kind 0x3000
entry 10 palogo_sqex_eff.t32 kind 0x1
entry 13 palogo_sqex_eff.t32 kind 0x1
*btn* elements with kind != 0x3002: 2
entry 2 ptbtn00.rat kind 0x73002
entry 3 ptbtn00.rat kind 0x73002
kind vs the focus index at +0x2C (-1 = not focusable), GP_TITLE:
kind 0x0 focus = -1 129 elements
kind 0x1 focus = -1 2 elements
kind 0x4 focus = -1 8 elements
kind 0x10 focus = -1 18 elements
kind 0x3000 focus = -1 3 elements
kind 0x3002 focus >= 0 16 elements
kind 0x73002 focus >= 0 2 elements
DISC-WIDE — 24 UI paks, 15493 declaration entries
kind focus = -1 focus >= 0
0x0 7459 0
0x1 1093 0
0x2 0 16
0x4 2964 0
0x5 282 0
0x8 650 0
0x9 6 0
0xC 48 0
0x10 329 0
0x14 2 0
0x2002 0 16
0x3000 817 0
0x3001 10 0
0x3002 0 778
0x3003 0 192
0x3004 426 0
0x3008 72 0
0x300C 135 0
0x3010 38 0
0x73002 0 64
0x73003 0 96
HYPOTHESIS: bit 0x2 of kind == (focus index >= 0)
violations: 0 of 15493

View File

@@ -0,0 +1,90 @@
# `kind` bit `0x2` is the FOCUSABLE flag — ✅ decoded, 0 violations disc-wide
**Status:**`CONFIRMED`, classification **decoded** — the field, plus a
disc-wide check. 2026-08-31.
## The claim
In the 60-byte UI declaration entry, `+0x28` is the **kind** word and `+0x2C` is
the **focus/nav index** (`1` on anything that cannot take the cursor). They are
the same fact twice:
> **`kind & 0x2` is set if and only if the focus index is ≥ 0.**
**0 violations in 15 493 declaration entries across 24 UI paks** — every parseable
build in each. 1 062 focusable elements, 14 431 not.
[`data/kind-focus-bit-census.txt`](../data/kind-focus-bit-census.txt) ·
[`examples/kind_census_five_screens.rs`](../../../crates/sylpheed-formats/examples/kind_census_five_screens.rs)
| kind | focus `= 1` | focus `≥ 0` | | kind | focus `= 1` | focus `≥ 0` |
|---|---|---|---|---|---|---|
| `0x0` | 7 459 | 0 | | `0x2` | 0 | **16** |
| `0x1` | 1 093 | 0 | | `0x2002` | 0 | **16** |
| `0x4` | 2 964 | 0 | | `0x3002` | 0 | **778** |
| `0x5` | 282 | 0 | | `0x3003` | 0 | **192** |
| `0x8` | 650 | 0 | | `0x73002` | 0 | **64** |
| `0x9` | 6 | 0 | | `0x73003` | 0 | **96** |
| `0xC` | 48 | 0 | | | | |
| `0x10` | 329 | 0 | | | | |
| `0x14` | 2 | 0 | | | | |
| `0x3000` | **817** | 0 | | | | |
| `0x3001` | 10 | 0 | | | | |
| `0x3004` | 426 | 0 | | | | |
| `0x3008` | 72 | 0 | | | | |
| `0x300C` | 135 | 0 | | | | |
| `0x3010` | 38 | 0 | | | | |
Every value in the right-hand column has bit `0x2`; no value in the left-hand
column does.
## 🔴 So `kind == 0x3002` is not the test for a button
It catches **778 of 1 062** focusable elements and **misses 284 — 26.7 %**, at
`0x2`, `0x2002`, `0x3003`, `0x73002` and `0x73003`. And `0x3000`, which looks
like a button and appears **817** times, is **not** focusable.
**On the screens the port ships this is not hypothetical.** `GP_TITLE` entries 2
and 3 — the `PRESS Ⓐ BUTTON` plate composited over the title — declare
`ptbtn00.rat` as **`0x73002`**. An equality test drops it. So does the title's
`ptlogoall_eff.t32` / `ptlogoall_eff2.t32` at `0x3000` go the other way: an
equality test correctly leaves them out, but a `kind >= 0x3000` test would not.
## Why this is a decode and not a correlation
The two fields are independent bytes four apart in a record nobody wrote them
into together, and the test is **two-sided**: it would fail if any focusable
element lacked the bit *or* if any non-focusable element carried it. A
one-directional check ("every button has the bit") would have been satisfied by
the bit simply being common.
⚠️ **What is NOT claimed.** The other bits look like independent flags — `0x1`
where the crate reads "has a parent", `0x4` "a repeated instance of a template",
`0x10` a primitive — and `0x2000` / `0x3000` / `0x70000` like a group in the high
half. That is **observed structure, not decoded**: nothing here tests them, and
the `0x5`/`0x9`/`0xC`/`0x14`/`0x3001`/`0x300C` combinations are consistent with
flags without establishing them.
⚠️ **Reach.** Every `.pak` under `dat/` that `parse_build` accepts — 24 archives.
It says nothing about whether a *focusable* element is reachable by the cursor at
run time, only that the declaration marks it.
## Refutation attempt on `sylpheed-port` — survives in its scope, fails past it
Their `DECISIONS.md` records: *"Every sprite decoration on both screens is `0x0`
`ptframe1``ptframe4` included — and every button is `0x3002`."*
**On `GP_TITLE` entries 5 and 6, both halves are exactly right**, and this
census reproduces them independently — a third reading of the same field after
their exporter and my earlier declaration walk.
🔴 **One build over, both halves fail.** The census above is deliberately wider
than the claim in two ways, because a census that only looks where the claim
looks cannot fail it: it covers every element rather than only `.t32` sprites, and
every build rather than the two screens. Entry 4 — the **title**, which they say
they have not run yet — puts `ptlogo1`/`ptlogo2` at `0x4` and
`ptlogoall_eff`/`ptlogoall_eff2` at `0x3000`, and entries 2/3 put a button at
`0x73002`.
The claim was true of what it examined. What is refuted is its **reach**, and the
reach is what was about to be used.