diff --git a/Cargo.lock b/Cargo.lock
index 093da7b8..37cfdb4c 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -4477,8 +4477,6 @@ dependencies = [
[[package]]
name = "lua-src"
version = "550.0.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e836dc8ae16806c9bdcf42003a88da27d163433e3f9684c52f0301258004a4fb"
dependencies = [
"cc",
]
diff --git a/Cargo.toml b/Cargo.toml
index 29fd776b..e3b389e1 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -17,8 +17,23 @@ anyhow = "1.0.102"
bevy = "0.18.1"
clap = { version = "4.6.1", features = ["derive"] }
-# No `[patch.crates-io]`: the WoW format + protocol stack is fully in-repo (decision 0021), so there
-# are no warcraft-rs / wow_messages crates (or forks of them) anywhere in the tree.
+# The WoW format + protocol stack is fully in-repo (decision 0021), so there are no warcraft-rs /
+# wow_messages crates (or forks of them) anywhere in the tree. That is still true and is why this
+# section stood empty for the project's whole life.
+#
+# The one entry below is not a fork of a WoW crate at all. `lua-src` compiles the Lua the client's
+# addon VM runs on, and the 1.12 corpus needs one opcode that Lua 5.1 deleted — the iterator-less
+# generic-for (`for k, v in someTable do`), which 183 of 218 corpus addons are reached by and which
+# is the first session-start error for 60 of them. It is an OPCODE, so no layer above the VM can
+# reach it: not a library function, not a global, not a metamethod.
+#
+# The fork is currently BYTE-IDENTICAL to upstream's 5.1.5 sources, and is landed that way on
+# purpose: the pipeline is proven transparent before the behavioural payload goes into it, so that
+# when the suite moves, the patch is the only thing that could have moved it. The payload waits on
+# an RE answer about what the real client's own Lua does — stock Lua 5.0 is corroboration, not the
+# authority. `third_party/lua-src/BENILLA.md` has the current state and the verifying command.
+[patch.crates-io]
+lua-src = { path = "third_party/lua-src" }
# Optimize *dependencies* even in dev builds. avian/parry collision math (and wgpu/bevy render) is
# unusably slow at opt-level 0 — a plain `cargo run` crawls to <1 fps once physics is doing real work.
diff --git a/crates/benilla-app/assets/ui/ActionBar.xml b/crates/benilla-app/assets/ui/ActionBar.xml
index 14d5fc86..24299ccd 100644
--- a/crates/benilla-app/assets/ui/ActionBar.xml
+++ b/crates/benilla-app/assets/ui/ActionBar.xml
@@ -446,6 +446,34 @@
end
end
+ -- ── The reference's names, so an addon that hooks them is reached ──────────────────────
+ --
+ -- The 1.12 customisation model is "replace a global and the client calls your replacement"
+ -- (Bagnon does it to `ToggleBackpack`; `zBar`/`zBarEx` do
+ -- `SetScript("OnShow", ActionButton_Update)`; `CT_BarMod` calls `ActionButton_Update()`
+ -- outright). Wherever our UI calls a `Benilla`-named function and never the reference's
+ -- name, every one of those addons is SILENTLY INERT — the override is installed and never
+ -- invoked. A survey cannot see it; it loads addons and fires events but never clicks.
+ --
+ -- These are ADAPTERS, not renames, and the distinction is the whole point: the reference's
+ -- contract is `this`-shaped and no-argument, ours takes the button explicitly. Renaming
+ -- would let `TipBuddy`'s `originalUnitFrame_OnEnter()` reach us with a nil frame — reachable
+ -- but broken, which is worse than inert. The adapter publishes the reference's SHAPE and
+ -- delegates.
+ --
+ -- NOT adapted, and named so nobody assumes it was missed: `ActionButton_OnLoad`. Ours is
+ -- `(button, index, base, commandPrefix)` and the reference's takes nothing — the extra
+ -- arguments are real state our per-container registration needs, so no adapter can supply
+ -- them. That is the per-button event registration change already at the top of the arc
+ -- queue, not a naming fix.
+ function ActionButton_Update()
+ BenillaActionButton_Update(this)
+ end
+
+ function ActionButton_UpdateUsable()
+ BenillaActionButton_UpdateUsable(this)
+ end
+
function BenillaActionButton_Update(button)
local action = BenillaActionButton_Action(button)
if HasAction(action) then
@@ -1361,4 +1389,88 @@
BenillaActionBar_OnEvent(self)
+
+
+
+
+
+
+
+
+
+
+ BenillaActionButton_OnLoad(self, 1)
+
+
+
+ BenillaActionButton_OnLoad(self, 2)
+
+
+
+ BenillaActionButton_OnLoad(self, 3)
+
+
+
+ BenillaActionButton_OnLoad(self, 4)
+
+
+
+ BenillaActionButton_OnLoad(self, 5)
+
+
+
+ BenillaActionButton_OnLoad(self, 6)
+
+
+
+ BenillaActionButton_OnLoad(self, 7)
+
+
+
+ BenillaActionButton_OnLoad(self, 8)
+
+
+
+ BenillaActionButton_OnLoad(self, 9)
+
+
+
+ BenillaActionButton_OnLoad(self, 10)
+
+
+
+ BenillaActionButton_OnLoad(self, 11)
+
+
+
+ BenillaActionButton_OnLoad(self, 12)
+
+
+
diff --git a/crates/benilla-app/assets/ui/AddonList.xml b/crates/benilla-app/assets/ui/AddonList.xml
index 5d09d4db..af5345b0 100644
--- a/crates/benilla-app/assets/ui/AddonList.xml
+++ b/crates/benilla-app/assets/ui/AddonList.xml
@@ -43,6 +43,32 @@
ADDON_BUTTON_HEIGHT = 16
MAX_ADDONS_DISPLAYED = 19
+ -- The interface benilla implements. Kept as a name rather than a literal at the call site
+ -- because `ui_script::addons::INTERFACE_VERSION` is the same constant on the Rust side and
+ -- the two must not drift.
+ BENILLA_INTERFACE_VERSION = 11200
+
+ -- **`## Interface` mismatch, which `GetAddOnInfo`'s `reason` cannot carry.** 1191 §6 reports
+ -- the mismatch and never enforces it — enforcing would refuse most of a real vanilla corpus,
+ -- whose manifests say 11100, 10900, anything — so it is not a reason the addon *cannot*
+ -- load, and `not_loadable` rightly has no arm for it. That left this screen silent about a
+ -- status its char-select twin has always shown, for the same addon: two views of one folder
+ -- (1197 §3) disagreeing. This is the missing half.
+ --
+ -- Stated divergence, measured: `GetAddOnMetadata` returns the FIRST `## Interface` line
+ -- while the Rust `out_of_date()` accepts a match among ALL of them. **Zero of the 218 corpus
+ -- addons declare it twice**, so the two agree everywhere it is currently observable; a
+ -- manifest that declared 11100 and 11200 would read out-of-date here and current there.
+ function AddonList_OutOfDate(name)
+ local declared = GetAddOnMetadata(name, "Interface")
+ if not declared then
+ -- No `## Interface` at all is SILENT, not out of date — the same rule the char
+ -- select panel pins in `a_manifest_with_no_interface_is_silent`.
+ return nil
+ end
+ return tonumber(declared) ~= BENILLA_INTERFACE_VERSION
+ end
+
-- The staged edit. `AddonList.original` is the enable state as the panel found it, so
-- Cancel can put it back — the reference's `ResetAddOns` over the client's own records,
-- which we do not have an engine verb for (SaveAddOns/ResetAddOns are GLUE-namespace
@@ -72,10 +98,16 @@
-- One row's worth of facts, so the row painter below reads as a list of assignments
-- rather than as a pile of GetAddOnInfo returns.
+ -- Slot 4 is `enabled`, and `url` is not in the in-game return at all: the client registers
+ -- GetAddOnInfo twice and the in-game one (`0x48e390`) answers seven values without a URL,
+ -- where glue's (`0x46d460`) answers eight with one. We had the glue shape here, so this
+ -- read `url` out of slot 4 and then had to RECONSTRUCT `enabled` from the reason token —
+ -- a workaround that existed only because the slot it wanted was occupied by the wrong
+ -- value. `## URL` lives in the manifest either way, so the tooltip asks for it directly.
local function rowFacts(index)
- local name, title, notes, url, loadable, reason = GetAddOnInfo(index)
- local enabled = not (reason == "DISABLED")
- return name, (title or name), loadable, reason, enabled, notes, url
+ local name, title, notes, enabled, loadable, reason = GetAddOnInfo(index)
+ return name, (title or name), loadable, reason, enabled, notes,
+ GetAddOnMetadata(name, "URL")
end
function AddonList_Update()
@@ -117,6 +149,16 @@
local status = getglobal("AddonListEntry" .. i .. "Status")
if reason then
status:SetText(getglobal("ADDON_" .. reason) or reason)
+ elseif AddonList_OutOfDate(name) then
+ -- INTERFACE_VERSION ("Out of date"), which `reason` can never carry here
+ -- and which the char-select twin has always shown. The two screens are the
+ -- same job (1197 §1) and were answering DIFFERENTLY about the same addon:
+ -- glue read `## Interface` off the folder, this one asked `GetAddOnInfo`,
+ -- and `not_loadable` has no interface arm — because 1191 §6 reports the
+ -- mismatch and never enforces it, so it is not a reason the addon cannot
+ -- load. Surfaced separately for exactly that reason: `loadable` stays true
+ -- and most of a real vanilla corpus keeps loading.
+ status:SetText(ADDON_INTERFACE_VERSION or "Out of date")
else
status:SetText("")
end
diff --git a/crates/benilla-app/assets/ui/BagFrame.xml b/crates/benilla-app/assets/ui/BagFrame.xml
index 294a6b5c..32593ff7 100644
--- a/crates/benilla-app/assets/ui/BagFrame.xml
+++ b/crates/benilla-app/assets/ui/BagFrame.xml
@@ -439,7 +439,7 @@
-- `frame` IS the backpack — preserving that global's observability (existing tests patch it
-- directly to count backpack repaints, e.g. delete_item_tests.rs's stuck-darkening
-- regression check) — and BenillaBagWindow_Update generically for every other window. Every
- -- runtime repaint trigger (a slot click, an event, BenillaOpenAllBags' pre-show paint) goes
+ -- runtime repaint trigger (a slot click, an event, OpenAllBags' pre-show paint) goes
-- through this; BenillaBagBarSlot_OnClick calls BenillaBagWindow_Update directly (it only ever
-- targets bags 1..4).
function BenillaBagWindow_Repaint(frame)
@@ -507,7 +507,23 @@
-- by bag id (BENILLA_BAG_WINDOWS[bagId+1]) — one code path over all of them.
BENILLA_BAG_WINDOWS = { "BenillaBagFrame", "BenillaBagFrame1", "BenillaBagFrame2", "BenillaBagFrame3", "BenillaBagFrame4" }
- function BenillaOpenAllBags()
+ -- THE REFERENCE'S NAMES ARE THE REAL ENTRY POINTS, and our own UI calls THEM.
+ --
+ -- This is how a 1.12 addon customises anything: it replaces a global and expects the
+ -- client to call its replacement. `Bagnon_Core/core/Overrides.lua` is the canonical shape —
+ -- local bToggleBackpack = ToggleBackpack
+ -- ToggleBackpack = function() ... end
+ -- and until now that override was INERT here, because the 'B' binding and the bag button
+ -- called `BenillaBagToggle_OnClick` directly and nothing ever looked up `ToggleBackpack`.
+ -- Bagnon loaded, appeared in the AddOns list, replaced the globals, and the player still
+ -- got the stock bags. The director saw that; no instrument here could, because the corpus
+ -- survey loads addons and fires events but never CLICKS anything.
+ --
+ -- The class is far wider than one addon: every "hook a global to change behaviour" addon is
+ -- silently inert wherever our UI calls a benilla-named equivalent instead of the
+ -- reference's name. `CloseAllBags`/`OpenBackpack`/`CloseBackpack` already lived under the
+ -- ref's names in UiPanels.xml for exactly this reason; these are the three that did not.
+ function OpenAllBags()
for bagId = 0, 4 do
-- The backpack always exists; an equipped-bag slot only when a bag is in it.
if bagId == 0 or GetContainerNumSlots(bagId) > 0 then
@@ -520,27 +536,79 @@
end
end
- function BenillaCloseAllBags()
- for _, name in ipairs(BENILLA_BAG_WINDOWS) do
- local frame = getglobal(name)
- if frame and frame:IsShown() then frame:Hide() end
+ -- `ToggleBag(id)` — the ref's per-bag toggle (ContainerFrame.lua l.152-170), and the other
+ -- global Bagnon replaces. Ours drives the same window the all-bags path does.
+ function ToggleBag(id)
+ local frame = getglobal(BENILLA_BAG_WINDOWS[(id or 0) + 1])
+ if not frame then return end
+ if frame:IsShown() then
+ frame:Hide()
+ else
+ BenillaBagWindow_Repaint(frame)
+ frame:Show()
end
end
- -- The backpack button (BenillaBagToggle) and the 'B' binding: if any bag window is open,
- -- close them all; otherwise open every existing bag (ref ToggleBackpack — "if numOpen > 0
- -- then CloseAllBags() else OpenAllBags()").
- function BenillaBagToggle_OnClick()
+ -- `OpenBag(id)` / `CloseBag(id)` / `IsBagOpen(id)` — ToggleBag's three siblings from the
+ -- same reference file (ContainerFrame.lua l.144-185), and the last of that family that was
+ -- still missing here. `Bagnon_Core/core/Overrides.lua` l.42/51 captures BOTH of the first
+ -- two and calls the capture back for any bag Bagnon does not control — a nil call, on the
+ -- ordinary path, for an addon that only *partly* takes the bags over.
+ --
+ -- The reference's `OpenBag` opens a bag only when `GetContainerNumSlots(id) > 0`; ours keeps
+ -- that gate for 1..4 and exempts bag 0, which always exists (the same rule `OpenAllBags`
+ -- above already applies, for the same reason).
+ function OpenBag(id)
+ id = id or 0
+ if id ~= 0 and GetContainerNumSlots(id) <= 0 then return end
+ local frame = getglobal(BENILLA_BAG_WINDOWS[id + 1])
+ if frame and not frame:IsShown() then
+ BenillaBagWindow_Repaint(frame)
+ frame:Show()
+ end
+ end
+
+ function CloseBag(id)
+ local frame = getglobal(BENILLA_BAG_WINDOWS[(id or 0) + 1])
+ if frame and frame:IsShown() then frame:Hide() end
+ end
+
+ -- ONE STATED DIVERGENCE. The reference returns the INDEX of the ContainerFrame showing that
+ -- bag (1..NUM_CONTAINER_FRAMES) so its own code can `getglobal("ContainerFrame"..i)`; we
+ -- ship no such pool (each bag has one fixed window), so there is no index to name and this
+ -- answers 1 / nil. Every corpus caller — and the reference's own `ToggleBackpack` — uses it
+ -- as a boolean; a caller that indexed the result would already be reaching for frames that
+ -- do not exist here.
+ function IsBagOpen(id)
+ local frame = getglobal(BENILLA_BAG_WINDOWS[(id or 0) + 1])
+ if frame and frame:IsShown() then return 1 end
+ return nil
+ end
+
+ -- `ToggleBackpack()` — the ref's all-bags toggle ("if numOpen > 0 then CloseAllBags() else
+ -- OpenAllBags()"). It calls the two GLOBALS by name rather than local bodies, so hooking
+ -- either of those works too.
+ function ToggleBackpack()
local anyOpen = false
for _, name in ipairs(BENILLA_BAG_WINDOWS) do
local frame = getglobal(name)
if frame and frame:IsShown() then anyOpen = true end
end
- if anyOpen then BenillaCloseAllBags() else BenillaOpenAllBags() end
+ if anyOpen then CloseAllBags() else OpenAllBags() end
+ end
+
+ -- The backpack button (MainMenuBarBackpackButton) and the 'B' binding: if any bag window is open,
+ -- close them all; otherwise open every existing bag (ref ToggleBackpack — "if numOpen > 0
+ -- then CloseAllBags() else OpenAllBags()").
+ function BenillaBagToggle_OnClick()
+ -- Through the GLOBAL, so an addon that replaced `ToggleBackpack` gets the click. This
+ -- one line is the whole Bagnon fix; the rest below is our own button's checked art,
+ -- which is ours to keep whatever the bag window turns out to be.
+ ToggleBackpack()
-- The auto-toggle is provisional (ref BackpackButton_OnClick l.58-67): the button
-- lights for the BACKPACK window specifically, whatever the all-toggle just did.
local backpack = getglobal(BENILLA_BAG_WINDOWS[1])
- getglobal("BenillaBagToggle"):SetChecked(backpack and backpack:IsShown())
+ getglobal("MainMenuBarBackpackButton"):SetChecked(backpack and backpack:IsShown())
end
-- The window's bar button (ref ContainerFrame_OnShow/OnHide: bag 0 →
@@ -550,8 +618,8 @@
if name == "BenillaKeyRingFrame" then return getglobal("KeyRingButton") end
for bagId = 0, 4 do
if name == BENILLA_BAG_WINDOWS[bagId + 1] then
- if bagId == 0 then return getglobal("BenillaBagToggle") end
- return getglobal("BenillaBagBarSlot" .. bagId)
+ if bagId == 0 then return getglobal("MainMenuBarBackpackButton") end
+ return getglobal("CharacterBag" .. (bagId - 1) .. "Slot")
end
end
end
@@ -647,7 +715,7 @@
end
end
- -- ── The bag bar (decision 0216 slice 2) — four 30×30 buttons left of BenillaBagToggle,
+ -- ── The bag bar (decision 0216 slice 2) — four 30×30 buttons left of MainMenuBarBackpackButton,
-- mirroring the reference main-bar bag buttons (CharacterBag0..3Slot). No exact reference
-- XML was available to transcribe (not in the session's extracted set) — size (30×30) and
-- placement (left of the backpack button) are per the brief; inter-button spacing is an
@@ -674,7 +742,17 @@
function BenillaBagBarSlot_OnLoad(button, bagId)
button.bagId = bagId
- button.icon = getglobal(button:GetName() .. "Icon")
+ -- The button's ID is the REFERENCE's: its 1-based inventory slot, 20..23 (the real
+ -- client gets there through PaperDollItemSlotButton_OnLoad, which resolves
+ -- "CharacterBag0Slot" → "Bag0Slot" → GetInventorySlotInfo → 20). Nothing of ours reads
+ -- it — every body here goes through `button.bagId` — but every ADDON does:
+ -- `Bagnon.lua:104` computes its highlight target as `this:GetID() - 19`, and against
+ -- an unset id that is -19, so Bagnon highlighted the wrong bag (silently, with no
+ -- raise) on every bag-slot hover. `isBag` is the same story from the same template
+ -- (ref BagSlotButtonTemplate OnLoad l.33), read by KeyRingItemButton_OnClick.
+ button:SetID(19 + bagId)
+ button.isBag = 1
+ button.icon = getglobal(button:GetName() .. "IconTexture")
if BenillaActionBarArt_SeatAbove then BenillaActionBarArt_SeatAbove(button) end
-- Both buttons (ref BagSlotButtonTemplate inherits PaperDollItemSlotButtonTemplate, and
-- its OnLoad calls PaperDollItemSlotButton_OnLoad — PaperDollFrame.lua:86 — so a bag-bar
@@ -776,6 +854,26 @@
end
end
+ -- `BagSlotButton_OnEnter()` — THE REFERENCE'S NAME for the hover above, and the entry point
+ -- the four `CharacterBagSlot` buttons actually call (their XML below). Published for the
+ -- same standing reason `ToggleBackpack` is: an addon replaces the client's global and
+ -- expects the client to call the replacement.
+ --
+ -- `Bagnon.lua:67` is the exact shape, and it is a hover-frequency call, not a rare one:
+ -- bBagSlotButton_OnEnter = BagSlotButton_OnEnter -- capture ours
+ -- BagSlotButton_OnEnter = BagnonBlizBag_OnEnter -- replace the global
+ -- Bagnon's replacement highlights the matching slots in its own window and then calls the
+ -- capture. With the global absent that capture was nil, and — because nothing here ever
+ -- looked the global up — the replacement was never reached to raise on it: Bagnon's
+ -- highlight was simply, silently inert. Corpus demand is exactly one addon (this one) at
+ -- two sites; its siblings `BagSlotButton_OnClick`/`_OnDrag`/`_OnShiftClick`/
+ -- `BackpackButton_OnClick` score ZERO and are deliberately not published (1210).
+ --
+ -- The reference's body takes no argument and reads `this`; so does this one.
+ function BagSlotButton_OnEnter()
+ BenillaBagBarSlot_OnEnter(this)
+ end
+
-- ref OnLeave for both (l.100-102 / the template's own): a plain Hide, no fade — the item
-- half of the split BenillaUnitFrame_OnLeave's comment describes.
function BenillaBagBarButton_OnLeave()
@@ -821,8 +919,8 @@
return
end
end
- if BenillaErrorsFrame_AddMessage then
- BenillaErrorsFrame_AddMessage(NO_EMPTY_KEYRING_SLOTS_ERROR, 1.0, 0.1, 0.1)
+ if UIErrorsFrame then
+ UIErrorsFrame:AddMessage(NO_EMPTY_KEYRING_SLOTS_ERROR, 1.0, 0.1, 0.1)
end
end
@@ -1011,8 +1109,8 @@
-- The bag BAR's five buttons (the backpack toggle + the four equipped-bag slots) — the row
-- Disable_/Enable_BagButtons greys as a unit. Distinct from UiPanels.xml's BAG_WINDOWS,
-- which is the five bag *windows* those buttons open.
- BENILLA_BAG_BUTTONS = { "BenillaBagToggle", "BenillaBagBarSlot1", "BenillaBagBarSlot2",
- "BenillaBagBarSlot3", "BenillaBagBarSlot4" }
+ BENILLA_BAG_BUTTONS = { "MainMenuBarBackpackButton", "CharacterBag0Slot", "CharacterBag1Slot",
+ "CharacterBag2Slot", "CharacterBag3Slot" }
-- Disable_BagButtons / Enable_BagButtons — ref MainMenuBarBagButtons.lua l.102-128, called
-- by GameMenuFrame's OnShow/OnHide (decision 0674) and by nothing else: while the game menu
@@ -1021,8 +1119,8 @@
-- while the menu is up — UiPanels.xml `CanOpenPanels`); without it the bags would look
-- clickable and simply do nothing.
--
- -- Mapping onto benilla's row: the ref's MainMenuBarBackpackButton is BenillaBagToggle and
- -- CharacterBag0..3Slot are BenillaBagBarSlot1..4 — and in BOTH cases the greying goes onto
+ -- Mapping onto benilla's row: the ref's MainMenuBarBackpackButton is MainMenuBarBackpackButton and
+ -- CharacterBag0..3Slot are CharacterBag0Slot..4 — and in BOTH cases the greying goes onto
-- the button's `$parentIcon` LAYER texture, never onto a state texture. That is the
-- reference's own structure and it is load-bearing here: a disabled button draws its
-- DisabledTexture, and with none set it draws no state texture at all
@@ -1048,7 +1146,7 @@
-- The four slots' own paint decides their restored colour: an item locked on the cursor
-- wants 0.4 grey, not the flat white SetDesaturation(nil) just left.
for i = 1, 4 do
- BenillaBagBarSlot_Update(getglobal("BenillaBagBarSlot" .. i))
+ BenillaBagBarSlot_Update(getglobal("CharacterBag" .. (i - 1) .. "Slot"))
end
end
]]>
@@ -1569,7 +1667,7 @@
the bar. (MainMenuBarArtFrame is defined in ActionBar.xml, which the loader loads before
this file; the anchor resolves by name.)
-->
-
+
@@ -1587,7 +1685,7 @@
bag-bar slots' own `$parentIcon`, and the greying is the vertex tint on it. -->
-
+
- self.icon = getglobal("BenillaBagToggleIcon")
+ self.icon = getglobal("MainMenuBarBackpackButtonIconTexture")
if BenillaActionBarArt_SeatAbove then BenillaActionBarArt_SeatAbove(self) end
BenillaBagToggle_OnClick()
@@ -1627,19 +1725,19 @@
-
+
-
+
-
+
@@ -1680,7 +1778,7 @@
BenillaBagBarSlot_OnLoad(self, 1)
BenillaBagBarSlot_OnClick(self)
BenillaBagBarSlot_OnEvent(self)
- BenillaBagBarSlot_OnEnter(self)
+ BagSlotButton_OnEnter()
BenillaBagBarButton_OnLeave()
@@ -1734,7 +1832,7 @@
BenillaBagBarSlot_OnLoad(self, 2)
BenillaBagBarSlot_OnClick(self)
BenillaBagBarSlot_OnEvent(self)
- BenillaBagBarSlot_OnEnter(self)
+ BagSlotButton_OnEnter()
BenillaBagBarButton_OnLeave()
@@ -1788,7 +1886,7 @@
BenillaBagBarSlot_OnLoad(self, 3)
BenillaBagBarSlot_OnClick(self)
BenillaBagBarSlot_OnEvent(self)
- BenillaBagBarSlot_OnEnter(self)
+ BagSlotButton_OnEnter()
BenillaBagBarButton_OnLeave()
@@ -1842,7 +1940,7 @@
BenillaBagBarSlot_OnLoad(self, 4)
BenillaBagBarSlot_OnClick(self)
BenillaBagBarSlot_OnEvent(self)
- BenillaBagBarSlot_OnEnter(self)
+ BagSlotButton_OnEnter()
BenillaBagBarButton_OnLeave()
-
+ SetPortraitTexture(region, "npc"). The name is the REFERENCE'S, unprefixed —
+ its bank frame breaks the $parentPortrait convention every other window here
+ follows, and addons write the name the reference gave them. -->
+
diff --git a/crates/benilla-app/assets/ui/CastingBar.xml b/crates/benilla-app/assets/ui/CastingBar.xml
index 6fe53476..cbf8d686 100644
--- a/crates/benilla-app/assets/ui/CastingBar.xml
+++ b/crates/benilla-app/assets/ui/CastingBar.xml
@@ -52,6 +52,18 @@
this:RegisterEvent("SPELLCAST_CHANNEL_STOP")
this.casting = nil
this.holdTime = 0
+ -- The ref's own last line of this function (`CastingBarFrame.lua:16`), and the one
+ -- line of it we dropped: a plain alias global. `CastingBarFrame` IS the StatusBar in
+ -- 1.12 — there is no separate child frame — so the reference publishes the name an
+ -- addon would look for, and then uses it itself for the rest of the file.
+ --
+ -- 11 corpus addons index `CastingBarFrameStatusBar`, and it was the largest single
+ -- name in the whole session-start `attempt to index global` row. Nothing warned:
+ -- a line dropped INSIDE a function we otherwise transcribed verbatim is invisible to
+ -- every instrument here. `framexml-transcription-diff.py` compares elements and
+ -- handlers, not function bodies, and would not even pair these two files (ours is
+ -- CastingBar.xml, the reference's is CastingBarFrame.xml).
+ CastingBarFrameStatusBar = CastingBarFrame
this:Hide()
end
diff --git a/crates/benilla-app/assets/ui/ChatFrame.xml b/crates/benilla-app/assets/ui/ChatFrame.xml
index 4611637f..42da4dc5 100644
--- a/crates/benilla-app/assets/ui/ChatFrame.xml
+++ b/crates/benilla-app/assets/ui/ChatFrame.xml
@@ -17,6 +17,22 @@
"Say:" with static insets.
-->
+
diff --git a/crates/benilla-app/assets/ui/Cooldown.xml b/crates/benilla-app/assets/ui/Cooldown.xml
index 1c9e4c49..58686198 100644
--- a/crates/benilla-app/assets/ui/Cooldown.xml
+++ b/crates/benilla-app/assets/ui/Cooldown.xml
@@ -3,19 +3,60 @@
The shared cooldown-frame machine — the ref's own file split (Cooldown.lua/Cooldown.xml,
loaded before every consumer): CooldownFrame_SetTimer drives BOTH the action buttons
(ActionBar.xml) and the bag slots (BagFrame.xml), exactly as the reference shares it.
- The ref's Model template + OnUpdateModel scrub/finish-flash machine live inside our
- native widget instead (decision 0263), armed by SetCooldown.
+ The ref's OnUpdateModel scrub/finish-flash machine lives inside our native
+ widget instead (decision 0263), armed by SetCooldown.
+
+ `CooldownFrameTemplate` below is the reference's own declaration, and it is here because
+ it was MISSING: three corpus addons name it in an XML `inherits=` (Bagnon_Core,
+ Bongos_ActionBar, BuffCheck2), and Bagnon hangs one off EVERY item button, so a single
+ session logged roughly sixty
+ WARN ui_script: unknown template 'CooldownFrameTemplate' referenced by inherits
+ lines while each of those children came out as a bare, attribute-less frame.
-->
+
+
+
diff --git a/crates/benilla-app/assets/ui/ErrorsFrame.xml b/crates/benilla-app/assets/ui/ErrorsFrame.xml
index cb1ef077..2f1ddae1 100644
--- a/crates/benilla-app/assets/ui/ErrorsFrame.xml
+++ b/crates/benilla-app/assets/ui/ErrorsFrame.xml
@@ -1,123 +1,68 @@
-
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
UIErrorsFrame_OnLoad()
UIErrorsFrame_OnEvent(event, arg1)
- BenillaErrorsFrame_OnUpdate(self, arg1)
-
+
+
+
diff --git a/crates/benilla-app/assets/ui/GameTooltip.xml b/crates/benilla-app/assets/ui/GameTooltip.xml
index ff56b0a0..2b5fe2c5 100644
--- a/crates/benilla-app/assets/ui/GameTooltip.xml
+++ b/crates/benilla-app/assets/ui/GameTooltip.xml
@@ -305,6 +305,85 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/crates/benilla-app/assets/ui/MultiBars.xml b/crates/benilla-app/assets/ui/MultiBars.xml
index 45dd0af0..b03d3a0e 100644
--- a/crates/benilla-app/assets/ui/MultiBars.xml
+++ b/crates/benilla-app/assets/ui/MultiBars.xml
@@ -35,6 +35,9 @@
-- base = (page - 1) * 12.
BENILLA_MULTIBAR_BOTTOMLEFT_BASE = 60
BENILLA_MULTIBAR_BOTTOMRIGHT_BASE = 48
+ -- ref ActionButton.lua:8-9 — LEFT_ACTIONBAR_PAGE = 4, RIGHT_ACTIONBAR_PAGE = 3.
+ BENILLA_MULTIBAR_LEFT_BASE = 36
+ BENILLA_MULTIBAR_RIGHT_BASE = 24
-- The MULTIACTIONBAR keybind pair — ref MultiActionBars.lua:24-45 MultiActionButtonDown/Up,
-- the same NORMAL→PUSHED debounce dance as the main bar's ActionButtonDown/Up
@@ -185,4 +188,148 @@
+
+
+
+
+
+
+
+
+
+
+
+ BenillaActionButton_OnLoad(self, 1, BENILLA_MULTIBAR_RIGHT_BASE, "MULTIACTIONBAR3BUTTON")
+
+
+
+ BenillaActionButton_OnLoad(self, 2, BENILLA_MULTIBAR_RIGHT_BASE, "MULTIACTIONBAR3BUTTON")
+
+
+
+ BenillaActionButton_OnLoad(self, 3, BENILLA_MULTIBAR_RIGHT_BASE, "MULTIACTIONBAR3BUTTON")
+
+
+
+ BenillaActionButton_OnLoad(self, 4, BENILLA_MULTIBAR_RIGHT_BASE, "MULTIACTIONBAR3BUTTON")
+
+
+
+ BenillaActionButton_OnLoad(self, 5, BENILLA_MULTIBAR_RIGHT_BASE, "MULTIACTIONBAR3BUTTON")
+
+
+
+ BenillaActionButton_OnLoad(self, 6, BENILLA_MULTIBAR_RIGHT_BASE, "MULTIACTIONBAR3BUTTON")
+
+
+
+ BenillaActionButton_OnLoad(self, 7, BENILLA_MULTIBAR_RIGHT_BASE, "MULTIACTIONBAR3BUTTON")
+
+
+
+ BenillaActionButton_OnLoad(self, 8, BENILLA_MULTIBAR_RIGHT_BASE, "MULTIACTIONBAR3BUTTON")
+
+
+
+ BenillaActionButton_OnLoad(self, 9, BENILLA_MULTIBAR_RIGHT_BASE, "MULTIACTIONBAR3BUTTON")
+
+
+
+ BenillaActionButton_OnLoad(self, 10, BENILLA_MULTIBAR_RIGHT_BASE, "MULTIACTIONBAR3BUTTON")
+
+
+
+ BenillaActionButton_OnLoad(self, 11, BENILLA_MULTIBAR_RIGHT_BASE, "MULTIACTIONBAR3BUTTON")
+
+
+
+ BenillaActionButton_OnLoad(self, 12, BENILLA_MULTIBAR_RIGHT_BASE, "MULTIACTIONBAR3BUTTON")
+
+
+
+
+
+
+
+
+
+
+
+
+ BenillaActionButton_OnLoad(self, 1, BENILLA_MULTIBAR_LEFT_BASE, "MULTIACTIONBAR4BUTTON")
+
+
+
+ BenillaActionButton_OnLoad(self, 2, BENILLA_MULTIBAR_LEFT_BASE, "MULTIACTIONBAR4BUTTON")
+
+
+
+ BenillaActionButton_OnLoad(self, 3, BENILLA_MULTIBAR_LEFT_BASE, "MULTIACTIONBAR4BUTTON")
+
+
+
+ BenillaActionButton_OnLoad(self, 4, BENILLA_MULTIBAR_LEFT_BASE, "MULTIACTIONBAR4BUTTON")
+
+
+
+ BenillaActionButton_OnLoad(self, 5, BENILLA_MULTIBAR_LEFT_BASE, "MULTIACTIONBAR4BUTTON")
+
+
+
+ BenillaActionButton_OnLoad(self, 6, BENILLA_MULTIBAR_LEFT_BASE, "MULTIACTIONBAR4BUTTON")
+
+
+
+ BenillaActionButton_OnLoad(self, 7, BENILLA_MULTIBAR_LEFT_BASE, "MULTIACTIONBAR4BUTTON")
+
+
+
+ BenillaActionButton_OnLoad(self, 8, BENILLA_MULTIBAR_LEFT_BASE, "MULTIACTIONBAR4BUTTON")
+
+
+
+ BenillaActionButton_OnLoad(self, 9, BENILLA_MULTIBAR_LEFT_BASE, "MULTIACTIONBAR4BUTTON")
+
+
+
+ BenillaActionButton_OnLoad(self, 10, BENILLA_MULTIBAR_LEFT_BASE, "MULTIACTIONBAR4BUTTON")
+
+
+
+ BenillaActionButton_OnLoad(self, 11, BENILLA_MULTIBAR_LEFT_BASE, "MULTIACTIONBAR4BUTTON")
+
+
+
+ BenillaActionButton_OnLoad(self, 12, BENILLA_MULTIBAR_LEFT_BASE, "MULTIACTIONBAR4BUTTON")
+
+
+
diff --git a/crates/benilla-app/assets/ui/QuestLogFrame.xml b/crates/benilla-app/assets/ui/QuestLogFrame.xml
index ae545df9..0a3f081b 100644
--- a/crates/benilla-app/assets/ui/QuestLogFrame.xml
+++ b/crates/benilla-app/assets/ui/QuestLogFrame.xml
@@ -59,15 +59,12 @@
`QuestLogTitleButton_OnClick`, `QuestLogFrame.lua:469-505`). The modifier plumbing this
note once named as the missing engine slice (`IsShiftKeyDown` + the app's per-frame
mirror) landed with the cursor arc; the interim right-click substitute is retired.
- - **The two ref watch errors (`QUEST_WATCH_NO_OBJECTIVES`/`QUEST_WATCH_TOO_MANY`,
- `UIErrorsFrame:AddMessage`) have no seam to reach `UIErrorsFrame` from Lua**: that
- frame only reacts to the `UI_ERROR_MESSAGE` *event*, and firing an event is a method on
- `UiScript` (`fire_event`) — there is no Lua-callable "fire this event"/"post this error"
- global anywhere in this engine (grepped; every `UI_ERROR_MESSAGE` fire in this codebase is
- Rust-side, e.g. `ui_merchant.rs`/`ui_action.rs`). The two guards below therefore no-op
- silently instead of erroring — a real (if narrow) gap: right-clicking a no-objectives or
- 6th-watch row does nothing visible rather than explaining why. Worth a small Lua-callable
- error-post global as a follow-up once a second caller wants one too.
+ - The two ref watch errors (`QUEST_WATCH_NO_OBJECTIVES`/`QUEST_WATCH_TOO_MANY`) go out
+ through the reference's own `UIErrorsFrame:AddMessage`. That was once impossible here —
+ `UIErrorsFrame` was a hand-rolled `` reachable only by firing `UI_ERROR_MESSAGE`
+ from Rust — and the guards no-opped silently in the interim; the MessageFrame widget
+ (`script::messageframe::plain`) closed it, and the seam this note asked for turned out
+ to be the real widget method rather than a benilla global.
-->
\n\
+ ";
+ let mut s = Scan::default();
+ scan_lua(&strip_xml_comments(xml), &mut s);
+ for noise in ["PURPOSE", "License", "Id"] {
+ assert!(
+ !s.indexed.contains(noise) && !s.called.contains(noise),
+ "{noise} is license boilerplate, not a surface the addon expects"
+ );
+ }
+ assert!(
+ s.indexed.contains("GameTooltip"),
+ "the script body must still be scanned: {:?}",
+ s.indexed
+ );
+ assert!(
+ s.called.contains("MyHelper"),
+ "the script body must still be scanned: {:?}",
+ s.called
+ );
+ // ...and the METHOD arm obeys the same stripper. `See the` in that header is a capitalised
+ // word after a word — harmless — but `GameTooltip:AddLine(` inside the CDATA is the shape
+ // the method scan lives on, so both halves are asserted on the same file (1218's rule: a
+ // noise fix that goes blind is worse than the noise).
+ assert!(
+ s.methods.contains("AddLine"),
+ "the script body's method call must be scanned: {:?}",
+ s.methods
+ );
+ }
+
+ /// An unterminated `