- Sashité for Developers
- Nostr
- NIPs
- Game Session
NIP-XX
Game Session
draft optional
Abstract
Defines kind: 3422, a regular event published by a player to found a session, and from which the session is playable. The Game Session carries the session’s two players, their seat assignments, their per-player variants, the game family, the rule system, the timing designation, and the initial position of play, and constitutes the signer’s explicit commitment to the session terms. The founding follows one of two paths that converge here — a Direct Challenge (kind 3420, fresh or rematch) accepted by the challenged player, or a matchmaking Pairing (kind 3419) founded by either of its players — 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.
On the directed path the Game Session is the acceptance: the challenged player accepts a Direct Challenge by founding the session on it, in one signed act.
Motivation
The lifecycle of a session has a transition between “proposed” (a challenge or a pairing exists) and “playable” (the players can submit Plies). Three concerns motivate making this transition one explicit, signed event:
-
A single anchor. Every later event of the session — each Ply (kind
3423), the Conclusion (kind3425), a rematch challenge (kind3420§Rematch challenge) — references the Game Session and nothing else. It is the event a consumer resolves first and the one by which a session is named. -
Self-containment for verifiers. The Game Session mirrors everything a verifier needs to replay the session from its events — players, seats, variants,
rules, the timing designation, the initial position — so that no consumer has to walk the founding chain to know what the session is. Whatever a Direct Challenge left open (a variant, the seat) is settled here by the accepting player. -
t₀. The Game Session defines the session-start moment used for time-control accounting: its optional
start_attag when present, otherwise its canonical timing (§Canonical session start). Before t₀, no Ply is valid; at t₀, the clock of the player seatedfirststarts running.
The suite designates no arbiter. What the Game Session states is not a third party’s ratification but a player’s commitment, and its correctness is not taken on trust: every term it carries is either mirrored from a founding event (and checked against it) or prescribed by the rule-system document named by rules (and checked against that). A Game Session that fails those checks founds nothing.
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 a player of the session, as its founding path determines:
- Directed path (the founding reference is a Direct Challenge, kind
3420, whether fresh or a rematch challenge): the signer MUST be the challenge’sopponent-marked pubkey — the challenged player. Publishing the Game Session is how the challenged player accepts; the challenger cannot found the session on their own challenge. - Matchmaking path (the founding reference is a Pairing, kind
3419): the signer MUST be one of the Pairing’s twoplayer-marked pubkeys. Either player may found the session; if both do, meta-resolution decides (§Idempotence and race resolution) — the two candidates are identical in content, since a Pairing leaves nothing open (kind3419§From Pairing to Game Session).
A Game Session signed by any other pubkey is invalid and MUST be ignored by verifiers.
Tags
Founding reference
| Tag | Cardinality | Value | Description |
|---|---|---|---|
e |
exactly one | event id, optional relay hint, marker | reference to the session’s founding event |
The Game Session MUST reference, via exactly one e tag, the founding event the session is founded on. The marker (fourth element) names the founding event type:
["e", "<challenge_event_id>", "<relay_hint>", "direct_challenge"]— the Direct Challenge (kind3420) the signer accepts: a fresh challenge, or a rematch challenge (kind3420§Rematch challenge).["e", "<pairing_event_id>", "<relay_hint>", "pairing"]— the canonical Pairing (kind3419) that matched the two players.
The founding event 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), the rules term, and establishes the two players, so the Game Session’s signing-party and player rules apply uniformly regardless of path; seat and variant sourcing differ by path (see §Seats and §Variants). 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}$ and MUST equal the game declared in the referenced founding event.
["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 the two players established by the referenced founding event — on the directed path the challenge’s signer and its opponent, on the matchmaking path the Pairing’s two player-marked pubkeys — and the signer is one of them. The p tags serve a dual purpose: semantic (identifying the players) AND structural (notification routing via standard Nostr #p filters, so the other player receives the Game Session without chained subscription logic).
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 path, seat declared: when the Direct Challenge carries a
seattag (always the case for a rematch challenge), the assignment MUST be consistent with it — the challenger holds the declared value, the challenged player the other value (first↔second). - Directed path, seat open: when the Direct Challenge carries no
seattag, the challenged player chooses the assignment here, constrained only by the requirement that the two players receive the two distinct values. The choice is authoritative from this event. - Matchmaking path: the assignment MUST equal the
seattags carried by the Pairing (kind3419§Match-terms tags), which the matchmaker draws.
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 when it declares one. On the directed path, a variant the Direct Challenge left open — the challenged player’s own, or the challenger’s when the challenger delegated it by omission (kind 3420 §Match-terms tags) — is supplied here by the challenged player, and is authoritative from this event. On the matchmaking path the Pairing fixes both (kind 3419 §Match-terms tags).
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 rule-system document named by rules interprets them (for sanki, each player’s variant — chess, ogi, xiongqi — determines their setup and movement rules on the shared board).
Rules
| Tag | Cardinality | Value | Description |
|---|---|---|---|
rules |
exactly one | digest + optional retrieval hint | the rule-system document of the session, mirrored from the founding |
The Game Session MUST carry exactly one rules tag, ["rules", "<digest>", "<hint>"] (kind 3420 §Match-terms tags), whose digest MUST equal the rules digest declared in the referenced founding event. The hint MAY differ from the founding’s (the signer MAY name a server of their own).
Mirroring rules is what makes the Game Session self-contained for verifiers on this axis too: a consumer holding only the Game Session knows which rule-system document to apply to every Ply of the session, and to its Conclusion, without resolving the founding chain. It is also the signer’s commitment to play under it: a client that cannot retrieve and execute the named document MUST NOT publish a Game Session for it (kind 3420 §Validation responsibility).
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 both founding paths.
Because t₀ is shared by both players, a start_at shifts both clocks identically and cannot create an asymmetric advantage. A session on either founding path MAY use it to schedule a start ahead of publication — including, on the directed path, to give the challenger notice before the clock of the player seated first starts running (§Client guidelines).
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; the position itself is the one the rule-system document named by rules prescribes for the session’s (game, variants) — the signer has no discretion over it, and a Game Session whose content is not the prescribed position is non-conforming (constraint 7). The suite supports no randomized-setup variant: no party is designated to draw one, and a rule-system document MUST prescribe a deterministic initial position for every pairing it defines (kind 3420 §Match-terms 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 and does NOT interpret it. Recognition of the encoding (from the declared game and variant), parsing, and the check against the rule system’s prescribed position 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. It also generalizes uniformly to game-specific setups (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 contains exactly one
etag, markeddirect_challengeorpairing, referencing a retrievable, signature-valid, conforming founding event (per that kind’s own constraints — kind3420§Semantic constraints, including §Rematch challenge for a rematch challenge; kind3419§Consent constraints, the Pairing being canonical). - The event’s
pubkey(the signer) is a player established by that founding event, as §Signing party requires for its path: theopponentof a Direct Challenge; eitherplayerof a Pairing. - The event contains exactly one
gametag, with a valid identifier per §Game, whose value equals thegamedeclared in the referenced founding event. - The event contains exactly two
ptags carrying the markerplayer, with distinct pubkeys, equal as a set to the two players established by the founding event, and including the signer. - 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). On the directed path the assignment is consistent with the Direct Challenge’sseattag when present (the challenger holds the declared value); when absent, any assignment satisfying the one-each rule conforms. On the matchmaking path the assignment equals the Pairing’sseattags. - The event contains exactly two
varianttags, one per player pubkey (per constraint 4). Eachvariant’s third element equals that player’s variant declared in the referenced founding event when it declares one (§Variants); a variant the Direct Challenge left open is any valid identifier. - The
contentfield is a non-empty string per §Content, with length at most 4096 bytes, and — for a consumer able to interpret it — encodes the initial position the rule-system document named byrulesprescribes for the session’s(game, variants). - 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. The event’s canonical timing is at or before the founding event’s deadline: the Direct Challenge’saccept_untilon the directed path, the Pairing’sfound_untilon the matchmaking path (kind3419§Lifecycle tags); a Game Session whose canonical timing exceeds it is non-conforming. - Exactly one
rulestag is present, whose digest (second element) matches^[0-9a-f]{64}$and equals therulesdigest of the referenced founding event (§Rules).
Constraints 1–6, 9 and 10 require cross-event validation against the referenced founding event (directed: the Direct Challenge, and for a rematch challenge the concluded Game Session and Conclusion it references; matchmade: the Pairing, kind 3419, and the two Open Challenges it references). Constraint 7 requires the rule-system document; a consumer that does not hold it checks the field’s form only and treats the session as unverified rather than conforming. Constraint 8 is checkable from the event alone. The Game Session’s created_at is informational and is not subject to any cross-event semantic constraint beyond constraint 8; 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. The slot a Game Session occupies is:
- on the directed path, the referenced Direct Challenge — except for a rematch challenge, whose slot is
(rematch, concluded session): one concluded session founds at most one rematch, however many rematch challenges its two players publish of it (kind3420§Rematch challenge), so Game Sessions founded on different rematch challenges of the same concluded session compete for the same slot; - on the matchmaking path, the referenced Pairing.
If several Game Session events exist for the same slot — a player publishing twice, both players of a Pairing founding it, both players’ rematch challenges being accepted — 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. A player SHOULD publish only one Game Session per slot, and SHOULD NOT publish one once they observe a canonical Game Session for it; a single player publishing several is non-conforming behavior and may indicate misbehavior or implementation bugs, whereas the two players of a Pairing racing is the expected cost of letting either found, resolved here. 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 (the other player’s client first — it is the acceptance it was waiting for — then downstream observers and rating verifiers). A Game Session that fails semantic constraints is structurally invalid and MUST be rejected: it founds nothing, and on the directed path it does not accept the challenge either, which stays open until its accept_until.
A session without a conforming Game Session remains in the “proposed 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 a founded but untimed state: distinct from proposed (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); on the matchmaking path, the Open Challenges constrain the pool 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.
Example 1 — Accepting a Direct Challenge (FEN encoding)
Alice challenged Bob (kind 3420, Example 1 there: game="chess", both variant="chess", Alice first, attested mode with Dave as timestamper). Bob accepts by founding the session: the Game Session is signed by Bob, references the challenge, mirrors its terms, and carries the standard chess starting position, which is what the named rule system prescribes:
{
"kind": 3422,
"pubkey": "<bob_pubkey>",
"created_at": 1700000400,
"tags": [
["e", "<challenge_event_id>", "wss://relay.example.com", "direct_challenge"],
["p", "<dave_pubkey>", "wss://relay.example.com", "timestamper"],
["game", "chess"],
["rules", "3f6d1a0c9e4b2a7d5c8e1f0a9b3c7d2e4f6a8b0c1d3e5f7a9b2c4d6e8f0a1b3c", "https://blobs.example.com"],
["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 — Accepting a challenge that left terms open (FEEN encoding)
Bob challenged Alice in the sanki context, self-timed, fixing neither variant nor the seat (kind 3420, Example 3 there). Alice accepts, supplying both variants (she plays chess, gives Bob ogi — the delegation Bob consented to) and choosing the seats (Bob first), and carries the multi-variant starting position the rule system prescribes for (sanki, chess, ogi):
{
"kind": 3422,
"pubkey": "<alice_pubkey>",
"created_at": 1700000400,
"tags": [
["e", "<challenge_event_id>", "wss://relay.example.com", "direct_challenge"],
["timing_relay", "wss://relay.example.com"],
["game", "sanki"],
["rules", "3f6d1a0c9e4b2a7d5c8e1f0a9b3c7d2e4f6a8b0c1d3e5f7a9b2c4d6e8f0a1b3c", "https://blobs.example.com"],
["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 encoding Bob's ogi setup and Alice's chess 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). A verifier holding the rule-system document checks that this is indeed the prescribed position for these variants and seats.
Example 3 — Matchmade session (Pairing founding)
The session was founded through the matchmaking pool: the canonical Pairing (kind 3419) matched Alice and Bob, fixed both variants (ogi), and drew the seats (Bob first). Either player may found; here Alice does, referencing the Pairing with the pairing marker and mirroring its seats:
{
"kind": 3422,
"pubkey": "<alice_pubkey>",
"created_at": 1700000400,
"tags": [
["e", "<pairing_event_id>", "wss://relay.example.com", "pairing"],
["timing_relay", "wss://relay.example.com"],
["game", "sanki"],
["rules", "3f6d1a0c9e4b2a7d5c8e1f0a9b3c7d2e4f6a8b0c1d3e5f7a9b2c4d6e8f0a1b3c", "https://blobs.example.com"],
["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": "..."
}
Had Bob published his own Game Session for the Pairing at the same time, it would be identical in every tag and in content; the one with the smaller canonical timing (then the smaller id) is canonical, the other ignored. Everything downstream of this event is identical to a directed session.
Example 4 — Rematch (accepting a rematch challenge)
Alice proposed the rematch of a concluded session (kind 3420, Example 6 there: seats swapped, every other term inherited). Bob accepts exactly as he would any Direct Challenge; the slot this Game Session occupies is (rematch, <concluded_session_id>):
{
"kind": 3422,
"pubkey": "<bob_pubkey>",
"created_at": 1700009100,
"tags": [
["e", "<rematch_challenge_id>", "wss://relay.example.com", "direct_challenge"],
["timing_relay", "wss://relay.example.com"],
["game", "sanki"],
["rules", "3f6d1a0c9e4b2a7d5c8e1f0a9b3c7d2e4f6a8b0c1d3e5f7a9b2c4d6e8f0a1b3c", "https://blobs.example.com"],
["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>", "ogi"],
["variant", "<bob_pubkey>", "chess"]
],
"content": "<FEEN string for the swapped-seat starting position>",
"id": "...",
"sig": "..."
}
Nothing here is Bob’s choice: the rematch challenge fixed the seats and both variants, and the content is the position the rule system prescribes for them.
Client guidelines
Clients implementing kind: 3422 SHOULD:
- For the accepting player’s client (directed path): before publishing, retrieve and verify the rule-system document, compute the prescribed initial position, mirror every term the challenge fixed, and supply — with the user’s input or a deterministic default — every term it left open. Consider carrying a
start_ata short interval ahead when the challenge is old, so that the challenger’s client has time to notice the acceptance before the clock of the player seatedfirststarts running. - For the founding player’s client (matchmaking path): publish the Game Session as soon as the canonical Pairing is observed; the content is fully determined, so no user input is needed. Do not publish if a canonical Game Session for the Pairing is already observed.
- 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 acceptance / founding (a challenge or a canonical Pairing exists, no Game Session yet).
- Awaiting authoritative timing (Game Session received but not yet canonically timed — in attested mode, no Session Start Attestation yet): “founded; waiting for authoritative timing”.
- For all clients: validate a received Game Session against the semantic constraints — including, when the rule-system document is held, the prescribed initial position — before treating the session as founded; parse the content per the variant’s encoding conventions to display the initial position. 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 Conclusion can be produced).
Handling an unaccepted challenge or an unfounded Pairing
If the challenged player never publishes a Game Session, the Direct Challenge expires at its accept_until without effect (kind 3420 §Security considerations, Decline by silence). If neither player of a canonical Pairing founds the session before the Pairing’s deadline, the Pairing lapses likewise (kind 3419 §Lifecycle tags), and the players re-enter the pool. In both cases the dormant events remain on Nostr indefinitely but carry no protocol-level consequence; there is no void mechanism and none is needed, since no clock has started.
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 “founded but untimed” state indefinitely: the session is dormant, no Ply is valid, no Conclusion 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 players’ 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.
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 founded 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
-
Acceptance is commitment: on the directed path the Game Session is the challenged player’s signed acceptance of every term of the challenge — those the challenge fixed and those the acceptor supplied — and of the rule system. There is no later ratification step; a player who publishes it is bound from t₀.
-
No discretion in the founding: every term of a Game Session is either mirrored (and checked against the founding event) or prescribed by the rule-system document (and checked against it); the only choices the protocol leaves to the signer are the ones the Direct Challenge explicitly delegated (an open variant, an open seat). A Game Session that departs from this is non-conforming and founds nothing — the signer cannot improve their position by publishing one, only fail to accept.
-
Content trust: the
contentfield is checked, not trusted: a consumer holding the rule-system document verifies it is the prescribed initial position. A consumer without the document can only check its form and MUST treat the session as unverified rather than founded (constraint 7). -
Time accounting: the Game Session’s canonical timing defines t₀. On the directed path the acceptor chooses when to accept, and therefore when the clock of the player seated
firststarts; when the acceptor holdssecond, the challenger’s clock starts at an instant the challenger did not pick. This is symmetric with the challenger having chosen theaccept_untilwindow, andstart_atlets the acceptor give notice (§Scheduled start). On the matchmaking path either player may found as soon as the Pairing is canonical, so neither controls t₀ unilaterally. -
Trust model: no party’s honesty is presumed on the rules axis — the Game Session’s correctness is verifiable by anyone holding the document. What remains trusted is the timing authority, whose designation the Game Session mirrors. The fuller analysis belongs to the consuming application’s documentation (this suite’s application layer maintains one).
-
Delegated signatures (NIP-26): a Game Session MUST be signed directly by the player’s key; NIP-26-delegated signatures are NOT accepted. Remote signing (NIP-46) is not delegation and is fine (see kind
3420§Security considerations).
References
- NIP-01 — Basic protocol flow description
- Kind
3418— Open Challenge (matchmaking pool entry) - Kind
3419— Pairing (matchmaking founding event; referenced via thepairingmarker; carries the seats) - Kind
3420— Direct Challenge (the directed founding event, fresh or rematch; referenced via thedirect_challengemarker) - Kind
3423— Ply (cannot be valid before Game Session) - Kind
3425— Conclusion (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é
