diff --git a/crates/benilla-app/assets/ui/ActionBar.xml b/crates/benilla-app/assets/ui/ActionBar.xml
index d27416c2..bd210cb5 100644
--- a/crates/benilla-app/assets/ui/ActionBar.xml
+++ b/crates/benilla-app/assets/ui/ActionBar.xml
@@ -1482,9 +1482,12 @@
does (decision 1636).
-->
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
ActionButton_OnEvent()
diff --git a/crates/benilla-app/assets/ui/CastingBar.xml b/crates/benilla-app/assets/ui/CastingBar.xml
deleted file mode 100644
index 954627a2..00000000
--- a/crates/benilla-app/assets/ui/CastingBar.xml
+++ /dev/null
@@ -1,294 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- CastingBarFrame_OnLoad()
- CastingBarFrame_OnEvent()
- CastingBarFrame_OnUpdate()
-
-
-
-
-
diff --git a/crates/benilla-app/assets/ui/Fonts.xml b/crates/benilla-app/assets/ui/Fonts.xml
deleted file mode 100644
index 1996af64..00000000
--- a/crates/benilla-app/assets/ui/Fonts.xml
+++ /dev/null
@@ -1,397 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/crates/benilla-app/assets/ui/HelpFrame.xml b/crates/benilla-app/assets/ui/HelpFrame.xml
deleted file mode 100644
index 7bdb7415..00000000
--- a/crates/benilla-app/assets/ui/HelpFrame.xml
+++ /dev/null
@@ -1,1017 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- HelpFrameButton_FitToText(HelpFrameHomeIssues)
- HelpFrameButton_FitToText(BenillaHelpFrameHomeUnstick)
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- ScrollFrame_OnLoad()
- local trough = getglobal(self:GetName() .. "ScrollBarTrough")
- BenillaScrollTrough_Seat(trough, getglobal(self:GetName() .. "ScrollBar"))
- trough:Show()
-
-
-
-
-
-
-
- ScrollingEdit_OnTextChanged()
- ScrollingEdit_OnCursorChanged(arg1, arg2, arg3, arg4)
- HideUIPanel(HelpFrame)
-
-
-
-
-
-
-
-
-
-
-
- self:RegisterEvent("UPDATE_TICKET")
- self:RegisterEvent("GMSURVEY_DISPLAY")
-
- HelpFrameOpenTicket_OnEvent(event)
-
-
-
-
-
-
- HelpFrame_OnLoad(self)
- HelpFrame_OnShow(self)
- HelpFrame_OnEvent()
-
- PlaySound("igCharacterInfoClose")
- if UpdateMicroButtons then UpdateMicroButtons() end
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- if TOOLTIP_DEFAULT_COLOR then
- self:SetBackdropBorderColor(TOOLTIP_DEFAULT_COLOR.r, TOOLTIP_DEFAULT_COLOR.g, TOOLTIP_DEFAULT_COLOR.b)
- end
- if TOOLTIP_DEFAULT_BACKGROUND_COLOR then
- self:SetBackdropColor(TOOLTIP_DEFAULT_BACKGROUND_COLOR.r, TOOLTIP_DEFAULT_BACKGROUND_COLOR.g, TOOLTIP_DEFAULT_BACKGROUND_COLOR.b)
- end
- self:RegisterEvent("UPDATE_TICKET")
- self:RegisterEvent("PLAYER_ENTERING_WORLD")
-
- TicketStatusFrame_OnEvent(self)
- TicketStatus_OnUpdate(arg1)
-
- TicketStatus_OnClick()
-
-
-
diff --git a/crates/benilla-app/assets/ui/OptionsFrame.xml b/crates/benilla-app/assets/ui/OptionsFrame.xml
index 34096572..7aa4871e 100644
--- a/crates/benilla-app/assets/ui/OptionsFrame.xml
+++ b/crates/benilla-app/assets/ui/OptionsFrame.xml
@@ -240,6 +240,39 @@
-- reference at typical resolutions reads nearer two-thirds.
ERA_WINDOW_SCALE = 0.78
+ -- ── UpdateNameplates (ref UIOptionsFrame.lua l.768-780, verbatim) ────────────────────
+ -- Its reference home is `UIOptionsFrame.lua` and this file is our stand-in for that window
+ -- (0950/0992), so the function belongs here rather than beside its caller.
+ --
+ -- **Decision 1878 recorded this as "a missing ENGINE verb, and 1203 says build it or leave
+ -- it loudly absent", and named it the thing blocking the UIParent keystone. It is neither.**
+ -- It is FrameXML — `UIOptionsFrame.lua:768`, absent from wow-re's binding-shape table — and
+ -- every verb it calls is already ours: `ShowNameplates`/`HideNameplates`/
+ -- `ShowFriendNameplates`/`HideFriendNameplates` are the four CVar-backed bindings in
+ -- `script/cvars.rs`, all argc=0 arity=0, matching the reference's shapes.
+ --
+ -- `NAMEPLATES_ON`/`FRIENDNAMEPLATES_ON` are `RegisterForSave` globals the reference drives
+ -- from the keybinding alone (1.12's own options UI has no nameplate row). Nil is a normal
+ -- state for them and the else-branches are the reference's own answer to it, so this needs
+ -- no seeding — which is why the whole thing is twelve lines rather than a build.
+ --
+ -- The reference's odd second arm is kept as it stands: on FRIENDNAMEPLATES_ON it calls
+ -- Show and then Hide, which is a no-op in the real client too. Transcribed, not corrected;
+ -- fixing a reference bug here would be inventing a difference.
+ function UpdateNameplates()
+ if ( NAMEPLATES_ON ) then
+ ShowNameplates()
+ else
+ HideNameplates()
+ end
+ if ( FRIENDNAMEPLATES_ON ) then
+ ShowFriendNameplates()
+ HideFriendNameplates()
+ else
+ HideFriendNameplates()
+ end
+ end
+
function OptionsFrame_UpdateScale()
-- The era panel is registered `checkFit = 1`: the panel manager also shrinks it to
-- fit a small screen (UIPanelUpdateScaleForFit: min over width/height ratios
diff --git a/crates/benilla-app/assets/ui/UIParent.xml b/crates/benilla-app/assets/ui/UIParent.xml
index 572a53de..7d9d9705 100644
--- a/crates/benilla-app/assets/ui/UIParent.xml
+++ b/crates/benilla-app/assets/ui/UIParent.xml
@@ -215,6 +215,40 @@
-- statements on the same event). `MultiActionBar_Update` is existence-guarded because the
-- ~25 per-window harnesses that load this file load no bars; the binding itself is engine
-- level and always installed. Decision 1500.
+ -- ── ITEM_QUALITY_COLORS (ref UIParent.lua l.65-72) ───────────────────────────────
+ -- Its reference home is THIS file. It lived in our own `Fonts.xml` until 1888 put
+ -- that file on the chain — the reference's `Fonts.xml` does not declare it, so the
+ -- table had to come back here or vanish. Every reader is below UIParent.xml in the
+ -- manifest (GameTooltip 169, UnitPopup 203, AuctionFrame 928; UnitPopup's is a
+ -- FILE-SCOPE read inside a table literal, which is the one that makes the order
+ -- load-bearing rather than incidental).
+ -- The item-quality palette — **built from the engine verb, exactly as the reference
+ -- builds it** (ref UIParent.lua l.65-72, verbatim shape; decision 1199):
+ --
+ -- ITEM_QUALITY_COLORS = { }
+ -- for i = -1, 6 do … = GetItemQualityColor(i) end
+ --
+ -- It used to be seven hand-typed rows here. They were CLOSE and not exact — 0.62 where
+ -- the client's byte is 0x9d/255 = 0.6157, 0.64/0.21/0.93 where Epic is
+ -- 0xa3/0x35/0xee — and they carried no `.hex`, which the reference's table has and
+ -- which addons splice straight into a chat link. `GetItemQualityColor` is now a real
+ -- engine binding over wow-5875-re RF-0055's byte-verified table, so the rounding and
+ -- the second source both go.
+ --
+ -- The loop starts at -1 like the reference's, and the -1 row is not a curiosity: it is the
+ -- colour a loot row wears while its item template is uncached, which is the value
+ -- `GetLootSlotInfo` answers there. `GetItemQualityColor`'s clamp is UNSIGNED, so -1 lands
+ -- on Common/white with 7-and-up (decision 1805, correcting 1199's "reads before the
+ -- array").
+ ITEM_QUALITY_COLORS = { }
+ for i = -1, 6 do
+ ITEM_QUALITY_COLORS[i] = { }
+ ITEM_QUALITY_COLORS[i].r,
+ ITEM_QUALITY_COLORS[i].g,
+ ITEM_QUALITY_COLORS[i].b,
+ ITEM_QUALITY_COLORS[i].hex = GetItemQualityColor(i)
+ end
+
-- ── UIParentLoadAddOn (ref UIParent.lua l.638-647, and now our slot for it) ──────
-- It lived in our own BasicControls.xml until 1881 migrated that file to the chain;
-- the reference never put it there, so the move is back to where it belongs.
@@ -285,6 +319,16 @@
end
end
+ -- ref UIParent.lua l.194-196, verbatim — the fifth squatter, and the smallest. Stock
+ -- `HelpFrame.xml` calls it when a GM survey is waiting (its l.1487), and its whole body is
+ -- `UIParentLoadAddOn("Blizzard_GMSurveyUI")` — a verb 1881 already moved here and an addon
+ -- that really is on the player's chain (patch-2.MPQ). So this is the reference's own
+ -- function, not a stand-in: 1889 needed it because the stock help window is on the chain
+ -- now and ours is gone.
+ function GMSurveyFrame_LoadUI()
+ UIParentLoadAddOn("Blizzard_GMSurveyUI")
+ end
+
-- ref UIParent.lua's own `ShowMacroFrame`, moved here from our MacroFrame.xml when 1848
-- retired that file — the third such squatter after `InspectUnit` (1832) and
-- `ToggleTalentFrame` (1833), and by now the expected shape.
diff --git a/crates/benilla-app/assets/ui/benilla.toc b/crates/benilla-app/assets/ui/benilla.toc
index b7edb5fc..4a59df41 100644
--- a/crates/benilla-app/assets/ui/benilla.toc
+++ b/crates/benilla-app/assets/ui/benilla.toc
@@ -38,7 +38,13 @@
# The named virtual Font objects (decision 0084) — must load first so any FontString's
# `inherits="GameFontNormal"` resolves against a registered object.
-Fonts.xml
+# ══ THE FONT REGISTRY — the reference's own, off the player's chain (1751, 1888) ══
+# All 51 `` objects, name for name, plus the colour codes, `CHAT_FONT_HEIGHTS`,
+# `MATERIAL_TEXT_COLOR_TABLE`, `MATERIAL_TITLETEXT_COLOR_TABLE` and `RAID_CLASS_COLORS`.
+# Ours was a transcription differing only in formatting (it inlined ``), plus one
+# addition: `ITEM_QUALITY_COLORS`, whose reference home is `UIParent.lua` and which 1888 moved
+# to our UIParent.xml so it survives this swap.
+Interface\FrameXML\Fonts.xml
# ══ THE STRINGS — the reference's own first line, seated second here (1848) ══
# **Second, not first, and that is deliberate.** Manifest entry 0 is the FONT REGISTRY:
@@ -337,6 +343,19 @@ TalentWipeConfirm.xml
# The shared CooldownFrame_SetTimer (the ref's Cooldown.lua file split) — before its
# consumers (ActionBar's buttons, the multibars, the stance bar, BagFrame's slots).
Cooldown.xml
+# ══ THE ACTION-BUTTON REGIONS — the reference's own, off the player's chain (1751) ══
+# `ActionButtonTemplate`: the REGIONS-ONLY half of the reference's two-level split — icon, count,
+# hotkey, name, border, flash, cooldown and normal texture. It declares NO scripts and calls
+# nothing, so it needs no `.lua` beside it. The handler half is `ActionBarButtonTemplate`, which
+# ActionBar.xml still declares and which inherits this; those handlers stay ours until
+# `ActionButton.lua`'s 21 verbs migrate (we have 10 of them, under the reference's own names).
+#
+# Ours was a transcription of this exact file — the eight `$parent*` child names match to the
+# letter — so the swap is a deletion.
+#
+# **It must come BEFORE ActionBar.xml.** A template is registered by name and the later line
+# wins, so seated below it our transcription would take the name straight back.
+Interface\FrameXML\ActionButtonTemplate.xml
ActionBar.xml
# The four OPTIONAL extra bars (bottom left/right 61-72 / 49-60, vertical right/left
# 25-36 / 37-48), all shipping hidden until the player ticks their row (decision 1500):
@@ -493,9 +512,16 @@ Interface\FrameXML\ColorPickerFrame.xml
# WorldMapRaid1..40 with inherits=, so the virtual Frames have to be registered before it.
WorldMapFrameTemplates.xml
WorldMapFrame.xml
-# The cast bar (decision 0137 phase 1) — the extracted 1.12 CastingBarFrame, driven by
-# the SPELLCAST_* events `crate::ui_cast` fires. Only needs Fonts before it.
-CastingBar.xml
+# ══ THE CAST BAR — the reference's own, off the player's chain (1751) ══
+# `CastingBarFrame` + its Border/Flash/Spark/Text children, driven by the SPELLCAST_* events
+# `crate::ui_cast` fires (decision 0137 phase 1). Our `CastingBar.xml` was a transcription of this
+# very file under a different name — all five frame names identical — so the swap is a deletion.
+#
+# The file SOURCES ITS OWN LUA (`` at its l.3), which is why
+# only the `.xml` is listed here and `every_chain_xml_brings_its_own_lua` is satisfied.
+#
+# It brings `CastingBarFrame_UpdatePosition`, which ours never had. Only needs Fonts before it.
+Interface\FrameXML\CastingBarFrame.xml
# ══ THE MIRROR TIMERS — the reference's own, off the player's chain (1751, window 7) ══
# `MirrorTimer1..3`, `MIRRORTIMER_NUMTIMERS`, `MirrorTimerColors` and the `MirrorTimer_Show` /
# `MirrorTimerFrame_*` family out of `MirrorTimer.lua`. The breath/fatigue bars at top-centre,
@@ -1014,7 +1040,12 @@ ScriptLogFrame.xml
# resolved at call time, so that pair could sit either way round). The one seat that is a REAL
# requirement rather than legibility is **after BuffFrame.xml**: the toast re-anchors
# TemporaryEnchantFrame out of its own way, and that frame is declared there.
-HelpFrame.xml
+#
+# **The reference's own since 1889.** Ours was 1017 lines against the reference's 2088 — the
+# largest shortfall of any window we shipped — so this swap is mostly ARRIVAL, not replacement.
+# The file sources its own `HelpFrame.lua` (its l.3), so only the `.xml` is listed. Its one
+# named need, `GMSurveyFrame_LoadUI`, is now in UIParent.xml where the reference declares it.
+Interface\FrameXML\HelpFrame.xml
# The cinematic frame: the ESC that skips a fly-by, and the SCREENSHOT chord it hands back. It
# draws NOTHING — the letterbox moved to Bevy UI nodes in `crate::cinematic` (decision 1699),
# because the lane this frame paints into is dark for the whole shot.
diff --git a/crates/benilla-app/src/net/apply/combat_log.rs b/crates/benilla-app/src/net/apply/combat_log.rs
index c7e59bc0..1ec69e53 100644
--- a/crates/benilla-app/src/net/apply/combat_log.rs
+++ b/crates/benilla-app/src/net/apply/combat_log.rs
@@ -650,4 +650,8 @@ pub(super) fn exploration_xp(
pub(super) fn level_up(l: LevelUpInfo, chat_log: &mut ChatLog) {
let talent_points = u32::from(l.level >= 10);
chat_log.push_level_up(&l, talent_points);
+ // ...and park the raw tuple for `ui_unit`'s `PLAYER_LEVEL_UP`, whose nine args the reference's
+ // own ChatFrame reads (1884). The Rust lines above stay until that window migrates and can
+ // take over printing them — retiring them first would simply lose the ding.
+ chat_log.push_level_up_gains(&l, talent_points);
}
diff --git a/crates/benilla-app/src/ui_action/feed_tests.rs b/crates/benilla-app/src/ui_action/feed_tests.rs
index 3c39ad7e..022f421d 100644
--- a/crates/benilla-app/src/ui_action/feed_tests.rs
+++ b/crates/benilla-app/src/ui_action/feed_tests.rs
@@ -419,7 +419,10 @@ fn pre_resolved_lines_land_on_the_errors_frame_in_the_arms_colour() {
script.set_screen_size(1024.0, 768.0);
// The errors frame is the reference's own file since 1751 window 14, so this reads both
// stores through the one loader that speaks them.
- for file in ["Fonts.xml", "Interface\\FrameXML\\UIErrorsFrame.xml"] {
+ for file in [
+ "Interface\\FrameXML\\Fonts.xml",
+ "Interface\\FrameXML\\UIErrorsFrame.xml",
+ ] {
crate::ui_script::load_ui_for_test(&script, file);
}
}
diff --git a/crates/benilla-app/src/ui_chat/feed.rs b/crates/benilla-app/src/ui_chat/feed.rs
index a6931e60..f2750c47 100644
--- a/crates/benilla-app/src/ui_chat/feed.rs
+++ b/crates/benilla-app/src/ui_chat/feed.rs
@@ -200,6 +200,14 @@ pub(super) fn text_emote_event(
pub(crate) struct ChatLog {
pending: Vec,
broadcasts: Vec,
+ /// The ding's gain tuple, parked for `ui_unit`'s `PLAYER_LEVEL_UP` fire (decision 1884).
+ ///
+ /// The net layer has no `UiScript` — deliberately — so the gains arrive here, on the same
+ /// net-to-UI channel `broadcasts` uses, and the feed that owns the level edge picks them up.
+ /// They are matched BY LEVEL rather than just drained, because the trigger is a descriptor
+ /// diff and the gains are packet-borne: the two land together today, and matching means a
+ /// stale entry can never attach itself to a later ding if they ever stop doing so.
+ level_up_gains: Vec<(LevelUpInfo, u32)>,
}
impl ChatLog {
@@ -217,6 +225,22 @@ impl ChatLog {
pub(crate) fn take_broadcasts(&mut self) -> Vec {
std::mem::take(&mut self.broadcasts)
}
+
+ /// Park the ding's gains for the `PLAYER_LEVEL_UP` fire (see [`ChatLog::level_up_gains`]).
+ pub(crate) fn push_level_up_gains(&mut self, info: &LevelUpInfo, talent_points: u32) {
+ self.level_up_gains.push((*info, talent_points));
+ }
+
+ /// The parked gains for `level`, removed from the queue. `None` when the level edge came from
+ /// somewhere the packet did not — a GM demotion's descriptor write, or a first observation —
+ /// in which case the caller fires zeros, which is what those gains actually are.
+ pub(crate) fn take_level_up_gains(&mut self, level: u32) -> Option<(LevelUpInfo, u32)> {
+ let i = self
+ .level_up_gains
+ .iter()
+ .position(|(l, _)| l.level == level)?;
+ Some(self.level_up_gains.remove(i))
+ }
}
impl ChatLog {
diff --git a/crates/benilla-app/src/ui_chat/tests.rs b/crates/benilla-app/src/ui_chat/tests.rs
index 9d3e60e6..713d9f22 100644
--- a/crates/benilla-app/src/ui_chat/tests.rs
+++ b/crates/benilla-app/src/ui_chat/tests.rs
@@ -438,7 +438,7 @@ fn chat_vm() -> benilla_ui::script::UiScript {
// that would hide a real ordering fault. (The tooltip file is the dropdown kit's own
// dependency — its MenuBackdrop reads `TOOLTIP_DEFAULT_COLOR`.)
for file in [
- "Fonts.xml",
+ "Interface\\FrameXML\\Fonts.xml",
"GameTooltip.xml",
"Interface\\FrameXML\\UIDropDownMenu.xml",
// The UIMenu kit is the reference's own file since 1751 window 21, so this reads both
@@ -2324,3 +2324,38 @@ fn the_posture_emotes_carry_no_swim_suppression_flag() {
);
}
}
+
+/// **The ding's gains reach `PLAYER_LEVEL_UP` matched BY LEVEL, and a miss is zeros, not absence.**
+///
+/// The net layer has no `UiScript`, so `SMSG_LEVELUP_INFO`'s tuple is parked on `ChatLog` and the
+/// feed that owns the level edge picks it up (decision 1884). Matching on the level is what keeps
+/// a stale entry from attaching to a later ding — the trigger is a descriptor diff and the gains
+/// are packet-borne, so the two are only coincidentally in step.
+///
+/// The miss case is the load-bearing half: a GM demotion writes the descriptor with no packet, and
+/// the event must still carry nine arguments. `ChatFrame.lua` guards every one with
+/// `if ( argN > 0 )`, so a zero reads as "no line" while a nil raises.
+#[test]
+fn level_up_gains_are_matched_by_level_and_a_miss_is_not_an_absence() {
+ use benilla_protocol::messages::LevelUpInfo;
+
+ let mut log = super::feed::ChatLog::default();
+ let ding = LevelUpInfo {
+ level: 10,
+ health: 22,
+ powers: [15, 0, 0, 0, 0],
+ stats: [0, 1, 2, 3, 0],
+ };
+ log.push_level_up_gains(&ding, 1);
+
+ // A level edge that is not this ding's leaves the entry parked...
+ assert!(log.take_level_up_gains(11).is_none(), "wrong level matched");
+ // ...and the right one takes it, exactly once.
+ let (got, talent_points) = log.take_level_up_gains(10).expect("parked for level 10");
+ assert_eq!(got, ding);
+ assert_eq!(talent_points, 1);
+ assert!(
+ log.take_level_up_gains(10).is_none(),
+ "the entry was taken, not copied"
+ );
+}
diff --git a/crates/benilla-app/src/ui_items/drain.rs b/crates/benilla-app/src/ui_items/drain.rs
index 2dc8a2c7..b5589152 100644
--- a/crates/benilla-app/src/ui_items/drain.rs
+++ b/crates/benilla-app/src/ui_items/drain.rs
@@ -1067,16 +1067,14 @@ mod bind_confirm_tests {
const F_OBJECT_ENTRY: u16 = 3;
fn load_ui(s: &UiScript) {
- for file in ["Fonts.xml", "MoneyFrame.xml", "UiPanels.xml"] {
- let text = std::fs::read_to_string(
- std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
- .join("assets/ui")
- .join(file),
- )
- .unwrap();
- let doc = benilla_ui::framexml::parse(&text).unwrap();
- let report = benilla_ui::loader::load(s, &doc, &|_| None);
- assert!(report.errors.is_empty(), "{file}: {:?}", report.errors);
+ // Through the chain-aware reader: this list names chain files now, and a
+ // reader that joins `assets/ui` cannot resolve one (1838, 1887, 1888).
+ for file in [
+ "Interface\\FrameXML\\Fonts.xml",
+ "MoneyFrame.xml",
+ "UiPanels.xml",
+ ] {
+ crate::ui_script::load_ui_for_test(s, file);
}
}
diff --git a/crates/benilla-app/src/ui_items/feed.rs b/crates/benilla-app/src/ui_items/feed.rs
index b8903b24..9c5951ee 100644
--- a/crates/benilla-app/src/ui_items/feed.rs
+++ b/crates/benilla-app/src/ui_items/feed.rs
@@ -1431,7 +1431,6 @@ fn diff_and_push(
}
}
memory.pushed = fresh;
- script.fire_event("BAG_UPDATE_DELAYED", vec![]);
return true;
}
// A `BAG_CLOSED` with nothing else to say still went into the VM — two identical bags swapped
diff --git a/crates/benilla-app/src/ui_loot.rs b/crates/benilla-app/src/ui_loot.rs
index b326194d..f4897e42 100644
--- a/crates/benilla-app/src/ui_loot.rs
+++ b/crates/benilla-app/src/ui_loot.rs
@@ -1111,11 +1111,10 @@ fn feed_loot(
script.fire_event("LOOT_SLOT_CLEARED", vec![ScriptValue::Int(i as i64 + 1)]);
}
}
- script.fire_event("LOOT_UPDATE", vec![]);
- // The reference keeps the two apart: `LOOT_UPDATE` repaints the rows, while a changed
- // candidate list refreshes the open dropdown in place without re-toggling it
- // (`LootFrame_OnEvent`'s `UIDropDownMenu_Refresh(GroupLootDropDown)`,
- // `LootFrame.lua:63`). Firing it only on a real change keeps a closed menu untouched.
+ // A changed candidate list refreshes the open dropdown in place without re-toggling
+ // it — `LootFrame_OnEvent`'s `UIDropDownMenu_Refresh(GroupLootDropDown)`, which the
+ // reference hangs off `UPDATE_MASTER_LOOT_LIST` (`LootFrame.lua:62-64`). Firing it
+ // only on a real change keeps a closed menu untouched.
if before.master_candidates != after.master_candidates {
script.fire_event("UPDATE_MASTER_LOOT_LIST", vec![]);
}
diff --git a/crates/benilla-app/src/ui_loot_roll.rs b/crates/benilla-app/src/ui_loot_roll.rs
index 46772150..c9d61b36 100644
--- a/crates/benilla-app/src/ui_loot_roll.rs
+++ b/crates/benilla-app/src/ui_loot_roll.rs
@@ -17,11 +17,12 @@
//! announcement lines into the chat window once their names resolve. [`drain_loot_rolls`] pulls the
//! `RollOnLoot` votes back out as [`ClientCommand::LootRoll`].
//!
-//! ## `UPDATE_LOOT_ROLL` — why a snapshot seam needs an event the real client doesn't have
+//! ## The late item template, and the `?` that outlives it
//!
-//! The reference's `GetLootRollItemInfo` reads live C state off the roll node, so its
-//! `GroupLootFrame_OnShow` can paint once and be done. Ours reads a **pushed snapshot**, and a
-//! snapshot is always at least one step behind the thing that made it change — twice over:
+//! The reference's `GetLootRollItemInfo` reads live C state off the roll node, so by the time
+//! `START_LOOT_ROLL` fires there is something to paint and `GroupLootFrame_OnShow` can paint once
+//! and be done. Ours reads a **pushed snapshot**, and a snapshot is at least one step behind the
+//! thing that changed it — twice over:
//!
//! 1. A roll is added to [`LootRolls::active`] and to `opened` in the same [`LootRolls::start`]
//! call, so the *first* snapshot that contains a roll is built in the same pass that announces
@@ -30,12 +31,20 @@
//! `None` until the query lands, which is typically several frames after the roll opens.
//!
//! (1) is fixed by ordering — the push precedes the events here, as it does in [`crate::ui_loot`].
-//! (2) cannot be, so `UPDATE_LOOT_ROLL(rollID)` fires whenever an open roll's *display identity*
-//! ([`display_identity`]) changes underneath a frame, and the frame repaints. Ordering alone would
-//! be a guarantee resting on system order in a file far from the Lua that depends on it; with the
-//! repair in place the paint is correct either way, and ordering only decides whether there is a
-//! visible blank first. Both halves ship: (1) for the frame you actually see, (2) because it is what
-//! makes the documented in-flight fallback resolve instead of staying a `?` forever.
+//!
+//! **(2) is currently a live defect: a roll whose template lands late paints `?` and keeps it.**
+//! We used to fire an `UPDATE_LOOT_ROLL(rollID)` when an open roll's display identity changed, and
+//! our own `GroupLootFrame` re-entered its paint from it. That frame is the reference's now, and
+//! the reference has no such seam — so the repaint stopped when the window migrated, and the event
+//! went on being fired into a room with nobody in it. 1883 removed it: an event the 1.12 client
+//! does not have, which nothing listened for, was not fixing anything.
+//!
+//! **Decision 1838 already carries the fix**, and it is the reference's own invariant rather than a
+//! re-invented seam: hold the roll until its template resolves, so the snapshot is populated when
+//! the frame first paints, exactly as the reference's C state is. The cost is the roll timer — a
+//! roll announced late is a roll with less time on it — so it wants a deadline (announce anyway
+//! after N ms, with the `?`) rather than an unbounded wait. Not built yet; 1883 only removed the
+//! event that was standing in for it.
//!
//! ## Two client-side behaviours, and why
//!
@@ -510,29 +519,6 @@ fn snapshot(
LootRollsState { rolls: entries }
}
-/// Everything about a roll a `GroupLootFrame` actually *paints* — deliberately **not**
-/// `time_left_ms`, which ticks every frame and would make "the snapshot changed" mean nothing.
-fn display_identity(e: &LootRollEntry) -> (&Option, &Option, Option, bool) {
- (&e.name, &e.texture, e.quality, e.bind_on_pickup)
-}
-
-/// The rolls open in *both* snapshots whose display identity changed — i.e. the ones with a frame
-/// already up that is now painting the wrong thing. A roll only in `fresh` is newly opened and gets
-/// its `START_LOOT_ROLL`; one only in `last` is closed and gets its `CANCEL_LOOT_ROLL`.
-fn repainted(last: &LootRollsState, fresh: &LootRollsState) -> Vec {
- fresh
- .rolls
- .iter()
- .filter(|f| {
- last.rolls
- .iter()
- .find(|l| l.roll_id == f.roll_id)
- .is_some_and(|l| display_identity(l) != display_identity(f))
- })
- .map(|f| f.roll_id)
- .collect()
-}
-
/// Tick the open rolls, push them into the VM, fire the open/close events, and drain the queued
/// announcement lines into chat.
#[allow(clippy::too_many_arguments)]
@@ -580,7 +566,6 @@ fn feed_loot_rolls(
// same `start()` call that queued `opened`, so pushing after would hand every fresh roll an
// empty lookup. Same order as ui_loot's window feed, for the same reason.
let fresh = snapshot(&rolls, &mut items, icons.as_deref(), &commands, catalogs);
- let changed = repainted(last, &fresh);
if fresh != *last {
script.set_loot_rolls(fresh.clone());
*last = fresh;
@@ -600,11 +585,6 @@ fn feed_loot_rolls(
debug!("ui_loot_roll: roll {roll_id} cancelled");
script.fire_event("CANCEL_LOOT_ROLL", vec![ScriptValue::Int(roll_id as i64)]);
}
- // The late item template landing under a frame that is already up (see the module docs).
- for roll_id in changed {
- debug!("ui_loot_roll: roll {roll_id} display identity resolved");
- script.fire_event("UPDATE_LOOT_ROLL", vec![ScriptValue::Int(roll_id as i64)]);
- }
}
/// Drain the Lua votes: each `RollOnLoot(rollID, rollType)` becomes one `CMSG_LOOT_ROLL` addressed
@@ -1008,55 +988,4 @@ mod tests {
r.start(start(0xBB, 0, 4306));
assert_eq!(r.active[0].roll_id, 2);
}
-
- fn entry(roll_id: u32, name: Option<&str>, time_left_ms: u32) -> LootRollEntry {
- LootRollEntry {
- roll_id,
- name: name.map(Into::into),
- texture: name.map(|_| "Interface\\Icons\\INV_Staff_12".into()),
- quantity: 1,
- quality: name.map(|_| 4),
- bind_on_pickup: name.is_some(),
- time_left_ms,
- item_id: 17182,
- // Lands with the name — one template answer fills both (decision 1059).
- link: name.map(|n| crate::ui_items::item_link(17182, n, 4)),
- random_property_id: 0,
- }
- }
-
- fn state(entries: &[LootRollEntry]) -> LootRollsState {
- LootRollsState {
- rolls: entries.to_vec(),
- }
- }
-
- /// `UPDATE_LOOT_ROLL` fires only when what a frame *paints* changed. The load-bearing half is
- /// the negative: `time_left_ms` ticks every single frame, so counting it would fire the repaint
- /// 60x a second and make the signal worthless (and the timer bar reads the model directly — it
- /// has never needed an event).
- #[test]
- fn only_a_display_change_asks_for_a_repaint() {
- let flight = state(&[entry(7, None, 60_000)]);
- let landed = state(&[entry(7, Some("Staff of Jordan"), 59_000)]);
-
- assert_eq!(repainted(&flight, &landed), vec![7], "the template landed");
- assert_eq!(repainted(&landed, &landed), Vec::::new(), "unchanged");
-
- // The timer alone, which is what changes on almost every frame.
- let ticked = state(&[entry(7, Some("Staff of Jordan"), 58_000)]);
- assert_eq!(
- repainted(&landed, &ticked),
- Vec::::new(),
- "a ticking bar is not a repaint"
- );
-
- // Opened and closed rolls are the START/CANCEL events' business, not this one's.
- let two = state(&[
- entry(7, Some("Staff of Jordan"), 59_000),
- entry(8, None, 60_000),
- ]);
- assert_eq!(repainted(&landed, &two), Vec::::new(), "8 is new");
- assert_eq!(repainted(&two, &landed), Vec::::new(), "8 closed");
- }
}
diff --git a/crates/benilla-app/src/ui_pet_stats.rs b/crates/benilla-app/src/ui_pet_stats.rs
index f0a54c0c..56d62128 100644
--- a/crates/benilla-app/src/ui_pet_stats.rs
+++ b/crates/benilla-app/src/ui_pet_stats.rs
@@ -29,7 +29,7 @@
use bevy::prelude::*;
-use benilla_ui::script::{PetStats, UiScript};
+use benilla_ui::script::{PetStats, ScriptValue, UiScript};
use crate::names::NameCache;
use crate::net::{NetCommands, ObjectStore};
@@ -317,7 +317,10 @@ fn feed_pet_stats(
script.set_pet_stats(fresh.0, fresh.1);
// Push before firing — dispatch runs the Lua handlers synchronously (the `ui_unit` rule).
if happiness_moved {
- script.fire_event("UNIT_HAPPINESS", vec![]);
+ // `%s` — the unit token, per the reference's own fire site (SignalEvent2, decision 1884).
+ // Every 1.12 `UNIT_*` event carries it, and its consumers gate on it: a handler's first
+ // line is `if ( arg1 == this.unit )`, so an argless fire reaches nobody.
+ script.fire_event("UNIT_HAPPINESS", vec![ScriptValue::Str("pet".into())]);
}
if xp_moved {
script.fire_event("UNIT_PET_EXPERIENCE", vec![]);
diff --git a/crates/benilla-app/src/ui_script/action_bar_tests.rs b/crates/benilla-app/src/ui_script/action_bar_tests.rs
index 7b3e9a97..79a57585 100644
--- a/crates/benilla-app/src/ui_script/action_bar_tests.rs
+++ b/crates/benilla-app/src/ui_script/action_bar_tests.rs
@@ -14,28 +14,16 @@ fn action_ids(s: &mut UiScript) -> Vec {
fn shipped_action_bar_drives_end_to_end() {
let mut s = UiScript::new().unwrap();
s.set_screen_size(1024.0, 768.0);
- for file in ["Cooldown.xml", "ActionBar.xml"] {
- let text = std::fs::read_to_string(
- std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
- .join("assets/ui")
- .join(file),
- )
- .unwrap();
- let doc = benilla_ui::framexml::parse(&text).unwrap();
- let report = benilla_ui::loader::load(&s, &doc, &|_| None);
- assert!(
- report.errors.is_empty(),
- "{file}: loader errors: {:?}",
- report.errors
- );
- if file == "ActionBar.xml" {
- assert_eq!(
- report.frames, 59,
- "bar + XP StatusBar (+ its numerals overlay) + exhaustion tick + max-level rail + art frame + 12 buttons (each with a Cooldown child) + 2 page buttons + the performance meter and its hover button, \
+ super::test_ui::load_ui(&s, "Cooldown.xml");
+ // `load_ui` returns the same `report.frames` the disk reader asserted on, so this
+ // count is the one that always stood here — moved, not re-derived.
+ super::test_ui::load_ui(&s, "Interface\\FrameXML\\ActionButtonTemplate.xml");
+ let frames = super::test_ui::load_ui(&s, "ActionBar.xml");
+ assert_eq!(
+ frames, 59,
+ "bar + XP StatusBar (+ its numerals overlay) + exhaustion tick + max-level rail + art frame + 12 buttons (each with a Cooldown child) + 2 page buttons + the performance meter and its hover button, \
+ BonusActionBarFrame and its 12 buttons with their Cooldown children (25 — hidden, as the reference's is; decision 1223) — and NO ReputationWatchBar: its three frames went home to the reference's own ReputationFrame.xml with 1875"
- );
- }
- }
+ );
// `ExhaustionTick_Update` indexes `ReputationWatchBar` UNGUARDED — the reference's own code,
// safe there because `ReputationFrame.xml` is always loaded and always declares the bar. Since
@@ -43,7 +31,7 @@ fn shipped_action_bar_drives_end_to_end() {
// or the tick raises on its first event.
// In manifest order: the fonts its check-box labels colour from, the panel templates those
// boxes inherit through, then the pane.
- super::test_ui::load_ui(&s, "Fonts.xml");
+ super::test_ui::load_ui(&s, "Interface\\FrameXML\\Fonts.xml");
super::test_ui::load_ui(&s, r"Interface\FrameXML\UIPanelTemplates.lua");
super::test_ui::load_ui(&s, r"Interface\FrameXML\UIPanelTemplates.xml");
super::test_ui::load_ui(&s, r"Interface\FrameXML\OptionsFrameTemplates.xml");
@@ -161,21 +149,9 @@ fn shipped_action_bar_drives_end_to_end() {
}
fn load_action_bar(s: &UiScript) {
- for file in ["Cooldown.xml", "ActionBar.xml"] {
- let text = std::fs::read_to_string(
- std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
- .join("assets/ui")
- .join(file),
- )
- .unwrap();
- let doc = benilla_ui::framexml::parse(&text).unwrap();
- let report = benilla_ui::loader::load(s, &doc, &|_| None);
- assert!(
- report.errors.is_empty(),
- "{file}: loader errors: {:?}",
- report.errors
- );
- }
+ super::test_ui::load_ui(s, "Cooldown.xml");
+ super::test_ui::load_ui(s, "Interface\\FrameXML\\ActionButtonTemplate.xml");
+ super::test_ui::load_ui(s, "ActionBar.xml");
// `ExhaustionTick_Update` indexes `ReputationWatchBar` UNGUARDED — the reference's own code,
// safe there because `ReputationFrame.xml` is always loaded and always declares the bar. Since
@@ -183,7 +159,7 @@ fn load_action_bar(s: &UiScript) {
// or the tick raises on its first event.
// In manifest order: the fonts its check-box labels colour from, the panel templates those
// boxes inherit through, then the pane.
- super::test_ui::load_ui(s, "Fonts.xml");
+ super::test_ui::load_ui(s, "Interface\\FrameXML\\Fonts.xml");
super::test_ui::load_ui(s, r"Interface\FrameXML\UIPanelTemplates.lua");
super::test_ui::load_ui(s, r"Interface\FrameXML\UIPanelTemplates.xml");
super::test_ui::load_ui(s, r"Interface\FrameXML\OptionsFrameTemplates.xml");
@@ -802,6 +778,7 @@ fn shipped_bag_frame_drives_end_to_end() {
for file in BAG_UI {
let frames = load_ui(&s, file);
if *file == "Cooldown.xml" {
+ load_ui(&s, "Interface\\FrameXML\\ActionButtonTemplate.xml");
load_ui(&s, "ActionBar.xml");
}
if *file == "Interface\\FrameXML\\MainMenuBarBagButtons.xml" {
@@ -1298,9 +1275,10 @@ fn the_main_bar_pages_and_a_bonus_page_still_outranks_it() {
s.set_screen_size(1024.0, 768.0);
for file in [
// Fonts first: the pane's check-box labels colour from `RED_FONT_COLOR` in their own OnLoad.
- "Fonts.xml",
+ "Interface\\FrameXML\\Fonts.xml",
"UIParent.xml",
"Cooldown.xml",
+ "Interface\\FrameXML\\ActionButtonTemplate.xml",
"ActionBar.xml",
// The reference declares the reputation WATCH BAR in `ReputationFrame.xml`, and
// `ExhaustionTick_Update` reads `ReputationWatchBar:IsShown()` twice — the reference's own
@@ -1430,17 +1408,9 @@ fn the_main_bar_pages_and_a_bonus_page_still_outranks_it() {
fn bonus_bar_slides_up_with_sound_and_down_without() {
let mut s = UiScript::new().unwrap();
s.set_screen_size(1024.0, 768.0);
- for file in ["Cooldown.xml", "ActionBar.xml"] {
- let text = std::fs::read_to_string(
- std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
- .join("assets/ui")
- .join(file),
- )
- .unwrap();
- let doc = benilla_ui::framexml::parse(&text).unwrap();
- let report = benilla_ui::loader::load(&s, &doc, &|_| None);
- assert!(report.errors.is_empty(), "{file}: {:?}", report.errors);
- }
+ super::test_ui::load_ui(&s, "Cooldown.xml");
+ super::test_ui::load_ui(&s, "Interface\\FrameXML\\ActionButtonTemplate.xml");
+ super::test_ui::load_ui(&s, "ActionBar.xml");
// `ExhaustionTick_Update` indexes `ReputationWatchBar` UNGUARDED — the reference's own code,
// safe there because `ReputationFrame.xml` is always loaded and always declares the bar. Since
@@ -1448,7 +1418,7 @@ fn bonus_bar_slides_up_with_sound_and_down_without() {
// or the tick raises on its first event.
// In manifest order: the fonts its check-box labels colour from, the panel templates those
// boxes inherit through, then the pane.
- super::test_ui::load_ui(&s, "Fonts.xml");
+ super::test_ui::load_ui(&s, "Interface\\FrameXML\\Fonts.xml");
super::test_ui::load_ui(&s, r"Interface\FrameXML\UIPanelTemplates.lua");
super::test_ui::load_ui(&s, r"Interface\FrameXML\UIPanelTemplates.xml");
super::test_ui::load_ui(&s, r"Interface\FrameXML\OptionsFrameTemplates.xml");
@@ -1621,17 +1591,9 @@ fn bonus_bar_slides_up_with_sound_and_down_without() {
fn bonus_bar_turnaround_continues_from_position() {
let mut s = UiScript::new().unwrap();
s.set_screen_size(1024.0, 768.0);
- for file in ["Cooldown.xml", "ActionBar.xml"] {
- let text = std::fs::read_to_string(
- std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
- .join("assets/ui")
- .join(file),
- )
- .unwrap();
- let doc = benilla_ui::framexml::parse(&text).unwrap();
- let report = benilla_ui::loader::load(&s, &doc, &|_| None);
- assert!(report.errors.is_empty(), "{file}: {:?}", report.errors);
- }
+ super::test_ui::load_ui(&s, "Cooldown.xml");
+ super::test_ui::load_ui(&s, "Interface\\FrameXML\\ActionButtonTemplate.xml");
+ super::test_ui::load_ui(&s, "ActionBar.xml");
// `ExhaustionTick_Update` indexes `ReputationWatchBar` UNGUARDED — the reference's own code,
// safe there because `ReputationFrame.xml` is always loaded and always declares the bar. Since
@@ -1639,7 +1601,7 @@ fn bonus_bar_turnaround_continues_from_position() {
// or the tick raises on its first event.
// In manifest order: the fonts its check-box labels colour from, the panel templates those
// boxes inherit through, then the pane.
- super::test_ui::load_ui(&s, "Fonts.xml");
+ super::test_ui::load_ui(&s, "Interface\\FrameXML\\Fonts.xml");
super::test_ui::load_ui(&s, r"Interface\FrameXML\UIPanelTemplates.lua");
super::test_ui::load_ui(&s, r"Interface\FrameXML\UIPanelTemplates.xml");
super::test_ui::load_ui(&s, r"Interface\FrameXML\OptionsFrameTemplates.xml");
@@ -1688,17 +1650,9 @@ fn bonus_bar_turnaround_continues_from_position() {
fn the_page_arrows_do_not_steal_each_other_s_clicks() {
let mut s = UiScript::new().unwrap();
s.set_screen_size(1024.0, 768.0);
- for file in ["Cooldown.xml", "ActionBar.xml"] {
- let text = std::fs::read_to_string(
- std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
- .join("assets/ui")
- .join(file),
- )
- .unwrap();
- let doc = benilla_ui::framexml::parse(&text).unwrap();
- let report = benilla_ui::loader::load(&s, &doc, &|_| None);
- assert!(report.errors.is_empty(), "{file}: {:?}", report.errors);
- }
+ super::test_ui::load_ui(&s, "Cooldown.xml");
+ super::test_ui::load_ui(&s, "Interface\\FrameXML\\ActionButtonTemplate.xml");
+ super::test_ui::load_ui(&s, "ActionBar.xml");
// `ExhaustionTick_Update` indexes `ReputationWatchBar` UNGUARDED — the reference's own code,
// safe there because `ReputationFrame.xml` is always loaded and always declares the bar. Since
@@ -1706,7 +1660,7 @@ fn the_page_arrows_do_not_steal_each_other_s_clicks() {
// or the tick raises on its first event.
// In manifest order: the fonts its check-box labels colour from, the panel templates those
// boxes inherit through, then the pane.
- super::test_ui::load_ui(&s, "Fonts.xml");
+ super::test_ui::load_ui(&s, "Interface\\FrameXML\\Fonts.xml");
super::test_ui::load_ui(&s, r"Interface\FrameXML\UIPanelTemplates.lua");
super::test_ui::load_ui(&s, r"Interface\FrameXML\UIPanelTemplates.xml");
super::test_ui::load_ui(&s, r"Interface\FrameXML\OptionsFrameTemplates.xml");
diff --git a/crates/benilla-app/src/ui_script/basic_controls_tests.rs b/crates/benilla-app/src/ui_script/basic_controls_tests.rs
index f17253b9..62bd6963 100644
--- a/crates/benilla-app/src/ui_script/basic_controls_tests.rs
+++ b/crates/benilla-app/src/ui_script/basic_controls_tests.rs
@@ -12,7 +12,7 @@ use super::test_ui::load_ui as load_xml;
/// Fonts then BasicControls — the manifest's own order, and the whole dependency this file has.
fn basic_controls() -> UiScript {
let mut s = UiScript::new().unwrap();
- load_xml(&s, "Fonts.xml");
+ load_xml(&s, "Interface\\FrameXML\\Fonts.xml");
load_xml(&s, "Interface\\FrameXML\\BasicControls.xml");
s.set_screen_size(1024.0, 768.0);
s.resolve();
diff --git a/crates/benilla-app/src/ui_script/binder_tests.rs b/crates/benilla-app/src/ui_script/binder_tests.rs
index e200fbde..c84d646d 100644
--- a/crates/benilla-app/src/ui_script/binder_tests.rs
+++ b/crates/benilla-app/src/ui_script/binder_tests.rs
@@ -11,7 +11,7 @@ use super::test_ui::load_ui as load_xml;
fn setup() -> UiScript {
let mut s = UiScript::new().unwrap();
s.set_screen_size(1024.0, 768.0);
- load_xml(&s, "Fonts.xml");
+ load_xml(&s, "Interface\\FrameXML\\Fonts.xml");
load_xml(&s, "MoneyFrame.xml");
load_xml(&s, "UiPanels.xml");
load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.lua");
diff --git a/crates/benilla-app/src/ui_script/buff_tests.rs b/crates/benilla-app/src/ui_script/buff_tests.rs
index 0f1e9b8c..e10f8bb2 100644
--- a/crates/benilla-app/src/ui_script/buff_tests.rs
+++ b/crates/benilla-app/src/ui_script/buff_tests.rs
@@ -26,10 +26,10 @@ fn harness() -> UiScript {
// under a timed aura. Our copy carried them as `X = X or "%d s"` fallbacks; the reference's
// file formats them straight and `format(nil, …)` raises.
load_xml(&s, "Interface\\FrameXML\\GlobalStrings.lua");
- load_xml(&s, "Fonts.xml"); // NORMAL/HIGHLIGHT_FONT_COLOR + the FontStrings' faces
- // `GameTooltip`, which the reference's BuffButton_Update indexes on EVERY repaint to ask
- // `IsOwned(this)` (BuffFrame.lua l.104) — not just on hover. Ours guarded it; the reference
- // does not, so a session without the tooltip loses the whole repaint.
+ load_xml(&s, "Interface\\FrameXML\\Fonts.xml"); // NORMAL/HIGHLIGHT_FONT_COLOR + the FontStrings' faces
+ // `GameTooltip`, which the reference's BuffButton_Update indexes on EVERY repaint to ask
+ // `IsOwned(this)` (BuffFrame.lua l.104) — not just on hover. Ours guarded it; the reference
+ // does not, so a session without the tooltip loses the whole repaint.
load_xml(&s, "GameTooltip.xml");
// `SecondsToTimeAbbrev`, which 1.12 keeps in UIParent.lua and so do we since window 18.
load_xml(&s, "MoneyFrame.xml");
@@ -38,6 +38,7 @@ fn harness() -> UiScript {
load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.xml");
load_xml(&s, "UIParent.xml");
load_xml(&s, "Cooldown.xml");
+ load_xml(&s, "Interface\\FrameXML\\ActionButtonTemplate.xml");
load_xml(&s, "ActionBar.xml"); // BENILLA_FALLBACK_ICON (the unknown-icon fallback)
// The timer switch, PLANTED ON — not the shipped value. 1.12 declares it in
// UIOptionsFrame.lua (default "0"); we have no counterpart to that file, so it lives with the
diff --git a/crates/benilla-app/src/ui_script/cast_tests.rs b/crates/benilla-app/src/ui_script/cast_tests.rs
index 362fadaa..6002d0b6 100644
--- a/crates/benilla-app/src/ui_script/cast_tests.rs
+++ b/crates/benilla-app/src/ui_script/cast_tests.rs
@@ -15,8 +15,12 @@ use super::test_ui::load_ui as load_xml;
fn harness() -> UiScript {
let mut s = UiScript::new().unwrap();
s.set_screen_size(1024.0, 768.0);
- load_xml(&s, "Fonts.xml");
- load_xml(&s, "CastingBar.xml");
+ load_xml(&s, "Interface\\FrameXML\\Fonts.xml");
+ // The stock file writes `CastingBarText:SetText(FAILED)` rather than a literal, so the window
+ // needs GlobalStrings — which the manifest has above it, and which our own retired
+ // `CastingBar.xml` did not need because it hardcoded "Failed".
+ load_xml(&s, "Interface\\FrameXML\\GlobalStrings.lua");
+ load_xml(&s, "Interface\\FrameXML\\CastingBarFrame.xml");
s
}
@@ -381,9 +385,9 @@ fn bottom(s: &UiScript, name: &str) -> f64 {
fn managed_positions_track_the_bottom_bar_stack() {
let mut s = UiScript::new().unwrap();
s.set_screen_size(1024.0, 768.0);
- load_xml(&s, "Fonts.xml");
+ load_xml(&s, "Interface\\FrameXML\\Fonts.xml");
load_xml(&s, "UIParent.xml");
- load_xml(&s, "CastingBar.xml");
+ load_xml(&s, "Interface\\FrameXML\\CastingBarFrame.xml");
load_xml(&s, "Interface\\FrameXML\\UIMenu.xml"); // the kit the chat menus build from
load_xml(&s, "ChatFrame.xml");
diff --git a/crates/benilla-app/src/ui_script/character_tests.rs b/crates/benilla-app/src/ui_script/character_tests.rs
index 1178204f..25906786 100644
--- a/crates/benilla-app/src/ui_script/character_tests.rs
+++ b/crates/benilla-app/src/ui_script/character_tests.rs
@@ -1198,7 +1198,7 @@ fn an_addons_tab_click_selects_through_the_generic_entry_point() {
let _data = benilla_formats::wow_data_or_skip!();
let mut s = UiScript::new().unwrap();
s.set_screen_size(1024.0, 768.0);
- load_xml(&s, "Fonts.xml");
+ load_xml(&s, "Interface\\FrameXML\\Fonts.xml");
load_xml(&s, "MoneyFrame.xml");
load_xml(&s, "UiPanels.xml");
// The reference's `PanelTemplates_SelectTab` ends with `if GameTooltip:IsOwned(tab)` —
diff --git a/crates/benilla-app/src/ui_script/chat_options_tests.rs b/crates/benilla-app/src/ui_script/chat_options_tests.rs
index 7be47096..17a776ca 100644
--- a/crates/benilla-app/src/ui_script/chat_options_tests.rs
+++ b/crates/benilla-app/src/ui_script/chat_options_tests.rs
@@ -39,7 +39,7 @@ fn bake_strings(s: &UiScript) {
fn chat_with_menu() -> UiScript {
let mut s = UiScript::new().unwrap();
for file in [
- "Fonts.xml",
+ "Interface\\FrameXML\\Fonts.xml",
"MoneyFrame.xml",
"UiPanels.xml",
"UIParent.xml",
diff --git a/crates/benilla-app/src/ui_script/chat_resize_tests.rs b/crates/benilla-app/src/ui_script/chat_resize_tests.rs
index 95720f71..be101254 100644
--- a/crates/benilla-app/src/ui_script/chat_resize_tests.rs
+++ b/crates/benilla-app/src/ui_script/chat_resize_tests.rs
@@ -17,7 +17,7 @@ use super::test_ui::load_ui as load_xml;
fn chat_ui() -> UiScript {
let mut s = UiScript::new().unwrap();
for file in [
- "Fonts.xml",
+ "Interface\\FrameXML\\Fonts.xml",
"MoneyFrame.xml",
"UiPanels.xml",
"UIParent.xml",
diff --git a/crates/benilla-app/src/ui_script/chat_tests.rs b/crates/benilla-app/src/ui_script/chat_tests.rs
index 2c73aa21..9072c731 100644
--- a/crates/benilla-app/src/ui_script/chat_tests.rs
+++ b/crates/benilla-app/src/ui_script/chat_tests.rs
@@ -12,7 +12,7 @@ use super::test_ui::load_ui as load_xml;
/// The full ChatFrame stack (fonts first, so the FontString's `inherits="ChatFontNormal"` resolves).
fn chat_frame() -> UiScript {
let mut s = UiScript::new().unwrap();
- load_xml(&s, "Fonts.xml");
+ load_xml(&s, "Interface\\FrameXML\\Fonts.xml");
// GameTooltip.xml + UIDropDownMenu.xml are real RUNTIME dependencies of the chat tabs since
// decision 1589: a left click closes any open menu (`CloseDropDownMenus`, the reference's own
// first move in `FCF_Tab_OnClick`) and a right click opens the window's options menu.
@@ -241,7 +241,7 @@ fn chat_click_dismisses_a_stuck_spell_but_not_an_item() {
let mut s = UiScript::new().unwrap();
s.set_screen_size(1024.0, 768.0);
for f in [
- "Fonts.xml",
+ "Interface\\FrameXML\\Fonts.xml",
"MoneyFrame.xml",
"UiPanels.xml",
r"Interface\FrameXML\UIPanelTemplates.lua",
@@ -978,7 +978,7 @@ fn the_chat_menu_builds_its_rows_on_the_references_kit() {
let mut s = UiScript::new().unwrap();
s.set_screen_size(1024.0, 768.0);
for f in [
- "Fonts.xml",
+ "Interface\\FrameXML\\Fonts.xml",
"MoneyFrame.xml",
"UiPanels.xml",
r"Interface\FrameXML\UIPanelTemplates.lua",
diff --git a/crates/benilla-app/src/ui_script/cinematic_tests.rs b/crates/benilla-app/src/ui_script/cinematic_tests.rs
index c8245178..1c6e8ad2 100644
--- a/crates/benilla-app/src/ui_script/cinematic_tests.rs
+++ b/crates/benilla-app/src/ui_script/cinematic_tests.rs
@@ -22,7 +22,7 @@ use super::test_ui::load_ui as load_xml;
fn ui_with_the_cinematic_frame() -> UiScript {
let mut s = UiScript::new().unwrap();
s.set_screen_size(1024.0, 768.0);
- load_xml(&s, "Fonts.xml");
+ load_xml(&s, "Interface\\FrameXML\\Fonts.xml");
load_xml(&s, "UIParent.xml");
load_xml(&s, "MoneyFrame.xml"); // StaticPopup's money row, or UiPanels errors at load
load_xml(&s, "UiPanels.xml");
diff --git a/crates/benilla-app/src/ui_script/color_picker_tests.rs b/crates/benilla-app/src/ui_script/color_picker_tests.rs
index 9c029faa..6bebc046 100644
--- a/crates/benilla-app/src/ui_script/color_picker_tests.rs
+++ b/crates/benilla-app/src/ui_script/color_picker_tests.rs
@@ -17,7 +17,7 @@ use super::test_ui::load_ui as load_xml;
fn picker() -> UiScript {
let mut s = UiScript::new().unwrap();
for file in [
- "Fonts.xml",
+ "Interface\\FrameXML\\Fonts.xml",
"MoneyFrame.xml",
"UiPanels.xml",
"UIParent.xml",
diff --git a/crates/benilla-app/src/ui_script/combat_text_tests.rs b/crates/benilla-app/src/ui_script/combat_text_tests.rs
index c03d36d1..149bebbe 100644
--- a/crates/benilla-app/src/ui_script/combat_text_tests.rs
+++ b/crates/benilla-app/src/ui_script/combat_text_tests.rs
@@ -18,7 +18,7 @@ use super::test_ui::load_ui as load_xml;
fn load_combat_text() -> UiScript {
let mut s = UiScript::new().unwrap();
s.set_screen_size(1024.0, 768.0);
- load_xml(&s, "Fonts.xml");
+ load_xml(&s, "Interface\\FrameXML\\Fonts.xml");
load_xml(&s, "UIParent.xml");
load_xml(&s, "CombatText.xml");
s.run("SHOW_COMBAT_TEXT = \"1\"; CombatText_UpdateDisplayedMessages()")
diff --git a/crates/benilla-app/src/ui_script/combo_frame_tests.rs b/crates/benilla-app/src/ui_script/combo_frame_tests.rs
index 6c3ef75e..ea5c1716 100644
--- a/crates/benilla-app/src/ui_script/combo_frame_tests.rs
+++ b/crates/benilla-app/src/ui_script/combo_frame_tests.rs
@@ -18,7 +18,7 @@ use super::test_ui::load_ui as load_xml;
fn load_combo_frame() -> UiScript {
let mut s = UiScript::new().unwrap();
s.set_screen_size(1024.0, 768.0);
- load_xml(&s, "Fonts.xml");
+ load_xml(&s, "Interface\\FrameXML\\Fonts.xml");
load_xml(&s, "UIParent.xml");
load_xml(&s, "GameTooltip.xml");
load_xml(&s, "Interface\\FrameXML\\UIDropDownMenu.xml");
diff --git a/crates/benilla-app/src/ui_script/death_tests.rs b/crates/benilla-app/src/ui_script/death_tests.rs
index 7dcf4639..a47de347 100644
--- a/crates/benilla-app/src/ui_script/death_tests.rs
+++ b/crates/benilla-app/src/ui_script/death_tests.rs
@@ -10,7 +10,7 @@ use super::test_ui::load_ui as load_xml;
fn setup() -> UiScript {
let mut s = UiScript::new().unwrap();
s.set_screen_size(1024.0, 768.0);
- load_xml(&s, "Fonts.xml");
+ load_xml(&s, "Interface\\FrameXML\\Fonts.xml");
load_xml(&s, "MoneyFrame.xml");
load_xml(&s, "UiPanels.xml");
load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.lua");
diff --git a/crates/benilla-app/src/ui_script/delete_item_tests.rs b/crates/benilla-app/src/ui_script/delete_item_tests.rs
index 7a4ee2b1..2778770d 100644
--- a/crates/benilla-app/src/ui_script/delete_item_tests.rs
+++ b/crates/benilla-app/src/ui_script/delete_item_tests.rs
@@ -55,7 +55,7 @@ fn one_item_backpack(item_id: u32, name: &str, quality: u32) -> ContainerState {
fn setup() -> UiScript {
let mut s = UiScript::new().unwrap();
s.set_screen_size(1024.0, 768.0);
- load_xml(&s, "Fonts.xml");
+ load_xml(&s, "Interface\\FrameXML\\Fonts.xml");
load_xml(&s, "MoneyFrame.xml");
load_xml(&s, "UiPanels.xml");
load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.lua");
diff --git a/crates/benilla-app/src/ui_script/dressup_tests.rs b/crates/benilla-app/src/ui_script/dressup_tests.rs
index c310e687..b5a4fdfc 100644
--- a/crates/benilla-app/src/ui_script/dressup_tests.rs
+++ b/crates/benilla-app/src/ui_script/dressup_tests.rs
@@ -49,7 +49,7 @@ const ROOM_UI: &[&str] = &[
/// cannot.
fn load_room(s: &UiScript) {
for file in [
- "Fonts.xml",
+ "Interface\\FrameXML\\Fonts.xml",
"MoneyFrame.xml",
"UiPanels.xml",
r"Interface\FrameXML\UIPanelTemplates.lua",
diff --git a/crates/benilla-app/src/ui_script/dropdown_tests.rs b/crates/benilla-app/src/ui_script/dropdown_tests.rs
index 6f6a59b3..7f7ff0f5 100644
--- a/crates/benilla-app/src/ui_script/dropdown_tests.rs
+++ b/crates/benilla-app/src/ui_script/dropdown_tests.rs
@@ -20,7 +20,7 @@ use super::test_ui::load_ui as load_xml;
/// which GameTooltip.xml declares.
fn load_dropdown_kit(s: &UiScript) {
for file in [
- "Fonts.xml",
+ "Interface\\FrameXML\\Fonts.xml",
"UIParent.xml",
"GameTooltip.xml",
"Interface\\FrameXML\\UIDropDownMenu.xml",
diff --git a/crates/benilla-app/src/ui_script/duel_tests.rs b/crates/benilla-app/src/ui_script/duel_tests.rs
index 374b2f87..e6e8fac7 100644
--- a/crates/benilla-app/src/ui_script/duel_tests.rs
+++ b/crates/benilla-app/src/ui_script/duel_tests.rs
@@ -10,7 +10,7 @@ use super::test_ui::load_ui as load_xml;
fn setup() -> UiScript {
let mut s = UiScript::new().unwrap();
s.set_screen_size(1024.0, 768.0);
- load_xml(&s, "Fonts.xml");
+ load_xml(&s, "Interface\\FrameXML\\Fonts.xml");
load_xml(&s, "MoneyFrame.xml");
load_xml(&s, "UiPanels.xml");
load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.lua");
diff --git a/crates/benilla-app/src/ui_script/durability_tests.rs b/crates/benilla-app/src/ui_script/durability_tests.rs
index 04172362..230ce4fd 100644
--- a/crates/benilla-app/src/ui_script/durability_tests.rs
+++ b/crates/benilla-app/src/ui_script/durability_tests.rs
@@ -15,7 +15,7 @@ fn harness() -> UiScript {
let mut s = UiScript::new().unwrap();
s.set_screen_size(1024.0, 768.0);
for f in [
- "Fonts.xml",
+ "Interface\\FrameXML\\Fonts.xml",
"MoneyFrame.xml",
"UiPanels.xml",
r"Interface\FrameXML\UIPanelTemplates.lua",
@@ -387,7 +387,7 @@ fn the_quest_tracker_stacks_below_the_durability_guy() {
let mut s = UiScript::new().unwrap();
s.set_screen_size(1024.0, 768.0);
for f in [
- "Fonts.xml",
+ "Interface\\FrameXML\\Fonts.xml",
"MoneyFrame.xml",
"UiPanels.xml",
r"Interface\FrameXML\UIPanelTemplates.lua",
diff --git a/crates/benilla-app/src/ui_script/enchant_confirm_tests.rs b/crates/benilla-app/src/ui_script/enchant_confirm_tests.rs
index 9e2dabaf..fbb7031d 100644
--- a/crates/benilla-app/src/ui_script/enchant_confirm_tests.rs
+++ b/crates/benilla-app/src/ui_script/enchant_confirm_tests.rs
@@ -15,7 +15,7 @@ use super::test_ui::load_ui as load_xml;
fn setup() -> UiScript {
let mut s = UiScript::new().unwrap();
s.set_screen_size(1024.0, 768.0);
- load_xml(&s, "Fonts.xml");
+ load_xml(&s, "Interface\\FrameXML\\Fonts.xml");
load_xml(&s, "MoneyFrame.xml");
load_xml(&s, "UiPanels.xml");
load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.lua");
diff --git a/crates/benilla-app/src/ui_script/errors_tests.rs b/crates/benilla-app/src/ui_script/errors_tests.rs
index 2d4be695..b3dba47f 100644
--- a/crates/benilla-app/src/ui_script/errors_tests.rs
+++ b/crates/benilla-app/src/ui_script/errors_tests.rs
@@ -39,7 +39,7 @@ fn info_and_error_messages_stack_hold_and_expire() {
let _data = benilla_formats::wow_data_or_skip!();
let mut s = UiScript::new().unwrap();
s.set_screen_size(1024.0, 768.0);
- load_xml(&s, "Fonts.xml");
+ load_xml(&s, "Interface\\FrameXML\\Fonts.xml");
load_xml(&s, "Interface\\FrameXML\\UIErrorsFrame.xml");
// Empty at load. The frame itself is shown — a MessageFrame with nothing to say simply draws
@@ -109,7 +109,7 @@ fn an_error_toast_draws_over_an_open_panel_window() {
let _data = benilla_formats::wow_data_or_skip!();
let mut s = UiScript::new().unwrap();
s.set_screen_size(1024.0, 768.0);
- load_xml(&s, "Fonts.xml");
+ load_xml(&s, "Interface\\FrameXML\\Fonts.xml");
load_xml(&s, "MoneyFrame.xml");
load_xml(&s, "UiPanels.xml");
load_xml(&s, "GameTooltip.xml");
diff --git a/crates/benilla-app/src/ui_script/escape_tests.rs b/crates/benilla-app/src/ui_script/escape_tests.rs
index 62a7c9b4..4a47ad81 100644
--- a/crates/benilla-app/src/ui_script/escape_tests.rs
+++ b/crates/benilla-app/src/ui_script/escape_tests.rs
@@ -178,7 +178,7 @@ fn escape_is_consumed_by_a_focused_editbox_and_leaves_windows_open() {
fn escape_closes_the_options_window_before_opening_the_menu() {
let mut s = UiScript::new().unwrap();
s.set_screen_size(1024.0, 768.0);
- load_xml(&s, "Fonts.xml");
+ load_xml(&s, "Interface\\FrameXML\\Fonts.xml");
load_xml(&s, "MoneyFrame.xml");
load_xml(&s, "UiPanels.xml");
load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.lua");
@@ -478,7 +478,7 @@ fn escape_ladder_targeting_rung_after_cast_before_windows() {
fn an_addon_frame_registered_in_uispecialframes_closes_on_escape() {
let mut s = UiScript::new().unwrap();
s.set_screen_size(1024.0, 768.0);
- load_xml(&s, "Fonts.xml");
+ load_xml(&s, "Interface\\FrameXML\\Fonts.xml");
load_xml(&s, "MoneyFrame.xml");
load_xml(&s, "UiPanels.xml");
load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.lua");
diff --git a/crates/benilla-app/src/ui_script/exp_bar_tests.rs b/crates/benilla-app/src/ui_script/exp_bar_tests.rs
index b7b1a54e..e9f6c317 100644
--- a/crates/benilla-app/src/ui_script/exp_bar_tests.rs
+++ b/crates/benilla-app/src/ui_script/exp_bar_tests.rs
@@ -15,12 +15,13 @@ fn exp_bar_harness() -> UiScript {
// is the reference's own since 1751, and a hand-rolled reader here would look for it in the
// wrong store — and would leave its `` unresolved besides.
for file in [
- "Fonts.xml",
+ "Interface\\FrameXML\\Fonts.xml",
"UIParent.xml",
"GameTooltip.xml",
"Interface\\FrameXML\\TextStatusBar.lua",
"Interface\\FrameXML\\TextStatusBar.xml",
"Cooldown.xml",
+ "Interface\\FrameXML\\ActionButtonTemplate.xml",
"ActionBar.xml",
// The reference declares the reputation WATCH BAR in `ReputationFrame.xml`, and
// `ExhaustionTick_Update` reads `ReputationWatchBar:IsShown()` twice — the reference's own
diff --git a/crates/benilla-app/src/ui_script/faux_scroll_tests.rs b/crates/benilla-app/src/ui_script/faux_scroll_tests.rs
index ed084ed3..21bd7802 100644
--- a/crates/benilla-app/src/ui_script/faux_scroll_tests.rs
+++ b/crates/benilla-app/src/ui_script/faux_scroll_tests.rs
@@ -40,7 +40,7 @@ fn load_inline(s: &UiScript, xml: &str) {
fn harness() -> UiScript {
let mut s = UiScript::new().unwrap();
s.set_screen_size(1024.0, 768.0);
- load_xml(&s, "Fonts.xml");
+ load_xml(&s, "Interface\\FrameXML\\Fonts.xml");
load_xml(&s, "ScrollTemplates.xml");
load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.lua");
load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.xml");
@@ -399,7 +399,7 @@ fn the_reference_on_vertical_scroll_path_runs_once_a_scroll_child_exists() {
#[test]
fn scrolling_edit_helpers_answer_bare_calls_from_a_handler() {
let s = UiScript::new().unwrap();
- load_xml(&s, "Fonts.xml");
+ load_xml(&s, "Interface\\FrameXML\\Fonts.xml");
load_xml(&s, "ScrollTemplates.xml");
load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.lua");
load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.xml");
diff --git a/crates/benilla-app/src/ui_script/friends_tests.rs b/crates/benilla-app/src/ui_script/friends_tests.rs
index 7c649486..7cb9001c 100644
--- a/crates/benilla-app/src/ui_script/friends_tests.rs
+++ b/crates/benilla-app/src/ui_script/friends_tests.rs
@@ -15,13 +15,14 @@ use super::test_ui::load_ui_strict as load_xml;
fn setup() -> UiScript {
let mut s = UiScript::new().unwrap();
s.set_screen_size(1024.0, 768.0);
- load_xml(&s, "Fonts.xml");
+ load_xml(&s, "Interface\\FrameXML\\Fonts.xml");
load_xml(&s, "MoneyFrame.xml");
load_xml(&s, "UiPanels.xml");
load_xml(&s, "GameTooltip.xml");
load_xml(&s, "Interface\\FrameXML\\UIDropDownMenu.xml");
// The row right-click menu is the shared UnitPopup "FRIEND" menu, so the window's slice of
// the manifest includes it (it loads well before FriendsFrame.xml in the real order).
+ load_xml(&s, "UIParent.xml");
load_xml(&s, "UnitPopup.xml");
load_xml(&s, "ScrollTemplates.xml");
// The guild pane's frames inherit the reference's shared UIPanelButtonTemplate /
diff --git a/crates/benilla-app/src/ui_script/game_menu_tests.rs b/crates/benilla-app/src/ui_script/game_menu_tests.rs
index dfd950b6..f64af21c 100644
--- a/crates/benilla-app/src/ui_script/game_menu_tests.rs
+++ b/crates/benilla-app/src/ui_script/game_menu_tests.rs
@@ -37,7 +37,7 @@ fn harness_with(extra: &[&str]) -> UiScript {
let mut s = UiScript::new().unwrap();
s.set_screen_size(1024.0, 768.0);
let files: Vec<&str> = [
- "Fonts.xml",
+ "Interface\\FrameXML\\Fonts.xml",
// `GameMenuFrame` and the panels it opens declare `parent="UIParent"`, resolved at LOAD
// (decision 1734) — UIParent must already be there, as it is in the manifest.
"UIParent.xml",
@@ -662,7 +662,11 @@ fn the_bag_row_greys_under_the_menu_without_any_of_it_disappearing() {
// bar anchors into and seats itself above (`BenillaActionBarArt_SeatAbove`, nil-guarded in
// BagFrame.xml precisely because most harnesses load no bar).
let mut s = bag_harness_with(
- &["Cooldown.xml", "ActionBar.xml"],
+ &[
+ "Cooldown.xml",
+ "Interface\\FrameXML\\ActionButtonTemplate.xml",
+ "ActionBar.xml",
+ ],
&["Interface\\FrameXML\\MerchantFrame.xml"],
);
s.set_money(0);
diff --git a/crates/benilla-app/src/ui_script/group_loot_tests.rs b/crates/benilla-app/src/ui_script/group_loot_tests.rs
index b65ead62..48956876 100644
--- a/crates/benilla-app/src/ui_script/group_loot_tests.rs
+++ b/crates/benilla-app/src/ui_script/group_loot_tests.rs
@@ -70,8 +70,8 @@ fn setup() -> UiScript {
// loader warns on a key with no global behind it rather than failing — which is exactly the
// kind of warning `load_ui_no_warnings` is here to catch (decision 1838).
load_xml(&s, r"Interface\FrameXML\GlobalStrings.lua");
- load_xml(&s, "Fonts.xml"); // ITEM_QUALITY_COLORS + GameFontNormalSmall
- // The loot window's slots inherit it — the same dependency the inspect window needed (1832).
+ load_xml(&s, "Interface\\FrameXML\\Fonts.xml"); // ITEM_QUALITY_COLORS + GameFontNormalSmall
+ // The loot window's slots inherit it — the same dependency the inspect window needed (1832).
load_xml(&s, r"Interface\FrameXML\ItemButtonTemplate.xml");
// `UIPanelCloseButton`, which the loot window's four close buttons inherit.
load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.lua");
@@ -80,6 +80,7 @@ fn setup() -> UiScript {
load_xml(&s, "UiPanels.xml");
load_xml(&s, "GameTooltip.xml"); // PASS/NEED/GREED + item hovers
load_xml(&s, "Cooldown.xml");
+ load_xml(&s, "Interface\\FrameXML\\ActionButtonTemplate.xml");
load_xml(&s, "ActionBar.xml"); // BENILLA_FALLBACK_ICON (the in-flight icon fallback) —
// buff_tests.rs's own load-order precedent for this same global.
s
@@ -444,9 +445,12 @@ fn in_flight_roll_does_not_error_and_falls_back() {
/// exactly how a roll dialog that shipped "green" reached the director showing a `?` icon and a
/// blank name.
///
-/// The guarantee this pins is therefore not an ordering but a *repair*: whenever a roll's display
-/// identity changes under an open frame — the snapshot finally arriving, or a late item template —
-/// `UPDATE_LOOT_ROLL(rollID)` repaints it.
+/// What this pins now is the DEFECT, deliberately: a roll whose template lands after the frame is
+/// up keeps its `?`. Our retired frame repaired it from an `UPDATE_LOOT_ROLL(rollID)`; the stock
+/// frame has no such seam and never listened, so 1883 stopped firing an event the 1.12 client does
+/// not have. Decision 1838 carries the real fix — hold the roll until its template resolves, which
+/// is the reference's own invariant. This test goes green-to-red the day that lands, which is the
+/// point of writing it against the current behaviour rather than deleting it.
#[test]
fn a_roll_that_opens_before_its_snapshot_stays_blank() {
let _data = benilla_formats::wow_data_or_skip!();
@@ -473,11 +477,12 @@ fn a_roll_that_opens_before_its_snapshot_stays_blank() {
// ...and then the snapshot carrying it lands.
s.set_loot_rolls(rolls());
- s.fire_event("UPDATE_LOOT_ROLL", vec![ScriptValue::Int(7)]);
assert!(s.errors().is_empty(), "script errors: {:?}", s.errors());
// **The repaint does not happen, and this is the migration's one real loss.** Our retired file
- // split the paint out of `OnShow` precisely so `UPDATE_LOOT_ROLL` could re-enter it; the
+ // split the paint out of `OnShow` precisely so an `UPDATE_LOOT_ROLL` could re-enter it — an
+ // event the 1.12 client does not have, which 1883 stopped firing once the stock frame proved
+ // nothing listened for it. The
// reference has no such seam, because its `GetLootRollItemInfo` reads live C state that is
// already populated when `START_LOOT_ROLL` fires. Ours reads a pushed snapshot, and while
// `feed_loot_rolls` does push it BEFORE firing, the item template can still be in flight.
@@ -506,9 +511,8 @@ fn a_roll_that_opens_before_its_snapshot_stays_blank() {
}),
"and no icon arrives either — same missing repaint, same one cause"
);
- // An update for a roll this frame does not hold still leaves it alone and still does not
+ // A snapshot naming a roll this frame does not hold still leaves it alone and still does not
// error — the half of this test that survives the migration intact.
- s.fire_event("UPDATE_LOOT_ROLL", vec![ScriptValue::Int(999)]);
assert!(s.errors().is_empty(), "script errors: {:?}", s.errors());
assert_eq!(
s.eval::