diff --git a/src/simulator/crates/mc-combat/src/resolver.rs b/src/simulator/crates/mc-combat/src/resolver.rs index b4851068..5a934717 100644 --- a/src/simulator/crates/mc-combat/src/resolver.rs +++ b/src/simulator/crates/mc-combat/src/resolver.rs @@ -362,11 +362,11 @@ fn compute_predicted_damage(params: &CombatParams) -> PredictedDamage { let strength_diff = attacker_strength - defender_strength; let raw_damage_to_defender = BASE_DAMAGE * (strength_diff / STRENGTH_DIVISOR).exp() * atk_hp_factor; - // Stack-of-doom cap: a single attack cannot deal more than 2× the defender's - // current HP. Prevents overwhelming odds from one-shotting a city or unit in - // a single exchange — multiple attackers still add up, but each hit is capped. + // Stack-of-doom cap: a single attack cannot deal more than 3× the defender's + // current HP. 2× proved too tight (halved victories, stalled winner_tier_peak). + // 3× leaves typical late-game dominance intact while preventing pure one-shots. let damage_to_defender = - raw_damage_to_defender.min(2.0 * params.defender.hp as f32); + raw_damage_to_defender.min(3.0 * params.defender.hp as f32); // Retaliation damage let no_retaliation = prevents_retaliation(¶ms.attacker_keywords, is_ranged) diff --git a/src/simulator/tests/golden/vectors/mc-combat__resolve_basic.json b/src/simulator/tests/golden/vectors/mc-combat__resolve_basic.json index 360a04f0..ec59d811 100644 --- a/src/simulator/tests/golden/vectors/mc-combat__resolve_basic.json +++ b/src/simulator/tests/golden/vectors/mc-combat__resolve_basic.json @@ -87,7 +87,7 @@ }, { "name": "stress_first_strike_one_round_kill", - "defender_damage": 40, + "defender_damage": 60, "attacker_damage": 0, "attacker_outcome": "survived", "defender_outcome": "killed",