Files
benilla-octo/crates/benilla-app/src/ui_script/instance_tests.rs
T
SamandClaude Fable 5.1 058ca4b6c8 every dialog is the reference's own, and the trainer loads on demand
- every dialog is the reference's own: `StaticPopup.xml` + `StaticPopup.lua` load off the player's chain, and 0308's engine with its 37 dialogs leaves `UiPanels.xml`; all 76 of the reference's dialogs are the stock table's, 28 of which no file of ours had ever registered (the resurrect family among them); the nine hidden listener frames that existed only because benilla once had no `UIParent_OnEvent` come home to it verbatim from UIParent.lua, and the seven files that were nothing but a dialog and its driver are deleted; what the swap surfaced: the reference capitalises the countdown's unit word ("5 Minutes until release") where ours had lowercased it, and ESC in the typed delete confirm's box is swallowed — the entry names no escape handler; arms for events this engine never fires (PLAYER_SKINNED, TRADE_REQUEST) wait on their feeds rather than being armed blind (1960)
- the social window and the unit menus are the reference's own files: `FriendsFrame.xml` loads off the chain and our transcription is deleted — its twelve dialogs re-homed, `FRIENDLIST_SHOW` fired as the answer to an explicit `ShowFriends()`, and `Get`/`SetLookingForGroup` a client-side flag until their bodies are carved; `UnitPopup.xml` loads off the chain, our row catalogue and its driver frame go, and nine verbs are built for it — `HasFullControl` off the player-control flag the reference reads at `0x51a158`, `UnitPlayerOrPetInParty`/`InRaid` over a new unit owner, and the six pet one-shots, each pressing the bar's own reaction or command slot word through the same latch and `CMSG_PET_ACTION`, confirmed by wow-re's carve; the Guild Leave/Promote confirms 1958 believed nothing registered were our social file's all along, and the duplicate went with it (1958, 1959)
- the trainer loads on demand: our `TrainerFrame.xml` is deleted and no row replaces it — `Blizzard_TrainerUI` loads off the player's own chain through the reference's `ClassTrainerFrame_LoadUI`, and the LoadOnDemand `Blizzard_*` addons are registry rows read off the chain; the timing IS the mechanism: the list builder writes the state-filter mask to 3 on every `SMSG_TRAINER_LIST` (`0x4d75d9`) and the stock addon pushes its saved filters exactly once, on its own ADDON_LOADED — which fires on the session's first trainer, after that packet, so the saved filters win that open and an eager load would never have applied them; the loader holds an anchor to a sibling not built yet and applies it once the subtree exists; and `UNIT_FLAGS` fires only on a change between two snapshots — wow-re's trio found the per-field watch bridge runs no notify pass on the create leg, correcting 1953 (1957)
- the additive window, built and withdrawn the same hour: under `BLEND_ADD` every additive particle draw commutes with every other, so same-key additive draws can be regrouped exactly — built in the effect lane's merge walk and measured on the crowd rig (Stormwind, forty partybots): the adjacency merge already folds about half the effect items and the regroup found two more folds in a frame of ninety-three, so the pre-regroup walk is what runs; what stays: an `fx=` items/merged census on the FPS probe line, a `DRESS_PART` line per drawn part of the self body on the dress census, and the regression the A/B uncovered: 1948's stock chat migration dropped the drain's plain-line sender, so every `WOW_PROBE_CHAT` rig — the crowd rig's forty `.partybot add`s among them — had gone silent on the server; a probe's slash-less line reaches the wire as SAY again (1955)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-04 20:57:24 +07:00

232 lines
8.6 KiB
Rust

//! The instance-lockout **Era surface** (decision 1748): the three engine bindings, and the one
//! place in the shipped UI that reads them — the SELF menu's "Reset all instances" row and the
//! `CONFIRM_RESET_INSTANCES` dialog behind it.
//!
//! The four chat lines the family's other packets raise are engine-composed and have no Lua at
//! all (no FrameXML file mentions `RAID_INSTANCE_WELCOME` or its siblings); they are tested at the
//! composer, in `crate::ui_instance`.
use benilla_ui::script::UiScript;
use super::test_ui::load_ui as load_xml;
/// The three bindings answer with the reference's own shapes — `IsInInstance` a PAIR, both
/// readers `1`/`nil` rather than `true`/`false` (the reference pushes the double 1.0), and
/// `ResetInstances` a queue.
#[test]
fn the_three_bindings_have_the_reference_shapes() {
let _data = benilla_formats::wow_data_or_skip!();
let mut s = UiScript::new().unwrap();
// Nothing pushed yet: the app has not said where we are.
assert_eq!(
s.eval::<(Option<f64>, String)>("return IsInInstance()")
.unwrap(),
(None, "none".into()),
"no map pushed reads as not-an-instance"
);
for (ty, inside, name) in [
(0u32, None, "none"),
(1, Some(1.0), "party"),
(2, Some(1.0), "raid"),
(3, Some(1.0), "pvp"),
// Past the reference's own `cmp esi,4; jae` guard.
(4, Some(1.0), "none"),
] {
s.set_instance_type(Some(ty));
assert_eq!(
s.eval::<(Option<f64>, String)>("return IsInInstance()")
.unwrap(),
(inside, name.into()),
"InstanceType {ty}"
);
}
assert_eq!(
s.eval::<Option<f64>>("return CanShowResetInstances()")
.unwrap(),
None,
"false by default — no bind, no dungeon behind us"
);
s.set_can_reset_instances(true);
assert_eq!(
s.eval::<Option<f64>>("return CanShowResetInstances()")
.unwrap(),
Some(1.0)
);
assert_eq!(s.take_reset_instance_asks(), 0);
s.run("ResetInstances(); ResetInstances()").unwrap();
assert_eq!(s.take_reset_instance_asks(), 2, "each call is one send");
assert_eq!(s.take_reset_instance_asks(), 0, "the drain is a take");
}
/// The whole shipped path, through the real hit test: right-click your own portrait, the row is
/// there only while `CanShowResetInstances()` is true, clicking it raises the reference's confirm,
/// and Yes is the only thing that sends.
#[test]
fn the_self_menu_row_gates_on_the_binding_and_confirms_before_sending() {
let _data = benilla_formats::wow_data_or_skip!();
let mut s = UiScript::new().unwrap();
s.set_screen_size(1024.0, 768.0);
// The row labels a bare harness has no GlobalStrings.lua for. Verbatim 1.12 values
// (`RESET_INSTANCES` l.3342, `CONFIRM_RESET_INSTANCES` l.851, `YES` l.5463, `NO` l.2794,
// `GROUP` l.2029) — production runs the player's own string table at boot.
s.run(
r#"
-- The stock raid pane concatenates this into each of its eight group headers inside
-- their own OnLoad, so it has to exist before the addon loads (1874).
GROUP = "Group"
RESET_INSTANCES = "Reset all instances"
CONFIRM_RESET_INSTANCES = "Do you really want to reset all of your instances?"
YES = "Yes"
NO = "No"
CANCEL = "Cancel"
"#,
)
.unwrap();
for file in [
"Interface\\FrameXML\\Fonts.xml",
"UIParent.xml",
"MoneyFrame.xml",
"UiPanels.xml",
r"Interface\FrameXML\UIPanelTemplates.lua",
r"Interface\FrameXML\UIPanelTemplates.xml",
"Interface\\FrameXML\\GlobalStrings.lua",
"Interface\\FrameXML\\BasicControls.xml",
"Interface\\FrameXML\\LocaleProperties.lua", // `TEXT`, which StaticPopup.lua and UnitPopup.lua read at file scope
"Interface\\FrameXML\\StaticPopup.xml",
"GameTooltip.xml",
"Interface\\FrameXML\\UIDropDownMenu.xml",
"Interface\\FrameXML\\UnitPopup.xml",
"Interface\\FrameXML\\TextStatusBar.lua",
"Interface\\FrameXML\\TextStatusBar.xml",
"Interface\\FrameXML\\BuffFrame.xml",
"Interface\\FrameXML\\UnitFrame.xml",
"Interface\\FrameXML\\CombatFeedback.xml",
"Interface\\FrameXML\\PlayerFrame.xml",
"Interface\\FrameXML\\PartyFrame.xml",
"Interface\\FrameXML\\TargetFrame.xml",
"Interface\\FrameXML\\PetFrame.xml",
r"Interface\FrameXML\RaidFrame.xml",
r"Interface\AddOns\Blizzard_RaidUI\Blizzard_RaidUI.xml",
] {
load_xml(&s, file);
}
s.resolve();
assert!(s.errors().is_empty(), "load errors: {:?}", s.errors());
let open_self_menu = |s: &mut UiScript| {
s.run("CloseDropDownMenus()").unwrap();
s.run(r#"PlayerFrame_OnClick("RightButton")"#).unwrap();
s.resolve();
};
let row_labels = |s: &mut UiScript| -> Vec<String> {
let n = s.eval::<i64>("return DropDownList1.numButtons").unwrap();
(1..=n)
.map(|i| {
s.eval::<String>(&format!("return DropDownList1Button{i}:GetText() or \"\""))
.unwrap()
})
.collect()
};
// Off: the row is not in the menu at all — hidden, not greyed (the reference's
// `UnitPopupShown[index] = 0`). Solo, it is the ONLY row of the SELF menu that could show
// (the loot trio, Leave and the raid marks all need a party), so the menu's "nothing but
// CANCEL" early-out fires and no menu opens at all. That is the reference's behaviour too,
// and it is the sharpest possible assertion that the row is really gone.
open_self_menu(&mut s);
assert!(
s.eval::<bool>("return not DropDownList1:IsVisible()")
.unwrap(),
"solo with no lockout to reset, the SELF menu has nothing to show"
);
// On: the row appears, and with it the menu.
s.set_can_reset_instances(true);
open_self_menu(&mut s);
assert!(
s.eval::<bool>("return DropDownList1:IsVisible()").unwrap(),
"the one showable row is enough to open the SELF menu"
);
let labels = row_labels(&mut s);
let row = labels
.iter()
.position(|l| l == "Reset all instances")
.unwrap_or_else(|| panic!("the row shows once the binding says so: {labels:?}"));
// Clicking it sends NOTHING — it raises the confirm.
let button = format!("DropDownList1Button{}", row + 1);
let (cx, cy) = s
.eval::<(f64, f64)>(&format!("return {button}:GetCenter()"))
.unwrap();
s.mouse_button(cx as f32, cy as f32, "LeftButton", true);
s.mouse_button(cx as f32, cy as f32, "LeftButton", false);
s.resolve();
assert_eq!(
s.take_reset_instance_asks(),
0,
"the row itself never sends — it asks"
);
assert!(
s.eval::<bool>("return StaticPopup1:IsVisible()").unwrap(),
"the row raises CONFIRM_RESET_INSTANCES"
);
assert_eq!(
s.eval::<String>("return StaticPopup1Text:GetText()")
.unwrap(),
"Do you really want to reset all of your instances?"
);
assert_eq!(
s.eval::<String>("return StaticPopup1Button1:GetText()")
.unwrap(),
"Yes"
);
assert_eq!(
s.eval::<String>("return StaticPopup1Button2:GetText()")
.unwrap(),
"No"
);
// No sends nothing; Yes sends exactly one.
let (nx, ny) = s
.eval::<(f64, f64)>("return StaticPopup1Button2:GetCenter()")
.unwrap();
s.mouse_button(nx as f32, ny as f32, "LeftButton", true);
s.mouse_button(nx as f32, ny as f32, "LeftButton", false);
s.resolve();
assert_eq!(
s.take_reset_instance_asks(),
0,
"No is not an answer that sends"
);
open_self_menu(&mut s);
let labels = row_labels(&mut s);
let row = labels
.iter()
.position(|l| l == "Reset all instances")
.expect("still offered");
let button = format!("DropDownList1Button{}", row + 1);
let (cx, cy) = s
.eval::<(f64, f64)>(&format!("return {button}:GetCenter()"))
.unwrap();
s.mouse_button(cx as f32, cy as f32, "LeftButton", true);
s.mouse_button(cx as f32, cy as f32, "LeftButton", false);
s.resolve();
let (yx, yy) = s
.eval::<(f64, f64)>("return StaticPopup1Button1:GetCenter()")
.unwrap();
s.mouse_button(yx as f32, yy as f32, "LeftButton", true);
s.mouse_button(yx as f32, yy as f32, "LeftButton", false);
s.resolve();
assert_eq!(
s.take_reset_instance_asks(),
1,
"Yes is the one call that sends CMSG_RESET_INSTANCES"
);
assert!(s.errors().is_empty(), "script errors: {:?}", s.errors());
}