diff --git a/src/simulator/crates/mc-turn/src/action_handlers.rs b/src/simulator/crates/mc-turn/src/action_handlers/mod.rs similarity index 81% rename from src/simulator/crates/mc-turn/src/action_handlers.rs rename to src/simulator/crates/mc-turn/src/action_handlers/mod.rs index a527ff00..3ff319ce 100644 --- a/src/simulator/crates/mc-turn/src/action_handlers.rs +++ b/src/simulator/crates/mc-turn/src/action_handlers/mod.rs @@ -4,6 +4,15 @@ //! `processor.rs` routes external action invocations here. Move/Attack/Combat //! are NOT routed here — they require target coordinates and use the existing //! pathfinding and combat subsystems; the bridge calls those paths directly. +//! +//! Archetype-specific handlers live in sub-modules: +//! - `infantry` — ShieldWall, Brace, Shove, Rage, Cleave, WarCry (p2-53f) +//! - `ranged` — AimedShot, FireArrows, StopFireArrows (p2-53g) +//! - `cavalry` — Pursue (p2-53h) + +mod infantry; +mod ranged; +mod cavalry; use crate::action::{ActionKind, DisabledReason}; use crate::game_state::GameState; @@ -81,25 +90,32 @@ pub fn invoke( ActionKind::Unsentry => handle_unsentry(state, player_idx, unit_idx), // p2-53g ranged actions // Volley requires a target hex — routed through the bridge's target-pick path. + // Queue-drain (pending_volley_requests + process_volley_requests phase) is + // not yet implemented; deferred pending bridge plumbing. ActionKind::Volley => Err(ActionError { kind, reason: DisabledReason::WrongTerrain }), - ActionKind::AimedShot => handle_aimed_shot(state, player_idx, unit_idx), - ActionKind::FireArrows => handle_fire_arrows(state, player_idx, unit_idx), - ActionKind::StopFireArrows => handle_stop_fire_arrows(state, player_idx, unit_idx), + ActionKind::AimedShot => ranged::handle_aimed_shot(state, player_idx, unit_idx), + ActionKind::FireArrows => ranged::handle_fire_arrows(state, player_idx, unit_idx), + ActionKind::StopFireArrows => ranged::handle_stop_fire_arrows(state, player_idx, unit_idx), // p2-53h cavalry actions - // Charge and Wheel require target coords — routed through bridge. + // Charge requires a target hex — routed through bridge's target-pick path. + // Queue-drain (pending_charge_requests) not yet implemented; deferred pending + // bridge plumbing. + // Wheel requires facing/edge state not yet present on MapUnit; deferred. ActionKind::Charge | ActionKind::Wheel => Err(ActionError { kind, reason: DisabledReason::WrongTerrain }), - ActionKind::Pursue => handle_pursue(state, player_idx, unit_idx), + ActionKind::Pursue => cavalry::handle_pursue(state, player_idx, unit_idx), // p2-53f infantry line actions - ActionKind::ShieldWall => handle_shield_wall(state, player_idx, unit_idx), - ActionKind::UnshieldWall => handle_unshield_wall(state, player_idx, unit_idx), - ActionKind::Brace => handle_brace(state, player_idx, unit_idx), - ActionKind::Unbrace => handle_unbrace(state, player_idx, unit_idx), - ActionKind::Shove | ActionKind::Cleave => Err(ActionError { - kind, - reason: DisabledReason::WrongTerrain, - }), - ActionKind::Rage => handle_rage(state, player_idx, unit_idx), - ActionKind::WarCry => handle_war_cry(state, player_idx, unit_idx), + ActionKind::ShieldWall => infantry::handle_shield_wall(state, player_idx, unit_idx), + ActionKind::UnshieldWall => infantry::handle_unshield_wall(state, player_idx, unit_idx), + ActionKind::Brace => infantry::handle_brace(state, player_idx, unit_idx), + ActionKind::Unbrace => infantry::handle_unbrace(state, player_idx, unit_idx), + ActionKind::Shove => infantry::handle_shove(state, player_idx, unit_idx), + // p2-53f infantry shock actions + ActionKind::Rage => infantry::handle_rage(state, player_idx, unit_idx), + // Cleave: the combat resolver emits `cleave_secondary_damage` (50% of primary); + // the bridge selects the adjacent target and applies it. No pre-action handler + // needed — Cleave fires as part of the attack action, not as a standalone toggle. + ActionKind::Cleave => Err(ActionError { kind, reason: DisabledReason::WrongTerrain }), + ActionKind::WarCry => infantry::handle_war_cry(state, player_idx, unit_idx), // p2-53i engineer actions — multi-turn; require hex targets from bridge. ActionKind::BuildBridge | ActionKind::SapWall @@ -132,7 +148,7 @@ pub fn invoke( } } -fn get_unit_mut<'a>( +pub(crate) fn get_unit_mut<'a>( state: &'a mut GameState, player_idx: usize, unit_idx: usize, @@ -404,177 +420,6 @@ fn handle_disembark( Ok(()) } -// ── p2-53g ranged action handlers ──────────────────────────────────────────── - -fn handle_aimed_shot( - state: &mut GameState, - player_idx: usize, - unit_idx: usize, -) -> Result<(), ActionError> { - let unit = get_unit_mut(state, player_idx, unit_idx, ActionKind::AimedShot)?; - if unit.aimed_shot_pending { - return Err(ActionError { - kind: ActionKind::AimedShot, - reason: DisabledReason::AlreadyAiming, - }); - } - unit.aimed_shot_pending = true; - Ok(()) -} - -fn handle_fire_arrows( - state: &mut GameState, - player_idx: usize, - unit_idx: usize, -) -> Result<(), ActionError> { - let unit = get_unit_mut(state, player_idx, unit_idx, ActionKind::FireArrows)?; - if unit.is_fire_arrows { - return Err(ActionError { - kind: ActionKind::FireArrows, - reason: DisabledReason::AlreadyFireArrows, - }); - } - unit.is_fire_arrows = true; - Ok(()) -} - -fn handle_stop_fire_arrows( - state: &mut GameState, - player_idx: usize, - unit_idx: usize, -) -> Result<(), ActionError> { - let unit = get_unit_mut(state, player_idx, unit_idx, ActionKind::StopFireArrows)?; - if !unit.is_fire_arrows { - return Err(ActionError { - kind: ActionKind::StopFireArrows, - reason: DisabledReason::NotFireArrows, - }); - } - unit.is_fire_arrows = false; - Ok(()) -} - -// ── p2-53h cavalry action handlers ──────────────────────────────────────────── - -fn handle_pursue( - state: &mut GameState, - player_idx: usize, - unit_idx: usize, -) -> Result<(), ActionError> { - let unit = get_unit_mut(state, player_idx, unit_idx, ActionKind::Pursue)?; - if unit.is_pursuing { - return Err(ActionError { - kind: ActionKind::Pursue, - reason: DisabledReason::AlreadyPursuing, - }); - } - unit.is_pursuing = true; - Ok(()) -} - -// ── p2-53f infantry line action handlers ───────────────────────────────────── - -fn handle_shield_wall( - state: &mut GameState, - player_idx: usize, - unit_idx: usize, -) -> Result<(), ActionError> { - let unit = get_unit_mut(state, player_idx, unit_idx, ActionKind::ShieldWall)?; - if unit.is_shield_wall { - return Err(ActionError { - kind: ActionKind::ShieldWall, - reason: DisabledReason::AlreadyShieldWall, - }); - } - unit.is_shield_wall = true; - Ok(()) -} - -fn handle_unshield_wall( - state: &mut GameState, - player_idx: usize, - unit_idx: usize, -) -> Result<(), ActionError> { - let unit = get_unit_mut(state, player_idx, unit_idx, ActionKind::UnshieldWall)?; - if !unit.is_shield_wall { - return Err(ActionError { - kind: ActionKind::UnshieldWall, - reason: DisabledReason::NotShieldWall, - }); - } - unit.is_shield_wall = false; - Ok(()) -} - -fn handle_brace( - state: &mut GameState, - player_idx: usize, - unit_idx: usize, -) -> Result<(), ActionError> { - let unit = get_unit_mut(state, player_idx, unit_idx, ActionKind::Brace)?; - if unit.is_braced { - return Err(ActionError { - kind: ActionKind::Brace, - reason: DisabledReason::AlreadyBraced, - }); - } - unit.is_braced = true; - Ok(()) -} - -fn handle_unbrace( - state: &mut GameState, - player_idx: usize, - unit_idx: usize, -) -> Result<(), ActionError> { - let unit = get_unit_mut(state, player_idx, unit_idx, ActionKind::Unbrace)?; - if !unit.is_braced { - return Err(ActionError { - kind: ActionKind::Unbrace, - reason: DisabledReason::NotBraced, - }); - } - unit.is_braced = false; - Ok(()) -} - -// ── p2-53f infantry shock action handlers ──────────────────────────────────── - -fn handle_rage( - state: &mut GameState, - player_idx: usize, - unit_idx: usize, -) -> Result<(), ActionError> { - let unit = get_unit_mut(state, player_idx, unit_idx, ActionKind::Rage)?; - if unit.rage_turns_remaining > 0 { - return Err(ActionError { - kind: ActionKind::Rage, - reason: DisabledReason::AlreadyRaging, - }); - } - // +40% attack for 2 turns; Fortify and Sentry are incompatible (cleared here). - unit.rage_turns_remaining = 2; - unit.is_fortified = false; - unit.is_sentrying = false; - Ok(()) -} - -fn handle_war_cry( - state: &mut GameState, - player_idx: usize, - unit_idx: usize, -) -> Result<(), ActionError> { - let unit = get_unit_mut(state, player_idx, unit_idx, ActionKind::WarCry)?; - if unit.war_cry_used_this_battle { - return Err(ActionError { - kind: ActionKind::WarCry, - reason: DisabledReason::WarCryUsed, - }); - } - unit.war_cry_used_this_battle = true; - Ok(()) -} - // ── p2-53i: Scout action handlers ─────────────────────────────────────────── fn handle_stealth( @@ -887,7 +732,6 @@ mod tests { strategic_axes: Default::default(), scoring_weights: Default::default(), expansion_points: 0, - city_buildings: vec![], city_improvements: Default::default(), city_ecology: vec![], tech_state: None,