Architecture-agnostic middleware that gives any chess engine an emotional state.
A continuous psyche score (ψ ∈ [−100, +100]) tracks position quality move-by-move and drives a five-stage audio-inspired signal chain that reshapes the engine's move probability distribution — without any search, tree traversal, or model retraining.
Works as a drop-in layer on top of any move predictor that outputs logits or probabilities: Maia2, Stockfish NNUE policy heads, or your own transformer.
This module is a partial release accompanying the research paper: "Ailed: A Psyche-Driven Chess Engine with Dynamic Emotional Modulation"
The PsycheCalculator evaluates the board position after every move across five factors:
| Factor | Default Weight | Description |
|---|---|---|
| Material | 10 | Piece value difference (P=1, N/B=3, R=5, Q=9) |
| King safety | 5 | Pawn shield + nearby enemy attackers |
| Pieces attacked | 3 | Own pieces under attack |
| Center control | 2 | Attacks on d4/d5/e4/e5 |
| Mobility | 1 | Legal moves minus opponent's legal moves |
These are combined, tanh-compressed to [−100, +100], and blended into the running psyche state with configurable reactivity, phase-awareness (opening/middlegame/endgame), sacrifice dampening, and daily decay toward neutral.
Phase is detected per move and controls reactivity scaling (opening = dampened, middlegame = baseline, endgame = strongly dampened / freeze-biased by default, all configurable via PsycheConfig):
| Phase | Condition |
|---|---|
| Opening | fullmove ≤ 15 and non-pawn/non-king material loss < 6 pts |
| Endgame | total non-pawn/non-king material on board ≤ 13 pts |
| Middlegame | everything else |
Endgame takes priority. The opening threshold prevents damped reactivity from masking rapid early collapses — gambits, sacrifices, or early trades that lose 6+ points of material (≈ two minor pieces) exit opening phase immediately, even before move 15. Both thresholds are configurable via PsycheConfig:
from ailed.psyche.config import PsycheConfig
config = PsycheConfig(
opening_move_limit=15, # fullmove limit for opening phase
opening_material_loss_threshold=6, # points lost to exit opening early (default: 6)
endgame_material_threshold=13, # total material to enter endgame
)from ailed.psyche import PsycheCalculator
import chess
calc = PsycheCalculator() # default config
board = chess.Board()
psyche = 0.0 # neutral at game start
board.push_san("e4")
psyche = calc.update_psyche(psyche, board)
print(f"ψ = {psyche:.1f}") # e.g. ψ = 3.2MoveSelector applies a full audio-inspired signal chain to the move probability distribution:
Softmax → Noise gate → Dynamics → Sort + EQ bands → Saturation → Renormalize → Sample
- Noise gate — silences moves below probability threshold
- Dynamics — compressor/expander on probability spread
- EQ bands — moves sorted into 5 quality bands, each with gain multiplier
- Saturation — soft-clips dominant moves
- Renormalize — produces valid distribution
All five parameters interpolate across N psyche anchors. At ψ = −70 (stressed) behavior differs dramatically from ψ = +70 (overconfident).
from ailed.uci.move_selector import MoveSelector
from ailed.uci.eq_curve import PRESETS
import torch, chess
selector = MoveSelector(curve=PRESETS["human"])
board = chess.Board()
legal_moves = list(board.legal_moves)
logits = torch.randn(len(legal_moves))
move = selector.select_move(
legal_moves=legal_moves,
move_logits=logits,
base_confidence=0.8,
psyche=-40.0,
)| Name | Character |
|---|---|
human (default) |
Stressed → noisy. Confident → autopilot. |
human5_symmetric |
Fine-grained psyche response (5 anchors). |
human5_asymmetric |
Wider stress, narrower confidence zone. |
classical |
Tight, positional, disciplined. |
jazz |
Avoids obvious moves, boosts alternatives. |
rock |
Bold, loves extremes (V-shape EQ). |
metal |
Chaotic, everything gets a shot. |
flat |
Bypass — raw probabilities. |
Requires Python 3.11+.
pip install chess pydantic torch
git clone https://github.com/chrnx-dev/ailed-chess
cd ailed-chess
pip install -e .Run the package tests exactly as used for release readiness:
pytest -qMinimal sanity check of psyche + selector behavior:
python - <<'PY'
import chess
import torch
from ailed.psyche import PsycheCalculator
from ailed.uci.move_selector import MoveSelector
board = chess.Board()
board.push_san("e4")
calc = PsycheCalculator()
psi = calc.update_psyche(0.0, board)
selector = MoveSelector()
legal = list(board.legal_moves)
logits = torch.randn(len(legal))
move = selector.select_move(legal_moves=legal, move_logits=logits, base_confidence=0.8, psyche=psi)
print(f"psyche={psi:.2f}, selected={move.uci()}")
PYWorks on top of any move predictor that outputs logits or probabilities:
- Maia2 (convert probability dict to log-probs)
- Stockfish NNUE policy head
- Your custom transformer
- Any neural network with move output
The paper demonstrates this across two engines of vastly different scale (60K-game model vs 169M-game Maia2), showing consistent behavioral signatures from psyche regardless of base model.
This repository contains only the psyche and personality modules.
Full AILED system (released with paper) includes:
- Move predictor (23.5M param transformer)
- Training pipeline
- Lichess bot deployment
- Web dashboard
- Nightly study loop
- Terminal UI
| Contributing | Bug fixes, new psyche factors, EQ personalities, docs |
| Code of Conduct | Contributor Covenant v2.1 |
| Security | Private vulnerability reporting via GitHub advisories |
| Issue templates | Bug report · Feature request · Docs improvement |
AI-generated contributions are welcome. Contributors are responsible for understanding and testing their code. Changes to critical paths (psyche math, phase detection, EQ signal chain) may require human validation before merge — see CONTRIBUTING.md for details.
Please cite both:
- the software release (preferred via GitHub citation tools /
CITATION.cff) - the arXiv paper once public
@misc{ailed2026,
title={Ailed: A Psyche-Driven Chess Engine with Dynamic Emotional Modulation},
author={Resendez Prado, Diego Armando},
year={2026},
eprint={TBD},
archivePrefix={arXiv},
primaryClass={cs.AI}
}Apache 2.0 — see LICENSE.