> ## Documentation Index
> Fetch the complete documentation index at: https://openturn.io/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Gamekit moves and outcomes

> The full set of move outcomes, what each compiles to, and when to reach for each one.

A gamekit move's `run` function always returns an outcome built with the `move` helper:

```ts theme={null}
move({
  run({ G, args, move, player }) {
    // return one of:
    move.stay(patch?)
    move.endTurn(patch?)
    move.goto(phase, patch?, { endTurn?: boolean })
    move.finish(result, patch?)
    move.invalid(reason?, details?)
  },
})
```

Each outcome compiles to a core transition. Knowing which is which helps when you are reading the state graph or the replay log.

## `stay(patch)`

Keep the current phase, keep the current player's turn. The most common outcome for rounds where multiple players submit before anyone ends a turn (rock-paper-scissors plan phase).

Compiles to a core transition with `to: <same phase>`, `turn: "preserve"`.

## `endTurn(patch)`

Keep the current phase, advance the turn counter by one. The most common outcome for classic turn-based games (tic-tac-toe, chess).

Compiles to `to: <same phase>`, `turn: "increment"`.

## `goto(phase, patch, options?)`

Move to a different phase. By default keeps the turn counter; pass `{ endTurn: true }` to advance it.

Compiles to `to: <target phase>`, `turn: "preserve"` (or `"increment"`).

Use `goto` when the game has multiple phases (planning → battle → game-over, bidding → play). The target phase must be listed in `phases`.

## `finish(result, patch)`

End the match. `result` is any JSON-compatible record — the conventional shapes are `{ winner: playerID }` and `{ draw: true }`, but multi-winner, ranked, scored, and co-op results are all valid. The result is recorded on `snapshot.meta.result` and `profile.commit` reads it. The engine moves to the internal `__gamekit_finished` terminal state; no further moves are accepted.

```ts theme={null}
return move.finish({ winner: player.id }, { board });
return move.finish({ draw: true }, { board });
return move.finish({ scores: { "0": 12, "1": 7 } });
```

Compiles to `to: "__gamekit_finished"`, `turn: "increment"`, with the result field set.

## `invalid(reason, details)`

Reject the move. The engine returns `{ ok: false, error: "invalid_event", ... }` to the dispatcher and leaves `G` unchanged. Use it when the move was legal enough to try (the player had permission to dispatch) but invalid at the current state (the square was already occupied, the dice roll was out of range, etc).

Compiles to `rejectTransition(reason, details)` from core.

## Patches

Every outcome (except `invalid`) takes an optional `patch`: a `Partial<TState>` that is shallow-merged into `G`. If you omit it, `G` is unchanged.

```ts theme={null}
return move.endTurn({ board, lastMove: { row, col } });
```

Patches only express the fields that change. The rest of `G` is preserved.

## `enqueue`

Every outcome accepts `{ enqueue: [...] }` for follow-up internal events:

```ts theme={null}
return move.endTurn({ board }, {
  enqueue: [{ kind: "resolveEffects" }],
});
```

Gamekit forwards `enqueue` straight to the core reducer. Internal events run through your move definitions as if they had been dispatched, but with no player attribution.

## Picking the right outcome

* "I want the same player to go again": `stay`.
* "I want the next player to go": `endTurn`.
* "I want to change phases": `goto`.
* "The game is over": `finish` — pass `{ winner }`, `{ draw: true }`, or any JSON-shaped result your game records.
* "The move is illegal": `invalid`.

## What to read next

* [Gamekit permissions and turns](/docs/concepts/gamekit-permissions-and-turns) for when you need more than round-robin.
* [Concepts: reducers and queued events](/docs/concepts/reducers-and-queued-events) covers the `enqueue` mechanics.
