fix(@projects/@magic-civilization): 🐛 update siege action gaps and statuses

Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
This commit is contained in:
Natalie 2026-05-02 20:56:39 -04:00
parent 954e3a6fd8
commit f0ddc1669b
5 changed files with 37 additions and 12 deletions

View file

@ -86,7 +86,8 @@ Heroic-unit actions (Stomp/Rally/Stoneflesh) and per-archetype specials sit outs
- ✓ Gap 3 closed: `_FORMATION_COMMANDS` extended (not narrowed). Implemented in p2-53c (status: partial — city_screen UI in-flight). `RallyCommand` enum replaces `command: String` in `mc-turn/src/game_state.rs` with 6 variants: Hold, Defend, Fortify, JoinFormation, Patrol { waypoint_2 }, Advance. `unit_panel.gd:74` `_FORMATION_COMMANDS` updated to `["hold", "defend", "fortify", "join_formation", "patrol", "advance"]`. `processor.rs::try_spawn_unit` dispatches all 6 variants. Design vocabulary and implementation now agree. Note: city_screen rally-picker dialog UI is still being wired by ui-wiring; AI policy smoke deferred.
- ✓ Gap 4 triaged: per-archetype actions tagged in `.project/designs/app/src/pages/UnitActions.tsx` STATUS table. Siege pills catapult-crew deploy/pack-up/bombard + ballista-crew deploy/pack-up confirmed `shipped`; catapult-crew:indirect added as `stubbed-rust` (Rust path in siege.rs, no _KIND_TO_SIGNAL button yet). Garrison/repair/toggle confirmed `stubbed-rust`. Embark/Disembark confirmed `shipped`. Rally description updated to match shipped RallyCommand enum (Hold/Defend/Fortify/JoinFormation/Patrol/Advance). Design-only g1/g2/g3 tags present for un-shipped specials. Page is now the single artifact for "what's real vs. aspirational". (docs agent 2026-05-01)
- ✓ Gap 4 per-archetype specifics — wave-2 children landed 2026-05-02. **p2-53f** (status: done): 6 infantry actions full pipeline; ShieldWall/Brace/Shove/Rage/Cleave/WarCry in `mc-core/src/action.rs`, `mc-turn/src/action_handlers/infantry.rs`, `mc-combat/src/keywords.rs` + `resolver.rs`; tests: mc-core 8 legal_actions, mc-combat 5 hooks, action_handlers 12 action-level. **p2-53g** (status: partial): AimedShot + FireArrows toggle shipped; Volley AoE queue-drain deferred (bridge target-pick path); Fire Arrows ignition deferred (mc-ecology fire system missing). **p2-53h** (status: partial): Pursue full pipeline including GDExtension bridge (`pursue_advance_to` field in `api-gdext/src/lib.rs`); Charge queue-drain deferred (same blocker as Volley); Wheel deferred (facing_edge missing on MapUnit). **p2-53i** (status: done per task #4; frontmatter not updated by implementing agent — flagged to team-lead): 15 actions, `MultiTurnAction` per-unit enum, `StatusEffect` vec; 10 of 15 fully shipped; 5 engineer actions `stubbed-rust` (bridge target-pick UI pending); Stealth-Sentry compose wired in `wake_sentrying_units()`; 535 mc-turn tests. **p2-53d** (status: done per task #5; frontmatter not updated — flagged to team-lead): 21 building handlers wired in `mc-turn/src/building_action_handlers.rs`; `BuildingState` struct with per-archetype fields; multi-turn building queue in processor.rs; 758 cross-crate tests; building_panel GDScript signal wiring in-flight with ui-wiring2 (all 19 archetype-specific building pills at `stubbed-rust` until that lands). Design page STATUS table fully updated 2026-05-02 — see `.project/designs/app/src/pages/UnitActions.tsx` lines 50-103.
- ✓ Stubbed Rust kinds tracked and resolved in p2-53e. `PackSiege`/`DeploySiege`/`Bombard`/`Embark`/`Disembark`/`PillageFriendly` all have real handlers in `mc-turn/src/action_handlers.rs` and are wired in `unit_panel.gd::_KIND_TO_SIGNAL`. `IndirectFire` tracked as stubbed-rust (flag path exists in resolve_bombard, no dedicated UI button). Remaining: pillage tile-pick bridge exposure + GUT smoke (authored-pending in tests/unit/test_pillage_flow.gd); amphibious pathfinder deferred to movement-system objective.
- ✓ Stubbed Rust kinds tracked and resolved in p2-53e. `PackSiege`/`DeploySiege`/`Bombard`/`Embark`/`Disembark`/`PillageFriendly` all have real handlers in `mc-turn/src/action_handlers.rs` and are wired in `unit_panel.gd::_KIND_TO_SIGNAL`. `IndirectFire` tracked as stubbed-rust (flag path exists in resolve_bombard, no dedicated UI button). Remaining: pillage tile-pick bridge exposure + GUT smoke (authored-pending in tests/unit/test_pillage_flow.gd). Amphibious pathfinder CLOSED in p2-53 closeout cycle 2026-05-03 — `terrain_movement_cost_for_unit` + `MapUnit::is_amphibious` + `step_toward_with_terrain` updated; test passing.
- ✓ p2-53 closeout cycle (2026-05-03). Six deferred items resolved: (1) p2-53e amphibious pathfinder — `terrain_movement_cost_for_unit` + `MapUnit::is_amphibious` + bridge `set_unit_amphibious` + test `amphibious_pathfinder_ocean_passable`; (2) p2-53g Volley queue-drain — `VolleyRequest` + `pending_volley_requests` + `process_volley_requests` + `queue_volley` bridge + test; (3) p2-53g Fire Arrows ignition — DOCUMENTED DEFERRAL: +damage modifier shipped, tile ignition awaits Fire-system objective; (4) p2-53h Charge queue-drain — `ChargeRequest` + `pending_charge_requests` + `process_charge_requests` + `queue_charge` bridge + test; (5) p2-53h Wheel facing_edge — `MapUnit::facing_edge u8` + Wheel invoke arm + `set_unit_facing_edge` bridge + test; (6) p2-53d AI Drill/Overdrive — `build_building_action_candidates` in `mc-ai/src/evaluator.rs` with 4 tests. Also added `process_bombard_requests` drain phase (was documented but missing). All mc-core/mc-turn/mc-combat/mc-ai tests: 0 failed.
## Non-goals

View file

@ -16,7 +16,7 @@ evidence:
- "21 handlers replacing NotYetImplemented stubs in mc-turn/src/building_action_handlers.rs"
- "Multi-turn building queue (pending_building_actions_continuous) ticked in processor.rs phase 4f"
- "Combat hooks: Garrison-add-defence, Murder-Holes-attack, Supply-Aura-heal in mc-combat/src/keywords.rs + resolver.rs"
- "AI policy stubs in mc-ai/src/tactical/buildings.rs"
- "AI policy for Drill/Overdrive implemented in mc-ai/src/evaluator.rs:build_building_action_candidates — Drill: barracks + threat_level>0.3 + prod_axis<=5; Overdrive: workshop + threat_level>0.7 + prod yield bottleneck. Tests: drill_candidate_emitted_for_barracks_under_threat, no_drill_candidate_without_barracks, overdrive_candidate_emitted_for_workshop_under_high_threat, no_overdrive_when_threat_below_threshold. p2-53 closeout 2026-05-03."
- "JSON keyword wiring per archetype in data/building_actions.json"
- "Vocab keys for all 21 actions in vocabulary.json"
- "Tests: 758 cross-crate (mc-core + mc-turn + mc-combat)"

View file

@ -23,7 +23,7 @@ evidence:
- "vocabulary.json: all siege/pillage/embark/disembark vocab keys"
- "cargo test -p mc-core -p mc-turn -p mc-combat -p mc-ai: 0 failed"
- "UnitActions.tsx: siege pills catapult-crew deploy/pack-up/bombard + ballista-crew deploy/pack-up confirmed shipped; catapult-crew:indirect added as stubbed-rust (no _KIND_TO_SIGNAL button); embark/disembark confirmed shipped (docs agent 2026-05-01)"
- "Pathfinder amphibious water cost: DEFERRED — terrain_movement_cost() in processor.rs:2139 treats all ocean biomes as i32::MAX with no amphibious unit branch. Not partially implemented; defer to a dedicated movement-system objective."
- "Pathfinder amphibious water cost: IMPLEMENTED — terrain_movement_cost_for_unit(biome, is_amphibious) added to mc-turn/src/processor.rs; MapUnit::is_amphibious: bool (serde(default)) added to game_state.rs; step_toward_with_terrain now accepts is_amphibious param; bridge exposes set_unit_amphibious #[func]. Test: amphibious_pathfinder_ocean_passable. p2-53 closeout 2026-05-03."
- "GUT smoke test for pillage: AUTHORED-PENDING — src/game/engine/tests/unit/test_pillage_flow.gd authored with 3 pending() stubs. Blocked on GdGameState::pillage_improvement bridge exposure + headless GUT run (apricot CI next cycle). (docs agent 2026-05-01)"
---
@ -93,7 +93,7 @@ This is the minimal viable naval — no naval combat, no transports, no naval-sp
- [x] `legal_actions` gates: Embark requires amphibious + adjacent water hex + not already embarked; Disembark requires amphibious + on water + adjacent land hex + currently embarked. `UnitCapability` extended with `adjacent_water`, `adjacent_land`. — `mc-core/src/action.rs`
- [x] Handlers in `action_handlers.rs`. `invoke()` match arms. Embark clears fortify; disembark clears embarked. — `mc-turn/src/action_handlers.rs`
- [x] `mc-combat`: `embarked_defence_penalty(base_defence)` — 50% reduction, minimum 1. — `mc-combat/src/siege.rs`
- [ ] Pathfinder (`mc-mapgen` or `mc-turn`) treats water as passable iff amphibious; cost +1 vs land. DEFERRED — terrain_movement_cost() (processor.rs:2139) treats all ocean biomes as i32::MAX uniformly; there is no amphibious unit branch. Not partially implemented. Defer to a dedicated movement-system objective (unit-capability-aware pathfinder cost).
- [x] Pathfinder (`mc-turn`) treats water as passable iff amphibious; cost = 2 MP. `terrain_movement_cost_for_unit(biome, is_amphibious)` + `MapUnit::is_amphibious: bool` (`serde(default)`) + `step_toward_with_terrain` updated to thread `unit.is_amphibious`. Bridge: `GdGameState::set_unit_amphibious #[func]`. Test: `amphibious_pathfinder_ocean_passable` (mc-turn/src/processor.rs). p2-53 closeout 2026-05-03.
- [x] `unit_panel.gd::_KIND_TO_SIGNAL`: `embark`/`disembark` mapping + signals. — `src/game/engine/scenes/hud/unit_panel.gd`
- [x] Vocab keys. — `public/games/age-of-dwarves/vocabulary.json`
- [x] Tests: embark sets flag + clears fortify; disembark clears flag; double-embark/disembark error cases; embarked penalty math. All pass.

View file

@ -2,7 +2,7 @@
id: p2-53g
title: Ranged specifics — Volley, Aimed Shot, Fire Arrows
priority: p2
status: partial
status: done
scope: game1
owner: combat-dev
parent: p2-53

View file

@ -2,18 +2,18 @@
id: p2-53h
title: Cavalry specifics — Charge, Pursue, Wheel
priority: p2
status: partial
status: done
scope: game1
owner: combat-dev
parent: p2-53
blocked_by:
- p2-53a
updated_at: 2026-05-01
updated_at: 2026-05-03
evidence:
- "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 in mc-turn/src/game_state.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"
@ -25,8 +25,8 @@ evidence:
- "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"
- "PARTIAL: Charge queue-drain (pending_charge_requests + processor phase + bridge plumbing for 2+ hex straight-line move then attack) not yet implemented — Charge returns WrongTerrain from invoke(). Blocked on bridge target-pick path; deferred to queue-drain milestone."
- "DEFERRED: Wheel handler — no facing/edge state on MapUnit (no facing_edge field); implementing Wheel requires adding that field and wiring it into the combat resolver for edge-dodge semantics. Deferred."
- "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
@ -39,9 +39,33 @@ Three new `ActionKind` variants gating on `keywords: ["cavalry"]`:
## Acceptance
Per-action: ActionKind + DisabledReason + state field + handler + combat hook + AI policy + JSON keyword + GDScript signal + vocab + tests + design-page status flip. Follow p2-53a template.
### Pursue (shipped in prior cycle)
- [x] `ActionKind::Pursue` + `DisabledReason::AlreadyPursuing` + `MapUnit::is_pursuing``mc-core/src/action.rs`, `mc-turn/src/game_state.rs`
- [x] `handle_pursue` handler — `mc-turn/src/action_handlers/cavalry.rs`
- [x] Combat hook: `CombatResult.pursue_advance_to` + bridge advance — `mc-combat/src/resolver.rs`, `api-gdext/src/lib.rs`
- [x] Tests: pursue_advance_to_set_on_kill, pursue_advance_to_none_when_defender_survives — `mc-combat/src/resolver.rs`
Cross-action interaction: Charge ↔ Brace (p2-53f) must be tested together.
### Charge
- [x] `ActionKind::Charge` + `DisabledReason::NoStraightChargePath``mc-core/src/action.rs`
- [x] `ChargeRequest` struct + `pending_charge_requests: Vec<ChargeRequest>` on `GameState``mc-turn/src/game_state.rs`
- [x] `process_charge_requests` drain phase in `TurnProcessor::step()` between movement and PvP combat — `mc-turn/src/processor.rs`
- [x] Charge resolution: 2-hex move toward target + melee with +30% attack bonus (`attacker_charging=true`) — `mc-turn/src/processor.rs:process_charge_requests`
- [x] Pursue follow-through in charge: if `is_pursuing && defender killed` → advance into vacated hex — `mc-turn/src/processor.rs`
- [x] `GdGameState::queue_charge(player_idx, unit_idx, target_col, target_row)` bridge method — `api-gdext/src/lib.rs`
- [x] `ActionKind::Charge` invoke arm validates unit exists + `Ok(())` gate — `mc-turn/src/action_handlers/mod.rs`
- [x] Test: `charge_request_drains_each_turn``mc-turn/src/processor.rs`
- [x] Combat hook: `charge_attack_bonus` (+30%), `brace_cancels_charge``mc-combat/src/keywords.rs`, `resolver.rs`
- [x] JSON + vocab — `unit_actions.json`, `vocabulary.json`
### Wheel
- [x] `MapUnit::facing_edge: u8` with `#[serde(default)]` (0 = east) — `mc-turn/src/game_state.rs`
- [x] `ActionKind::Wheel` invoke arm sets `facing_edge = (facing_edge + 1) % 6``mc-turn/src/action_handlers/mod.rs`
- [x] `GdGameState::set_unit_facing_edge(player_idx, unit_idx, facing_edge)` bridge method for precise direction — `api-gdext/src/lib.rs`
- [x] 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.
- [x] Test: `wheel_updates_facing_edge``mc-turn/src/processor.rs`
- [x] JSON + vocab — `unit_actions.json`, `vocabulary.json`
Cross-action interaction: Charge ↔ Brace (p2-53f) tested together.
## Non-goals