From d6cfc81c71408a11da9a9e05de281161deece15a Mon Sep 17 00:00:00 2001 From: WiseDev <83840010+wisedevik@users.noreply.github.com> Date: Sun, 23 Aug 2026 12:30:46 +0300 Subject: [PATCH] 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. --- crates/game-service/src/battle_session.rs | 39 +++++++++++++++++++---- crates/game-service/src/service.rs | 1 + 2 files changed, 33 insertions(+), 7 deletions(-) diff --git a/crates/game-service/src/battle_session.rs b/crates/game-service/src/battle_session.rs index 2930a43..7fe9ba8 100644 --- a/crates/game-service/src/battle_session.rs +++ b/crates/game-service/src/battle_session.rs @@ -7,10 +7,15 @@ use tokio::sync::Mutex; pub struct BattleSession { mode: LogicGameMode, tick: i32, + queued: Vec<(i32, Vec)>, } impl BattleSession { pub fn new(mode: LogicGameMode) -> Self { - Self { mode, tick: 0 } + Self { + mode, + tick: 0, + queued: Vec::new(), + } } pub fn mode(&self) -> &LogicGameMode { &self.mode @@ -21,10 +26,31 @@ impl BattleSession { pub fn seconds(&self) -> i32 { self.tick / BATTLE_TICKS_PER_SECOND } + pub fn queue(&mut self, at_tick: i32, entries: Vec) { + if entries.is_empty() { + return; + } + self.queued.push((at_tick.max(self.tick + 1), entries)); + } pub fn advance_to(&mut self, tick: i32) { - if tick > self.tick { - self.tick = tick; - self.mode.time.tick = tick; + while self.tick < tick { + self.tick += 1; + 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 { @@ -95,6 +121,7 @@ impl BattleRegistry { executor: LogicLong, slot: i32, position: logic::battle::LogicVector2, + at_tick: i32, summon: F, ) -> usize where @@ -112,9 +139,7 @@ impl BattleRegistry { }; let entries = summon(&card, position, owner, session.next_instance()); let spawned = entries.len(); - for entry in entries { - session.push(entry); - } + session.queue(at_tick, entries); spawned } } diff --git a/crates/game-service/src/service.rs b/crates/game-service/src/service.rs index d665597..a5f6e1f 100644 --- a/crates/game-service/src/service.rs +++ b/crates/game-service/src/service.rs @@ -321,6 +321,7 @@ impl GameApi for GameService { played.header.executor_account_id, played.deck_slot, played.position, + played.header.execute_tick, |card, position, owner, instance| { self.battles.summon(card, position, owner, 0, instance) },