Time Accounting — Sanki game
==============================================

`reference` `suite-support`

This is a supporting document for the Sashité chess-family NIP suite. It does not define a new event kind or protocol mechanism; it specifies the per-ply time accounting that every consumer of a session in the `sanki` game context applies — the kernel the session's rule system names ([Rule System — Sanki](rule-system-sanki.md)) incorporates it — how the `time_control` configuration declared at founding (kind `3420` / `3418` §Match-terms tags) is turned into two running clocks, and exactly when a `timeout` verdict (see [Statuses — Sanki](statuses-sanki.md)) falls.

The protocol fixes the `time_control` **format** (kind `3420`) and the **timing source** (the events' canonical timing, per [Race Resolution](race-resolution.md)); it deliberately leaves the accounting arithmetic to the rule system. This document is that arithmetic for Sashité's Sanki game. Any two implementations of it reproduce the same clocks exactly, from public events alone.

## Scope

This document covers:

- How a ply's **elapsed time** is derived from canonical timing.
- How a **premove** is charged: a move pre-committed before its turn costs the mover no clock time.
- The two **spend rules** (fixed bank / Fischer, and quota periods) and where the increment applies.
- **Period transitions**: quota reset and bank-exhaustion rollover.
- The two **timeout flavours**: the played-Ply timeout and the abandonment timeout.
- The initial clock state, and client guidance for live display and claim prediction.

This document does NOT define:

- The `time_control` tag format and its validity constraints (kind `3420` §Match-terms tags).
- Canonical timing and (in attested mode) meta-resolution ([Race Resolution](race-resolution.md)).
- Verdict assembly — where each timeout flavour slots into the ruling ([Statuses — Sanki §Verdict resolution](statuses-sanki.md)).

## Inputs

**Time control.** The founding events declare one or more sequential periods, each `[duration, increment?, plies?]` (kind `3420`). The configuration is **session-global and symmetric**: the same periods feed both players' clocks. Validity (enforced at the format level by kind `3420`): at least one period; `plies` requires `increment`; `plies` is strictly positive.

**Clock state.** Each player's clock is the triple:

| Field | Meaning |
|---|---|
| `remaining` | the budget left in the current period (seconds) |
| `period` | the index of the current period (0-based) |
| `plies_in_period` | how many of the player's plies were accounted in the current period |

At t₀ — the Game Session's **`start_at` tag when present, otherwise its canonical timing** (kind `3422` §Canonical session start) — both clocks start identical: `remaining` = the first period's `duration`, `period` = 0, `plies_in_period` = 0. A **scheduled** `start_at` overrides the canonical-timing anchor (in attested mode the attestation may land after `start_at`; `start_at` still defines t₀): the on-move player's clock runs from the scheduled start, so a player who never appears flags from `start_at` under the same abandonment arithmetic (§The two timeout flavours), with no protocol deadline.

## Elapsed time

The elapsed time charged for a ply is the difference between **consecutive canonical timings**:

```
elapsed = max(0, t(ply) − anchor)
```

where `t(ply)` is the **selected** ply's canonical timing (the candidate the selection rule retains for the slot — [Move Encoding — Sanki §Slot candidates and selection](move-encoding-sanki.md)), and `anchor` is the **maximum canonical timing among the previous half-moves in the play order** — or t₀ for the session's first half-move. The anchor advances **monotonically**: a selected **premove** carries an *anterior* timing (it was committed before its predecessor even landed) and leaves the anchor where it stood — the moment the position became answerable — never rewinding it. For a chain with no applied premove this is exactly the previous half-move's canonical timing.

The clamp to zero is not a guard against anomalies; it is the **premove case**. A premove is committed during the opponent's turn, so its canonical timing precedes the opponent's preceding half-move — `t(ply) < anchor` — and its elapsed is **zero**: the mover spent no clock time on a move they had pre-committed during the opponent's thinking time. The mover's clock effectively pauses across a pre-committed move, and the time the opponent spent meanwhile was charged to the *opponent's* clock on the preceding half-move. Symmetrically, the premove's **successor** is charged from the anchor the premove *left in place* — the predecessor's landing — never from the premove's own anterior timing: rewinding the anchor would bill the next mover for an interval during which the position did not yet exist for them (a 4-second reply reading as 13, up to a wrongful played-Ply flag on that mover's own half-move). This monotonic-anchor rule is pinned across implementations by the shared conformance vector `scenario.premove-anchor-never-rewinds`. For a move played in the normal order, `t(ply) ≥ anchor` and the difference is non-negative in the usual way.

The whole non-zero interval is charged to the **mover** — thinking time, but also network propagation and, in attested mode, the timestamper's attestation latency. This is the price of determinism: every consumer derives the same clocks from the same public events (see [Race Resolution](race-resolution.md) §The `created_at` field of suite events). The timing authority's latency discretion is analyzed in [Trust Model](trust-model.md) (§Timestamper discretion / §Timing-relay discretion).

## Spend rules

What distinguishes the two rules is **when the increment is available**:

### Fixed bank / Fischer (no `plies`)

The ply MUST complete within the period's `remaining`; the increment is a **post-ply bonus** (kind `3420`: "increment is added to the budget *after each ply*"):

```
if elapsed > remaining        → bank exhausted (see §Period transitions)
else remaining' = remaining − elapsed + increment
```

With `increment` absent, this is the plain fixed bank: linear depletion.

### Quota periods (`plies` present — byōyomi, Canadian, per-move)

The increment is the per-ply **allowance, available during the ply**:

```
available = remaining + increment
if elapsed > available        → flag (timeout)
else remaining' = available − elapsed
```

This reading is forced by the `duration: 0 + increment + plies` configuration of kind `3420` ("per-move time accounting with no main clock"): were the increment a post-ply bonus there, the very first move would flag.

## Period transitions

1. **Quota reached** (`plies_in_period` reaches the period's `plies` after the ply): the clock moves to the **next** period — `remaining` reset to that period's `duration`, counter reset to 0 — or, when the current period is the **last**, it **repeats**: reset to its own `duration`, counter 0 (the byōyomi/Canadian reset). Any residual `remaining'` is **discarded** by the reset, as in over-the-board byōyomi.
2. **Bank exhausted** (a period *without* a quota, `elapsed > remaining`): the overspend **rolls over** into the next period — the clock enters it with its full `duration` and the overspend (`elapsed − remaining`) is charged there, recursively. This realizes "a main bank followed by overtime periods" (kind `3420` Example 5). The flag falls only when **no further period remains**, or when a quota period's allowance is exceeded.

## The two timeout flavours

Both are the same arithmetic, applied at two different moments (their place in the ruling is fixed by [Statuses — Sanki §Verdict resolution](statuses-sanki.md)):

- **Played-Ply timeout** — evaluated when a slot's **selected** Ply is evaluated in the natural-state chain: `elapsed` runs from the previous anchor to that ply's canonical timing (§Elapsed time). If the mover's clock flags, the session terminates `timeout` against the mover, carried by that very Ply. A selected premove (timed before the opponent's move) has `elapsed = 0` and so never flags here. The selection rule retains only a **legal** candidate — illegal ones are skipped, not evaluated ([Move Encoding — Sanki §Slot candidates and selection](move-encoding-sanki.md)) — so the clock is only ever ticked for a legal move; there is no `illegalmove` outcome to order against it.
- **Abandonment timeout** — evaluated at the **cutoff** (the Conclusion's canonical timing, kind `3425`), for the player **on move** at the end of the chain: `elapsed` runs from the chain's last anchor (or t₀ for an empty chain) to the cutoff. If that player's clock flags, the session terminates `timeout` against them. This is the recovery path against a silent opponent (the other player concludes; kind `3425` §Motivation) — and it is equally what runs when a player's slot is left **unfilled** because their only candidates (up to the cap `K`) were illegal and skipped (the right-to-err of [Move Encoding — Sanki §Premoves](move-encoding-sanki.md)): they are on move, their clock running, exactly as if they had not moved.

A claim cannot be voided by timing latency: the cutoff is at or after the moment the claim was published, and elapsed time only grows — an expired clock only becomes more expired. The claim made while the on-move player still has time is the one at the claimant's risk (it resolves to the claimant's resignation; [Statuses — Sanki §Implicit resignation](statuses-sanki.md)).

## Worked examples

### Fischer 5+3 — `["time_control", "300", "3"]`

One period `[300, +3]`. The first player's ply attested 10 s after t₀:
`300 − 10 + 3 = 293` s remaining. A player at 5 s remaining who takes 7 s **flags** — the increment is a post-ply bonus, not available during the ply.

### Main bank then per-move overtime — `["time_control", "3600"]` + `["time_control", "0", "30", "1"]`

(Kind `3420` Example 5.) Period 0 is a plain bank `[3600]`; period 1 is per-move `[0, +30, /1]`.

- A player with 10 s left in the bank takes 25 s: the bank is exhausted, the **overspend (15 s) rolls over** into the overtime, whose allowance is `0 + 30 = 30` s — it fits; the quota (1 ply) is reached, the period being the last, it **repeats**: reset to `remaining = 0`. Every subsequent ply must fit in 30 s.
- Same position but 50 s elapsed: overspend 40 s > 30 s allowance → **flag**.

### Classical two-period — `["time_control", "5400", "30", "40"]` + `["time_control", "1800", "30"]`

40 moves in 90 min + 30 s/move, then 30 min + 30 s/move for the rest. On the player's 40th accounted ply, the quota is reached: the clock advances to period 1, reset to `1800` (the period-0 residual is discarded); play continues in Fischer mode there.

### Per-move only — `["time_control", "0", "30", "1"]`

Every ply must complete within 30 s; the clock resets after each. A 40 s ply flags.

## Client guidance

- **Live display.** Between timed events, a client shows `remaining − (now − last canonical timing)` for the player on move (plus the allowance in a quota period), with `now` from the local clock. This display is an approximation — only the canonical-timing arithmetic is authoritative — and SHOULD be reconciled on every newly timed event.
- **Claim prediction.** A client SHOULD enable a win-on-time claim only when the opponent's budget is exhausted under this document's arithmetic from already-timed events (plus a safety margin for its own clock skew). Publishing early is at the claimant's risk; publishing "late" is safe (the cutoff only moves later).
- **Charged latency.** Because timing latency is charged to the mover, clients SHOULD publish plies promptly after the user commits a move; in attested mode, players SHOULD prefer a timestamper with low, consistent latency (a publicly analyzable property; [Trust Model](trust-model.md) §Reputation as a complementary layer).

## References

- Kind `3420` — Direct Challenge (the `time_control` tag format and its multi-period semantics)
- Kind `3418` — Open Challenge (carries the same `time_control` format on the matchmaking path)
- Kind `3422` — Game Session (t₀ = the `start_at` tag or, absent it, the canonical timing — the accounting anchor, per §Canonical session start)
- Kind `3423` — Ply (whose canonical timing drives the accounting)
- Kind `3425` — Conclusion (its canonical timing is the cutoff for the abandonment timeout)
- [Statuses — Sanki](statuses-sanki.md) — the `timeout` status and §Verdict resolution
- [Move Encoding — Sanki](move-encoding-sanki.md) — the two-window selection rule that determines which Ply a slot is charged on
- [Race Resolution](race-resolution.md) — canonical timing, the suite's timing source
- [Trust Model](trust-model.md) — the timing authority's latency discretion and its mitigations
