the bank is the reference's own window, and far sight freezes your body

- the bank window is the reference's own `BankFrame.xml` run off the player's chain — our 890-line copy is deleted whole; putting bags into the bank's bag slots works (the two verbs its buttons are wired to now exist), the purse is a real `SmallMoneyFrameTemplate` splitting 987654321 copper into three coin buttons instead of one string, `BANKFRAME_OPENED` loses the argument the real client never fires, and the bank stops auto-opening your bags while the vendor and mailbox keep doing it; the empty-slot click still plays the bag sound, because the reference's own line plays it on every click the cursor did not consume — named as faithful rather than quietly fixed
- underneath it, the bank's inventory band is a VIEW over the container feed, not a second copy — inventory ids 40..69 had answered nil because the doll array stops at 24, so the stock file swapped in would have drawn an empty bank; the six bank bag slots become real inventory slots (64..=69) fed off the player descriptor, the three bag verbs are carved from the binary (`PutItemInBackpack` IS `PutItemInBag(0xFF)` through a ten-byte thunk, the occupied-by-an-ordinary-item leg auto-stores with no destination slot at all, and the return is the Lua number 1 or nil — the nil being the whole reason a plain left-click opens a bag), and the swap caught `BankButtonIDToInvSlotID`'s bag arm six slots wrong: it took a bag number 1..6 where the reference passes the button's own id, which is the container id
- far sight freezes your own body while the view is out — the fifth conjunct of the movement predicates' shared precondition, joining the health term so the walk, the turn and the follow teardown go down together; both halves are load-bearing: the `IsActivePlayer` half is what keeps a Mind Control victim walking (possession rides the same PLAYER_FARSIGHT field), and the latch half is the RESOLVED engage rather than the raw field — and the camera is untouched, you still look around freely, which is the whole point of the spell
- the reset offer no longer outlives the character that earned it — log out after walking out of the Deadmines and your next character was offered a reset the first one had earned — and the one lockout leg no probe can ever reach (vmangos writes a last-instance map only from raid binds, which the client's handler discards as not-a-party-dungeon) is now covered end to end through the real system, its module doc saying plainly: tested and never observed

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Sam
2026-08-31 20:18:51 +07:00
co-authored by Claude Fable 5
parent 25c865c00a
commit 14bd5ea1a4
35 changed files with 1804 additions and 1347 deletions
+21 -26
View File
@@ -3,27 +3,17 @@ use benilla_ui::script::{
QuadContent, SoundRequest, UiScript,
};
/// Load one shipped `assets/ui/<file>` into `s`, panicking on any loader error and returning the
/// frame count it materialized — the panel tests all load `UiPanels.xml` (decision 0084 §2's slot
/// manager) before the panel frame(s) under test, exactly as `ui_script.rs`'s own shipped-list
/// order does, so `ShowUIPanel`/`HideUIPanel` already exist when a frame's OnLoad/OnEvent
/// references them.
fn load_xml(s: &UiScript, file: &str) -> usize {
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
);
report.frames
}
/// Load one manifest entry into `s`, panicking on any loader error and returning the frame count
/// it materialized — the panel tests all load `UiPanels.xml` (decision 0084 §2's slot manager)
/// before the panel frame(s) under test, exactly as `benilla.toc`'s own order does, so
/// `ShowUIPanel`/`HideUIPanel` already exist when a frame's OnLoad/OnEvent references them.
///
/// This was a private disk-only reader until 1751's second window: `BankFrame.xml` is the
/// reference's own file off the player's chain now, and a reader that only knows `assets/ui`
/// cannot name it. [`super::test_ui::load_ui`] knows both stores by the manifest's own rule, so a
/// caller that names a chain entry has to open with `wow_data_or_skip!` (only the bank test below
/// does).
use super::test_ui::load_ui as load_xml;
/// Find a bare frame's own rect via its `QuadContent::Frame` entry (every frame emits one, at its
/// resolved rect, whether or not it paints anything itself — `UiScript::extract`'s doc). Used for
@@ -729,25 +719,30 @@ fn shipped_panel_slot_pushable_promotes_to_center() {
/// center-parked "left-area" occupant back — UIParent.lua l.777-782).
#[test]
fn gossip_bank_option_hands_the_left_slot_to_the_bank() {
use benilla_ui::script::{BankState, ScriptValue};
use benilla_ui::script::BankState;
// The bank window is the reference's own file off the player's chain (1751).
let _data = benilla_formats::wow_data_or_skip!();
// The bank window's own dependency chain (bank_tests::setup), plus the gossip window.
let order_first = |bank_first: bool| {
let mut s = UiScript::new().unwrap();
s.set_screen_size(1024.0, 768.0);
load_xml(&s, "Fonts.xml");
load_xml(&s, "ItemButtonTemplate.xml"); // the reference bank's slot buttons inherit it
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, "MerchantFrame.xml");
load_xml(&s, "BankFrame.xml");
load_xml(&s, "ScrollTemplates.xml"); // the shared scroll kit the window rides
// UIPanelScrollFrameTemplate lives here, and the gossip scroll frame inherits it. NOT
// optional: a missing template is a loader *warning*, not an error, so an under-loaded
// list passes load_xml and then loses the scrollbar silently.
// Before BankFrame, not after: its close and purchase buttons inherit UIPanelCloseButton
// and UIPanelButtonTemplate, and an `inherits=` is resolved at LOAD.
load_xml(&s, "UIPanelTemplates.xml");
load_xml(&s, "Interface\\FrameXML\\BankFrame.xml");
load_xml(&s, "GossipFrame.xml");
// The gossip menu is open on the banker (its bank option showing).
@@ -772,11 +767,11 @@ fn gossip_bank_option_hands_the_left_slot_to_the_bank() {
s.set_bank(Some(BankState::default()));
s.set_gossip(None);
if bank_first {
s.fire_event("BANKFRAME_OPENED", vec![ScriptValue::Str("Banker".into())]);
s.fire_event("BANKFRAME_OPENED", vec![]);
s.fire_event("GOSSIP_CLOSED", vec![]);
} else {
s.fire_event("GOSSIP_CLOSED", vec![]);
s.fire_event("BANKFRAME_OPENED", vec![ScriptValue::Str("Banker".into())]);
s.fire_event("BANKFRAME_OPENED", vec![]);
}
assert!(s.errors().is_empty(), "script errors: {:?}", s.errors());