Skip to content

Seam vocabulary

The seam vocabulary is the shared language that every board adapter speaks and every session-layer consumer understands. All types live in the BoardKit target and import only ChessCore and Foundation.

BoardEvent

BoardEvent is the decoded output of a board adapter — the semantic events that a physical board can produce regardless of brand or protocol.

public enum BoardEvent: Sendable {
    // Sensor events
    case squareSensed(square: String, isLift: Bool, piece: Piece? = nil)
    case occupancySnapshot([Bool])
    case identitySnapshot([Piece?])
    // Connection lifecycle
    case connected
    case ready
    case disconnected(error: String?)
    // Housekeeping
    case battery(percent: Int)
    case raw(Data)
    // Hardware-reported picks
    case promotionPick(piece: PieceType)
    // Stored-game import
    case storedGameImported(moves: [Move], sanMoves: [String], isComplete: Bool)
}

Sensor cases

.squareSensed fires on every lift or place detected by the board's sensors. square is an algebraic string in the board's physical frame (no orientation correction — that is a session concern). piece is non-nil only on identity-sensing boards (Chessnut, Certabo, Millennium). Square Off and DGT Pegasus always emit piece: nil.

.occupancySnapshot is a 64-element [Bool] in file-major order (a1=0, a2=1, … a8=7, b1=8, … h8=63). Occupancy-only adapters (Square Off, DGT Pegasus) emit this; identity boards emit .identitySnapshot instead.

.identitySnapshot is a 64-element [Piece?] in the same file-major layout. Only emitted by Chessnut, Certabo, and Millennium. Callers that need only occupancy: map { $0 != nil }.

Lifecycle cases

Case Meaning
.connected Transport link writable and notifications subscribed; adapter handshake may still be in progress
.ready Board completed its handshake; sensor stream is live
.disconnected(error:) Link dropped; error is nil for a clean disconnect

Housekeeping

.battery(percent:) — battery level 0–100, emitted by boards that support it (Chessnut Air family, DGT Pegasus).

.raw(Data) — undecoded bytes for any frame an adapter does not decode, so a consumer can log or capture it without losing data. Do not branch on it for ordinary board handling: the payload is a vendor's wire format, it is not stable across firmware, and anything built on it stops working on the next board. The one exception is a capability the seam advertises but does not yet model — .perPieceTracking is the current example — where a consumer that has checked the bit, and therefore knows exactly which adapter it is talking to, may parse the payload that adapter documents.

Hardware-reported picks and stored games

.promotionPick(piece:) — a board-side promotion piece pick (ChessUp 0x97 frame). When the board reports the promotion piece this way, the session can auto-resolve the promotion picker without asking the human. Session code must handle this case and must not treat it like .raw.

.storedGameImported(moves:sanMoves:isComplete:) — one game reconstructed from a board's internal storage during a BoardCommand.requestStoredGames import. Emitted by adapters that advertise BoardCapabilities.gameArchive (Chessnut Air family), one event per stored game. isComplete is false when the replay truncated and moves holds the recovered prefix.


BoardCommand

BoardCommand is the session-to-adapter vocabulary — commands the session sends down to the board. The adapter's encode(_:) -> Data? translates each case into board-specific wire bytes, returning nil for unsupported commands (which the transport silently skips).

public enum BoardCommand: Sendable {
    case startSession
    case requestState
    case indicateSquares([String], style: LEDStyle)
    case executeMove(uci: String)
    case requestStoredGames
    case custom(Data)
}
Case Purpose
.startSession Begin a new game / enter active-play state
.requestState Request a full occupancy or identity snapshot
.indicateSquares([String], style:) Illuminate squares with the given style
.executeMove(uci:) Ask a motorized board to physically play a move
.requestStoredGames Begin importing the games stored on the board's internal flash (.gameArchive boards); each game surfaces as .storedGameImported
.custom(Data) Adapter-specific payload not yet in the shared vocabulary

squares in .indicateSquares is an array of algebraic strings (["e2", "e4"]). Pass an empty array to clear all LEDs.


LEDStyle

LEDStyle is an advisory illumination hint for .indicateSquares. Adapters that support only a single LED color treat all non-.highlight values as .highlight.

public enum LEDStyle: Sendable {
    case highlight          // plain on/off — universally supported
    case moveFrom           // source-square emphasis (green on Chessnut)
    case moveTo             // destination-square emphasis (yellow on Chessnut)
    case danger             // check/threat emphasis (red on Chessnut)
    case custom(UInt8)      // board-specific color index
}

BoardCapabilities

BoardCapabilities is an OptionSet of feature flags declared by a concrete BoardAdapter. Query it once at connect time to determine which kernel paths to activate.

public struct BoardCapabilities: OptionSet, Sendable {
    public static let occupancySensing  // per-square lift/place
    public static let pieceIdentity     // piece type+color per square
    public static let perSquareLEDs     // individually addressable per-square LEDs
    public static let moveIndication    // any move-highlight capability
    public static let motorised         // auto-move mechanism
    public static let batteryReporting  // reports battery level
    public static let perPieceTracking  // true per-piece unique identity (Chessnut Move)
    public static let gameArchive       // onboard stored-game archive (requestStoredGames)
}

Vendor capability presets live with the vendor's adapter, not on the shared type, so importing BoardKit alone does not pull in a vocabulary for hardware you do not use:

Preset Declared in Import
BoardCapabilities.squareOff SquareOffAdapter import SquareOffAdapter
BoardCapabilities.chessnutAirFamily ChessnutAdapter import ChessnutAdapter
BoardCapabilities.chessUp ChessUpAdapter import ChessUpAdapter

.perPieceTracking is a hardware fact, not a seam feature

The bit says the board can tell one knight from the other. The seam does not model that yet: identitySnapshot carries type and colour per square exactly as it does for .pieceIdentity, and per-piece status arrives as BoardEvent.raw. A consumer that wants it must poll and parse through the adapter it has already declared a dependency on — for the Chessnut Move that is ChessnutMoveAdapter.pieceStatusRequestData(), whose response shape that adapter documents. Read the bit as "the hardware can do it", not "the seam can express it".

Capability matrix

See the canonical hardware-status and capability tables, which distinguish implemented flags from hardware-tested scope and document runtime-dependent capabilities such as calibrated Certabo identity.

Millennium and perSquareLEDs

The Millennium board uses a 9×9 corner-LED grid rather than per-square LEDs. It sets .moveIndication but not .perSquareLEDs. Always check .perSquareLEDs before sending granular LED commands.