Before this commit, `unset SYLPHEED_DISC` did not disable the disc-backed
suites on the machine that has the disc: every `disc_root()` fell back to a
hardcoded absolute path that exists on this box. The env var looked like a
control and was not one. Same for $SYLPHEED_RES3D and $SYLPHEED_ISO.
Replace the duplicated resolvers with one `tests/common/mod.rs`:
- 17 local `disc_root()` definitions -> 1
- 7 copies of the skip macro -> 1 (`skip_without_disc!` and siblings)
- 16 hardcoded absolute paths -> 0 executable ones
(3 of those were inline in `mesh_disc.rs`, in no resolver at all,
and 2 were in `examples/`)
- `corpus_report.rs` now reports on the SAME resolver the suites use,
instead of a second copy of the logic its own comments flagged as a
drift risk.
The 17 copies had already drifted into FIVE variants, and they were not all
the same function. `movie_manifest_disc`, `movie_subtitle_disc` and `slb_disc`
honoured $SYLPHEED_DISC and nothing else, while the other 14 fell back. So one
name already meant two things -- a third instance of the shape #16 is about.
The shared helper adopts the env-only behaviour those three already had, rather
than inventing a sixth variant.
Two module docs still described the fallback after it was deleted, which is the
same defect in prose: `texture_disc` claimed "or the default dev path exists"
and `pak_idxd_disc` said "or drop it at the default dev path below". Both now
say what the code does.
Verified both ways on the machine that HAS the corpus, which is the only place
this refactor can be falsified:
A env unset -> "ABSENT -- $SYLPHEED_DISC unset; its suites self-skip"
suites=31 passed=209 failed=0 ignored=14, slowest 0.12s
B env set -> "PRESENT via $SYLPHEED_DISC" (all three corpora)
suites=31 passed=209 failed=0 ignored=14,
slowest 1235.53s (mesh_consistency_disc)
Identical tallies, opposite corpus states, ~10000x apart in wall clock. (A) is
new behaviour -- it was previously unreachable here. (B) proves nothing broke.
`just test-disc` sources `.env` (already gitignored) for the set case. Note the
quoting trap documented there: the corpus paths contain spaces, and an unquoted
`VAR=a b c` parses as "run command `b`", failing silently into ABSENT -- which
looks exactly like a working skip.
Remedy (1) (`#[ignore]` + `--ignored`) is deliberately NOT done here: (3) already
moves the mode from the filesystem into the environment, and `#[ignore]` already
carries three meanings in this directory (corpus-absent, known-failing, bare).
Overloading it a fourth time would re-create the defect.
`cargo fmt --all --check` clean; no new compiler warnings.
Refs #16
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
167 lines
6.0 KiB
Rust
167 lines
6.0 KiB
Rust
//! Does anything in the declaration entry mark a **focused** button state, or is
|
|
//! the pairing really just a naming convention?
|
|
//!
|
|
//! `mark_focused_states` pairs `pgmenu_btn00f.t32` with `pgmenu_btn00.t32` by
|
|
//! name, and the backlog has carried the obvious suspicion ever since: `kind` is
|
|
//! a flags word — `0x10` means untextured primitive, `0x4` a repeated instance,
|
|
//! `0x3002` a button record — so a focus bit would be the cheapest possible
|
|
//! answer. This sweeps every screen build on the disc and asks the question
|
|
//! exactly, rather than plausibly.
|
|
//!
|
|
//! It is written to be able to FAIL to find anything: the interesting outcome is
|
|
//! as likely to be "no bit distinguishes them" as "here is the bit", and either
|
|
//! way the number is what goes in the docs.
|
|
|
|
use std::collections::HashMap;
|
|
use std::path::{Path, PathBuf};
|
|
|
|
use sylpheed_formats::{pak::PakArchive, ratc, ui_layout};
|
|
|
|
mod common;
|
|
use common::disc_root;
|
|
|
|
const DECL_TABLE_AT: usize = 0x20;
|
|
const DECL_ENTRY: usize = 60;
|
|
|
|
fn for_each_build(root: &Path, mut f: impl FnMut(&str, &[u8])) {
|
|
let mut paks: Vec<PathBuf> = 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);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fn word_at(bundle: &[u8], index: usize, off: usize) -> Option<u32> {
|
|
let at = DECL_TABLE_AT + index * DECL_ENTRY + off;
|
|
bundle
|
|
.get(at..at + 4)
|
|
.map(|b| u32::from_be_bytes([b[0], b[1], b[2], b[3]]))
|
|
}
|
|
|
|
/// Compare every name-paired (focused, base) pair on the disc, word by word.
|
|
#[test]
|
|
fn focused_and_base_declaration_entries_are_compared_word_by_word() {
|
|
let Some(root) = disc_root() else {
|
|
eprintln!("SKIP: extracted disc not found (set SYLPHEED_DISC to enable)");
|
|
return;
|
|
};
|
|
|
|
let mut pairs = 0usize;
|
|
// For each unread word of the 60-byte entry, how often does it differ?
|
|
let mut differs: HashMap<usize, usize> = HashMap::new();
|
|
// kind bits that are set on the focused entry and clear on its base.
|
|
let mut set_on_focused: HashMap<u32, usize> = HashMap::new();
|
|
let mut kind_equal = 0usize;
|
|
let mut examples: Vec<String> = Vec::new();
|
|
|
|
for_each_build(&root, |pak, bytes| {
|
|
let Some(build) = ui_layout::parse_build(bytes) else {
|
|
return;
|
|
};
|
|
if build.from_fallback {
|
|
return;
|
|
}
|
|
let index_of: HashMap<String, usize> = build
|
|
.elements
|
|
.iter()
|
|
.enumerate()
|
|
.map(|(i, e)| (e.name.to_ascii_lowercase(), i))
|
|
.collect();
|
|
for (i, el) in build.elements.iter().enumerate() {
|
|
if !el.focused {
|
|
continue;
|
|
}
|
|
let l = el.name.to_ascii_lowercase();
|
|
let Some((stem, ext)) = l.rsplit_once('.') else {
|
|
continue;
|
|
};
|
|
let base_name = format!("{}.{}", &stem[..stem.len() - 1], ext);
|
|
let Some(&j) = index_of.get(&base_name) else {
|
|
continue;
|
|
};
|
|
pairs += 1;
|
|
let base = &build.elements[j];
|
|
if el.kind == base.kind {
|
|
kind_equal += 1;
|
|
} else {
|
|
let only_focused = el.kind & !base.kind;
|
|
for bit in 0..32 {
|
|
if only_focused & (1 << bit) != 0 {
|
|
*set_on_focused.entry(1 << bit).or_default() += 1;
|
|
}
|
|
}
|
|
}
|
|
for off in (0..DECL_ENTRY).step_by(4) {
|
|
if off < 28 {
|
|
continue; // the name
|
|
}
|
|
let (a, b) = (word_at(bytes, i, off), word_at(bytes, j, off));
|
|
if a.is_some() && a != b {
|
|
*differs.entry(off).or_default() += 1;
|
|
}
|
|
}
|
|
if examples.len() < 6 {
|
|
examples.push(format!(
|
|
"{pak}: {} kind {:#x} vs base {} kind {:#x}",
|
|
el.name, el.kind, base.name, base.kind
|
|
));
|
|
}
|
|
}
|
|
});
|
|
|
|
eprintln!("focused/base pairs on the disc: {pairs}");
|
|
eprintln!("pairs whose `kind` is IDENTICAL: {kind_equal}");
|
|
let mut bits: Vec<_> = set_on_focused.iter().collect();
|
|
bits.sort();
|
|
eprintln!("kind bits set on focused but not base: {bits:?}");
|
|
let mut d: Vec<_> = differs.iter().collect();
|
|
d.sort();
|
|
eprintln!("declaration words that ever differ (offset -> pairs): {d:?}");
|
|
for e in &examples {
|
|
eprintln!(" {e}");
|
|
}
|
|
|
|
assert!(
|
|
pairs > 0,
|
|
"no focused/base pairs found — the sweep is broken"
|
|
);
|
|
|
|
// MEASURED 2026-08-24, and asserted so the answer cannot rot back into a
|
|
// suspicion: all 54 pairs on the disc carry the SAME `kind`, no bit is ever
|
|
// set on the focused entry and clear on its base, and the only words of the
|
|
// 60-byte declaration entry that ever differ are +48 and +52 — the PIVOT,
|
|
// i.e. where the sprite sits, not what it is. So the entry does not mark a
|
|
// focused state at all, and the naming pairing is not a shortcut around a
|
|
// field that exists: there is no field.
|
|
assert_eq!(pairs, 54, "the disc has 54 name-paired focused elements");
|
|
assert_eq!(
|
|
kind_equal, pairs,
|
|
"some pair's `kind` differs from its base"
|
|
);
|
|
assert!(
|
|
set_on_focused.is_empty(),
|
|
"a kind bit distinguishes focused from base: {set_on_focused:?}"
|
|
);
|
|
let mut offs: Vec<usize> = differs.keys().copied().collect();
|
|
offs.sort();
|
|
assert_eq!(
|
|
offs,
|
|
vec![48, 52],
|
|
"a declaration word other than the pivot differs between focused and base"
|
|
);
|
|
}
|