the addon arc: the AddOns folder opens

- benilla loads real 1.12 addons from Interface/AddOns — TOC, XML and Lua through one loader, with per-addon saved variables, Bindings.xml and the lifecycle events addons expect
- both AddOns screens are in: the character-select list with per-addon enable state, and the ESC menu's in-game list
- the API surface is measured, not guessed — a corpus harness runs hundreds of real 1.12 addons and ranks exactly which globals, templates and verbs they demand
- the Lua dialect is the client's own, read off the binary — 5.0 semantics with the 1.12 additions, and UI source files are bytes, not UTF-8

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Sam
2026-08-11 18:18:31 +07:00
co-authored by Claude Fable 5
parent bfb3e3addc
commit 44ff435304
231 changed files with 31316 additions and 5594 deletions
@@ -126,3 +126,88 @@ fn gettime_starts_at_zero_and_tracks_tick() {
let t = s.eval::<f64>("return GetTime()").unwrap();
assert!((t - 0.5).abs() < 1e-6, "two 0.25s ticks = 0.5 (got {t})");
}
/// The rest of the bare globals Blizzard's own restricted scope enumerates (decision 1187).
///
/// The list is not from memory: `Blizzard_RestrictedAddOnEnvironment/RestrictedEnvironment.lua`
/// copies exactly these out of the engine, which is what distinguishes them from the table helpers
/// (`tContains`, `tInvert`, …) that FrameXML defines in Lua and we must therefore NOT supply.
///
/// The measured cost of their absence: Bagnon + BagBrother produced 94 load failures, 81 of them
/// `Cannot find a library instance of "…"`, because `strmatch` was nil so every LibStub-registering
/// library aborted before it could register.
#[test]
fn the_rest_of_the_bare_globals() {
let s = script();
s.run(
r#"
-- string family
assert(strbyte("A") == 65 and strchar(65) == "A")
-- and the Era-only names 1187 added are GONE (decision 1189): the 5.0 client has no
-- string.match/gmatch, and claiming otherwise misleads an addon that feature-detects.
assert(strmatch == nil and gmatch == nil and strrev == nil)
assert(strlenutf8 == nil and strcmputf8i == nil)
assert(securecall == nil and hooksecurefunc == nil and issecure == nil)
-- math family
assert(exp(0) == 1 and log(1) == 0 and log10(100) == 2)
assert(frexp(8) == 0.5 and ldexp(0.5, 4) == 8)
-- the trig globals are DEGREE-based, inverses included — same family as the verified
-- sin/cos, and what every addon rotation helper assumes.
assert(math.abs(tan(45) - 1) < 1e-9)
assert(math.abs(asin(1) - 90) < 1e-9)
assert(math.abs(acos(1)) < 1e-9)
assert(math.abs(atan(1) - 45) < 1e-9)
assert(math.abs(atan2(1, 0) - 90) < 1e-9)
"#,
)
.unwrap();
}
/// **The Lua 5.0 dialect a vanilla addon is written in runs on our 5.1 VM** — measured, because
/// this question has been answered three different ways from memory.
///
/// 1.12 runs Lua 5.0 (byte-confirmed in wow-5875-re: `0x811b30 = "Lua: Lua 5.0 Copyright..."`);
/// we run 5.1 via mlua's `lua51`. Decision 1188 called that "the deepest divergence and it is
/// unresolved" and told the next session to test it; 1189 replied that 0068 had already closed it.
/// Meanwhile five of our own transcribed FrameXML files carried the opposite claim in a comment —
/// *"`LUA_COMPAT_VARARG` isn't shipped"* — each citing the others as precedent, so a false fact
/// propagated by citation without anyone re-running it.
///
/// It is shipped. This asserts the five idioms vanilla addon code actually uses, so the next
/// session reads a result instead of a recollection. **The implicit `arg` table is the one that
/// matters**: it is the difference between a vanilla addon's vararg functions working and every
/// one of them erroring at runtime, which is 1188's stated redirect-the-arc risk.
#[test]
fn the_lua_5_0_dialect_vanilla_addons_are_written_in_runs_here() {
let s = script();
s.run(
r##"
-- The implicit vararg table, 5.0's spelling of what 5.1 does with `...`.
local function varargs(...) return arg.n, arg[1], arg[2] end
local n, first, second = varargs("a", "b")
assert(n == 2 and first == "a" and second == "b")
-- **The one real edge**, and it is compat mode's rule rather than a gap: `arg` is
-- synthesized only for a vararg function that does NOT also mention `...` in its body.
-- Use both spellings in one function and `arg` is nil. Vanilla addon code is uniformly
-- 5.0 and never mixes them, so this costs an addon nothing — it only means one of OUR
-- transcriptions must pick a spelling per function and stay with it.
local function mixed(...) return arg == nil and select("#", ...) or -1 end
assert(mixed(1, 2, 3) == 3)
-- `...` alone is the 5.1 spelling and is unaffected.
local function modern(...) return select("#", ...) end
assert(modern("x", "y") == 2)
-- 5.0's table/string/math spellings, all of which 5.1 renamed.
assert(table.getn({ 1, 2, 3 }) == 3)
assert(string.gfind ~= nil) -- 5.1 renamed this to string.gmatch
assert(math.mod(7, 3) == 1)
assert(7 % 3 == 1) -- and the 5.1 operator 5.0 lacks also works
"##,
)
.unwrap();
}