app/board_recovery.h

physical desync recovery decision logic (Track H6).

When a gantry reproduces a move (in networked play: the peer’s move on my board), the 96-sensor scan may not match what the engine says the position is: the drive skipped steps, dropped a piece, or a human bumped the board. Today GameSession treats any post-move mismatch as a dead-end Error. This module is the brain that decides what to do instead — retry the drive, halt and ask the operator to fix a square, or resync from authoritative truth (docs/networked-play-docs/PLAN.md §8).

It is deliberately pure and hardware-free: given two occupancy bitboards (engine-expected vs debounced scan), the just-reproduced move, a retry count, and the authoritative move count, it returns a plan. That makes the hard part — the expected-vs-scan diff → action decision — fully unit-testable off scripted inputs, with no gantry, no sensors, and no network (§10). The integration that acts on the plan (re-drive, operator prompt UI, resync protocol over the transport) lives in the hardware backend / game loop.

Occupancy is presence-only (a piece is here or not); it carries no colour or identity. Square indexing matches the engine: a8=0 … h1=63, so file = ‘a’ + (sq % 8) and rank = ‘8’ - (sq / 8).

namespace kirin

Enums

enum class RecoveryAction

Values:

enumerator Proceed
enumerator RetryDrive
enumerator PromptOperator
enumerator Resync
struct RecoveryInputs
#include <board_recovery.h>

Public Members

U64 expectedOcc = 0
U64 actualOcc = 0
int lastMove = 0
int attempt = 0
int maxRetries = 2
int localPlies = 0
int authorityPlies = 0
struct RecoveryPlan
#include <board_recovery.h>

Public Members

RecoveryAction action = RecoveryAction::Proceed
U64 missing = 0
U64 extra = 0
int resyncFromMove = -1
std::string detail