- Sashité for Developers
- Nostr
- NIPs
- Game Session
NIP-XX
Game Session
draft optional
Abstract
Defines kind: 3422, a regular event published, once the session’s founding is complete, by the session’s designated arbiter. The Game Session carries the session’s two players, their seat assignments, their per-player variants, the game family, and the initial state of play — including the initial board position for variants that require it (e.g., chess960, shogi960) — and constitutes an explicit ratification of the session parameters by its signer. The founding follows one of three paths that converge here — a one-to-one Direct Challenge / Accepted Challenge (kinds 3420/3421), a matchmaking Open Challenge / Pairing (kinds 3418/3419), or a Rematch Offer (kind 3430, a mutually addressed pair from a concluded session’s two players — §Rematch founding) — each yielding exactly two players, seated first and second (the protocol’s seat vocabulary, kind 3420 §Match-terms tags); the variant vocabulary is the rule system’s. No Ply (kind 3423) is valid until a conforming Game Session is published.
Motivation
The lifecycle of a session has a transition between “parameters declared” (after the founding) and “playable” (when the players can submit Plies). Two concerns motivate making this transition explicit, signed by the arbiter:
-
Initial position establishment. Some variants (chess960, shogi960, and other randomized-setup variants) require an initial position that is unknown until the session begins. The randomization must be authoritative: neither player can choose a favorable starting position. The arbiter — already trusted to rule on the session outcome — is the natural signer of this initial position.
-
Explicit arbiter ratification. Before a session begins, the arbiter should confirm they accept the session parameters (the
game, the chosen variants, the seat assignments, the time control, the players). Without an explicit ratification step, a session might start before the arbiter even acknowledges it — leading to plays that the arbiter later cannot or will not adjudicate. The Game Session is the arbiter’s signed commitment to officiating the session. For a matchmaking founding (Pairing, kind3419), where the seat is deliberately left open, the arbiter additionally assigns each player a seat at this step — typically a fair random draw, the same authoritative-randomization act as choosing a chess960 position (see §Seats).
By unifying these two concerns into one event, the protocol provides a clear and uniform “the arbiter is ready” milestone for every session, applicable both to deterministic-setup variants (where the content is empty) and randomized-setup variants (where the content carries the initial position).
Time accounting implication
The Game Session defines the session-start moment (t₀) used for time-control accounting: its optional start_at tag when present, otherwise its canonical timing (§Canonical session start). Before t₀, no Ply is valid; at t₀, the clock for the player expected to move first starts running. The detailed t₀ semantics — including the start_at override and the role of canonical timing — are specified in §Canonical session start below.
Specification
Event kind
kind: 3422
Game Session events are regular events per NIP-01: immutable once signed and broadcast.
Signing party
The event MUST be signed by the pubkey designated by its founding context. In this suite, for a directed session that is the arbiter-marked pubkey of the Accepted Challenge (kind 3421), for a matchmade session the arbiter-marked pubkey of the Pairing (kind 3419), and for a rematch session the arbiter-marked pubkey shared by the two Rematch Offers (kind 3430) — necessarily the arbiter that ruled the concluded session (§Rematch founding). The arbiter founds the Game Session on all three paths. Any other signer’s event referencing the same founding with kind 3422 is invalid and MUST be ignored by verifiers.
Tags
Founding reference
| Tag | Cardinality | Value | Description |
|---|---|---|---|
e |
one or two (path-dependent) | event id, optional relay hint, marker | reference(s) to the session’s founding event(s) |
The Game Session MUST reference, via e tag(s), the founding event(s) that established the session and designated its arbiter. The marker (fourth element) names the founding event type and is defined by the consuming application. In this suite, the founding is one of three alternatives — a single founding event on the directed and matchmade paths, or a mutually addressed pair on the rematch path (never mixed):
["e", "<founding_event_id>", "<relay_hint>", "accepted_challenge"]— the Accepted Challenge (kind3421) that instantiated a directed session.["e", "<founding_event_id>", "<relay_hint>", "pairing"]— the canonical Pairing (kind3419) that instantiated a matchmade session.["e", "<offer_1_id>", "<relay_hint>", "rematch_offer"]and["e", "<offer_2_id>", "<relay_hint>", "rematch_offer"]— the two mutually addressed Rematch Offers (kind3430) from a concluded session’s two players that instantiated a rematch session. This is the one path carrying two founding references (§Rematch founding).
Each founding event designates the arbiter (via an arbiter-marked p tag), carries the session’s timing designation (a timestamper-marked p tag in attested mode, or timing_relay tags in self-timed mode — Canonical Timing §Timing modes and mode selection), and establishes the two players — on the rematch path the pair of Offers does so jointly (§Rematch founding) — so the Game Session’s signing-party and player rules apply uniformly regardless of path; seat and variant sourcing differ by path (see §Seats, §Variants, and §Rematch founding). The relay hint (third element) is OPTIONAL but SHOULD be present.
Timing designation (mirrored)
| Tag | Cardinality | Value | Description |
|---|---|---|---|
p timestamper |
zero or one | pubkey, optional relay hint, marker timestamper |
in attested mode, the designated timestamper, mirrored from the founding |
timing_relay |
zero or more | relay URL (wss://…) |
in self-timed mode, the designated timing relays, mirrored from the founding verbatim |
The Game Session repeats the founding’s timing designation — exactly one of the two forms — so that it is self-contained for verifiers: a consumer holding only the Game Session knows the session’s timing mode and which parties (timestamper, or relays) establish canonical timing for every event of the session, without first resolving the founding chain.
Game
| Tag | Cardinality | Value | Description |
|---|---|---|---|
game |
exactly one | game-family identifier | the game family of the session |
The Game Session MUST carry exactly one game tag identifying the game family. The identifier MUST match the regular expression ^[a-z][a-z0-9]{0,31}$. The session’s game is the arbiter’s ratified statement of the family and MUST equal the game declared in the referenced founding event(s).
["game", "<game_id>"]
Example values: chess, shogi, xiangqi, go, or — in this suite — sanki (a multi-game 8×8 platform).
Players
| Tag | Cardinality | Value | Description |
|---|---|---|---|
p |
exactly two | pubkey, optional relay hint, marker player |
a session player |
The Game Session carries one p tag per player, each with the marker player as its fourth element:
["p", "<player_pubkey>", "<relay_hint>", "player"]— a session player.
The set of player pubkeys MUST be consistent with the players established by the referenced founding event(s). The p tags serve a dual purpose: semantic (identifying the players) AND structural (notification routing via standard Nostr #p filters, so each player receives the Game Session without chained subscription logic).
All three founding paths — directed (kinds 3420/3421), matchmade (kinds 3418/3419), rematch (kind 3430) — establish exactly two players, so a Game Session carries exactly two p tags.
Seats
| Tag | Cardinality | Value | Description |
|---|---|---|---|
seat |
exactly one per player | pubkey, seat-name | the player’s seat assignment |
The Game Session MUST carry exactly one seat tag per player. The second element is the player’s pubkey; the third is the seat-name, one of exactly two protocol-defined values: first (moves first) or second (moves second). The two players MUST hold the two distinct values. Seat-names are play-order indices, not game vocabulary; the traditional labels (white/black, sente/gote, red/black) are presentation, mapped by the rule system (kind 3420 §Match-terms tags).
["seat", "<player_pubkey>", "<seat_name>"]
The source of the seat assignment depends on the founding path:
- Directed founding (Accepted Challenge, kind
3421): the founding pair fixes the seat via aseattag carried by either the Direct Challenge (kind3420, the challenger’s declared seat) or the Accepted Challenge (the challenged player’s, when the Direct Challenge left it open) — exactly one of the two carries it (kind3421§Semantic constraints, constraint 8). The Game Session’sseatassignment MUST be consistent with that seat: the declaring player holds the declared value, the other player the other value (first↔second). - Matchmaking founding (Pairing, kind
3419): the founding carries no seat. The arbiter assigns each player a seat at this step — typically by a fair random draw, the same authoritative-randomization act as choosing a randomized initial position (chess960). The assignment originates here, in the Game Session’sseattags, and is authoritative from this event; it is constrained only by the requirement that the two players receive the two distinct values. - Rematch founding (Rematch Offers, kind
3430): the seats are swapped relative to the concluded session — each player receives the other value than the one they held there. The arbiter derives this deterministically from the concluded Game Session’s ownseattags (no draw, no discretion). See §Rematch founding.
Variants
| Tag | Cardinality | Value | Description |
|---|---|---|---|
variant |
exactly two (one per player) | pubkey, variant-name | the player’s variant within the game family |
The Game Session MUST carry exactly one variant tag per player. Each variant tag is structured ["variant", "<player_pubkey>", "<variant_name>"]. The second element is a player pubkey; the third is a variant identifier matching ^[a-z][a-z0-9]{0,31}$.
Each player’s variant MUST equal that player’s variant declared in the referenced founding event(s). On the rematch path the founding Offers carry no variant (they inherit — kind 3430 §Inherited terms); each player’s variant MUST instead equal that player’s variant in the concluded session the Offers reference (§Rematch founding).
The requiredness is unconditional — a deliberately structural rule, so that conformance is countable without knowing anything about the game: a game family with a single variant simply repeats its one style’s identifier (e.g., ["variant", "<pubkey>", "chess"] in a plain chess session). The identifiers’ meaning stays opaque to the protocol; the arbiter’s rule system interprets them (for sanki, each player’s variant — chess, ogi, xiongqi — determines their setup and movement rules on the shared board).
Scheduled start (optional)
| Tag | Cardinality | Value | Description |
|---|---|---|---|
start_at |
zero or one | unix seconds | the scheduled session-start instant (t₀); overrides canonical timing |
A Game Session MAY carry a start_at tag — a single unix-seconds instant declaring when the session’s clock starts:
["start_at", "<unix_seconds>"]
When present, start_at MUST be ≥ the event’s created_at (a session cannot schedule a start before it exists — locally verifiable), and it becomes the session’s t₀: no Ply is valid before it, and the game cannot begin before that instant. When absent, t₀ is the session’s canonical timing (§Canonical session start) — so start_at is fully backward-compatible and optional on every founding path (directed, matchmade, rematch).
Because t₀ is shared by both players, a start_at shifts both clocks identically and cannot create an asymmetric advantage. A session on any founding path MAY use it to schedule a start ahead of publication.
Rematch founding
This section applies only to a Game Session whose founding references are marked rematch_offer (kind 3430). A rematch Game Session realizes a new game between the same two players as a concluded session, with the seats swapped and every other term reused. Unlike the directed and matchmade paths — a single founding event — the rematch path has two founding references: the mutually addressed pair of Rematch Offers (kind 3430) by which the two players each consented (kind 3430 §Rematch founding). The arbiter that ruled the concluded session is both the binder and the founder — it already holds the terms, so nothing is redeclared. That the concluded session is in fact concluded is not taken on trust either: each Offer carries a concluded_by reference to an Adjudication (kind 3425) of it, so a verifier establishes the precondition from the events rather than from the arbiter’s word (kind 3430 §Tags).
- Signer. The signer MUST be the
arbiter-marked pubkey shared by the two Rematch Offers — necessarily the arbiter of the concluded session they reference (kind3430§Operating mode), uniform with the directed and matchmade paths. - Founding references. Exactly two
etags markedrematch_offer, referencing two conforming Rematch Offers (kind3430) that form a matched pair: both referencing the same concluded Game Session, mutually addressed (each signed by one player and naming the other as itsopponent), both designating this arbiter, and both unexpired (kind3430§Rematch founding). Each Offer additionally carries aconcluded_byreference to an Adjudication (kind3425) of that same concluded session; the two references need not name the same Adjudication — a session may carry several, and each is an equally valid proof that it ended — so a difference between them does not break the pair. - Players. The two
player-markedptags MUST be the concluded session’s two players — equivalently, the two Offers’ signers. - Game. The
gametag MUST equal the concluded session’sgame. - Seats. Swapped relative to the concluded session: each player receives the opposite of the seat they held there (§Seats). A rematch that preserves the concluded session’s seats is non-conforming.
- Variants. Each player’s
variantMUST equal that player’s variant in the concluded session (the Rematch Offers carry none — they inherit). For a multi-variant family (e.g.,sanki) thevarianttags are required, as on the other paths. - Operating mode. The same as the concluded session — the same timing designation (the same timestamper, or the same
timing_relayset), which the matched Offers mirror (kind3430§Operating mode). - Deadline. The rematch Game Session’s canonical timing MUST be at or before the earlier of the two Offers’
accept_untilvalues (constraint 9 below; kind3430§Lifecycle tags). - Content. The founder provides the canonical initial position for the pairing’s
(game, variants)after the seat swap, per §Content.
For this path the semantic constraints below apply with these substitutions: constraint 1’s founding reference is the pair of Rematch Offers (kind 3430, marked rematch_offer) rather than a single event; constraint 2’s signer is those Offers’ shared arbiter; constraint 3’s game, constraint 5’s seats (here swapped), and constraint 6’s per-player variant all resolve against the concluded session the Offers reference; and constraint 9 (the rematch deadline) applies. The canonical rematch for a concluded session is the first valid arbiter-signed Game Session for the slot (rematch, concluded session) (§Idempotence and race resolution; Canonical Timing §Meta-resolution).
Content
The content field MUST be a non-empty string representing the initial position of the session. The encoding format is application-specific and determined by the consuming application based on the session’s game and variant tags. Common encoding formats include:
- FEN (Forsyth-Edwards Notation) for chess variants.
- SFEN (Shogi Forsyth-Edwards Notation) for shogi variants.
- FEEN (Field Expression Encoding Notation, Sashité specs) for the reference
sankirule system, per its supporting document (Position Encoding — Sanki, in this suite’s application layer). - Other domain-specific or custom encodings for other game families.
The protocol does NOT prescribe any particular encoding, does NOT validate the content’s semantic well-formedness, and does NOT interpret it. Recognition of the encoding (from the declared game and variant), parsing, and validation against the variant’s rule system are the responsibility of the consuming application.
Requiring an always-present initial position encoding — rather than allowing an implicit “default position” for deterministic variants — eliminates ambiguity. Every Game Session is self-describing: a consumer can reconstruct the session’s starting state from the event alone, given the declared encoding format, without depending on hardcoded conventions about what “the standard position of variant X” means. This also generalizes uniformly to variants whose starting position is randomized (chess960), custom (handicap setups), or game-specific (e.g., the reference sanki rule system, where the multi-style nature of the game makes a “default” hard to define).
The content field MUST consist of valid UTF-8, contain no C0/C1 control characters (except those explicitly permitted by the declared encoding), and contain no Unicode bidirectional override characters. The total length of the content field MUST NOT exceed 4096 bytes.
Semantic constraints
A conforming Game Session event MUST satisfy all of the following:
- The event’s founding reference is conforming: exactly one
etag on the directed and matchmade paths, or exactly tworematch_offer-markedetags on the rematch path, each referencing a retrievable, signature-valid, conforming founding event (per that kind’s own constraints — §Semantic constraints for an Accepted Challenge or a Rematch Offer, §Consent constraints for a Pairing). (In this suite: the Accepted Challenge, kind3421, markedaccepted_challenge, for a directed session; the canonical Pairing, kind3419, markedpairing, for a matchmade session; or a matched pair of Rematch Offers, kind3430, markedrematch_offer, for a rematch session — §Rematch founding.) - The event’s
pubkey(the signer) MUST match the arbiter designated by that founding event. (In this suite: thearbiter-marked pubkey of the Accepted Challenge, of the Pairing, or shared by the pair of Rematch Offers.) - The event contains exactly one
gametag, with a valid identifier per §Game, whose value MUST equal thegamedeclared in the referenced founding event(s). - The event contains exactly two
ptags carrying the markerplayer, with distinct pubkeys, matching the two players established by the founding event(s). - The event contains exactly one
seattag per player. Eachseattag’s second element is a player pubkey (per constraint 4); the seat-names arefirstandsecond, one each (§Seats). For a directed founding (Accepted Challenge, kind3421), the seat assignment MUST be consistent with theseattag carried by the founding pair — by the Direct Challenge or, when the Direct Challenge omits it, by the Accepted Challenge (per kind3421§Semantic constraints): the declaring player holds the declared value, the other player the other value. For a matchmaking founding (Pairing, kind3419), which carries no seat, the arbiter assigns the two values (see §Seats); no founding seat constrains the assignment. For a rematch founding (Rematch Offers, kind3430), the seats MUST be swapped relative to the concluded session the Offers reference (§Rematch founding). - The event contains exactly two
varianttags, one per player pubkey (per constraint 4). Eachvariant’s third element MUST equal that player’s variant declared in the referenced founding event(s) — for a rematch, in the concluded session the Offers reference, the Offers themselves carrying none (§Rematch founding). - The
contentfield is a non-empty string per §Content, with length at most 4096 bytes. - If a
start_attag is present, it is a single unix-seconds value≥the event’screated_at; t₀ is then the later of it and the canonical timing (§Canonical session start). - The event’s timing designation mirrors the founding’s verbatim: the same
timestamper-markedptag (attested mode) or the sametiming_relayset (self-timed mode), and exactly one of the two forms. Rematch path only: the event’s canonical timing is at or before the earlier of the two referenced Offers’accept_untilvalues; a rematch Game Session whose canonical timing exceeds it is non-conforming (kind3430§Lifecycle tags).
Constraints 1–4, 6, and 9 require cross-event validation against the referenced founding event (directed: the Accepted Challenge, and possibly the Direct Challenge, depending on which carries the relevant tags, per kind 3421 §Semantic constraints; matchmade: the Pairing, kind 3419, and the two Open Challenges it references; rematch: the pair of Rematch Offers, kind 3430, and — for the game, variants and swapped seats — the concluded Game Session they reference). Constraint 5 is a cross-event check for a directed founding (consistency with the seat carried by the Direct Challenge or the Accepted Challenge) and for a rematch founding (the swap of the concluded session’s seats), and an event-local check for a matchmade founding (the arbiter’s own opposite-seat assignment). Constraint 7 is checkable from the event alone. The Game Session’s created_at is informational and is not subject to any cross-event semantic constraint; ordering is enforced structurally by the cross-event reference chain (the Game Session references the founding event by event ID, which proves it was signed after the founding event existed).
Idempotence and race resolution
A given session has exactly one canonical Game Session. If the arbiter publishes multiple Game Session events for the same session — the same founding reference for a directed (accepted_challenge) or matchmade (pairing) session, or the same (rematch, concluded session) slot for a rematch session (one concluded session founds at most one rematch, however many Rematch Offers its players publish — §Rematch founding) — the canonical event is determined per Canonical Timing §Meta-resolution: the candidate with the smallest canonical timing, with the smallest event ID as tiebreaker.
Non-canonical Game Sessions are ignored by verifiers. The arbiter SHOULD publish only one Game Session per session; publishing multiple is non-conforming behavior and may indicate arbiter misbehavior or implementation bugs. In attested mode, the Game Session’s own created_at is informational and MUST NOT be used for canonicalization; in self-timed mode it IS the canonical timing (made non-backdatable by the designated timing relay).
Validation responsibility
Validation of a Game Session against the semantic constraints is the responsibility of the client (consuming players’ clients, downstream observers, rating verifiers). The arbiter is trusted to publish a conforming event; an event from the arbiter that fails semantic constraints is structurally invalid and MUST be rejected.
A session without a conforming Game Session remains in the “declared but not playable” state. No Ply (kind 3423) can be valid for such a session: Ply validity requires t₀, and t₀ requires both a conforming Game Session and its canonical timing (§Canonical session start; kind 3423 §Time accounting). Plies whose canonical timing precedes t₀ MUST be treated as invalid.
Canonical session start
The session-start timestamp (t₀), used by time-control accounting (see kind 3423 §Time accounting), is:
t₀ = the later of the Game Session’s
start_attag value (when present) and the Game Session’s canonical timing (Canonical Timing §t₀).
With no start_at, t₀ is simply the canonical timing. With a future start_at, the session is scheduled and no clock runs before it. And if canonical timing is established only after start_at has passed, t₀ is that later moment — no player is ever charged for an interval during which no valid Ply existed.
The canonical timing is, always, the timing that orders the session and establishes its existence. In attested mode it is the created_at of the Event Timestamp Attestation (kind 3410) signed by the designated timestamper and referencing this Game Session via an attests-marked e tag (the Session Start Attestation). In self-timed mode it is the Game Session’s own created_at, once its acceptance by a designated timing relay is established. Whichever branch yields t₀, the result is authoritative for all time-control accounting — rule-system-defined; the reference sanki rule system specifies it in its supporting document (Time Accounting — Sanki).
In attested mode, the Game Session’s own created_at is informational and MUST NOT be used as the canonical timing or as input to any timing-dependent decision (a present start_at still defines t₀); in self-timed mode the created_at is the canonical timing — and t₀ when no start_at is present.
The structure of the Session Start Attestation is:
["e", "<game_session_event_id>", "<relay_hint>", "attests"]
Only the attests-marked e tag is required. NIP-26-delegated signatures from the timestamper are NOT accepted as authoritative; see kind 3420 §Security considerations.
Until the Game Session has canonical timing, it is pending (Canonical Timing §The pending state) and the session is in an initialized but untimed state: distinct from declared but not initialized (before the Game Session) and from playable (after t₀ exists and has been reached). No Ply is valid in this window — operationally, a Ply is early or late by comparing its own canonical timing to t₀; no wall-clock observer is involved. In attested mode, the Game Session acquires canonical timing when the designated timestamper attests it — a window bounded only by the timestamper’s responsiveness (§Handling a silent timestamper below). In self-timed mode, canonical timing is established the moment a designated timing relay accepts the Game Session, so the window is only propagation delay — unless no designated relay accepts it (§Handling a silent timing relay below).
No founding event’s created_at defines t₀. On the directed path, the Direct Challenge constrains the lifecycle deadline (accept_until) and the Accepted Challenge contributes to lifecycle ordering via its own canonical timing; on the matchmaking path, the Open Challenge constrains the deadline and the Pairing contributes ordering via its own canonical timing. None of these contributes to time-control accounting, which anchors on the Game Session’s start_at (when present) or else its canonical timing.
Examples
The examples below use placeholder pubkeys, event ids, and timestamps for illustration. They assume reference to a corresponding Accepted Challenge event (kind 3421).
Example 1 — Standard chess starting position (FEN encoding)
The session is game="chess", both players play variant="chess". The arbiter (Carol) signs the Game Session with the standard chess starting position encoded as a FEN string:
{
"kind": 3422,
"pubkey": "<carol_pubkey>",
"created_at": 1700000400,
"tags": [
["e", "<acceptance_event_id>", "wss://relay.example.com", "accepted_challenge"],
["timing_relay", "wss://relay.example.com"],
["game", "chess"],
["p", "<alice_pubkey>", "wss://relay.example.com", "player"],
["p", "<bob_pubkey>", "wss://relay.example.com", "player"],
["seat", "<alice_pubkey>", "first"],
["seat", "<bob_pubkey>", "second"],
["variant", "<alice_pubkey>", "chess"],
["variant", "<bob_pubkey>", "chess"]
],
"content": "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1",
"id": "...",
"sig": "..."
}
The content carries the standard chess starting position in FEN. Even though the position is conventionally well-known, the protocol requires it to be explicit: the Game Session is self-describing. The session becomes playable once this event has canonical timing — its Session Start Attestation in attested mode, or acceptance by a designated timing relay in self-timed mode — establishing t₀ per §Canonical session start; at that point, the player assigned to seat first (Alice in this example) submits the first Ply.
Example 2 — Randomized initial position (chess960)
The session is game="chess", both players play variant="chess960". The arbiter selects starting position 518 (the standard chess position, by coincidence — one of 960 possible positions) and encodes it as a FEN string:
{
"kind": 3422,
"pubkey": "<carol_pubkey>",
"created_at": 1700000400,
"tags": [
["e", "<acceptance_event_id>", "wss://relay.example.com", "accepted_challenge"],
["timing_relay", "wss://relay.example.com"],
["game", "chess"],
["p", "<alice_pubkey>", "wss://relay.example.com", "player"],
["p", "<bob_pubkey>", "wss://relay.example.com", "player"],
["seat", "<alice_pubkey>", "first"],
["seat", "<bob_pubkey>", "second"],
["variant", "<alice_pubkey>", "chess960"],
["variant", "<bob_pubkey>", "chess960"]
],
"content": "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1",
"id": "...",
"sig": "..."
}
The content carries a FEN representation of the chosen initial position. Both players’ clients interpret this string per chess960 conventions to display the starting position.
Example 3 — Sashité Sanki session (FEEN encoding)
The session is game="sanki", Alice plays variant="chess" and Bob plays variant="ogi" (a multi-variant session). The reference arbiter signs the Game Session with the multi-variant starting position encoded as a FEEN string:
{
"kind": 3422,
"pubkey": "<arbiter_pubkey>",
"created_at": 1700000400,
"tags": [
["e", "<acceptance_event_id>", "wss://relay.example.com", "accepted_challenge"],
["timing_relay", "wss://relay.example.com"],
["game", "sanki"],
["p", "<alice_pubkey>", "wss://relay.example.com", "player"],
["p", "<bob_pubkey>", "wss://relay.example.com", "player"],
["seat", "<alice_pubkey>", "first"],
["seat", "<bob_pubkey>", "second"],
["variant", "<alice_pubkey>", "chess"],
["variant", "<bob_pubkey>", "ogi"]
],
"content": "<FEEN string encoding Alice's chess setup and Bob's ogi setup on a shared 8×8 board; see Position Encoding — Sanki for the concrete encoding>",
"id": "...",
"sig": "..."
}
The content carries a FEEN representation of the multi-variant initial position. FEEN’s three-field structure (piece-placement, hands, style-turn) accommodates Sanki’s defining property: two players using different variants on a shared board (the exact profile is the rule system’s supporting document, Position Encoding — Sanki).
Example 4 — Matchmade session (Pairing founding, arbiter-assigned seats)
The session was founded through the matchmaking pool: the arbiter references the canonical Pairing (kind 3419) with the pairing marker instead of an Accepted Challenge, and — because the matchmaking path carries no seat — assigns the seats itself (here, a random draw gave Alice second and Bob first):
{
"kind": 3422,
"pubkey": "<arbiter_pubkey>",
"created_at": 1700000400,
"tags": [
["e", "<pairing_event_id>", "wss://relay.example.com", "pairing"],
["timing_relay", "wss://relay.example.com"],
["game", "sanki"],
["p", "<alice_pubkey>", "wss://relay.example.com", "player"],
["p", "<bob_pubkey>", "wss://relay.example.com", "player"],
["seat", "<alice_pubkey>", "second"],
["seat", "<bob_pubkey>", "first"],
["variant", "<alice_pubkey>", "ogi"],
["variant", "<bob_pubkey>", "ogi"]
],
"content": "<FEEN string for the ōgi-vs-ōgi starting position>",
"id": "...",
"sig": "..."
}
Everything downstream of this event is identical to a directed session. The players’ variants are those resolved by the Pairing; the seats are the arbiter’s draw, authoritative from this event (no founding seat exists to mirror).
Example 5 — Rematch session (Rematch Offer founding, swapped seats)
The two players of a concluded session (Example 3, where Alice held first/chess and Bob held second/ogi) each published a Rematch Offer (kind 3430). The arbiter references both Offers with the rematch_offer marker — the one path with two founding references — and founds the rematch with the seats swapped (Bob now first, Alice now second), reusing the concluded session’s game and per-player variants:
{
"kind": 3422,
"pubkey": "<arbiter_pubkey>",
"created_at": 1700010100,
"tags": [
["e", "<offer_alice_id>", "wss://relay.example.com", "rematch_offer"],
["e", "<offer_bob_id>", "wss://relay.example.com", "rematch_offer"],
["timing_relay", "wss://relay.example.com"],
["game", "sanki"],
["p", "<alice_pubkey>", "wss://relay.example.com", "player"],
["p", "<bob_pubkey>", "wss://relay.example.com", "player"],
["seat", "<bob_pubkey>", "first"],
["seat", "<alice_pubkey>", "second"],
["variant", "<alice_pubkey>", "chess"],
["variant", "<bob_pubkey>", "ogi"]
],
"content": "<FEEN string for the swapped-seat starting position>",
"id": "...",
"sig": "..."
}
Everything downstream of this event is identical to a directed session. The game and per-player variants are inherited from the concluded session; the seats are its own, swapped; and the slot is (rematch, <concluded_session_id>), so this rematch is canonical however many Offers the two players published (§Idempotence and race resolution; kind 3430 §Race conditions).
Client guidelines
Clients implementing kind: 3422 SHOULD:
- For players’ clients: do NOT allow the user to submit a Ply (kind
3423) until t₀ is established (see §Canonical session start). This requires a conforming Game Session with canonical timing — in attested mode, the Session Start Attestation from the designated timestamper; in self-timed mode, acceptance by a designated timing relay. Display distinct indicators for the two pre-playable states:- Awaiting Game Session (after the founding — Accepted Challenge or canonical Pairing — no Game Session yet): “waiting for arbiter to start the session”.
- Awaiting authoritative timing (Game Session received but not yet canonically timed — in attested mode, no Session Start Attestation yet): “initialized; waiting for authoritative timing”.
- For arbiter implementations: after observing a valid founding referencing this arbiter — an Accepted Challenge (kind
3421) for a directed session, a canonical Pairing (kind3419) for a matchmade session, or a matched pair of Rematch Offers (kind3430, both referencing a concluded session this arbiter ruled) for a rematch — validate the session parameters (game, variant, time control, etc.). For a matchmaking founding, also assign the two players opposite seats (typically at random); for a rematch founding, swap the concluded session’s seats and reuse its game, variants, and time control (§Rematch founding). If accepted, publish the Game Session within a reasonable time; if the parameters cannot be honored, the arbiter SHOULD NOT publish a Game Session (effectively declining the session by silence). - For all clients: parse the Game Session’s content per the variant’s encoding conventions to display the initial position correctly. Recognition of the encoding is by the declared
gameandvariant; common encodings include FEN, SFEN, and FEEN. - For rating verifiers and observers: a session without a conforming Game Session has no effect on ratings (no Ply is valid, no contributing Adjudication can be produced).
Handling a silent arbiter
If the designated arbiter never publishes a Game Session, the session remains in a “declared but not initialized” state indefinitely. This is a recognized but unrecoverable state at the protocol level: there is no built-in timeout, no fallback arbiter, and no void mechanism. The session is simply dormant — it has no effect on ratings, no Ply can be valid, and no Adjudication will be produced.
Players SHOULD treat the absence of a Game Session within a clock-reasonable interval (typically minutes to hours, depending on the arbiter’s published service level) as a strong signal that the arbiter has declined the session. To formally close the dormant state, the founders’ clients SHOULD display the session as “declined by arbiter” and offer the players the option to start a fresh session (a new Direct Challenge), possibly with a different arbiter. The dormant Acceptance event remains on Nostr indefinitely but carries no protocol-level consequence.
This behavior is consistent with the principle that the arbiter is a designated trusted party (§Security considerations). The principal mitigation against silent arbiters is selection: players SHOULD only accept challenges whose designated arbiter they trust to be operational.
Handling a silent timestamper (attested mode)
This applies in attested mode. After the Game Session has been published, the designated timestamper is expected to publish the Session Start Attestation. If they never do, t₀ is never established and the session remains in the “initialized but untimed” state indefinitely. This is structurally analogous to a silent arbiter at the Game Session step: the session is dormant, no Ply is valid, no Adjudication can produce a contributing verdict for rating purposes (Plies are required for any non-trivial verdict context, and Plies require t₀).
Players SHOULD treat the absence of a Session Start Attestation within a clock-reasonable interval as a strong signal that the timestamper is unavailable. To formally close this dormant state, the founders’ clients SHOULD display the session as “timestamper unavailable” and offer the players the option to start a fresh session (a new Direct Challenge) with a different timestamper. The dormant Game Session remains on Nostr indefinitely but carries no protocol-level consequence.
As with the silent-arbiter case, the principal mitigation is selection: players SHOULD only accept challenges whose designated timestamper they trust to be operational. A timestamper that goes silent after play has begun is a distinct case with no specified recovery — a known limitation of attested mode; a recovery path must be specified before a deployment activates the mode.
Handling a silent timing relay (self-timed mode)
In self-timed mode t₀ is established as soon as one of the designated timing relays accepts the Game Session, so there is normally no separate wait. The analogue of a silent timestamper is a designated set none of whose relays will accept the Game Session — because they are unreachable, or reject the event (for example, a created_at their clocks deem in the past). Until a designated relay accepts it, the Game Session is pending and t₀ is undefined — exactly the initialized but untimed state above. Players SHOULD treat persistent rejection or unavailability as a signal that the designated relays are unusable, and offer to start a fresh session designating working relays. The mitigation is the same as for every designated party: designate relays you trust to be operational and correctly configured (Canonical Timing §Security).
Security considerations
-
Arbiter ratification: the Game Session is the arbiter’s explicit signal that they accept the session parameters. An arbiter who designs the session parameters but declines to ratify is silent at this step. Session founders SHOULD treat the absence of a Game Session within a reasonable timeout as an implicit decline.
-
Initial position integrity (randomized variants): for chess960, shogi960, and similar variants, the arbiter chooses the initial position. A biased arbiter could choose a position favorable to one player. This is a form of arbiter discretion not prevented at the protocol level. Verifiers can compare initial positions across many sessions to detect statistical bias.
-
Content trust: the
contentfield’s semantic interpretation depends on the variant. A malicious arbiter could publish a Game Session whose content is malformed or inconsistent with the declared variant. The consuming application’s validation logic SHOULD reject such events and treat the session as non-playable. -
Time accounting: the Game Session’s canonical timing defines t₀. An arbiter who delays publishing the Game Session defers the start of the players’ clocks. This is acceptable behavior (the arbiter may need time to validate and set up); but a malicious arbiter who delays excessively to disadvantage one player has discretionary timing power (see the trust model).
-
Trust model: the arbiter is a designated trusted third party — the signatures bind WHAT it may declare (§Semantic constraints); WHETHER and WHEN it acts is reputational. The fuller analysis belongs to the consuming application’s documentation (this suite’s application layer maintains one).
-
Delegated signatures (NIP-26): NIP-26-delegated signatures from the designated arbiter are NOT accepted as authoritative for the Game Session. Authoritative signatures MUST come directly from the session’s arbiter as designated by the founding context (in this suite, the
arbiter-markedptag in the Accepted Challenge or the Pairing; see §Signing party).
References
- NIP-01 — Basic protocol flow description
- Kind
3418— Open Challenge (matchmaking pool entry) - Kind
3419— Pairing (matchmaking founding event; referenced via thepairingmarker) - Kind
3420— Direct Challenge (declares the session parameters) - Kind
3421— Accepted Challenge (the directed session-founding event) - Kind
3430— Rematch Offer (the rematch session-founding designations; a mutually addressed pair, referenced via therematch_offermarker) - Kind
3423— Ply (cannot be valid before Game Session) - Kind
3425— Adjudication (verdict event) - Event Timestamp Attestation (kind
3410) — defines authoritative timing for the Game Session - Canonical Timing — timing modes, mode selection, canonical timing, the pending state, meta-resolution, t₀
- FEN — Forsyth-Edwards Notation — common encoding for chess variants
- FEEN v1.0.0 — Field Expression Encoding Notation, used by Sashité
