Facón is a chess engine built from scratch in C++17, designed as a learning project and long-term development platform. The name comes from the facón — a traditional Argentine gaucho knife, forged by hand, raw and functional.
Each version carries a codename that follows the knife-making process: from rough rusty iron to a sharp, precise blade.
Author: Carlos M. Canavessi
Facón 1.2 - Rojo Vivo what's new?
+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

Comments
Post a Comment