# @tiptap/turns@0.1.0

<!-- Generated by scripts/gen-package-reference.mts from the package source.
     Do not edit: §15.4 — package docs are generated, nobody writes them. -->

**Spec §7.1: `@tiptap/turns`** — whose go it is, and what happens when they
stop taking it.

Extracted from `turnphysics`'s `expire_turn` and `skip_turn`. §7.1's entry is
"turn order, deadlines, simulate-to-rest"; the first two are here, and
simulate-to-rest stays in `turnphysics` because it is a physics loop with a
rest test in it, not a turn primitive.

The whole crate is about sixty lines of logic. It exists anyway, because the
sixty lines encode four decisions that are wrong in almost every first
implementation and are invisible until somebody walks away from their phone.

## 1. The deadline is a tick count, never a wall clock

A deadline in milliseconds is a different deadline on every machine, and two
clients that disagree about whether a turn expired have disagreed about the
whole game. Ticks are the only clock every client shares.

## 2. A skipped turn is not a played one

[`Outcome::Skipped`] is distinct from a normal rotation, and it has to reach
the replicated state rather than only an event: a client that joins mid-turn
needs to know the current player timed out, and an event it was not present
for cannot tell it.

## 3. Rotation policy does not apply to a timeout

This is the one that looks like a special case and is a deadlock fix.
[`Policy::RotateUnlessScored`] keeps the turn with a player who scored.
Applied to a *timeout*, it hands the stalled player their own turn back —
forever — off a flag set by the shot before they walked away. So a skip always
rotates, and the policy is consulted only on a played turn.

## 4. `Fixed` means the game owns turn order, so a timeout reports and stops

Under [`Policy::Fixed`] this crate does not own the rotation; the game does,
over its own replicated state. The honest response to an expiry is to say so
and leave the seat alone. A game that wants the turn to move must not choose
`Fixed` — which is a real constraint and is why it is written here rather than
discovered.

## Types

```rust
pub struct Turns
```

Turn order and its deadline.

Small and `Copy`, so it lives inside a game's state and is rolled back with
it. That matters more than it looks: a turn counter kept outside the
simulation would not rewind, and a rollback across a turn change would leave
two clients disagreeing about whose go it is.

## Enums

```rust
pub enum Policy
```

How the turn moves after a played shot.

```rust
pub enum Outcome
```

What a turn advance did.

## Functions

```rust
pub fn tick(&mut self, policy: Policy) -> Outcome
```

One tick of the turn clock.

Returns [`Outcome::Skipped`] on the tick the deadline is reached, and
rotates regardless of `policy` — see decision 3 in this crate's header.

```rust
pub fn played(&mut self, policy: Policy, scored: bool) -> Outcome
```

A shot was played. `scored` only matters under
[`Policy::RotateUnlessScored`].

```rust
pub fn remaining(&self) -> Option<u32>
```

Ticks left, or `None` when there is no clock.

```rust
pub fn resize(&mut self, seats: u16)
```

Somebody left. Keeps the turn on a valid seat.

Clamped rather than rotated: rotating here would hand a departure the
power to skip somebody's turn, and §18.2 already makes a departure an
input that every client applies identically — it should not also be a
turn change nobody asked for.
