Rule System — Sanki
===================

`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 **rule-system document** that the `rules` term names for the `sanki` game — the content-addressed manifest a Direct Challenge (kind `3420`), an Open Challenge (kind `3418`), a Pairing (kind `3419`), a Game Session (kind `3422`) and a Composition (kind `3440`) refer to by its SHA-256 digest.

The NIPs deliberately leave the *format* of the rule-system document to the application layer, exactly as they leave the encodings of positions and moves. This document is that application-layer specification for Sashité's Sanki game. It is the mechanised form of the *pluggable-arbiter clause* of the rules' playing principles — now that the suite has no arbiter ([ADR-0033](../../adr/adr-0033-arbiterless-sessions.md)), the clause reads: everything a publisher of a rule system may vary is in the manifest; everything it may not is in the kernel the manifest names.

## Scope

This document covers:

- what a Sanki rule-system document is made of — a **kernel identifier**, per-variant **movement tables** in [GGN](https://sashite.dev/specs/ggn/1.0.0/), and **parameters**;
- how the document is serialised, hashed, published and retrieved;
- what a consumer (a player's client, a verifier, a rating authority, a puzzle validator) does with it;
- how a revised rule system is introduced.

This document does NOT define:

- the GGN specification itself (normative reference: [GGN v1.0.0](https://sashite.dev/specs/ggn/1.0.0/));
- the **kernel** — the fixed set of rules the manifest parameterises (attack derivation, check marker, castling-rights and *en passant* recomposition, royal safety, uchifuzume, effect catalogue, terminations and their order, and the session-level natural-state function). The kernel is specified in [Kernel — Sanki](../../rules/kernel-sanki.md), identified by the `kernel` field below; this document only names it;
- the rules of the three Sanki variants as a reader learns them — those are the [rule pages](https://sashite.dev/rules/) — nor the cross-variant interactions; the manifest is their machine-readable projection, and the pages remain the human-readable one.

## What the document is

A Sanki rule-system document is the complete, machine-readable statement of the rules a session is played under. It answers, for every Ply of the session, the three questions the kernel asks the data: *what is mechanically possible* (GGN), *which values do the parameterised rules take* (parameters), and *which kernel is asking* (the kernel identifier). Given the document, the founding events and the session's Plies with their canonical timings, the natural state of the session (kind `3425`) is a pure function: two conforming implementations of the named kernel reach the same verdict bit for bit.

The document is **rule data, never rule code**. It contains no executable logic: a consumer that does not implement the named kernel cannot use it, and MUST say so rather than approximate.

## Structure

The document is a JSON object. Its top-level members:

| Member | Type | Required | Description |
|--------|------|----------|-------------|
| `format` | string | yes | `"sashite.sanki.rules/1"` — the version of this document's own structure |
| `game` | string | yes | the `game` identifier the document governs; `"sanki"` here |
| `kernel` | object | yes | the fixed rule kernel the document targets (§Kernel) |
| `variants` | object | yes | one entry per variant identifier the document defines (§Variants) |
| `pairings` | object | yes | the parameters that depend on the pair of variants in play (§Pairings) |
| `session` | object | yes | the session-wide parameters (§Session parameters) |
| `name` | string | no | a human-readable label (`"Sanki — Sashité reference rules"`) |
| `comment` | string | no | free text |

Unknown members MUST be rejected: a document is a contract, and a consumer must not silently ignore a term it does not understand.

### Kernel

```json
"kernel": {
  "id": "sashite.sanki.kernel/1",
  "digest": "<sha256 of the kernel specification document>",
  "conformance": "<sha256 of the conformance corpus the kernel is pinned to>",
  "reference": "<sha256 of a reference implementation build (optional)>"
}
```

- `id` names the kernel — the fixed set of rules (the *atoms*) the data below parameterises. A consumer that does not implement this exact identifier MUST refuse the document.
- `digest` is the SHA-256 of the kernel's specification document **as published** (the bytes a consumer retrieves), so that the identifier cannot drift: two parties agreeing on `id` agree on the text.
- `conformance` is the SHA-256 of the conformance corpus — one JSON object merging the corpus categories (`legality`, `selection`, `time`, `scenarios`, `clock`, `puzzle`), serialised with sorted keys and no insignificant whitespace ([Kernel — Sanki](../../rules/kernel-sanki.md) §Conformance). An implementation claiming the kernel MUST pass this corpus; publishing its digest in the manifest makes "the same kernel" a checkable claim rather than a name. **The corpus rules over the prose**: where the kernel's text admits two readings, the corpus decides, and a case neither covers is a kernel defect repaired only by a new `id`.
- `reference` (optional) is the SHA-256 of a build of a reference implementation of the kernel. When present, a session's verdict is by definition that program's output on the session's events; consumers MAY run it instead of their own implementation, and MUST treat a divergence from it as their own defect.

### Variants

`variants` maps each variant identifier (`"chess"`, `"ogi"`, `"xiongqi"`) to its definition:

| Member | Type | Description |
|--------|------|-------------|
| `style` | string | the [SIN](https://sashite.dev/specs/sin/1.0.0/) letter of the variant's Style, uppercase (`"W"`, `"J"`, `"C"`); the second player's form is its lowercase |
| `pieces` | object | the variant's piece vocabulary: [PNN](https://sashite.dev/specs/pnn/1.0.0/) letter → `{ "name": "...", "royal": bool }`. The names are the **actor vocabulary** of Ply contents (*Move Encoding — Sanki* §Piece-name format): a promotion target and a dropped piece are named by them |
| `ggn` | string | the SHA-256 digest of the variant's GGN document — the complete pseudo-legal movement table of every piece of the variant, for both Sides, from every square, including drops (§GGN documents) |
| `initial` | object | the variant's initial setup, per Side: the FEEN piece-placement fragment of that Side's ranks (`"first"`, `"second"`), so that a pairing's initial position is assembled from two fragments. The setup MUST be **deterministic**: a Sanki rule-system document cannot describe a randomized-setup variant (chess960-style), since no party is designated to draw it ([ADR-0033](../../adr/adr-0033-arbiterless-sessions.md)) |
| `foot_soldier` | object | the parameters of the double-step recomposition: `"letter"`, the foot-soldier letter, and `"home_rank"` per Side (`{ "first": 2, "second": 7 }`); the marching direction is the kernel's (`first` up the ranks, `second` down) |
| `promotion` | object | per promoting letter: `"rank"` per Side and `"targets"`, the list of target letters (one target → automatic, `actor` forbidden in the Ply; several → `actor` required and naming one of them by its `pieces` name) |
| `drops` | object | the drop rules: `"letters"`, the letters droppable from the hand (empty for a variant without drops); `"nifu"`, the letters that may not be dropped on a file already holding an unpromoted piece of the same letter and side, whatever its state (`["F"]` for ōgi — a kernel rule, since GGN cannot deny the three states of a letter on one square); `"uchifuzume"`, the letters whose mating drop is forbidden (`["F"]` for ōgi, `[]` otherwise) |
| `capture` | object | the variant's **capture mutation**, applied when a piece of this variant is the *capturer*: `"side": "keep" \| "flip"`, `"demote": { "<letter>": "<letter>" }`, `"foreign": "keep" \| "<letter>"` (what a piece of another variant becomes on capture — kept as is, or replaced by the given letter of the capturer's variant) |

The hand canonicalisation (no transient marker on a held piece), the check marker, the castling-rights recomposition and the royal-safety filter are **not** parameters: they are kernel rules, identical under every document that names this kernel. Castling geometry in particular is not declared — the kernel derives it from the GGN itself (the royal's possibility whose `must` references a rook-class piece marked `+`).

### Pairings

`pairings` carries what depends on the two variants in play, keyed by the unordered pair in alphabetical order (`"chess+chess"`, `"chess+ogi"`, …, six keys for three variants):

| Member | Type | Description |
|--------|------|-------------|
| `dead_positions` | array | the **dead-position predicates** of the kernel's catalogue the pairing enables, by name (*Kernel — Sanki* §I.8): `lone_royals`, `royal_and_bishop_vs_royal`, `royal_and_knight_vs_royal`, `royals_and_same_colour_bishops` (a colour predicate, hence a named rule rather than a material multiset), `royals_only_with_inert_hands`. `[]` for a pairing where none is detected (pure ōgi). Sanki: the four chess predicates for `chess+chess`, `lone_royals` for `xiongqi+xiongqi`, `royals_only_with_inert_hands` for the three mixed pairings |

### Session parameters

```json
"session": {
  "stalemate": "draw",
  "repetition": 3,
  "move_limit": 100,
  "move_cap": 600,
  "irreversible": { "capture": true, "foot_soldier_move": true, "drop": false },
  "candidate_cap": 8
}
```

- `stalemate`: the outcome of a stalemate — `"draw"` (Sanki) or `"loss"` (shōgi- and xiàngqí-style rule systems);
- `repetition`: the number of occurrences of a canonical position that ends the session;
- `move_limit`: the half-move count without an irreversible move that ends the session (`movelimit`);
- `move_cap`: the absolute half-move cap (`movecap`);
- `irreversible`: which events reset the `move_limit` counter;
- `candidate_cap`: the cap `K` of the slot selection rule (*Move Encoding — Sanki* §Bounding a slot's candidates) — carried by the document rather than by the deployment, so that every consumer of a session derives the same canonical chain.

Time accounting is **not** in the manifest: the time control is a term of the founding (`time_control`), and its arithmetic is the kernel's (*Time Accounting — Sanki*).

### GGN documents

Each variant's `ggn` digest names a separate document: a GGN v1.0.0 object enumerating, for every piece of the variant (both Sides, every state the variant uses), every source square (and the hand, for droppable pieces), every reachable destination, and the LCN pre-conditions of each possibility. It is **exhaustive** (GGN §13.2): a movement absent from the document is not possible. The documents are sizeable — about 1.2 MB per variant in plain JSON (22–24 QPI keys, some 25 000 possibilities each), about 80 KB gzipped — which is why they are referenced by digest rather than embedded, and why a consumer caches them by digest.

The reference documents are **generated** from the reference engine (`sashite-sanki-engine`, module `ggn`; `cargo run --release --example rules_manifest` writes them with the manifest and every digest) and verified against it by differential testing (`tests/ggn_differential.rs`: on positions of random play across the nine pairings, the engine's legal set equals the GGN-possible moves the kernel filters admit, and its attack relation equals the GGN-derived one); they are the single source of the geometry for the engine, the client and any third-party implementation alike. Generation is deterministic (sorted keys, no insignificant whitespace), so regenerating a document reproduces its digest.

## Serialisation and digest

The document is serialised as UTF-8 JSON. Its digest is the SHA-256 of the exact bytes published, expressed as 64 lowercase hexadecimal characters; there is no canonicalisation step — the published bytes are the document. Publishers SHOULD emit deterministic JSON (sorted keys, no insignificant whitespace) so that regenerating a document yields the same digest. The reference manifest is built by the engine's `rules` module from its own constants and tables (`rules::manifest`), and its tests pin the two to each other — every named promotion target and droppable letter resolves through the actor vocabulary, every pairing's initial fragments assemble into a canonical playable position, the thresholds are the engine's.

The same applies to each GGN document referenced from the manifest.

## Publication and retrieval

The manifest and its GGN documents are **content-addressed blobs**. The reference deployment publishes them on a [Blossom](https://github.com/hzrd149/blossom) server, where a blob is retrieved by its SHA-256 (`GET /<sha256>`); the `rules` tag's third element carries that server's URL as a retrieval hint. Any HTTP location a consumer can map a digest to is acceptable; the hint is a convenience and never an identity, and a consumer MUST verify the digest of what it retrieves.

Availability is a responsibility: a rule-system document MUST remain retrievable for as long as a session, a rating pool or a Composition refers to it. Sashité keeps every document it ever published; rating authorities and validators SHOULD mirror the documents of the pools and puzzles they serve.

## What a consumer does with it

- **A player's client**, before founding or accepting a session under a given `rules`: retrieve the manifest, check the `kernel.id` is one it implements, retrieve the GGN documents of the two variants, and only then sign. During play, generate and validate moves from the GGN and the parameters, through its kernel implementation.
- **A player concluding the session, and any verifier** ([ADR-0033](../../adr/adr-0033-arbiterless-sessions.md)): compute the natural state of the session under the document named by the Game Session, and nothing else; a Conclusion (kind `3425`) whose verdict differs from that computation is invalid.
- **The matchmaker**: pair only Open Challenges whose `rules` digests are equal (kind `3419` §Consent constraints).
- **A rating authority**: partition its pools by digest (*Rating Specification — Elo* / *Glicko-2* §Rating pool).
- **A puzzle validator**: walk a certificate under the document the Composition names (kind `3441`).

## Revising a rule system

A rule system is never edited in place. A rebalancing, a fix, a new variant, a different interpretation: each is a new manifest with a new digest. A deployment that adopts it (its client's default founding terms, its matchmaker's accepted terms) names the new digest from then on; sessions already founded keep theirs, rematches inherit the concluded session's (kind `3420`, a rematch challenge), and rating pools are partitioned accordingly. Nothing about an existing session changes, which is what allows a session to be replayed years later from its public events alone.

A revision that changes a **kernel rule** — not a parameter, not a table — is a new `kernel.id`, and a consumer that does not implement it refuses the manifest. This is the boundary the (formerly) pluggable-arbiter clause draws: a rule-system publisher may vary any geometry, any table, any threshold; it may not silently change what the kernel does with them.

## References

- Kind `3420` — Direct Challenge, §Match-terms tags (the `rules` term)
- Kind `3418` — Open Challenge; kind `3419` — Pairing (`rules` as a matching term)
- Kind `3422` — Game Session, §Rules (ratification and mirroring)
- Kind `3430` — Rematch Offer, §Inherited terms
- Kind `3440` — Composition; kind `3441` — Validation
- [GGN v1.0.0](https://sashite.dev/specs/ggn/1.0.0/) — Geometry Gameplay Notation
- [Sashité rule pages](https://sashite.dev/rules/) — the human-readable rules of the three variants
- [Kernel — Sanki](../../rules/kernel-sanki.md) — the kernel this manifest parameterises
- [ADR-0033](../../adr/adr-0033-arbiterless-sessions.md) — arbiter-less sessions: why verdicts are verified, not believed
- [Blossom](https://github.com/hzrd149/blossom) — content-addressed blob storage on Nostr
