feat(game1): mark last-stand defense objectives as implemented

Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
This commit is contained in:
Natalie 2026-05-04 03:34:57 -04:00
parent bfbecff550
commit de0c7ec5f8

View file

@ -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.