fix(rules): restricted charge - a creature limited to a standard action (staggered/disabled, surprise round) can still charge at 1x speed

This commit is contained in:
2026-09-17 23:06:03 +02:00
parent a5bd3c5a85
commit e253b03ffe
4 changed files with 74 additions and 20 deletions
+10 -4
View File
@@ -291,7 +291,9 @@ def _add_condition(state: CombatantState, name: str) -> None:
def test_staggered_after_budget_keeps_single_legal_action() -> None:
# (charge, attack): the budget keeps the charge and drops the attack as
# surplus standard; the staggered filter then drops the full-round charge.
# surplus standard; the staggered filter then keeps the charge as the
# Restricted Charge standard action (SRD "Charge": able to take only a
# standard action -> charge at up to 1x speed).
def policy(_e: CombatEngine, s: CombatantState) -> tuple[Action, ...]:
if s.combatant.id == "hero":
return (
@@ -307,7 +309,8 @@ def test_staggered_after_budget_keeps_single_legal_action() -> None:
eng._take_turn(hero)
reasons = [(r.kind, r.reason) for r in hero.last_action_results]
assert ("attack", "downgraded:action_economy") in reasons
assert ("charge", "downgraded:staggered") in reasons
assert ("charge", "downgraded:staggered") not in reasons
assert ("charge", "ok") in reasons
# ── (d2) the staggered filter keeps swift + free, one move-OR-standard ─────────
@@ -344,8 +347,11 @@ def test_staggered_filter_keeps_only_one_move_or_standard() -> None:
def test_staggered_filter_drops_full_round_and_downgrades_full_attack() -> None:
# A leading full_attack collapses to a single standard attack (kept).
assert _staggered_filter((_foe("full_attack"),)) == (_foe("attack"),)
# A full-round charge is dropped; a following standard fills the slot.
assert _staggered_filter((_foe("charge"), _foe("attack"))) == (_foe("attack"),)
# SRD "Charge": restricted to a standard action, charge survives as the
# Restricted Charge (standard, 1x speed) and spends the slot; run and
# other full-round kinds stay dropped.
assert _staggered_filter((_foe("charge"), _foe("attack"))) == (_foe("charge"),)
assert _staggered_filter((_foe("run"), _foe("attack"))) == (_foe("attack"),)
# full_attack after a spent slot is dropped (the slot is already used).
assert _staggered_filter((_foe("move"), _foe("full_attack"))) == (_foe("move"),)