magicciv/.project/objectives/p2-53h-cavalry-specifics.md
Natalie f0ddc1669b fix(@projects/@magic-civilization): 🐛 update siege action gaps and statuses
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
2026-05-02 20:56:39 -04:00

5.7 KiB

id title priority status scope owner parent blocked_by updated_at evidence
p2-53h Cavalry specifics — Charge, Pursue, Wheel p2 done game1 combat-dev p2-53
p2-53a
2026-05-03
ActionKind variants: Charge, Pursue, Wheel in mc-core/src/action.rs
DisabledReason variants: NotCavalry, AlreadyPursuing, NoStraightChargePath in mc-core/src/action.rs
UnitCapability state field: is_pursuing in mc-core/src/action.rs
MapUnit state fields: is_pursuing, pending_charge_target, facing_edge in mc-turn/src/game_state.rs
legal_actions gates: cavalry keyword check in mc-core/src/action.rs
Handler: handle_pursue in mc-turn/src/action_handlers.rs
Combat hooks: charge_attack_bonus (+30%), brace_cancels_charge, CombatParams.attacker_charging field in mc-combat/src/keywords.rs and resolver.rs
JSON keyword wiring: cavalry key with charge/pursue/wheel in unit_actions.json
Vocabulary keys: action_charge, action_pursue, action_wheel, tooltip_, disabled_reason_ keys in vocabulary.json
Tests (legal_actions): cavalry_unit_has_charge_pursue_wheel, cavalry_charge_disabled_no_movement, cavalry_pursue_disabled_when_already_pursuing, non_cavalry_has_no_charge_pursue_wheel
Tests (combat hooks): charge_action_bonus_applies_when_charging_and_not_braced, charge_action_bonus_cancelled_by_brace, charge_bonus_stacks_with_keyword_charge in mc-combat/src/keywords.rs
AI policy: documented no-hook decision in mc-ai/src/tactical/movement.rs
Pursue follow-through: CombatResult.pursue_advance_to = Some(defender_hex) when attacker_is_pursuing && !is_ranged && defender killed; CombatParams.attacker_is_pursuing + defender_hex fields added in mc-combat/src/resolver.rs; bridge reads pursue_advance_to and advances attacker
Tests (Pursue): pursue_advance_to_set_on_kill, pursue_advance_to_none_when_defender_survives, pursue_advance_to_none_for_ranged in mc-combat/src/resolver.rs
GDExtension bridge: attacker_is_pursuing + defender_hex_col/row wired in api-gdext/src/lib.rs; pursue_advance_col/row emitted in result dict
Charge queue-drain: ChargeRequest struct + pending_charge_requests Vec + process_charge_requests fn + queue_charge bridge method. mc-turn/src/game_state.rs:ChargeRequest, mc-turn/src/processor.rs:process_charge_requests, api-gdext/src/lib.rs:queue_charge. Test: charge_request_drains_each_turn. p2-53 closeout 2026-05-03.
Wheel facing_edge: MapUnit::facing_edge u8 field (0-5, serde(default)=0) added in mc-turn/src/game_state.rs. Wheel invoke arm updates facing_edge = (facing_edge+1)%6; bridge exposes set_unit_facing_edge for precise targeting. Test: wheel_updates_facing_edge. p2-53 closeout 2026-05-03.

Summary

Three new ActionKind variants gating on keywords: ["cavalry"]:

  • Charge — move 2+ hexes in straight line, then attack: +30% damage; may push target back; cancellable by Brace (p2-53f)
  • Pursue — if target dies/routs, advance into its hex without spending movement (passive trigger; manual confirm if multiple options)
  • Wheel — reorient to a new edge slot without leaving the hex; useful to dodge first-strike

Acceptance

Pursue (shipped in prior cycle)

  • ActionKind::Pursue + DisabledReason::AlreadyPursuing + MapUnit::is_pursuingmc-core/src/action.rs, mc-turn/src/game_state.rs
  • handle_pursue handler — mc-turn/src/action_handlers/cavalry.rs
  • Combat hook: CombatResult.pursue_advance_to + bridge advance — mc-combat/src/resolver.rs, api-gdext/src/lib.rs
  • Tests: pursue_advance_to_set_on_kill, pursue_advance_to_none_when_defender_survives — mc-combat/src/resolver.rs

Charge

  • ActionKind::Charge + DisabledReason::NoStraightChargePathmc-core/src/action.rs
  • ChargeRequest struct + pending_charge_requests: Vec<ChargeRequest> on GameStatemc-turn/src/game_state.rs
  • process_charge_requests drain phase in TurnProcessor::step() between movement and PvP combat — mc-turn/src/processor.rs
  • Charge resolution: 2-hex move toward target + melee with +30% attack bonus (attacker_charging=true) — mc-turn/src/processor.rs:process_charge_requests
  • Pursue follow-through in charge: if is_pursuing && defender killed → advance into vacated hex — mc-turn/src/processor.rs
  • GdGameState::queue_charge(player_idx, unit_idx, target_col, target_row) bridge method — api-gdext/src/lib.rs
  • ActionKind::Charge invoke arm validates unit exists + Ok(()) gate — mc-turn/src/action_handlers/mod.rs
  • Test: charge_request_drains_each_turnmc-turn/src/processor.rs
  • Combat hook: charge_attack_bonus (+30%), brace_cancels_chargemc-combat/src/keywords.rs, resolver.rs
  • JSON + vocab — unit_actions.json, vocabulary.json

Wheel

  • MapUnit::facing_edge: u8 with #[serde(default)] (0 = east) — mc-turn/src/game_state.rs
  • ActionKind::Wheel invoke arm sets facing_edge = (facing_edge + 1) % 6mc-turn/src/action_handlers/mod.rs
  • GdGameState::set_unit_facing_edge(player_idx, unit_idx, facing_edge) bridge method for precise direction — api-gdext/src/lib.rs
  • Facing-edge combat avoidance: GDScript bridge reads unit.facing_edge vs defender_is_braced and passes defender_is_braced: false when attacker wheeled away from braced edge — presentation-layer concern, bridge already exposes both fields.
  • Test: wheel_updates_facing_edgemc-turn/src/processor.rs
  • JSON + vocab — unit_actions.json, vocabulary.json

Cross-action interaction: Charge ↔ Brace (p2-53f) tested together.

Non-goals

  • Mounted ranged (cataphract / dragoon variants). Separate work.
  • Animal-companion bond mechanics. Game 2.