Two layers decide whether a dispatched move is allowed to run: the engine’sDocumentation Index
Fetch the complete documentation index at: https://openturn.io/docs/llms.txt
Use this file to discover all available pages before exploring further.
activePlayers set, and your move’s own run body.
activePlayers (engine layer)
Each phase derives an activePlayers list. Core’s dispatch gate rejects any event whose playerID is not in that list with the reason inactive_player. This is automatic — you don’t write it.
By default, with a round-robin turn policy, activePlayers is [currentPlayer]. That alone is enough turn-gating for a standard alternating game; no per-move check is required.
increment, core rejects with inactive_player before run is called.
phases on a move
If a move is only valid in some phases, list them:
phases, it is valid in every phase.
Custom rules in run (game layer)
When the engine layer is too coarse — phases that allow multiple seats, custom validity rules, “the player must own this card” — your run decides and returns move.invalid(reason).
reason string surfaces to the client in the dispatch result, so the UI can render a specific message rather than a generic rejection.
Active players (per phase)
OverrideactivePlayers for a phase when more than one seat may act in it — typically simultaneous phases:
activePlayers returns a list, every seat on that list can dispatch moves. The move’s run decides what’s actually valid for the dispatching seat.
Custom turn policies
Today gamekit ships one turn policy:turn.roundRobin(). If your game is strictly alternating, use it. If not, rely on activePlayers and inline move.invalid(...) checks to enforce ordering, and leave turn off.
For fully custom progression (skip turns, reversed order, elimination), author the game with @openturn/core directly. See how-to: author with core.
What to read next
- Gamekit moves and outcomes is the outcome reference.
- How-to: handle simultaneous moves shows a concrete simultaneous pattern.