Every action is validated before anything is mutated, so a rejected action changes nothing. The
messages below are the exact messages the engine raises with IllegalAction, and the preconditions
are listed in the order they are checked.
Before any action-specific check: the game MUST NOT be over (winner is None), else
“the game is over”.
PlayCard(player, hand_index) — sorcery speedPLAN — “sorcery-speed cards only during Plan”.hand_index is a valid index into the player’s hand — “no such card in hand”.INSTANT — “use CastInstant for instants”.time >= cost) — “not enough Time (have/cost)”.On success: the card leaves the hand, its cost is paid in Time, it goes on the stack, and the active player receives priority.
CastInstant(player, hand_index, target_id=None) — instant speedhand_index is a valid index into the player’s hand — “no such card in hand”.INSTANT — “that card isn’t an instant”.time >= cost) — “not enough Time (have/cost)”.On success: as above, and the optional target_id is recorded on the new stack item.
PassPriority(player)On success: priority passes — resolve the top of the stack, advance the phase, or hand priority to the
next player (see turn-priority.md).
An unrecognised action is rejected with “unknown action: …“.