UCI Protocol

Overview

The engine communicates via the Universal Chess Interface (UCI) protocol.

Supported Commands

Standard UCI Commands

IN uci

Initialize UCI mode and request engine identification.

IN isready

Check if engine is ready. Engine responds with readyok.

IN ucinewgame

Reset engine state for a new game.

IN position [fen | startpos] moves ...

Set up a position.

IN go [options]

Start calculating. Options include:

  • depth n – search to depth n

  • nodes n – search a fixed number of nodes (reproducible; see Ch. Measuring Strength)

  • movetime ms – search for ms milliseconds

  • wtime/btime ms – time remaining for white/black

  • winc/binc ms – increment per move

  • movestogo n – moves until next time control

  • infinite – search until stopped

  • perft n – run perft test to depth n

IN stop

Stop calculating.

IN quit

Exit the engine.

Engine Responses

id name Kirin
id author Strydr Silverberg
uciok

info score cp 35 depth 8 nodes 234567 time 1250 pv e2e4 e7e5 g1f3

bestmove e2e4

The option surface

Kirin advertises 26 options in response to uci. They fall into four groups:

  • Search features — one check option per pruning or ordering technique (§The pruning and reduction surface).

  • EvaluationUse NNUE, EvalFile and the five NNUE policy knobs (Ch. NNUE Evaluation).

  • Time managementMove Overhead, Slow Mover, Max Managed Move Time (Ch. Time Management).

  • GeneralSkill Level and Hash.

The full table, with every default and range, is Appendix UCI Option Reference.

This is a larger option surface than a chess engine strictly needs, and that is deliberate: it is the mechanism that makes A/B testing possible without rebuilding. A candidate and a baseline in a gauntlet are the same binary differing by one setoption line, which removes the compiler, the flags and the machine as confounders (Ch. Measuring Strength). Every toggle in the search group exists to be measured, and several are off today precisely because that measurement has not happened yet.

Skill Level

Kirin exposes a three-tier difficulty setting that adjusts both search depth and move quality, making the engine accessible to players of all levels while preserving full strength for competitive play.

Difficulty Tiers

Level

Name

Max Search Depth

Move Noise

0

Easy

3 plies

$\pm$<!-- -->{=html}150 centipawns

1

Medium

5 plies

$\pm$<!-- -->{=html}50 centipawns

2

Hard (default)

Caller-supplied depth

None

Skill level configuration

Two independent mechanisms are combined to simulate human-like play at lower difficulty settings:

  1. Depth capping. Inside searchPosition, the caller-supplied depth is silently clamped to a tier-specific ceiling before iterative deepening begins. At skill 0 the engine never searches deeper than 3 plies; at skill 1 the cap is 5 plies; at skill 2 the cap is set to 64 (effectively unlimited for practical time controls).

  2. Root-move noise. After the search completes, the engine’s best move is not always played. At skill levels below 2, each legal root move is assigned the engine’s score perturbed by a random offset drawn uniformly from $[-\textit{range},, +\textit{range}]$, where range is 150 cp at skill 0 and 50 cp at skill 1. The move with the highest noisy score is selected. This causes the engine to occasionally prefer a suboptimal move in a way that resembles human error rather than purely random play.

UCI Configuration

The skill level is exposed as a standard UCI spin option and can be set from any compatible GUI or from the command line:

setoption name Skill Level value 0   % Easy
setoption name Skill Level value 1   % Medium
setoption name Skill Level value 2   % Hard (default)

The engine announces the option during UCI initialisation:

option name Skill Level type spin default 2 min 0 max 2

Values outside the range [0, 2] are clamped silently. The engine acknowledges each change with an info string:

info string Skill Level set to 1

Implementation

The global variable skillLevel in search.cpp stores the current setting. It is read in two places:

// Depth cap applied at the start of searchPosition()
static const int skillDepthCap[3] = { 3, 5, 64 };
int depthCap = skillDepthCap[skillLevel];
if (depth > depthCap) depth = depthCap;

// Noise injection applied after the best move is found
if (skillLevel < 2) {
    static const int noiseRange[2] = { 150, 50 };
    int range = noiseRange[skillLevel];
    // ... re-score root moves with random perturbation
}

Note

Skill level does not affect the correctness of move generation or position evaluation — illegal moves are never produced regardless of the difficulty setting. The engine test suite (engine_test.cpp) verifies this invariant across all three levels and multiple board positions.