our API surface is diffed against the client's 19,572 globals

- `--surface` re-runs the comparison against the captured in-world global table on demand, and `--surface-dump` writes our raw side for any tool to join — a one-shot measurement stops drifting
- CreateFrame with an unresolvable template raises exactly as the client does, instead of handing back a bare frame; `inherits=` is one name, matched case-insensitively
- CreateFontString and CreateTexture honour their third argument — 49 corpus call sites were passing an inherit into the void
- the addon-message inbound byte map is corrected (a test had agreed with the wrong constants), and the group-loot roll template publishes under the reference's own name

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Sam
2026-08-14 19:08:30 +07:00
co-authored by Claude Fable 5
parent a6aecb98df
commit d7f4c59fa8
19 changed files with 795 additions and 121 deletions
+39 -2
View File
@@ -350,8 +350,12 @@ fn kind_method_registries(lua: &Lua, this: &Table) -> &'static [&'static str] {
/// `OnLoad` must be able to see the frame's real name, and `$parent` inside the template must
/// resolve against **the caller's** name, not the template's.
///
/// An unusable template (unknown, non-virtual, the wrong shape) is a warning on the model and a
/// perfectly usable frame — never an error that takes the frame with it.
/// An **unresolvable** template name raises `CreateFrame(): Couldn't find inherited node "%s"` and
/// creates nothing — the reference's own behaviour, byte-verified (`0x7061dd` → `luaL_error`, which
/// never returns). A template that resolves but is *unusable in some other way* — declared without
/// `virtual="true"`, or of a shape that does not fit the kind asked for — is still a warning plus a
/// working frame, because the registry lookup itself succeeded and that is the only thing the miss
/// branch tests.
fn create_frame(
lua: &Lua,
(kind, name, parent, inherits): (String, Option<Value>, Option<Value>, Option<Value>),
@@ -392,6 +396,39 @@ fn create_frame(
}
}
// ── THE TEMPLATE LOOKUP HAPPENS HERE — before anything is constructed or published ──────────
//
// `0x7061dd` calls the registry lookup `0x6ee6f0` and, on NULL, falls into
// `luaL_error(L, "CreateFrame(): Couldn't find inherited node \"%s\"")` at `0x7061ed`. That
// never returns — `luaG_errormsg`/`luaD_throw` contain no `ret` at all and end in `longjmp`, so
// the `xor eax,eax; ret` after the call is dead code. The frame is **not created**, no global
// is published, and the calling statement is abandoned; the error is ordinary Lua propagation
// and `pcall`-catchable, which is how the widget dispatcher (`0x704f10`, every handler under
// `lua_pcall`) turns it into one dead handler rather than a dead client.
//
// We used to warn and hand back a usable bare frame, and the doc above said the reference
// "creates the frame anyway". That claim is refuted at the bytes. It is the same class as 1249:
// a failure the client reports and we swallowed — and swallowing is the worse half, because the
// addon then runs on a frame with none of the art, regions or scripts it asked for and fails
// later, somewhere unrelated.
//
// Ordering is the carved part, not a detail: the miss branch precedes the synthetic-node build
// (`0x706208`) and the `name` store (`0x70622d`), so a miss leaves no partial widget and no
// orphan global behind. Doing this after construction would leave both.
if let Some(t) = template.as_deref().filter(|t| !t.is_empty()) {
let known = {
let model = lua.app_data_ref::<Model>().expect("model app_data");
let templates = model.framexml_templates.borrow();
// One name, verbatim, folded — the law `framexml::expand` resolves under.
templates.contains_key(t) || templates.keys().any(|k| k.eq_ignore_ascii_case(t))
};
if !known {
return Err(mlua::Error::runtime(format!(
"CreateFrame(): Couldn't find inherited node \"{t}\""
)));
}
}
// Create in the arena, mint the id, seed a default layout input. All under one write borrow.
let id = {
let mut model = lua.app_data_mut::<Model>().expect("model app_data");