play spawns on the tick the command names

commands carry tick_when_given and execute_tick, so a spawn no longer
lands the moment its turn message arrives. it is queued and released on
its own tick while advance_to steps the battle one tick at a time, which
is what makes the simulation reproducible from the command stream rather
than from when packets happened to arrive.

the per-tick object update goes in this loop next.
This commit is contained in:
WiseDev 2026-08-23 12:30:46 +03:00
parent 83a1dd2bd3
commit d6cfc81c71
2 changed files with 33 additions and 7 deletions

View file

@ -7,10 +7,15 @@ use tokio::sync::Mutex;
pub struct BattleSession { pub struct BattleSession {
mode: LogicGameMode, mode: LogicGameMode,
tick: i32, tick: i32,
queued: Vec<(i32, Vec<LogicGameObjectEntry>)>,
} }
impl BattleSession { impl BattleSession {
pub fn new(mode: LogicGameMode) -> Self { pub fn new(mode: LogicGameMode) -> Self {
Self { mode, tick: 0 } Self {
mode,
tick: 0,
queued: Vec::new(),
}
} }
pub fn mode(&self) -> &LogicGameMode { pub fn mode(&self) -> &LogicGameMode {
&self.mode &self.mode
@ -21,10 +26,31 @@ impl BattleSession {
pub fn seconds(&self) -> i32 { pub fn seconds(&self) -> i32 {
self.tick / BATTLE_TICKS_PER_SECOND self.tick / BATTLE_TICKS_PER_SECOND
} }
pub fn queue(&mut self, at_tick: i32, entries: Vec<LogicGameObjectEntry>) {
if entries.is_empty() {
return;
}
self.queued.push((at_tick.max(self.tick + 1), entries));
}
pub fn advance_to(&mut self, tick: i32) { pub fn advance_to(&mut self, tick: i32) {
if tick > self.tick { while self.tick < tick {
self.tick = tick; self.tick += 1;
self.mode.time.tick = tick; self.release_queued(self.tick);
}
self.mode.time.tick = self.tick;
}
fn release_queued(&mut self, tick: i32) {
let mut due = Vec::new();
self.queued.retain(|(at, entries)| {
if *at <= tick {
due.extend(entries.iter().cloned());
false
} else {
true
}
});
for entry in due {
self.mode.battle.objects.push(entry);
} }
} }
pub fn is_finished(&self) -> bool { pub fn is_finished(&self) -> bool {
@ -95,6 +121,7 @@ impl BattleRegistry {
executor: LogicLong, executor: LogicLong,
slot: i32, slot: i32,
position: logic::battle::LogicVector2, position: logic::battle::LogicVector2,
at_tick: i32,
summon: F, summon: F,
) -> usize ) -> usize
where where
@ -112,9 +139,7 @@ impl BattleRegistry {
}; };
let entries = summon(&card, position, owner, session.next_instance()); let entries = summon(&card, position, owner, session.next_instance());
let spawned = entries.len(); let spawned = entries.len();
for entry in entries { session.queue(at_tick, entries);
session.push(entry);
}
spawned spawned
} }
} }

View file

@ -321,6 +321,7 @@ impl GameApi for GameService {
played.header.executor_account_id, played.header.executor_account_id,
played.deck_slot, played.deck_slot,
played.position, played.position,
played.header.execute_tick,
|card, position, owner, instance| { |card, position, owner, instance| {
self.battles.summon(card, position, owner, 0, instance) self.battles.summon(card, position, owner, 0, instance)
}, },