- Sashité for Developers
- Rules
- Sanki
- Kernel
Kernel — Sanki (sashite.sanki.kernel/1)
This page renders the kernel specification whose SHA-256 the Sanki rule-system manifest carries as
kernel.digest. The normative bytes are the blobcd7d2eb232ef7240e0275c79458a9c9dfec08d0c2da824ef68738003c48cdbf5; this page is a courtesy rendering of the same text.
This document specifies the kernel that the Sanki rule-system document names — the fixed rules that the document’s data (the GGN movement tables and the parameters of Rule System — Sanki) parameterise. It is the document whose SHA-256 the manifest carries as kernel.digest, and it is written to be the load-bearing half of an executable contract: everything a session’s verdict depends on is either stated here, incorporated here by digest, or carried by the rule-system document; nothing is left to an arbiter, because under ADR-0033 there is none.
The kernel has two layers. The position kernel (Part I) is the per-ply transition legality → apply → canonicalize: given a position, a ply and the rule-system data, it either rejects the ply or yields the next canonical position. The session kernel (Part II) is the natural-state function: given the public events of a session — its founding, its Plies with their canonical timings, and the invocation that ends it — it yields the verdict. The position kernel is a pure function of a position and a ply; the session kernel is a pure function of events. Neither has any other input.
Identification and contract
| Field of the manifest | Value | Meaning |
|---|---|---|
kernel.id |
sashite.sanki.kernel/1 |
the name of this set of rules; a consumer that does not implement it MUST refuse the manifest |
kernel.digest |
the SHA-256 of this document as published | pins the text — two parties agreeing on the identifier agree on these words |
kernel.conformance |
the SHA-256 of the conformance corpus (§Conformance) | pins the executable form of the same rules |
kernel.reference (optional) |
the SHA-256 of a reference implementation | pins one program whose output is the verdict |
Incorporated documents. Four documents of the Sashité suite are part of this kernel by incorporation; their digests at the time of publication are listed here, so that kernel.digest transitively pins them. They are normative for what they specify; this document restates their rules only as far as needed to name them.
| Document | Specifies | SHA-256 at publication |
|---|---|---|
| Time Accounting — Sanki | the clock arithmetic (§II.4) | 171f71cbccf471c5952fef31688a8bf58d6d159bd2abf59f5efeb255e450c65b |
| Move Encoding — Sanki | the ply content, the slot structure, the selection rule and the cap K (§II.2–II.3) |
ed49e170861d0c4fadc9c9789579479d7250e5ef9653227d5a7fb998ef4bf756 |
| Statuses — Sanki | the status vocabulary, the implicit conventions and the verdict resolution (§II.5–II.6) | 82eb9648a58fcc511198758fe39d1cdcb00c292401deece80f6394c9247380e7 |
| GGN v1.0.0 | the movement-table format the position kernel reads | c054ef693a1f6c3b367d94390c512e581730a39ac85c0a8f40bba921cce175b4 |
The corpus rules over the prose. An implementation that passes the conformance corpus is a conforming implementation of this kernel. Where the prose of this document or of an incorporated document admits two readings and the corpus decides between them, the corpus is right. Where the corpus is silent and two conforming implementations diverge, the kernel is defective: the defect is repaired by publishing a new kernel identifier with a corpus that covers the case — never by editing this document or its corpus under the same identifier, since sessions founded under it have signed these exact digests.
No discretion. Nothing in this kernel is “MAY” for the party computing a verdict. The one place a rule-system document could leave a choice open — a non-deterministic initial position — is excluded: a Sanki rule-system document MUST prescribe a deterministic initial position for every pairing it defines (Rule System — Sanki §Variants).
Part I — The position kernel
I.1 Inputs and output
The position kernel takes:
- a position in canonical FEEN form — board, two hands, the active side and the two styles;
- a ply in the Sanki content form
[from, to, actor](Move Encoding — Sanki §Content format):froma square ornull(a drop),toa square,actora piece name ornull; - the rule-system data: the GGN document of each side’s variant and the manifest’s parameters.
It returns either Illegal (with a reason, informational) or the next canonical position together with one bit, irreversible, consumed by the session kernel’s move-limit counter (§I.8).
The pipeline is fixed: resolve (§I.4) → uchifuzume (§I.5) → apply (§I.6) → capture mutation and hand canonicalisation (§I.6) → canonicalize (§I.7). A rejection at any stage leaves the position untouched.
I.2 Data the kernel reads
The GGN documents enumerate, for every QPI the variant uses, every source (a square, or the hand *) and every destination, the possibilities — each a set of LCN pre-conditions (must/deny) on named squares. A possibility is satisfied in a position when every one of its conditions holds. A movement absent from the documents is not possible (GGN §13.2, exhaustiveness).
Condition matching (normative). "empty" is satisfied by an unoccupied square; "enemy" by a square holding a piece whose Side differs from the Side of the QPI under which the possibility is listed (GGN §7.2); a QPI value by a piece whose style letter (cased by its Side), state marker, letter and terminal marker all equal the value’s — exact token equality, the EPIN derivation marker ' (not part of QPI) being ignored. A must object is a conjunction, a deny object a disjunction (GGN §7.3).
The manifest’s parameters the position kernel reads are listed in Appendix B. Everything else in this Part is a fixed rule.
I.3 Derived relations
A1 — Attack. A square s is attacked by side X in a position if and only if some piece of X has, from its square, a satisfied possibility with destination s whose destination condition is enemy, the possibility being evaluated as though s were occupied by an enemy piece. The relation is derived from the GGN alone; it therefore excludes foot-soldier pushes, the xiongqi General’s step to an empty square and castling (destination empty), and includes the General’s Chariot-range capture. Every rule below that mentions attack, check or safety is phrased in this relation.
In check. A side is in check when the square of a piece of that side bearing the terminal marker ^ is attacked by the other side.
Pseudo-legal set. The pseudo-legal moves of the active side are the satisfied possibilities of its pieces (board and hand). Legal set. The pseudo-legal moves that survive §I.4 (royal safety) and §I.5 (uchifuzume).
I.4 Resolution of a ply (A8, A6, E)
A ply [from, to, actor] is admitted if and only if all of the following hold; otherwise it is rejected with the first failing condition as reason:
- Possibility.
fromis a square holding a piece of the active side, ornullwithactornaming a piece the active side holds in hand (hand availability, a conservation fact read on the FEEN hands); and some possibility of that piece’s QPI from that source totois satisfied. - Actor.
actorobeys the promotion table (parameter): for a board move that promotes — the piece is a promoting letter of its variant andtolies on its promotion rank,fromnot already on it —actorMUST name one of the table’s targets when the table lists several, and MUST benullwhen it lists one (automatic promotion); for every other board moveactorMUST benull; for a dropactornames the dropped piece. - Royal safety (A6). The canonical position resulting from the ply (after §I.6–I.7) does not leave the mover in check. This single condition covers pins, moving into check, failing to parry, and castling out of, through or into check — the last through §I.7 A5, which withholds
+from a rook whose castling would cross an attacked square.
A8 — Rejection is not a sanction. A rejected ply changes nothing: the position, the hands, the clocks and the turn are as before. The session kernel treats it as a non-event (§II.3). There is no illegalmove termination.
Effect recognition (E). An admitted ply is one of exactly five effects, recognised on the form of the satisfied possibility, never on an annotation:
| Effect | Recognised when | Applied as |
|---|---|---|
| E1 move / ordinary capture | the default | the piece leaves from and occupies to; an occupant of to is captured |
| E2 castling | the possibility’s must names a square holding a rook-class QPI of the mover marked + (the only possibility shape of this form in the whole GGN) |
E1 for the royal; that rook is relocated to the square of the straight segment from→to adjacent to to |
| E3 en passant | the possibility’s must names a square holding an enemy foot-soldier marked - |
E1 for the mover; that foot-soldier is captured although it does not stand on to |
| E4 promotion | condition 2 above identifies a promotion | E1, then the piece at to is replaced by the target letter (from actor, or the single target) in the mover’s case, without state marker |
| E5 drop | from is null |
the hand token is placed on to exactly as held (no marker, no capture, no promotion), and removed from the hand |
The turn passes to the other side after every effect (a protocol invariant, not a rule of this kernel).
I.5 Drop restrictions (A7)
Two restrictions apply to drops (E5) above the GGN’s own conditions (an empty destination; for a letter the document keeps off a rank, its omission there):
- Nifu (parameter
drops.nifu,["F"]for ōgi): a piece whose letter is listed may not be dropped on a file where the same side already has an unpromoted piece of that letter, whatever its state marker (F,+For-Falike). This rule is the kernel’s and not the document’s because GGN matches a QPI value exactly and adenyobject admits one value per square, so the three states cannot be denied together (GGN §7.3, Limitation). - Uchifuzume (parameter
drops.uchifuzume,["F"]for ōgi): a drop of a listed letter is rejected when the resulting position would be checkmate for the opponent (§I.8, evaluated one ply ahead). A drop that merely gives check is admitted.
Both lists may be empty.
I.6 Capture mutation and hand canonicalisation
When an effect captures (E1 with an occupant, E3), the captured token goes to the capturer’s hand after the variant’s capture mutation (parameter, keyed by the capturer’s variant):
- A2′ — Hand canonicalisation (fixed rule). A token in hand never bears a state marker:
+and-are stripped. This is a recomposition of the position, not an action of the move; it is why PMN writese5~f6bare. - Demotion (parameter). The letter is replaced per the
demotetable (T → Fin ōgi; empty elsewhere). - Side and foreign pieces (parameter).
side: "flip"puts the token in the capturer’s case (ōgi capturing ōgi);side: "keep"leaves it in the victim’s case, an inert token the capturer can never drop (chess and xiongqi capturing anything).foreignsays what a piece of another variant becomes: kept as is, or replaced by a letter of the capturer’s variant (ōgi captures chess or xiongqi pieces asF).
A token is droppable by a side if and only if its case is that side’s (§I.2: an inert token has no * entry in the GGN and so no possibility).
I.7 Canonicalisation
After apply, four recompositions are performed on the resulting position, in this order, each a pure function of the board (and, for A5, of the ply’s effect). They are mutually independent — none reads a marker another writes — and together they define position identity: two positions are the same for repetition (§I.8) if and only if their canonical FEEN strings are equal, markers and hands included.
| Recomposition | Rule | |
|---|---|---|
| A2 | check marker | every piece marked ^ bears - if and only if its square is attacked (A1) by the other side; every other - on a ^ piece is removed |
| A3 | double-step marker (parameter: foot-soldier letter and home rank per side) | + is removed from every foot-soldier not on its home rank; + is never added |
| A4 | en passant window (parameter: foot-soldier letter, marching direction) | - is removed from every foot-soldier; then - is set on the foot-soldier that has just made a double step if and only if an en passant capture of it is legal for the side now to move — a capturer stands in position and the capture passes A6 — so that the marker is present exactly when the capture is possible. This is the kernel’s only arrow from canonicalisation back into legality (a one-ply look-ahead on a tentative position). |
| A5 | castling rights (no parameter — derived from the GGN) | a rook-class piece marked R (bare) stays bare forever. A rook marked +R or -R becomes bare R when the ply moved its royal (every marked rook of that side) or moved that rook, or when any piece arrives on the rook’s square by capture. Otherwise it is marked +R if the royal’s castling possibility that references it (the possibility whose must names its square with value +R) would be satisfied with, in addition, no square of the royal’s transit — the straight segment from→to, source excluded — attacked (A1) and the royal not in check; and -R if not. The transit is read on the geometry of the possibility; the manifest carries no castling vocabulary. |
The initial position of a pairing carries no - markers and carries + only on the pieces the rule-system document lists (playing-principles §10, condition 4).
I.8 Terminal classification (A9)
After canonicalisation, the resulting position is classified for the side now to move. Facts: has a pseudo-legal move, has a legal move (§I.3), in check, and the four background conditions below. The classification order is fixed:
- if there is no legal move:
nomove(a draw) when there is not even a pseudo-legal move; elsecheckmate(the side to move loses) when in check; elsestalemate(outcome: parametersession.stalemate,drawfor Sanki); - otherwise, in this order:
insufficient(a draw) when one of the dead-position predicates the pairing enables holds (parameterpairings.<pair>.dead_positions, names from this fixed catalogue:lone_royals— only the two royals on the board;royal_and_bishop_vs_royalandroyal_and_knight_vs_royal— a lone royal against a royal and one minor, either orientation;royals_and_same_colour_bishops— royals and any number of bishops all standing on squares of one colour;royals_only_with_inert_hands— only the two royals on the board and no hand holding a token droppable by its holder);repetitionwhen the canonical position has now occurredsession.repetitiontimes (3) in the session, initial position included;movelimitwhensession.move_limit(100) half-moves have passed without an irreversible move;movecapwhen the session has reachedsession.move_cap(600) half-moves; - otherwise the session is ongoing.
Irreversible (parameter session.irreversible): a ply is irreversible when it captures, or when it is a board move of an unpromoted foot-soldier (read on the source piece, so a promotion counts); a drop and a castling are not. The move-limit counter resets on an irreversible ply and counts every other one.
A delivered checkmate takes precedence over any background draw the same ply would produce, by the order above.
Part II — The session kernel
II.1 Inputs and output
The session kernel takes the founding (the Game Session: players, seats, variants, rules, time_control, t₀), the session’s Plies with their canonical timings (Canonical Timing), and an invocation: the pubkey of the player ending the session and a cutoff timing. The protocol layer supplies the invocation — before ADR-0033 the Adjudication Request, under it the Conclusion event itself, whose signer and canonical timing are the invocation. Plies with canonical timing strictly after the cutoff, and Plies without canonical timing, are not inputs.
It returns a verdict: a status of the Statuses — Sanki vocabulary and a result split — 100/0, 50/50 or 0/100, seats being the axis. A conforming invocation always yields a verdict (§II.6).
II.2 t₀, slots and play order
t₀ is the Game Session’s start_at when present, otherwise its canonical timing. A Ply’s slot is (session, signer, step), step being the signer’s own move ordinal; the play order interleaves the two sequences by strict alternation, seat first before seat second within each step (Move Encoding — Sanki §Within-step ordering).
II.3 The canonical chain
Slots are consumed in the play order. At each slot the selection rule picks the canonical Ply among the slot’s candidates: the boundary T is the maximum canonical timing among the preceding half-moves (t₀ for the first), never rewound by a selected premove; candidates timed before T form the anterior window (premoves), the others the informed window (live moves); the canonical Ply is the latest legal anterior candidate among the K most recent, else the earliest legal informed candidate among the K earliest, else the slot is unfilled and the chain stops there. K = 8 (parameter of the deployment, identical for every consumer of a session — it is part of the rules document’s session object). Legality is the position kernel’s (§I.4–I.5); an illegal candidate is skipped in either window and is never a termination.
II.4 Clocks
Each selected Ply charges its mover elapsed = max(0, t(ply) − anchor), the anchor being the boundary T of its slot; the charge is spent per the session’s time_control under the rules of Time Accounting — Sanki (fixed bank / Fischer, quota periods, period transitions). A selected premove charges zero. If the mover’s clock flags on the charge, the session terminates timeout against the mover at that Ply (played-Ply timeout); the position is still the one the Ply produces.
II.5 Chain replay and termination
Starting from the pairing’s initial position, each selected Ply is applied by the position kernel; after each, the clocks are charged (§II.4) and then the position is classified (§I.8). The first Ply that terminates — by timeout or by any status of §I.8 — ends the session at that Ply; later slots are irrelevant.
II.6 Post-chain resolution
If the chain replays to an ongoing position — because a slot is unfilled, or because the cutoff cuts the candidates short — the invocation is resolved at the cutoff, in this order:
agreement(50/50): the last selected Ply carries thedrawflag and the invoker is its signer’s opponent;- abandonment
timeout: the player on move has let their clock, charged from the chain’s last anchor (t₀ for an empty chain) to the cutoff, expire — decisive against them; resignation: otherwise, decisive against the invoker — the residual reading of an invocation with no objective cause.
The order is normative: an invocation by the offeree while a draw offer stands is an acceptance; an invocation while the on-move player’s clock has expired is a win-on-time claim (or a loss on time if the invoker is that player); anything else is a resignation. A conforming invocation therefore always yields a verdict.
Which invocation. When several conforming invocations exist, the one with the smallest canonical timing (then the smallest event id) is the session’s; the verdict is a function of that cutoff. Under ADR-0033 this is the slot rule of the Conclusion event, and a Conclusion whose stated verdict differs from the kernel’s for its own cutoff is invalid.
Part III — Conformance
III.1 The corpus
The conformance corpus is the executable form of this kernel. It is published as one JSON object merging its categories under their names (legality, selection, time, scenarios, clock, puzzle), serialised with sorted keys and no insignificant whitespace; the SHA-256 of that document is kernel.conformance. It is composed of:
- legality vectors — position (FEEN) + ply → admitted with the next canonical FEEN and the irreversible bit, or rejected; they cover every effect E1–E5, every recomposition A2–A5 including the en passant look-ahead and the three castling refusals, nifu and uchifuzume, hand availability and inert tokens, promotion
actorerrors, and every cross-variant capture mutation; - classification vectors — position (+ history for repetition and counters) → status; they cover
nomovebeforecheckmate/stalemate, the dead-position tables per pairing including the mixed-pairing rule, and the precedence of a delivered mate over a background draw; - clock vectors — time control + sequence of charges → clocks and flags, per period type and transition;
- scenario vectors — complete sessions as event sets (founding, Plies with timings, invocation) → verdict; they cover premove selection and the non-rewinding anchor, the cap
K, both timeout flavours, the three post-chain resolutions and their order, and cutoff exclusion; - differential vectors — random positions → the full legal set, for the GGN documents to be checked against the generator they were produced from (the reference engine’s
tests/ggn_differential.rsperforms this check on ~67 000 positions of random play across the nine pairings, on both the legal set and the attack relation).
Every vector carries the digests of the rule-system document it was computed under. A vector is never edited: a wrong vector is a kernel defect (§Identification and contract).
III.2 Reference implementation
Sashité’s reference implementation of this kernel is the sashite-sanki-engine crate (position kernel and clocks) with its session layer. When the manifest carries kernel.reference, that digest names a build of it (a WASM module with a fixed ABI: natural_state(events) → verdict), and the verdict of a session is by definition that program’s output. Publishing the reference digest removes even the residual risk of prose ambiguity, at the price that a defect of the build is the rule until a new kernel is published.
Appendix A — The atoms and where they live
| Atom | Fixed rule | Parameterised by | Reference module |
|---|---|---|---|
| E | the five effects, recognised on the form of the possibility | promotion table; capture mutation | apply.rs, legality/resolve.rs, capture.rs |
| A1 | attack, derived from the GGN | — | movement/attack.rs |
| A2 | check marker | — | canonicalize/check_marker.rs |
| A2′ | no marker in hand | — | capture.rs (to move under canonicalize) |
| A3 | double-step marker strip | foot-soldier letter, home rank | canonicalize/double_step.rs |
| A4 | en passant window iff capture legal | foot-soldier letter, direction | canonicalize/en_passant_flag.rs |
| A5 | castling rights, derived from the GGN | — | canonicalize/castling_rights.rs |
| A6 | royal safety | — | legality/self_check.rs |
| A7 | drop restrictions: nifu, uchifuzume | the letters concerned by each (may be empty) | legality/drops.rs, terminal/uchifuzume.rs |
| A8 | rejection, never a sanction | — | kernel/step.rs |
| A9 | terminations and their order | stalemate outcome; dead-position tables; repetition, move-limit, move-cap thresholds; irreversible class | terminal/* |
| S1 | slots, play order, selection, cap | K |
session layer |
| S2 | clock accounting, two timeout flavours | time_control (a founding term) |
clock.rs, session layer |
| S3 | post-chain resolution and its order; which invocation | — | session layer |
Changing anything in the “Fixed rule” column is a new kernel.id. Changing anything in the “Parameterised by” column, or any GGN table, is a new rule-system document under the same kernel.
Appendix B — Manifest members the kernel reads
variants.<v>.style, .pieces, .ggn, .initial, .foot_soldier, .promotion, .drops (.letters, .nifu, .uchifuzume), .capture; pairings.<pair>.dead_positions; session.stalemate, .repetition, .move_limit, .move_cap, .irreversible, .candidate_cap (K). Any other member is ignored by the kernel but MUST be known to the manifest format (Rule System — Sanki §Structure: unknown members are rejected).
References
- Rule System — Sanki — the manifest this kernel executes
- ADR-0033 — why the kernel is an executable contract
_reviews/2026-09-03/inventaire-atomes-rule-system.md— the inventory the atoms come from, with the decisions of 2026-09-03/04- Playing Principles §10 — the pluggable-arbiter clause this kernel mechanises
- Game Protocol — the protocol invariants the kernel assumes (turn passing, conservation of pieces)
