diff --git a/crates/benilla-app/src/ui_taxi/routing.rs b/crates/benilla-app/src/ui_taxi/routing.rs index 6dd97a1c..80e8e381 100644 --- a/crates/benilla-app/src/ui_taxi/routing.rs +++ b/crates/benilla-app/src/ui_taxi/routing.rs @@ -429,6 +429,14 @@ mod tests { if n.id == 3 { continue; // Programmer Isle } + // The realm's own canoe node, and genuinely outside the continent's taxi + // rectangle rather than a projection fault: Kalimdor's `WorldMapContinent.dbc` + // box ends at x = 12470 and this node sits at x = 13651.5, 1181 yards past it. + // `project` is the reference's own arithmetic, so the real client draws this blip + // a few per cent below the map art too. Every other one of the 122 rows is inside. + if n.id == 119 { + continue; // Canoe: Icepoint Rock - Darkshore + } let (x, y) = project(cont, n.pos[0], n.pos[1]); assert!( (0.02..=0.98).contains(&x) && (0.02..=0.98).contains(&y), @@ -444,7 +452,9 @@ mod tests { let ironforge = nodes.get(6).expect("Ironforge"); let menethil = nodes.get(7).expect("Menethil Harbor"); let lakeshire = nodes.get(5).expect("Lakeshire"); - assert_eq!(stormwind.name, "Stormwind, Elwynn"); + // "Elwynn Forest", not stock's clipped "Elwynn": the realm spells the zone out. The other + // three rows read as 1.12 wrote them. + assert_eq!(stormwind.name, "Stormwind, Elwynn Forest"); assert_eq!(ironforge.name, "Ironforge, Dun Morogh"); assert_eq!(menethil.name, "Menethil Harbor, Wetlands"); assert_eq!(lakeshire.name, "Lakeshire, Redridge"); @@ -464,7 +474,7 @@ mod tests { ); } - /// The real Stormwind (2) to Sentinel Hill (4) hop, `TaxiPath` id 6 at 110 copper; with every + /// The real Stormwind (2) to Sentinel Hill (4) hop, `TaxiPath` id 1 at 110 copper (stock numbered it 6); with every /// node known, the ones Stormwind cannot route to are left out. #[test] fn build_nodes_classifies_a_real_known_hop() { @@ -486,7 +496,7 @@ mod tests { let sw_idx = ui .iter() - .position(|n| n.name == "Stormwind, Elwynn") + .position(|n| n.name == "Stormwind, Elwynn Forest") .expect("Stormwind visible"); assert_eq!(ui[sw_idx].node_type, TaxiNodeType::Current); assert_eq!(resolved[sw_idx].chain, vec![2]); diff --git a/crates/benilla-app/src/world_state_ui.rs b/crates/benilla-app/src/world_state_ui.rs index 957542be..1d85cee6 100644 --- a/crates/benilla-app/src/world_state_ui.rs +++ b/crates/benilla-app/src/world_state_ui.rs @@ -331,8 +331,8 @@ mod tests { let rows = build(&catalog, &states, true); assert_eq!( rows.len(), - 3, - "the two tower counters plus the progress bar" + 4, + "the two tower counters, the progress bar, and the realm's second capture point" ); assert_eq!(rows[0].text, "Towers Controlled: 3"); assert_eq!(rows[0].icon, "Interface\\WorldStateFrame\\AllianceTower"); @@ -343,6 +343,14 @@ mod tests { assert_eq!(rows[2].text, "Progress: 60"); assert_eq!(rows[2].extended_ui, "CAPTUREPOINT"); assert_eq!(rows[2].extended_ui_state, [60, 40, 0]); + + // The realm added a second `CAPTUREPOINT` row to this zone. This fixture writes no value + // for its StateVariable, so it resolves to 0 and its `ui_state` is 0 — and handing it up + // anyway is correct: `WorldStateFrame_Update`'s loop is gated on `if ( state > 0 )` + // (`WorldStateFrame.lua:41`), so the filtering is the reference's own Lua, not ours. + assert_eq!(rows[3].extended_ui, "CAPTUREPOINT"); + assert_eq!(rows[3].ui_state, 0, "unset, so the Lua skips it"); + assert_eq!(rows[3].extended_ui_state, [0, 0, 0]); assert_eq!(rows[2].ui_state, 1, "state 2426 reads 1"); // The server flips a tower. @@ -351,9 +359,19 @@ mod tests { assert_eq!(rows[0].text, "Towers Controlled: 2"); assert_eq!(rows[1].text, "Towers Controlled: 2"); + // Elsewhere on the same continent, nothing the reference would draw. Not nothing at all: + // the realm's new `CAPTUREPOINT` row is scoped to the whole of map 0, so it comes back + // here too — with its StateVariable unset, so `ui_state` is 0 and + // `WorldStateFrame_Update`'s `if ( state > 0 )` skips it (`WorldStateFrame.lua:41`). The + // property that matters is that no *live* row escapes its zone. let mut elsewhere = WorldStates::default(); elsewhere.init_scope(0, 12); // Elwynn Forest - assert!(build(&catalog, &elsewhere, true).is_empty()); + let stray: Vec = build(&catalog, &elsewhere, true) + .into_iter() + .filter(|r| r.ui_state > 0) + .map(|r| r.text) + .collect(); + assert!(stray.is_empty(), "a live row escaped its zone: {stray:?}"); // Warsong Gulch: `Type` 0, with dynamic flag icons. let mut wsg = WorldStates::default(); diff --git a/crates/benilla-formats/src/taxi.rs b/crates/benilla-formats/src/taxi.rs index 3bf8dc3e..dbf6bcb7 100644 --- a/crates/benilla-formats/src/taxi.rs +++ b/crates/benilla-formats/src/taxi.rs @@ -121,8 +121,11 @@ mod tests { cat.len() ); + // 23 nodes, where 1.12's path 302 had 36: the realm reshaped it, and it now stays on + // Eastern Kingdoms rather than crossing. `TaxiPath` grew 287 -> 364 hops and `TaxiNodes` + // 85 -> 122 in the same update, so a re-cut path is the expected kind of difference. let path302 = cat.path(302).expect("path 302 exists"); - assert_eq!(path302.len(), 36, "path 302 has 36 nodes"); + assert_eq!(path302.len(), 23, "the realm's path 302 has 23 nodes"); for w in path302.windows(2) { assert!( w[0].node_index < w[1].node_index, @@ -131,33 +134,61 @@ mod tests { w[1].node_index ); } - let maps: std::collections::BTreeSet = path302.iter().map(|n| n.map_id).collect(); + // The cross-continent property moved with it, so it is pinned on a path that still has + // it: 72 spans both maps over 37 nodes. That a path *can* cross is what the decode has to + // keep answering, since the map a node belongs to is per node, not per path. + let crossing: std::collections::BTreeSet = cat + .path(72) + .expect("path 72 exists") + .iter() + .map(|n| n.map_id) + .collect(); assert_eq!( - maps, + crossing, std::collections::BTreeSet::from([0, 1]), - "path 302 spans maps {{0,1}}" + "path 72 spans maps {{0,1}}" ); + // The per-node `flags`/`delay` pair, pinned on path 72 as well, because the realm's + // re-cut left 302 and 285 with no stop nodes at all (every node flags 0) — the landing + // stops moved onto the paths that cross, which is where they belong. 72 carries two, at + // node indexes 11 and 29, each a 60-second delay. + let crossing_path = cat.path(72).expect("path 72 exists"); let mut flag_histogram: HashMap = HashMap::new(); - for n in path302 { + for n in crossing_path { *flag_histogram.entry(n.flags).or_insert(0) += 1; } - assert_eq!(flag_histogram.get(&0).copied(), Some(34)); + assert_eq!(flag_histogram.get(&0).copied(), Some(35)); assert_eq!(flag_histogram.get(&2).copied(), Some(2)); - let flag2_delays: Vec = path302 - .iter() - .filter(|n| n.flags == 2) - .map(|n| n.delay) - .collect(); - assert_eq!(flag2_delays, vec![60, 60], "both flag-2 nodes delay 60s"); + assert_eq!( + crossing_path + .iter() + .filter(|n| n.flags == 2) + .map(|n| n.delay) + .collect::>(), + vec![60, 60], + "both flag-2 nodes delay 60s" + ); + assert_eq!( + crossing_path + .iter() + .filter(|n| n.flags == 2) + .map(|n| n.node_index) + .collect::>(), + vec![11, 29], + "path 72 stops at indexes 11, 29" + ); + // 302 and 285 keep their own sanity: a straight run with no stops. + assert!( + path302.iter().all(|n| n.flags == 0), + "the realm's path 302 is a straight run" + ); let path285 = cat.path(285).expect("path 285 exists"); - assert_eq!(path285.len(), 27, "path 285 has 27 nodes"); - let stop_indexes: Vec = path285 - .iter() - .filter(|n| n.flags == 2) - .map(|n| n.node_index) - .collect(); - assert_eq!(stop_indexes, vec![4, 20], "path 285 stops at indexes 4, 20"); + assert_eq!(path285.len(), 21, "the realm's path 285 has 21 nodes"); + assert!( + path285.iter().all(|n| n.flags == 0), + "and so is 285 — stock had it stopping at indexes 4 and 20" + ); let path292 = cat.path(292).expect("path 292 exists"); assert!(!path292.is_empty(), "path 292 is non-empty"); diff --git a/crates/benilla-formats/src/taxi_nodes.rs b/crates/benilla-formats/src/taxi_nodes.rs index 8e95a07b..7b8c52f2 100644 --- a/crates/benilla-formats/src/taxi_nodes.rs +++ b/crates/benilla-formats/src/taxi_nodes.rs @@ -108,10 +108,12 @@ mod tests { let data = crate::wow_data_or_skip!(); let mut chain = crate::open_chain(&data).expect("open chain"); let cat = load_taxi_nodes(&mut chain).expect("load TaxiNodes"); - assert_eq!(cat.len(), 85, "1.12 ships 85 taxi nodes"); + // 122, not 1.12's 85: the realm adds 37 nodes, its boat and canoe routes among them. + assert_eq!(cat.len(), 122, "the realm ships 122 taxi nodes"); let stormwind = cat.get(2).expect("node 2 exists"); - assert_eq!(stormwind.name, "Stormwind, Elwynn"); + // The realm spells the zone out where stock clipped it to "Elwynn". + assert_eq!(stormwind.name, "Stormwind, Elwynn Forest"); assert_eq!(stormwind.map_id, 0, "Eastern Kingdoms"); assert_eq!(stormwind.mount_horde, 0, "no Horde service from Stormwind"); assert_eq!( diff --git a/crates/benilla-formats/src/taxi_path.rs b/crates/benilla-formats/src/taxi_path.rs index 95635a3b..f3f63346 100644 --- a/crates/benilla-formats/src/taxi_path.rs +++ b/crates/benilla-formats/src/taxi_path.rs @@ -83,16 +83,19 @@ pub fn load_taxi_paths(chain: &mut Chain) -> Result { mod tests { use super::*; - /// Hop 6 runs Stormwind (node 2) to Sentinel Hill (node 4) for 110 copper. + /// Hop **1** runs Stormwind (node 2) to Sentinel Hill (node 4) for 110 copper. Stock 1.12 had + /// that hop as id 6; the realm renumbered when it added its 77 hops, and 6 is now Stormwind to + /// Ironforge at 50 copper. The fare is the part that did not move. #[test] fn real_taxi_path_layout_sanity() { let data = crate::wow_data_or_skip!(); let mut chain = crate::open_chain(&data).expect("open chain"); let cat = load_taxi_paths(&mut chain).expect("load TaxiPath"); - assert_eq!(cat.len(), 287, "1.12 ships 287 taxi path hops"); + // 364, not 1.12's 287: the realm adds 77 hops alongside its 37 new nodes. + assert_eq!(cat.len(), 364, "the realm ships 364 taxi path hops"); let hop = cat.between(2, 4).expect("a direct 2 -> 4 hop exists"); - assert_eq!(hop.id, 6); + assert_eq!(hop.id, 1); assert_eq!(hop.cost, 110); assert!(hop.cost > 0, "a real hop always carries a nonzero fare"); diff --git a/crates/benilla-formats/tests/chain.rs b/crates/benilla-formats/tests/chain.rs index 009eb058..381df58e 100644 --- a/crates/benilla-formats/tests/chain.rs +++ b/crates/benilla-formats/tests/chain.rs @@ -199,7 +199,13 @@ fn dumps_taxinodes_to_csv() { let (records, fields) = benilla_formats::dbc_to_csv(&bytes, "TaxiNodes.dbc", &out).expect("dbc->csv"); - assert_eq!((records, fields), (85, 16), "vanilla TaxiNodes.dbc shape"); + // 122 rows, not vanilla's 85: the realm adds 37 flight nodes — its boat and canoe routes + // among them. The 16 fields are unchanged, which is the half that would break the decode. + assert_eq!( + (records, fields), + (122, 16), + "the realm's TaxiNodes.dbc shape" + ); let csv = std::fs::read_to_string(&out).expect("read csv"); assert!( csv.lines() @@ -209,7 +215,7 @@ fn dumps_taxinodes_to_csv() { "schema-derived header" ); assert!(csv.contains("Stormwind"), "expected a known flight node"); - assert_eq!(csv.lines().count(), 86, "85 records + 1 header row"); + assert_eq!(csv.lines().count(), 123, "122 records + 1 header row"); let _ = std::fs::remove_file(&out); }