fix(@projects/@magic-civilization): 🛡️ p3-26 B2 — siege-suppress city healing (besieged cities don't heal)
Regression fix: the B2 healing phase ran end-of-turn BEFORE siege and healed cities unconditionally — so a besieged city healed (e.g. 30→50 hp) before the same turn's siege resolved, defeating captures (last_survivor_via_capture got empty events: a 30-hp city + 3 attackers @15 = 45 dmg no longer captured after healing to 50). Bisected to this session (passed at e926345ad; the healing phase introduced the bug). Fix: process_healing_phase now snapshots all units' tiles and SKIPS healing any city with an enemy unit on its tile (under siege) — real-time siege-suppress matching the live game's intent (its `last_attacked_turn` window). Un-besieged cities still heal. Tests: besieged_city_does_not_heal (new) + last_survivor_via_capture (restored) + healing 11/0. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
91095be232
commit
7f4b69eac1
1 changed files with 59 additions and 3 deletions
|
|
@ -65,7 +65,19 @@ const CITY_HEAL_PER_TURN: i32 = 20;
|
|||
/// This function does **not** call `step()` or any other phase — the parent
|
||||
/// wires the call at the correct slot in the turn sequence.
|
||||
pub fn process_healing_phase(state: &mut GameState) {
|
||||
for player in &mut state.players {
|
||||
// Snapshot every unit's (owner, tile) up front so city healing can skip
|
||||
// BESIEGED cities — a city with an enemy unit on its tile is under siege and
|
||||
// must NOT heal (else end-of-turn healing would undo the same turn's siege
|
||||
// damage, e.g. heal a 30-hp city to 50 before a 45-dmg siege resolves).
|
||||
// Mirrors the live game's siege-suppress intent via real-time occupancy.
|
||||
let unit_tiles: Vec<(usize, i32, i32)> = state
|
||||
.players
|
||||
.iter()
|
||||
.enumerate()
|
||||
.flat_map(|(pi, p)| p.units.iter().map(move |u| (pi, u.col, u.row)))
|
||||
.collect();
|
||||
|
||||
for (pi, player) in state.players.iter_mut().enumerate() {
|
||||
// Snapshot city positions for garrison detection; `player` is borrowed
|
||||
// mutably below for units, so we can't hold a reference to
|
||||
// `player.city_positions` at the same time.
|
||||
|
|
@ -102,9 +114,17 @@ pub fn process_healing_phase(state: &mut GameState) {
|
|||
unit.hp = (unit.hp + heal_amount).min(unit.max_hp);
|
||||
}
|
||||
|
||||
// ── City healing ──────────────────────────────────────────────────
|
||||
for city in &mut player.cities {
|
||||
// ── City healing (siege-suppressed) ───────────────────────────────
|
||||
for (ci, city) in player.cities.iter_mut().enumerate() {
|
||||
if city.hp > 0 && city.hp < city.max_hp {
|
||||
let besieged = player.city_positions.get(ci).copied().is_some_and(|(cc, cr)| {
|
||||
unit_tiles
|
||||
.iter()
|
||||
.any(|&(opi, uc, ur)| opi != pi && uc == cc && ur == cr)
|
||||
});
|
||||
if besieged {
|
||||
continue; // under siege → no heal
|
||||
}
|
||||
city.hp = (city.hp + CITY_HEAL_PER_TURN).min(city.max_hp);
|
||||
}
|
||||
}
|
||||
|
|
@ -290,6 +310,42 @@ mod tests {
|
|||
);
|
||||
}
|
||||
|
||||
/// A city with an enemy unit on its tile is under siege and must NOT heal
|
||||
/// (so end-of-turn healing can't undo the same turn's siege damage).
|
||||
#[test]
|
||||
fn besieged_city_does_not_heal() {
|
||||
let mut p0 = PlayerState {
|
||||
city_positions: vec![(3, 3)],
|
||||
..PlayerState::default()
|
||||
};
|
||||
let mut city = CityState::default();
|
||||
city.hp = 80;
|
||||
city.max_hp = 200;
|
||||
p0.cities.push(city);
|
||||
|
||||
let mut p1 = PlayerState {
|
||||
player_index: 1,
|
||||
..PlayerState::default()
|
||||
};
|
||||
p1.units.push(MapUnit {
|
||||
col: 3,
|
||||
row: 3,
|
||||
hp: 60,
|
||||
max_hp: 60,
|
||||
..MapUnit::default()
|
||||
});
|
||||
|
||||
let mut state = GameState::default();
|
||||
state.players.push(p0);
|
||||
state.players.push(p1);
|
||||
process_healing_phase(&mut state);
|
||||
|
||||
assert_eq!(
|
||||
state.players[0].cities[0].hp, 80,
|
||||
"besieged city (enemy unit on tile) must not heal"
|
||||
);
|
||||
}
|
||||
|
||||
/// A city at max HP does not overheal.
|
||||
#[test]
|
||||
fn full_hp_city_is_not_overhealed() {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue