Skip to content

Releases: CMCanavessi/facon

Facón 1.6 - Temple

11 Jun 21:42

Choose a tag to compare

Approximately +250 Elo over 1.5 (Ordo ~2800, Gauntlet 4 n=1040). Direct self-play vs 1.5: +222.8 Elo (n=10000, +/- 7.0) at 10+0.1.

Unlike 1.5, whose gain came almost entirely from a single critical bug fix, 1.6's improvement is a deliberate, broad evaluation overhaul. Every tunable evaluation weight was centralized into a single flat vector and optimized with Texel tuning; the piece-square tables were made phase-tapered (separate middlegame and endgame values); and three new evaluation feature groups were added: pawn shelter/storm, a second king-safety layer, and a positional refinement group covering tempo, bishop outpost, and passed-pawn play. On the infrastructure side, the magic numbers are now hardcoded, making engine startup effectively instant.

The development was strict and incremental: features were added one at a time (or in small thematic groups), each validated by self-play before the next was included, and only measurable gainers were kept. Three conceptually sound features were implemented, tuned, measured, and rejected (rook-behind-passer, non-linear mobility, material imbalance).


Evaluation -- Tuning and Tapering

  • Weight centralization: every tunable evaluation weight collected into a single flat array eval_weights[NUM_WEIGHTS] (934 entries), organized into named groups. Previously weights were scattered as named compile-time constants. The array is non-const so the tuner can mutate it; the eval hot path treats it as read-only by convention.
  • Texel-tuned weight set: the integrated weights were produced by Texel tuning on a labeled quiet-position dataset. Material was modeled as a separate tunable during tuning and folded back into the PSTs afterward, so PIECE_VALUE stays fixed at 100/320/330/500/900 while the evaluation reproduces the tuned values.
  • Tapered piece-square tables: each PST square now holds separate middlegame and endgame values, blended by game_phase(). Lets the tuner express phase-dependent placement (king centralization bad in the middlegame and good in the endgame, advanced pawns worth progressively more as material comes off).

Evaluation -- New Feature Groups

  • Pawn shelter / storm: scores the friendly pawn nearest the king on the king's file and the two adjacent files (shelter, bucketed by rank gap), and enemy pawns advancing on those files (storm, bucketed by advancement). Tapered.
  • Second king-safety layer: open/semi-open files toward the king (king's file weighted apart from adjacent files) and safe checks (squares an enemy can check from without being captured, per piece type). Complements the attacker-count king safety from earlier versions.
  • Tempo (+19 Elo, n=860, LOS 98.5%): a bonus for the side to move, added per the side to move rather than per colour. The single most valuable feature added in 1.6.
  • Bishop outpost (+6.6 Elo, n=3480, LOS 93%): a bishop on relative ranks 4-6 on a square no enemy pawn can challenge, with a larger bonus when pawn-supported. Mirrors the knight outpost.
  • Passed-pawn refinement (+10.6 Elo as a group, n=6520, LOS 99.94%): king proximity (own king near is good, enemy king near is bad, endgame-weighted), blockade (enemy piece on the stop square; a minor blockades more effectively than a major), free path to promotion, and pawn protection. Computed in a single pass over passed pawns, no NPS cost.

Evaluation -- Trace and Consistency

  • trace_evaluate() and the trace command: trace_evaluate() produces the linear coefficient decomposition of the evaluation (required for Texel tuning); its dot product with the weight vector reproduces evaluate() within the documented tapered-blend rounding tolerance (<=2cp). The new trace UCI debug command prints the per-weight coefficients and the engine-vs-trace fidelity check.

Infrastructure

  • Hardcoded magic numbers: the bishop/rook magics, previously found at startup by a fixed-seed randomized search, are now baked in as constants. Startup time dropped from ~261 ms to ~11 ms (~96% faster) on the Ryzen 7 1700; the generated attacks are bit-identical (bench unchanged at 618952, depth 8). The randomized search is preserved under #ifdef FACON_REGENERATE_MAGICS for offline regeneration; not compiled into the normal build.

Build

  • Version bumped to 1.6, codename "Temple": the version-string/binary-name logic distinguishes development codenames (suffix carried) from release codenames (no suffix), and the version-selection comments were generalized so they need no editing between development and release.

Features attempted and rejected

  • Rook behind passed pawn (Tarrasch rule): -9 Elo (n=3020, LOS 2.9%). Redundant with the rook PST and existing passed-pawn terms.
  • Non-linear mobility (per-count tables): -12 Elo (n=5300). Overfit; the linear mobility from 1.4 is more robust at this strength.
  • Material imbalance (Kaufman-style, knight/rook/bishop-pair scaled by pawn count): approximately neutral to -5 Elo. The tuning itself signaled redundancy (two of three terms wrong-signed vs theory, tiny magnitudes, error barely moved, optimizer redistributed existing PST/bishop-pair value); self-play confirmed it.

Known limitations

  • Drawn pawnless endings evaluated as material (carried from 1.5): K+B vs K, K+N vs K, K+N+N vs K, and K+B+B same-color vs K return the raw material count rather than 0. An override forcing 0 was attempted in 1.5 and reverted after measuring ~30 Elo regression. Proper resolution requires material-signature endgame recognition or tablebase probing, planned for a future version.

Checksums (SHA1)

b38f845e6de27867c9cf08b2f0f4b6d1d9e8f187  facon-1.6
ac61999b8a482173eef5d206fcfac1590b6e78a8  facon-1.6.exe

Facón 1.5 - Espiga

26 May 17:17

Choose a tag to compare

Approximately +220 Elo over 1.4 (Ordo ~2550, G3+G4 combined n=2080). Gauntlet 3 (low-Ordo field, avg ~2310): 800.5/1040 (77.0%, Ordo ~2550). Gauntlet 4 (high-Ordo field, avg ~2680): 345.5/1040 (33.2%, Ordo ~2545). Cross-validation across the two fields confirms the rating within ~5 Elo.

The bulk of the gain comes from fixing a game_phase() inversion that had silently disabled king-safety evaluation in the middlegame since version 1.1 -- the fix in isolation measured +224 Elo at 10+0.1 self-play. The rest of the release adds search refinements (LMP, SEE-aware ordering, IIR, countermove, razoring, TT aging), evaluation refinements (knight outpost two-tier, mopup guard extended to KNN and KBB same-color), and infrastructure improvements (bench rebalanced and deepened, eval debug command, CLI bench mode, observability output).


Search

  • Late Move Pruning (LMP): skip late quiet moves at shallow depths in non-PV nodes when enough alternatives have already been searched without raising alpha.
  • SEE-aware capture ordering: captures split into GOOD (SEE >= 0, before quiets) and BAD (SEE < 0, after quiets) tiers. Replaces the single MVV-LVA tier from 1.4.
  • Internal Iterative Reductions (IIR): non-PV nodes without a TT move at sufficient depth are reduced by 1 ply before searching; the next visit benefits from move ordering.
  • Countermove heuristic: third quiet-move ordering tier after killers; remembers the refutation move per opponent (piece, to-square) pair.
  • Razoring: at depth 1-2 in non-PV nodes, drop into qsearch if eval + 250cp <= alpha.

Evaluation

  • game_phase() inversion fix (carried over from 1.1): the function had been returning the inverse of its documented contract, silently zeroing king-safety in the middlegame and using the endgame king PST from move 1. +224 Elo measured in isolation. Single largest source of strength gain in 1.5.
  • Knight outpost refactor: previous outpost detection required only that the square not be attackable by enemy pawns. Refactored to two tiers: KNIGHT_OUTPOST_REACHABLE (10cp, no friendly pawn support) and KNIGHT_OUTPOST_SUPPORTED (25cp, supported by a friendly pawn).
  • Knight outpost forward_mask fix: the disqualifying forward mask incorrectly included the knight's own rank, causing false-negatives when an enemy pawn sat on the same rank in an adjacent file. Pawns capture diagonally forward, so such pawns cannot attack the knight. Mask now strictly ahead.
  • Mopup insufficient material guard extended: now covers K+N+N vs K and K+B+B same-color vs K, in addition to the K+B vs K and K+N vs K cases from 1.4. K+B+B with opposite-colored bishops continues to trigger mopup correctly.

Transposition Table

  • TT aging: global generation counter packed into the entry's bound byte. Replacement compares (age_in_generations * 4) + (stored_depth - new_depth). Smarter eviction keeps the TT relevant across moves without losing meaningful old data. Entry size unchanged at 16 bytes.

Infrastructure

  • bench default depth raised from 15 to 18: more meaningful per-position measurements. Bench signature: 299,881,540 nodes at depth 18 (deterministic, used as no-regression check).
  • bench position rebalancing: 6 of 10 positions replaced for better time distribution. Per-position share went from 0.01%-38.85% (1.4 set) to 2.7%-16.7% (much more uniform). Each position now carries a label describing the search feature it stresses.
  • bench verbose flag: when present, full search output is emitted. Default (quiet) mode prints only per-position summary and total.
  • CLI bench mode: the binary can now be invoked as ./facon-1.5 bench [verbose] [depth N] (or facon-1.5.exe bench ... on Windows) to run the benchmark once and exit, without UCI handshake. Useful for automation.
  • eval UCI command: new debug command that prints a per-component breakdown of the static evaluation for the current position. Calls evaluate_verbose() which reproduces evaluate() exactly but accumulates each term separately.
  • Final summary on aborted iteration: when the search is interrupted mid-iteration, the engine now emits a final UCI info depth line and a human-readable info string summary right before bestmove, capturing total nodes / time / nps that would otherwise be lost between the last heartbeat and the bestmove.
  • currmove output suppressed for first 2 seconds: UCI info currmove lines from the root are gated by elapsed time. At shallow depths each move at the root completes in well under a millisecond, producing hundreds of currmove lines per second; suppressing this volume reduces stdout pressure on GUI pipe readers without losing useful information.

Build

  • MSVC support removed from CMakeLists.txt: the previous MSVC branch was non-functional because the source uses GCC/Clang builtins directly without wrappers. CMake now rejects MSVC at configure time with FATAL_ERROR. Native Windows builds should use MinGW-w64.
  • -fomit-frame-pointer added to Release builds: frees %rbp as a general-purpose register in the hot search/eval paths. Measured ~+2.3% NPS on Ryzen 7 1700 (Zen 1).

Known limitations

  • Drawn pawnless endings evaluated as material: KB vs K, KN vs K, KNN vs K, and KBB same-color vs K are theoretically drawn but evaluate() returns the raw material count (~+330 to ~+660 cp) rather than 0. An override forcing 0 was attempted during 1.5 development but reverted after extended gauntlet testing measured ~30 Elo regression -- the override was technically correct but demotivated the engine from reaching positions whose drawn final endings propagated back through search. Proper resolution requires material-signature endgame recognition or tablebase probing, planned for a future version.

Checksums (SHA1)

f7627bc8d319610b9d607f8349b955201ca152b0  facon-1.5
705aa911390d15bb819d9b792620cf3456bb6c5e  facon-1.5.exe

Facón 1.4 - Hoja

25 Apr 00:29

Choose a tag to compare

+430 Elo over 1.3 (Ordo ~2330, gauntlet 1040 games at 2min+1sec). NPS: +53% (Linux), +92% (Windows).


Search

  • Check extension: when in check, extend search depth by 1. Check evasions are now resolved at full depth instead of dropping into quiescence. +30 Elo.
  • Static Exchange Evaluation (SEE): full exchange simulation with x-ray discovery. Losing captures pruned in quiescence search. Shortcut: skip SEE for captures where the victim is worth >= the attacker.
  • Reverse futility pruning: at depth 1-3, if the static evaluation already exceeds beta by a margin, prune the node without searching. Like NMP without the null search cost.
  • Move-level futility pruning: at depth 1-2, skip quiet moves when the static evaluation plus a margin is still below alpha.
  • Make/unmake legality: the per-move board copy in is_legal() (~700 bytes per pseudo-legal move) replaced with make, check, unmake. NPS: +130%.
  • Move scores computed once: parallel scoring array eliminates O(n^2) move_score() calls during selection sort. NPS: +9%.
  • LMR table precalculated: startup-initialized LMR_table[MAX_PLY][MAX_MOVES] replaces per-node log() calls.
  • Quiet queen promotions in qsearch: pawn pushing to the 8th rank without capturing is now visible to quiescence search.
  • Forced move instant-play: only one legal move = play immediately with zero search time.

Evaluation

  • Piece mobility: pseudo-legal squares per piece, excluding own pieces and enemy pawn attacks. Knight 4cp, bishop 5cp, rook 2cp, queen 1cp per square.
  • Open/semi-open files: rook bonuses for files with no pawns (+20cp) or no friendly pawns (+10cp).
  • Rook on 7th rank: +20cp bonus.
  • Bishop pair: +30cp when both bishops present.
  • Knight outposts: +20cp for knights on ranks 4-6 that cannot be attacked by enemy pawns.

Transposition Table

  • Depth-preferred replacement: shallow entries for different positions no longer evict deeper entries.
  • TTEntry::depth int8_t to uint8_t: fixes overflow at MAX_PLY (128).

Time Management

  • Base time budget increased (MOVES_TO_GO 30 to 25).
  • Progressive easy-move reduction (x0.95 per stable iteration, cumulative, cancellable).
  • AW fail-high time extension (proportional to fail-high count, capped at x1.50).
  • Time cap relaxed (remaining/3 to remaining*2/5).
  • Mate reduction only when winning (no longer triggers when being mated).
  • TM extensions now always run before checking soft_stop().
  • go depth X without clock no longer activates TM.
  • movestogo UCI parameter now parsed and used.

Infrastructure

  • bench command: 10 positions, depth 15 default. Quiet by default, bench verbose for full output.
  • FACON_DEBUG build mode: diagnostic counters compiled only with -DFACON_DEBUG=ON.

Checksums (SHA1)

09f0fba11511470f8b06102a8f118293ef94fe4e  facon-1.4
a8815dbc6e952bceee4f5f7c7478f96eb3c7c18b  facon-1.4.exe

Facón 1.3 - Yunque

11 Apr 19:43

Choose a tag to compare

+200 Elo over 1.2 (Ordo ~1900, gauntlet 1040 games at 2min+1sec)


Pre-work bug fixes

  • NMP depth floor (search.cpp): at depth == NMP_MIN_DEPTH (3), the NMP recursive call produced depth -1. With the corrected depth == 0 quiescence entry condition, this caused infinite recursion. Fixed: std::max(0, depth - 1 - NMP_REDUCTION).
  • depth <= 0 to depth == 0 (search.cpp): prerequisite for LMR. Negative depths from buggy reductions are now immediately detectable instead of silently entering quiescence.

Search

  • Late Move Reductions (LMR): quiet moves searched after the first 3 legal moves at depth >= 3 are searched at reduced depth. Formula: log(depth) * log(move_number) / 2.25, floored at 1. Re-searched at full depth if the reduced search raises alpha. Skipped for: captures, en passant, promotions, killer moves, in check.
  • History heuristic: history_[color][from][to] incremented by depth^2 on beta cutoffs. Replaces the flat ORDER_QUIET=0 score for quiet move ordering, improving LMR accuracy. Capped at 50,000 (below ORDER_KILLER2). Reset each search.
  • Aspiration windows: iterative deepening searches with a +/-50cp window around the previous score from depth 4+. On fail-low or fail-high, widens only in the failing direction and doubles delta. Full window used for depth < 4 and mate scores.

Evaluation

  • Pawn structure: five terms via bitboard operations -- isolated (-15cp), doubled (-15cp), backward (-12cp), passed (rank-scaled: 0/0/10/20/35/55/80/0cp), connected (+8cp). All computed symmetrically for both colors.
  • Mopup insufficient material guard: K+B vs K and K+N vs K are theoretical draws. Both exceed MOPUP_THRESHOLD (300cp) and previously activated corner-chasing. mopup_eval() now returns 0 when the strong side has exactly one minor piece.

Time Management

  • Quadratic extension scaling: extend_time() factors pre-scaled by (depth^2 / EXTENSION_FULL_DEPTH^2). PV changes at depth 2-9 have near-zero effect; extensions at the engine's operating depth (14+) apply the full factor. EXTENSION_FULL_DEPTH = 18.
  • accumulated_ext_ cap removed: the 2.0x cap consumed the budget at low depths before real extensions at depth 14+ could fire. The soft limit is now bounded only by the hard limit.
  • Easy move reduction (reduce_time): mate found (x0.05, one-shot), forced move (x0.1), PV+score stable >= 7 iterations at depth > 12 (x0.40, one-shot). Cancelled before extensions so they act on the full soft limit.
  • Emergency hard limit: when depth >= 25 and the extended soft would exceed hard, hard is raised to match (capped at 50% of raw remaining clock). Both limits rise together on subsequent extensions. Only triggered by real instability -- stable positions at depth 25+ would have already fired easy-move reduction.

Infrastructure

  • Centralized version system: PROJECT_VERSION and FACON_CODENAME in CMakeLists.txt control the binary name, startup banner, UCI id, and Windows version resource. To release: change codename from "dev" to "Yunque" and recompile.
  • perft command: perft N counts leaf nodes, perft divide N gives per-move breakdown. Bulk-counting at depth 1. Verified: startpos depth 5 = 4,865,609.
  • bitboard.cpp init message gated: suppressed when launched by GUI or automated tool.

Bug fixes

  • Aspiration window fail-low: the old handler set beta_asp = (alpha_asp + beta_asp) / 2, squeezing the upper bound. On re-search via TT/LMR interactions, this triggered artificial fail-highs (yo-yo effect). Fixed: widen only in the failing direction.
  • Mate reduction one-shot: is_mate_score() is true on every iteration after a mate is found. reduce_time(0.05) firing repeatedly collapsed the soft limit exponentially (x0.05^N). Fixed: mate_reduction_applied_ one-shot guard.
  • Race condition in cmd_ucinewgame(): TT.clear() could race with TT.probe()/TT.store() in the search thread. Fixed: join the search thread first.
  • Castling SAN check/mate: move_to_san() returned immediately for castling without checking if it delivers check or mate. Fixed: castling now falls through to the check/mate detection block.
  • seen[] guard mismatch: array enlarged to 1154 slots in 1.2, but the insertion guard still stopped at 1152. Updated to match.

Checksums (SHA1)

a8a681ae3c6129205f9e98bcc86adadf2d33b70a  facon-1.3
3d51dc6625f69ca24d62d02d6cd72a67c254bb06  facon-1.3.exe

Facón 1.2 - Rojo Vivo

24 Mar 02:09

Choose a tag to compare

+330 Elo over 1.1 (Ordo ~1690, confirmed across two gauntlets totaling 2080 games at 2min+1sec)


Pre-work bug fixes

  • unmake_move() hash corruption: hash = st.hash was placed before the piece operations, which XOR the hash incrementally. The restored hash was immediately re-corrupted after every unmake. Latent bug since 1.0 — affected TT hit rates, repetition detection, and PV display. Fixed by moving hash = st.hash to the last line of unmake_move().
  • make_null_move() full-move counter corruption: unmake_null_move() decremented full_move_number when Black made a null move, but make_null_move() never incremented it. Fixed.
  • Double generate_all_moves() in TT probe path: eliminated by moving a single generation before the TT probe and reusing it. ~2-5% nps improvement.

Search

  • Null Move Pruning (NMP): if the side to move can pass their turn and the resulting reduced-depth search still exceeds beta, prune immediately. Guards: not in check, ply > 0, non-pawn material present (zugzwang guard), depth >= 3. Reduction R = 3. Largest single search improvement in Facon's history: +5.6 average depth over 1.1 at long time controls.
  • Triangular PV array: replaced TT-based PV retrieval with an explicit pv_table_[MAX_PLY][MAX_PLY] updated on every alpha raise. Eliminates stale PV lines and the "PV continues after threefold repetition" GUI warning.
  • PV repetition detection: the PV walk seeds a seen[] array with game history hashes and stops if any resulting position was seen before.

Evaluation

  • Mopup evaluation: in pawnless endings with a decisive advantage (≥300cp), rewards pushing the losing king toward corners and keeping the winning king close. Guides conversion of technically won positions that standard PSTs cannot resolve.

Infrastructure

  • UCI threading: go now launches the search in a dedicated thread. The UCI loop returns immediately and can process stop while searching. Previously stop was silently ignored during search.
  • isatty()-gated output: startup banner, TT info, and interactive prompt are suppressed when launched by a GUI or automated tool.
  • TT silent constructor: no output is emitted during static initialization; print_info() is called explicitly from main() after the banner.

Time Management

  • Time forfeit fix: engine was losing games on time. Fixed: HARD_FACTOR 3.0 → 2.0, SAFETY_FACTOR 0.95 → 0.90, OVERHEAD_MS = 100 subtracted upfront, hard limit capped at remaining/3, 100ms grace buffer before expiry. Zero time forfeit losses across 2080 gauntlet games.
  • extend_time() reason parameter: time extension events are logged with a human-readable reason string ("PV change", "score drop").
  • start() allocation report: soft and hard limits for each move are emitted as info string for TM diagnostics at long time controls.

Observability

  • currmove/currmovenumber: each root move emits a standard UCI info currmove line as it begins searching.
  • New-best SAN info string: when the best move at the root changes relative to the previous iteration, emits a human-readable info string with move in SAN, score, depth, and timestamp.
  • Heartbeat: if no output has been emitted for 5 minutes, a standard info line plus a status string are emitted. Distinguishes a deep search from a crash at long time controls.

Code audit fixes

  • Quiescence time check: quiescence() checked stats_.nodes (negamax-only counter) for its periodic time check. During deep capture sequences the check never fired. Fixed: uses stats_.qnodes.
  • Nodes / NPS reporting: all UCI output reported only negamax nodes, excluding quiescence. In tactical positions this caused severe underreporting. Fixed: all output now uses stats_.nodes + stats_.qnodes.
  • PV seen[] array size: off-by-one in the theoretical worst case. Fixed: seen[MAX_GAME_HISTORY + MAX_PLY + 2].

Checksums (SHA1)

2b02adc2dee72ada51a35b2c96134e7862d43f71  facon-1.2
c4710908026a479e4fa81ae076699db1c4a4bf78  facon-1.2.exe

Facón 1.1 - Herrumbre

11 Mar 20:27

Choose a tag to compare

+120 Elo over 1.0 (gauntlet testing vs field Elo ~1358, 1040 games at 2min+1sec)


Search

  • Killer move heuristic: quiet moves that cause a beta cutoff are stored per-ply (two slots per node) and tried before other quiet moves in sibling nodes. Improves move ordering in quiet positions where MVV-LVA has no effect.
  • Dynamic time management: replaced the fixed time budget with a soft/hard limit model. The soft limit is extended when the PV move changes between iterations (×1.5) or the score drops by ≥30 centipawns (×1.25). The hard limit acts as an absolute ceiling.
  • seldepth tracking: maximum depth reached including quiescence search is tracked and reported in the UCI info line each iteration.

Evaluation

  • King safety: penalty for enemy pieces attacking the king zone (king square plus adjacent squares). Attack weights: Knight=2, Bishop=2, Rook=3, Queen=5 (queens counted twice — once as diagonal, once as straight attacker). Penalty grows quadratically with total weight and scales with game phase (middlegame only).

Stability

  • Abort flag (abort_search_): when the hard time limit is hit inside the search, a flag is set and every level of the call stack returns immediately. Ensures all unmake_move() calls execute, leaving the board consistent for the next search.
  • Root best move tracking (root_best_move_): the best move at ply 0 is saved directly inside negamax() whenever a new best is found. Eliminates bestmove 0000 outputs.
  • is_legal() piece ownership check: verifies that the from-square contains a piece belonging to the side to move before the expensive board copy. Eliminates ghost moves from stale TT entries.
  • TT move validation: TT moves are verified against the generated move list before use. Prevents hash collisions from inserting garbage into move ordering.
  • TT pollution prevention: if abort_search_ is set when returning from a child node, the result is discarded without storing to the TT.
  • safe_move pre-seed: the first legal move is stored as a fallback before iterative deepening begins. Guarantees bestmove is never 0000 even if depth 1 is aborted.

Cleanup

  • probe_move() removed from tt.h/tt.cpp (dead code).
  • Non-ASCII characters removed from all source files.
  • All source file headers updated to Facon 1.1 — Herrumbre.

Checksums (SHA1)

231d9973d9f1062a40b6b39c4ac3f3ea872efcd6  facon-1.1
028e1671bf4fcad4fe19c1f5876e216709daf924  facon-1.1.exe

Facón 1.0 - Óxido

05 Mar 12:02

Choose a tag to compare

Changelog

All notable changes to Facón will be documented here.


[1.0] "Óxido" — 2026-03-05

Initial public release.

Engine

  • Bitboard board representation with redundant piece array for O(1) square queries
  • Magic bitboards for sliding piece attack generation (rooks, bishops, queens)
  • Zobrist hashing with incremental updates on make/unmake
  • Full make/unmake move stack supporting any game length
  • Draw detection: threefold repetition and 50-move rule

Move Generation

  • Complete pseudo-legal move generation for all piece types
  • Separate capture-only generator for quiescence search
  • Handles all special moves: castling, en passant, promotions (all four pieces)

Search

  • Negamax alpha-beta with iterative deepening
  • Quiescence search at leaf nodes
  • Transposition table (Zobrist hash, 16 MB default, configurable via UCI)
  • Move ordering: TT move first, then MVV-LVA for captures, then quiet moves
  • Time management with Fischer clock support and movetime override

Evaluation

  • Material count (P=100, N=320, B=330, R=500, Q=900)
  • Piece-square tables for all piece types
  • King safety interpolated between middlegame and endgame tables based on remaining material

UCI

  • Full UCI protocol support
  • Supported commands: uci, isready, ucinewgame, position, go, stop, setoption, quit
  • Supported options: Hash (transposition table size in MB)
  • Debug command: d (display current board)

Build

  • CMake build system
  • Linux native build (GCC/Clang, C++17)
  • Windows cross-compilation via MinGW-w64
  • Statically linked Windows binary (no DLL dependencies)