the bag windows are the real 1.12 files, and /walk actually walks

- the bag windows are the reference's own `ContainerFrame.xml` running off the player's patch chain — decision 1751 makes the stock 1.12 UI off the install the end state and `assets/ui` scaffolding that retires file-by-file (86 files against the 99-file stock corpus, 72% of the 1128-function global surface already built) — via one ordered manifest, `benilla.toc`, where a bare name is a file we ship and an `Interface\FrameXML\` path is the player's own file; the windows on screen are `ContainerFrame1..12`, the bank's six copy-pasted popouts fold into them at ids 5..10, net −750 lines, and the swap forced real engine verbs into being rather than stubs (`SetBagPortaitTexture` with its id-0 no-texture arm, `GameTooltip:AppendText`, `SetButtonPulse`, and a `<Model>` whose file is the cooldown indicator materializing as the native sweep, which three corpus addons inherit)
- the 1.12 walk toggle lands: `TOGGLERUN` latches one bit with `ToggleRun`'s own silent refusal chain (dead, rooted, on a live server spline), and the walk animation was never missing at all — the flag reaches it through exactly one channel, the speed (`0x5fd100` picks Walk over Run on `speed > 2 × walkSpeed`); pinning it caught our remote extrapolator with the two min rungs backwards — `min(walk, run)` runs BEFORE the backward min, so a walking backpedaller moves at 2.5 yd/s where we would have played them the Run clip at 4.5 — and one shared `current_speed` now keeps the local controller and the extrapolator from ever disagreeing about it
- the instance and raid lockout messages exist — six packets that were dropped as unknown opcodes now land, four of them as the CHAT_MSG_SYSTEM lines the client composes itself, which is why no Lua handler ever surfaced them — plus `IsInInstance`, `ResetInstances`, and the unit popup's "Reset all instances" row behind the reference's own predicate; where the binary contradicts vmangos: the ownership flag means "any instance bind" but vmangos only ever names raid binds, so the row was dead for every character without a raid lockout — term 1 is now also satisfied by our own eyes, walking out of a party dungeon the server itself bound us to
- opening the world map showed 3D world at the sheet's margins: `BlackoutWorld` was parented to the very `UIParent` a fullscreen panel hides, and three more frames had crossed the same boundary — the shared dropdown lists (so the map's continent and zone pickers answered a click with nothing at all), the master-looter menu, and the screenshot confirmation, which could never appear during a cinematic; `frame_flag_gate` now checks the parent seat against the reference both ways a frame acquires one (the attribute, template-inherited included, and `<Frames>` nesting), and its first harvest took 30 divergences down to three accepted ones

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Sam
2026-08-31 20:17:25 +07:00
co-authored by Claude Fable 5
parent 23b1a3e68b
commit 03c70a7035
73 changed files with 5561 additions and 3278 deletions
@@ -0,0 +1,226 @@
//! 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;
/// Load one shipped `assets/ui/<file>` into `s`, panicking on any loader error.
fn load_xml(s: &UiScript, file: &str) {
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
);
}
/// 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 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 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) —
// production runs the player's own string table at boot.
s.run(
r#"
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 [
"Fonts.xml",
"UIParent.xml",
"MoneyFrame.xml",
"UiPanels.xml",
"GameTooltip.xml",
"UIDropDownMenu.xml",
"UnitPopup.xml",
"UnitFrames.xml",
"RaidFrame.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());
}