The battleground queue slots answer "none" from the first frame, so a party login no longer breaks BattlefieldFrame_Update (#603)
The reference's three queue slots are a static array (`0xb6e9d0`): `GetBattlefieldStatus(1..3)` always reads one (`0x4ab4df`-`0x4ab4f3`), and a zeroed slot answers "none". The VM held an empty list until the app's first push, so an in-range index took the off-slot leg and answered a nil status. A party login fires PARTY_LEADER_CHANGED before that push, `BattlefieldFrame_Update` read the nil status as queued and concatenated the nil map name (`BattlefieldFrame.lua:242`). The VM now holds exactly three slots, zeroed until pushed, and the push takes three. Verified: the new app test fires PARTY_LEADER_CHANGED on the stock BattlefieldFrame before any push and failed with the reported error before the change; live, Probefive relogged into a stored party raised the same error on the old build and none on this one (slot 1 reads "none", "Eastern Kingdoms"), and a solo login after leaving the party is clean. Built on macOS only; no platform seam. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
ca7111ed1a
commit
e4b06c5c2f
@@ -211,9 +211,8 @@ fn feed_battlefield(
|
||||
|
||||
let slots = queue
|
||||
.slots()
|
||||
.iter()
|
||||
.map(|s| slot_view(s.as_ref(), catalog, now))
|
||||
.collect();
|
||||
.each_ref()
|
||||
.map(|s| slot_view(s.as_ref(), catalog, now));
|
||||
script.set_battlefield_queue(slots, queue.instance_expiration_ms(now));
|
||||
|
||||
if std::mem::take(&mut state.show) {
|
||||
|
||||
@@ -77,7 +77,7 @@ fn the_queue_icon_follows_the_slots_across_update_battlefield_status() {
|
||||
let mut s = session();
|
||||
assert!(!visible(&s, "MiniMapBattlefieldFrame"), "no queue at load");
|
||||
|
||||
s.set_battlefield_queue(vec![slot(529, "Arathi Basin", 1, 0), empty(), empty()], 0);
|
||||
s.set_battlefield_queue([slot(529, "Arathi Basin", 1, 0), empty(), empty()], 0);
|
||||
s.fire_event("UPDATE_BATTLEFIELD_STATUS", vec![]);
|
||||
assert!(
|
||||
visible(&s, "MiniMapBattlefieldFrame"),
|
||||
@@ -103,7 +103,7 @@ fn the_queue_icon_follows_the_slots_across_update_battlefield_status() {
|
||||
assert!(!visible(&s, "StaticPopup1"), "queued is not yet a question");
|
||||
|
||||
// Status 2, ready to enter: the confirm dialog and the port countdown.
|
||||
s.set_battlefield_queue(vec![slot(529, "Arathi Basin", 2, 3), empty(), empty()], 0);
|
||||
s.set_battlefield_queue([slot(529, "Arathi Basin", 2, 3), empty(), empty()], 0);
|
||||
s.fire_event("UPDATE_BATTLEFIELD_STATUS", vec![]);
|
||||
assert!(
|
||||
visible(&s, "StaticPopup1"),
|
||||
@@ -127,7 +127,7 @@ fn the_queue_icon_follows_the_slots_across_update_battlefield_status() {
|
||||
s.run("StaticPopup1Button1:Click()").unwrap();
|
||||
assert_eq!(s.take_battlefield_port_requests(), vec![(1, true)]);
|
||||
|
||||
s.set_battlefield_queue(vec![empty(), empty(), empty()], 0);
|
||||
s.set_battlefield_queue([empty(), empty(), empty()], 0);
|
||||
s.fire_event("UPDATE_BATTLEFIELD_STATUS", vec![]);
|
||||
assert!(
|
||||
!visible(&s, "MiniMapBattlefieldFrame"),
|
||||
@@ -154,7 +154,7 @@ fn the_list_window_opens_on_battlefields_show_and_joins_the_selection() {
|
||||
}),
|
||||
group_queue: true,
|
||||
});
|
||||
s.set_battlefield_queue(vec![empty(), empty(), empty()], 0);
|
||||
s.set_battlefield_queue([empty(), empty(), empty()], 0);
|
||||
assert!(!visible(&s, "BattlefieldFrame"));
|
||||
s.fire_event("BATTLEFIELDS_SHOW", vec![]);
|
||||
assert!(visible(&s, "BattlefieldFrame"), "the event shows the panel");
|
||||
@@ -228,7 +228,7 @@ fn the_list_rows_carry_the_queue_status() {
|
||||
}),
|
||||
..Default::default()
|
||||
});
|
||||
s.set_battlefield_queue(vec![slot(529, "Arathi Basin", 1, 4), empty(), empty()], 0);
|
||||
s.set_battlefield_queue([slot(529, "Arathi Basin", 1, 4), empty(), empty()], 0);
|
||||
s.fire_event("BATTLEFIELDS_SHOW", vec![]);
|
||||
assert_eq!(
|
||||
s.eval::<String>("return BattlefieldZone2Status:GetText()")
|
||||
@@ -243,3 +243,14 @@ fn the_list_rows_carry_the_queue_status() {
|
||||
(`FontString:GetText 0x79d690` substitutes nil)"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_party_leader_change_before_any_queue_push_updates_the_list_cleanly() {
|
||||
let _data = benilla_formats::wow_data_or_skip!();
|
||||
let mut s = session();
|
||||
s.take_errors();
|
||||
// A party login fires PARTY_LEADER_CHANGED, which runs `BattlefieldFrame_Update`; it reads
|
||||
// `GetBattlefieldStatus(1..3)` and concatenates the name of any status but "none" (line 242).
|
||||
s.fire_event("PARTY_LEADER_CHANGED", vec![]);
|
||||
assert_eq!(s.take_errors(), Vec::<String>::new());
|
||||
}
|
||||
|
||||
@@ -13,7 +13,8 @@ use super::Model;
|
||||
pub struct BattlefieldQueueSlot {
|
||||
/// `+0x00`, the Map.dbc row id; 0 is a cleared slot, its name looked up like any other.
|
||||
pub map_id: u32,
|
||||
/// The localized map name, or `None` when the id has no row (a real `nil`).
|
||||
/// The localized map name, or `None` when the id has no row (a real `nil`). The VM holds no
|
||||
/// Map.dbc, so a slot not yet pushed answers `nil` where the reference names row 0.
|
||||
pub map_name: Option<String>,
|
||||
/// `+0x04`: `0` none, `1` queued, `2` confirm, `3` active; anything else answers `"error"`.
|
||||
pub status: u32,
|
||||
@@ -102,7 +103,7 @@ impl super::UiScript {
|
||||
/// clock getters move.
|
||||
pub fn set_battlefield_queue(
|
||||
&mut self,
|
||||
slots: Vec<BattlefieldQueueSlot>,
|
||||
slots: [BattlefieldQueueSlot; 3],
|
||||
instance_expiration_ms: u32,
|
||||
) {
|
||||
let mut model = self.model_mut();
|
||||
@@ -498,7 +499,7 @@ mod tests {
|
||||
#[test]
|
||||
fn get_battlefield_status_answers_five_values_on_every_leg() {
|
||||
let mut s = UiScript::new().unwrap();
|
||||
s.set_battlefield_queue(vec![slot(489, 1, 5), slot(0, 0, 0), slot(529, 9, 2)], 0);
|
||||
s.set_battlefield_queue([slot(489, 1, 5), slot(0, 0, 0), slot(529, 9, 2)], 0);
|
||||
let n = |s: &mut UiScript, i: &str| s.arity(&format!("GetBattlefieldStatus({i})")).unwrap();
|
||||
assert_eq!(n(&mut s, "1"), 5);
|
||||
assert_eq!(n(&mut s, "0"), 5);
|
||||
@@ -532,7 +533,7 @@ mod tests {
|
||||
assert!(err.contains("Usage: GetBattlefieldStatus(index)"), "{err}");
|
||||
let mut empty = slot(0, 0, 0);
|
||||
empty.map_name = None;
|
||||
s.set_battlefield_queue(vec![empty], 0);
|
||||
s.set_battlefield_queue([empty, Default::default(), Default::default()], 0);
|
||||
assert!(
|
||||
s.eval::<bool>("local _, name = GetBattlefieldStatus(1) return name == nil")
|
||||
.unwrap(),
|
||||
@@ -540,10 +541,26 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn every_slot_answers_none_before_the_first_push() {
|
||||
let s = UiScript::new().unwrap();
|
||||
for i in 1..=3 {
|
||||
assert_eq!(
|
||||
s.eval::<String>(&format!("return (GetBattlefieldStatus({i}))"))
|
||||
.unwrap(),
|
||||
"none",
|
||||
"slot {i}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_time_getters_read_the_pushed_milliseconds() {
|
||||
let mut s = UiScript::new().unwrap();
|
||||
s.set_battlefield_queue(vec![slot(489, 1, 5)], 120_000);
|
||||
s.set_battlefield_queue(
|
||||
[slot(489, 1, 5), Default::default(), Default::default()],
|
||||
120_000,
|
||||
);
|
||||
assert_eq!(
|
||||
s.eval::<i64>("return GetBattlefieldPortExpiration(1)")
|
||||
.unwrap(),
|
||||
@@ -594,7 +611,7 @@ mod tests {
|
||||
#[test]
|
||||
fn show_battlefield_list_gates_on_a_queued_slot() {
|
||||
let mut s = UiScript::new().unwrap();
|
||||
s.set_battlefield_queue(vec![slot(489, 1, 5), slot(529, 2, 1), slot(0, 1, 0)], 0);
|
||||
s.set_battlefield_queue([slot(489, 1, 5), slot(529, 2, 1), slot(0, 1, 0)], 0);
|
||||
s.run("ShowBattlefieldList(1) ShowBattlefieldList(2) ShowBattlefieldList(3) ShowBattlefieldList(4)")
|
||||
.unwrap();
|
||||
assert_eq!(s.take_battlefield_list_requests(), vec![489]);
|
||||
|
||||
@@ -490,8 +490,10 @@ pub(crate) struct Model {
|
||||
pub(crate) battlefield_list: super::battlefield_queue::BattlefieldListView,
|
||||
/// The selected instance id (`[0xb6eba0]`), not an index; written by `SetSelectedBattlefield`.
|
||||
pub(crate) battlefield_selected: u32,
|
||||
/// The three queue slots, pushed each frame with their clocks reduced to values.
|
||||
pub(crate) battlefield_slots: Vec<super::battlefield_queue::BattlefieldQueueSlot>,
|
||||
/// The three queue slots, pushed each frame with their clocks reduced to values. Zeroed
|
||||
/// until the first push, as the reference's static slots (`0xb6e9d0`) are, so each answers
|
||||
/// "none" from the first frame.
|
||||
pub(crate) battlefield_slots: [super::battlefield_queue::BattlefieldQueueSlot; 3],
|
||||
/// `GetBattlefieldInstanceExpiration()`, pushed each frame.
|
||||
pub(crate) battlefield_instance_expiration_ms: u32,
|
||||
/// `JoinBattlefield` calls, `(instance id, as group)`.
|
||||
@@ -1191,7 +1193,7 @@ impl Model {
|
||||
battlefield_leave_requests: 0,
|
||||
battlefield_list: Default::default(),
|
||||
battlefield_selected: 0,
|
||||
battlefield_slots: Vec::new(),
|
||||
battlefield_slots: Default::default(),
|
||||
battlefield_instance_expiration_ms: 0,
|
||||
battlefield_join_requests: Vec::new(),
|
||||
battlefield_list_requests: Vec::new(),
|
||||
|
||||
Reference in New Issue
Block a user