fix(rules): expire a readied action at the combatant's next turn

SRD 'Ready - Initiative Consequences of Readying': 'If you come to your next action and have not yet performed your readied action, you don't get to take the readied action (though you can ready the same action again).' _take_turn's start-of-turn block now clears state.readied/readied_trigger (logged 'ready expired'), before the policy call; the ready still survives the round boundary and can fire in the next round as long as the readier's own turn has not arrived. Updated the two tests that pinned the old infinite persistence and added cross-round fire + expiry tests.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
2026-09-12 13:59:11 +02:00
parent ec2088ab90
commit 7810d88488
3 changed files with 131 additions and 21 deletions
+17
View File
@@ -1511,6 +1511,7 @@ class CombatEngine:
# entry (it was cleared at round start and only a delay action sets it).
# The delayed turn still gets its policy call, budget and execution.
if not state.delayed_this_round:
self._expire_readied_action(state)
self._decrement_durations(state)
gate = self._turn_gate(state)
if gate is not None:
@@ -3933,6 +3934,22 @@ class CombatEngine:
self._in_reaction = False
return
def _expire_readied_action(self, state: CombatantState) -> None:
"""PF1e (SRD "Ready — Initiative Consequences of Readying"): "If you
come to your next action and have not yet performed your readied
action, you don't get to take the readied action (though you can
ready the same action again)." An untriggered readied action expires
at the START of the combatant's next turn, before the policy runs (a
forfeited turn still expires it)."""
if state.readied is None:
return
self._log(
f"round {self._current_round} {state.combatant.id}: "
"ready expired (trigger never occurred)"
)
state.readied = None
state.readied_trigger = None
def _check_readied(self, event: ReactionEvent) -> bool:
"""Fire the first matching readied action; True when one fired.