From 3e22b31e06dd721fda2640e03add37506972f918 Mon Sep 17 00:00:00 2001 From: Hugo Souza Date: Thu, 7 May 2026 11:34:07 -0300 Subject: [PATCH 01/12] tapered eval --- src/eval.cpp | 104 +++++++++++++++++++++++++++++++++++---------------- src/eval.h | 5 ++- src/values.h | 8 ++++ 3 files changed, 83 insertions(+), 34 deletions(-) diff --git a/src/eval.cpp b/src/eval.cpp index 908ef0f..8f962ee 100644 --- a/src/eval.cpp +++ b/src/eval.cpp @@ -5,7 +5,8 @@ #include "game.h" -int Eval::score_casas[LADOS][TIPOS_DE_PIECES][CASAS_DO_TABULEIRO]; +int Eval::score_casas_mg[LADOS][TIPOS_DE_PIECES][CASAS_DO_TABULEIRO]; +int Eval::score_casas_eg[LADOS][TIPOS_DE_PIECES][CASAS_DO_TABULEIRO]; int Eval::reis_score_finais[LADOS][CASAS_DO_TABULEIRO]; int Eval::passados[LADOS][CASAS_DO_TABULEIRO]; @@ -16,28 +17,42 @@ int peao_ala_da_dama[LADOS],peao_ala_do_rei[LADOS]; void Eval::init_eval_tables(){ for (int x = 0; x < CASAS_DO_TABULEIRO; x++){ - score_casas[BRANCAS][P][x] = Values::peao_score[x] + VALOR_PEAO; - score_casas[BRANCAS][C][x] = Values::cavalo_score[x] + VALOR_CAVALO; - score_casas[BRANCAS][B][x] = Values::bispo_score[x] + VALOR_BISPO; - score_casas[BRANCAS][T][x] = Values::torre_score[x] + VALOR_TORRE; - score_casas[BRANCAS][D][x] = Values::dama_score[x] + VALOR_DAMA; - score_casas[BRANCAS][R][x] = Values::rei_score[x]; - - score_casas[PRETAS][P][x] = Values::peao_score[Consts::flip[x]] + VALOR_PEAO; - score_casas[PRETAS][C][x] = Values::cavalo_score[Consts::flip[x]] + VALOR_CAVALO; - score_casas[PRETAS][B][x] = Values::bispo_score[Consts::flip[x]] + VALOR_BISPO; - score_casas[PRETAS][T][x] = Values::torre_score[Consts::flip[x]] + VALOR_TORRE; - score_casas[PRETAS][D][x] = Values::dama_score[Consts::flip[x]] + VALOR_DAMA; - score_casas[PRETAS][R][x] = Values::rei_score[Consts::flip[x]]; - - reis_score_finais[BRANCAS][x] = Values::rei_finais_score[x] - score_casas[BRANCAS][R][x]; - reis_score_finais[PRETAS][x] = Values::rei_finais_score[x] - score_casas[PRETAS][R][x]; + score_casas_mg[BRANCAS][P][x] = Values::peao_score[x] + VALOR_PEAO; + score_casas_mg[BRANCAS][C][x] = Values::cavalo_score[x] + VALOR_CAVALO; + score_casas_mg[BRANCAS][B][x] = Values::bispo_score[x] + VALOR_BISPO; + score_casas_mg[BRANCAS][T][x] = Values::torre_score[x] + VALOR_TORRE; + score_casas_mg[BRANCAS][D][x] = Values::dama_score[x] + VALOR_DAMA; + score_casas_mg[BRANCAS][R][x] = Values::rei_score[x]; + + score_casas_mg[PRETAS][P][x] = Values::peao_score[Consts::flip[x]] + VALOR_PEAO; + score_casas_mg[PRETAS][C][x] = Values::cavalo_score[Consts::flip[x]] + VALOR_CAVALO; + score_casas_mg[PRETAS][B][x] = Values::bispo_score[Consts::flip[x]] + VALOR_BISPO; + score_casas_mg[PRETAS][T][x] = Values::torre_score[Consts::flip[x]] + VALOR_TORRE; + score_casas_mg[PRETAS][D][x] = Values::dama_score[Consts::flip[x]] + VALOR_DAMA; + score_casas_mg[PRETAS][R][x] = Values::rei_score[Consts::flip[x]]; + + for (int p = 0; p < TIPOS_DE_PIECES; p++){ + score_casas_eg[BRANCAS][p][x] = score_casas_mg[BRANCAS][p][x]; + score_casas_eg[PRETAS][p][x] = score_casas_mg[PRETAS][p][x]; + } + + reis_score_finais[BRANCAS][x] = Values::rei_finais_score[x] - score_casas_mg[BRANCAS][R][x]; + reis_score_finais[PRETAS][x] = Values::rei_finais_score[x] - score_casas_mg[PRETAS][R][x]; passados[BRANCAS][x] = Values::peao_passado_score[Consts::flip[x]]; passados[PRETAS][x] = Values::peao_passado_score[x]; } } +int Eval::fase(){ + int phase = PHASE_CAVALO * Bitboard::popcount(Bitboard::bit_pieces[BRANCAS][C] | Bitboard::bit_pieces[PRETAS][C]) + + PHASE_BISPO * Bitboard::popcount(Bitboard::bit_pieces[BRANCAS][B] | Bitboard::bit_pieces[PRETAS][B]) + + PHASE_TORRE * Bitboard::popcount(Bitboard::bit_pieces[BRANCAS][T] | Bitboard::bit_pieces[PRETAS][T]) + + PHASE_DAMA * Bitboard::popcount(Bitboard::bit_pieces[BRANCAS][D] | Bitboard::bit_pieces[PRETAS][D]); + if (phase > PHASE_MAX) phase = PHASE_MAX; + return phase; +} + void Eval::atualizar_materiais(){ int cor; @@ -99,7 +114,8 @@ int avaliar_torre(const int l, const int casa){ } int Eval::avaliar(){ - int score[LADOS] = {0, 0}; + int mg_score[LADOS] = {0, 0}; + int eg_score[LADOS] = {0, 0}; peao_ala_da_dama[BRANCAS] = 0; peao_ala_do_rei[BRANCAS] = 0; @@ -110,14 +126,17 @@ int Eval::avaliar(){ int casa; for (int l = 0; l < LADOS; l++){ - + t1 = Bitboard::bit_pieces[l][P]; while (t1){ casa = Bitboard::bitscan(t1); t1 &= Bitboard::not_mask[casa]; - score[l] += score_casas[l][P][casa]; - score[l] += avaliar_peao(l, casa); + mg_score[l] += score_casas_mg[l][P][casa]; + eg_score[l] += score_casas_eg[l][P][casa]; + const int peao_b = avaliar_peao(l, casa); + mg_score[l] += peao_b; + eg_score[l] += peao_b; } t1 = Bitboard::bit_pieces[l][C]; @@ -125,7 +144,8 @@ int Eval::avaliar(){ casa = Bitboard::bitscan(t1); t1 &= Bitboard::not_mask[casa]; - score[l] += score_casas[l][C][casa]; + mg_score[l] += score_casas_mg[l][C][casa]; + eg_score[l] += score_casas_eg[l][C][casa]; } t1 = Bitboard::bit_pieces[l][B]; @@ -133,7 +153,8 @@ int Eval::avaliar(){ casa = Bitboard::bitscan(t1); t1 &= Bitboard::not_mask[casa]; - score[l] += score_casas[l][B][casa]; + mg_score[l] += score_casas_mg[l][B][casa]; + eg_score[l] += score_casas_eg[l][B][casa]; } t1 = Bitboard::bit_pieces[l][T]; @@ -141,8 +162,11 @@ int Eval::avaliar(){ casa = Bitboard::bitscan(t1); t1 &= Bitboard::not_mask[casa]; - score[l] += score_casas[l][T][casa]; - score[l] += avaliar_torre(l, casa); + mg_score[l] += score_casas_mg[l][T][casa]; + eg_score[l] += score_casas_eg[l][T][casa]; + const int torre_b = avaliar_torre(l, casa); + mg_score[l] += torre_b; + eg_score[l] += torre_b; } t1 = Bitboard::bit_pieces[l][D]; @@ -150,33 +174,47 @@ int Eval::avaliar(){ casa = Bitboard::bitscan(t1); t1 &= Bitboard::not_mask[casa]; - score[l] += score_casas[l][D][casa]; + mg_score[l] += score_casas_mg[l][D][casa]; + eg_score[l] += score_casas_eg[l][D][casa]; } } if (Bitboard::bit_pieces[PRETAS][D] == 0){ - score[BRANCAS] += reis_score_finais[BRANCAS][Bitboard::bitscan(Bitboard::bit_pieces[BRANCAS][R])]; + const int rei_eg = reis_score_finais[BRANCAS][Bitboard::bitscan(Bitboard::bit_pieces[BRANCAS][R])]; + mg_score[BRANCAS] += rei_eg; + eg_score[BRANCAS] += rei_eg; } else{ + int shield = 0; if (Bitboard::bit_pieces[BRANCAS][R] & Bitboard::mask_ala_do_rei){ - score[BRANCAS] += peao_ala_do_rei[BRANCAS]; + shield = peao_ala_do_rei[BRANCAS]; } else if (Bitboard::bit_pieces[BRANCAS][R] & Bitboard::mask_ala_da_dama){ - score[BRANCAS] += peao_ala_da_dama[BRANCAS]; + shield = peao_ala_da_dama[BRANCAS]; } + mg_score[BRANCAS] += shield; + eg_score[BRANCAS] += shield; } if (Bitboard::bit_pieces[BRANCAS][D] == 0){ - score[PRETAS] += reis_score_finais[PRETAS][Bitboard::bitscan(Bitboard::bit_pieces[PRETAS][R])]; + const int rei_eg = reis_score_finais[PRETAS][Bitboard::bitscan(Bitboard::bit_pieces[PRETAS][R])]; + mg_score[PRETAS] += rei_eg; + eg_score[PRETAS] += rei_eg; } else { + int shield = 0; if (Bitboard::bit_pieces[PRETAS][R] & Bitboard::mask_ala_do_rei){ - score[PRETAS] += peao_ala_do_rei[PRETAS]; + shield = peao_ala_do_rei[PRETAS]; } else if (Bitboard::bit_pieces[PRETAS][R] & Bitboard::mask_ala_da_dama){ - score[PRETAS] += peao_ala_da_dama[PRETAS]; + shield = peao_ala_da_dama[PRETAS]; } + mg_score[PRETAS] += shield; + eg_score[PRETAS] += shield; } - return score[Game::lado] - score[Game::xlado]; + const int phase = fase(); + const int mg_diff = mg_score[Game::lado] - mg_score[Game::xlado]; + const int eg_diff = eg_score[Game::lado] - eg_score[Game::xlado]; + return (mg_diff * phase + eg_diff * (PHASE_MAX - phase)) / PHASE_MAX; } \ No newline at end of file diff --git a/src/eval.h b/src/eval.h index 287f05b..61d6f4a 100644 --- a/src/eval.h +++ b/src/eval.h @@ -4,7 +4,8 @@ #include "consts.h" namespace Eval{ - extern int score_casas[LADOS][TIPOS_DE_PIECES][CASAS_DO_TABULEIRO]; + extern int score_casas_mg[LADOS][TIPOS_DE_PIECES][CASAS_DO_TABULEIRO]; + extern int score_casas_eg[LADOS][TIPOS_DE_PIECES][CASAS_DO_TABULEIRO]; extern int reis_score_finais[LADOS][CASAS_DO_TABULEIRO]; extern int passados[LADOS][CASAS_DO_TABULEIRO]; @@ -14,6 +15,8 @@ namespace Eval{ void init_eval_tables(); void atualizar_materiais(); + int fase(); + int avaliar(); }; diff --git a/src/values.h b/src/values.h index 9e45728..dfc0636 100644 --- a/src/values.h +++ b/src/values.h @@ -29,6 +29,14 @@ namespace Values{ #define COLUNA_SEMI_ABERTA_BONUS 11 #define COLUNA_ABERTA_BONUS 40 + #define PHASE_PEAO 0 + #define PHASE_CAVALO 1 + #define PHASE_BISPO 1 + #define PHASE_TORRE 2 + #define PHASE_DAMA 4 + #define PHASE_REI 0 + #define PHASE_MAX 24 + // REDUÇÕES E CONDIÇÕES #define REDUCAO_IID /4 From f59011a4ff7edc0f88540df64a8df2e9fae112b4 Mon Sep 17 00:00:00 2001 From: Hugo Souza Date: Fri, 8 May 2026 12:33:59 -0300 Subject: [PATCH 02/12] improve tapered eval speed --- src/bitboard.cpp | 5 ++ src/eval.cpp | 128 +++++++++++++++++++++++++++-------------------- src/eval.h | 48 +++++++++++++++++- src/game.cpp | 4 ++ src/update.cpp | 6 +++ 5 files changed, 134 insertions(+), 57 deletions(-) diff --git a/src/bitboard.cpp b/src/bitboard.cpp index f171eeb..0491383 100644 --- a/src/bitboard.cpp +++ b/src/bitboard.cpp @@ -1,6 +1,7 @@ #include "bitboard.h" #include "update.h" +#include "eval.h" #include #include @@ -209,6 +210,10 @@ void Bitboard::init_board(){ memset(bit_lados, 0, sizeof(bit_lados)); bit_total = 0; + // Reset incremental phase counter before the per-square adicionar_piece + // loop accumulates it back to PHASE_MAX for the starting position. + Eval::fase_valor = 0; + for (int casa = 0; casa < CASAS_DO_TABULEIRO; ++casa) { tabuleiro[casa] = VAZIO; diff --git a/src/eval.cpp b/src/eval.cpp index 8f962ee..3f97bcf 100644 --- a/src/eval.cpp +++ b/src/eval.cpp @@ -5,39 +5,66 @@ #include "game.h" -int Eval::score_casas_mg[LADOS][TIPOS_DE_PIECES][CASAS_DO_TABULEIRO]; -int Eval::score_casas_eg[LADOS][TIPOS_DE_PIECES][CASAS_DO_TABULEIRO]; +Eval::Score Eval::score_casas[LADOS][TIPOS_DE_PIECES][CASAS_DO_TABULEIRO]; int Eval::reis_score_finais[LADOS][CASAS_DO_TABULEIRO]; int Eval::passados[LADOS][CASAS_DO_TABULEIRO]; int Eval::peao_mat[LADOS]; int Eval::piece_mat[LADOS]; +int Eval::fase_valor = 0; + +// Phase weight per piece type, indexed by the {P, C, B, T, D, R} encoding +// in consts.h. Mirrors Values::PHASE_* macros — kept here as a contiguous +// array so Update::adicionar_piece / Update::remover_piece can do a single +// indexed load instead of a switch. +const int Eval::phase_weights[6] = { + PHASE_PEAO, PHASE_CAVALO, PHASE_BISPO, PHASE_TORRE, PHASE_DAMA, PHASE_REI +}; + int peao_ala_da_dama[LADOS],peao_ala_do_rei[LADOS]; void Eval::init_eval_tables(){ + // Phase 1 of the tapered-eval rollout still has mg = eg, so the packed + // score per square just duplicates the legacy single value into both + // halves. Future passes will differentiate (e.g. king PST: mg = rei_score, + // eg = rei_finais_score, dropping the conditional swap below). for (int x = 0; x < CASAS_DO_TABULEIRO; x++){ - score_casas_mg[BRANCAS][P][x] = Values::peao_score[x] + VALOR_PEAO; - score_casas_mg[BRANCAS][C][x] = Values::cavalo_score[x] + VALOR_CAVALO; - score_casas_mg[BRANCAS][B][x] = Values::bispo_score[x] + VALOR_BISPO; - score_casas_mg[BRANCAS][T][x] = Values::torre_score[x] + VALOR_TORRE; - score_casas_mg[BRANCAS][D][x] = Values::dama_score[x] + VALOR_DAMA; - score_casas_mg[BRANCAS][R][x] = Values::rei_score[x]; - - score_casas_mg[PRETAS][P][x] = Values::peao_score[Consts::flip[x]] + VALOR_PEAO; - score_casas_mg[PRETAS][C][x] = Values::cavalo_score[Consts::flip[x]] + VALOR_CAVALO; - score_casas_mg[PRETAS][B][x] = Values::bispo_score[Consts::flip[x]] + VALOR_BISPO; - score_casas_mg[PRETAS][T][x] = Values::torre_score[Consts::flip[x]] + VALOR_TORRE; - score_casas_mg[PRETAS][D][x] = Values::dama_score[Consts::flip[x]] + VALOR_DAMA; - score_casas_mg[PRETAS][R][x] = Values::rei_score[Consts::flip[x]]; - - for (int p = 0; p < TIPOS_DE_PIECES; p++){ - score_casas_eg[BRANCAS][p][x] = score_casas_mg[BRANCAS][p][x]; - score_casas_eg[PRETAS][p][x] = score_casas_mg[PRETAS][p][x]; - } - - reis_score_finais[BRANCAS][x] = Values::rei_finais_score[x] - score_casas_mg[BRANCAS][R][x]; - reis_score_finais[PRETAS][x] = Values::rei_finais_score[x] - score_casas_mg[PRETAS][R][x]; + const int peao_w = Values::peao_score[x] + VALOR_PEAO; + const int cavalo_w = Values::cavalo_score[x] + VALOR_CAVALO; + const int bispo_w = Values::bispo_score[x] + VALOR_BISPO; + const int torre_w = Values::torre_score[x] + VALOR_TORRE; + const int dama_w = Values::dama_score[x] + VALOR_DAMA; + const int rei_w = Values::rei_score[x]; + + score_casas[BRANCAS][P][x] = make_score(peao_w, peao_w); + score_casas[BRANCAS][C][x] = make_score(cavalo_w, cavalo_w); + score_casas[BRANCAS][B][x] = make_score(bispo_w, bispo_w); + score_casas[BRANCAS][T][x] = make_score(torre_w, torre_w); + score_casas[BRANCAS][D][x] = make_score(dama_w, dama_w); + score_casas[BRANCAS][R][x] = make_score(rei_w, rei_w); + + const int peao_b = Values::peao_score[Consts::flip[x]] + VALOR_PEAO; + const int cavalo_b = Values::cavalo_score[Consts::flip[x]] + VALOR_CAVALO; + const int bispo_b = Values::bispo_score[Consts::flip[x]] + VALOR_BISPO; + const int torre_b = Values::torre_score[Consts::flip[x]] + VALOR_TORRE; + const int dama_b = Values::dama_score[Consts::flip[x]] + VALOR_DAMA; + const int rei_b = Values::rei_score[Consts::flip[x]]; + + score_casas[PRETAS][P][x] = make_score(peao_b, peao_b); + score_casas[PRETAS][C][x] = make_score(cavalo_b, cavalo_b); + score_casas[PRETAS][B][x] = make_score(bispo_b, bispo_b); + score_casas[PRETAS][T][x] = make_score(torre_b, torre_b); + score_casas[PRETAS][D][x] = make_score(dama_b, dama_b); + score_casas[PRETAS][R][x] = make_score(rei_b, rei_b); + + // reis_score_finais is the eg-king-PST delta from rei_score to + // rei_finais_score. While we still apply it as a conditional swap + // (queen-presence based), keep storing the legacy delta. Future + // pass: drop this entirely and bake rei_finais_score into the eg + // half of score_casas[R] directly. + reis_score_finais[BRANCAS][x] = Values::rei_finais_score[x] - rei_w; + reis_score_finais[PRETAS][x] = Values::rei_finais_score[x] - rei_b; passados[BRANCAS][x] = Values::peao_passado_score[Consts::flip[x]]; passados[PRETAS][x] = Values::peao_passado_score[x]; @@ -45,12 +72,7 @@ void Eval::init_eval_tables(){ } int Eval::fase(){ - int phase = PHASE_CAVALO * Bitboard::popcount(Bitboard::bit_pieces[BRANCAS][C] | Bitboard::bit_pieces[PRETAS][C]) - + PHASE_BISPO * Bitboard::popcount(Bitboard::bit_pieces[BRANCAS][B] | Bitboard::bit_pieces[PRETAS][B]) - + PHASE_TORRE * Bitboard::popcount(Bitboard::bit_pieces[BRANCAS][T] | Bitboard::bit_pieces[PRETAS][T]) - + PHASE_DAMA * Bitboard::popcount(Bitboard::bit_pieces[BRANCAS][D] | Bitboard::bit_pieces[PRETAS][D]); - if (phase > PHASE_MAX) phase = PHASE_MAX; - return phase; + return (fase_valor > PHASE_MAX) ? PHASE_MAX : fase_valor; } void Eval::atualizar_materiais(){ @@ -114,8 +136,13 @@ int avaliar_torre(const int l, const int casa){ } int Eval::avaliar(){ - int mg_score[LADOS] = {0, 0}; - int eg_score[LADOS] = {0, 0}; + // Single packed accumulator per side. mg lives in the high 16 bits, + // eg in the low 16; both halves accumulate in lockstep through plain + // int32 addition. Single-valued bonuses (avaliar_peao, avaliar_torre, + // king PST swap, pawn shield) are wrapped via make_score(v, v) so the + // same delta lands in both halves. Future tapered terms differentiate + // by passing distinct mg/eg values to make_score. + Score score[LADOS] = {0, 0}; peao_ala_da_dama[BRANCAS] = 0; peao_ala_do_rei[BRANCAS] = 0; @@ -132,11 +159,9 @@ int Eval::avaliar(){ casa = Bitboard::bitscan(t1); t1 &= Bitboard::not_mask[casa]; - mg_score[l] += score_casas_mg[l][P][casa]; - eg_score[l] += score_casas_eg[l][P][casa]; + score[l] += score_casas[l][P][casa]; const int peao_b = avaliar_peao(l, casa); - mg_score[l] += peao_b; - eg_score[l] += peao_b; + score[l] += make_score(peao_b, peao_b); } t1 = Bitboard::bit_pieces[l][C]; @@ -144,8 +169,7 @@ int Eval::avaliar(){ casa = Bitboard::bitscan(t1); t1 &= Bitboard::not_mask[casa]; - mg_score[l] += score_casas_mg[l][C][casa]; - eg_score[l] += score_casas_eg[l][C][casa]; + score[l] += score_casas[l][C][casa]; } t1 = Bitboard::bit_pieces[l][B]; @@ -153,8 +177,7 @@ int Eval::avaliar(){ casa = Bitboard::bitscan(t1); t1 &= Bitboard::not_mask[casa]; - mg_score[l] += score_casas_mg[l][B][casa]; - eg_score[l] += score_casas_eg[l][B][casa]; + score[l] += score_casas[l][B][casa]; } t1 = Bitboard::bit_pieces[l][T]; @@ -162,11 +185,9 @@ int Eval::avaliar(){ casa = Bitboard::bitscan(t1); t1 &= Bitboard::not_mask[casa]; - mg_score[l] += score_casas_mg[l][T][casa]; - eg_score[l] += score_casas_eg[l][T][casa]; + score[l] += score_casas[l][T][casa]; const int torre_b = avaliar_torre(l, casa); - mg_score[l] += torre_b; - eg_score[l] += torre_b; + score[l] += make_score(torre_b, torre_b); } t1 = Bitboard::bit_pieces[l][D]; @@ -174,15 +195,13 @@ int Eval::avaliar(){ casa = Bitboard::bitscan(t1); t1 &= Bitboard::not_mask[casa]; - mg_score[l] += score_casas_mg[l][D][casa]; - eg_score[l] += score_casas_eg[l][D][casa]; + score[l] += score_casas[l][D][casa]; } } if (Bitboard::bit_pieces[PRETAS][D] == 0){ const int rei_eg = reis_score_finais[BRANCAS][Bitboard::bitscan(Bitboard::bit_pieces[BRANCAS][R])]; - mg_score[BRANCAS] += rei_eg; - eg_score[BRANCAS] += rei_eg; + score[BRANCAS] += make_score(rei_eg, rei_eg); } else{ int shield = 0; @@ -192,14 +211,12 @@ int Eval::avaliar(){ else if (Bitboard::bit_pieces[BRANCAS][R] & Bitboard::mask_ala_da_dama){ shield = peao_ala_da_dama[BRANCAS]; } - mg_score[BRANCAS] += shield; - eg_score[BRANCAS] += shield; + score[BRANCAS] += make_score(shield, shield); } if (Bitboard::bit_pieces[BRANCAS][D] == 0){ const int rei_eg = reis_score_finais[PRETAS][Bitboard::bitscan(Bitboard::bit_pieces[PRETAS][R])]; - mg_score[PRETAS] += rei_eg; - eg_score[PRETAS] += rei_eg; + score[PRETAS] += make_score(rei_eg, rei_eg); } else { int shield = 0; @@ -209,12 +226,13 @@ int Eval::avaliar(){ else if (Bitboard::bit_pieces[PRETAS][R] & Bitboard::mask_ala_da_dama){ shield = peao_ala_da_dama[PRETAS]; } - mg_score[PRETAS] += shield; - eg_score[PRETAS] += shield; + score[PRETAS] += make_score(shield, shield); } + // Side-to-move difference, then unpack and interpolate. + const Score diff = score[Game::lado] - score[Game::xlado]; + const int mg_diff = mg_score(diff); + const int eg_diff = eg_score(diff); const int phase = fase(); - const int mg_diff = mg_score[Game::lado] - mg_score[Game::xlado]; - const int eg_diff = eg_score[Game::lado] - eg_score[Game::xlado]; return (mg_diff * phase + eg_diff * (PHASE_MAX - phase)) / PHASE_MAX; } \ No newline at end of file diff --git a/src/eval.h b/src/eval.h index 61d6f4a..3238368 100644 --- a/src/eval.h +++ b/src/eval.h @@ -1,20 +1,64 @@ #ifndef EVAL #define EVAL +#include + #include "consts.h" namespace Eval{ - extern int score_casas_mg[LADOS][TIPOS_DE_PIECES][CASAS_DO_TABULEIRO]; - extern int score_casas_eg[LADOS][TIPOS_DE_PIECES][CASAS_DO_TABULEIRO]; + // Packed midgame/endgame score (Stockfish-style). eg lives in the high + // 16 bits, mg in the low 16. Construction uses + (not |) and the eg + // extractor adds 0x8000 to round-correct the carry that mg's negative + // sign-extension introduces into the eg half during +. With this + // encoding, plain int32 addition of two Scores correctly sums both mg + // and eg as if computed independently (verified algebraically: the + // borrow from negative mg cancels symmetrically when both operands + // share the same encoding). Per-half range stays signed int16. + typedef int32_t Score; + + inline Score make_score(int mg, int eg){ + return (Score)(((uint32_t)eg << 16) + (uint32_t)mg); + } + // mg extraction is a clean low-16 mask + sign extension. + inline int mg_score(Score s){ + return (int16_t)((uint32_t)s & 0xFFFF); + } + // eg extraction adds 0x8000 first so the high-16 read rounds up by one + // when mg has its sign bit set (i.e. mg < 0), undoing the borrow that + // the construction `+` left in the high half. + inline int eg_score(Score s){ + return (int16_t)(((uint32_t)s + 0x8000) >> 16); + } + + // Single packed PST + material table replacing the previous mg/eg pair. + // Memory footprint stays the same (one int32 per slot vs two int32s), so + // cache behavior is comparable, but every per-piece accumulation in + // `avaliar` becomes one load + one add instead of two of each. + extern Score score_casas[LADOS][TIPOS_DE_PIECES][CASAS_DO_TABULEIRO]; extern int reis_score_finais[LADOS][CASAS_DO_TABULEIRO]; extern int passados[LADOS][CASAS_DO_TABULEIRO]; extern int peao_mat[2]; extern int piece_mat[2]; + // Incremental phase counter: maintained by Update::adicionar_piece / + // Update::remover_piece (one += / -= per call) instead of recomputed + // on every Eval::avaliar. Reset to 0 at each board-init path + // (Bitboard::init_board, Game::nova_posicao, Update::setar_posicao); + // those routines already loop through adicionar_piece per square, so + // the counter naturally accumulates the full position's phase weight. + // Like peao_mat / piece_mat / peao_ala_*, this is currently a global — + // when Lazy SMP arrives (§15 item 19) all of them migrate to per-thread + // state together. + extern int fase_valor; + extern const int phase_weights[6]; + void init_eval_tables(); void atualizar_materiais(); + // Cheap accessor: clamps fase_valor to [0, PHASE_MAX]. Promotion to a + // 9th queen could push the raw counter past PHASE_MAX, so the clamp + // keeps the interpolation in its valid range. int fase(); int avaliar(); diff --git a/src/game.cpp b/src/game.cpp index e1d920d..26f4ac3 100644 --- a/src/game.cpp +++ b/src/game.cpp @@ -54,6 +54,10 @@ void Game::nova_posicao(){ Eval::piece_mat[BRANCAS] = Eval::peao_mat[BRANCAS] = 0; Eval::piece_mat[PRETAS] = Eval::peao_mat[PRETAS] = 0; + // Reset the incremental phase counter so the adicionar_piece loop + // below rebuilds it from scratch (just like the material counters above). + Eval::fase_valor = 0; + for (int casa = 0; casa < CASAS_DO_TABULEIRO; casa++){ if (Bitboard::tabuleiro[casa] < VAZIO){ if (Bitboard::bit_lados[BRANCAS] & Bitboard::mask[casa]){ diff --git a/src/update.cpp b/src/update.cpp index b36d333..2531f01 100644 --- a/src/update.cpp +++ b/src/update.cpp @@ -13,6 +13,7 @@ #include "hash.h" #include "game.h" #include "attacks.h" +#include "eval.h" int casa_reversa[LADOS] = {-8,8}; @@ -42,6 +43,8 @@ void Update::remover_piece(const int l, const int p, const int casa){ Bitboard::bit_lados[l] &= Bitboard::not_mask[casa]; Bitboard::bit_pieces[l][p] &= Bitboard::not_mask[casa]; Bitboard::bit_total = Bitboard::bit_lados[BRANCAS] | Bitboard::bit_lados[PRETAS]; + + Eval::fase_valor -= Eval::phase_weights[p]; } void Update::desfaz_lance(){ @@ -218,6 +221,8 @@ void Update::adicionar_piece(const int l, const int piece, const int casa){ Bitboard::bit_lados[l] |= Bitboard::mask[casa]; Bitboard::bit_pieces[l][piece] |= Bitboard::mask[casa]; Bitboard::bit_total = Bitboard::bit_lados[BRANCAS] | Bitboard::bit_lados[PRETAS]; + + Eval::fase_valor += Eval::phase_weights[piece]; } void Update::desfaz_captura(){ @@ -511,6 +516,7 @@ void Update::setar_posicao(char posicao[80], char lado_a_jogar[1], char roques[4 memset(Bitboard::bit_pieces, 0, sizeof(Bitboard::bit_pieces)); memset(Bitboard::bit_lados, 0, sizeof(Bitboard::bit_lados)); Bitboard::bit_total = 0; + Eval::fase_valor = 0; for (int casa = 0; casa < CASAS_DO_TABULEIRO; casa++){ Bitboard::tabuleiro[casa] = VAZIO; From f6fe03a4477097221c621407dbe03bb603bc2dd1 Mon Sep 17 00:00:00 2001 From: Hugo Souza Date: Fri, 8 May 2026 13:57:12 -0300 Subject: [PATCH 03/12] king end-game eval --- src/eval.cpp | 63 +++++++++++++++++++++++----------------------------- src/eval.h | 1 - 2 files changed, 28 insertions(+), 36 deletions(-) diff --git a/src/eval.cpp b/src/eval.cpp index 3f97bcf..f8f9460 100644 --- a/src/eval.cpp +++ b/src/eval.cpp @@ -6,7 +6,6 @@ Eval::Score Eval::score_casas[LADOS][TIPOS_DE_PIECES][CASAS_DO_TABULEIRO]; -int Eval::reis_score_finais[LADOS][CASAS_DO_TABULEIRO]; int Eval::passados[LADOS][CASAS_DO_TABULEIRO]; int Eval::peao_mat[LADOS]; @@ -37,12 +36,20 @@ void Eval::init_eval_tables(){ const int dama_w = Values::dama_score[x] + VALOR_DAMA; const int rei_w = Values::rei_score[x]; + // King PST is the first natively-tapered term: mg = rei_score + // (cares about safety / the back rank), eg = rei_finais_score + // (cares about activity / centralization). The legacy code applied + // a hard conditional swap based on enemy-queen presence; tapered + // eval replaces that with smooth phase interpolation. + const int rei_w_eg = Values::rei_finais_score[x]; + const int rei_b_eg = Values::rei_finais_score[Consts::flip[x]]; + score_casas[BRANCAS][P][x] = make_score(peao_w, peao_w); score_casas[BRANCAS][C][x] = make_score(cavalo_w, cavalo_w); score_casas[BRANCAS][B][x] = make_score(bispo_w, bispo_w); score_casas[BRANCAS][T][x] = make_score(torre_w, torre_w); score_casas[BRANCAS][D][x] = make_score(dama_w, dama_w); - score_casas[BRANCAS][R][x] = make_score(rei_w, rei_w); + score_casas[BRANCAS][R][x] = make_score(rei_w, rei_w_eg); const int peao_b = Values::peao_score[Consts::flip[x]] + VALOR_PEAO; const int cavalo_b = Values::cavalo_score[Consts::flip[x]] + VALOR_CAVALO; @@ -56,15 +63,7 @@ void Eval::init_eval_tables(){ score_casas[PRETAS][B][x] = make_score(bispo_b, bispo_b); score_casas[PRETAS][T][x] = make_score(torre_b, torre_b); score_casas[PRETAS][D][x] = make_score(dama_b, dama_b); - score_casas[PRETAS][R][x] = make_score(rei_b, rei_b); - - // reis_score_finais is the eg-king-PST delta from rei_score to - // rei_finais_score. While we still apply it as a conditional swap - // (queen-presence based), keep storing the legacy delta. Future - // pass: drop this entirely and bake rei_finais_score into the eg - // half of score_casas[R] directly. - reis_score_finais[BRANCAS][x] = Values::rei_finais_score[x] - rei_w; - reis_score_finais[PRETAS][x] = Values::rei_finais_score[x] - rei_b; + score_casas[PRETAS][R][x] = make_score(rei_b, rei_b_eg); passados[BRANCAS][x] = Values::peao_passado_score[Consts::flip[x]]; passados[PRETAS][x] = Values::peao_passado_score[x]; @@ -199,35 +198,29 @@ int Eval::avaliar(){ } } - if (Bitboard::bit_pieces[PRETAS][D] == 0){ - const int rei_eg = reis_score_finais[BRANCAS][Bitboard::bitscan(Bitboard::bit_pieces[BRANCAS][R])]; - score[BRANCAS] += make_score(rei_eg, rei_eg); + // Pawn shield is a midgame-only concept (king wants to stay tucked + // behind pawns while the opponent has heavy pieces; in the endgame + // the king should be active). The legacy code gated this on + // `enemy_queen_present` as a binary proxy for "still mg"; with the + // king PST now natively tapered, we just apply the shield to the mg + // half and let the phase interpolation fade it out as material thins. + int shield_w = 0; + if (Bitboard::bit_pieces[BRANCAS][R] & Bitboard::mask_ala_do_rei){ + shield_w = peao_ala_do_rei[BRANCAS]; } - else{ - int shield = 0; - if (Bitboard::bit_pieces[BRANCAS][R] & Bitboard::mask_ala_do_rei){ - shield = peao_ala_do_rei[BRANCAS]; - } - else if (Bitboard::bit_pieces[BRANCAS][R] & Bitboard::mask_ala_da_dama){ - shield = peao_ala_da_dama[BRANCAS]; - } - score[BRANCAS] += make_score(shield, shield); + else if (Bitboard::bit_pieces[BRANCAS][R] & Bitboard::mask_ala_da_dama){ + shield_w = peao_ala_da_dama[BRANCAS]; } + score[BRANCAS] += make_score(shield_w, 0); - if (Bitboard::bit_pieces[BRANCAS][D] == 0){ - const int rei_eg = reis_score_finais[PRETAS][Bitboard::bitscan(Bitboard::bit_pieces[PRETAS][R])]; - score[PRETAS] += make_score(rei_eg, rei_eg); + int shield_b = 0; + if (Bitboard::bit_pieces[PRETAS][R] & Bitboard::mask_ala_do_rei){ + shield_b = peao_ala_do_rei[PRETAS]; } - else { - int shield = 0; - if (Bitboard::bit_pieces[PRETAS][R] & Bitboard::mask_ala_do_rei){ - shield = peao_ala_do_rei[PRETAS]; - } - else if (Bitboard::bit_pieces[PRETAS][R] & Bitboard::mask_ala_da_dama){ - shield = peao_ala_da_dama[PRETAS]; - } - score[PRETAS] += make_score(shield, shield); + else if (Bitboard::bit_pieces[PRETAS][R] & Bitboard::mask_ala_da_dama){ + shield_b = peao_ala_da_dama[PRETAS]; } + score[PRETAS] += make_score(shield_b, 0); // Side-to-move difference, then unpack and interpolate. const Score diff = score[Game::lado] - score[Game::xlado]; diff --git a/src/eval.h b/src/eval.h index 3238368..6eb0e05 100644 --- a/src/eval.h +++ b/src/eval.h @@ -35,7 +35,6 @@ namespace Eval{ // cache behavior is comparable, but every per-piece accumulation in // `avaliar` becomes one load + one add instead of two of each. extern Score score_casas[LADOS][TIPOS_DE_PIECES][CASAS_DO_TABULEIRO]; - extern int reis_score_finais[LADOS][CASAS_DO_TABULEIRO]; extern int passados[LADOS][CASAS_DO_TABULEIRO]; extern int peao_mat[2]; From 1860f319868098468b13ac821c88fe4ea1939702 Mon Sep 17 00:00:00 2001 From: Hugo Souza Date: Sat, 9 May 2026 16:57:28 -0300 Subject: [PATCH 04/12] add bishop pair bonus --- src/eval.cpp | 4 ++++ src/values.h | 3 +++ 2 files changed, 7 insertions(+) diff --git a/src/eval.cpp b/src/eval.cpp index f8f9460..844142b 100644 --- a/src/eval.cpp +++ b/src/eval.cpp @@ -179,6 +179,10 @@ int Eval::avaliar(){ score[l] += score_casas[l][B][casa]; } + if (Bitboard::popcount(Bitboard::bit_pieces[l][B]) >= 2){ + score[l] += make_score(BISHOP_PAIR_MG, BISHOP_PAIR_EG); + } + t1 = Bitboard::bit_pieces[l][T]; while (t1){ casa = Bitboard::bitscan(t1); diff --git a/src/values.h b/src/values.h index dfc0636..41849d4 100644 --- a/src/values.h +++ b/src/values.h @@ -29,6 +29,9 @@ namespace Values{ #define COLUNA_SEMI_ABERTA_BONUS 11 #define COLUNA_ABERTA_BONUS 40 + #define BISHOP_PAIR_MG 15 + #define BISHOP_PAIR_EG 45 + #define PHASE_PEAO 0 #define PHASE_CAVALO 1 #define PHASE_BISPO 1 From 02f639317adf67cc3d1b9826f8dd555c76ed077b Mon Sep 17 00:00:00 2001 From: Hugo Souza Date: Sat, 9 May 2026 18:59:31 -0300 Subject: [PATCH 05/12] add mobility bonus --- src/eval.cpp | 36 ++++++++++++++++++++++++++++++++++++ src/eval.h | 12 ++++++++++++ src/gen.cpp | 30 +++++++++++++++--------------- src/gen.h | 28 ++++++++++++++++++++++++++++ src/magics.h | 5 +++++ src/values.h | 34 ++++++++++++++++++++++++++++++++++ 6 files changed, 130 insertions(+), 15 deletions(-) diff --git a/src/eval.cpp b/src/eval.cpp index f8f9460..6df630b 100644 --- a/src/eval.cpp +++ b/src/eval.cpp @@ -3,6 +3,7 @@ #include "bitboard.h" #include "consts.h" #include "game.h" +#include "gen.h" Eval::Score Eval::score_casas[LADOS][TIPOS_DE_PIECES][CASAS_DO_TABULEIRO]; @@ -21,6 +22,15 @@ const int Eval::phase_weights[6] = { PHASE_PEAO, PHASE_CAVALO, PHASE_BISPO, PHASE_TORRE, PHASE_DAMA, PHASE_REI }; +// Mobility tables. Definitions are storage-only; the values live in +// `Values::mobilidade_*_mg / _eg` in values.h and get packed into Score +// pairs at startup by init_eval_tables. Same pattern capizero uses for +// PSTs (raw values in Values::, packed/initialized in init_eval_tables). +Eval::Score Eval::mobilidade_cavalo[9]; +Eval::Score Eval::mobilidade_bispo[14]; +Eval::Score Eval::mobilidade_torre[15]; +Eval::Score Eval::mobilidade_dama[28]; + int peao_ala_da_dama[LADOS],peao_ala_do_rei[LADOS]; void Eval::init_eval_tables(){ @@ -68,6 +78,21 @@ void Eval::init_eval_tables(){ passados[BRANCAS][x] = Values::peao_passado_score[Consts::flip[x]]; passados[PRETAS][x] = Values::peao_passado_score[x]; } + + // Pack mobility tables. Values live in values.h as parallel mg/eg + // arrays; build the packed Score lookup once at startup. + for (int i = 0; i < 9; i++){ + mobilidade_cavalo[i] = make_score(Values::mobilidade_cavalo_mg[i], Values::mobilidade_cavalo_eg[i]); + } + for (int i = 0; i < 14; i++){ + mobilidade_bispo[i] = make_score(Values::mobilidade_bispo_mg[i], Values::mobilidade_bispo_eg[i]); + } + for (int i = 0; i < 15; i++){ + mobilidade_torre[i] = make_score(Values::mobilidade_torre_mg[i], Values::mobilidade_torre_eg[i]); + } + for (int i = 0; i < 28; i++){ + mobilidade_dama[i] = make_score(Values::mobilidade_dama_mg[i], Values::mobilidade_dama_eg[i]); + } } int Eval::fase(){ @@ -153,6 +178,13 @@ int Eval::avaliar(){ for (int l = 0; l < LADOS; l++){ + // Mobility uses popcount of attack squares that aren't own pieces. + // Each attacked square counts; enemy-occupied squares (capturable + // targets) and empty squares both contribute. Phase-aware weights + // recognize that bishop/rook mobility matters more in the endgame + // where boards are open, while knight mobility is roughly uniform. + const Bitboard::u64 nao_proprios = ~Bitboard::bit_lados[l]; + t1 = Bitboard::bit_pieces[l][P]; while (t1){ casa = Bitboard::bitscan(t1); @@ -169,6 +201,7 @@ int Eval::avaliar(){ t1 &= Bitboard::not_mask[casa]; score[l] += score_casas[l][C][casa]; + score[l] += mobilidade_cavalo[Bitboard::popcount(Gen::bit_moves_cavalo[casa] & nao_proprios)]; } t1 = Bitboard::bit_pieces[l][B]; @@ -177,6 +210,7 @@ int Eval::avaliar(){ t1 &= Bitboard::not_mask[casa]; score[l] += score_casas[l][B][casa]; + score[l] += mobilidade_bispo[Bitboard::popcount(Gen::atacantes_bispo(casa) & nao_proprios)]; } t1 = Bitboard::bit_pieces[l][T]; @@ -187,6 +221,7 @@ int Eval::avaliar(){ score[l] += score_casas[l][T][casa]; const int torre_b = avaliar_torre(l, casa); score[l] += make_score(torre_b, torre_b); + score[l] += mobilidade_torre[Bitboard::popcount(Gen::atacantes_torre(casa) & nao_proprios)]; } t1 = Bitboard::bit_pieces[l][D]; @@ -195,6 +230,7 @@ int Eval::avaliar(){ t1 &= Bitboard::not_mask[casa]; score[l] += score_casas[l][D][casa]; + score[l] += mobilidade_dama[Bitboard::popcount((Gen::atacantes_bispo(casa) | Gen::atacantes_torre(casa)) & nao_proprios)]; } } diff --git a/src/eval.h b/src/eval.h index 6eb0e05..ff2fb6a 100644 --- a/src/eval.h +++ b/src/eval.h @@ -52,6 +52,18 @@ namespace Eval{ extern int fase_valor; extern const int phase_weights[6]; + // Non-linear tapered mobility tables. Indexed by the popcount of attack + // squares the piece reaches that aren't blocked by own pieces. Built at + // startup in init_eval_tables by packing the parallel mg/eg arrays from + // values.h (`Values::mobilidade_*_mg / _eg`) into Score pairs. Negative + // values at low indices encode the trapped-piece penalty; saturating + // tail avoids over-rewarding pieces that are already maximally mobile. + // Knight max mob = 8, bishop max = 13, rook max = 14, queen max = 27. + extern Score mobilidade_cavalo[9]; + extern Score mobilidade_bispo[14]; + extern Score mobilidade_torre[15]; + extern Score mobilidade_dama[28]; + void init_eval_tables(); void atualizar_materiais(); diff --git a/src/gen.cpp b/src/gen.cpp index 655886d..90fb17d 100644 --- a/src/gen.cpp +++ b/src/gen.cpp @@ -19,8 +19,8 @@ Bitboard::u64 peao_uma_casa[LADOS][CASAS_DO_TABULEIRO]; Bitboard::u64 peao_duas_casas[LADOS][CASAS_DO_TABULEIRO]; -Bitboard::u64 bit_casas_relevantes_bispo[CASAS_DO_TABULEIRO]; -Bitboard::u64 bit_casas_relevantes_torres[CASAS_DO_TABULEIRO]; +Bitboard::u64 Gen::bit_casas_relevantes_bispo[CASAS_DO_TABULEIRO]; +Bitboard::u64 Gen::bit_casas_relevantes_torres[CASAS_DO_TABULEIRO]; Bitboard::u64 Gen::bit_esquerda[LADOS][CASAS_DO_TABULEIRO]; Bitboard::u64 Gen::bit_direita[LADOS][CASAS_DO_TABULEIRO]; @@ -186,20 +186,20 @@ void init_rei_lookups(){ void init_casas_relevantes(){ for (int casa = 0; casa < CASAS_DO_TABULEIRO; casa++){ - bit_casas_relevantes_bispo[casa] = Gen::bit_moves_bispo[casa] & Bitboard::bordas_neg; - bit_casas_relevantes_torres[casa] = Gen::bit_moves_torre[casa]; + Gen::bit_casas_relevantes_bispo[casa] = Gen::bit_moves_bispo[casa] & Bitboard::bordas_neg; + Gen::bit_casas_relevantes_torres[casa] = Gen::bit_moves_torre[casa]; if (Consts::colunas[casa] != COLUNA_A){ - bit_casas_relevantes_torres[casa] &= ~Bitboard::mask_cols[COLUNA_A]; + Gen::bit_casas_relevantes_torres[casa] &= ~Bitboard::mask_cols[COLUNA_A]; } if (Consts::colunas[casa] != COLUNA_H){ - bit_casas_relevantes_torres[casa] &= ~Bitboard::mask_cols[COLUNA_H]; + Gen::bit_casas_relevantes_torres[casa] &= ~Bitboard::mask_cols[COLUNA_H]; } if (Consts::linhas[casa] != PRIMEIRA_LINHA){ - bit_casas_relevantes_torres[casa] &= ~Bitboard::mask_rows[LINHA_1]; + Gen::bit_casas_relevantes_torres[casa] &= ~Bitboard::mask_rows[LINHA_1]; } if (Consts::linhas[casa] != ULTIMA_LINHA){ - bit_casas_relevantes_torres[casa] &= ~Bitboard::mask_rows[LINHA_8]; + Gen::bit_casas_relevantes_torres[casa] &= ~Bitboard::mask_rows[LINHA_8]; } } } @@ -333,22 +333,22 @@ void init_magic_lookups(){ int index; for (int casa = 0; casa < CASAS_DO_TABULEIRO; casa++){ for (int pecaBloqueadora = 0; pecaBloqueadora < (1 << (64-Magics::bits_indices_bispos[casa])); pecaBloqueadora++){ - Bitboard::u64 bloqueadores = obterBloqueadoresPorCasa(pecaBloqueadora, bit_casas_relevantes_bispo[casa]); - + Bitboard::u64 bloqueadores = obterBloqueadoresPorCasa(pecaBloqueadora, Gen::bit_casas_relevantes_bispo[casa]); + #ifdef USE_PEXT - index = _pext_u64(bloqueadores, bit_casas_relevantes_bispo[casa]); + index = _pext_u64(bloqueadores, Gen::bit_casas_relevantes_bispo[casa]); #else index = (bloqueadores * Magics::magicas_bispos[casa]) >> (Magics::bits_indices_bispos[casa]); #endif - Gen::bit_magicas_bispo[casa][index] = gerarLancesBispoSemMagica(casa, bloqueadores); + Gen::bit_magicas_bispo[casa][index] = gerarLancesBispoSemMagica(casa, bloqueadores); } for (int pecaBloqueadora = 0; pecaBloqueadora < (1 << (64-Magics::bits_indices_torres[casa])); pecaBloqueadora++){ - Bitboard::u64 bloqueadores = obterBloqueadoresPorCasa(pecaBloqueadora, bit_casas_relevantes_torres[casa]); - + Bitboard::u64 bloqueadores = obterBloqueadoresPorCasa(pecaBloqueadora, Gen::bit_casas_relevantes_torres[casa]); + #ifdef USE_PEXT - index = _pext_u64(bloqueadores, bit_casas_relevantes_torres[casa]); + index = _pext_u64(bloqueadores, Gen::bit_casas_relevantes_torres[casa]); #else index = (bloqueadores * Magics::magicas_torres[casa]) >> (Magics::bits_indices_torres[casa]); #endif diff --git a/src/gen.h b/src/gen.h index 9173c4d..7c94cf6 100644 --- a/src/gen.h +++ b/src/gen.h @@ -5,6 +5,11 @@ #include "bitboard.h" #include "params.h" +#include "magics.h" + +#ifdef USE_PEXT +#include +#endif namespace Gen{ // Packed move: squares fit in 6 bits (0-63), promotion piece in 4 bits @@ -40,9 +45,32 @@ namespace Gen{ extern Bitboard::u64 bit_magicas_bispo[CASAS_DO_TABULEIRO][MAGIC_HASHTABLE_SIZE]; extern Bitboard::u64 bit_magicas_torre[CASAS_DO_TABULEIRO][MAGIC_HASHTABLE_SIZE]; + extern Bitboard::u64 bit_casas_relevantes_bispo[CASAS_DO_TABULEIRO]; + extern Bitboard::u64 bit_casas_relevantes_torres[CASAS_DO_TABULEIRO]; + extern Bitboard::u64 bit_peao_capturas[LADOS][CASAS_DO_TABULEIRO]; extern Bitboard::u64 bit_peao_defende[LADOS][CASAS_DO_TABULEIRO]; + // Sliding-piece attack bitboards given current `Bitboard::bit_total` + // occupancy. Inline so eval/mobility code gets the magic-table lookup + // without a function-call boundary. gen.cpp's hot paths still inline + // the same incantation manually — adopting these in those spots is a + // followup cleanup, not a behavior change. + inline Bitboard::u64 atacantes_bispo(int casa){ + #ifdef USE_PEXT + return bit_magicas_bispo[casa][_pext_u64(Bitboard::bit_total, bit_casas_relevantes_bispo[casa])]; + #else + return bit_magicas_bispo[casa][((Bitboard::bit_total & bit_casas_relevantes_bispo[casa]) * Magics::magicas_bispos[casa]) >> (Magics::bits_indices_bispos[casa])]; + #endif + } + inline Bitboard::u64 atacantes_torre(int casa){ + #ifdef USE_PEXT + return bit_magicas_torre[casa][_pext_u64(Bitboard::bit_total, bit_casas_relevantes_torres[casa])]; + #else + return bit_magicas_torre[casa][((Bitboard::bit_total & bit_casas_relevantes_torres[casa]) * Magics::magicas_torres[casa]) >> (Magics::bits_indices_torres[casa])]; + #endif + } + void init_lookup_tables(); // Full pseudo-legal move list (captures + quiets). Used by perft and by // anything outside `Search::pesquisa` that wants the whole list at once. diff --git a/src/magics.h b/src/magics.h index b5bf7a5..f1dc835 100644 --- a/src/magics.h +++ b/src/magics.h @@ -1,3 +1,6 @@ +#ifndef MAGICS +#define MAGICS + #include "bitboard.h" namespace Magics{ @@ -55,3 +58,5 @@ namespace Magics{ 52, 53, 53, 53, 53, 53, 53, 52 }; }; + +#endif diff --git a/src/values.h b/src/values.h index dfc0636..8ba72b0 100644 --- a/src/values.h +++ b/src/values.h @@ -37,6 +37,40 @@ namespace Values{ #define PHASE_REI 0 #define PHASE_MAX 24 + // Tapered mobility tables, indexed by attack-count (popcount of the + // piece's attack bitboard with own pieces masked out). Stored here as + // parallel mg/eg int arrays — `Eval::init_eval_tables` packs them into + // `Eval::mobilidade_*` Score tables at startup. Curve shape: brutal + // negative at trapped (low index), near-linear in the middle, saturating + // at the top. First-pass literature values; will be Texel-tuned later. + // Knight max attack-count = 8, bishop = 13, rook = 14, queen = 27. + const int mobilidade_cavalo_mg[9] = { + -40, -15, -3, 0, 3, 6, 9, 11, 13 + }; + const int mobilidade_cavalo_eg[9] = { + -35, -12, -3, 0, 3, 6, 9, 11, 13 + }; + const int mobilidade_bispo_mg[14] = { + -25, -10, -2, 0, 3, 5, 7, 9, 10, 11, 12, 12, 12, 12 + }; + const int mobilidade_bispo_eg[14] = { + -30, -15, -3, 3, 8, 11, 14, 17, 19, 21, 23, 24, 25, 26 + }; + const int mobilidade_torre_mg[15] = { + -25, -10, -3, 0, 2, 4, 6, 7, 8, 9, 10, 10, 10, 10, 10 + }; + const int mobilidade_torre_eg[15] = { + -35, -15, -5, 0, 5, 10, 15, 18, 21, 23, 25, 27, 28, 29, 30 + }; + const int mobilidade_dama_mg[28] = { + -15, -8, -2, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, + 11, 12, 13, 14, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15 + }; + const int mobilidade_dama_eg[28] = { + -20, -12, -4, 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, + 22, 24, 26, 28, 29, 30, 31, 32, 33, 34, 34, 34, 34, 34 + }; + // REDUÇÕES E CONDIÇÕES #define REDUCAO_IID /4 From 3a7fb904db153140c61461e9e2c54340986e6299 Mon Sep 17 00:00:00 2001 From: Hugo Souza Date: Sun, 10 May 2026 15:50:25 -0300 Subject: [PATCH 06/12] add PeSTO PSTs --- src/eval.cpp | 62 +++++++--------- src/values.h | 198 ++++++++++++++++++++++++++++++++------------------- 2 files changed, 153 insertions(+), 107 deletions(-) diff --git a/src/eval.cpp b/src/eval.cpp index 04bb335..cfdfe84 100644 --- a/src/eval.cpp +++ b/src/eval.cpp @@ -39,41 +39,33 @@ void Eval::init_eval_tables(){ // halves. Future passes will differentiate (e.g. king PST: mg = rei_score, // eg = rei_finais_score, dropping the conditional swap below). for (int x = 0; x < CASAS_DO_TABULEIRO; x++){ - const int peao_w = Values::peao_score[x] + VALOR_PEAO; - const int cavalo_w = Values::cavalo_score[x] + VALOR_CAVALO; - const int bispo_w = Values::bispo_score[x] + VALOR_BISPO; - const int torre_w = Values::torre_score[x] + VALOR_TORRE; - const int dama_w = Values::dama_score[x] + VALOR_DAMA; - const int rei_w = Values::rei_score[x]; - - // King PST is the first natively-tapered term: mg = rei_score - // (cares about safety / the back rank), eg = rei_finais_score - // (cares about activity / centralization). The legacy code applied - // a hard conditional swap based on enemy-queen presence; tapered - // eval replaces that with smooth phase interpolation. - const int rei_w_eg = Values::rei_finais_score[x]; - const int rei_b_eg = Values::rei_finais_score[Consts::flip[x]]; - - score_casas[BRANCAS][P][x] = make_score(peao_w, peao_w); - score_casas[BRANCAS][C][x] = make_score(cavalo_w, cavalo_w); - score_casas[BRANCAS][B][x] = make_score(bispo_w, bispo_w); - score_casas[BRANCAS][T][x] = make_score(torre_w, torre_w); - score_casas[BRANCAS][D][x] = make_score(dama_w, dama_w); - score_casas[BRANCAS][R][x] = make_score(rei_w, rei_w_eg); - - const int peao_b = Values::peao_score[Consts::flip[x]] + VALOR_PEAO; - const int cavalo_b = Values::cavalo_score[Consts::flip[x]] + VALOR_CAVALO; - const int bispo_b = Values::bispo_score[Consts::flip[x]] + VALOR_BISPO; - const int torre_b = Values::torre_score[Consts::flip[x]] + VALOR_TORRE; - const int dama_b = Values::dama_score[Consts::flip[x]] + VALOR_DAMA; - const int rei_b = Values::rei_score[Consts::flip[x]]; - - score_casas[PRETAS][P][x] = make_score(peao_b, peao_b); - score_casas[PRETAS][C][x] = make_score(cavalo_b, cavalo_b); - score_casas[PRETAS][B][x] = make_score(bispo_b, bispo_b); - score_casas[PRETAS][T][x] = make_score(torre_b, torre_b); - score_casas[PRETAS][D][x] = make_score(dama_b, dama_b); - score_casas[PRETAS][R][x] = make_score(rei_b, rei_b_eg); + const int xf = Consts::flip[x]; + + score_casas[BRANCAS][P][x] = make_score(VALOR_PEAO_MG + Values::peao_score_mg[x], + VALOR_PEAO_EG + Values::peao_score_eg[x]); + score_casas[BRANCAS][C][x] = make_score(VALOR_CAVALO_MG + Values::cavalo_score_mg[x], + VALOR_CAVALO_EG + Values::cavalo_score_eg[x]); + score_casas[BRANCAS][B][x] = make_score(VALOR_BISPO_MG + Values::bispo_score_mg[x], + VALOR_BISPO_EG + Values::bispo_score_eg[x]); + score_casas[BRANCAS][T][x] = make_score(VALOR_TORRE_MG + Values::torre_score_mg[x], + VALOR_TORRE_EG + Values::torre_score_eg[x]); + score_casas[BRANCAS][D][x] = make_score(VALOR_DAMA_MG + Values::dama_score_mg[x], + VALOR_DAMA_EG + Values::dama_score_eg[x]); + score_casas[BRANCAS][R][x] = make_score(Values::rei_score_mg[x], + Values::rei_score_eg[x]); + + score_casas[PRETAS][P][x] = make_score(VALOR_PEAO_MG + Values::peao_score_mg[xf], + VALOR_PEAO_EG + Values::peao_score_eg[xf]); + score_casas[PRETAS][C][x] = make_score(VALOR_CAVALO_MG + Values::cavalo_score_mg[xf], + VALOR_CAVALO_EG + Values::cavalo_score_eg[xf]); + score_casas[PRETAS][B][x] = make_score(VALOR_BISPO_MG + Values::bispo_score_mg[xf], + VALOR_BISPO_EG + Values::bispo_score_eg[xf]); + score_casas[PRETAS][T][x] = make_score(VALOR_TORRE_MG + Values::torre_score_mg[xf], + VALOR_TORRE_EG + Values::torre_score_eg[xf]); + score_casas[PRETAS][D][x] = make_score(VALOR_DAMA_MG + Values::dama_score_mg[xf], + VALOR_DAMA_EG + Values::dama_score_eg[xf]); + score_casas[PRETAS][R][x] = make_score(Values::rei_score_mg[xf], + Values::rei_score_eg[xf]); passados[BRANCAS][x] = Values::peao_passado_score[Consts::flip[x]]; passados[PRETAS][x] = Values::peao_passado_score[x]; diff --git a/src/values.h b/src/values.h index 11c7088..86fae56 100644 --- a/src/values.h +++ b/src/values.h @@ -16,7 +16,19 @@ namespace Values{ #define VALOR_DAMA 900 #define VALOR_REI 10000 - const int pieces_valor[6] = + // PeSTO tapered piece values — mg and eg material baked into score_casas. + #define VALOR_PEAO_MG 82 + #define VALOR_PEAO_EG 94 + #define VALOR_CAVALO_MG 337 + #define VALOR_CAVALO_EG 281 + #define VALOR_BISPO_MG 365 + #define VALOR_BISPO_EG 297 + #define VALOR_TORRE_MG 477 + #define VALOR_TORRE_EG 512 + #define VALOR_DAMA_MG 1025 + #define VALOR_DAMA_EG 936 + + const int pieces_valor[6] = { VALOR_PEAO, VALOR_CAVALO, VALOR_BISPO, VALOR_TORRE, VALOR_DAMA, VALOR_REI }; @@ -231,88 +243,130 @@ namespace Values{ 0, 0, 0, 0, 0, 0, 0, 0 }}; - const int peao_score[64] = - { - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 2, 4, -12, -12, 4, 2, 0, - 0, 2, 4, 4, 4, 4, 2, 0, - 0, 2, 4, 8, 8, 4, 2, 0, - 0, 2, 4, 8, 8, 4, 2, 0, - 4, 8, 10, 16, 16, 10, 8, 4, - 100, 100, 100, 100, 100, 100, 100, 100, - 0, 0, 0, 0, 0, 0, 0, 0 + const int peao_score_mg[64] = { + 0, 0, 0, 0, 0, 0, 0, 0, + -35, -1, -20, -23, -15, 24, 38, -22, + -26, -4, -4, -10, 3, 3, 33, -12, + -27, -2, -5, 12, 17, 6, 10, -25, + -14, 13, 6, 21, 23, 12, 17, -23, + -6, 7, 26, 31, 65, 56, 25, -20, + 98, 134, 61, 95, 68, 126, 34, -11, + 0, 0, 0, 0, 0, 0, 0, 0 }; - - const int cavalo_score[64] = - { - -30, -20, -10, -8, -8, -10, -20, -30, - -16, -6, -2, 0, 0, -2, -6, -16, - -8, -2, 4, 6, 6, 4, -2, -8, - -5, 0, 6, 8, 8, 6, 0, -5, - -5, 0, 6, 8, 8, 6, 0, -5, - -10, -2, 4, 6, 6, 4, -2, -10, - -20, -10, -2, 0, 0, -2, -10, -20, - -150, -20, -10, -5, -5, -10, -20, -150 + const int peao_score_eg[64] = { + 0, 0, 0, 0, 0, 0, 0, 0, + 13, 8, 8, 10, 13, 0, 2, -7, + 4, 7, -6, 1, 0, -5, -1, -8, + 13, 9, -3, -7, -7, -8, 3, -1, + 32, 24, 13, 5, -2, 4, 17, 17, + 94, 100, 85, 67, 56, 53, 82, 84, + 178, 173, 158, 134, 147, 132, 165, 187, + 0, 0, 0, 0, 0, 0, 0, 0 }; - const int bispo_score[64] = - { - -10, -10, -12, -10, -10, -12, -10, -10, - 0, 4, 4, 4, 4, 4, 4, 0, - 2, 4, 6, 6, 6, 6, 4, 2, - 2, 4, 6, 8, 8, 6, 4, 2, - 2, 4, 6, 8, 8, 6, 4, 2, - 2, 4, 6, 6, 6, 6, 4, 2, - -10, 4, 4, 4, 4, 4, 4, -10, - -10, -10, -10, -10, -10, -10, -10, -10 + const int cavalo_score_mg[64] = { + -105, -21, -58, -33, -17, -28, -19, -23, + -29, -53, -12, -3, -1, 18, -14, -19, + -23, -9, 12, 10, 19, 17, 25, -16, + -13, 4, 16, 13, 28, 19, 21, -8, + -9, 17, 19, 53, 37, 69, 18, 22, + -47, 60, 37, 65, 84, 129, 73, 44, + -73, -41, 72, 36, 23, 62, 7, -17, + -167, -89, -34, -49, 61, -97, -15, -107 + }; + const int cavalo_score_eg[64] = { + -29, -51, -23, -15, -22, -18, -50, -64, + -42, -20, -10, -5, -2, -20, -23, -44, + -23, -3, -1, 15, 10, -3, -20, -22, + -18, -6, 16, 25, 16, 17, 4, -18, + -17, 3, 22, 22, 22, 11, 8, -18, + -24, -20, 10, 9, -1, -9, -19, -41, + -25, -8, -25, -2, -9, -25, -24, -52, + -58, -38, -13, -28, -31, -27, -63, -99 }; - const int torre_score[64] = - { - 4, 4, 4, 6, 6, 4, 4, 4, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 20, 20, 20, 20, 20, 20, 20, 20, - 10, 10, 10, 10, 10, 10, 10, 10 + const int bispo_score_mg[64] = { + -33, -3, -14, -21, -13, -12, -39, -21, + 4, 15, 16, 0, 7, 21, 33, 1, + 0, 15, 15, 15, 14, 27, 18, 10, + -6, 13, 13, 26, 34, 12, 10, 4, + -4, 5, 19, 50, 37, 37, 7, -2, + -16, 37, 43, 40, 35, 50, 37, -2, + -26, 16, -18, -13, 30, 59, 18, -47, + -29, 4, -82, -37, -25, -42, 7, -8 + }; + const int bispo_score_eg[64] = { + -23, -9, -23, -5, -9, -16, -5, -17, + -14, -18, -7, -1, 4, -9, -15, -27, + -12, -3, 8, 10, 13, 3, -7, -15, + -6, 3, 13, 19, 7, 10, -3, -9, + -3, 9, 12, 9, 14, 10, 3, 2, + 2, -8, 0, -1, -2, 6, 0, 4, + -8, -4, 7, -12, -3, -13, -4, -14, + -14, -21, -11, -8, -7, -9, -17, -24 }; - const int dama_score[64] = - { - -10, -10, -6, -4, -4, -6, -10, -10, - -10, 2, 2, 2, 2, 2, 2, -10, - 2, 2, 2, 3, 3, 2, 2, 2, - 2, 2, 3, 4, 4, 3, 2, 2, - 2, 2, 3, 4, 4, 3, 2, 2, - 2, 2, 2, 3, 3, 2, 2, 2, - -10, 2, 2, 2, 2, 2, 2, -10, - -10, -10, 2, 2, 2, 2, -10, -10 + const int torre_score_mg[64] = { + -19, -13, 1, 17, 16, 7, -37, -26, + -44, -16, -20, -9, -1, 11, -6, -71, + -45, -25, -16, -17, 3, 0, -5, -33, + -36, -26, -12, -1, 9, -7, 6, -23, + -24, -11, 7, 26, 24, 35, -8, -20, + -5, 19, 26, 36, 17, 45, 61, 16, + 27, 32, 58, 62, 80, 67, 26, 44, + 32, 42, 32, 51, 63, 9, 31, 43 + }; + const int torre_score_eg[64] = { + -9, 2, 3, -1, -5, -13, 4, -20, + -6, -6, 0, 2, -9, -9, -11, -3, + -4, 0, -5, -1, -7, -12, -8, -16, + 3, 5, 8, 4, -5, -6, -8, -11, + 4, 3, 13, 1, 2, 1, -1, 2, + 7, 7, 7, 5, 4, -3, -5, -3, + 11, 13, 13, 11, -3, 3, 8, 3, + 13, 10, 18, 15, 12, 12, 8, 5 }; - const int rei_score[64] = - { - 20, 20, 20, -40, 10, -60, 20, 20, - 15, 20, -25, -30, -30, -45, 20, 15, - -48, -48, -48, -48, -48, -48, -48, -48, - -48, -48, -48, -48, -48, -48, -48, -48, - -48, -48, -48, -48, -48, -48, -48, -48, - -48, -48, -48, -48, -48, -48, -48, -48, - -48, -48, -48, -48, -48, -48, -48, -48, - -48, -48, -48, -48, -48, -48, -48, -48 + const int dama_score_mg[64] = { + -1, -18, -9, 10, -15, -25, -31, -50, + -35, -8, 11, 2, 8, 15, -3, 1, + -14, 2, -11, -2, -5, 2, 14, 5, + -9, -26, -9, -10, -2, -4, 3, -3, + -27, -27, -16, -16, -1, 17, -2, 1, + -13, -17, 7, 8, 29, 56, 47, 57, + -24, -39, -5, 1, -16, 57, 28, 54, + -28, 0, 29, 12, 59, 44, 43, 45 + }; + const int dama_score_eg[64] = { + -33, -28, -22, -43, -5, -32, -20, -41, + -22, -23, -30, -16, -16, -23, -36, -32, + -16, -27, 15, 6, 9, 17, 10, 5, + -18, 28, 19, 47, 31, 34, 39, 23, + 3, 22, 24, 45, 57, 40, 57, 36, + -20, 6, 9, 49, 47, 35, 19, 9, + -17, 20, 32, 41, 58, 25, 30, 0, + -9, 22, 22, 27, 27, 19, 10, 20 }; - const int rei_finais_score[64] = - { - 0, 8, 16, 18, 18, 16, 8, 0, - 8, 16, 24, 32, 32, 24, 16, 8, - 16, 24, 32, 40, 40, 32, 24, 16, - 25, 32, 40, 48, 48, 40, 32, 25, - 25, 32, 40, 48, 48, 40, 32, 25, - 16, 24, 32, 40, 40, 32, 24, 16, - 8, 16, 24, 32, 32, 24, 16, 8, - 0, 8, 16, 18, 18, 16, 8, 0 + const int rei_score_mg[64] = { + -15, 36, 12, -54, 8, -28, 24, 14, + 1, 7, -8, -64, -43, -16, 9, 8, + -14, -14, -22, -46, -44, -30, -15, -27, + -49, -1, -27, -39, -46, -44, -33, -51, + -17, -20, -12, -27, -30, -25, -14, -36, + -9, 24, 2, -16, -20, 6, 22, -22, + 29, -1, -20, -7, -8, -4, -38, -29, + -65, 23, 16, -15, -56, -34, 2, 13 + }; + const int rei_score_eg[64] = { + -53, -34, -21, -11, -28, -14, -24, -43, + -27, -11, 4, 13, 14, 4, -5, -17, + -19, -3, 11, 21, 23, 16, 7, -9, + -18, -4, 21, 24, 27, 23, 9, -11, + -8, 22, 24, 27, 26, 33, 26, 3, + 10, 17, 23, 15, 20, 45, 44, 13, + -12, 17, 14, 17, 17, 38, 23, 11, + -74, -35, -18, -18, -11, 15, 4, -17 }; const int peao_passado_score[64] = From 8ce743d0f18e6fe6441bfb801ead19341146de2f Mon Sep 17 00:00:00 2001 From: Hugo Souza Date: Sun, 10 May 2026 18:49:44 -0300 Subject: [PATCH 07/12] Update version from beta2.5.0 to beta2.6.0 --- version.capizero | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.capizero b/version.capizero index 1af2aef..0bc543e 100644 --- a/version.capizero +++ b/version.capizero @@ -1 +1 @@ -'"beta2.5.0"' +'"beta2.6.0"' From 50345fe01dffec7b4a113353d977a251fa24c881 Mon Sep 17 00:00:00 2001 From: Hugo Souza Date: Sun, 10 May 2026 19:10:34 -0300 Subject: [PATCH 08/12] add texel tuner --- Makefile | 8 ++ src/tuner.cpp | 228 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 236 insertions(+) create mode 100644 src/tuner.cpp diff --git a/Makefile b/Makefile index 0264289..32083dc 100644 --- a/Makefile +++ b/Makefile @@ -80,6 +80,14 @@ bench: clean ./src/bench_tests.o $(SRCS) @ echo "=================" @ echo "capi_bench compilado com sucesso" +# Texel-style eval tuner. Reuses the engine's eval/board/state code; tuner.cpp +# adds the dataset loader, loss function, and (eventually) coordinate-descent +# tuning loop. Runs as: ./capi_tuner +tuner: clean ./src/tuner.o $(SRCS) + @ $(COMP) $(CXXFLAGS) -o capi_tuner ./src/tuner.o $(SRCS) + @ echo "=================" + @ echo "capi_tuner compilado com sucesso" + magics: ./src/generate_magics.cpp @ $(COMP) -c $(CXXFLAGS) ./src/generate_magics.cpp -o ./src/generate_magics.o @ $(COMP) -o generate_magics ./src/generate_magics.o diff --git a/src/tuner.cpp b/src/tuner.cpp new file mode 100644 index 0000000..6f9ca88 --- /dev/null +++ b/src/tuner.cpp @@ -0,0 +1,228 @@ +/* + * Texel-style eval tuner for capizero. + * + * Commit 1 (this file): scaffolding + FEN parser + dataset loader + loss + * function + K (sigmoid-scaling) optimization. Reports the initial loss for + * the engine's current eval parameters. Does NOT yet mutate parameters. + * + * Commit 2 (follow-up): coordinate-descent tuning loop that walks every + * Eval::score_casas slot and Eval::mobilidade_* entry, accepting any + * ±1 step that reduces total dataset loss. Output formatting for new + * values.h content. + * + * Build with `make tuner`. Run as: `./capi_tuner `. + * + * Dataset format (one position per line; tokenizer splits on whitespace, + * '|', and brackets so common variants all work): + * + * + * + * where is one of: 1.0 / 0.5 / 0.0 (or 1-0 / 1/2-1/2 / 0-1). + * + * Examples: + * rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1 [0.5] + * r1bqkb1r/pppp1ppp/2n2n2/4p3/2B1P3/5N2/PPPP1PPP/RNBQK2R w KQkq - 4 4 1.0 + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "consts.h" +#include "init.h" +#include "game.h" +#include "update.h" +#include "bitboard.h" +#include "eval.h" + +// Compact per-position record. Stored as parsed FEN tokens so the inner loop +// can re-set the board cheaply via Update::setar_posicao without re-tokenizing +// strings every pass. +struct TuningPos { + char fen[96]; + char lado[4]; + char roques[6]; + char ep[4]; + char hm[6]; + char fm[6]; + float result; // 0.0, 0.5, 1.0 +}; + +static std::vector dataset; + +// --------------------------------------------------------------------------- +// FEN line parsing. +// Accepts tokens separated by space, tab, '|', '[', or ']' to handle the +// common dataset variants (Stockfish-style "FEN [1.0]", Ethereal-style +// "FEN | 1.0", or plain "FEN 1.0"). Missing hm/fm fields default to 0/1. + +static bool result_to_float(const char *s, float &out) { + if (!strcmp(s, "1.0") || !strcmp(s, "1") || !strcmp(s, "1-0")) { + out = 1.0f; return true; + } + if (!strcmp(s, "0.5") || !strcmp(s, "1/2-1/2") || !strcmp(s, "0.5-0.5")) { + out = 0.5f; return true; + } + if (!strcmp(s, "0.0") || !strcmp(s, "0") || !strcmp(s, "0-1")) { + out = 0.0f; return true; + } + return false; +} + +static bool parse_line(char *line, TuningPos &out) { + // Tokenize. strtok mutates `line`; that's fine since the caller owns it. + const char *seps = " \t\r\n|[]"; + char *tokens[10] = {0}; + int n = 0; + char *tok = strtok(line, seps); + while (tok && n < 10) { + tokens[n++] = tok; + tok = strtok(NULL, seps); + } + + // Need at minimum: 4 FEN fields (placement, stm, castling, ep) + result. + // hm/fm are optional and don't affect eval. + if (n < 5) return false; + + // Result is always the last token. + if (!result_to_float(tokens[n - 1], out.result)) return false; + + strncpy(out.fen, tokens[0], sizeof(out.fen) - 1); out.fen[sizeof(out.fen)-1] = '\0'; + strncpy(out.lado, tokens[1], sizeof(out.lado) - 1); out.lado[sizeof(out.lado)-1] = '\0'; + strncpy(out.roques, tokens[2], sizeof(out.roques) - 1); out.roques[sizeof(out.roques)-1] = '\0'; + strncpy(out.ep, tokens[3], sizeof(out.ep) - 1); out.ep[sizeof(out.ep)-1] = '\0'; + + // hm/fm defaulted if not present (eval doesn't use them but setar_posicao expects fields). + if (n >= 7) { + strncpy(out.hm, tokens[4], sizeof(out.hm) - 1); out.hm[sizeof(out.hm)-1] = '\0'; + strncpy(out.fm, tokens[5], sizeof(out.fm) - 1); out.fm[sizeof(out.fm)-1] = '\0'; + } else { + strcpy(out.hm, "0"); + strcpy(out.fm, "1"); + } + + return true; +} + +static void load_dataset(const char *path) { + FILE *f = fopen(path, "r"); + if (!f) { + fprintf(stderr, "tuner: cannot open dataset %s: %s\n", path, strerror(errno)); + exit(1); + } + + char line[512]; + int loaded = 0, skipped = 0; + while (fgets(line, sizeof(line), f)) { + // Skip empty lines and comments + char *p = line; + while (*p == ' ' || *p == '\t') p++; + if (*p == '\0' || *p == '\n' || *p == '#') continue; + + TuningPos pos; + if (parse_line(line, pos)) { + dataset.push_back(pos); + loaded++; + } else { + skipped++; + } + } + fclose(f); + + fprintf(stderr, "tuner: loaded %d positions, skipped %d\n", loaded, skipped); + if (loaded == 0) { + fprintf(stderr, "tuner: empty dataset, aborting\n"); + exit(1); + } +} + +// --------------------------------------------------------------------------- +// Loss function. For each position, set up the board, get the static eval +// from white's perspective, apply sigmoid, compute squared error vs game +// result. Total loss = mean over dataset. +// +// `K` is the sigmoid scaling constant; optimal K varies per engine (different +// eval scales) and is the first thing tuned. Standard formulation: +// P(white wins) = 1 / (1 + 10^(-K * eval_cp / 400)) + +static inline double sigmoid(double cp, double K) { + return 1.0 / (1.0 + std::pow(10.0, -K * cp / 400.0)); +} + +static double total_loss(double K) { + double sum = 0.0; + const int n = (int)dataset.size(); + for (int i = 0; i < n; i++) { + TuningPos &p = dataset[i]; + + // setar_posicao takes non-const char* (legacy C-style API). Cast + // away const for the call; the function doesn't actually mutate + // the input strings. + Update::setar_posicao(p.fen, p.lado, p.roques, p.ep, p.hm, p.fm); + + // Eval::avaliar returns the score from the side-to-move's perspective. + // Normalize to white's perspective for the sigmoid: a positive number + // always means "good for white". + int eval_cp = Eval::avaliar(); + if (Game::lado == PRETAS) eval_cp = -eval_cp; + + double pred = sigmoid((double)eval_cp, K); + double err = (double)p.result - pred; + sum += err * err; + } + return sum / (double)n; +} + +// --------------------------------------------------------------------------- +// K optimization via ternary search. +// The loss function is convex in K (single minimum), so ternary search +// converges to the optimum in O(log) iterations. ~30 iterations gives +// K precision around 1e-4 starting from [0.1, 3.0]. + +static double optimize_K() { + double low = 0.1, high = 3.0; + for (int iter = 0; iter < 30 && (high - low) > 0.0005; iter++) { + double m1 = low + (high - low) / 3.0; + double m2 = high - (high - low) / 3.0; + double l1 = total_loss(m1); + double l2 = total_loss(m2); + if (l1 < l2) high = m2; + else low = m1; + fprintf(stderr, "tuner: K-opt iter %2d: K in [%.4f, %.4f], loss=%.6f\n", + iter, low, high, std::min(l1, l2)); + } + return (low + high) / 2.0; +} + +// --------------------------------------------------------------------------- +// Entry point. Initialize engine, load dataset, find best K, report loss. +// Coordinate descent ships in a follow-up commit. + +int main(int argc, char **argv) { + if (argc < 2) { + fprintf(stderr, "Usage: %s \n", argv[0]); + fprintf(stderr, " Dataset format: one position per line, FEN fields + result.\n"); + fprintf(stderr, " Result token: 1.0 / 0.5 / 0.0 (or 1-0 / 1/2-1/2 / 0-1).\n"); + return 1; + } + + fprintf(stderr, "tuner: initializing engine...\n"); + Init::init(); + + fprintf(stderr, "tuner: loading dataset from %s ...\n", argv[1]); + load_dataset(argv[1]); + + fprintf(stderr, "tuner: optimizing K ...\n"); + double K = optimize_K(); + double loss = total_loss(K); + + fprintf(stderr, "tuner: optimal K = %.4f, initial loss = %.6f\n", K, loss); + fprintf(stderr, "tuner: skeleton ready. Coordinate descent ships in commit 2.\n"); + + return 0; +} From 42b949afda77bcef9c7fb45c7e40b07386b5fb37 Mon Sep 17 00:00:00 2001 From: Hugo Souza Date: Sun, 10 May 2026 19:25:19 -0300 Subject: [PATCH 09/12] ignore position files --- .gitignore | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 4f2f6e8..8cd0449 100644 --- a/.gitignore +++ b/.gitignore @@ -46,4 +46,6 @@ # Chess game files *.pgn config.json -UHO_Lichess_4852_v1.epd \ No newline at end of file +*.epd +*.book + From 617f2260b0521ddf94c52c5c8f042c03dae804cf Mon Sep 17 00:00:00 2001 From: Hugo Souza Date: Sun, 10 May 2026 19:28:19 -0300 Subject: [PATCH 10/12] finish tuning code --- src/tuner.cpp | 260 +++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 236 insertions(+), 24 deletions(-) diff --git a/src/tuner.cpp b/src/tuner.cpp index 6f9ca88..f6dd6e3 100644 --- a/src/tuner.cpp +++ b/src/tuner.cpp @@ -1,27 +1,34 @@ /* * Texel-style eval tuner for capizero. * - * Commit 1 (this file): scaffolding + FEN parser + dataset loader + loss - * function + K (sigmoid-scaling) optimization. Reports the initial loss for - * the engine's current eval parameters. Does NOT yet mutate parameters. + * Mutates `Eval::score_casas` (per-piece PSTs with material baked in) and + * `Eval::mobilidade_*` (per-piece-type mobility lookup tables) via coordinate + * descent. For each parameter, tries ±1 cp steps and accepts whichever + * reduces total dataset loss; iterates passes until a full pass yields no + * accepted change. * - * Commit 2 (follow-up): coordinate-descent tuning loop that walks every - * Eval::score_casas slot and Eval::mobilidade_* entry, accepting any - * ±1 step that reduces total dataset loss. Output formatting for new - * values.h content. + * Build with `make tuner`. Run as: `./capi_tuner [--max N] [--passes P]`. * - * Build with `make tuner`. Run as: `./capi_tuner `. + * Dataset format: one position per line, FEN fields + result token. Tokenizer + * treats whitespace, '|', '[', and ']' as separators so the common formats + * (Stockfish "FEN [result]", Ethereal "FEN result", plain "FEN | result") all + * parse. Result token: 1.0 / 0.5 / 0.0 (or 1-0 / 1/2-1/2 / 0-1). * - * Dataset format (one position per line; tokenizer splits on whitespace, - * '|', and brackets so common variants all work): + * CLI flags: + * --max N Cap dataset to N positions (random shuffle, then truncate). + * Default: 200000. Use 0 for unlimited. + * --passes P Stop after P full passes regardless of convergence. + * Default: 30. Use 0 for unlimited. * - * + * Output: prints a copy-pasteable values.h-style block to stdout once + * convergence is reached. The block has material baked into PSTs (so the user + * should set `VALOR_*_MG` / `VALOR_*_EG` to 0 in values.h when adopting, or + * the tuner can split material out — currently bakes for simplicity). * - * where is one of: 1.0 / 0.5 / 0.0 (or 1-0 / 1/2-1/2 / 0-1). - * - * Examples: - * rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1 [0.5] - * r1bqkb1r/pppp1ppp/2n2n2/4p3/2B1P3/5N2/PPPP1PPP/RNBQK2R w KQkq - 4 4 1.0 + * Performance note: on a 10M-position dataset, naive coord descent is ~100h + * to converge. Default `--max 200000` keeps a single pass at ~6 minutes on + * M3, ~20 passes ~= 2 hours. If convergence quality is insufficient, raise + * `--max` and rerun starting from the previous output as the new baseline. */ #include @@ -30,8 +37,10 @@ #include #include #include +#include #include #include +#include #include "consts.h" #include "init.h" @@ -200,29 +209,232 @@ static double optimize_K() { } // --------------------------------------------------------------------------- -// Entry point. Initialize engine, load dataset, find best K, report loss. -// Coordinate descent ships in a follow-up commit. +// Parameter registry. +// +// Each "parameter" is one int16-sized half (mg or eg) of a packed +// Eval::Score slot. We tune two classes of slots: +// +// 1. `Eval::score_casas[BRANCAS][p][x]` — white-side PST + material per +// piece-type and square. After each accepted tweak, the corresponding +// black-side slot `score_casas[PRETAS][p][Consts::flip[x]]` is updated +// to mirror, keeping the eval symmetric. Black is never tuned directly. +// +// 2. `Eval::mobilidade_*[i]` — phase-aware mobility lookup tables. These +// are side-independent (same value applies to both colors via popcount), +// so no mirroring needed. +// +// Total params: 6 piece types × 64 squares × 2 halves (mg/eg) + sum of +// (9 + 14 + 15 + 28) × 2 halves = 768 + 132 = 900. + +struct Param { + Eval::Score* slot; // pointer to a packed Score we tune + Eval::Score* mirror; // black-side mirror (NULL for non-PST slots) + bool is_mg; // tune the mg half or the eg half +}; + +static std::vector params; + +static void register_params() { + // PSTs: white side, with black mirror. + for (int p = P; p <= R; p++) { + for (int x = 0; x < CASAS_DO_TABULEIRO; x++) { + Eval::Score* white = &Eval::score_casas[BRANCAS][p][x]; + Eval::Score* black = &Eval::score_casas[PRETAS][p][Consts::flip[x]]; + params.push_back({white, black, true}); // mg + params.push_back({white, black, false}); // eg + } + } + // Mobility tables: side-independent, no mirror. + for (int i = 0; i < 9; i++){ params.push_back({&Eval::mobilidade_cavalo[i], NULL, true}); params.push_back({&Eval::mobilidade_cavalo[i], NULL, false}); } + for (int i = 0; i < 14; i++){ params.push_back({&Eval::mobilidade_bispo[i], NULL, true}); params.push_back({&Eval::mobilidade_bispo[i], NULL, false}); } + for (int i = 0; i < 15; i++){ params.push_back({&Eval::mobilidade_torre[i], NULL, true}); params.push_back({&Eval::mobilidade_torre[i], NULL, false}); } + for (int i = 0; i < 28; i++){ params.push_back({&Eval::mobilidade_dama[i], NULL, true}); params.push_back({&Eval::mobilidade_dama[i], NULL, false}); } + + fprintf(stderr, "tuner: registered %zu parameters (PSTs + mobility, mg+eg halves)\n", params.size()); +} + +// Apply delta (typically ±1) to one half of the parameter, mirroring to +// black if this is a PST slot. +static inline void tweak(const Param& p, int delta) { + int mg = Eval::mg_score(*p.slot); + int eg = Eval::eg_score(*p.slot); + if (p.is_mg) mg += delta; + else eg += delta; + *p.slot = Eval::make_score(mg, eg); + if (p.mirror) *p.mirror = *p.slot; +} + +// --------------------------------------------------------------------------- +// Coordinate descent. For each parameter, try +1 and -1. Take whichever +// improves loss. Iterate passes until no parameter improves in a full pass +// (or `--passes` cap reached). Shuffles parameter order each pass to avoid +// any pathological deterministic walk. + +static double coordinate_descent(double K, int max_passes) { + std::mt19937 rng(0xC0FFEE); + std::vector order(params.size()); + for (size_t i = 0; i < params.size(); i++) order[i] = i; + + double best_loss = total_loss(K); + fprintf(stderr, "tuner: starting loss = %.6f\n", best_loss); + + for (int pass = 0; max_passes == 0 || pass < max_passes; pass++) { + std::shuffle(order.begin(), order.end(), rng); + + int accepted = 0; + time_t pass_start = time(NULL); + + for (size_t idx = 0; idx < order.size(); idx++) { + Param& p = params[order[idx]]; + + // Try +1 + tweak(p, +1); + double loss_plus = total_loss(K); + + if (loss_plus < best_loss) { + best_loss = loss_plus; + accepted++; + continue; + } + + // Revert and try -1 + tweak(p, -1); // undo +1 → back to original + tweak(p, -1); // step to -1 + double loss_minus = total_loss(K); + + if (loss_minus < best_loss) { + best_loss = loss_minus; + accepted++; + continue; + } + + // Neither direction helped, revert to original. + tweak(p, +1); + } + + time_t elapsed = time(NULL) - pass_start; + fprintf(stderr, "tuner: pass %d done, accepted %d / %zu, loss=%.6f, elapsed=%lds\n", + pass + 1, accepted, params.size(), best_loss, (long)elapsed); + + if (accepted == 0) { + fprintf(stderr, "tuner: converged after pass %d (no improvements)\n", pass + 1); + break; + } + } + + return best_loss; +} + +// --------------------------------------------------------------------------- +// Output formatter. Prints a copy-pasteable values.h-style block of the +// tuned PSTs and mobility tables. PSTs are emitted with material baked in +// (the tuned `Eval::score_casas[BRANCAS][p][x]` halves include the original +// VALOR_*_MG / VALOR_*_EG contribution). To adopt these in values.h, set the +// material defines to 0 before regenerating from the new PSTs, or split the +// material out manually (mean of each piece's mg/eg array works as the new +// material value, residual goes back into the PST). + +static void print_pst_array(const char* name, int piece, bool is_mg) { + fprintf(stdout, "\tconst int %s[64] = {\n", name); + for (int rank = 0; rank < 8; rank++) { + fprintf(stdout, "\t\t"); + for (int file = 0; file < 8; file++) { + int x = rank * 8 + file; + int v = is_mg ? Eval::mg_score(Eval::score_casas[BRANCAS][piece][x]) + : Eval::eg_score(Eval::score_casas[BRANCAS][piece][x]); + fprintf(stdout, "%5d%s", v, (file == 7 && rank == 7) ? "\n" : ","); + if (file == 7 && rank != 7) fprintf(stdout, "\n"); + } + } + fprintf(stdout, "\t};\n\n"); +} + +static void print_mobility_array(const char* name, const Eval::Score* tbl, int len, bool is_mg) { + fprintf(stdout, "\tconst int %s[%d] = {\n\t\t", name, len); + for (int i = 0; i < len; i++) { + int v = is_mg ? Eval::mg_score(tbl[i]) : Eval::eg_score(tbl[i]); + fprintf(stdout, "%5d%s", v, (i == len - 1) ? "\n" : ","); + if ((i + 1) % 8 == 0 && i != len - 1) fprintf(stdout, "\n\t\t"); + } + fprintf(stdout, "\t};\n\n"); +} + +static void print_tuned_values() { + fprintf(stdout, "// ==== TUNED VALUES (paste into values.h, set VALOR_*_MG/_EG to 0) ====\n"); + fprintf(stdout, "// PSTs below have material baked in.\n\n"); + + print_pst_array("peao_score_mg", P, true); + print_pst_array("peao_score_eg", P, false); + print_pst_array("cavalo_score_mg", C, true); + print_pst_array("cavalo_score_eg", C, false); + print_pst_array("bispo_score_mg", B, true); + print_pst_array("bispo_score_eg", B, false); + print_pst_array("torre_score_mg", T, true); + print_pst_array("torre_score_eg", T, false); + print_pst_array("dama_score_mg", D, true); + print_pst_array("dama_score_eg", D, false); + print_pst_array("rei_score_mg", R, true); + print_pst_array("rei_score_eg", R, false); + + print_mobility_array("mobilidade_cavalo_mg", Eval::mobilidade_cavalo, 9, true); + print_mobility_array("mobilidade_cavalo_eg", Eval::mobilidade_cavalo, 9, false); + print_mobility_array("mobilidade_bispo_mg", Eval::mobilidade_bispo, 14, true); + print_mobility_array("mobilidade_bispo_eg", Eval::mobilidade_bispo, 14, false); + print_mobility_array("mobilidade_torre_mg", Eval::mobilidade_torre, 15, true); + print_mobility_array("mobilidade_torre_eg", Eval::mobilidade_torre, 15, false); + print_mobility_array("mobilidade_dama_mg", Eval::mobilidade_dama, 28, true); + print_mobility_array("mobilidade_dama_eg", Eval::mobilidade_dama, 28, false); + + fprintf(stdout, "// ==== end tuned values ====\n"); +} + +// --------------------------------------------------------------------------- +// Entry point. int main(int argc, char **argv) { if (argc < 2) { - fprintf(stderr, "Usage: %s \n", argv[0]); - fprintf(stderr, " Dataset format: one position per line, FEN fields + result.\n"); - fprintf(stderr, " Result token: 1.0 / 0.5 / 0.0 (or 1-0 / 1/2-1/2 / 0-1).\n"); + fprintf(stderr, "Usage: %s [--max N] [--passes P]\n", argv[0]); + fprintf(stderr, " --max N Cap dataset to N positions (random shuffle then truncate). Default: 200000. 0 = unlimited.\n"); + fprintf(stderr, " --passes P Stop after P coord-descent passes. Default: 30. 0 = unlimited.\n"); return 1; } + int max_positions = 200000; + int max_passes = 30; + + for (int i = 2; i < argc; i++) { + if (!strcmp(argv[i], "--max") && i + 1 < argc) { + max_positions = atoi(argv[++i]); + } else if (!strcmp(argv[i], "--passes") && i + 1 < argc) { + max_passes = atoi(argv[++i]); + } + } + fprintf(stderr, "tuner: initializing engine...\n"); Init::init(); fprintf(stderr, "tuner: loading dataset from %s ...\n", argv[1]); load_dataset(argv[1]); + // Random subsample to keep coord-descent passes fast on huge datasets. + if (max_positions > 0 && (int)dataset.size() > max_positions) { + std::mt19937 rng(0xDA7A); // deterministic shuffle for reproducible runs + std::shuffle(dataset.begin(), dataset.end(), rng); + dataset.resize(max_positions); + fprintf(stderr, "tuner: subsampled to %d positions\n", max_positions); + } + fprintf(stderr, "tuner: optimizing K ...\n"); double K = optimize_K(); - double loss = total_loss(K); + fprintf(stderr, "tuner: optimal K = %.4f\n", K); + + register_params(); + + fprintf(stderr, "tuner: starting coordinate descent (max %d passes)...\n", max_passes); + double final_loss = coordinate_descent(K, max_passes); + fprintf(stderr, "tuner: final loss = %.6f\n", final_loss); - fprintf(stderr, "tuner: optimal K = %.4f, initial loss = %.6f\n", K, loss); - fprintf(stderr, "tuner: skeleton ready. Coordinate descent ships in commit 2.\n"); + print_tuned_values(); return 0; } From 9111042b8f505239b7679e4b1a547b395661d52b Mon Sep 17 00:00:00 2001 From: Hugo Souza Date: Sun, 10 May 2026 20:49:42 -0300 Subject: [PATCH 11/12] Update README.md --- README.md | 61 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/README.md b/README.md index 51487bb..14213c5 100644 --- a/README.md +++ b/README.md @@ -101,6 +101,67 @@ depois de compilar o binário `capizero_` fica na raiz do repositório. # imprime Nodes: N / NPS: M no final ``` +## Para tunar os pesos da avaliação + +o tuner é um binário separado que ajusta os pesos das tabelas de pesos das peças (PSTs) e das tabelas de mobilidade através de [Texel tuning](https://www.chessprogramming.org/Texel%27s_Tuning_Method) — descida coordenada com perda MSE entre o sigmoid da avaliação estática e o resultado real das partidas. + +### Compilar o tuner + +``` +make tuner +``` + +gera o binário `capi_tuner` na raiz do repositório. + +### Dataset + +o tuner espera um arquivo texto com uma posição por linha no formato: + +``` + +``` + +onde `` são os 6 campos padrão FEN e `` é `1.0` / `0.5` / `0.0` (ou `1-0` / `1/2-1/2` / `0-1`). variações com colchetes (`[0.5]`) ou pipe (`| 0.5`) também são aceitas. exemplos compatíveis: + +``` +rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1 [0.5] +r1bqkb1r/pppp1ppp/2n2n2/4p3/2B1P3/5N2/PPPP1PPP/RNBQK2R w KQkq - 4 4 1.0 +``` + +datasets recomendados: +* [Andrew Grant's Ethereal tuning data](https://github.com/AndyGrant/EtherealDev) — ~10M posições D12-resolved (formato compatível direto) +* posições geradas por self-play da própria capizero (mais representativas, mas mais lentas de gerar) + +### Executar o tuner + +``` +./capi_tuner [--max N] [--passes P] +``` + +* `--max N` — limita o dataset a N posições amostradas aleatoriamente (padrão: 200000; use `0` para o conjunto completo). +* `--passes P` — interrompe após P passes mesmo sem convergência (padrão: 30; use `0` para ilimitado). + +logs vão para `stderr`; o output formatado dos valores tunados vai para `stdout`. exemplo de uso típico (run rápido para iteração): + +``` +./capi_tuner ./E12.33-1M-D12-Resolved.book --max 200000 --passes 30 > tuned_values.h.txt 2> tuner.log +``` + +run completo sobre o dataset inteiro + +``` +./capi_tuner ./E12.33-1M-D12-Resolved.book --max 0 --passes 1 > tuned_values.h.txt 2> tuner.log +``` + +### Aplicar os valores tunados + +1. abra `tuned_values.h.txt` — contém 12 arrays de PST (6 peças × mg/eg) e 8 arrays de mobilidade. +2. substitua os arrays correspondentes em [src/values.h](src/values.h). +3. as PSTs tunadas já têm material embutido (PeSTO `VALOR_*_MG` / `VALOR_*_EG` baked in). para evitar somar material duas vezes, zere os defines `VALOR_*_MG` / `VALOR_*_EG` em [src/values.h](src/values.h) (ou remova a soma em `Eval::init_eval_tables`). +4. recompile (`make build`) e rode `./capizero_ bench` para sanity check. +5. valide a mudança via SPRT — veja a seção "Testes de força" abaixo. + + ## Testes de força duas ferramentas estão disponíveis em [tests/](tests/) para medir a força da engine: From 2bd1a8ac4dc61c00011dbede3e44bf508574dde8 Mon Sep 17 00:00:00 2001 From: Hugo Souza Date: Sat, 16 May 2026 02:36:35 -0300 Subject: [PATCH 12/12] raise aspiration windows retries --- src/search.cpp | 37 ++++++++++++++++++++++++------------- src/values.h | 18 +----------------- 2 files changed, 25 insertions(+), 30 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index ddc23ef..3610176 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -480,29 +480,40 @@ void Search::pensar(bool verbose){ } } + int delta = TAMANHO_JANELA_DE_PESQUISA; if (profundidade == 1){ alpha = ALPHA_INICIAL; - beta = BETA_INICIAL; + beta = BETA_INICIAL; } else{ - alpha = melhor_linha - TAMANHO_JANELA_DE_PESQUISA; - beta = melhor_linha + TAMANHO_JANELA_DE_PESQUISA; + alpha = melhor_linha - delta; + beta = melhor_linha + delta; } - melhor_linha = pesquisa(alpha, beta, profundidade, true); - - if (melhor_linha <= alpha){ - alpha = (melhor_linha - (TAMANHO_JANELA_DE_PESQUISA * READAPTACAO_JANELA_DE_PESQUISA)); + int fails = 0; + while (true){ melhor_linha = pesquisa(alpha, beta, profundidade, true); - } - else if (melhor_linha >= beta){ - if (profundidade == 1){ - beta = BETA_INICIAL; + + if (melhor_linha > alpha && melhor_linha < beta) break; + if (++fails > MAX_ASPIRATION_FAILS) break; + + if (fails == MAX_ASPIRATION_FAILS){ + // Final attempt — full window, guaranteed to return a usable + // score on the next iteration. + alpha = ALPHA_INICIAL; + beta = BETA_INICIAL; + continue; + } + + delta *= 2; + if (melhor_linha <= alpha){ + alpha = melhor_linha - delta; + if (alpha < ALPHA_INICIAL) alpha = ALPHA_INICIAL; } else{ - beta = (melhor_linha + (TAMANHO_JANELA_DE_PESQUISA * READAPTACAO_JANELA_DE_PESQUISA)); + beta = melhor_linha + delta; + if (beta > BETA_INICIAL) beta = BETA_INICIAL; } - melhor_linha = pesquisa(alpha, beta, profundidade, true); } if (Hash::hash_lookup(Game::lado)){ diff --git a/src/values.h b/src/values.h index 86fae56..33d3b1f 100644 --- a/src/values.h +++ b/src/values.h @@ -52,13 +52,6 @@ namespace Values{ #define PHASE_REI 0 #define PHASE_MAX 24 - // Tapered mobility tables, indexed by attack-count (popcount of the - // piece's attack bitboard with own pieces masked out). Stored here as - // parallel mg/eg int arrays — `Eval::init_eval_tables` packs them into - // `Eval::mobilidade_*` Score tables at startup. Curve shape: brutal - // negative at trapped (low index), near-linear in the middle, saturating - // at the top. First-pass literature values; will be Texel-tuned later. - // Knight max attack-count = 8, bishop = 13, rook = 14, queen = 27. const int mobilidade_cavalo_mg[9] = { -40, -15, -3, 0, 3, 6, 9, 11, 13 }; @@ -91,9 +84,8 @@ namespace Values{ #define REDUCAO_IID /4 #define PROFUNDIDADE_CONDICAO_IID 5 - // WINDOWS #define TAMANHO_JANELA_DE_PESQUISA 20 - #define READAPTACAO_JANELA_DE_PESQUISA 3 + #define MAX_ASPIRATION_FAILS 3 // VALORES PARA ORDENAÇÃO DE LANCES #define SCORE_ROQUE 5000000 @@ -104,8 +96,6 @@ namespace Values{ #define SCORE_CAPTURAS_V 50000000 #define PONTUACAO_HASH 100000000 - // Promotion move scoring. Queen above MVV/LVA so it is always tried first, - // knight moderate (tactical value). R/B are not generated. #define SCORE_PROMO_Q_CAP (SCORE_CAPTURAS_V + 1000000) #define SCORE_PROMO_Q 60000000 #define SCORE_PROMO_N_CAP 18000000 @@ -113,12 +103,6 @@ namespace Values{ #define REDUCAO_LMR 3 - // Null-move pruning depth reduction. Dynamic R: heavier pruning at deep - // depths where the search tree is huge and the marginal cost of missing - // tactics is offset by the breadth gain; lighter pruning near the leaves - // where the reduced-depth subsearch needs to retain enough resolution - // to be informative. With R_NULL_HIGH = 3 and the threshold at 6, a - // depth-6 null-move recurses at depth 2 (still beats qsearch by 2 plies). #define R_NULL_LOW 2 #define R_NULL_HIGH 3 #define R_NULL_DEPTH_THRESH 6