Kernel — Sanki (`sashite.sanki.kernel/1`)
==========================================

`reference` `rule-system` `normative`

This document specifies the **kernel** that Sashité's reference rule system for Sanki implements — the fixed rules that the module's embedded data (the GGN movement tables and the parameters of Appendix B) parameterise. Under ADR-0034 a rule system is an executable WebAssembly module named by a signed Rule System event (kind `3417`), and this document is that module's **specification**: the human-readable statement of what it does, pinned by the event's `spec` tag, the reference a defect is judged against. It is written so that everything a session's verdict depends on is either stated here or incorporated here by digest; the verdict itself is what the module computes ([Rule System — Sanki](https://blobs.sanki.app/8f57a592223a275d0a65acff64fb41e49a5529ec3a92f8ea68380faccd0a942e.md)), and 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 standing

| Where | Value | Meaning |
|---|---|---|
| `describe.kernel` of the reference module ([Kernel ABI — Sanki](https://blobs.sanki.app/a7ab0569bec74f247c9101058d9d98320ca62cd30dba20b22e169a05fea1cab2.md) §`describe`) | `sashite.sanki.kernel/1` | the name of this set of rules — documentary: what the module was built to |
| the `spec` tag of Sashité's Rule System event (kind `3417`) | the SHA-256 of this document as published | pins the text beside the module's bytes — two parties committing to the rule system can read the same words; documentary, never computed from |

**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 the `spec` 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](https://blobs.sanki.app/8bc18fc997aecbcb56f4226fa0d14f92de4bb6e5ceab5fc36eafd9a5629fc573.md) | the clock arithmetic (§II.4) | `8bc18fc997aecbcb56f4226fa0d14f92de4bb6e5ceab5fc36eafd9a5629fc573` |
| [Move Encoding — Sanki](https://blobs.sanki.app/4694c64fd312a3eae36c99c217e367f58fac03b0e4a54609c95b53da5a4df1ff.md) | the ply content, the slot structure, the selection rule and the cap `K` (§II.2–II.3) | `4694c64fd312a3eae36c99c217e367f58fac03b0e4a54609c95b53da5a4df1ff` |
| [Statuses — Sanki](https://blobs.sanki.app/490337601f139c6391fe09e54680da3ac41c3f2bd1fd22312287be680fbc5a99.md) | the status vocabulary, the implicit conventions and the verdict resolution (§II.5–II.6) | `490337601f139c6391fe09e54680da3ac41c3f2bd1fd22312287be680fbc5a99` |
| [GGN v1.0.0](https://sashite.dev/specs/ggn/1.0.0/) | the movement-table format the position kernel reads | `c054ef693a1f6c3b367d94390c512e581730a39ac85c0a8f40bba921cce175b4` |

**The module is the rule; the prose describes it.** For a session, the rules are what the module its rule system names computes — every answer, the verdict included ([Kernel ABI — Sanki](https://blobs.sanki.app/a7ab0569bec74f247c9101058d9d98320ca62cd30dba20b22e169a05fea1cab2.md) §Normativity). This document says what that module is meant to do; the conformance corpus (§III.1) is the module's test suite, run through every build before publication. Where the prose admits two readings, the module's behaviour is the rule and the corpus pins it; where a published module contradicts the prose, the module is **defective**, and the defect is repaired forward by a new rule system — a new module, a new event — never by editing the module, this document or its corpus under a published rule system, since sessions founded under it name those exact bytes and that exact `spec` digest (ADR-0034 §8). A revision of this document is a new `spec` digest, carried by the next Rule System event; a change to a fixed rule is also a new kernel identifier (Appendix A).

**No discretion.** Nothing in this kernel is "MAY" for the party computing a verdict. The one place a rule system could leave a choice open — a non-deterministic initial position — is excluded: a Sanki rule system MUST prescribe a deterministic initial position for every pairing it defines, reported by its module's `describe.positions` ([Rule System — Sanki](https://blobs.sanki.app/8f57a592223a275d0a65acff64fb41e49a5529ec3a92f8ea68380faccd0a942e.md) §What `describe` reports).

---

## Part I — The position kernel

### I.1 Inputs and output

The position kernel takes:

- a **position** in canonical [FEEN](https://sashite.dev/specs/feen/1.0.0/) form — board, two hands, the active side and the two styles;
- a **ply** in the Sanki content form `[from, to, actor]` ([Move Encoding — Sanki](https://blobs.sanki.app/4694c64fd312a3eae36c99c217e367f58fac03b0e4a54609c95b53da5a4df1ff.md) §Content format): `from` a square or `null` (a drop), `to` a square, `actor` a piece name or `null`;
- the **rule-system data**: the GGN table of each side's variant and the parameters of Appendix B, both embedded in the module.

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 tables enumerate, for every [QPI](https://sashite.dev/specs/qpi/1.0.0/) the variant uses, every source (a square, or the hand `*`) and every destination, the **possibilities** — each a set of [LCN](https://sashite.dev/specs/lcn/1.0.0/) 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 tables 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 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:

1. **Possibility.** `from` is a square holding a piece of the active side, or `null` with `actor` naming 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 to `to` is satisfied.
2. **Actor.** `actor` obeys the promotion table (parameter): for a board move that promotes — the piece is a promoting letter of its variant and `to` lies on its promotion rank, `from` not already on it — `actor` MUST name one of the table's targets when the table lists several, and MUST be `null` when it lists one (automatic promotion); for every other board move `actor` MUST be `null`; for a drop `actor` names the dropped piece.
3. **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`, `+F` or `-F` alike). This rule is the kernel's and not the document's because GGN matches a QPI value exactly and a `deny` object 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):

1. **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](https://sashite.dev/specs/pmn/1.0.0/) writes `e5~f6` bare.
2. **Demotion (parameter).** The letter is replaced per the `demote` table (`T → F` in ōgi; empty elsewhere).
3. **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). `foreign` says 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 as `F`).

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 parameters carry no castling vocabulary. |

The initial position of a pairing carries no `-` markers and carries `+` only on the pieces the rule system's parameters list (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:

1. if there is **no legal move**: `nomove` (a draw) when there is not even a pseudo-legal move; else `checkmate` (the side to move loses) when in check; else `stalemate` (outcome: parameter `session.stalemate`, `draw` for Sanki);
2. otherwise, in this order: `insufficient` (a draw) when one of the **dead-position predicates** the pairing enables holds (parameter `pairings.<pair>.dead_positions`, names from this fixed catalogue: `lone_royals` — only the two royals on the board; `royal_and_bishop_vs_royal` and `royal_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); `repetition` when the canonical position has now occurred `session.repetition` times (3) in the session, initial position included; `movelimit` when `session.move_limit` (100) half-moves have passed without an irreversible move; `movecap` when the session has reached `session.move_cap` (600) half-moves;
3. 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](https://github.com/sashite/nostr/blob/main/nips/canonical-timing.md)), 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. An invocation's cutoff is at or after t₀: the session is not playable before t₀ (kind `3422` §Canonical session start), a Conclusion timed before it is not a conforming invocation (kind `3425` §Semantic constraints, item 9), and the kernel is not invoked for it. 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](https://blobs.sanki.app/490337601f139c6391fe09e54680da3ac41c3f2bd1fd22312287be680fbc5a99.md) 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](https://blobs.sanki.app/4694c64fd312a3eae36c99c217e367f58fac03b0e4a54609c95b53da5a4df1ff.md) §Within-step ordering).

### II.3 The canonical chain

Slots are consumed in the play order. A slot's **candidates** are its Plies whose canonical timing lies in `[t₀, cutoff]` (§II.1; a Ply timed before t₀ is invalid and never a candidate). **Identical candidates** — same `content`, byte for byte, same `draw` flag — are one candidate for the cap, represented in each window by the one that window's scan reaches first; an identical pair straddling `T` is one candidate per window ([Move Encoding — Sanki](https://blobs.sanki.app/4694c64fd312a3eae36c99c217e367f58fac03b0e4a54609c95b53da5a4df1ff.md) §Slot candidates and selection). At each slot the **selection rule** picks the canonical Ply: the boundary `T` is the maximum canonical timing among the preceding selected Plies (t₀ for the first slot), never rewound by a selected premove; candidates timed strictly before `T` form the *anterior* window (premoves), those timed at or after `T` the *informed* window (live moves). The canonical Ply is the **latest legal** anterior candidate among the `K` most recent by (canonical timing, event id), scanned newest-first; else the **earliest legal** informed candidate among the `K` earliest by (canonical timing, event id), scanned oldest-first; else the slot is **unfilled** and the chain stops there. `K` is the rule system's parameter `session.candidate_cap` (Appendix B; 8 for the reference module), compiled into the module and identical for every consumer of a session. Legality is the position kernel's (§I.4–I.5); an illegal candidate is skipped in either window and is never a termination. Identical candidates have identical legality, so their collapse never changes which candidate a scan reaches first — it only keeps retries from consuming the cap.

### 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](https://blobs.sanki.app/8bc18fc997aecbcb56f4226fa0d14f92de4bb6e5ceab5fc36eafd9a5629fc573.md) (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:

1. **`agreement`** (`50/50`): the last selected Ply carries the `draw` flag and the invoker is its signer's opponent;
2. **abandonment `timeout`**: the player on move has let their clock, charged from the chain's **anchor** — the boundary `T` the next slot would have (§II.3): the maximum canonical timing among the selected Plies, never rewound by a selected premove, t₀ for an empty chain — to the cutoff, expire — decisive against them;
3. **`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 kernel's **test suite**: the vectors every build of the reference module runs through, in two runtimes and with byte-identical answers, before it is published ([Kernel ABI — Sanki](https://blobs.sanki.app/a7ab0569bec74f247c9101058d9d98320ca62cd30dba20b22e169a05fea1cab2.md) §Conformance of a build). It is kept with the module's sources and published as documentation beside this text (Sanki conformance vectors, categories `legality`, `selection`, `time`, `scenarios`, `clock`, `puzzle`); it names nothing by digest and arbitrates between no implementations, there being one. 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 `actor` errors, and every cross-variant capture mutation;
- **classification vectors** — position (+ history for repetition and counters) → status; they cover `nomove` before `checkmate`/`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 and `draw` flags, invocation) → chain, termination and verdict, under the cap `K` compiled into the module; they cover premove selection and the non-rewinding anchor, the identical-candidate collapse, the cap `K`, the t₀ and cutoff bounds, both timeout flavours, the three post-chain resolutions and their order;
- **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.rs` performs this check on ~67 000 positions of random play across the nine pairings, on both the legal set and the attack relation).

A vector is never edited under a published rule system: a vector found wrong is a defect of the kernel or of its module (§Identification and standing), repaired by a new rule system whose corpus covers the case.

### III.2 The reference module

Sashité's reference implementation of this kernel is the `sashite-sanki-engine` crate (position kernel and clocks) with its session layer, the `sashite-sanki-session` crate (Part II over an abstract event model), compiled through `sashite-sanki-kernel-wasm` — the ABI crate, which computes nothing of its own — into the **reference module**: a WebAssembly module under `sashite.sanki.kernel-abi/1` ([Kernel ABI — Sanki](https://blobs.sanki.app/a7ab0569bec74f247c9101058d9d98320ca62cd30dba20b22e169a05fea1cab2.md)), named by digest by Sashité's Rule System event (kind `3417`; [Rule System — Sanki](https://blobs.sanki.app/8f57a592223a275d0a65acff64fb41e49a5529ec3a92f8ea68380faccd0a942e.md)). The verdict of a session is by definition that module's output on the session's public events; this document describes what that output is, and a divergence between the two is a defect of the module, repaired forward by a new rule system (ADR-0034 §8) — the price of having no residual prose ambiguity is that a defect of the build is the rule for the sessions that name it, which is why a build is verified before publication as the ABI requires: the corpus and a random-session differential against the native engine, run through the module in a browser's WebAssembly engine and in `wasmi` with byte-identical responses, and the extreme session of the ABI's bounds completing in both.

---

## Appendix A — The atoms and where they live

| Atom | Fixed rule | Parameterised by | Reference engine |
|---|---|---|---|
| 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 either column, or any GGN table, is a new module and therefore a new rule system — one revision rule, without exception (ADR-0034 §1). A change in the "Fixed rule" column is also a new kernel identifier; a change confined to the "Parameterised by" column, or to a table, keeps `sashite.sanki.kernel/1` and is a new module under the same kernel.

## Appendix B — Parameters the kernel reads

`variants.<v>.style`, `.pieces`, `.ggn` (the variant's movement table itself), `.initial`, `.foot_soldier`, `.promotion`, `.drops` (`.letters`, `.nifu`, `.uchifuzume`), `.capture`; `pairings.<pair>.dead_positions`; `session.stalemate`, `.repetition`, `.move_limit`, `.move_cap`, `.irreversible`, `.candidate_cap` (`K`). They are compiled into the reference module and reported, in this shape, by its `describe.parameters` — documentary: a consumer reads them to know the rules, never to compute them ([Kernel ABI — Sanki](https://blobs.sanki.app/a7ab0569bec74f247c9101058d9d98320ca62cd30dba20b22e169a05fea1cab2.md) §`describe`; [Rule System — Sanki](https://blobs.sanki.app/8f57a592223a275d0a65acff64fb41e49a5529ec3a92f8ea68380faccd0a942e.md) §The module).

## References

- [Rule System — Sanki](https://blobs.sanki.app/8f57a592223a275d0a65acff64fb41e49a5529ec3a92f8ea68380faccd0a942e.md) — the reference module that implements this kernel, its build and its publication
- [Kernel ABI — Sanki](https://blobs.sanki.app/a7ab0569bec74f247c9101058d9d98320ca62cd30dba20b22e169a05fea1cab2.md) — the interface the reference module exposes
- ADR-0034 — the rule system is a signed event naming an executable module
- ADR-0033 — why verdicts are verified, not believed
- `_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](https://sashite.dev/game-protocol/) — the protocol invariants the kernel assumes (turn passing, conservation of pieces)
