From 251daa14c5865a57def997ba40dc1f4e8e5cab48 Mon Sep 17 00:00:00 2001 From: Sam Date: Tue, 11 Aug 2026 18:19:08 +0700 Subject: [PATCH] addons come alive: Bagnon draws, hovers and clicks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Lua 5.0's generic-for and nested long strings are restored in a forked VM — 60 corpus addons stopped dying on one deleted opcode - a day of demand-driven carving takes the 218-addon corpus from 24 addons surviving a session start to 114, with 144 loading without a single error - Bagnon draws a real bag and answers real gestures — hover tooltip, pickup, use, drag — because our frames wear the reference's names, and the reference's own ContainerFrame.lua runs off the player's install instead of being transcribed - the physics broad phase leaves: nothing in the client reads a contact, and it was measured at up to 41% of main-thread samples in real sessions Co-Authored-By: Claude Fable 5 --- Cargo.lock | 2 - Cargo.toml | 19 +- crates/benilla-app/assets/ui/ActionBar.xml | 112 + crates/benilla-app/assets/ui/AddonList.xml | 48 +- crates/benilla-app/assets/ui/BagFrame.xml | 192 +- crates/benilla-app/assets/ui/BankFrame.xml | 30 +- crates/benilla-app/assets/ui/CastingBar.xml | 12 + crates/benilla-app/assets/ui/ChatFrame.xml | 158 +- .../assets/ui/ContainerFrameAdapters.xml | 82 + crates/benilla-app/assets/ui/Cooldown.xml | 47 +- crates/benilla-app/assets/ui/ErrorsFrame.xml | 133 +- crates/benilla-app/assets/ui/GameTooltip.xml | 79 + .../assets/ui/ItemButtonTemplate.xml | 174 + crates/benilla-app/assets/ui/MultiBars.xml | 147 + .../benilla-app/assets/ui/QuestLogFrame.xml | 25 +- crates/benilla-app/assets/ui/TaxiFrame.xml | 17 +- .../benilla-app/assets/ui/UIDropDownMenu.xml | 53 +- crates/benilla-app/assets/ui/UiPanels.xml | 2 +- crates/benilla-app/assets/ui/UnitFrames.xml | 14 + crates/benilla-app/assets/ui/benilla.toc | 10 + crates/benilla-app/examples/addon_harness.rs | 408 +- crates/benilla-app/src/addon_harness.rs | 1565 -------- crates/benilla-app/src/addon_harness/mod.rs | 3560 +++++++++++++++++ .../benilla-app/src/addon_harness/render.rs | 330 ++ .../src/addon_harness/render_tests.rs | 234 ++ .../src/addon_harness/use_probe.rs | 281 ++ .../src/addon_harness/use_probe_tests.rs | 323 ++ crates/benilla-app/src/char_create/mod.rs | 7 +- crates/benilla-app/src/char_select/addons.rs | 65 +- crates/benilla-app/src/char_select/mod.rs | 22 +- crates/benilla-app/src/cvars.rs | 48 +- crates/benilla-app/src/ui_chat.rs | 4 + crates/benilla-app/src/ui_chat/edit.rs | 60 +- crates/benilla-app/src/ui_chat/feed.rs | 6 + crates/benilla-app/src/ui_chat/tests.rs | 34 + .../src/ui_script/action_bar_tests.rs | 69 +- .../src/ui_script/addon_list_tests.rs | 57 + crates/benilla-app/src/ui_script/addons.rs | 109 +- crates/benilla-app/src/ui_script/bag_tests.rs | 213 +- .../src/ui_script/bagnon_render_tests.rs | 947 +++++ .../benilla-app/src/ui_script/bank_tests.rs | 2 +- .../benilla-app/src/ui_script/cast_tests.rs | 36 + .../benilla-app/src/ui_script/chat_tests.rs | 147 + .../src/ui_script/dropdown_tests.rs | 184 + .../benilla-app/src/ui_script/errors_tests.rs | 130 +- .../src/ui_script/game_menu_tests.rs | 26 +- .../benilla-app/src/ui_script/macro_tests.rs | 27 +- crates/benilla-app/src/ui_script/manifest.rs | 6 + crates/benilla-app/src/ui_script/mod.rs | 71 + .../src/ui_script/multibar_stance_tests.rs | 50 +- .../src/ui_script/questlog_tests.rs | 27 +- .../benilla-app/src/ui_script/reference_ui.rs | 173 + .../src/ui_script/shipped_xml_tests.rs | 8 + .../src/ui_script/tooltip_template_tests.rs | 66 + .../src/ui_script/unit_frame_tests.rs | 58 + crates/benilla-app/src/ui_unit.rs | 26 + crates/benilla-ui/src/framexml.rs | 38 +- crates/benilla-ui/src/loader/mod.rs | 83 +- crates/benilla-ui/src/loader/scripts.rs | 34 +- crates/benilla-ui/src/loader/tests.rs | 166 + crates/benilla-ui/src/loader/widgets.rs | 44 +- crates/benilla-ui/src/order.rs | 5 + crates/benilla-ui/src/script/addon.rs | 68 +- crates/benilla-ui/src/script/button.rs | 190 +- crates/benilla-ui/src/script/channel.rs | 119 + crates/benilla-ui/src/script/chat_window.rs | 295 ++ crates/benilla-ui/src/script/client.rs | 21 +- crates/benilla-ui/src/script/cvars.rs | 33 + .../benilla-ui/src/script/editbox/methods.rs | 107 +- crates/benilla-ui/src/script/extract.rs | 130 +- crates/benilla-ui/src/script/font.rs | 5 +- crates/benilla-ui/src/script/font_block.rs | 351 ++ crates/benilla-ui/src/script/layout.rs | 235 +- .../benilla-ui/src/script/messageframe/mod.rs | 317 ++ .../src/script/messageframe/plain.rs | 422 ++ .../scrolling.rs} | 22 +- crates/benilla-ui/src/script/mod.rs | 90 +- crates/benilla-ui/src/script/model.rs | 10 + crates/benilla-ui/src/script/object.rs | 10 +- .../src/script/object/events_regions.rs | 69 +- .../src/script/object/frame_state.rs | 76 + .../src/script/object/layout_methods.rs | 31 + crates/benilla-ui/src/script/pointer.rs | 36 +- crates/benilla-ui/src/script/region.rs | 338 +- crates/benilla-ui/src/script/stdlib.rs | 205 +- crates/benilla-ui/src/script/tests/anchors.rs | 128 + .../benilla-ui/src/script/tests/backdrop.rs | 108 + crates/benilla-ui/src/script/tests/button.rs | 63 + crates/benilla-ui/src/script/tests/channel.rs | 110 + crates/benilla-ui/src/script/tests/events.rs | 60 + .../src/script/tests/generic_for.rs | 223 ++ crates/benilla-ui/src/script/tests/mod.rs | 2 + .../src/script/tests/reference_surface.rs | 791 ++++ crates/benilla-ui/src/script/tests/regions.rs | 88 + crates/benilla-ui/src/script/tests/stdlib.rs | 113 +- crates/benilla-ui/src/script/tick.rs | 37 +- crates/benilla-ui/src/script/types.rs | 80 +- crates/benilla-ui/src/widget/kinds/editbox.rs | 97 + .../src/widget/kinds/messageframe.rs | 171 +- crates/benilla-ui/src/widget/kinds/mod.rs | 79 +- crates/benilla-ui/src/widget/mod.rs | 26 +- crates/benilla-ui/src/widget/propagation.rs | 72 +- crates/benilla-world/src/collision.rs | 109 + crates/benilla-world/src/world_plugins.rs | 20 +- 104 files changed, 14356 insertions(+), 2306 deletions(-) create mode 100644 crates/benilla-app/assets/ui/ContainerFrameAdapters.xml create mode 100644 crates/benilla-app/assets/ui/ItemButtonTemplate.xml delete mode 100644 crates/benilla-app/src/addon_harness.rs create mode 100644 crates/benilla-app/src/addon_harness/mod.rs create mode 100644 crates/benilla-app/src/addon_harness/render.rs create mode 100644 crates/benilla-app/src/addon_harness/render_tests.rs create mode 100644 crates/benilla-app/src/addon_harness/use_probe.rs create mode 100644 crates/benilla-app/src/addon_harness/use_probe_tests.rs create mode 100644 crates/benilla-app/src/ui_script/bagnon_render_tests.rs create mode 100644 crates/benilla-app/src/ui_script/dropdown_tests.rs create mode 100644 crates/benilla-app/src/ui_script/reference_ui.rs create mode 100644 crates/benilla-ui/src/script/channel.rs create mode 100644 crates/benilla-ui/src/script/chat_window.rs create mode 100644 crates/benilla-ui/src/script/font_block.rs create mode 100644 crates/benilla-ui/src/script/messageframe/mod.rs create mode 100644 crates/benilla-ui/src/script/messageframe/plain.rs rename crates/benilla-ui/src/script/{messageframe.rs => messageframe/scrolling.rs} (97%) create mode 100644 crates/benilla-ui/src/script/tests/channel.rs create mode 100644 crates/benilla-ui/src/script/tests/generic_for.rs 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/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 `