diff --git a/.project/objectives/p1-29a-last-stand-defense.md b/.project/objectives/p1-29a-last-stand-defense.md index 4890ae6d..1b14ff3d 100644 --- a/.project/objectives/p1-29a-last-stand-defense.md +++ b/.project/objectives/p1-29a-last-stand-defense.md @@ -2,11 +2,11 @@ id: p1-29a title: "Last-stand defense — combat-strength multiplier when defender is at last city" priority: p1 -status: stub +status: partial scope: game1 tags: [balance, combat, pacing] owner: combat-dev -updated_at: 2026-05-03 +updated_at: 2026-05-04 parent: p1-29 filed_by: cycle-5-closeout --- @@ -18,13 +18,16 @@ This objective addresses the territory problem by giving the defender (when redu ## Acceptance criteria -- [ ] **Combat-strength multiplier on last-stand defense**: in `mc-combat` (Rust), when an attacking unit targets a city owned by a player whose `cities.len() == 1` AND that player has been alive ≥ N turns (gate against single-city civ at game start), apply a defender combat-strength multiplier `1.0 + 0.5 × cities_lost` where `cities_lost = cities_lost_total` (engine-tracked). Cap at 3.0× (i.e. ≥4 lost cities cap out the multiplier). Spec: see `mc-combat::resolver` for where to apply. +- [x] ✓ **Combat-strength multiplier on last-stand defense**: in `mc-combat` (Rust), when an attacking unit targets a city owned by a player whose `cities.len() == 1` AND that player has been alive ≥ N turns (gate against single-city civ at game start), apply a defender combat-strength multiplier `1.0 + 0.5 × cities_lost` where `cities_lost = cities_lost_total` (engine-tracked). Cap at 3.0× (i.e. ≥4 lost cities cap out the multiplier). Spec: see `mc-combat::resolver` for where to apply. + - Multiplier applied in `src/simulator/crates/mc-combat/src/resolver.rs:588-592` via `last_stand_defense_multiplier`. Rust callers wired in `src/simulator/crates/mc-turn/src/processor.rs:1711-1727` (`resolve_single_pvp_attack`) and `src/simulator/crates/mc-turn/src/processor.rs:2151-2167` (`process_pvp_combat`). Live engine bridge wires from GDScript via `src/simulator/api-gdext/src/lib.rs:3784-3786` and `src/game/engine/src/modules/combat/combat_resolver.gd:382-389`. -- [ ] **Wall HP scaling on last-stand defense**: when defender is at their last city, the city's effective wall HP scales `1.0 + 0.5 × cities_lost` (same formula, same cap). City wall HP is in `mc-turn::City::walls` — apply the multiplier in `mc-combat::city_attack_resolver` rather than mutating the city state itself. +- [x] ✓ **Wall HP scaling on last-stand defense**: when defender is at their last city, the city's effective wall HP scales `1.0 + 0.5 × cities_lost` (same formula, same cap). City wall HP is in `mc-turn::City::walls` — apply the multiplier in `mc-combat::city_attack_resolver` rather than mutating the city state itself. + - New `effective_city_hp_with_last_stand(wall_tier, at_last_city, cities_lost) -> i32` at `src/simulator/crates/mc-combat/src/siege.rs:78-94`. Reuses `last_stand_defense_multiplier` (no duplicate formula). Re-exported from `src/simulator/crates/mc-combat/src/lib.rs:28-32`. Source of truth for raw wall HP stays `city_total_hp`; the situational multiplier is layered at the resolver helper, not by mutating `City::walls`. -- [ ] **Mc-combat unit tests** verify: (a) multiplier is 1.0× when defender owns ≥2 cities (no last-stand condition); (b) multiplier scales correctly at 0/1/2/3/4+ cities lost; (c) multiplier composes correctly with existing terrain / fortification / promotion bonuses (no double-counting); (d) cap at 3.0× holds. +- [x] ✓ **Mc-combat unit tests** verify: (a) multiplier is 1.0× when defender owns ≥2 cities (no last-stand condition); (b) multiplier scales correctly at 0/1/2/3/4+ cities lost; (c) multiplier composes correctly with existing terrain / fortification / promotion bonuses (no double-counting); (d) cap at 3.0× holds. + - Inline tests in `src/simulator/crates/mc-combat/src/resolver.rs:1774-1872` already covered the gate + sub-conditions. New integration tests at `src/simulator/crates/mc-combat/tests/last_stand.rs` add `test_last_stand_strength_multiplier`, `test_wall_hp_scales_for_last_city`, `test_no_multiplier_when_multiple_cities`. All 3 green; full mc-combat suite 150/150. -- [ ] **Mc-ai integration test**: a `tactical/combat_predict.rs` test verifies the AI's combat-prediction layer accounts for the last-stand multiplier (so attackers correctly avoid attempting hopeless attacks rather than throwing units into the meat-grinder). +- [ ] ❌ **Mc-ai integration test**: a `tactical/combat_predict.rs` test verifies the AI's combat-prediction layer accounts for the last-stand multiplier (so attackers correctly avoid attempting hopeless attacks rather than throwing units into the meat-grinder). - [ ] **`tier_peak_gap` ≤4 (alive-aware) median in 10-seed batch**: re-run cycle-4's autoplay batch with last-stand defense landed (`AUTOPLAY_HOST=apricot SEEDS=10 TURN_LIMIT=300 bash tools/autoplay-batch.sh`). The cycle-4 baseline showed every game `p1_tier_peak=1` (so gap=p0_tp - p1_tp = 1-5+ ineligible for alive-aware filter). Pass criterion: ≥7/10 games show `p0_tp >= 2 AND p1_tp >= 2` (alive-aware filter eligible) AND median `tier_peak_gap` ≤4 across those 7+ games.