a fresh clone gets the whole project, and the maintainer's tooling moves out

What a clone lacked: the gates went green with a thousand skipped tests and said nothing, every
login script assumed a worktree-pool slot and its probe account, the client defaulted to the
maintainer's account, a docker path and a home directory were in the code, the temp files broke
on GNU coreutils, and two generators could only run against a sibling repo.

Now the test gates print how many data-gated tests skipped and why; a scripted run logs in as
the account its checkout declares in `.probe-identity` or as the three variables name, never a
default, and the client refuses a scripted login on any other account; the event and message
tables regenerate from vendored inputs, byte for byte; the docs state the real prerequisites.

Out of the tree, into the maintainer's own tooling: the worktree pool, its hooks, the perf leg
runner, the cross-platform compile and the Vulkan container leg — a way of working, and setups
not every machine has. One hook stays: no writes into the install.

Two tests that could never run are gone with their subject: the quest-timer geometry diff
compared a copy of the window that no longer exists (and joined a chain path onto a folder, so
it always skipped), and the FrameXML flag gate now reads the stock files off the player's own
patch chain instead of an extracted folder only one machine had.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
Sam
2026-09-23 17:27:14 +07:00
co-authored by Claude Fable 5.1
parent 64dc60f52f
commit aff5203393
106 changed files with 2145 additions and 3254 deletions
+1 -1
View File
@@ -71,7 +71,7 @@ fn read_source_tree(req: &str) -> Option<String> {
///
/// It exists because a number taken from the corpus harness is meaningless without it. In a dev
/// build [`read`] prefers the SOURCE TREE, so editing an `assets/ui` file changes what a survey
/// loads **with no rebuild** — and this repo's worktrees are routinely shared by several agents at
/// loads **with no rebuild** — and a tree is routinely edited by several agents at
/// once. Three separate measurements this arc were taken across a moving tree and one of them
/// landed a wrong attribution in a decision record: 87/218 was credited to a single table when a
/// controlled A/B later put it at 75, with the other twelve belonging to a neighbour's uncommitted
@@ -1,7 +1,7 @@
//! **The reference's frame FLAGS are the test oracle** — the flag half of decision 0675's "the
//! reference file is the test oracle", and the gate for the class decision 1739 closed.
//!
//! `framexml_diff` guards the *numbers* a transcribed window carries. Nothing guarded its *flags*,
//! A window's numbers are the stock file's own now. Nothing guarded its *flags*,
//! and they went quietly wrong at scale: on 2026-08-30 a sweep found **47 frames** the reference
//! marks `toplevel` and ours did not (so no window ever came to the front), **23** the reference
//! makes mouse-interactive and ours did not (so a click on a window's own background fell through
@@ -71,11 +71,10 @@
//! can hide a divergence, never invent one — so nothing it misses turns into a false failure. It
//! would be a `KNOWN` entry's reason if any frame still needed one.
//!
//! The whole module skips cleanly with no install — `_extracted_framexml/` is a gitignored
//! Blizzard asset, like every other client-data test here.
//! The whole module skips cleanly with no install — the reference corpus is read off the
//! player's own patch chain, like every other client-data test here.
use std::collections::HashMap;
use std::path::{Path, PathBuf};
use std::collections::{HashMap, HashSet};
use benilla_ui::framexml::{self, Element, TopLevel};
@@ -232,34 +231,54 @@ const KNOWN: &[Known] = &[
// window is the reference's own now, scroll children and all.
];
/// The extracted reference FrameXML directory, or `None` when the install isn't there.
fn reference_dir() -> Option<PathBuf> {
let dir = Path::new(env!("CARGO_MANIFEST_DIR")).join("../../WoW/_extracted_framexml");
dir.is_dir().then_some(dir)
/// Every reference FrameXML document, read off the player's own patch chain in the client's
/// load order: the files `FrameXML.toc` lists, and what each `<Include>`s, each once. `None`
/// without an install. Off the chain, never an extracted folder, so the gate is armed on any
/// machine with the install rather than on the one that happened to extract it.
fn reference_documents() -> Option<Vec<framexml::ParsedDocument>> {
let data = benilla_formats::wow_data()?;
let chain = benilla_formats::open_chain(&data).ok()?;
let toc = chain.read("Interface\\FrameXML\\FrameXML.toc").ok()?;
let toc = benilla_ui::toc::Toc::parse(&benilla_ui::source::decode(&toc));
let mut queue: Vec<String> = toc
.files
.iter()
.filter(|f| f.to_ascii_lowercase().ends_with(".xml"))
.map(|f| format!("Interface\\FrameXML\\{f}"))
.collect();
let mut seen = HashSet::new();
let mut docs = Vec::new();
while !queue.is_empty() {
let path = queue.remove(0);
if !seen.insert(path.to_ascii_lowercase()) {
continue;
}
let Ok(bytes) = chain.read(&path) else {
continue;
};
// Blizzard ships a UTF-8 BOM on some of these and stray high bytes in comments; the parse
// is what matters, so read lossily rather than refusing the file.
let text = String::from_utf8_lossy(&bytes);
let Ok(doc) = framexml::parse(text.trim_start_matches('\u{feff}')) else {
continue;
};
let dir = path.rsplit_once('\\').map(|(d, _)| d).unwrap_or("");
for item in &doc.items {
if let TopLevel::Include(file) = item {
queue.push(format!("{dir}\\{file}"));
}
}
docs.push(doc);
}
Some(docs)
}
/// Every named *frame* element in the reference corpus, keyed by name — templates and instances
/// alike, nested `<Frames>` included, `$parent`-relative names excluded (they repeat across
/// templates and name nothing on their own).
fn reference_frames() -> Option<HashMap<String, Element>> {
let dir = reference_dir()?;
let mut paths: Vec<PathBuf> = std::fs::read_dir(&dir)
.ok()?
.flatten()
.map(|e| e.path())
.filter(|p| p.extension().is_some_and(|e| e.eq_ignore_ascii_case("xml")))
.collect();
paths.sort();
let mut out: HashMap<String, Element> = HashMap::new();
for path in paths {
// Blizzard ships a UTF-8 BOM on some of these and stray high bytes in comments; the parse
// is what matters, so read lossily rather than refusing the file.
let bytes = std::fs::read(&path).ok()?;
let text = String::from_utf8_lossy(&bytes);
let Ok(doc) = framexml::parse(text.trim_start_matches('\u{feff}')) else {
continue;
};
for doc in reference_documents()? {
for item in &doc.items {
if let TopLevel::Template(el) | TopLevel::Instance(el) = item {
collect_named(el, &mut out);
@@ -315,24 +334,8 @@ fn collect_nesting(
/// The reference's nesting table, built over the same corpus [`reference_frames`] reads.
fn reference_nesting() -> Option<HashMap<String, Option<String>>> {
let dir = reference_dir()?;
let mut paths: Vec<PathBuf> = std::fs::read_dir(&dir)
.ok()?
.flatten()
.map(|e| e.path())
.filter(|p| p.extension().is_some_and(|e| e.eq_ignore_ascii_case("xml")))
.collect();
paths.sort();
let mut out: HashMap<String, Option<String>> = HashMap::new();
for path in paths {
let Ok(bytes) = std::fs::read(&path) else {
continue;
};
let text = String::from_utf8_lossy(&bytes);
let Ok(doc) = framexml::parse(text.trim_start_matches('\u{feff}')) else {
continue;
};
for doc in reference_documents()? {
for item in &doc.items {
if let TopLevel::Template(el) | TopLevel::Instance(el) = item {
collect_nesting(el, None, &mut out);
@@ -436,14 +439,8 @@ fn describe(frame: &str, flag: Flag, ours: &str, theirs: &str) -> String {
/// `parent="UIParent"` back on `BlackoutWorld` and it names that.
#[test]
fn the_shipped_frames_carry_the_references_flags() {
// Two gates, because the corpus and the manifest are two assets: the extracted reference
// dir below, and the chain `load_default_ui` reads — under `WOW_DATA=` (1451) the first is
// still there and the second is not.
let _data = benilla_formats::wow_data_or_skip!();
let Some(reference) = reference_frames() else {
eprintln!("skipping: no extracted reference FrameXML (WoW/_extracted_framexml)");
return;
};
let reference = reference_frames().expect("the reference FrameXML off the player's chain");
let nesting = reference_nesting().expect("the same corpus the frames came from");
assert!(
reference.len() > 500,
@@ -1,171 +0,0 @@
//! **The reference file is the test oracle** (decision 0675) — the shared half of it.
//!
//! A transcribed window's geometry errors are invisible to every behavioural test: the frames
//! load, the buttons click, the lists populate, and only the *look* is wrong. 0675 built the
//! detector for `FriendsFrame` after two rounds of the director's eye caught what a one-number
//! diff would have caught on its first run, and closed with "for the next transcribed window,
//! write the diff test first". This is that scraper, lifted out of `friends_tests` so the next
//! window costs a call rather than a copy (`QuestTimerFrame` is the first to pay the lower price).
//!
//! Deliberately narrow: it does not try to understand the XML, only to notice that a number moved.
//! Two properties make it a guard rather than a comfort, and both are the caller's to keep —
//! **known-benign differences are an explicit list, never a pattern**, so a new difference cannot
//! hide inside a tolerance; and **it must be verified to fail** (perturb one number by 1 px and
//! watch it name the element).
use std::collections::HashMap;
use std::path::{Path, PathBuf};
/// The extracted reference FrameXML file of that name, or `None` when the install isn't there
/// (these tests skip cleanly without it — it is a gitignored Blizzard asset).
pub(super) fn reference(file: &str) -> Option<PathBuf> {
let path = Path::new(env!("CARGO_MANIFEST_DIR"))
.join("../../WoW/_extracted_framexml")
.join(file);
path.is_file().then_some(path)
}
/// Named element → the `<AbsDimension>` pairs inside it, up to the next named element.
///
/// Hand-rolled rather than a regex: this crate has no regex dependency, and adding one for a scan
/// of two files is a worse trade than twenty lines of `find`.
///
/// `$parent*` names repeat across templates (every row template has a `$parentText`), so a bare
/// name would compare one template's column against another's. Each is qualified by the template
/// it lives in — the nearest preceding real name.
fn scrape(text: &str) -> Vec<(String, Vec<(f32, f32)>)> {
const TAGS: &[&str] = &[
"<Button name=\"",
"<Frame name=\"",
"<EditBox name=\"",
"<ScrollFrame name=\"",
"<FontString name=\"",
"<Texture name=\"",
"<CheckButton name=\"",
];
// Every (offset, name) where a named element starts, in document order.
let mut marks: Vec<(usize, String)> = Vec::new();
for tag in TAGS {
let mut from = 0;
while let Some(hit) = text[from..].find(tag) {
let at = from + hit;
let name_start = at + tag.len();
let Some(len) = text[name_start..].find('"') else {
break;
};
marks.push((at, text[name_start..name_start + len].to_string()));
from = name_start + len;
}
}
marks.sort_by_key(|(at, _)| *at);
let mut owner = String::new();
marks
.iter()
.enumerate()
.map(|(i, (at, name))| {
let end = marks.get(i + 1).map_or(text.len(), |(next, _)| *next);
let key = if let Some(child) = name.strip_prefix("$parent") {
format!("{owner}/{child}")
} else {
owner = name.clone();
name.clone()
};
(key, dimensions(&text[*at..end]))
})
.collect()
}
/// Every `<AbsDimension x=".." y=".."/>` in `chunk`, in order.
fn dimensions(chunk: &str) -> Vec<(f32, f32)> {
const OPEN: &str = "<AbsDimension x=\"";
let mut out = Vec::new();
let mut rest = chunk;
while let Some(hit) = rest.find(OPEN) {
rest = &rest[hit + OPEN.len()..];
let Some((x, tail)) = rest.split_once('"') else {
break;
};
let Some(y_at) = tail.find("y=\"") else { break };
let Some((y, tail)) = tail[y_at + 3..].split_once('"') else {
break;
};
if let (Ok(x), Ok(y)) = (x.parse(), y.parse()) {
out.push((x, y));
}
rest = tail;
}
out
}
/// Assert every element benilla's `ours` file shares with the reference `reference` file carries
/// the reference's `<AbsDimension>` numbers.
///
/// - `ours` is a path under `assets/ui/`; names are matched to the reference's after stripping the
/// house `Benilla` prefix (a window whose frames keep the bare reference names — decision 0591
/// §3 — matches unchanged).
/// - `expected` lists the *deliberate* deviations by their REFERENCE name. Every entry earns a
/// comment at the call site naming its reason; that is what keeps this a guard.
/// - `min_compared` fails the test if fewer elements paired up than that, so a scrape or a naming
/// change that quietly stops comparing anything is caught rather than reported as a pass.
///
/// Elements ours has and the reference does not (our own template faces) are skipped, not failed.
pub(super) fn assert_geometry_matches(
ours: &str,
reference: &Path,
expected: &[&str],
min_compared: usize,
) {
assert_geometry_matches_text(
ours,
&std::fs::read_to_string(reference).unwrap(),
expected,
min_compared,
);
}
/// [`assert_geometry_matches`] against reference TEXT rather than a file on disk.
///
/// The split exists because not every reference file is *on* disk: `_extracted_framexml/` holds
/// FrameXML only, and a window whose specification is partly a `Blizzard_*` **addon** — the raid
/// grid is the first, decision 1549 — has to read its half straight out of the patch chain
/// (`benilla_formats::open_chain`, the same door `mpqx` uses). Reading it rather than requiring
/// someone to have extracted it first is what keeps that half of the diff ARMED instead of
/// skipped, and it writes nothing into the install.
pub(super) fn assert_geometry_matches_text(
ours: &str,
reference_text: &str,
expected: &[&str],
min_compared: usize,
) {
let ours_text = std::fs::read_to_string(
Path::new(env!("CARGO_MANIFEST_DIR"))
.join("assets/ui")
.join(ours),
)
.unwrap();
let theirs: HashMap<String, Vec<(f32, f32)>> = scrape(reference_text).into_iter().collect();
let mut compared = 0;
let mut drifted = Vec::new();
for (name, dims) in scrape(&ours_text) {
let key = name.replace("Benilla", "");
let Some(ref_dims) = theirs.get(&key) else {
continue; // ours alone (the local template faces) — nothing to compare against
};
compared += 1;
if dims != *ref_dims && !expected.contains(&key.as_str()) {
drifted.push(format!("{key}: ours {dims:?} != ref {ref_dims:?}"));
}
}
assert!(
compared >= min_compared,
"only {compared} elements of {ours} matched by name (expected >= {min_compared}) — the \
scrape or the naming broke"
);
assert!(
drifted.is_empty(),
"{ours} geometry differs from the reference FrameXML:\n {}",
drifted.join("\n ")
);
}
-4
View File
@@ -1179,10 +1179,6 @@ mod duel_tests;
#[cfg(test)]
mod enchant_confirm_tests;
/// The shared reference-geometry diff (decision 0675) every transcribed window's test calls.
#[cfg(test)]
mod framexml_diff;
/// Its FLAG twin (decision 1739): the whole-tree sweep for `toplevel`/mouse/`id` against the
/// reference, read off the loaded engine rather than off our XML.
#[cfg(test)]
@@ -181,30 +181,3 @@ fn a_world_entry_paints_a_quest_already_running() {
// gap reads 89 through the `−1`.
assert_eq!(row_text(&s, 1), "1 Min 29 Secs ");
}
/// **The reference file is the test oracle** (decision 0675). Scrapes both this window and the
/// extracted reference `QuestTimerFrame.xml` for `<AbsDimension>` pairs per named element and
/// asserts every shared element's numbers match — the guard that catches a transcription whose
/// frames all load, click and populate while every number is wrong.
///
/// Verified to fail: perturbing `QuestTimer1`'s TOP offset from -30 to -29 reports
/// `QuestTimer1: ours [(140.0, 16.0), (0.0, -29.0)] != ref [(140.0, 16.0), (0.0, -30.0)]`.
#[test]
fn the_window_geometry_matches_the_reference_framexml() {
let _data = benilla_formats::wow_data_or_skip!();
let Some(reference) =
super::framexml_diff::reference("Interface\\FrameXML\\QuestTimerFrame.xml")
else {
eprintln!("skipping: no extracted FrameXML");
return;
};
// No exemptions: this window's frames keep the reference's bare names (decision 0591 §3 — the
// manage pass and the ref's own row lookup both resolve by literal name), and every number in
// it is the reference's. If an entry ever needs to go here, it names its reason.
super::framexml_diff::assert_geometry_matches(
"Interface\\FrameXML\\QuestTimerFrame.xml",
&reference,
&[],
22,
);
}