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
@@ -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