From b89ff673154ca01fb33644012d60c1c8c8efed8d Mon Sep 17 00:00:00 2001 From: autocommit Date: Fri, 1 May 2026 18:52:57 -0700 Subject: [PATCH] =?UTF-8?q?feat(simulator):=20=E2=9C=A8=20Implement=20Clea?= =?UTF-8?q?rRally=20building=20action=20handler=20and=20update=20processor?= =?UTF-8?q?=20logic=20for=20Minecraft=20turn-based=20simulator?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Lilith Autocommit --- .../mc-turn/src/building_action_handlers.rs | 84 +++++++++++++++++++ src/simulator/crates/mc-turn/src/processor.rs | 1 + 2 files changed, 85 insertions(+) diff --git a/src/simulator/crates/mc-turn/src/building_action_handlers.rs b/src/simulator/crates/mc-turn/src/building_action_handlers.rs index bc4c7b9b..ac49c026 100644 --- a/src/simulator/crates/mc-turn/src/building_action_handlers.rs +++ b/src/simulator/crates/mc-turn/src/building_action_handlers.rs @@ -43,3 +43,87 @@ pub fn drain_pending_building_actions(state: &mut GameState) { let _ = invoke(state, &req); } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::game_state::{BuildingRallyPoint, PlayerState, RallyCommand}; + + fn state_with_rally(building_id: &str) -> GameState { + let mut player = PlayerState::default(); + player.rally_points.push(BuildingRallyPoint { + city_index: 0, + building_id: building_id.to_string(), + hex: (3, 5), + command: RallyCommand::Defend, + }); + let mut state = GameState::default(); + state.players.push(player); + state + } + + #[test] + fn clear_rally_removes_matching_entry() { + let mut state = state_with_rally("barracks"); + assert_eq!(state.players[0].rally_points.len(), 1); + + state.pending_building_actions.push(BuildingActionRequest { + player_idx: 0, + city_idx: 0, + building_id: "barracks".to_string(), + kind: BuildingActionKind::ClearRally, + }); + drain_pending_building_actions(&mut state); + + assert!(state.pending_building_actions.is_empty(), "queue drained"); + assert!(state.players[0].rally_points.is_empty(), "rally point removed"); + } + + #[test] + fn clear_rally_leaves_other_buildings_untouched() { + let mut state = state_with_rally("barracks"); + state.players[0].rally_points.push(BuildingRallyPoint { + city_index: 0, + building_id: "watchtower".to_string(), + hex: (1, 2), + command: RallyCommand::Defend, + }); + assert_eq!(state.players[0].rally_points.len(), 2); + + state.pending_building_actions.push(BuildingActionRequest { + player_idx: 0, + city_idx: 0, + building_id: "barracks".to_string(), + kind: BuildingActionKind::ClearRally, + }); + drain_pending_building_actions(&mut state); + + assert_eq!(state.players[0].rally_points.len(), 1); + assert_eq!(state.players[0].rally_points[0].building_id, "watchtower"); + } + + #[test] + fn stubbed_actions_return_not_yet_implemented() { + let mut state = GameState::default(); + state.players.push(PlayerState::default()); + let req = BuildingActionRequest { + player_idx: 0, + city_idx: 0, + building_id: "barracks".to_string(), + kind: BuildingActionKind::GarrisonIn, + }; + assert_eq!(invoke(&mut state, &req), Err(BuildingActionError::NotYetImplemented)); + } + + #[test] + fn out_of_range_player_returns_error() { + let mut state = GameState::default(); // no players + let req = BuildingActionRequest { + player_idx: 0, + city_idx: 0, + building_id: "barracks".to_string(), + kind: BuildingActionKind::ClearRally, + }; + assert_eq!(invoke(&mut state, &req), Err(BuildingActionError::PlayerOutOfRange)); + } +} diff --git a/src/simulator/crates/mc-turn/src/processor.rs b/src/simulator/crates/mc-turn/src/processor.rs index d626f201..45aa4e29 100644 --- a/src/simulator/crates/mc-turn/src/processor.rs +++ b/src/simulator/crates/mc-turn/src/processor.rs @@ -307,6 +307,7 @@ impl TurnProcessor { // Phase 4f: drain formation requests from GDScript (rally, command, shape, split, auto-join). Self::drain_formation_requests(state); + crate::building_action_handlers::drain_pending_building_actions(state); // Phase 4g: recompute formation membership from adjacency. Self::aggregate_formations(state);