magicciv/.project/objectives/p2-53f-infantry-specifics.md
Natalie ccc19b0461 fix(@projects/@magic-civilization): 🐛 mark infantry objectives as complete
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
2026-05-02 19:55:30 -04:00

5.2 KiB
Raw Blame History

id title priority status scope owner parent blocked_by updated_at evidence
p2-53f Infantry specifics — Shield Wall, Brace, Shove, Rage, Cleave, War Cry p2 done game1 combat-dev p2-53
p2-53a
2026-05-01
ActionKind variants: ShieldWall, UnshieldWall, Brace, Unbrace, Shove, Rage, Cleave, WarCry in mc-core/src/action.rs
DisabledReason variants: NotInfantryLine, NotInfantryShock, AlreadyShieldWall, NotShieldWall, AlreadyBraced, NotBraced, AlreadyRaging, WarCryUsed, NoAdjacentTarget, ShoveBlocked in mc-core/src/action.rs
UnitCapability state fields: is_shield_wall, is_braced, rage_turns_remaining, war_cry_used_this_battle in mc-core/src/action.rs
MapUnit state fields: is_shield_wall, is_braced, rage_turns_remaining, war_cry_used_this_battle in mc-turn/src/game_state.rs
legal_actions gates: infantry_line + infantry_shock keyword checks with toggle logic; Rage gates Fortify and Sentry via is_raging check in mc-core/src/action.rs
Cross-turn phase: Rage countdown (rage_turns_remaining -= 1) in processor.rs prologue
Handlers: handle_shield_wall, handle_unshield_wall, handle_brace, handle_unbrace, handle_rage (clears fortify/sentry on entry), handle_war_cry in mc-turn/src/action_handlers.rs
Combat hooks: shield_wall_ranged_defence_bonus (+50%), rage_attack_bonus (+40%), war_cry_attack_debuff (-10%), brace_cancels_charge, CombatParams fields in mc-combat/src/keywords.rs and resolver.rs
JSON keyword wiring: infantry_line and infantry_shock keys in unit_actions.json
Vocabulary keys: all action_/tooltip_action_/disabled_reason_ keys for all 6 actions in vocabulary.json
Tests (legal_actions): 8 infantry tests in mc-core/src/action.rs; rage_gates_fortify, rage_gates_sentry
Tests (combat hooks): rage_bonus_applies_when_raging, rage_bonus_absent_when_not_raging, war_cry_debuff_reduces_attack, shield_wall_defence_bonus_vs_ranged, shield_wall_no_bonus_vs_melee in mc-combat/src/keywords.rs
AI policy: documented no-hook decision in mc-ai/src/tactical/movement.rs
Shove displacement: handle_shove in mc-turn/src/action_handlers/infantry.rs iterates ODD_Q_NEIGHBORS, finds first adjacent enemy, computes push destination (one more step in same direction), checks passability + occupancy, moves enemy and clears formation_id
Tests (Shove): shove_pushes_enemy_to_empty_hex, shove_breaks_formation, shove_no_adjacent_enemy_returns_no_adjacent_target, shove_blocked_when_destination_occupied in action_handlers/infantry.rs
Cleave AoE: resolver emits cleave_secondary_damage = 50% of primary in CombatResult when attacker_has_cleave=true; bridge selects adjacent target. CombatParams.attacker_has_cleave + CombatResult.cleave_secondary_damage in mc-combat/src/resolver.rs
Tests (Cleave): cleave_hits_one_adjacent_enemy_at_50_pct, cleave_secondary_zero_without_cleave in mc-combat/src/resolver.rs
WarCry adjacency-scan: handle_war_cry in action_handlers/infantry.rs sets war_cry_debuff_turns_remaining=1 on all adjacent enemy units; MapUnit.war_cry_debuff_turns_remaining ticked in processor.rs; bridge sets CombatParams.attacker_war_cry_debuff = unit.war_cry_debuff_turns_remaining > 0
Tests (WarCry): war_cry_debuff_applied_to_adjacent_enemies, war_cry_does_not_debuff_non_adjacent_enemies, war_cry_twice_in_battle_errors in action_handlers/infantry.rs
Brace first-strike: in mc-combat/src/resolver.rs resolve(), brace_first_strike_active triggers when defender_is_braced && !is_ranged && !has_first_strike; if attacker killed by retaliation, final_damage_to_defender=0
Tests (Brace first-strike): brace_first_strikes_attacker, brace_first_strike_no_effect_if_attacker_survives, brace_first_strike_does_not_apply_to_ranged in mc-combat/src/resolver.rs
action_handlers.rs factored into action_handlers/ submodule (mod.rs + infantry.rs + ranged.rs + cavalry.rs) per 500-LOC rule

Summary

Six new ActionKind variants gating on archetype keyword (infantry_line for Defender, infantry_shock for Berserker). Each is a per-action handler + combat hook.

Actions

  • Shield Wall (infantry_line keyword): toggle posture; +50% defence vs ranged for the formation, 1 movement next turn
  • Brace (infantry_line keyword): set against charge; cancels cavalry charge bonus, deals first-strike on incoming melee
  • Shove (infantry_line): push adjacent enemy back one hex if destination empty; breaks their formation
  • Rage (infantry_shock keyword): +40% attack for 2 turns; cannot guard or fortify while raging
  • Cleave (infantry_shock): single attack hits target plus one adjacent enemy at 50% damage
  • War Cry (infantry_shock): adjacent enemies suffer 10% attack for 1 turn; free action once per battle

Acceptance summary

Each action: ActionKind variant + DisabledReason + state field if persistent (is_shield_wall, is_braced, rage_turns_remaining, war_cry_used_this_battle) + handler + combat hook in mc-combat + AI policy + JSON keyword wiring + GDScript signal + vocab + tests + design-page status flip.

Acceptance per action — copy from p2-53a structure.

Non-goals

  • Per-personality preferences for posture choice.
  • Berserker tier-2 promotions stacking with Rage (separate promotion-tree work).