the action bars' empty slots go ghost, and a rooted creature stays put
- picking a spell up off the bar no longer blacks out every empty slot: `UI-Quickslot` is a FILLED 60%-black plate, not a hollow ring (decoded off the shipped BLPs — Quickslot2's interior is RGBA 0,0,0,0, Quickslot's 0,0,0,153), and the reference dims it to half alpha in `ActionButton_ShowGrid`, so an empty well now ghosts at 30% black where it read as a hole; the wow-re cross-check then found the reference never UN-dims it (a 3-argument `SetVertexColor` preserves the current alpha at `0x79abd0`, and `SetNormalTexture` reuses the region), so in the real client the first drag leaves every button's border at half alpha for the rest of the session — benilla ends the dim on purpose and records it as the divergence it is (1782) - the bag bar is the reference's own `MainMenuBarBagButtons.xml` (window 3 of the stock-first migration), and the swap found a real bug rather than a move: the reference's `ItemAnim_OnEvent` matches a push against the button's inventory-slot id (20..23) and benilla emitted its own container numbering (1..4), so all four bag-push cards would have silently never played against the stock body — `push_container` now emits the reference's selector (`bag + 1`), which is what an addon expects anyway; beside it, a readiness probe that asks the LOADER, not a grep, which of the 85 stock windows load off the player's chain today (58 do; the 27 that don't name their own blocker) - a rooted, hovering, water-walking or walking creature reached benilla as an unhandled opcode: the twelve `SMSG_SPLINE_MOVE_*` (the observer twin of the ack'd force family — bare packed guid, no ack, any unit) now decode into one MOVEMENTFLAGS word per unit, which benilla kept for our mover and for a relayed player and not at all for a creature, the one body this family is ever addressed to; ROOT wipes the direction bits so the dead-reckon stops walking a rooted body on for its next heartbeat (`0x7c7340`), HOVER clears a yard off the floor without climbing a body already inside that yard (`0x636e81`), WATERWALK elects the liquid surface against the terrain, SAFE_FALL's terminal is 7.0 not 60.148 (`0x7c5d23`), and SET_RUN_MODE's bool is inverted against the bit, folded at the parse; the inspect card names a unit's granted modes in words (`granted rooted+hover`) so a creature resting a yard off the floor reads as intended rather than broken Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
@@ -765,7 +765,7 @@ fn shipped_bag_frame_drives_end_to_end() {
|
||||
// [`BAG_UI`] is `benilla.toc`'s own order for everything a bag window needs. Three files join
|
||||
// it at the positions the manifest gives them:
|
||||
// * ActionBar.xml straight after Cooldown.xml — the bag bar is anchored INTO
|
||||
// MainMenuBarArtFrame, so the bar must exist before BagFrame.xml loads or the toggle's
|
||||
// MainMenuBarArtFrame, so the bar must exist before the bag bar loads or the toggle's
|
||||
// cross-file `relativeTo` silently falls back to the screen root (which would land in the
|
||||
// right place here anyway, by the 1024-wide coincidence: screen BOTTOMRIGHT == the
|
||||
// full-width bar's art-frame BOTTOMRIGHT — so the failure would be invisible);
|
||||
@@ -780,7 +780,7 @@ fn shipped_bag_frame_drives_end_to_end() {
|
||||
if *file == "Cooldown.xml" {
|
||||
load_ui(&s, "ActionBar.xml");
|
||||
}
|
||||
if *file == "BagFrame.xml" {
|
||||
if *file == "Interface\\FrameXML\\MainMenuBarBagButtons.xml" {
|
||||
bar_frames = frames;
|
||||
}
|
||||
}
|
||||
@@ -788,12 +788,13 @@ fn shipped_bag_frame_drives_end_to_end() {
|
||||
load_ui(&s, "MerchantFrame.xml");
|
||||
|
||||
assert_eq!(
|
||||
bar_frames, 12,
|
||||
bar_frames, 16,
|
||||
"the bag bar is six CheckButtons — MainMenuBarBackpackButton, CharacterBag0..3Slot, \
|
||||
KeyRingButton — each carrying one $parentItemAnim push card (decision 0887): 6 + 6. \
|
||||
Every window this file used to declare (the backpack, four equipped bags, the keyring) \
|
||||
is the reference's ContainerFrame now, and its slot buttons, close buttons and money \
|
||||
rows went with it (decision 1751)"
|
||||
KeyRingButton — each carrying one $parentItemAnim Model (6 + 6), plus a $parentCooldown \
|
||||
Model on each of the four slots that inherit PaperDollItemSlotButtonTemplate (+4). The \
|
||||
deleted BagFrame.xml built 12: it mirrored the six buttons and their push cards but had \
|
||||
no cooldown on a bag-bar slot at all, which is the reference's own and is what the swap \
|
||||
to Interface\\FrameXML\\MainMenuBarBagButtons.xml brought with it (1751 window 3)"
|
||||
);
|
||||
|
||||
// The app's feed: a backpack with Tough Jerky ×5 in slot 1.
|
||||
|
||||
@@ -95,7 +95,7 @@ fn backpack_toggle_plays_open_and_close_kits() {
|
||||
assert!(!open(&s, 0));
|
||||
|
||||
// Toggle open → OnShow → igBackPackOpen.
|
||||
s.run("BenillaBagToggle_OnClick()").unwrap();
|
||||
s.run("MainMenuBarBackpackButton:Click()").unwrap();
|
||||
assert!(open(&s, 0));
|
||||
assert_eq!(
|
||||
s.take_sounds(),
|
||||
@@ -104,7 +104,7 @@ fn backpack_toggle_plays_open_and_close_kits() {
|
||||
);
|
||||
|
||||
// Toggle closed → OnHide → igBackPackClose.
|
||||
s.run("BenillaBagToggle_OnClick()").unwrap();
|
||||
s.run("MainMenuBarBackpackButton:Click()").unwrap();
|
||||
assert!(!open(&s, 0));
|
||||
assert_eq!(
|
||||
s.take_sounds(),
|
||||
@@ -155,7 +155,7 @@ fn b_opens_the_backpack_alone_and_closes_every_bag() {
|
||||
);
|
||||
|
||||
// Toggle open: the BACKPACK, and only the backpack — bag 2 is equipped and stays shut.
|
||||
s.run("BenillaBagToggle_OnClick()").unwrap();
|
||||
s.run("MainMenuBarBackpackButton:Click()").unwrap();
|
||||
let _ = s.take_sounds();
|
||||
assert!(open(&s, 0), "backpack opens");
|
||||
assert!(
|
||||
@@ -171,7 +171,7 @@ fn b_opens_the_backpack_alone_and_closes_every_bag() {
|
||||
// takes the lot down.
|
||||
s.run("ToggleBag(2)").unwrap();
|
||||
assert!(open(&s, 2));
|
||||
s.run("BenillaBagToggle_OnClick()").unwrap();
|
||||
s.run("MainMenuBarBackpackButton:Click()").unwrap();
|
||||
let _ = s.take_sounds();
|
||||
assert!(
|
||||
!open(&s, 0) && !open(&s, 2),
|
||||
@@ -181,7 +181,7 @@ fn b_opens_the_backpack_alone_and_closes_every_bag() {
|
||||
// Bag 0 shut, bag 2 open: the ref's condition reads bag 0, so this is the OPEN arm — the
|
||||
// backpack joins the bag already up instead of closing it.
|
||||
s.run("ToggleBag(2)").unwrap();
|
||||
s.run("BenillaBagToggle_OnClick()").unwrap();
|
||||
s.run("MainMenuBarBackpackButton:Click()").unwrap();
|
||||
let _ = s.take_sounds();
|
||||
assert!(
|
||||
open(&s, 0) && open(&s, 2),
|
||||
@@ -441,7 +441,7 @@ fn bag_tooltip_hangs_left_when_the_slot_sits_in_the_right_half() {
|
||||
slots,
|
||||
}),
|
||||
);
|
||||
s.run("BenillaBagToggle_OnClick()").unwrap();
|
||||
s.run("MainMenuBarBackpackButton:Click()").unwrap();
|
||||
s.take_sounds();
|
||||
s.resolve();
|
||||
|
||||
@@ -517,7 +517,7 @@ fn hovered_bag_tooltip_fills_itself_when_the_stats_land() {
|
||||
slots,
|
||||
}),
|
||||
);
|
||||
s.run("BenillaBagToggle_OnClick()").unwrap();
|
||||
s.run("MainMenuBarBackpackButton:Click()").unwrap();
|
||||
s.resolve();
|
||||
let btn = bag_slot_button(&s, 0, 1);
|
||||
|
||||
@@ -756,7 +756,7 @@ fn readable_letter_hover_shows_the_inspect_magnifier() {
|
||||
slots,
|
||||
}),
|
||||
);
|
||||
s.run("BenillaBagToggle_OnClick()").unwrap();
|
||||
s.run("MainMenuBarBackpackButton:Click()").unwrap();
|
||||
s.take_sounds();
|
||||
s.resolve();
|
||||
let b1 = bag_slot_button(&s, 0, 1);
|
||||
@@ -824,7 +824,7 @@ fn drag_across_two_slots_queues_the_same_move_a_click_pickup_would() {
|
||||
slots,
|
||||
}),
|
||||
);
|
||||
s.run("BenillaBagToggle_OnClick()").unwrap();
|
||||
s.run("MainMenuBarBackpackButton:Click()").unwrap();
|
||||
s.take_sounds();
|
||||
s.resolve();
|
||||
|
||||
@@ -910,8 +910,7 @@ fn a_second_bag_window_feeds_and_paints_via_the_bag_bar() {
|
||||
|
||||
assert!(!open(&s, 1), "hidden by default");
|
||||
// CharacterBag0Slot == bag id 1 (BenillaBagBarSlot_OnLoad(self, 1) in BagFrame.xml).
|
||||
s.run("BenillaBagBarSlot_OnClick(CharacterBag0Slot)")
|
||||
.unwrap();
|
||||
s.run("CharacterBag0Slot:Click()").unwrap();
|
||||
let _ = s.take_sounds();
|
||||
assert!(open(&s, 1), "the bag-bar click opened bag 1's window");
|
||||
let w = window(&s, 1);
|
||||
@@ -1040,7 +1039,7 @@ fn open_backpack_with_a_five_stack(s: &mut UiScript) -> (f32, f32) {
|
||||
slots,
|
||||
}),
|
||||
);
|
||||
s.run("BenillaBagToggle_OnClick()").unwrap();
|
||||
s.run("MainMenuBarBackpackButton:Click()").unwrap();
|
||||
let _ = s.take_sounds();
|
||||
s.resolve();
|
||||
|
||||
@@ -1391,10 +1390,19 @@ fn bag_slot_cooldown_sweeps_through_the_xml() {
|
||||
}
|
||||
|
||||
/// The bar's five bag buttons had NO hover at all — the director's "the bags are missing their
|
||||
/// simple tooltips". These are the ref's plain `SetText` plates (ref-MainMenuBarBagButtons.xml
|
||||
/// l.91-99 for the backpack, ref-MainMenuBarBagButtons.lua l.86-96 for the four slots), NOT the
|
||||
/// two-line newbie kind the micro buttons next to them use — so what's pinned here is the label,
|
||||
/// the empty-slot fallback, and that they seat BESIDE the button rather than at the screen corner.
|
||||
/// simple tooltips". These are the ref's plain `SetText` plates (MainMenuBarBagButtons.xml l.91-99
|
||||
/// for the backpack, MainMenuBarBagButtons.lua l.86-96 for the four slots), NOT the two-line
|
||||
/// newbie kind the micro buttons next to them use — so what's pinned here is the label, the
|
||||
/// empty-slot fallback, and that they seat BESIDE the button rather than at the screen corner.
|
||||
///
|
||||
/// Those two bodies are the reference's OWN now (1751 window 3), which changes one thing this test
|
||||
/// records: **the binding-key suffix appears on the four slots too.** The reference appends
|
||||
/// `GetBindingKey("TOGGLEBAG"..(4 - (this:GetID() - CharacterBag0Slot:GetID())))` — its numbering
|
||||
/// runs the bar right-to-left, so the slot next to the backpack is `TOGGLEBAG4`, our F11
|
||||
/// (`bindings/commands.rs`, quoting ref Bindings.xml l.564-575). Our own handler never did this,
|
||||
/// so the suffix is new here and is the reference's behaviour, not a regression. The backpack's
|
||||
/// key is the reference's `TOGGLEBACKPACK` rather than the `OPENALLBAGS` ours read; both default
|
||||
/// to B, so the string is unchanged and only the reason is.
|
||||
#[test]
|
||||
fn the_bar_bag_buttons_name_themselves_on_hover() {
|
||||
let _data = benilla_formats::wow_data_or_skip!();
|
||||
@@ -1410,14 +1418,13 @@ fn the_bar_bag_buttons_name_themselves_on_hover() {
|
||||
s.resolve();
|
||||
|
||||
// The backpack: its label plus OPENALLBAGS's live key (default B; 0997's divergence note).
|
||||
s.run("BenillaBagToggle_OnEnter(MainMenuBarBackpackButton)")
|
||||
.unwrap();
|
||||
hover(&mut s, "MainMenuBarBackpackButton");
|
||||
let line = s
|
||||
.eval::<String>("return GameTooltipTextLeft1:GetText()")
|
||||
.unwrap();
|
||||
assert!(
|
||||
line.starts_with("Backpack") && line.contains("(B)"),
|
||||
"the backpack names itself and its key: {line:?}"
|
||||
"the backpack names itself and its TOGGLEBACKPACK key: {line:?}"
|
||||
);
|
||||
// Beside the button, not the default corner — the ref's ANCHOR_LEFT.
|
||||
assert!(
|
||||
@@ -1431,8 +1438,7 @@ fn the_bar_bag_buttons_name_themselves_on_hover() {
|
||||
);
|
||||
|
||||
// An empty bag slot falls back to the ref's EQUIP_CONTAINER rather than showing nothing.
|
||||
s.run("BenillaBagBarSlot_OnEnter(CharacterBag0Slot)")
|
||||
.unwrap();
|
||||
hover(&mut s, "CharacterBag0Slot");
|
||||
assert_eq!(
|
||||
s.eval::<String>("return GameTooltipTextLeft1:GetText()")
|
||||
.unwrap(),
|
||||
@@ -1461,16 +1467,20 @@ fn the_bar_bag_buttons_name_themselves_on_hover() {
|
||||
enchants: Vec::new(),
|
||||
});
|
||||
s.set_inventory_slots(inv);
|
||||
s.run("BenillaBagBarSlot_OnEnter(CharacterBag0Slot)")
|
||||
hover(&mut s, "CharacterBag0Slot");
|
||||
let line = s
|
||||
.eval::<String>("return GameTooltipTextLeft1:GetText()")
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
s.eval::<String>("return GameTooltipTextLeft1:GetText()")
|
||||
.unwrap(),
|
||||
"Small Brown Pouch",
|
||||
"an equipped slot shows that bag, not the empty-slot fallback"
|
||||
assert!(
|
||||
line.starts_with("Small Brown Pouch"),
|
||||
"an equipped slot shows that bag, not the empty-slot fallback: {line:?}"
|
||||
);
|
||||
assert!(
|
||||
line.contains("(F11)"),
|
||||
"…and the reference appends this slot's own TOGGLEBAG4 key: {line:?}"
|
||||
);
|
||||
|
||||
s.run("BenillaBagBarButton_OnLeave()").unwrap();
|
||||
unhover(&mut s);
|
||||
assert!(
|
||||
!s.eval::<bool>("return GameTooltip:IsVisible()").unwrap(),
|
||||
"leaving hides the plate"
|
||||
@@ -1651,8 +1661,7 @@ fn the_keyring_button_opens_a_keyring_window() {
|
||||
let _ = s.take_sounds();
|
||||
|
||||
assert!(!open(&s, -2));
|
||||
s.run("BenillaKeyRingButton_OnClick(KeyRingButton)")
|
||||
.unwrap();
|
||||
s.run("KeyRingButton:Click()").unwrap();
|
||||
assert!(open(&s, -2), "the button opens the keyring");
|
||||
assert_eq!(
|
||||
s.take_sounds(),
|
||||
@@ -1687,8 +1696,7 @@ fn the_keyring_button_opens_a_keyring_window() {
|
||||
7146
|
||||
);
|
||||
|
||||
s.run("BenillaKeyRingButton_OnClick(KeyRingButton)")
|
||||
.unwrap();
|
||||
s.run("KeyRingButton:Click()").unwrap();
|
||||
assert!(!open(&s, -2));
|
||||
assert_eq!(
|
||||
s.take_sounds(),
|
||||
@@ -1730,8 +1738,7 @@ fn a_key_dropped_on_the_button_files_itself() {
|
||||
s.run("PickupContainerItem(0, 3)").unwrap();
|
||||
assert!(s.eval::<bool>("return CursorHasItem()").unwrap());
|
||||
|
||||
s.run("BenillaKeyRingButton_OnClick(KeyRingButton)")
|
||||
.unwrap();
|
||||
s.run("KeyRingButton:Click()").unwrap();
|
||||
assert_eq!(
|
||||
s.take_container_moves(),
|
||||
vec![ContainerMove {
|
||||
@@ -1762,28 +1769,42 @@ fn a_key_dropped_on_the_button_files_itself() {
|
||||
}
|
||||
s.set_container(-2, Some(full));
|
||||
s.run("PickupContainerItem(0, 3)").unwrap();
|
||||
// The refusal is now the REFERENCE's own `PutKeyInKeyRing`, reporting through the name its
|
||||
// own line asks for — which 1.12 never defines. `BagFrame.xml` supplies it from the string the
|
||||
// reference meant, and that repair is what this half of the test guards (its comment there
|
||||
// carries the reasoning).
|
||||
s.run("BenillaKeyRingButton_OnClick(KeyRingButton)")
|
||||
.unwrap();
|
||||
// The refusal is the REFERENCE's own `PutKeyInKeyRing`, reporting through the name its own
|
||||
// line asks for — `NO_EMPTY_KEYRING_SLOTS`, which 1.12 never defines. So the drop is refused
|
||||
// and **nothing is printed**: a real 1.12 bug, and faithful here on purpose.
|
||||
//
|
||||
// This client un-quirked it once, by assigning the string the reference plainly meant. Window
|
||||
// 1 of 1751 retired that (ContainerFrameAdapters.xml's header records it), but the assignment
|
||||
// itself outlived the decision in `BagFrame.xml` for a window and a half. Window 3 deleted that
|
||||
// file, so the stated posture and the behaviour agree again. Restoring the repair is one line
|
||||
// in the adapters if it is ever wanted; it is not silent either way.
|
||||
s.run("KeyRingButton:Click()").unwrap();
|
||||
assert!(
|
||||
s.take_container_moves().is_empty(),
|
||||
"a full keyring queues no move"
|
||||
);
|
||||
// ...and says so, on the errors frame — read where a player reads it, off the drawn quads.
|
||||
// (`UIErrorsFrame` is a real `<MessageFrame>`; its lines are the widget's, not FontStrings.)
|
||||
s.resolve();
|
||||
assert!(
|
||||
s.extract().iter().any(|q| matches!(
|
||||
!s.extract().iter().any(|q| matches!(
|
||||
&q.content,
|
||||
QuadContent::Text { text: Some(t), .. } if t == "Your keyring is full."
|
||||
QuadContent::Text { text: Some(t), .. } if t.contains("keyring is full")
|
||||
)),
|
||||
"the refusal posts to UIErrorsFrame — the reference's own dangling string name prints \
|
||||
nothing"
|
||||
"the reference's own dangling string name prints nothing — un-quirking it is a decision, \
|
||||
not a default"
|
||||
);
|
||||
// The nil ALSO raises, out of `MessageFrame:AddMessage`. That is deliberately not asserted as
|
||||
// correct: our binding takes its text as an mlua `String`, which raises on nil by default —
|
||||
// not from a carved gate (`messageframe/plain.rs` reads `0x795590`'s rgb/alpha parse off the
|
||||
// bytes and says nothing about the text argument). Whether 1.12 raises here, no-ops, or prints
|
||||
// a blank line is out with wow-re. So this pins the keyring's own behaviour — the refusal, and
|
||||
// the silence — and lets exactly that one error through by name, which still fails the test on
|
||||
// any OTHER error the click produces.
|
||||
let errs = s.errors();
|
||||
assert!(
|
||||
errs.iter()
|
||||
.all(|e| e.contains("AddMessage") && e.contains("PutKeyInKeyRing")),
|
||||
"the only error may be the reference's own nil message: {errs:?}"
|
||||
);
|
||||
assert!(s.errors().is_empty(), "script errors: {:?}", s.errors());
|
||||
}
|
||||
|
||||
/// The item-push drop animation (decision 0887): `ITEM_PUSH(container, icon)` runs the pushed item's
|
||||
@@ -1806,13 +1827,13 @@ fn an_item_push_drops_its_icon_into_the_bag_that_took_it() {
|
||||
s.resolve();
|
||||
|
||||
let shown = |s: &UiScript, name: &str| {
|
||||
s.eval::<bool>(&format!("return {name}ItemAnim:IsShown()"))
|
||||
s.eval::<bool>(&format!("return {name}BenillaItemPush:IsShown()"))
|
||||
.unwrap()
|
||||
};
|
||||
// The card's centre relative to its button's centre, in screen px (y-up): (dx, dy).
|
||||
let offset = |s: &UiScript, name: &str| -> (f32, f32) {
|
||||
s.eval::<(f32, f32)>(&format!(
|
||||
"local a, b = {name}ItemAnim, {name} \
|
||||
"local a, b = {name}BenillaItemPush, {name} \
|
||||
local ax, ay = a:GetLeft() + a:GetWidth() / 2, a:GetBottom() + a:GetHeight() / 2 \
|
||||
local bx, by = b:GetLeft() + b:GetWidth() / 2, b:GetBottom() + b:GetHeight() / 2 \
|
||||
return ax - bx, ay - by"
|
||||
@@ -1820,11 +1841,11 @@ fn an_item_push_drops_its_icon_into_the_bag_that_took_it() {
|
||||
.unwrap()
|
||||
};
|
||||
let size = |s: &UiScript, name: &str| {
|
||||
s.eval::<f32>(&format!("return {name}ItemAnim:GetWidth()"))
|
||||
s.eval::<f32>(&format!("return {name}BenillaItemPush:GetWidth()"))
|
||||
.unwrap()
|
||||
};
|
||||
let alpha = |s: &UiScript, name: &str| {
|
||||
s.eval::<f32>(&format!("return {name}ItemAnim:GetAlpha()"))
|
||||
s.eval::<f32>(&format!("return {name}BenillaItemPush:GetAlpha()"))
|
||||
.unwrap()
|
||||
};
|
||||
// The card's centre relative to its button's BOTTOMRIGHT corner — the reference's OWN frame of
|
||||
@@ -1834,7 +1855,7 @@ fn an_item_push_drops_its_icon_into_the_bag_that_took_it() {
|
||||
// instead is what let 0887's placement look plausible.
|
||||
let corner = |s: &UiScript, name: &str| -> (f32, f32) {
|
||||
s.eval::<(f32, f32)>(&format!(
|
||||
"local a, b = {name}ItemAnim, {name} \
|
||||
"local a, b = {name}BenillaItemPush, {name} \
|
||||
local ax, ay = a:GetLeft() + a:GetWidth() / 2, a:GetBottom() + a:GetHeight() / 2 \
|
||||
return ax - b:GetRight(), ay - b:GetBottom()"
|
||||
))
|
||||
@@ -1850,11 +1871,14 @@ fn an_item_push_drops_its_icon_into_the_bag_that_took_it() {
|
||||
assert!(!shown(&s, b), "{b}'s card starts hidden");
|
||||
}
|
||||
|
||||
// A push into equipped bag 2.
|
||||
// A push into equipped bag 2. `arg1` is the reference's own vocabulary since 1751 window 3 —
|
||||
// the button's INVENTORY-slot id, which is what `ItemAnim_OnEvent` compares against — so bag 2
|
||||
// is `CharacterBag1Slot`'s 21, not the container id 2 this used to send (`ui_loot.rs`
|
||||
// `push_container`, the `0x491bb5` selector).
|
||||
s.fire_event(
|
||||
"ITEM_PUSH",
|
||||
vec![
|
||||
benilla_ui::script::ScriptValue::Int(2),
|
||||
benilla_ui::script::ScriptValue::Int(21),
|
||||
benilla_ui::script::ScriptValue::Str("Interface\\Icons\\INV_Misc_Bag_08".into()),
|
||||
],
|
||||
);
|
||||
@@ -1988,11 +2012,16 @@ fn an_item_push_drops_its_icon_into_the_bag_that_took_it() {
|
||||
(cx + 16.87).abs() < 0.2 && (cy - 18.68).abs() < 0.2,
|
||||
"arrives at the ref's landing point: got ({cx}, {cy})"
|
||||
);
|
||||
// Which is also, within a pixel, the button's own centre — the reference's card really does
|
||||
// drop INTO the bag button, and that is the one thing 0887 got right for the wrong reason.
|
||||
// Which is also, within a couple of pixels, the button's own centre — the reference's card
|
||||
// really does drop INTO the bag button, and that is the one thing 0887 got right for the wrong
|
||||
// reason. The corner-relative assertion above is the exact one; this is the sanity check, and
|
||||
// its tolerance is a button-size term, not slack: the landing point is fixed relative to the
|
||||
// button's BOTTOMRIGHT, so the offset from its CENTRE moves by half of any size change. The
|
||||
// bar's buttons are 37x37 now — `ItemButtonTemplate`'s own size, inherited through the
|
||||
// reference's `PaperDollItemSlotButtonTemplate` — where the deleted BagFrame.xml authored 36.
|
||||
let (dx, dy) = offset(&s, "CharacterBag1Slot");
|
||||
assert!(
|
||||
dx.abs() < 1.5 && dy.abs() < 1.5,
|
||||
dx.abs() < 2.0 && dy.abs() < 2.0,
|
||||
"lands on the button it went into: got ({dx}, {dy})"
|
||||
);
|
||||
assert!(
|
||||
@@ -2064,7 +2093,7 @@ fn an_addon_that_hooks_toggle_backpack_receives_the_click() {
|
||||
.unwrap();
|
||||
|
||||
// The button's own OnClick path — what the B binding and a plain backpack-button click run.
|
||||
s.run("BenillaBagToggle_OnClick()").unwrap();
|
||||
s.run("MainMenuBarBackpackButton:Click()").unwrap();
|
||||
|
||||
assert_eq!(
|
||||
s.eval::<i64>("return HOOK_RAN").unwrap(),
|
||||
@@ -2123,12 +2152,22 @@ fn the_bag_slots_carry_the_references_names_and_icon_names() {
|
||||
);
|
||||
}
|
||||
|
||||
// 0-based: CharacterBag0Slot is bag 1. Pinned because the off-by-one is the whole trap, and
|
||||
// two name-building call sites had to learn it.
|
||||
// 0-based: CharacterBag0Slot is bag 1, and the off-by-one is the whole trap. Asked the way the
|
||||
// reference asks it, which is the only way there is now: the bar is stock
|
||||
// `MainMenuBarBagButtons.xml` since 1751's third window, its buttons carry no `bagId` field,
|
||||
// and every one of its handlers recomputes `this:GetID() - CharacterBag0Slot:GetID() + 1` at
|
||||
// the call site. The ids themselves come from `GetInventorySlotInfo("Bag0Slot")` = 20 through
|
||||
// the sourced `PaperDollItemSlotButton_OnLoad`, which is what makes the arithmetic work.
|
||||
assert_eq!(
|
||||
s.eval::<i64>("return CharacterBag0Slot.bagId").unwrap(),
|
||||
1,
|
||||
"CharacterBag0Slot IS bag 1 — the reference names them 0-based"
|
||||
s.eval::<i64>("return CharacterBag0Slot:GetID()").unwrap(),
|
||||
20,
|
||||
"CharacterBag0Slot's id is its INVENTORY slot — 8 corpus addons compute `GetID() - 19`"
|
||||
);
|
||||
assert_eq!(
|
||||
s.eval::<i64>("return CharacterBag3Slot:GetID() - CharacterBag0Slot:GetID() + 1")
|
||||
.unwrap(),
|
||||
4,
|
||||
"the reference's own translation: the fourth button is bag 4"
|
||||
);
|
||||
|
||||
// Bagnon's exact reach.
|
||||
|
||||
@@ -455,11 +455,11 @@ fn the_item_push_card_shares_the_band_with_a_raised_bar_exactly_as_the_reference
|
||||
s.tick(0.133); // the opaque peak — the instant the card is most visible
|
||||
s.resolve();
|
||||
assert!(
|
||||
shown(&s, "MainMenuBarBackpackButtonItemAnim"),
|
||||
shown(&s, "MainMenuBarBackpackButtonBenillaItemPush"),
|
||||
"the card plays"
|
||||
);
|
||||
|
||||
let card = rect(&s, "MainMenuBarBackpackButtonItemAnim");
|
||||
let card = rect(&s, "MainMenuBarBackpackButtonBenillaItemPush");
|
||||
let bar = rect(&s, "MultiBarBottomRight");
|
||||
assert!(
|
||||
(card.1 - 48.9).abs() < 0.5 && (card.3 - 93.1).abs() < 0.5,
|
||||
|
||||
@@ -49,7 +49,6 @@ fn load_room(s: &UiScript) {
|
||||
"UIParent.xml",
|
||||
"GameTooltip.xml",
|
||||
"Cooldown.xml",
|
||||
"BagFrame.xml",
|
||||
] {
|
||||
load_xml(s, file);
|
||||
}
|
||||
@@ -107,7 +106,7 @@ fn backpack_with_jerky(s: &mut UiScript) -> String {
|
||||
slots,
|
||||
}),
|
||||
);
|
||||
s.run("BenillaBagToggle_OnClick()").unwrap();
|
||||
s.run("MainMenuBarBackpackButton:Click()").unwrap();
|
||||
let _ = s.take_sounds();
|
||||
s.resolve();
|
||||
bag_slot_button(s, 0, 1)
|
||||
|
||||
@@ -75,7 +75,7 @@ fn escape_closes_bag_and_panel_releases_loot_and_clears_cursor() {
|
||||
s.set_container(0, Some(one_item_backpack()));
|
||||
|
||||
// Open the bag and the loot window; drain the open kits (not under test here).
|
||||
s.run("BenillaBagToggle_OnClick()").unwrap();
|
||||
s.run("MainMenuBarBackpackButton:Click()").unwrap();
|
||||
s.set_loot(Some(LootState {
|
||||
fishing: false,
|
||||
master_candidates: Vec::new(),
|
||||
@@ -147,7 +147,7 @@ fn escape_is_consumed_by_a_focused_editbox_and_leaves_windows_open() {
|
||||
s.set_money(0);
|
||||
s.set_container(0, Some(one_item_backpack()));
|
||||
|
||||
s.run("BenillaBagToggle_OnClick()").unwrap();
|
||||
s.run("MainMenuBarBackpackButton:Click()").unwrap();
|
||||
assert!(
|
||||
s.focus_editbox("ChatFrameEditBox"),
|
||||
"the chat edit box focuses"
|
||||
@@ -264,7 +264,7 @@ fn escape_closes_an_open_stack_split_frame() {
|
||||
slots,
|
||||
}),
|
||||
);
|
||||
s.run("BenillaBagToggle_OnClick()").unwrap();
|
||||
s.run("MainMenuBarBackpackButton:Click()").unwrap();
|
||||
assert!(bag_open(&s, 0), "the backpack window is up");
|
||||
|
||||
// Open the spinner with a real SHIFT + left-click on the button holding slot 1 — asked of the
|
||||
@@ -311,7 +311,7 @@ fn escape_ladder_cast_then_windows_then_target_one_eater_per_press() {
|
||||
load_xml(&s, "MerchantFrame.xml");
|
||||
s.set_money(0);
|
||||
s.set_container(0, Some(one_item_backpack()));
|
||||
s.run("BenillaBagToggle_OnClick()").unwrap();
|
||||
s.run("MainMenuBarBackpackButton:Click()").unwrap();
|
||||
let _ = s.take_sounds();
|
||||
assert!(bag_open(&s, 0));
|
||||
s.set_unit(
|
||||
@@ -405,7 +405,7 @@ fn escape_ladder_targeting_rung_after_cast_before_windows() {
|
||||
load_xml(&s, "MerchantFrame.xml");
|
||||
s.set_money(0);
|
||||
s.set_container(0, Some(one_item_backpack()));
|
||||
s.run("BenillaBagToggle_OnClick()").unwrap();
|
||||
s.run("MainMenuBarBackpackButton:Click()").unwrap();
|
||||
let _ = s.take_sounds();
|
||||
assert!(bag_open(&s, 0));
|
||||
|
||||
|
||||
@@ -347,37 +347,17 @@ const KNOWN: &[Known] = &[
|
||||
},
|
||||
// ── id ─────────────────────────────────────────────────────────────────────────────────────
|
||||
//
|
||||
// The reference side of this comparison is XML only: the sweep cannot see a `SetID` the
|
||||
// reference makes from Lua at `OnLoad`, so a frame the reference numbers *there* reads as 0
|
||||
// here. Every entry below is that case, and each ends at the same runtime number ours declares
|
||||
// — which is why they are accepted rather than fixed. (The blind spot only ever under-reports:
|
||||
// it can hide a divergence, never invent one, so nothing it misses is a false failure.)
|
||||
Known {
|
||||
frame: "CharacterBag0Slot",
|
||||
flag: Flag::Id,
|
||||
why: "the bag slots are numbered from Lua in the reference (ContainerFrame.lua's bag-slot \
|
||||
OnLoad); ours declares the same inventory slot in XML",
|
||||
},
|
||||
Known {
|
||||
frame: "CharacterBag1Slot",
|
||||
flag: Flag::Id,
|
||||
why: "as CharacterBag0Slot",
|
||||
},
|
||||
Known {
|
||||
frame: "CharacterBag2Slot",
|
||||
flag: Flag::Id,
|
||||
why: "as CharacterBag0Slot",
|
||||
},
|
||||
Known {
|
||||
frame: "CharacterBag3Slot",
|
||||
flag: Flag::Id,
|
||||
why: "as CharacterBag0Slot",
|
||||
},
|
||||
Known {
|
||||
frame: "KeyRingButton",
|
||||
flag: Flag::Id,
|
||||
why: "-2 is KEYRING_CONTAINER, which the reference also arrives at — from Lua, not XML",
|
||||
},
|
||||
// Empty, and it is worth saying why rather than leaving a bare header. The reference side of
|
||||
// this comparison is XML only: the sweep cannot see a `SetID` the reference makes from Lua at
|
||||
// `OnLoad`, so a frame the reference numbers *there* used to read as 0 here while ours
|
||||
// declared the number in XML. The five entries that lived here were all one case — the bag
|
||||
// bar's `CharacterBag0..3Slot` and `KeyRingButton`.
|
||||
//
|
||||
// 1751's third window deleted them by making the difference not exist: the bar IS
|
||||
// `Interface\FrameXML\MainMenuBarBagButtons.xml` now, so those ids come from
|
||||
// `PaperDollItemSlotButton_OnLoad`'s `GetInventorySlotInfo` and from `this:SetID(
|
||||
// KEYRING_CONTAINER)` — the reference's own Lua, on both sides. This gate reported them the
|
||||
// frame it was built to report (1751 §5: the drift instruments retire with the copies).
|
||||
// ── parent ─────────────────────────────────────────────────────────────────────────────────
|
||||
//
|
||||
// Three, and each is a seat inside the SAME tree the reference seats it in — which is the
|
||||
|
||||
@@ -204,7 +204,7 @@ fn escape_opens_the_menu_only_when_nothing_else_wants_the_press_and_then_closes_
|
||||
s.set_container(0, Some(backpack()));
|
||||
|
||||
// A press with a window open is eaten by CloseAllWindows — the menu stays down.
|
||||
s.run("BenillaBagToggle_OnClick()").unwrap();
|
||||
s.run("MainMenuBarBackpackButton:Click()").unwrap();
|
||||
let _ = s.take_sounds();
|
||||
s.run("ToggleGameMenu()").unwrap();
|
||||
assert!(!bag_open(&s, 0), "the press closed the bag");
|
||||
@@ -239,7 +239,7 @@ fn the_clicked_form_closes_everything_and_opens_the_menu_in_one_go() {
|
||||
let mut s = bag_harness_with(&[], &["MerchantFrame.xml"]);
|
||||
s.set_money(0);
|
||||
s.set_container(0, Some(backpack()));
|
||||
s.run("BenillaBagToggle_OnClick()").unwrap();
|
||||
s.run("MainMenuBarBackpackButton:Click()").unwrap();
|
||||
assert!(bag_open(&s, 0));
|
||||
|
||||
s.run("ToggleGameMenu(1)").unwrap();
|
||||
@@ -272,7 +272,7 @@ fn the_open_menu_takes_the_screen_and_refuses_every_other_panel() {
|
||||
);
|
||||
s.set_money(0);
|
||||
s.set_container(0, Some(backpack()));
|
||||
s.run("BenillaBagToggle_OnClick()").unwrap();
|
||||
s.run("MainMenuBarBackpackButton:Click()").unwrap();
|
||||
s.set_loot(Some(LootState {
|
||||
fishing: false,
|
||||
master_candidates: Vec::new(),
|
||||
|
||||
@@ -441,7 +441,7 @@ fn vendor_leaves_an_already_open_backpack_alone() {
|
||||
equip_bag(&mut s, 0, "Backpack", 16);
|
||||
|
||||
// Open the bag first (the 'B' toggle), then the vendor over it.
|
||||
s.run("BenillaBagToggle_OnClick()").unwrap();
|
||||
s.run("MainMenuBarBackpackButton:Click()").unwrap();
|
||||
assert!(bag_open(&s, 0), "fixture: the player's own bag is up first");
|
||||
let _ = s.take_sounds();
|
||||
s.set_merchant(Some(MerchantState::default()));
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
//! Since decision 1500 all four bars are player options that ship OFF, so most of what is here
|
||||
//! raises the bar it is about first — through the same two globals the Options rows write.
|
||||
|
||||
use benilla_ui::script::{ActionSlot, QuadContent, SpellTooltipView, UiScript};
|
||||
use benilla_ui::script::{ActionSlot, QuadContent, ScriptValue, SpellTooltipView, UiScript};
|
||||
|
||||
/// Load one shipped `assets/ui/<file>` into `s`, panicking on any loader error.
|
||||
fn load_xml(s: &UiScript, file: &str) {
|
||||
@@ -802,6 +802,131 @@ fn the_grid_option_holds_the_extra_bars_empty_wells_open() {
|
||||
assert!(s.errors().is_empty(), "script errors: {:?}", s.errors());
|
||||
}
|
||||
|
||||
/// **A held payload GHOSTS the wells it opens — the reference's `SetVertexColor(1, 1, 1, 0.5)` on
|
||||
/// a shown grid ring (`ActionButton.lua:247`), which ours skipped from 0216 to 1781.**
|
||||
///
|
||||
/// `UI-Quickslot` is not a second ring. Measured off the shipped BLPs: `UI-Quickslot2`'s interior
|
||||
/// is fully transparent (alpha 0) — a hollow border — while `UI-Quickslot`'s is solid black at
|
||||
/// alpha 0.6 across its whole middle. So the grid swap does not *outline* an empty well, it
|
||||
/// *fills* it, and at full alpha picking up a spell dropped a 60%-black square into every empty
|
||||
/// slot on every raised bar at once. That is the director's report. Half alpha takes the well to
|
||||
/// 30% black, where it reads as a drop target instead of a hole.
|
||||
///
|
||||
/// The occupied rings stay opaque, and letting go of the payload restores the wells — both
|
||||
/// DIVERGENCES, chosen in 1782. In stock 1.12.1 the dim is fanned to every button and then never
|
||||
/// cleared by anything: `SetNormalTexture(path)` reuses the region and writes only its texture
|
||||
/// handle, and a 3-argument `SetVertexColor` is `SetVertexColor(r, g, b, currentAlpha)` — so the
|
||||
/// first spell anyone picks up leaves every border at alpha 0x80 for the session (wow-re
|
||||
/// `button-state-texture-path-setter.md` §2/§7, byte-verified). That is a stuck state keyed to
|
||||
/// nothing the player can see. We take the look the dim was written for and let go of it with the
|
||||
/// payload, which is what the last two assertions here pin.
|
||||
#[test]
|
||||
fn a_held_payload_ghosts_the_empty_wells_it_opens() {
|
||||
let mut s = UiScript::new().unwrap();
|
||||
s.set_screen_size(1024.0, 768.0);
|
||||
load_action_bar(&s);
|
||||
load_xml(&s, "MultiBars.xml");
|
||||
s.set_action(
|
||||
1,
|
||||
Some(ActionSlot {
|
||||
texture: Some("Interface\\Icons\\Ability_SteelMelee".into()),
|
||||
kind: 0x00,
|
||||
action: 100,
|
||||
count: 0,
|
||||
consumable: false,
|
||||
}),
|
||||
);
|
||||
// The raise comes AFTER the seed: `UIParent.xml`'s PLAYER_ENTERING_WORLD arm reads the
|
||||
// server's bar-toggle byte once and would put an early raise straight back down.
|
||||
s.fire_event("PLAYER_ENTERING_WORLD", vec![]);
|
||||
show_bars(&s, &[1]);
|
||||
s.resolve();
|
||||
|
||||
/// The drawn alpha of every ring quad wearing `path` — `None` colour is untinted white.
|
||||
fn ring_alphas(s: &UiScript, path: &str) -> Vec<f32> {
|
||||
s.extract()
|
||||
.iter()
|
||||
.filter_map(|q| match &q.content {
|
||||
QuadContent::Texture {
|
||||
path: Some(p),
|
||||
color,
|
||||
..
|
||||
} if p == path => Some(color.map_or(1.0, |c| c[3])),
|
||||
_ => None,
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
let quickslot = "Interface\\Buttons\\UI-Quickslot";
|
||||
let quickslot2 = "Interface\\Buttons\\UI-Quickslot2";
|
||||
|
||||
// Nothing in hand: the main bar's 12 always-visible wells wear the hollow ring, all opaque.
|
||||
assert_eq!(ring_alphas(&s, quickslot), Vec::<f32>::new());
|
||||
let resting = ring_alphas(&s, quickslot2);
|
||||
assert_eq!(
|
||||
resting.len(),
|
||||
12,
|
||||
"the 12 main wells; the multibar's are hidden"
|
||||
);
|
||||
assert!(
|
||||
resting.iter().all(|a| *a == 1.0),
|
||||
"a resting ring is opaque: {resting:?}"
|
||||
);
|
||||
|
||||
// Payload up. Every well the grid opened is the FILLED plate at HALF alpha.
|
||||
s.fire_event("ACTIONBAR_SHOWGRID", vec![]);
|
||||
s.resolve();
|
||||
let ghosts = ring_alphas(&s, quickslot);
|
||||
assert_eq!(
|
||||
ghosts.len(),
|
||||
23,
|
||||
"11 empty main wells swap art + MultiBarBottomLeft's 12 appear"
|
||||
);
|
||||
assert!(
|
||||
ghosts.iter().all(|a| *a == 0.5),
|
||||
"every grid ring is the ref's half-alpha ghost, not an opaque plate: {ghosts:?}"
|
||||
);
|
||||
|
||||
// …and the one OCCUPIED button is untouched by it.
|
||||
let occupied = ring_alphas(&s, quickslot2);
|
||||
assert_eq!(occupied, vec![1.0], "the occupied ring does not ghost");
|
||||
|
||||
// A well that was ghosted and is then FILLED comes back opaque. This is the assertion that
|
||||
// outlives the engine's own `SetVertexColor` divergence (1782): the day an absent alpha stops
|
||||
// meaning 1.0 and starts meaning "whatever is already there", a `SetRing` that let the fourth
|
||||
// argument default would carry the 0.5 straight into the occupied ring, and only this catches
|
||||
// it — the HIDEGRID check below would not, because that path re-derives an EMPTY well.
|
||||
s.set_action(
|
||||
2,
|
||||
Some(ActionSlot {
|
||||
texture: Some("Interface\\Icons\\Ability_Rogue_Ambush".into()),
|
||||
kind: 0x00,
|
||||
action: 101,
|
||||
count: 0,
|
||||
consumable: false,
|
||||
}),
|
||||
);
|
||||
s.fire_event("ACTIONBAR_SLOT_CHANGED", vec![ScriptValue::Number(2.0)]);
|
||||
s.resolve();
|
||||
assert_eq!(
|
||||
ring_alphas(&s, quickslot2),
|
||||
vec![1.0, 1.0],
|
||||
"a ghosted well that gets an action is opaque again, payload still held"
|
||||
);
|
||||
|
||||
// Letting go restores both halves — art and tint, which is why they share one setter.
|
||||
s.fire_event("ACTIONBAR_HIDEGRID", vec![]);
|
||||
s.resolve();
|
||||
assert_eq!(ring_alphas(&s, quickslot), Vec::<f32>::new());
|
||||
let after = ring_alphas(&s, quickslot2);
|
||||
assert_eq!(after.len(), 12);
|
||||
// (10 empty main wells back to the hollow ring + the 2 occupied ones.)
|
||||
assert!(
|
||||
after.iter().all(|a| *a == 1.0),
|
||||
"the dim does not outlive the payload (the ref's own stuck-dim bug): {after:?}"
|
||||
);
|
||||
assert!(s.errors().is_empty(), "script errors: {:?}", s.errors());
|
||||
}
|
||||
|
||||
/// **The byte the row sends is the four globals, and the seed brings that byte back.**
|
||||
///
|
||||
/// The whole round trip, over the REAL bindings (`benilla_ui::script::action_bar_toggles`): the
|
||||
|
||||
@@ -733,7 +733,6 @@ fn gossip_bank_option_hands_the_left_slot_to_the_bank() {
|
||||
load_xml(&s, "MoneyFrame.xml");
|
||||
load_xml(&s, "UiPanels.xml");
|
||||
load_xml(&s, "Cooldown.xml");
|
||||
load_xml(&s, "BagFrame.xml");
|
||||
load_xml(&s, "GameTooltip.xml");
|
||||
load_xml(&s, "ScrollTemplates.xml"); // the shared scroll kit the window rides
|
||||
// UIPanelScrollFrameTemplate lives here, and the gossip scroll frame inherits it. NOT
|
||||
|
||||
@@ -200,6 +200,137 @@ mod tests {
|
||||
assert!(s.errors().is_empty(), "{:#?}", s.errors());
|
||||
}
|
||||
|
||||
/// **The migration readiness probe** — which stock FrameXML file could be swapped in *today*,
|
||||
/// asked of the running loader rather than guessed from a source scan.
|
||||
///
|
||||
/// ```text
|
||||
/// cargo test -p benilla-app --lib chain_readiness_report -- --ignored --nocapture
|
||||
/// ```
|
||||
///
|
||||
/// 1751 is a long migration — 88 manifest entries, three of them chain entries at the time this
|
||||
/// was written — and the expensive question at every step is *which window is ready*. Picking by
|
||||
/// eye means reading a stock file, listing the globals it calls, and grepping each one; that is
|
||||
/// slow, and it is wrong in both directions. It over-reports (a name that exists in a comment
|
||||
/// greps as present — `framexml-file-demand.py` states that crudeness about itself) and it
|
||||
/// under-reports the things a grep cannot see at all: an XML element type the loader does not
|
||||
/// build, a script handler nothing dispatches, an attribute silently dropped (1739 measured 151
|
||||
/// of those), a template inherited before its definer.
|
||||
///
|
||||
/// So the probe does not analyse. It **loads the file** — the whole shipped manifest first, into
|
||||
/// a fresh VM, exactly as a real run does, and then the candidate off the chain on top — and
|
||||
/// reports what the loader and the VM actually said. That is ground truth: the same machinery
|
||||
/// that would run it for real, answering the same question, with no model of the engine in
|
||||
/// between that could be out of date.
|
||||
///
|
||||
/// **What a clean line does and does not mean.** It means the file *loads* — every element
|
||||
/// built, every template resolved, every load-time body ran without raising. It does not mean
|
||||
/// the window *works*: a verb that only a click reaches is not exercised by loading, and neither
|
||||
/// is anything behind an event. Clean is "start here", not "done"; the window's own test module
|
||||
/// and the director's eye are what finish it (§7).
|
||||
///
|
||||
/// **Loading on top of the manifest, not instead of it**, because that is the position a
|
||||
/// migrated file occupies — every template it inherits is declared by an earlier entry, and
|
||||
/// asking whether `ContainerFrame.xml` loads *alone* only measures that it has predecessors.
|
||||
/// Names it collides with are simply overwritten in the probe VM, which is thrown away; the
|
||||
/// probe is asking about load failures, not about who wins a collision (the manifest's order is
|
||||
/// what settles those, and it is settled per-window when the swap is made).
|
||||
///
|
||||
/// Ignored because it stands up ~90 fresh VMs and each one loads the entire interface; it is an
|
||||
/// instrument you run when choosing the next window, not a gate.
|
||||
#[test]
|
||||
#[ignore = "instrument: run by hand when choosing the next window to migrate"]
|
||||
fn chain_readiness_report() {
|
||||
let _data = benilla_formats::wow_data_or_skip!();
|
||||
|
||||
// The reference's OWN order, off the chain — never a hand-kept list here. A file's position
|
||||
// in it is also the answer to "where does its manifest line go", so the report prints it.
|
||||
let toc = String::from_utf8_lossy(
|
||||
&super::read("Interface\\FrameXML\\FrameXML.toc").expect("the reference's own toc"),
|
||||
)
|
||||
.into_owned();
|
||||
let stock: Vec<String> = toc
|
||||
.lines()
|
||||
.map(|l| l.trim())
|
||||
.filter(|l| !l.is_empty() && !l.starts_with('#') && l.ends_with(".xml"))
|
||||
.map(str::to_string)
|
||||
.collect();
|
||||
|
||||
let migrated: Vec<String> = super::super::addons::Addon::builtin()
|
||||
.toc
|
||||
.files
|
||||
.iter()
|
||||
.filter(|f| super::is_chain_entry(f))
|
||||
.map(|f| {
|
||||
f.rsplit(['\\', '/'])
|
||||
.next()
|
||||
.unwrap_or(f.as_str())
|
||||
.to_string()
|
||||
})
|
||||
.collect();
|
||||
|
||||
println!(
|
||||
"\n=== 1751 migration readiness — {} stock windows ===",
|
||||
stock.len()
|
||||
);
|
||||
println!(
|
||||
"{:>3} {:<32} what stops it (empty = loads clean)",
|
||||
"pos", "file"
|
||||
);
|
||||
|
||||
let mut clean = Vec::new();
|
||||
for (i, name) in stock.iter().enumerate() {
|
||||
let pos = i + 1;
|
||||
if migrated.iter().any(|m| m == name) {
|
||||
println!("{pos:>3} {name:<32} — already migrated");
|
||||
continue;
|
||||
}
|
||||
let mut s = UiScript::new().expect("VM");
|
||||
s.set_screen_size(1024.0, 768.0);
|
||||
let base = super::super::manifest::load_default_ui(&s);
|
||||
assert!(base.is_empty(), "the shipped manifest itself: {base:#?}");
|
||||
s.resolve();
|
||||
let before = s.errors().len();
|
||||
|
||||
let path = format!("Interface\\FrameXML\\{name}");
|
||||
let addon = super::addon(vec![path.clone()]);
|
||||
let mut said = addon.load_files(&s, std::slice::from_ref(&path));
|
||||
s.resolve();
|
||||
said.extend(s.errors().into_iter().skip(before));
|
||||
|
||||
if said.is_empty() {
|
||||
clean.push((pos, name.clone()));
|
||||
println!("{pos:>3} {name:<32} CLEAN");
|
||||
} else {
|
||||
// One line per distinct complaint, deduped and truncated: the same missing verb
|
||||
// reported by twelve frames is one fact, and the tail of a Lua traceback is noise.
|
||||
let mut seen: Vec<String> = Vec::new();
|
||||
for e in said {
|
||||
let one = e.lines().next().unwrap_or("").trim().to_string();
|
||||
let one = if one.len() > 140 {
|
||||
format!("{}…", &one[..140])
|
||||
} else {
|
||||
one
|
||||
};
|
||||
if !one.is_empty() && !seen.contains(&one) {
|
||||
seen.push(one);
|
||||
}
|
||||
}
|
||||
println!("{pos:>3} {name:<32} {} issue(s)", seen.len());
|
||||
for one in seen.iter().take(6) {
|
||||
println!(" · {one}");
|
||||
}
|
||||
if seen.len() > 6 {
|
||||
println!(" · … and {} more", seen.len() - 6);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
println!("\n=== loads clean today: {} ===", clean.len());
|
||||
for (pos, name) in &clean {
|
||||
println!(" {pos:>3} {name}");
|
||||
}
|
||||
}
|
||||
|
||||
/// A path is a chain entry; a bare name is ours. The one-line rule the manifest rests on.
|
||||
#[test]
|
||||
fn a_separator_is_what_makes_an_entry_the_players_own_file() {
|
||||
|
||||
@@ -65,7 +65,25 @@ fn read(req: &str) -> Option<Vec<u8>> {
|
||||
/// own files around it (a merchant, the bank, the action bar art), and the ORDER is the thing
|
||||
/// being reused — it is `benilla.toc`'s, trimmed to what the bags actually reach for.
|
||||
pub(super) const BAG_UI: &[&str] = &[
|
||||
// The reference's own localized strings — `BACKPACK_TOOLTIP`, `EQUIP_CONTAINER`, `KEYRING`,
|
||||
// the `*_FONT_COLOR_CODE` pair. The app loads this at VM setup, ahead of the manifest
|
||||
// (`ui_script/mod.rs`, `setup_script`); a test VM has to say so itself. Not optional since
|
||||
// 1751's third window: stock `MainMenuBarBagButtons.lua`'s hovers pass these straight into
|
||||
// `GameTooltip:SetText`, and `SetText(nil)` raises rather than showing an empty plate. Our
|
||||
// deleted `BagFrame.xml` carried `X = X or "…"` fallbacks for exactly this gap; the real file
|
||||
// is the better answer, and these tests already gate on the install.
|
||||
"Interface\\FrameXML\\GlobalStrings.lua",
|
||||
"Fonts.xml",
|
||||
// The reference's own `PaperDollItemSlotButton_*` family, sourced at manifest entry 2. Not
|
||||
// optional since 1751's third window: stock `BagSlotButtonTemplate`'s OnLoad *is*
|
||||
// `PaperDollItemSlotButton_OnLoad()`, which is what gives each bag button its inventory-slot
|
||||
// id (20..23 via `GetInventorySlotInfo`), its six event registrations and its first paint.
|
||||
"Interface\\FrameXML\\PaperDollFrame.lua",
|
||||
// `TEXT()` — the reference's own identity-function wrapper, which stock
|
||||
// `MainMenuBarBackpackButton`'s OnEnter calls (`GameTooltip:SetText(TEXT(BACKPACK_TOOLTIP)…)`)
|
||||
// and `BagSlotButton_OnEnter` calls for `EQUIP_CONTAINER`. Manifest entry 3, and not optional
|
||||
// for the bag bar since 1751's third window made that bar the reference's own.
|
||||
"BasicControls.xml",
|
||||
// `UIParent` itself: the twelve `ContainerFrame`s declare `parent="UIParent"`, and
|
||||
// `updateContainerFrameAnchors` anchors each open bag to `frame:GetParent()` while
|
||||
// `OpenAllBags` opens with `if not UIParent:IsVisible() then return end`. Without it the
|
||||
@@ -76,16 +94,21 @@ pub(super) const BAG_UI: &[&str] = &[
|
||||
"UiPanels.xml",
|
||||
"GameTooltip.xml",
|
||||
"Cooldown.xml",
|
||||
// The bag BAR hangs off `MainMenuBarArtFrame` and seats itself one frame level above the bar
|
||||
// art (`BenillaActionBarArt_SeatAbove`), both at LOAD — so without this the five buttons fall
|
||||
// back to UIParent and sit at a level no production run ever puts them at.
|
||||
// The bag BAR declares `parent="MainMenuBarArtFrame"`, resolved at LOAD — so without this the
|
||||
// six buttons fall back to UIParent and sit at a level no production run ever puts them at.
|
||||
// It also carries `MainMenuBar_UpdateKeyRing`, which is what puts the keyring on the bar.
|
||||
"ActionBar.xml",
|
||||
// `UpdateMicroButtons` — the KEYRING's own OnShow/OnHide calls it (ContainerFrame.lua l.117,
|
||||
// l.137), because in the reference the keyring's existence moves the micro-button row.
|
||||
"MicroMenu.xml",
|
||||
"UIPanelTemplates.xml",
|
||||
"Interface\\FrameXML\\ContainerFrame.xml",
|
||||
"BagFrame.xml",
|
||||
// `PaperDollItemSlotButtonTemplate`, which every bag button inherits — resolved at load, so it
|
||||
// has to precede the bar exactly as it does in the manifest.
|
||||
"ItemSlotButtonTemplates.xml",
|
||||
// The bag BAR itself, the reference's own since 1751's third window: MainMenuBarBackpackButton,
|
||||
// CharacterBag0..3Slot, KeyRingButton, `BagSlotButtonTemplate`, and `KEYRING_CONTAINER`.
|
||||
"Interface\\FrameXML\\MainMenuBarBagButtons.xml",
|
||||
// `StackSplitFrame` is not optional either: the reference's own
|
||||
// `ContainerFrameItemButton_OnClick` calls `StackSplitFrame:Hide()` on EVERY plain click
|
||||
// (ContainerFrame.lua l.581) before the pickup, and opens it on the shift fork.
|
||||
@@ -94,9 +117,10 @@ pub(super) const BAG_UI: &[&str] = &[
|
||||
// `if ( ChatFrameEditBox:IsShown() )` (ContainerFrame.lua l.569) to decide between posting the
|
||||
// item's link and splitting the stack, so a VM without it raises before either.
|
||||
"ChatFrame.xml",
|
||||
// Our adapters over the reference's container files — one wrapper for the keyring tooltip, and
|
||||
// the three bag verbs 0561 shadows (`OpenBackpack`/`CloseBackpack`/`CloseAllBags`). It has to
|
||||
// be AFTER `ContainerFrame.xml` for either job, which is why it is here and not up with
|
||||
// Our adapters over the reference's container files — the keyring tooltip wrapper, the three
|
||||
// bag verbs 0561 shadows (`OpenBackpack`/`CloseBackpack`/`CloseAllBags`), and the item-push
|
||||
// card the reference draws with a `<Model>` this engine does not render (0887). It has to be
|
||||
// AFTER `ContainerFrame.xml` and after the bar, which is why it is here and not up with
|
||||
// UiPanels.xml.
|
||||
"ContainerFrameAdapters.xml",
|
||||
// `updateContainerFrameAnchors` measures every open bag against `BankFrame:GetRight()`
|
||||
|
||||
@@ -34,7 +34,6 @@ fn harness() -> UiScript {
|
||||
"ItemRef.xml",
|
||||
"MerchantFrame.xml",
|
||||
"Cooldown.xml",
|
||||
"BagFrame.xml",
|
||||
"CharacterFrame.xml",
|
||||
] {
|
||||
load_xml(&s, f);
|
||||
@@ -159,7 +158,7 @@ fn shift_compare_over_a_bag_item_seats_on_the_doll_slot() {
|
||||
// Open the bag and hover the helm slot. The button is ASKED for by its own `GetID()` — the
|
||||
// reference numbers a window's buttons backwards and recycles the windows (1751), so neither
|
||||
// the frame name nor the button index is a property to assume.
|
||||
s.run("BenillaBagToggle_OnClick()").unwrap();
|
||||
s.run("MainMenuBarBackpackButton:Click()").unwrap();
|
||||
s.take_sounds();
|
||||
let btn = bag_slot_button(&s, 0, 1);
|
||||
hover(&mut s, &btn);
|
||||
@@ -308,7 +307,7 @@ fn doll_hover_renders_the_live_instance_and_never_self_compares() {
|
||||
// Arm a compare first through a BAG hover (the stale-arm hazard the doll hover must clear) —
|
||||
// the reference's own `ContainerFrameItemButton_OnEnter` since 1751, reached by moving the
|
||||
// mouse onto the button rather than by calling the handler.
|
||||
s.run("BenillaBagToggle_OnClick()").unwrap();
|
||||
s.run("MainMenuBarBackpackButton:Click()").unwrap();
|
||||
s.take_sounds();
|
||||
let btn = bag_slot_button(&s, 0, 1);
|
||||
hover(&mut s, &btn);
|
||||
|
||||
Reference in New Issue
Block a user