Decision Register

Every architectural decision recorded in this manual, in the order it appears in the body — so the register reads in the same sequence as the manual rather than by ID. The Page column points at the full entry, which gives the context, the alternatives considered, and the consequences.

IDs are allocated in blocks by subject area (engine, board, motion, application, services, fleet) with gaps left inside each block, so a decision recorded later sits beside its neighbours instead of at the end. An ID is therefore a stable name, not a position.

IDs are assigned once and never reused. A decision that has been overtaken is not edited: a new ID is allocated and the old entry’s status becomes superseded, so the register reads as a history rather than a snapshot. The Origin of each decision — the dated plan document it was first argued in — is given in the body entry; those plans remain in the repository under docs/*-docs/ exactly where the source code cites them.

ID

Claim

Decided

Status

D-01

The manual is canonical for current behaviour; the plan documents stay in place as the dated record of each decision as made

2026-07-22

in force

D-10

The difficulty ladder is one shared NNUE plus a parameterized weakening layer, not one net per rung

2026-06-29

in force

D-11

The node budget is the primary difficulty dial; deliberate imperfect move selection supplies the low end

2026-06-29

in force

D-12

The ceiling and the ladder are independent tracks — the top rung is full strength and rises with it

2026-06-29

in force

D-13

Any change that could affect playing strength must be validated by SPRT before it ships

2026-06

in force

D-14

There is one logical engine instance; `LinkedEngine` wraps its globals and access is serialized by the session worker

in force

D-15

Engine-consuming work outside the game loop runs on the session worker thread, not a background thread

2026-07-13, reaffirming a 2026-07-12 correction

in force

D-16

`Max Managed Move Time` defaults to 0 (no cap), not 1500 ms

2026-06-29

in force

D-25

The target of a physical setup is a `PieceTracker` identity snapshot, never a FEN

2026-07-12

in force

D-26

Promotion is physically faked — the pawn stays on the square and keeps its `SLOT_Px` identity

in force

D-27

An arbitrary FEN is assigned to physical pieces by optimal min-cost matching, and promoted pieces are represented by stand-ins

2026-07-13

in force

D-28

Never tear the board down between puzzles; plan each setup as a delta, and order a batch by setup distance

2026-07-13

in force

D-29

LEDs are driven directly by the Pi over SPI MOSI with DMA, on a second SPI bus

2026-07-12

in force, supersedes D-30

D-30

LEDs driven from GPIO18 via the PWM peripheral

2026-07-11

superseded by D-29

D-31

A lit square always lights all 8 of its ring LEDs, and no more than 16 squares may be lit at once

2026-07-12

in force

D-32

When a lifted piece has more legal targets than budget, show its single best destination rather than a truncated set

2026-07-12

in force

D-33

Legal-move highlighting triggers on a physical piece lift, not on a completed move

2026-07-11

in force

D-34

One continuous snake chain, and the chain order is the firmware pixel index

2026-07-11

in force

D-35

Desync recovery is a pure decision function, separated from the physical integration that acts on it

2026-07-03

in force

D-36

Persist only the colours that deviate from the factory palette, and stamp a schema version

2026-07

in force

D-40

Motion control is FluidNC on an ESP32 board with 4$\times$ TMC2209 drivers in UART mode

2026-07-01

in force

D-41

Driver configuration lives in a boot-time YAML file; motion parameters stay live `$`-writes

2026-07-01

in force

D-42

The controller class and the `$`/G-code protocol layer are unchanged by the firmware swap

2026-07-01

in force

D-50

The GUI links the engine and hardware libraries in-process — no IPC, no daemon

in force

D-51

A personality is data, not code: a named preset of existing UCI options

in force

D-52

Game orchestration is extracted out of `main.cpp` into a frontend-agnostic `GameSession`

in force

D-53

The backend is chosen at startup by capability detection, in a Qt-free factory, with environment overrides

2026-07-11

in force

D-54

QML stays logic-free; all logic lives in C++ bridges

in force

D-55

Restart prompts Auto vs Hand board reset on every use

in force

D-56

Game sharing is opt-in, off by default, and never retroactive

2026-07-12

in force

D-57

The board-local store is one JSON file per game, and it is the source of truth for solo games

in force

D-58

Store the minimum — `startFen` plus the UCI move list — and derive everything else

in force

D-59

The board analyzes its own games; the cloud renders a precomputed artifact

in force

D-60

Analysis lives in a separate table, not a column on `games`

in force

D-61

Boards do not upload networked games at all

2026-07-12

in force

D-62

The server attributes the human seat from the device’s linked account, ignoring any client-claimed user id

2026-07-12

in force

D-63

Completed puzzles are filed in a dedicated `PuzzleHistoryStore`, not in `GameStore`

2026-07-17

in force

D-64

Idle sleep blanks the panel backlight and wakes on touch — it is not system suspend

2026-07

in force

D-65

Accounts ship in v1, and the product is architected toward a public service

2026-07-03

in force

D-66

Cloudflare is the backend: Workers, one Durable Object per game, D1 and KV

2026-07-03

in force

D-67

A networked game is the existing loop with the opponent swapped for a socket

2026-07-03

in force

D-68

The wire carries only UCI move strings and a few control messages

2026-07-03

in force

D-69

Authoritative game state lives on the server; the Durable Object re-validates every move

2026-07-03

in force

D-70

The 6-digit join code is kept permanently as the zero-friction path

2026-07-03

in force

D-71

Track H (the hardware loop) and Track A (accounts) run in parallel, with H first

2026-07-03

in force

D-72

Player data lives in a separate D1 from the corporate database

2026-07-03

in force

D-73

An unprovisioned board is seated anonymous, not rejected — but a wrong token is a 401

2026-07-16

in force

D-74

The device token rides the upgrade as an `X-Device-Token` header, not a query parameter

2026-07-16

in force

D-75

The live feed is a `BoardFeed` Durable Object keyed per device, and its socket state is presence

2026-07-09

in force

D-76

No permission is required, but the board must be fully functional with no Lichess account

2026-07-13

in force

D-77

Fair play is structural: no hint source is supplied at all during Lichess-rated activity

2026-07-13

in force

D-78

HTTP stays in the Qt layer; `kirin_app` gains no HTTP dependency

2026-07-13

in force

D-79

Kirin keeps its own rating, authoritative for the device; the Lichess rating is displayed alongside

2026-07-13

in force

D-80

Keep `clocks=true`; the tokenizer extracts `[%clk]` rather than skipping it

2026-07-19

in force

D-81

Broadcast mirroring composes `ReviewSession` for its bidirectional cursor

2026-07-19

in force

D-82

Broadcast needs no auth at all, and cannot share the Lichess HTTP client

2026-07-19

in force

D-83

The account link is OAuth PKCE on the player’s phone, and we are a courier, not a custodian

2026-07-13

in force

D-84

Telemetry is live-only, and is sent now or dropped — never queued

2026-07-18

in force

D-85

The golden image locks three properties that cannot be changed on already-flashed boards

2026-07-18

in force

D-86

v1 OTA ships a prebuilt, self-contained bundle and swaps the application, not the OS

2026-07-17

in force

D-87

Bundles are signed with minisign, the public key is baked into the image, and nothing unpacks before verification

2026-07-17

in force

D-88

The board never installs on its own — a background check stages, and the owner taps Install

2026-07-17

in force

D-89

A boot-count guard backs up the heartbeat window for auto-rollback

2026-07-17

in force

D-90

The OTA admin console is built in the corporate admin backend, proxying to the player Worker

2026-07-17

in force

D-91

The wizard stores no step cursor and derives no resume point — it always opens at Welcome

2026-07-17

in force

D-92

The dev bench deliberately does not use the golden image, and the two paths must not merge

2026-07-18

in force

D-93

Tokens are hashed at rest, the email-sending endpoints are rate-limited, and the board service runs unprivileged

2026-07-22

in force

D-94

The launcher runs a fixed canonical deploy path

2026-07-16

in force

D-95

Unit serials are four-digit zero-padded decimal, with `0001`– `0099` reserved for internal boards

2026-07-19

in force