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.3 - Yunque what's new?
+200 Elo over 1.2 (Ordo ~1900, gauntlet 1040 games at 2min+1sec)
Pre-work bug fixes
- NMP depth floor (
search.cpp): atdepth == NMP_MIN_DEPTH(3), the NMP recursive call produced depth -1. With the correcteddepth == 0quiescence entry condition, this caused infinite recursion. Fixed:std::max(0, depth - 1 - NMP_REDUCTION). depth <= 0todepth == 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 bydepth^2on beta cutoffs. Replaces the flatORDER_QUIET=0score for quiet move ordering, improving LMR accuracy. Capped at 50,000 (belowORDER_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_VERSIONandFACON_CODENAMEin CMakeLists.txt control the binary name, startup banner, UCI id, and Windows version resource. To release: change codename from"dev"to"Yunque"and recompile. perftcommand:perft Ncounts leaf nodes,perft divide Ngives per-move breakdown. Bulk-counting at depth 1. Verified: startpos depth 5 = 4,865,609.bitboard.cppinit 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 withTT.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.
Facón 1.3 - Yunque download from page

Comments
Post a Comment