This checkout had no git history at all, so this one commit carries the whole
of the Everwood work as a single import on top of chairmanpow/benilla-octo's
`main` (706cd71). The two trees have genuinely diverged — each has files the
other does not — so this is a fork point, not a catch-up.
Today's work, which is the reason a baseline exists at all:
- Booth previews no longer cast world shadows (`NO_BOOTH_SHADOW`). Portrait
characters were entering the sun's shadow cascades, so every skinned-rig
shadow specialization compiled live mid-frame; the hitch detector logged
"main thread blocked 1316 ms" beside a `compiled LIVE` warning. This was
the Fashion/Tmog stutter.
- Per-pane dress-up: each `DressUpModel` pane assembles and stages its own
tried-on look instead of every pane mirroring the live player.
- Item template cache is append-only JSONL written on the IO pool, replacing
an 11.6 MB whole-map JSON rewrite on the main thread every 10 s.
- `WOW_SYSTEM_TIMING` rebuilt per-thread: it took 2 mutexes per system enter
and 4 per exit, costing 15 ms a frame (590 fps -> 60) and corrupting the
measurements it was taken to produce.
- The visibility-churn sweep moved to its own `WOW_FLASH_CHURN` switch, so a
frame-cost reading no longer pays 2.7 ms a frame for it.
Local state (`benilla-config/`, `logs/`, `target/`) is excluded by the
existing .gitignore; the account credential it holds is not in this commit.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SgstFWtn1RxEJ5kVrpgYwn
164 lines
6.1 KiB
Rust
164 lines
6.1 KiB
Rust
//! The stock taxi map (`TaxiFrame.xml`) fed a two-node snapshot shaped as `crate::ui_taxi`'s feed
|
|
//! pushes it: `TAXIMAP_OPENED` shows it and builds the node buttons, a click takes the node, and
|
|
//! `TAXIMAP_CLOSED` hides it.
|
|
|
|
use benilla_ui::script::{ScriptValue, TaxiNodeType, TaxiUiNode, TaxiUiState, UiScript};
|
|
|
|
use super::test_ui::load_ui as load_xml;
|
|
|
|
/// The taxi window and its dependencies.
|
|
fn taxi_script() -> UiScript {
|
|
let mut s = UiScript::new().unwrap();
|
|
s.set_screen_size(1024.0, 768.0);
|
|
load_xml(&s, "Interface\\FrameXML\\Fonts.xml");
|
|
load_xml(&s, r"Interface\FrameXML\MoneyFrame.lua");
|
|
load_xml(&s, r"Interface\FrameXML\MoneyFrame.xml");
|
|
load_xml(&s, "Interface\\FrameXML\\GlobalStrings.lua");
|
|
load_xml(&s, r"Interface\FrameXML\UIParent.xml");
|
|
load_xml(&s, r"Interface\FrameXML\BasicControls.xml");
|
|
load_xml(&s, r"Interface\FrameXML\LocaleProperties.lua");
|
|
load_xml(&s, r"Interface\FrameXML\StaticPopup.xml");
|
|
load_xml(&s, "Interface\\FrameXML\\GameTooltip.xml"); // TaxiNodeOnButtonEnter's tooltip
|
|
load_xml(&s, "Interface\\FrameXML\\UIErrorsFrame.xml");
|
|
// UIErrorsFrame takes `DrawOneHopLines`' refusal; a missing `ERR_TAXINOPATHS` draws blank.
|
|
// Without UIPanelTemplates, `TaxiCloseButton` loses its `UIPanelCloseButton` handler.
|
|
load_xml(&s, "Interface\\FrameXML\\UIPanelTemplates.lua");
|
|
load_xml(&s, "Interface\\FrameXML\\UIPanelTemplates.xml");
|
|
load_xml(&s, "Interface\\FrameXML\\TaxiFrame.xml");
|
|
s
|
|
}
|
|
|
|
/// Seat the flight master as "npc": the label reads `UnitName("npc")` (`TaxiFrame.lua:27`).
|
|
fn seat_flight_master(s: &mut UiScript, name: &str) {
|
|
let npc = benilla_ui::script::UnitState {
|
|
name: Some(name.into()),
|
|
..Default::default()
|
|
};
|
|
s.set_unit("npc", Some(npc));
|
|
}
|
|
|
|
/// Stormwind (Current) and the Sentinel Hill hop (Reachable, 110 copper, one route), as
|
|
/// `ui_taxi`'s `build_nodes` builds them.
|
|
fn menu() -> TaxiUiState {
|
|
TaxiUiState {
|
|
art: "Interface\\TaxiFrame\\TAXIMAP0".into(),
|
|
nodes: vec![
|
|
TaxiUiNode {
|
|
name: "Stormwind, Elwynn".into(),
|
|
node_type: TaxiNodeType::Current,
|
|
pos: (0.43, 0.33),
|
|
cost: 0,
|
|
routes: vec![],
|
|
},
|
|
TaxiUiNode {
|
|
name: "Sentinel Hill, Westfall".into(),
|
|
node_type: TaxiNodeType::Reachable,
|
|
pos: (0.41, 0.25),
|
|
cost: 110,
|
|
routes: vec![[0.43, 0.33, 0.41, 0.25]],
|
|
},
|
|
],
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn shipped_taxi_frame_drives_end_to_end() {
|
|
benilla_formats::wow_data_or_skip!();
|
|
let mut s = taxi_script();
|
|
|
|
assert!(!s.eval::<bool>("return TaxiFrame:IsVisible()").unwrap());
|
|
|
|
s.set_taxi(Some(menu()));
|
|
seat_flight_master(&mut s, "Dungar Longdrink");
|
|
s.fire_event(
|
|
"TAXIMAP_OPENED",
|
|
vec![ScriptValue::Str("Dungar Longdrink".into())],
|
|
);
|
|
assert!(s.errors().is_empty(), "script errors: {:?}", s.errors());
|
|
|
|
assert!(s.eval::<bool>("return TaxiFrame:IsVisible()").unwrap());
|
|
assert_eq!(
|
|
s.eval::<String>("return TaxiMerchant:GetText()").unwrap(),
|
|
"Dungar Longdrink"
|
|
);
|
|
|
|
// Node buttons are created on demand, one per node (`TaxiFrame.lua:39`): no third exists.
|
|
assert!(s.eval::<bool>("return TaxiButton1:IsVisible()").unwrap());
|
|
assert!(s.eval::<bool>("return TaxiButton2:IsVisible()").unwrap());
|
|
assert!(s.eval::<bool>("return TaxiButton3 == nil").unwrap());
|
|
assert!(s.eval::<bool>("return TaxiButton50 == nil").unwrap());
|
|
|
|
// A click on node 2, Sentinel Hill, calls `TakeTaxiNode`.
|
|
s.resolve();
|
|
let (cx, cy) = s
|
|
.eval::<(f32, f32)>("return TaxiButton2:GetCenter()")
|
|
.unwrap();
|
|
s.mouse_button(cx, cy, "LeftButton", true);
|
|
s.mouse_button(cx, cy, "LeftButton", false);
|
|
assert_eq!(s.take_taxi_node(), vec![2]);
|
|
assert!(s.take_taxi_node().is_empty(), "drained");
|
|
|
|
s.set_taxi(None);
|
|
s.fire_event("TAXIMAP_CLOSED", vec![]);
|
|
assert!(!s.eval::<bool>("return TaxiFrame:IsVisible()").unwrap());
|
|
assert!(s.errors().is_empty(), "script errors: {:?}", s.errors());
|
|
}
|
|
|
|
/// With no reachable node, `DrawOneHopLines` posts `ERR_TAXINOPATHS` and hides the window
|
|
/// (`TaxiFrame.lua:161`).
|
|
#[test]
|
|
fn no_single_hop_destination_posts_the_error_and_closes() {
|
|
benilla_formats::wow_data_or_skip!();
|
|
let mut s = taxi_script();
|
|
s.set_taxi(Some(TaxiUiState {
|
|
art: "Interface\\TaxiFrame\\TAXIMAP0".into(),
|
|
nodes: vec![TaxiUiNode {
|
|
name: "Stormwind, Elwynn".into(),
|
|
node_type: TaxiNodeType::Current,
|
|
pos: (0.43, 0.33),
|
|
cost: 0,
|
|
routes: vec![],
|
|
}],
|
|
}));
|
|
seat_flight_master(&mut s, "Dungar Longdrink");
|
|
s.fire_event(
|
|
"TAXIMAP_OPENED",
|
|
vec![ScriptValue::Str("Dungar Longdrink".into())],
|
|
);
|
|
assert!(s.errors().is_empty(), "script errors: {:?}", s.errors());
|
|
|
|
assert!(!s.eval::<bool>("return TaxiFrame:IsVisible()").unwrap());
|
|
s.resolve();
|
|
let quads = s.extract();
|
|
let has_refusal = quads.iter().any(|q| match &q.content {
|
|
benilla_ui::script::QuadContent::Text { text: Some(t), .. } => {
|
|
t.contains("don\u{2019}t know any flight locations")
|
|
}
|
|
_ => false,
|
|
});
|
|
assert!(
|
|
has_refusal,
|
|
"ERR_TAXINOPATHS posted to UIErrorsFrame: {quads:?}"
|
|
);
|
|
}
|
|
|
|
/// The close button hides the window, whose OnHide calls `CloseTaxiMap()` (`TaxiFrame.xml:167`).
|
|
#[test]
|
|
fn close_button_queues_the_intent_and_hides() {
|
|
benilla_formats::wow_data_or_skip!();
|
|
let mut s = taxi_script();
|
|
s.set_taxi(Some(menu()));
|
|
seat_flight_master(&mut s, "Dungar Longdrink");
|
|
s.fire_event(
|
|
"TAXIMAP_OPENED",
|
|
vec![ScriptValue::Str("Dungar Longdrink".into())],
|
|
);
|
|
assert!(s.eval::<bool>("return TaxiFrame:IsVisible()").unwrap());
|
|
|
|
// `TaxiCloseButton` is a plain `UIPanelCloseButton` (`TaxiFrame.xml:133`).
|
|
s.run("TaxiCloseButton:Click()").unwrap();
|
|
assert!(s.errors().is_empty(), "script errors: {:?}", s.errors());
|
|
assert!(s.take_taxi_close());
|
|
assert!(!s.eval::<bool>("return TaxiFrame:IsVisible()").unwrap());
|
|
}
|