fix(rules): total defense and fighting defensively cannot be combined (SRD Total Defense) - no stacking, second declaration rejected

This commit is contained in:
2026-09-17 23:13:22 +02:00
parent 980d53d80b
commit a144423522
2 changed files with 64 additions and 3 deletions
+45
View File
@@ -256,3 +256,48 @@ def test_control_moving_foe_does_provoke_aoo() -> None:
eng = _duel(moving_foe, [10, 5, 8, 3], round_cap=1)
t = eng.run().transcript
assert any("AoO vs foe" in line for line in t)
# ── (g) total defense and fighting defensively do not stack ────────────────────
def test_fight_defensively_rejected_while_total_defense_active() -> None:
# SRD "Total Defense": "You can't combine total defense with fighting
# defensively." Declaring it after total defense is rejected; the AC
# bonus stays exactly +4.
def policy(_engine: CombatEngine, state: CombatantState) -> tuple[Action, ...]:
if state.combatant.id == "hero":
return (
Action(kind="total_defense"),
Action(kind="fight_defensively"),
)
return _WAIT
eng = _duel(policy, [10, 5], round_cap=1)
t = eng.run().transcript
hero = eng._states[0]
assert "round 1 hero: fight_defensively rejected (cannot_combine)" in t
assert hero.defensive_ac_bonus == 4
assert hero.fight_defensively is False
assert ("fight_defensively", False, "cannot_combine") in tuple(
(r.kind, r.ok, r.reason) for r in hero.last_action_results
)
def test_total_defense_supersedes_a_fighting_defensively_declaration() -> None:
# The reverse order: the +2 declaration is voided by the later total
# defense — the bonus is set to +4 (not +6) and the -4 attack flag clears.
def policy(_engine: CombatEngine, state: CombatantState) -> tuple[Action, ...]:
if state.combatant.id == "hero":
return (
Action(kind="fight_defensively"),
Action(kind="total_defense"),
)
return _WAIT
eng = _duel(policy, [10, 5], round_cap=1)
eng.run()
hero = eng._states[0]
assert hero.defensive_ac_bonus == 4
assert hero.fight_defensively is False
assert hero.total_defense is True