fourteen settings go live, /leave holds, and /assist can swing

- mouse-look speed is cameraYawMoveSpeed and cameraPitchMoveSpeed, so pfUI's afkcam does what it asks and an addon driving the stock Interface Options window runs its load and Defaults walks to the end; the shipped feel does not move (2147)
- the eight combat-log range sliders are real settings, so BigWigs' Range plugin and DPSMate read and write them, and the Show Pet Melee Damage and periodic-spell settings genuinely silence what they gate (2147)
- the stock Sound Options window loads hidden beside ours for addons to find, and its three check-box settings are real: the listener sits at your character, emote voices can be muted, zone music can loop without its silence gap (2147)
- /assist with assistAttack set selects and starts swinging; /leave General survives a relog, a /join puts the channel back in its chat window's list, and SetGuildRecruitmentMode joins and leaves GuildRecruitment - City (2144)
- wipe, strsplit, strjoin, strtrim, strconcat and tostringall are gone: no 1.12 client has them, and the addons that call them are multi-client builds whose vanilla path raises on the real client too (2146)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
Sam
2026-09-11 20:53:36 +07:00
co-authored by Claude Fable 5.1
parent 31371641eb
commit 26fb54b402
40 changed files with 2912 additions and 631 deletions
@@ -243,3 +243,71 @@ fn the_guild_recruitment_setter_gates_its_argument_the_way_the_reference_does()
"0x4a0060 returns `xor eax,eax` — zero values, not a nil"
);
}
/// **The setter is not inert** (decision 2144). `0x49ea70` stores the latch and tail-jumps into
/// the cascade `0x49ea90` on the new value alone — `Set(1)` raises the app's cue whether or not
/// the value moved; `Set(0)` never does — and `0x4a00a4`/`0x4a00a9` fire `UPDATE_CHAT_WINDOWS`
/// on every successful call, before any cascade fires it again.
#[test]
fn the_setter_asks_for_the_cascade_on_one_and_fires_update_chat_windows() {
let mut s = script();
s.run(
r#"
n = 0
local f = CreateFrame("Frame", "GRF")
f:RegisterEvent("UPDATE_CHAT_WINDOWS")
f:SetScript("OnEvent", function() n = n + 1 end)
"#,
)
.unwrap();
assert!(!s.take_guild_recruitment_cascade(), "nothing asked yet");
// Boots at 1; a Set(1) that moves nothing still asks — the reference's store is
// unconditional and the jump reads only `ecx == 1`.
s.run("SetGuildRecruitmentMode(1)").unwrap();
assert!(s.take_guild_recruitment_cascade());
assert!(!s.take_guild_recruitment_cascade(), "drained");
assert!(
!s.take_guild_recruitment_change(),
"…and the file is not dirtied by a no-move"
);
s.run("SetGuildRecruitmentMode(0)").unwrap();
assert!(
!s.take_guild_recruitment_cascade(),
"mode 0 is the latch alone"
);
assert!(
s.take_guild_recruitment_change(),
"the file is dirtied by the move"
);
// A refused call fires nothing.
s.run("SetGuildRecruitmentMode(2)").unwrap_err();
s.tick(0.016);
assert_eq!(
s.eval::<i64>("return n").unwrap(),
2,
"one UPDATE_CHAT_WINDOWS per successful call: Set(1), Set(0); the raise fired none"
);
}
/// **A manual join or leave of `GuildRecruitment` forces the latch to 0** — `0x49ed3d`/
/// `0x49ef8f`, `call 0x49ea70(0)`. A player gesture, so it dirties the file; and mode 0 is the
/// latch alone, so it asks for no cascade.
#[test]
fn a_manual_guild_recruitment_verb_resets_the_latch() {
let mut s = script();
assert!(s.reset_guild_recruitment_mode(), "1 → 0 moved");
assert_eq!(
s.eval::<f64>("return GetGuildRecruitmentMode()").unwrap(),
0.0
);
assert!(s.take_guild_recruitment_change());
assert!(!s.take_guild_recruitment_cascade());
assert!(
!s.reset_guild_recruitment_mode(),
"already 0: nothing moved"
);
assert!(!s.take_guild_recruitment_change());
}
+1 -1
View File
@@ -1,5 +1,5 @@
//! Rust-driven tests of the Lua host: the object model, layout+size reads, show/hide + event + tick
//! firing (both RF-0025 conventions), the WoW stdlib (positional `format`, `strsplit`, `wipe`,
//! firing (both RF-0025 conventions), the WoW stdlib (positional `format`, the aliases,
//! `getglobal`), the sandbox holes, and an end-to-end extract in ZKey order.
//!
//! Split by subject; the shared `script()` fixture lives in [`common`].
@@ -119,12 +119,6 @@ fn allowed_beyond_1_12() -> HashSet<&'static str> {
"UnitAura",
"UnitIsAFK",
"UnitIsDND",
"strconcat",
"strjoin",
"strsplit",
"strtrim",
"tostringall",
"wipe",
]
.into_iter()
.collect()
+5 -20
View File
@@ -1,4 +1,4 @@
//! The WoW stdlib: positional `format`, the `getglobal`/`strsplit`/`wipe` alias layer, and the
//! The WoW stdlib: positional `format`, the `getglobal` alias layer, and the
//! sandbox holes (`loadstring` text-only, dangerous globals removed).
use super::common::script;
@@ -36,7 +36,7 @@ fn positional_format_reorders_and_mix_is_an_error() {
assert!(!mixed_ok, "mixed positional+sequential must error");
}
// ── getglobal / strsplit / wipe / the alias layer ───────────────────────────────────────────────
// ── getglobal / the alias layer ───────────────────────────────────────────────
#[test]
fn stdlib_aliases_and_helpers() {
@@ -47,17 +47,6 @@ fn stdlib_aliases_and_helpers() {
local f = CreateFrame("Frame", "GG")
assert(getglobal("GG") == f)
-- strsplit returns pieces (empty fields preserved)
local a, b, c = strsplit(",", "x,y,z")
assert(a == "x" and b == "y" and c == "z")
local e1, e2 = strsplit(",", ",tail")
assert(e1 == "" and e2 == "tail")
-- strjoin / strconcat / strtrim
assert(strjoin("-", "a", "b", "c") == "a-b-c")
assert(strconcat("a", "b", "c") == "abc")
assert(strtrim(" hi \t") == "hi")
-- the bare-global aliases
assert(strupper("ab") == "AB" and strlower("AB") == "ab")
assert(strsub("hello", 2, 3) == "el")
@@ -68,13 +57,9 @@ fn stdlib_aliases_and_helpers() {
tremove(t, 1)
assert(t[1] == 20)
-- wipe empties a table in place
local w = { 1, 2, x = 3 }
assert(wipe(w) == w and next(w) == nil)
-- tostringall
local s1, s2 = tostringall(1, true)
assert(s1 == "1" and s2 == "true")
-- and the six 2.0 names that are NOT here (decision 2146)
assert(wipe == nil and tostringall == nil)
assert(strsplit == nil and strjoin == nil and strconcat == nil and strtrim == nil)
"#,
)
.unwrap();
@@ -17,6 +17,11 @@
//! measure demand. It measures the SURFACE, and every entry in the two declared lists below is
//! where the reading of the demand got written down.
//!
//! When re-reading that demand: those corpora hold addons that *run on* 1.12, not addons written
//! only for it — most of the big ones are multi-client and pick a code path at load, and they come
//! in several versions of themselves. `scripts/api-coverage.sh`'s header has the detail; decision
//! 2146 is the case that made it matter.
//!
//! ## The reference side is a CHAIN, not 23 flat surfaces
//!
//! The reference's 23 widget registrar tables are mutually **disjoint** — a class's real surface is