Skip to content

Avo-k/rorschach

Repository files navigation

Rorschach

A Lichess bot that plays moves which look alien to humans but are still objectively sound. It pipes a strong chess engine through a human-move predictor and uses the predictor as a negative filter: among the engine's top candidates within an eval-loss budget, pick the move a human is least likely to play.

move = argmin_{m ∈ engine_topK(pos)}  P_Maia(m | pos, elo)
       subject to  eval(best) − eval(m) ≤ Δ
  • engine_topK — top-K candidates from Patricia (MultiPV, cp-scored).
  • P_Maia — probability under the human-move predictor (Maia-3) at a chosen Elo.
  • Δ — eval-loss budget in centipawns. The dominant calibration knob.

What's interesting about this

The math is one line; the calibration is the project. Two metrics fight each other:

  1. Soundness — average eval loss vs engine-best, blunder rate, win rate vs a fixed-Elo opponent.
  2. Alien-ness — average −log P_Maia(played | elo) over a game; fraction of moves with P_Maia < threshold.

Δ trades them off. Profiles bundle (Δ-shape, K, target Elo) per time control.

Components

Layer Tool Role
Strong engine Patricia 5+ MultiPV candidate generation with cp evals. Single-threaded NNUE, MIT, ~3500 CCRL. Biased toward sharp lines — compounds with the anti-human filter.
Human predictor Maia-3 / Chessformer Returns {uci → P(move)} over all legal moves, conditioned on (elo_self, elo_oppo) and the last 8 board positions.
Lichess oracle Lichess Opening Explorer When the position has ≥10 games in the DB, real human frequencies replace Maia's prediction (zero-played moves get P=0 — maximally alien and never made up).
Orchestrator This repo Spawns Patricia as a UCI subprocess, calls Maia in-process, runs the selector, exposes a UCI shim for any host.
Lichess client lichess-bot Manages the Bot API, challenges, clocks. We plug in via UCI.

The narrative-break selector

Maia-3 conditions on game history, which lets us score two probabilities per candidate:

  • P_with(m) — likelihood given the position and the last 8 plies.
  • P_without(m) — likelihood from the current position alone.

break(m) = P_without(m) − P_with(m) is positive when the way we got here made m less likely than the static position would suggest — the move breaks the game's narrative. The narrative-aware selector picks:

argmin (1+λ)·P_with(m) − λ·P_without(m)   subject to the same Δ budget

λ=0 is plain Maia-3 selection. λ=1 (the narrative profile) rewards history-induced rarity equally to absolute rarity. Costs 2× Maia inference per move (~+50–100 ms on CPU at 5M params); fits the blitz/rapid budget.

Repo layout

rorschach/
  rorschach/                   # Python package
    engine.py                  # Patricia UCI wrapper (subprocess + UCI protocol)
    maia.py                    # Maia-3 inference wrapper
    explorer.py                # Lichess Opening Explorer client
    selector.py                # adaptive + narrative selection rules
    bot.py                     # composition root: engine + predictor + selector
    uci.py                     # UCI shim — `rorschach-uci` entry point
  bin/
    patricia                   # vendored Patricia binary (Linux x86-64, AVX2)
  configs/
    lichess-bot.yml.example    # example lichess-bot config
  tests/
    test_selector.py           # pure-logic tests for the selection rule
  CLAUDE.md                    # internal research notes / agent guidance

Quick start

# 1. install deps (Maia-3 ships from git; first run downloads the checkpoint)
uv sync

# 2. sanity check
uv run pytest -q

# 3. drive the engine via UCI for one move
printf 'uci\nsetoption name MaiaType value maia3-5m\nsetoption name Profile value narrative\nisready\nposition startpos moves e2e4 c7c5\ngo movetime 200\nquit\n' \
  | uv run rorschach-uci

Lichess deployment

rorschach-uci is a normal UCI engine. Any host that speaks UCI can drive it. To run as a Lichess bot:

# 1. one-time bot account upgrade (account must have zero rated games first)
curl -d '' https://lichess.org/api/bot/account/upgrade \
  -H "Authorization: Bearer YOUR_BOT_TOKEN"

# 2. clone lichess-bot next to this repo and install its deps
git clone https://github.com/lichess-bot-devs/lichess-bot.git
cd lichess-bot && pip install -r requirements.txt

# 3. copy our example config and edit the token
cp ../rorschach/configs/lichess-bot.yml.example config.yml

# 4. put LICHESS_TOKEN in rorschach/.env (for the explorer; bot token is separate)
# 5. run via our launcher (injects the chat-command patch: !bal/!agg, !eval)
LICHESS_BOT_DIR=$PWD python ../rorschach/scripts/run_lichess_bot.py

The example config points lichess-bot at rorschach/.venv/bin/rorschach-uci. On the first isready, the shim loads Patricia + Maia + the explorer.

For the Docker deployment, none of the above applies on the host: the config (configs/config.docker.yml, greetings included) is baked into the image and the launcher is the entrypoint. The only host-side state is LICHESS_TOKEN in .env. Edit the config in the repo, push, redeploy.

UCI options

Option Type Values Default
Profile combo balanced, aggressive, narrative balanced
MaiaType combo maia3-5m, maia3-23m, maia3-79m maia3-5m
Elo spin 0 = auto (track opponent), or 600..2600 0
TimeMs spin 0..5000; 0 = derive from clock 0

Per-move info line includes Δ, eval loss, P (Maia of chosen), optional break (narrative score for the chosen move), and oracle ∈ {E=explorer, M=Maia, N=Maia + narrative break}.

Deployment target

CPU-only Proxmox VM, 2–4 vCPU, 2–4 GB RAM. The 5M Maia-3 model is the CPU-friendly default; 23M / 79M variants exist (more accurate, slower).

Important constraints

  • Bot accounts are explicitly whitelisted by Lichess's anti-cheat (Irwin/Kaladin). This bot's policy by design maximises the exact signal those classifiers flag. That's fine for a registered BOT account; it would be an instant ban on a human account. Never run this code under a non-BOT login.
  • Patricia is a black box. If we want different behavior, change selector parameters, not the engine.

Prior art

  • Maia-3 / Chessformergithub · models · paper · blog
  • Patriciagithub
  • lichess-botgithub
  • Detecting Fair Play Violations in Chess Using Neural Networks (CEUR 2024) — pdf. The adversarial dual of this project's objective: their positive class is exactly our policy.
  • Behavioral Stylometry in Chess (CSSLab, NeurIPS 2021) — arXiv
  • Ken Regan's engine-correlation methodologypdf

No public Lichess bot doing exactly this composition was found at project start. The novelty is the deployment, not the math.

About

Lichess bot that plays the least human-like moves while not throwing the game

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors