From d13e8458baa7bd609fb1423456fc2da71770c39e Mon Sep 17 00:00:00 2001 From: WiseDev <83840010+wisedevik@users.noreply.github.com> Date: Sun, 23 Aug 2026 17:57:02 +0300 Subject: [PATCH] deploy the bot in front of its own towers, nothing else read the client's memory during a live battle and the board told the story at a glance - a column of the bot's objects down one lane, x fixed at 3500, y stepping by exactly two thousand: 23500, 21500, 19500, ... 3500, 1500, -500, -2500 two thousand is BOT_DEPLOY_AHEAD. the landmark was any building the bot owned, so a hut it had just played became the reference for the next card, that card became the reference for the one after, and they walked up the lane and off the top of the map. every one of them had a movie clip and sat exactly where its sprite said - they were never invisible, they were marching into nowhere. the huts spawning from out there are the 177688 addLogicGameObject calls the hook counted. the landmark is now one of the two tower refs the battle already keeps in leader_towers, and nothing else can become one. the harness missed this because the bot had been given the player's deck, which has no buildings in it. it now plays a cannon and a hut, and asserts each deploy lands in front of a tower on the bot's own half - the old code deploys on top of the king tower and fails. --- crates/game-service/src/battle_session.rs | 41 +++++++--------------- crates/game-service/tests/walk_to_tower.rs | 23 ++++++++---- 2 files changed, 30 insertions(+), 34 deletions(-) diff --git a/crates/game-service/src/battle_session.rs b/crates/game-service/src/battle_session.rs index e512089..20b83d6 100644 --- a/crates/game-service/src/battle_session.rs +++ b/crates/game-service/src/battle_session.rs @@ -102,38 +102,23 @@ impl BattleSession { let card = filled .get(self.random.next(filled.len() as i32).max(0) as usize)? .clone(); - let towers: Vec<(i32, i32)> = self - .mode - .battle - .objects - .objects + let leader = self.mode.battle.leader(crate::bot::BOT_OWNER_INDEX as usize)?; + let (king_x, king_y) = leader.position(); + let towers: Vec<(i32, i32)> = self.mode.battle.leader_towers + [crate::bot::BOT_OWNER_INDEX as usize] .iter() - .filter(|entry| { - entry.owner_index() == crate::bot::BOT_OWNER_INDEX - && entry.stats().is_building() - && entry.is_alive() + .filter_map(|tower| { + self.mode + .battle + .objects + .objects + .iter() + .find(|entry| entry.global_id == *tower && entry.is_alive()) }) .map(|entry| entry.position()) .collect(); - let leader = self.mode.battle.leader(crate::bot::BOT_OWNER_INDEX as usize)?; - let (king_x, king_y) = leader.position(); - let front = towers - .iter() - .copied() - .fold((king_x, king_y), |best, tower| { - if (tower.1 - king_y).abs() > (best.1 - king_y).abs() { - tower - } else { - best - } - }); - let lane: Vec<(i32, i32)> = towers - .iter() - .copied() - .filter(|(_, y)| *y == front.1) - .collect(); - let (x, tower_y) = lane - .get(self.random.next(lane.len().max(1) as i32).max(0) as usize) + let (x, tower_y) = towers + .get(self.random.next(towers.len().max(1) as i32).max(0) as usize) .copied() .unwrap_or((king_x, king_y)); let ahead = tower_y + (tower_y - king_y).signum() * BOT_DEPLOY_AHEAD; diff --git a/crates/game-service/tests/walk_to_tower.rs b/crates/game-service/tests/walk_to_tower.rs index 2cd8eef..2ca867c 100644 --- a/crates/game-service/tests/walk_to_tower.rs +++ b/crates/game-service/tests/walk_to_tower.rs @@ -91,7 +91,7 @@ fn the_bot_deploys_in_front_of_its_own_towers() { } let builder = BattleBuilder::new(&root); let mut deck = logic::model::LogicSpellDeck::default(); - for (slot, name) in ["Knight", "Archers", "Goblins", "Giant"].iter().enumerate() { + for (slot, name) in ["Knight", "Cannon", "GoblinHut", "Giant"].iter().enumerate() { deck.slots[slot] = Some(logic::model::LogicSpell { data: LogicDataRef::spell(name), ..logic::model::LogicSpell::default() @@ -113,12 +113,26 @@ fn the_bot_deploys_in_front_of_its_own_towers() { if tick % 20 == 0 { if let Some((card, at, instance)) = session.bot_play() { let entries = builder.summon(&card, at, 1, 0, instance); + let entries_debug = entries.clone(); session.reserve_instances(entries.len()); for entry in entries { session.push(entry); } + assert!( + (17000..25000).contains(&at.y) && (at.x == 3500 || at.x == 14500), + "the bot deployed at {:?}, which is not in front of one of its towers", + (at.x, at.y) + ); plays += 1; - if plays <= 6 { println!("bot played at {:?}", (at.x, at.y)); } + if plays <= 8 { + println!( + "bot played {:?} at {:?}, {} entries, speeds {:?}", + card, + (at.x, at.y), + entries_debug.len(), + entries_debug.iter().map(|e| e.stats().speed).collect::>() + ); + } } } session.advance_to(tick); @@ -140,13 +154,10 @@ fn the_bot_deploys_in_front_of_its_own_towers() { break; } for entry in session.mode().battle.objects.objects.iter() { - if entry.stats().speed < 1 { - continue; - } let (x, y) = entry.position(); if !((500..=17500).contains(&x) && (1000..=31000).contains(&y)) { println!( - "t={tick}: troop owner={} speed={} alive={} at ({x}, {y})", + "t={tick}: object owner={} speed={} alive={} at ({x}, {y})", entry.owner_index(), entry.stats().speed, entry.is_alive()