From dfdcc3072aa0ae63aaac930761923678d754ec10 Mon Sep 17 00:00:00 2001 From: WiseDev <83840010+wisedevik@users.noreply.github.com> Date: Sun, 23 Aug 2026 18:10:25 +0300 Subject: [PATCH] stop a battle that has already been decided the hook showed a battle at tick 4177 - two hundred and eight seconds of a hundred and eighty second match - sitting at three crowns to nil with the player holding no towers at all. advance_to never asked whether the battle was over, so the clock ran past the end, the bot kept playing cards, and the snapshots kept coming. anything the player put down after that walked into an army that had been piling up for a minute, which is what "my units do not spawn" actually looked like. is_end_condition_matched was right all along - it reads the leaders and the clock and says so. nothing called it. now the tick loop breaks on it, and the harness walks a match to its end and checks the tick stops moving. two things ruled out while looking. the account ids are right: the client reads its own as 0-5, finds itself at index 0 and takes the bottom avatar, and the 0-0 lookups in the trace are it resolving the bot. and "visitor" is cosmetic - getHomeTeamIndex defaults to 1 when both avatars share an arena, which ours do. --- crates/game-service/src/battle_session.rs | 3 +++ crates/game-service/tests/walk_to_tower.rs | 31 ++++++++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/crates/game-service/src/battle_session.rs b/crates/game-service/src/battle_session.rs index 20b83d6..99ecab5 100644 --- a/crates/game-service/src/battle_session.rs +++ b/crates/game-service/src/battle_session.rs @@ -228,6 +228,9 @@ impl BattleSession { pub fn advance_to(&mut self, tick: i32) { let tick = tick.min(self.tick + MAX_CATCH_UP_TICKS); while self.tick < tick { + if self.mode.battle.is_end_condition_matched(self.tick) { + break; + } self.tick += 1; self.mode.time.tick = self.tick; self.release_queued(self.tick); diff --git a/crates/game-service/tests/walk_to_tower.rs b/crates/game-service/tests/walk_to_tower.rs index 2ca867c..1afb13c 100644 --- a/crates/game-service/tests/walk_to_tower.rs +++ b/crates/game-service/tests/walk_to_tower.rs @@ -1,6 +1,7 @@ use std::path::Path; use std::sync::Arc; use game_service::battle::BattleBuilder; +use game_service::battle_session::MAX_CATCH_UP_TICKS; use logic::battle::{LogicVector2, BATTLE_TICKS_PER_SECOND}; use logic::data::{table, LogicDataRef, LogicDataTables}; use logic::model::LogicClientAvatar; @@ -265,3 +266,33 @@ fn both_leaders_resolve_to_a_summoner() { ); } } +#[test] +fn a_finished_battle_stops_ticking() { + let root = assets(); + if let Ok(tables) = LogicDataTables::load_from_dir(&root) { + LogicDataTables::install(Arc::new(tables)); + } + let builder = BattleBuilder::new(&root); + let mode = builder + .build( + LogicDataRef::by_name(table::LOCATIONS, "PvP_goblin"), + LogicDataRef::None, + LogicDataRef::by_name(table::ARENAS, "Arena_T"), + vec![avatar(5), avatar(0)], + [None, None], + 1, + ) + .expect("battle"); + let mut session = game_service::battle_session::BattleSession::new(mode, Vec::new()); + for _ in 0..400 { + session.advance_to(session.tick() + MAX_CATCH_UP_TICKS); + } + assert!(session.is_finished(), "the battle never reached its end"); + let settled = session.tick(); + session.advance_to(settled + 10_000); + assert_eq!( + session.tick(), + settled, + "a finished battle carried on ticking" + ); +}