Blundermind — Bot Controls

Technical Brief
Move-selection mechanics, formulas & architecture

This brief explains what happens under the hood every time a Blundermind bot chooses a move: the pipeline it runs, the formulas each control uses, and how the pieces fit together. Everything described here maps to a control you can see and set in the Bot Controls panel.

1 · Architecture — Move Selection Pipeline

Every bot move passes through the sequential stages below. Each stage can veto, modify, or short-circuit to skip later stages. The stages run in strict order so that think time — computed in Stage 2 — feeds every downstream degradation calculation, and so the final engine-verification stages (8–9) see the move the personality actually chose.

StageNameWhat happens
1Opening BookECO table lookup (preferred) or Lichess Masters (mainline). If a matching move is found, it is played immediately after a brief book delay (400–1200 ms). Skipped once the game leaves known lines.
2Think TimeActual think time for this move is computed from the timing mode, the Hustle attractor, and clock pressure. The result feeds all downstream degradation curves.
3Engine QueryA rough think-time estimate picks the Maia ELO (via curve A, if ELO degradation is on). Maia or LCSF is queried for candidate-move probabilities.
4Distribution FilterAn absolute min-probability floor removes near-zero candidates. Curve B (if the distribution toggle is on) narrows the upper percentile; the Luck attractor shifts the window (§8).
5Attractor ReweightingEach candidate move's probability is multiplied by exp(logBoost), where logBoost is the sum of all active strategic attractor signals. Sets an "applied" flag read by Stage 8.
6Desperation (opt.)If "seek stalemate when losing" is armed and the engine eval confirms a lost position, candidate weights are re-shaped toward low-own-mobility and material-dumping moves (§10).
7Conviction Pick30% of moves: the top-ranked move of the reweighted distribution is played outright (argmax). The other 70%: final temperature applied (base T × phase/pressure modifiers) and one move is sampled.
8CP-Budget AcceptancePersonality bots only: if the pick differs from Maia's most-popular move, a shallow Stockfish probe checks that the pick loses ≤ CP Budget vs. the popular move; otherwise it walks down the preference order, up to 15 candidates deep (§9).
9Degradation GuardBad-Day / distribution-restricted picks are re-checked so a degraded choice can never out-perform Maia's top move — popularity is not quality (§9).
10Hard Floor BackstopAny final pick that differs from the most-popular move — whatever produced it — is capped at the Hard Floor (≥ Budget): if it loses more than Floor cp, the popular move is played instead. Skipped when the Floor is Off (§9c).
Principle #1 — think time drives degradation: Think time is computed first (Stage 2), and the resulting value drives Stages 3–4 through the degradation curves. A Coffeehouse Hustler thinking 0.3 s on a move sees heavy ELO and distribution degradation on that specific move — not an average derived from the remaining clock.
Principle #2 — real centipawns, not probability: Maia probability measures how popular a move is at a given rating, which is not the same as how good it is — at low ratings the two can even anticorrelate. So the CP Budget, the Hard Floor, and all quality guarantees are enforced with actual Stockfish evaluations at Stages 8–10. The probes are fail-open: any timeout leaves the pick untouched, and results are reused between stages so a move never pays for the same evaluation twice.

2 · Move Source Layer

2a · Opening Book

Two independent book modes are available. Either can be active at once; each deactivates permanently the first time it fails to find a candidate move.

ModeSourceBehaviour
PreferredIn-memory ECO table (~3,000 named openings)Matches game history against ECO lines whose ECO code or family prefix is in the bot's slot list. Plays the next move from the highest-priority matching line. Exits the book at maxBookDepth (default 20 half-moves).
MainlineLichess Masters (live)Queries the masters database for the most popular continuation from the current position; falls back to the Lichess community database if there is no masters result. Plays the top-weighted move. Deactivates after the first cache miss or API error.

Book delay: 400–1200 ms random pause to simulate human reading time.

2b · Maia Neural Network

Maia is a series of neural networks (600–2600 ELO) trained to predict human moves at each rating level. The bot queries the appropriate ELO model (determined by degradation curve A) and receives a probability distribution over all legal moves.

2c · Stockfish (SF) Engine

Used when the bot engine mode is set to Stockfish. Skill level runs 1–20. The engine is queried via a Web Worker; MultiPV probes provide the complexity scores used by the Chaos attractor. Single-move quality is guaranteed by the engine-calculated CP budget (§9).

effective level: floor = pressureFloor pressureFloor = max(1, startLevel − round(timePressureMaxDrop / 50)) timePressureMaxDrop = base ELO − curve A minimum (derived from the visible curve) (or the pressure-level slider when no max-drop is set)

Time-degradation path (highest priority wins):

2d · Level-Controlled Stockfish (LCSF)

LCSF uses Stockfish with controlled skill variation rather than Maia probability distributions. Each call's skill level is drawn from a distribution around the target level:

p(offset) distribution: −2: var2/2 (e.g. 10% at ±2 → 5% chance) −1: var1/2 (e.g. 30% at ±1 → 15% chance) 0: 1 − var1 − var2 (remaining probability) +1: var1/2 +2: var2/2 Level clamped to [1, 20]

var1 (±1 spread): 0–50%. var2 (±2 spread): 0–20%. Setting both to 0 uses the exact target level every move.

3 · Think Time Calculation

Think time is calculated before the engine is queried. The result drives all downstream degradation curves, so attractors that shorten think time (the Hustler) directly increase move-quality degradation for that specific move.

3a · Timing Modes

ModeFormula / behaviour
InstantReturns 0 ms. No delay whatsoever.
FixedthinkMs = fixed delay. Capped at min(fixed, clockMs × 0.08) when the clock is under 30 s. Capped at clockMs − 3 s when flagging is disabled.
Pace (default)baseSec = (5 × 60) / pace. complexity = min(1 + entropy × 0.35, 2.5). thinkMs = baseSec × complexity × 1000. Pace slider: 5–120 (moves per 5-minute equivalent).
Complexitycplx = SF complexity score if available, else min(1, entropy / 4). mult = cplxMin + cplx × (cplxMax − cplxMin). thinkMs = cplxBase × mult × 1000. Explicit base / min-mult / max-mult sliders in the panel.
Mirroravg = rolling average of human move times. thinkMs = avg × jitter(0.8–1.2) × (1 + mirrorOffset%/100). Falls through to Complexity/Pace when no human moves are recorded yet.

3b · Think-Time Modifiers (applied in order)

ModifierConditionEffect
Premove firesA premove is armed and still legal after the human's movePlays instantlybotThinkTime() is never called. See 3c.
Weaponizer shortcutEnabled AND the opponent's clock ≤ botWeaponizerTriggerMs (default 15 s)Return botWeaponizerMinMs (default 0 = instant). Absolute time remaining, not a lead.
Move blinkBlink flag AND entropy < 0.5 (forced/obvious positions)200–500 ms random; skips further calculation — including the timing mode
Clock-pressure mirroringClock-mirror flag AND oppClock < botClock × 0.6thinkMs ×= 0.5
Hustle attractorvalue v ∈ [−5, +5]thinkMs ×= (1 − v × 0.15). At +5: ×0.25 (very fast). At −5: ×1.75 (very slow).
ReconsiderationReconsider flag, 15% random chancethinkMs ×= 1.5 to 2.5 (hesitation pause)
Base jitterAlways appliedthinkMs ×= uniform(0.8, 1.2)
Clock cap (low clock)clockMs < 30,000 msthinkMs = min(thinkMs, clockMs × 0.08)
Flagging guardCan-flag = falsethinkMs = min(thinkMs, max(200, clockMs − 3000))
Global capAlways appliedthinkMs = max(200, min(cap, thinkMs)). Cap = max(6 s, min(45 s, startClock × 0.02)) — scales with time control.

Position entropy is the Shannon entropy over Maia move probabilities: H = −Σ p·log(p). Higher entropy = more complex / branching position.

Precedence matters. The table above is evaluated top-down and the first match wins, so Move blink is checked before the timing mode. Leaving blink enabled alongside Fixed interval therefore lets any near-forced position ignore the duration the user set — a bot configured for 22 s still answers obvious recaptures in ~0.3 s. Selecting Fixed interval in the panel therefore switches blink off and marks the row with the reason; re-enabling it by hand is respected.

3c · Premove Simulation

Human speed-chess players premove: they queue a reply before the opponent has moved, and it fires the instant the opponent's move lands. It buys seconds but commits blind — which is what makes it both realistic and exploitable. The bot reproduces this, so players can practise baiting a premove and punishing it.

Arming runs in botPremoveArm() immediately after the bot's own move, while the human is on move. It is fully asynchronous and never blocks input; a human move made mid-computation discards the result through a generation guard (_botPremoveGen). Two Maia passes:

PassQuestionGate
1What will the human play from the current position?Top-move confidence must be ≥ botPremoveMinPct, else no commitment
2Apply that predicted move; what should the bot play in the resulting position?That answer is locked in as the premove

When the human moves, botPremoveTryFire() tests the locked move against the real board:

SettingDefaultBehaviour
botPremoveEnabledfalseMaster switch. Requires the Maia 3 model — the prediction runs on it.
botPremoveMinPct85Minimum Maia confidence in the predicted human reply. The setting that decides whether the bot reads as fast or as broken. 85% ≈ near-forced replies only, matching real play. Far below ~60% it premoves into positions no human would, and play looks erratic.
botPremoveRatePct80Percentage of the moves that already cleared the confidence bar — not a percentage of all moves.
botPremoveOnlyLowClockfalseOptional master gate restricting premoving to time scrambles.
botPremoveOppClockSecs30Trigger: opponent at or below this. The aggressive reason humans premove — keep their clock burning and play for the flag. 0 = off.
botPremoveClockSecs30Trigger: the bot itself at or below this, out of its own desperation. 0 = off.
botPremoveBustDelayMs2000The "didn't see that coming" stall after a bust.

The two clock triggers are independent — either one alone arms a premove. Both clocks read null in untimed games, so the gate simply never opens there. Per-game telemetry (armed / fired / busted) is surfaced live in the panel, and premove state is shown in the collapsed Move Timing header, because a bot replying instantly looks like a malfunction if you don't know the setting is on.

The predictor uses the bot's own Maia rating band to guess the human's move, since that is the only band available to it. This is realistic — a human premoving is also guessing from their own understanding — but it does mean a 2400 bot anticipates a 900's replies as though they were a 2400's.

4 · Time-Pressure Play Degradation

Two independent, user-editable spline curves control how the bot's play quality degrades as think time per move decreases. Curve A's control points are seeded on a closed-form time–rating model (below); curve B's on a sigmoid. Both use log-scale interpolation on the X axis (think time in seconds), so visually equal spacing on the panel matches the mathematical interpolation.

Each curve has its own ON/OFF toggle beside its chart, so the two mechanisms are fully independent: ELO degradation (curve A) reduces the Maia ELO the engine is queried at, while distribution restriction (curve B) keeps the ELO fixed but narrows which slice of Maia's move distribution is available. You can run either alone, both together, or switch both off for constant strength. When a curve is off it is flat at its reference value (curve A at the configured ELO, curve B at 100%), and its slider no longer re-anchors the other curve.

Curve interpolation (log-x space): pts sorted by x. For xSec in [pts[i].x, pts[i+1].x]: t = (ln(xSec) − ln(pts[i].x)) / (ln(pts[i+1].x) − ln(pts[i].x)) y = pts[i].y + t × (pts[i+1].y − pts[i].y)

Curve A — ELO Degradation

Maps think time (s) → effective Maia ELO. At the time control's nominal pace the bot queries its configured ELO. As think time shrinks, the ELO drops, producing weaker move distributions.

Default: the Regan time–rating model — a power-law fit of IM Kenneth Regan's rating-loss vs time-control data, from his Perpetual Chess Podcast interview with Ben Johnson. Regan's x-axis (total minutes for a 60-move game) is numerically identical to seconds per move, so the anchor uses EXPECTED_MOVES = 60:

D(s) = 339 − 1442 · s^(−0.283) // rating loss, ≈0 at 165 s/move anchorSec = baseSec / 60 + incrementSec // 15+0 → 15, 5+3 → 8; untimed → flat curve floor = max(600, E₀ − maxDrop) // Max ELO drop slider caps the fall seed(s) = clamp(E₀ + D(s) − D(anchorSec), floor, min(E₀, 2600))

The rough think-time estimate (computed before the Maia query) is used for ELO selection. The precise think time (after the engine responds) is used for curve B and temperature.

Hybrid Maia slots carry their own ratings, so the curve's absolute ELO (anchored to the main Elometer) doesn't apply directly. Instead each slot takes the curve's relative drop — the spline's relaxed top minus its value at this think time — subtracted from the slot's own ELO. Relaxed think = no drop; under pressure all slots degrade by the same amount, preserving their identity gap.

Curve B — Distribution Cutoff

Maps think time (s) → upper-percentile cutoff of the candidate list. 100% = full distribution (top candidate down to last). As think time falls the cutoff drops, forcing the bot to pick from a narrower top slice of the distribution (higher-probability, safer-looking moves).

effectiveUpperPct = min(botDayUpper, evalPressureCurve(curveB, thinkSec))

Time-Pressure Temperature Boost

In addition to the ELO and distribution effects, low think time raises the sampling temperature, making move selection more random under pressure:

boost = { steady: 0.0, normal: 1.0, panicky: 2.5 }[timePressureMode] if curveB present: fraction = 1 − evalPressureCurve(curveB, thinkSec) / 100 else: fraction = max(0, 1 − thinkSec / 30) # linear fallback effectiveTemp = baseTemp + fraction × boost

Time-pressure modes: "steady" (no boost), "normal" (+1.0 max), "panicky" (+2.5 max).

5 · Temperature & Move Sampling

After attractor reweighting, the conviction pick decides how the move is chosen: 30% of the time the bot plays the top-ranked move of the reweighted distribution outright (argmax — its personality's honest first choice), and 70% of the time one move is sampled using temperature-scaled probabilities. This keeps personalities assertive without making them perfectly predictable.

if rand() < 0.30 → play argmax of reweighted distribution else: scaled[m] = prob[m] ^ (1/T) total = Σ scaled[m] sample r ~ Uniform(0, total) return first m where cumulative sum ≥ r

At T = 1.0 the original Maia probabilities are used directly. T < 1 sharpens the distribution (top move more likely); T > 1 flattens it (unlikely moves become relatively more likely). T is clamped to ≥ 0.1 to prevent division issues.

5a · Base Temperature Presets

PresetT valueBehaviour
Focused0.4Strongly favours the top 1–2 Maia moves
Neutral1.0Samples Maia's full distribution as-is
Varied1.8Wider sample — more surprise choices
Wild3.0Even low-probability moves get meaningful weight

5b · Hustler Phase Temperature

When the Coffeehouse Hustler personality is active, the base temperature is replaced by a phase-sensitive curve:

piecesLeft = count of non-pawn, non-king pieces on board fraction = clamp(1 − (piecesLeft − 4) / 10, 0, 1) # 0 at 14 pieces (opening), 1 at ≤4 pieces (endgame) T = 5.0 + fraction × (0.6 − 5.0) = 5.0 in opening → 0.6 in endgame

14 non-pawn/non-king pieces = full material. At 4 or fewer such pieces, the endgame temperature floor of 0.6 is reached.

5c · Complexity-Adjusted Temperature

For Maia mode, the base temperature can optionally be scaled by position complexity (MultiPV evaluation spread):

if complexityScore is null → return baseT cplxFactor = 0.8 + complexityScore × 0.4 # 0.8 at simple, 1.2 at complex return baseT × cplxFactor

The complexity score is the normalized standard deviation of the top-N Stockfish evaluations, ranging 0 (all moves equal) to 1 (widely spread evaluations).

6 · Strategic Attractors (Centipawmeter)

Strategic attractors reweight each candidate move's probability by multiplying it by exp(logBoost). The total logBoost for a move is the sum of all active attractor contributions. Attractors are zero at centre and scale linearly with slider value (−5 to +5).

6a · CP Budget, Hard Floor and Scale Factor

The CP (centipawn) budget (0–300), shown on the Centipawmeter dial, is the personality's allowance and does double duty. First, it is a master gain knob: it controls how strongly attractors push the distribution, regardless of the individual slider positions (the 150-cp-per-log-unit scale below). Second, it is a real, engine-calculated centipawn ceiling on how far a personality pick may fall below Maia's most-popular choice — enforced at Stage 8 (§9a).

The Hard Floor (Budget–1000 cp, or Off) is a separate slider directly beneath the Budget, always ≥ Budget: it is the absolute backstop on every other selection mechanism — temperature sampling, the Luck window shift, Bad Day, curve-B time-pressure restriction — enforced at Stage 10 (§9c). At Off, nothing is capped. The Engine panel shows both values beside the Elometer with a one-click ELO-based recommendation. Budget and Floor have separate curves: Budget ≈ (2700 − ELO)/15 clamped to 10–120 cp (persistent style spend — a fraction of the rating's typical centipawn loss), Floor ≈ (2900 − ELO)/4.5 clamped to 80–520 cp (the worst single move still in character for the rating).

totalAbs = Σ |v| for all attractor and piece sliders scale = cpBudget / (totalAbs × 150) if cpBudget > 0 and totalAbs > 0 = 0 otherwise For each move: prob_new = prob × exp(logBoost) where logBoost = Σ (attractor_v × scale × signal) At cpBudget = 150 with one slider at 1 and all others at 0: scale = 1/150, signal = 1 → logBoost = 1 → exp(1) ≈ 2.7× boost

Allocating 150 cp of budget on a single maximally-set attractor produces roughly a 2.7× probability boost on moves that fully satisfy that attractor.

6b · Attractor Formulas

AttractorLeft (−) / Right (+)Signal formulaNotes
Chaos Agent / Simplifier− Simplifier → low-σ moves
+ Chaos Agent → high-σ moves
logBoost += chaosVal × scale × signal (signal from MultiPV σ)Requires a MultiPV Stockfish probe. σ = std. dev. of top-N eval scores.
Complicate / Simplify winning− Simplify when winning
+ Complicate when winning
Active when eval ≥ +100 cp. Boosts moves that increase or decrease MultiPV variance.Only fires in clearly winning positions. Combines with Chaos Agent for full effect.
Space Cadet / Space Waster− Space Waster → cede space
+ Space Cadet → reduce weak sq.
delta = currentWeakSq − simWeakSq
logBoost += v × scale × tanh(delta/5)
Full-board attack map on the simulated position. Captures discovered attacks and blocking moves. delta > 0 = fewer weak squares after the move.
Fort Knox / Glass Cannon− Glass Cannon → exposed pieces
+ Fort Knox → more defenders
delta = simTotalDefs − currentDefs
logBoost += v × scale × tanh(delta/3)
Sum of defensive coverage across all bot pieces, computed on the simulated position.
Gambito / Gambit Shy− Gambit Shy → avoid sacrifices
+ Gambito → follow gambit lines
Opening only (< 20 half-moves). ECO: logBoost += v × scale if the move is an ECO gambit continuation. Fallback: pawn to an attacked, undefended square.ECO gambit-line match takes priority; structural fallback if ECO data is not yet loaded.
Trade Seeker / Trade Avoider− Trade Avoider → no captures
+ Trade Seeker → captures
Capture: logBoost += v × scale
Threat: logBoost += v × scale × tanh(newThreats / 2)
Non-captures scored by the number of new opponent-piece threats created from the destination square.
Rigid / Loose Pawn Structure− Loose → open mobile structure
+ Rigid → tighter structure
Own pawn moves only. delta = currentPenalty − simPenalty
logBoost += v × scale × tanh(delta)
Penalty = pawn islands + doubled pawns + isolated pawns. delta > 0 = move tightens structure.
Coffeehouse Hustler / Overthinker− Overthinker → longer think time
+ Hustler → shorter think time
Applied to think time (not prob): thinkMs ×= (1 − v × 0.15)At +5: ×0.25 (4× faster). At −5: ×1.75 (75% slower). Feeds into the pressure curves as actual think time.
Luck / Bad Day− Good day → top of distribution
+ Bad day → bottom of distribution
Shifts the quality-range window:
lo = botDayLower − v × 4
hi = pressureUpper − v × 4
See §8 Move Quality Range for the full description.
Panicky / Calm under pressure− Calm → no temperature boost
+ Panicky → larger T boost under pressure
Modifies the time-pressure boost mode (steady / normal / panicky).See §4 Pressure Temperature Boost.
Attacker / Peacemaker− Peacemaker → quiet positions
+ Attacker → maximize threats
totalThreats = Σ attacks on each opp. piece
logBoost += v × scale × tanh(threats/6)
Full-board attack map on the simulated position. Counts attack coverage across all opponent pieces.

All attractor signals that use tanh map their input to a smooth −1..+1 range, preventing any single move from receiving an unbounded boost.

7 · Piece-Type Attractors

Six independent sliders (−5 to +5) bias the bot toward or away from moving each piece type. The formula is the same for all six:

logBoost += pieceVals[pieceType] × scale

The piece type is determined from the moving piece before the move. The signal is 1 (or 0 for no boost) — there is no continuous signal function as with most strategic attractors. Scale is shared with the strategic attractors, so the CP budget controls piece-attractor strength alongside everything else.

PieceLeft (−) behaviourRight (+) behaviourBest combined with
♙ PawnSuppress pawn movesPrioritise pawn advancesRigid structure (connected chains)
♘ KnightDe-prioritise knight hopsKnight manoeuvres firstRigid structure (closed positions)
♗ BishopKeep bishops passiveDiagonal play, bishop pairLoose structure (open diagonals)
♖ RookPassive rooks, avoid early tradesFile seizure, rook liftsOpen files, trade-seeker
♕ QueenConservative queen, avoid exposureActive queen, queen-led attacksChaos Agent (keeps complications)
♔ KingKing safety, stay castledKing activity, endgame aggressionEndgame positions, low piece count

8 · Move Distribution Filter & Luck

The Move Distribution Range dual slider selects which segment of the Maia probability-ranked candidate list the bot samples from. Candidates outside the [lower, upper] percentile window are excluded before temperature sampling and attractor reweighting.

Popularity is not engine quality — Maia probability is how often players at the selected rating choose a move. A strong move few players see can sit low in the distribution, which is why the rare tail is where creative, unconventional play lives; real move-quality enforcement is the engine checking in §9.

8a · Percentile Band Filtering

sorted candidates by probability (highest first) cumulative probability sum = total band.lo = (lo/100) × total band.hi = (hi/100) × total Include move m if: cumulative_end(m) > band.lo AND cumulative_start(m) < band.hi

Default: lo = 0%, hi = 100% (full distribution). Setting lo = 20%, hi = 60% samples only the middle tier — avoiding the top moves (too accurate) and the worst moves (random blunders).

8b · Luck Attractor Shift

The Luck attractor shifts both bounds of the quality window simultaneously:

lo_effective = botDayLower − luckVal × 4 hi_effective = pressureUpper − luckVal × 4 luckVal > 0 (Bad day): window shifts down → samples lower-ranked moves → worse play luckVal < 0 (Good day): window shifts up → samples top-ranked moves → sharper play

pressureUpper is the curve-B degraded upper bound, so the luck shift compounds with time-pressure effects.

8c · Min-Probability Filter

A single absolute-popularity filter prunes the candidate list before the quality range is applied. It is an honest distribution control expressed as a percentage — it removes near-zero candidates and makes no claim about move quality (centipawn enforcement is the engine-calculated check in §9).

absFloor = minProbPct / 100 keep only moves with prob ≥ absFloor (if that empties the list, keep the single most-popular move)

9 · CP-Budget Acceptance, Degradation Guard & Hard Floor

Maia probability is popularity, not quality. Three post-pick stages use a shallow Stockfish probe to hold move selection to a real centipawn standard. All are fail-open — any probe failure or timeout leaves the pick unchanged — and probe results are shared between stages so no move pays for the same evaluation twice. The probe reuses the MultiPV complexity machinery with a searchmoves restriction (depth ~10), evaluating one wide batch in a single search rather than one call per move.

9a · CP-Budget Acceptance

Turns the Centipawmeter into a real centipawn ceiling. When personality reweighting produced a pick different from the most-popular move, the pick, the popular move, and up to 15 of the personality's next favourites are evaluated together in one probe. The pick is accepted only if it loses no more than the CP Budget versus the popular move; otherwise the bot walks down its own preference order until one fits, falling back to the popular move itself (0 cp by definition). The wide walk means a rare move from deep in Maia's tail still gets a genuine chance to be played — it just has to prove it can afford it.

topMove = argmax Maia probability if pick == topMove or reweighting not applied → keep pick evals = SF probe over [topMove, pick, next favourites] for m in [pick, ...favourites]: if evals[topMove] − evals[m] ≤ cpBudget → play m else → play topMove

Stalemate-seeking picks (§10) are deliberately exempt — throwing material is the whole point, so the budget must not veto them.

9b · Degradation Guard

Guarantees that a deliberately degraded choice never accidentally out-performs Maia's top move. It is active when Grandmaster Bad Day is on, or when the time-pressure distribution restriction (curve B) is actively narrowing the window. The chosen move and the most-popular move are evaluated, and whichever scores worse is played.

active if botBadDayMode OR curve-B is narrowing now if pick == topMove → keep evals = SF probe over [pick, topMove] play the move with the LOWER eval

Consequence: Grandmaster Bad Day picks the lowest-probability move above its configured floor, but if that move happens to be strong (a shot few players see), the guard swaps it back to the mainstream move — the bot is never accidentally brilliant.

9c · Hard Floor Backstop

The last stage in the pick flow. Whatever produced the final pick — temperature sampling, the Luck shift, Bad Day, curve-B restriction, or plain sampling variance — if it differs from the most-popular move, it must not lose more than the Hard Floor's centipawns against it; otherwise the popular move is played instead. Picks already verified within the Budget skip this check (Budget ≤ Floor by construction), stalemate-seeking picks are exempt, and a Floor set to Off disables the stage entirely — the honest setting for beginner bots that should be able to hang pieces.

active if Floor < Off AND pick ≠ topMove AND not budget-verified AND not stale-seek evals = reuse guard's probe if available, else SF probe over [pick, topMove] if evals[topMove] − evals[pick] > floor → play topMove else → keep pick

10 · Draw & Desperation Behaviours

Four opt-in behaviours (Personality section → "Draws & Desperation" tab) let a bot handle draw offers, offer draws, and swindle when lost — all judged the way a human of the bot's strength would, not at raw engine accuracy.

10a · Perceived Evaluation

Every draw decision uses the bot's perceived advantage, not Stockfish's. A shallow eval probe gives the true centipawn advantage from the bot's side; that value is then blurred by rating-scaled Gaussian noise plus a mild self-optimism bias. A novice genuinely misjudges; a master barely does.

sigma(elo) = 15 + 700 × exp(−elo / 500) ≈ 225 cp at 600 ELO, ≈ 50 cp at 1500, ≈ 19 cp at 2600 perceived = trueAdv + gauss() × sigma + 0.25 × sigma

So a 700-rated bot can believe a lost position is fine (and decline your draw, or offer one from a losing position it thinks is level); a 2400 is almost never fooled.

10b · Accepting & Offering Draws

BehaviourControlRule
Accepts draw offerstoggle + accept-margin (0–300 cp)On your ½ offer the bot accepts iff its perceived advantage ≤ margin. Declines are wordless (a human just plays on — no eval is announced), and a bot that knows it is worse may still decline, hoping for a blunder.
Offers drawstoggle + level-band (±cp) + from-moveAfter its move, in a position that feels level (|perceived| ≤ band) and past the move threshold, it occasionally offers — never twice within 12 plies.

10c · Clock Awareness

Draw decisions read the clock (only when the increment is under 10 s, since nobody flags with a healthy increment):

Untimed games and games with a ≥ 10 s increment ignore the clock entirely.

10d · Seek Stalemate When Losing

A desperation swindle mode. Once the engine eval says the bot is worse than the threshold and the game has passed the configured move number, candidate weights are re-shaped toward the classic stalemate-trap recipe:

active if staleSeek AND fullmove ≥ fromMove AND eval ≤ −thresholdCp weight ×= exp( mobilityBoost + desperadoBoost )

These picks are exempt from the §9 CP-budget check. Arming this behaviour also forces the complexity/eval probe every move so the trigger stays current.

11 · Preset Personalities

Each preset personality is a named collection of attractor values; loading one sets all sliders to those values (−5 to +5 scale). Some presets also reconfigure the engine, opening repertoire, or draw behaviour and confirm first via a dialog. The table below shows each personality's notable settings.

PersonalityTagNotable attractor valuesCharacter
Captain EntropyChaoschaos +4, compwin +4, gambito +3, trade +2, spacecadet +2, hustle −3, structure −3Seeks complications, gambit lines, and tactical chaos. Thinks longer per move (hustle −3).
NormOrderstructure +4, fortkx +3, spacecadet 0, chaos −4, compwin −4, gambito −2, hustle +2Closes positions, defends solidly. Simplifies when winning. Slightly faster pace.
Attacky McTackersonCapturestrade +4, attacker +3, chaos +2, hustle −2, structure −1Favours captures and threats. Slightly deliberate (hustle −2).
OverthinkerMethodicalstructure +3, spacecadet +3, fortkx +2, hustle −4, pressure −3, chaos −3, compwin −2Maximises board control and structure. Thinks long on every move (hustle −4). Likely to accumulate time pressure in classical games.
Coffeehouse HustlerFasthustle +5, pressure +2, luck +2, gambito +2, structure −2Plays fast and loose. Phase temperature: T = 5 opening → T = 0.6 endgame. At hustle +5: think time ×0.25 of baseline.
The BlundererHumanluck +3, pressure +5Plays normally but cracks unpredictably under pressure. Luck shifts toward lower-ranked moves.
The HoarderMaterialgambito −3, trade −3, fortkx +2, chaos −2, hustle +2, luck −2Never sacrifices material. Avoids trades and gambits. Slightly faster pace.
Pawn Chain GangPawnsstructure +5, fortkx +1, hustle +1, chaos −1, compwin −2, gambito −1Maximises pawn structure. Slightly faster pace.
Spite CheckCheckschaos +3, compwin +2, attacker +3, trade +2, hustle −2Always prefers checking moves. Builds up attacks. Slower, deliberate pace (hustle −2).
Clock WatcherClockcompwin −2, trade −1, pressure −3Conservative when ahead on time, reckless when behind. Calm under pressure (pressure −3).
Grandmaster Bad DayHumanluck +5, pressure +2 (Maia @ 2400, picks lowest-prob move above 4%)A strong player having an off day. The §9 degradation guard prevents it from ever accidentally choosing a strong move — never brilliant by mistake.
Drunken MasterHumanattacker +4, chaos +3, gambito +3 (Hybrid: 50% Maia 2400 + 50% Maia 1000 per move)Each move rolls one of two Maia strengths — brilliant one move, blundering the next. A plain hybrid config, so the two slots are freely re-tunable.
The DrawmeisterHumantrade +4, fortkx +3, structure +2, chaos −4, gambito −3, attacker −3 (accepts + offers draws)Plays for the half point: simplifies, fortresses up, offers/accepts draws (§10), and installs solid drawish repertoires for both colours (London / Exchange Slav / Four Knights; Petroff / Berlin / Slav).

Hustle attractor sign convention: +5 = Coffeehouse Hustler (fast, think time ×0.25). −5 = Overthinker (slow, think time ×1.75).

12 · Time Controls

Time controls are selected from the bot panel grid, divided into Blitz (≤ 5 min), Rapid (10–30 min), Classical/Correspondence (60 min+), Infinite (untimed), and a special 90+30 column for the FIDE/Candidates format.

FormatBase timeIncrementBonusBonus atInc from
Bullet 1+01 min0 s
Blitz 3+23 min2 s
Blitz 5+05 min0 s
Rapid 10+010 min0 s
Rapid 15+1015 min10 s
Classical 30+030 min0 s
Classical 60+060 min0 s
Classical 90+090 min0 s
FIDE 90+30 *90 min30 s+30 minMove 40Move 41
Untimed ∞0 s

* 90+30 (FIDE / Candidates): 90 minutes for the first 40 moves, then +30 minutes added to both clocks at move 40. A 30-second increment applies from move 41 onward. A guard flag prevents the bonus from being awarded twice.

Bonus Time Implementation

fullMoveNum = ceil(gameMovesAlgebraic.length / 2) if tc.bonusSecs AND fullMoveNum ≥ tc.bonusAtMove AND NOT bonusApplied: clockTimeW += bonusSecs (capped at 59940 s) clockTimeB += bonusSecs bonusApplied = true if clockInc > 0 AND fullMoveNum ≥ tc.incFromMove: add increment to the side that just moved

13 · Human Behaviour Flags

Human behaviour flags add realistic timing irregularities and clock-pressure responses. Each flag is independently toggleable, and they modify think time or move selection in specific situations.

FlagWhen activeEffect
Move BlinkMaia mode only. Position entropy < 0.5 (forced or obvious move)Returns a 200–500 ms random pause immediately, skipping the full think-time calculation. Simulates a human instantly playing the obvious recapture or forced reply.
Reconsider15% random chance per moveMultiplies computed think time by 1.5–2.5×. Simulates the human hesitation of starting to play a move then reconsidering.
Clock MirrorOpponent clock < bot clock × 0.6Halves think time when the opponent is significantly lower on time. Simulates a human speeding up to maintain a clock advantage.
Clock WeaponizerOpponent's clock at or below the trigger (default 15 s)Returns the minimum move time (default 0 ms) AND drops Stockfish to the floor level. Maximises pressure when the opponent is close to flagging. The trigger is absolute time remaining, not a lead — 15 s is flaggable in any format — and is set by the Opponent Clock slider, with a separate Min Move Time slider for the floor.
Can FlagAlways on or offWhen OFF: think time capped at max(200 ms, clockMs − 3000 ms) to stop the bot flagging itself. When ON: no such safeguard.

14 · Appendix — Play From Any Position

Beyond bot configuration, games are not locked to the standard start. Any finished game, or any loaded PGN, can be reviewed move-by-move and resumed as a new game — against a bot or a friend — from any position along the way.

14a · Review & Replay

14b · Resume Play From Here

All ELO values are relative playing strengths within the Blundermind engine; they are not official FIDE ratings.  ·  Back to top ↑