fix(rules): keep swift and free actions for staggered combatants
PF1e (CRB, Staggered): a staggered creature takes a single move OR standard action but can still take free, swift, and immediate actions. _staggered_filter returned only the first non-full-round action, so a disabled combatant with a queued swift bonus (deed/inspire_courage) lost its standard action. It now keeps every swift and free action plus the first move-or-standard, preserving the full_attack -> single attack downgrade. Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
@@ -33,6 +33,7 @@ from pf1e_simulator.combat import (
|
||||
_cast_time_slot,
|
||||
_dropped_kinds,
|
||||
_dropped_kinds_multiset,
|
||||
_staggered_filter,
|
||||
default_policy,
|
||||
)
|
||||
from pf1e_simulator.conditions import CONDITIONS_BY_NAME
|
||||
@@ -309,6 +310,46 @@ def test_staggered_after_budget_keeps_single_legal_action() -> None:
|
||||
assert ("charge", "downgraded:staggered") in reasons
|
||||
|
||||
|
||||
# ── (d2) the staggered filter keeps swift + free, one move-OR-standard ─────────
|
||||
|
||||
|
||||
def test_staggered_filter_keeps_swift_and_free_actions() -> None:
|
||||
# PF1e (CRB, Staggered): a single move OR standard, but free/swift/immediate
|
||||
# actions are still allowed. The policy's swift deed must not be dropped.
|
||||
assert _staggered_filter((_foe("deed"), _foe("attack"))) == (
|
||||
_foe("deed"),
|
||||
_foe("attack"),
|
||||
)
|
||||
# A swift taken AFTER the standard is likewise kept.
|
||||
assert _staggered_filter((_foe("attack"), _foe("deed"))) == (
|
||||
_foe("attack"),
|
||||
_foe("deed"),
|
||||
)
|
||||
# A free 5-foot step survives alongside the single standard action.
|
||||
assert _staggered_filter((_foe("5foot_step"), _foe("attack"))) == (
|
||||
_foe("5foot_step"),
|
||||
_foe("attack"),
|
||||
)
|
||||
|
||||
|
||||
def test_staggered_filter_keeps_only_one_move_or_standard() -> None:
|
||||
# move then standard → the move wins (first move-or-standard).
|
||||
assert _staggered_filter((_foe("move"), _foe("attack"))) == (_foe("move"),)
|
||||
# standard then move → the standard wins.
|
||||
assert _staggered_filter((_foe("attack"), _foe("move"))) == (_foe("attack"),)
|
||||
# two standards → only the first survives.
|
||||
assert _staggered_filter((_foe("attack"), _foe("attack"))) == (_foe("attack"),)
|
||||
|
||||
|
||||
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"),)
|
||||
# full_attack after a spent slot is dropped (the slot is already used).
|
||||
assert _staggered_filter((_foe("move"), _foe("full_attack"))) == (_foe("move"),)
|
||||
|
||||
|
||||
def test_nauseated_after_budget_keeps_single_move() -> None:
|
||||
# (move, attack, move): the budget drops the second move; the nauseated
|
||||
# filter then keeps only the first move and drops the standard attack.
|
||||
|
||||
Reference in New Issue
Block a user