the world map blips are the reference's, and particles go untinted

- the world map speaks the reference's names: WorldMapTooltip is declared (six corpus addons index it; pfUI dies on it at file scope), the blackout frame takes the reference's spelling BlackoutWorld, and the four hand-written party blips become the reference's own unit-blip templates — all 44 blips as instances, each with its published $parentIcon, the name pfUI recolours group blips by class through; the 40 raid ones are declared but not positioned yet, named in the OUT list rather than faked
- the templates addons build their own buttons from are published: ItemButtonTemplate, ContainerFrameItemButtonTemplate and the bag-slot chain — most of the handler scripts were live the whole time (this client executes the reference's own container and paper-doll code off the player's patch chain), so what was missing was mostly declarations, not behaviour — the bank arm stays out and is named as the omission it is; OptionsFrameSliders becomes real, with the three Graphics rows reading it and re-reading on show, so an addon's write to a slider's bounds actually moves the slider; and the template census that had ranked these at "zero demand" learns to see a local bound to a literal — its table went from 3 rows to 7
- the bindings answer in 1.12's own shapes: Button:IsEnabled() answers the NUMBER 1 or 0 (0 is truthy in Lua, so the reference's own == 0 tests got false forever, and three of our files had hand-worked around the divergence rather than fixed it); the constructors' string arguments are four shapes, not one rule — pfUI passes a Button where a template name goes and the reference constructs the FontString and ignores it; SetInventoryItem returns THREE values, the reference's own paper doll destructuring repairCost; UnitIsVisible is object presence and nothing else (57 bytes, one branch, no distance — an out-of-range party member is exists-yes, visible-nil); the tapped pair reads the two dynamic-flags bits, and its repaint wire fires UNIT_FACTION on the tap bit — the half a passing predicate test never sees; UnitIsPartyLeader is two ORed legs covering disjoint failures (a held stranger leading their own party, and an out-of-range member with no object to read a flag from); and the four nameplate verbs take no argument and return zero values
- the ghost's wisps compose onto its body instead of over it — the four emitters drew at full strength over a body at alpha 0.5, and now inherit the instance alpha down the attachment chain the way the reference recomposes it per frame; the residual we had parked ("the wisps should inherit the tint too") is REFUTED at the bytes: a particle batch commits hardcoded white RGB, and even that never reaches the fragment — particles are never tinted, only the alpha crosses, applied to the alpha byte alone — so the shipped shader split was already right and no code changes

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Sam
2026-08-31 20:15:16 +07:00
co-authored by Claude Fable 5
parent 961d7e46a0
commit a0d2fc383b
68 changed files with 2186 additions and 272 deletions
+2 -1
View File
@@ -81,7 +81,8 @@ fn button_state_textures_switch_with_interaction() {
// Disabled: disabled texture only, and no highlight even under the cursor.
s.run("StateBtn:Disable()").unwrap();
assert_eq!(visible(&s), vec!["Interface\\D.blp".to_string()]);
assert!(!s.eval::<bool>("return StateBtn:IsEnabled()").unwrap());
// 0, not false, and not nil — the number the real client answers.
assert_eq!(s.eval::<i64>("return StateBtn:IsEnabled()").unwrap(), 0);
s.run("StateBtn:Enable()").unwrap();
assert_eq!(s.eval::<String>("return StateBtn:GetText()").unwrap(), "Go");
assert!(s.errors().is_empty(), "{:?}", s.errors());
@@ -1551,3 +1551,74 @@ fn every_installed_region_method_lands_in_a_leaf() {
"installed on the region table but in NO leaf list, so no region can call them: {orphans:?}"
);
}
/// **The four nameplate verbs take no argument and return nothing.**
///
/// `ShowNameplates 0x489450` / `HideNameplates 0x489460` / `ShowFriendNameplates 0x489470` /
/// `HideFriendNameplates 0x489480` — four 10-byte bodies over two setters, differing only in an
/// `or`/`and` mask (wow-re `ui/scratch/party-leader-and-nameplate-verbs.md`).
///
/// Both halves of the shape are asserted because both are easy to invent differently:
///
/// - **No argument is read.** `ShowNameplates(false)` still shows — there is no `lua_gettop` and
/// no `lua_toboolean` in any of the four. That is *why* there are four verbs instead of two
/// taking a flag, and a binding typed `fn(bool)` would reject a call the reference accepts.
/// - **Zero return values, not nil.** `select('#', ...)` sees 0, which is observably different
/// from one nil for a caller that counts.
#[test]
fn the_nameplate_verbs_ignore_their_arguments_and_return_nothing() {
let mut s = script();
s.register_cvars([
(crate::script::CVAR_NAMEPLATE_ENEMIES, "1"),
(crate::script::CVAR_NAMEPLATE_FRIENDS, "0"),
]);
let get = |s: &crate::script::UiScript, n: &str| s.cvar(n);
assert_eq!(
get(&s, crate::script::CVAR_NAMEPLATE_ENEMIES).as_deref(),
Some("1")
);
// Each verb writes its own bit and leaves the other alone.
s.run("HideNameplates()").unwrap();
assert_eq!(
get(&s, crate::script::CVAR_NAMEPLATE_ENEMIES).as_deref(),
Some("0")
);
assert_eq!(
get(&s, crate::script::CVAR_NAMEPLATE_FRIENDS).as_deref(),
Some("0"),
"the friendly bit is a separate setter — hiding enemies must not touch it"
);
s.run("ShowFriendNameplates()").unwrap();
assert_eq!(
get(&s, crate::script::CVAR_NAMEPLATE_FRIENDS).as_deref(),
Some("1")
);
assert_eq!(
get(&s, crate::script::CVAR_NAMEPLATE_ENEMIES).as_deref(),
Some("0"),
"...and back the other way"
);
// **An argument is accepted and IGNORED.** `ShowNameplates(false)` shows, which is the whole
// reason the reference ships four verbs rather than two taking a flag.
s.run("ShowNameplates(false)").unwrap();
assert_eq!(
get(&s, crate::script::CVAR_NAMEPLATE_ENEMIES).as_deref(),
Some("1"),
"the verb IS the value — a falsy argument does not invert it"
);
for call in ["HideNameplates(1, 2, 3)", "ShowFriendNameplates({})"] {
s.run(call)
.unwrap_or_else(|e| panic!("{call} must not raise: {e}"));
}
// ZERO return values, not one nil.
assert_eq!(
s.eval::<usize>("return select('#', ShowNameplates())")
.unwrap(),
0,
"zero values — observably different from nil for a caller that counts"
);
}
@@ -1096,3 +1096,107 @@ fn a_textures_getters_report_its_texel_span_on_an_unsized_axis() {
.unwrap();
assert!(s.take_errors().is_empty());
}
/// **The three constructors' string arguments are FOUR different shapes, not one rule.**
///
/// Verified together in wow-re `ui/scratch/xml-template-name-lookup.md` §5.2 (§5 pair, orchestrator
/// byte-arbitration). The discriminator is **not** the argument's type — it is whether the binding
/// *tests* its parser's return, which is the sentence `numeric-arg-coercion-law.md` §6 had wrong:
///
/// | position | fetch | a table there | a number there |
/// |---|---|---|---|
/// | `CreateFontString`/`CreateTexture` `name`, `layer` | `0x6f3510` → `0x6f3690`, untested | absent | **accepted, stringified** |
/// | `CreateFontString`/`CreateTexture` `inherits` | raw `lua_type == LUA_TSTRING`, no coercion | ignored | **ignored** |
/// | `CreateFrame` `name`, `inherits` | `0x6f3690` **unguarded** | absent | **accepted, stringified** |
/// | `CreateFrame` `kind` | `0x6f3510`, tested | **raises** | accepted |
///
/// The last two rows are the same value taking opposite paths from argument order alone:
/// `CreateFrame` coerces at `0x70613f` *before* its `cmp 4`, and `0x6f7cb1` retags the number's
/// stack slot **in place**, so a number arrives at the gate already a string.
#[test]
fn the_constructors_string_arguments_are_four_shapes_not_one() {
let mut s = script();
s.set_screen_size(800.0, 600.0);
s.run(r#"host = CreateFrame("Frame", "ArgHost", UIParent)"#)
.unwrap();
// ── pfUI's line, verbatim. `f.buffs[i]:CreateFontString(nil, "OVERLAY", f.buffs[i])` passes
// the BUTTON as `inheritsFrom`. The real client constructs it, ignores the argument, and
// logs nothing — this raised `bad argument #4` until 2026-08-30 and stopped pfUI, and with
// it pfQuest, pfQuest-turtle and ShaguDPS.
s.run(r#"fs = ArgHost:CreateFontString(nil, "OVERLAY", ArgHost)"#)
.unwrap_or_else(|e| panic!("a table in `inherits` must be ignored, not raise: {e}"));
assert_eq!(
s.eval::<String>("return type(fs)").unwrap(),
"table",
"...and the FontString is really constructed"
);
// Every other non-string in every region-constructor position: also silent, also constructed.
for pos in ["{}, \"OVERLAY\"", "nil, {}", "true, false"] {
for ctor in ["CreateFontString", "CreateTexture"] {
s.run(&format!("r = ArgHost:{ctor}({pos})"))
.unwrap_or_else(|e| panic!("{ctor}({pos}) must not raise: {e}"));
assert_eq!(s.eval::<String>("return type(r)").unwrap(), "table");
}
}
// ── A NUMBER is a string to `lua_isstring` — so it NAMES the region.
s.run("named = ArgHost:CreateTexture(4242)").unwrap();
assert_eq!(
s.eval::<String>("return named:GetName()").unwrap(),
"4242",
"a number in `name` is stringified, not dropped"
);
// ...but NOT in `inherits`, whose gate reads the raw tag and never coerces. A number there is
// ignored, where the same number in CreateFrame's fourth argument would raise.
s.run("ArgHost:CreateTexture(nil, nil, 5)")
.unwrap_or_else(|e| panic!("a number in a region ctor's `inherits` is ignored: {e}"));
// ── CreateFrame: `name` coerces the same way.
s.run("nf = CreateFrame(\"Frame\", 77)").unwrap();
assert_eq!(
s.eval::<String>("return nf:GetName()").unwrap(),
"77",
"CreateFrame reads `name` through an UNGUARDED lua_tostring"
);
// ...and an unresolvable `inherits` still raises, which is the pre-existing contract and the
// control for the changes above: loosening the OTHER positions must not have loosened this.
assert!(
s.run(r#"CreateFrame("Frame", nil, nil, "NoSuchTemplateAnywhere")"#)
.is_err(),
"an unresolvable template name still raises — the miss branch is luaL_error"
);
// ── The layer argument is PERMISSIVE, and an unrecognised one is ARTWORK, not an error.
// `0x6f18b0` returns 0 with its out-param unwritten and neither constructor reads the
// result, so the pre-staged ARTWORK (2) stands. Matching is case-insensitive.
s.run(r#"lay = ArgHost:CreateTexture(nil, "not-a-layer")"#)
.unwrap_or_else(|e| panic!("an unrecognised layer must not raise: {e}"));
// Read off the arena — 1.12 publishes no `GetDrawLayer`, so there is no Lua getter to ask,
// the same reason the sequence scrub is read this way in `modelframe`'s tests.
let layer_of = |s: &UiScript, name: &str| {
let lua = s.lua();
let model = lua.app_data_ref::<crate::script::Model>().expect("model");
let id = *model.region_names.get(name).expect("published region");
let rh = *model.id_to_region.get(&id).expect("live region");
model.arena.region(rh).expect("region").draw_layer
};
s.run(r#"ArgHost:CreateTexture("LayLower", "overlay")"#)
.unwrap();
s.run(r#"ArgHost:CreateTexture("LayUpper", "OVERLAY")"#)
.unwrap();
s.run(r#"ArgHost:CreateTexture("LayJunk", "not-a-layer")"#)
.unwrap();
assert_eq!(
layer_of(&s, "LayLower"),
layer_of(&s, "LayUpper"),
"layer matching is case-insensitive (SStrCmpI 0x64a4c0)"
);
assert_eq!(
layer_of(&s, "LayJunk"),
crate::order::DrawLayer::Artwork,
"an unrecognised layer leaves the PRE-STAGED default standing — ARTWORK (2), not an error \
and not BACKGROUND: `0x6f18b0` returns 0 with its out-param unwritten and neither \
constructor reads the result"
);
}
@@ -224,6 +224,18 @@ fn set_inventory_item_renders_full_outside_compare() {
assert(quoted, "full render keeps the description")
assert(tt:SetInventoryItem("player", 5) == nil, "empty slot answers nil")
assert(tt:SetInventoryItem("target", 16) == nil, "self-only feed")
-- THREE returns, on every leg. The reference's own PaperDollFrame.lua:741 destructures
-- `hasItem, hasCooldown, repairCost` unconditionally and only then tests hasItem, so an
-- empty slot answering ONE value hands its caller a nil where a number belongs. pfUI's
-- durability scan (panel.lua:499) does `totalRep + repCost` with no guard at all and died
-- exactly there.
local n = { tt:SetInventoryItem("player", 16) }
assert(table.getn(n) == 3, "occupied: three returns, got " .. table.getn(n))
assert(n[3] == 0, "repairCost is a NUMBER — the reference always pushes one; 0 INTERIM")
local e = { tt:SetInventoryItem("player", 5) }
assert(table.getn(e) == 3, "empty: three returns too, got " .. table.getn(e))
assert(e[1] == nil and e[3] == 0, "empty slot: no item, but still a numeric repairCost")
"#,
)
.unwrap();