Chal - UCI Chess Engine
Author: Naman Thanki
Strength
v1.3.0 is estimated at ~2100 Elo, validated across thousands of games
against known reference engines. The headline result is a convincing win over
Stash v14 (~2054 Elo) by a good margin in gauntlet play.
SPRT test against the previous version:
Results of ChalTest vs ChalBase (8+0.08, UHO_Lichess_4852_v1.epd):
Elo: +224.27 ± 51.54 | LOS: 100% | DrawRatio: 19.61%
Wins: 144 Losses: 28 Draws: 32 (78.43%)
LLR: 2.94 (100.0%) [-2.94, 2.94] — SPRT [0.00, 10.00] H1 accepted
Evaluation — PeSTO tapered eval
The evaluation function has been completely replaced. The old hand-crafted
piece-square tables and MAX_PHASE material blend give way to Rofchade's
Texel-tuned PeSTO MG/EG tables with a proper 0–24 phase counter.
- Separate
mg_pst/eg_psttables (6 piece types × 64 squares each) - Separate
mg_val/eg_valmaterial values;piece_val[]kept for MVV-LVA - Tapered blend:
(mg * phase + eg * (24 - phase)) / 24 - Mobility centered on
mob_center[]so inactive pieces are penalised rather
than all pieces receiving a flat bonus - Pawn shield MG-only; EG king centralisation handled by the EG king PST
- Rook open/semi-open file and 7th-rank bonuses applied to both MG and EG
- Passed-pawn bonus removed — it double-counted the PeSTO EG pawn table
- Eval loop replaced with a 64-square rank×file pass; occupied squares cached
inpseudo_list[]so the rook activity pass never visits an empty square
Search — correctness and architecture
search() signature change (breaking):
int search(int depth, int alpha, int beta, int was_null, int sply);A new explicit sply (ply-from-root) parameter replaces the#define sply (ply - root_ply) macro. Killer moves are now unambiguously
indexed by ply-from-root and null-move pruning passes sply + 1 cleanly
with no global mutation needed.
| Bug | Fix |
|---|---|
| Double null move | was_null parameter; NMP skips when was_null=1 |
| Fail-hard / fail-soft mix | All returns use actual scores throughout |
| TT mate score corruption | Encode/decode relative to node sply |
| TT cutoffs on PV nodes | is_pv guard prevents hash cutoffs narrowing the PV window |
LMR is_cap read after make_move | Saved before make_move |
| LMR gives-check direction | IN_CHECK(side) after make_move tests the right side |
NMP returns beta not sc | Fixed to return sc (fail-soft) |
| QS stand-pat fail-hard | Returns best_sc throughout |
| QS null-window before first move | (caps_only || legal==1) guard |
| KRK / KQK falsely drawn | has_major flag in insufficient-material check |
| Repetition — in-tree vs history | In-tree: 1 prior = draw; history: 2 prior = draw |
| PVS first-move window | || best_sc == -INF sentinel covers first legal move |
Move ordering
sort_moves()(full O(n²) upfront sort) replaced byscore_moves()+pick_move(): moves are pre-scored once then selected lazily, paying O(n)
only for moves actually searched- EP captures now scored as pawn captures under MVV-LVA (previously fell
through to quiet move ordering) - Score tiers raised to eliminate overlap and give each category an unambiguous bucket:
| Score | Category |
|---|---|
| 30000 | Hash move |
| 20000 – 28900 | Captures (20000 + 10×cap_val − atk_val) |
| 19999 | Promotion |
| 19998 | Killer slot 0 |
| 19997 | Killer slot 1 |
| 0 – 19996 | History heuristic |

Comments
Post a Comment