Skip to content
Home » News » Revolution 2.41 dev-150925

Revolution 2.41 dev-150925

revolution chess engine

Summary

  • Introduced a blitz-specific slow mover adjustment that conservatively reduces time usage for short controls with minimal increments, helping maintain search stability under fast time limits
  • Documented the new blitz time heuristic in the README to inform users about its effect in rapid games
  • Recorded the addition in the changelog for visibility in upcoming release notes.

Strategy sprt test

Perfect—here’s a step-by-step, one-patch-at-a-time plan for Codex.
Each step:

  • adds one switchable change (UCI option defaults to off)
  • gives exact file edits (minimal, copy-paste)
  • includes a quick test you can run to compare before/after

Pre-condition for fair A/B (do this once before XP-1)
Build both BASE and DEV as x86-64-sse41-popcnt on Ivy Bridge.

CMake (add to target):
-O3 -DNDEBUG -fno-exceptions -fno-rtti -msse4.1 -mpopcnt -mno-avx -mno-avx2 -mno-bmi2 -mtune=ivybridge
Makefile profile (ARCH=x86-64-sse41-popcnt):
-msse4.1 -mpopcnt -mno-avx -mno-avx2 -mno-bmi2 -mtune=ivybridge


Experiment Harness (add once)

FILES: src/ucioption.cpp, src/search.h, src/search.cpp (or your nearest equivalents)

  1. Define a small config struct & globals
// search.h
struct XPToggles {
    bool XP1_TimeClamp = false;
    bool XP2_AspWide   = false;
    bool XP3_LMRCons   = false;
    bool XP4_NMPSoft   = false;
    bool XP5_LMPFutil  = false;
    bool XP6_OrderHist = false;
    bool XP7_TTPolicy  = false;
    bool XP8_QSCap     = false;
    bool XP9_EvalSafe  = false;
    bool XP10_RootBias = false;
};
extern XPToggles XP;
  1. Declare options (default OFF) and mirror into XP on init
// ucioption.cpp (near other options)
UCI::Option<bool> XP1_TimeClamp("XP1 TimeClamp", false);
UCI::Option<bool> XP2_AspWide("XP2 AspWide", false);
UCI::Option<bool> XP3_LMRCons("XP3 LMRCons", false);
UCI::Option<bool> XP4_NMPSoft("XP4 NMPSoft", false);
UCI::Option<bool> XP5_LMPFutil("XP5 LMPFutil", false);
UCI::Option<bool> XP6_OrderHist("XP6 OrderHist", false);
UCI::Option<bool> XP7_TTPolicy("XP7 TTPolicy", false);
UCI::Option<bool> XP8_QSCap("XP8 QSCap", false);
UCI::Option<bool> XP9_EvalSafe("XP9 EvalSafe", false);
UCI::Option<bool> XP10_RootBias("XP10 RootBias", false);

// after options are created (engine init):
XP.XP1_TimeClamp = XP1_TimeClamp;
XP.XP2_AspWide   = XP2_AspWide;
XP.XP3_LMRCons   = XP3_LMRCons;
XP.XP4_NMPSoft   = XP4_NMPSoft;
XP.XP5_LMPFutil  = XP5_LMPFutil;
XP.XP6_OrderHist = XP6_OrderHist;
XP.XP7_TTPolicy  = XP7_TTPolicy;
XP.XP8_QSCap     = XP8_QSCap;
XP.XP9_EvalSafe  = XP9_EvalSafe;
XP.XP10_RootBias = XP10_RootBias;

XP-1 — Time clamp (fast-TC stabiliser)

Goal: Minimum think time + panic margin + adaptive overhead.

FILES: src/timeman.h, src/timeman.cpp

// timeman.h
struct TimeModel {
    int64_t min_think_ms = 30;
    int64_t move_overhead_ms = 20;
    int64_t panic_margin_ms = 80;
};
extern TimeModel GTime;
// timeman.cpp (in compute_move_time or equivalent)
if (XP.XP1_TimeClamp) {
    int64_t base = std::max<int64_t>(GTime.min_think_ms, alloc_for_this_move);
    int64_t dyn_overhead = GTime.move_overhead_ms;
    if (inc_ms < 200) dyn_overhead += 10;
    if (nodes_last_move == 0 || bestmove_changed_often) dyn_overhead += 10;

    int64_t budget = std::max<int64_t>(GTime.min_think_ms, base - dyn_overhead);
    budget = std::min<int64_t>(budget, std::max<int64_t>(0, time_left_ms - GTime.panic_margin_ms));
    return budget;
}

Quick test:
fastchess ... -each tc=10+0.1 -games 300 -sprt elo0=0 elo1=6 "opt.XP1 TimeClamp"=true


XP-2 — Aspiration window predictable widening

FILE: src/search.cpp (root iteration)

if (XP.XP2_AspWide) {
    int alpha = bestScore - 16, beta = bestScore + 16;
    int fh = 0, fl = 0;
    while (true) {
        Score s = searchRoot(depth, alpha, beta);
        if (s <= alpha) { alpha = s - (32 << std::min(++fl, 3)); continue; }
        if (s >= beta ) { beta  = s + (32 << std::min(++fh, 3)); continue; }
        break;
    }
} else {
    // existing code path
}

Test: same as above; enable only XP2.


XP-3 — Conservative LMR schedule

FILE: src/search.cpp (or lmr.cpp)

inline int xp3_reduction(bool pv, bool cut, bool improving, int depth, int moveCount) {
    int r = (moveCount > 3 && depth > 2) ? int(0.75 + log(moveCount)*log(depth)) : 0;
    if (pv) r -= 1;
    if (improving) r -= 1;
    if (depth <= 6) r = std::max(0, r - 1);
    return std::clamp(r, 0, depth - 1);
}

// use:
int r = XP.XP3_LMRCons ? xp3_reduction(pv, cut, improving, depth, moveCount)
                       : currentReduction(...);

// Also: guard non-reduction cases when XP3 is on:
if (XP.XP3_LMRCons) {
    if (isTTMove || isCheck || isCapture || isEvasion || (firstQuietAfterTT && cutNode))
        r = 0;
}

Test: enable XP3 only.


XP-4 — Softer Null-Move Pruning, zugzwang guard

FILE: src/search.cpp

if (XP.XP4_NMPSoft && !pv && !inCheck && depth >= 3 && !inZugzwangLike(pos)) {
    int R = 3 + depth / 8;
    Score margin = 80 + 8 * depth;
    if (eval >= beta + margin) {
        makeNullMove(pos);
        Score s = -search(pos, depth - 1 - R, -beta, -beta + 1, NON_PV);
        undoNullMove(pos);
        if (s >= beta) return s;
    }
}

Test: enable XP4 only.


XP-5 — LMP/Futility gates (SEE/history-aware)

FILE: src/search.cpp

// LMP for quiets
if (XP.XP5_LMPFutil && !pv && !inCheck && !isCapture(m) && !givesCheck(m)) {
    if (depth <= 8 && moveCount > lmpLimit[depth] && see(m) < 0 && hist(m) < histCutoff)
        continue;
}

// Futility
if (XP.XP5_LMPFutil && !pv && !inCheck && depth <= 7 && !isCapture(m) && !isPromotion(m)) {
    Score margin = 120 + 80 * depth;
    if (eval + margin <= alpha) continue;
}

Test: enable XP5 only.


XP-6 — Move ordering + bounded history updates

FILES: src/movepick.cpp, src/history.{h,cpp}

  • Ordering when XP6 is ON: TT → good captures (SEE≥0) → killers(2) → countermove → quiets(hist/CMH) → bad captures.
  • Bound history updates:
if (XP.XP6_OrderHist) {
    // on cutoff for a quiet
    int bonus = std::min(2000, 32 * depth * depth);
    history[from][to] = std::clamp(history[from][to] + bonus, -32767, 32767);

    // on quiet fail-low
    int malus = std::min(2000, 16 * depth);
    if (quiet_fail_low)
        history[from][to] = std::clamp(history[from][to] - malus, -32767, 32767);
    // killers: store only if quiet and != TT move
}

Test: XP6 only.


XP-7 — TT replacement: deeper/EXACT/newer preferred

FILES: src/tt.h, src/tt.cpp

// tt.h
struct TTEntry { Key key; int16_t score; uint8_t depth, bound, gen; Move best; };
extern uint8_t TTGen; // ++ at each root
// tt.cpp
static TTEntry* select_victim(Bucket& b, int depth, uint8_t bound, uint8_t gen) {
    TTEntry* v = &b[0];
    for (auto& e : b) {
        bool empty = (e.key == 0);
        bool older = (e.gen != gen);
        bool shallower = (e.depth < v->depth);
        bool preferExact = (v->bound != 0 && bound == 0);
        if (empty || older || shallower || preferExact) v = &e;
    }
    return v;
}

void store(...) {
    if (XP.XP7_TTPolicy) {
        auto& bucket = table[index(k)];
        TTEntry* repl = select_victim(bucket, depth, (uint8_t)b, TTGen);
        *repl = {k, packScore(s), (uint8_t)std::min(depth,255), (uint8_t)b, m, TTGen};
    } else {
        // existing policy
    }
}

Test: XP7 only.


XP-8 — QSearch caps (check-ext +1; safe delta)

FILE: src/search.cpp (qsearch)

if (XP.XP8_QSCap) {
    // limit giving-check extension to +1 ply total
    if (givesCheck(m)) extension = std::min(extension + 1, 1);

    constexpr Score Delta = 925; // queen − small slack
    bool keepPassedPush = isPassedPawnPush(m);
    if (!keepPassedPush && eval + Delta <= alpha && see(m) < 0)
        continue; // delta prune
}

Test: XP8 only.


XP-9 — Small eval de-risk (tempo +16; calmer passed pawns; KS fade)

FILES: src/evaluate.cpp, src/pawns.cpp

if (XP.XP9_EvalSafe) {
    // tempo
    score += SCORE(16, 16);

    // passed pawns (where you compute their bonus):
    if (blockedByMinorOrRook) bonus /= 2;
    if (!supportedByPawn)     bonus = bonus * 3 / 4;

    // king safety scaling with phase (ensure you apply a 0..1 factor)
    ks = scaleByGamePhase(ks, phase); // linear fade
    contempt = 0; // unless user overrides via UCI
}

Test: XP9 only.


XP-10 — Root-only bias (experience/NNUE/book hints)

FILE: wherever bias is injected

if (XP.XP10_RootBias) {
    if (nodeType != ROOT) bias = std::clamp(bias, -10, +10);
    // or simply: if (nodeType != ROOT) bias = 0;
}

Test: XP10 only.


How to run each A/B (minimal)

For each XPi:

  1. Set all XP options to false, measure vs BASE.
  2. Toggle only that XPi to true, re-measure vs BASE.
  3. Log Elo delta and key stats (TT hit, FH rate, nodes/s).

Example command (blitz 10+0.1; 1t; 32MB):

fastchess -concurrency 8 \
  -engine cmd=.\revolution_dev.exe name=DEV opt.Hash=32 opt.Threads=1 \
  -engine cmd=.\revolution_base.exe name=BASE opt.Hash=32 opt.Threads=1 \
  -each tc=10+0.1 proto=uci \
  -openings file=UHO_2024_8mvs_+085_+094.pgn format=pgn order=sequential \
  -games 300 -sprt elo0=0 elo1=6 \
  -pgn out_XP{N}_blitz.pgn

Rapid 60+0.6 sanity (200–300 games):

fastchess -concurrency 8 \
  -engine cmd=.\revolution_dev.exe name=DEV opt.Hash=32 opt.Threads=1 \
  -engine cmd=.\revolution_base.exe name=BASE opt.Hash=32 opt.Threads=1 \
  -each tc=60+0.6 proto=uci \
  -openings file=UHO_2024_8mvs_+085_+094.pgn format=pgn order=sequential \
  -games 250 -sprt elo0=0 elo1=4 \
  -pgn out_XP{N}_rapid.pgn

Tip: keep a CSV log: XP name, on/off, Elo, LOS, TT hit, FH rate, nps. Promote only XPs that are non-negative at blitz and do not harm rapid.


Expected signs (rule-of-thumb)

  • XP-1 TimeClamp: + at blitz, neutral/+ at rapid
  • XP-2 AspWide: + stability, fewer re-search storms
  • XP-3 LMRCons: + at both, esp. tactical stability
  • XP-4 NMPSoft: + at tricky endgames; neutral otherwise
  • XP-5 LMP/Futil: + at fast, neutral at slow if margins sane
  • XP-6 Ordering/History bounds: + both; lower variance
  • XP-7 TT Policy: + both; better reuse, fewer horizon traps
  • XP-8 QSCap: + vs tactical blow-ups at fast
  • XP-9 EvalSafe: small +/neutral; avoid fast-TC drift
  • XP-10 RootBias: + if you had deep-node bias hurting pruning

Merge strategy after testing

  1. Keep all XPs OFF by default in main.
  2. Permanently enable only those with non-negative blitz and rapid deltas on your machine (Ivy Bridge).
  3. Leave the rest as optional UCI switches for future hardware.

Download Revolution uci chess engine

Leave a Reply

Your email address will not be published. Required fields are marked *