Merge commit '74a8fc060465a822f0c047f908d5fb07ebc6ad96' into cluster

This is the last commit before there are more conflicts.
This commit is contained in:
Steinar H. Gunderson
2025-12-25 15:16:24 +01:00
20 changed files with 224 additions and 66 deletions
+68 -32
View File
@@ -63,7 +63,7 @@ static constexpr double EvalLevel[10] = {0.981, 0.956, 0.895, 0.949, 0.913,
Value futility_margin(Depth d, bool noTtCutNode, bool improving, bool oppWorsening) {
Value futilityMult = 109 - 40 * noTtCutNode;
Value improvingDeduction = 59 * improving * futilityMult / 32;
Value worseningDeduction = 328 * oppWorsening * futilityMult / 1024;
Value worseningDeduction = oppWorsening * futilityMult / 3;
return futilityMult * d - improvingDeduction - worseningDeduction;
}
@@ -550,7 +550,7 @@ void Search::Worker::iterative_deepening() {
void Search::Worker::clear() {
counterMoves.fill(Move::none());
mainHistory.fill(0);
captureHistory.fill(0);
captureHistory.fill(-700);
pawnHistory.fill(-1193);
correctionHistory.fill(0);
@@ -582,9 +582,8 @@ Value Search::Worker::search(
// Limit the depth if extensions made it too large
depth = std::min(depth, MAX_PLY - 1);
// Check if we have an upcoming move that draws by repetition, or
// if the opponent had an alternative move earlier to this position.
if (!rootNode && alpha < VALUE_DRAW && pos.has_game_cycle(ss->ply))
// Check if we have an upcoming move that draws by repetition.
if (!rootNode && alpha < VALUE_DRAW && pos.upcoming_repetition(ss->ply))
{
alpha = value_draw(this->nodes);
if (alpha >= beta)
@@ -800,8 +799,7 @@ Value Search::Worker::search(
// Use static evaluation difference to improve quiet move ordering (~9 Elo)
if (((ss - 1)->currentMove).is_ok() && !(ss - 1)->inCheck && !priorCapture)
{
int bonus = std::clamp(-10 * int((ss - 1)->staticEval + ss->staticEval), -1590, 1371);
bonus = bonus > 0 ? 2 * bonus : bonus / 2;
int bonus = std::clamp(-10 * int((ss - 1)->staticEval + ss->staticEval), -1590, 1371) + 800;
thisThread->mainHistory[~us][((ss - 1)->currentMove).from_to()] << bonus;
if (type_of(pos.piece_on(prevSq)) != PAWN && ((ss - 1)->currentMove).type_of() != PROMOTION)
thisThread->pawnHistory[pawn_structure_index(pos)][pos.piece_on(prevSq)][prevSq]
@@ -835,8 +833,9 @@ Value Search::Worker::search(
&& eval - futility_margin(depth, cutNode && !ss->ttHit, improving, opponentWorsening)
- (ss - 1)->statScore / 263
>= beta
&& eval >= beta && eval < VALUE_TB_WIN_IN_MAX_PLY && (!ttData.move || ttCapture))
return beta > VALUE_TB_LOSS_IN_MAX_PLY ? beta + (eval - beta) / 3 : eval;
&& eval >= beta && (!ttData.move || ttCapture) && beta > VALUE_TB_LOSS_IN_MAX_PLY
&& eval < VALUE_TB_WIN_IN_MAX_PLY)
return beta + (eval - beta) / 3;
// Step 9. Null move search with verification search (~35 Elo)
if (!PvNode && (ss - 1)->currentMove != Move::null() && (ss - 1)->statScore < 14369
@@ -880,9 +879,12 @@ Value Search::Worker::search(
}
// Step 10. Internal iterative reductions (~9 Elo)
// For PV nodes without a ttMove, we decrease depth by 3.
// For PV nodes without a ttMove, we decrease depth.
// Additionally, if the current position is found in the TT
// and the stored depth in the TT is greater than or equal to
// current search depth, we decrease search depth even further.
if (PvNode && !ttData.move)
depth -= 3;
depth -= 3 + (ss->ttHit && ttData.depth >= depth);
// Use qsearch if depth <= 0.
if (depth <= 0)
@@ -909,12 +911,19 @@ Value Search::Worker::search(
assert(probCutBeta < VALUE_INFINITE && probCutBeta > beta);
MovePicker mp(pos, ttData.move, probCutBeta - ss->staticEval, &thisThread->captureHistory);
Move probcutCapturesSearched[32];
int probcutCaptureCount = 0;
Piece captured;
while ((move = mp.next_move()) != Move::none())
if (move != excludedMove && pos.legal(move))
{
assert(pos.capture_stage(move));
movedPiece = pos.moved_piece(move);
captured = pos.piece_on(move.to_sq());
// Prefetch the TT entry for the resulting position
prefetch(tt.first_entry(pos.key_after(move)));
@@ -938,6 +947,19 @@ Value Search::Worker::search(
if (value >= probCutBeta)
{
thisThread->captureHistory[movedPiece][move.to_sq()][type_of(captured)]
<< stat_bonus(depth - 2);
for (int i = 0; i < probcutCaptureCount; i++)
{
movedPiece = pos.moved_piece(probcutCapturesSearched[i]);
captured = pos.piece_on(probcutCapturesSearched[i].to_sq());
thisThread->captureHistory[movedPiece][probcutCapturesSearched[i].to_sq()]
[type_of(captured)]
<< -stat_malus(depth - 3);
}
// Save ProbCut data into transposition table
Distributed::save(tt, threads, thisThread, ttWriter, posKey,
value_to_tt(value, ss->ply), ss->ttPv, BOUND_LOWER, depth - 3,
@@ -945,6 +967,9 @@ Value Search::Worker::search(
return std::abs(value) < VALUE_TB_WIN_IN_MAX_PLY ? value - (probCutBeta - beta)
: value;
}
if (probcutCaptureCount < 32)
probcutCapturesSearched[probcutCaptureCount++] = move;
}
Eval::NNUE::hint_common_parent_position(pos, networks[numaAccessToken], refreshTable);
@@ -954,9 +979,8 @@ moves_loop: // When in check, search starts here
// Step 12. A small Probcut idea, when we are in check (~4 Elo)
probCutBeta = beta + 388;
if (ss->inCheck && !PvNode && ttCapture && (ttData.bound & BOUND_LOWER)
&& ttData.depth >= depth - 4 && ttData.value >= probCutBeta
&& std::abs(ttData.value) < VALUE_TB_WIN_IN_MAX_PLY
if (ss->inCheck && (ttData.bound & BOUND_LOWER) && ttData.depth >= depth - 4
&& ttData.value >= probCutBeta && std::abs(ttData.value) < VALUE_TB_WIN_IN_MAX_PLY
&& std::abs(beta) < VALUE_TB_WIN_IN_MAX_PLY)
return probCutBeta;
@@ -1042,7 +1066,7 @@ moves_loop: // When in check, search starts here
// Futility pruning for captures (~2 Elo)
if (!givesCheck && lmrDepth < 7 && !ss->inCheck)
{
Value futilityValue = ss->staticEval + 287 + 248 * lmrDepth
Value futilityValue = ss->staticEval + 294 + 246 * lmrDepth
+ PieceValue[capturedPiece] + captHist / 7;
if (futilityValue <= alpha)
continue;
@@ -1050,7 +1074,7 @@ moves_loop: // When in check, search starts here
// SEE based pruning for captures and checks (~11 Elo)
int seeHist = std::clamp(captHist / 32, -180 * depth, 163 * depth);
if (!pos.see_ge(move, -160 * depth - seeHist))
if (!pos.see_ge(move, -163 * depth - seeHist))
continue;
}
else
@@ -1061,15 +1085,15 @@ moves_loop: // When in check, search starts here
+ thisThread->pawnHistory[pawn_structure_index(pos)][movedPiece][move.to_sq()];
// Continuation history based pruning (~2 Elo)
if (lmrDepth < 6 && history < -4151 * depth)
if (lmrDepth < 6 && history < -3899 * depth)
continue;
history += 2 * thisThread->mainHistory[us][move.from_to()];
lmrDepth += history / 3678;
lmrDepth += history / 4040;
Value futilityValue =
ss->staticEval + (bestValue < ss->staticEval - 51 ? 138 : 54) + 140 * lmrDepth;
ss->staticEval + (bestValue < ss->staticEval - 51 ? 135 : 56) + 140 * lmrDepth;
// Futility pruning: parent node (~13 Elo)
if (!ss->inCheck && lmrDepth < 12 && futilityValue <= alpha)
@@ -1135,8 +1159,8 @@ moves_loop: // When in check, search starts here
// and if after excluding the ttMove with a reduced search we fail high over the original beta,
// we assume this expected cut-node is not singular (multiple moves fail high),
// and we can prune the whole subtree by returning a softbound.
else if (singularBeta >= beta)
return singularBeta;
else if (value >= beta && std::abs(value) < VALUE_TB_WIN_IN_MAX_PLY)
return value;
// Negative extensions
// If other moves failed high over (ttValue - margin) without the ttMove on a reduced search,
@@ -1185,7 +1209,8 @@ moves_loop: // When in check, search starts here
// Decrease reduction if position is or has been on the PV (~7 Elo)
if (ss->ttPv)
r -= 1 + (ttData.value > alpha) + (ttData.depth >= depth);
r -= 1 + (ttData.value > alpha) + (ttData.depth >= depth)
- (PvNode && ttData.value < alpha && ttData.depth >= depth);
// Decrease reduction for PvNodes (~0 Elo on STC, ~2 Elo on LTC)
if (PvNode)
@@ -1204,7 +1229,7 @@ moves_loop: // When in check, search starts here
// Increase reduction if next ply has a lot of fail high (~5 Elo)
if ((ss + 1)->cutoffCnt > 3)
r++;
r += 1 + !(PvNode || cutNode);
// For first picked move (ttMove) reduce reduction
// but never allow it to go below 0 (~3 Elo)
@@ -1293,7 +1318,7 @@ moves_loop: // When in check, search starts here
rm.effort += nodes - nodeCount;
rm.averageScore =
rm.averageScore != -VALUE_INFINITE ? (2 * value + rm.averageScore) / 3 : value;
rm.averageScore != -VALUE_INFINITE ? (value + rm.averageScore) / 2 : value;
// PV move or new best move?
if (moveCount == 1 || value > alpha)
@@ -1333,11 +1358,17 @@ moves_loop: // When in check, search starts here
rm.score = -VALUE_INFINITE;
}
if (value > bestValue)
// In case we have an alternative move equal in eval to the current bestmove,
// promote it to bestmove by pretending it just exceeds alpha (but not beta).
int inc = (value == bestValue && (int(nodes) & 15) == 0
&& ss->ply + 2 + ss->ply / 32 >= thisThread->rootDepth
&& std::abs(value) + 1 < VALUE_TB_WIN_IN_MAX_PLY);
if (value + inc > bestValue)
{
bestValue = value;
if (value > alpha)
if (value + inc > alpha)
{
bestMove = move;
@@ -1396,10 +1427,16 @@ moves_loop: // When in check, search starts here
// Bonus for prior countermove that caused the fail low
else if (!priorCapture && prevSq != SQ_NONE)
{
int bonus = (113 * (depth > 5) + 118 * (PvNode || cutNode)
+ 191 * ((ss - 1)->statScore < -14396) + 119 * ((ss - 1)->moveCount > 8)
int bonus = (113 * (depth > 5) + 118 * (PvNode || cutNode) + 119 * ((ss - 1)->moveCount > 8)
+ 64 * (!ss->inCheck && bestValue <= ss->staticEval - 107)
+ 147 * (!(ss - 1)->inCheck && bestValue <= -(ss - 1)->staticEval - 75));
// proportional to "how much damage we have to undo"
if ((ss - 1)->statScore < -8000)
bonus += std::clamp(-(ss - 1)->statScore / 100, 0, 250);
update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq,
stat_bonus(depth) * bonus / 100);
thisThread->mainHistory[~us][((ss - 1)->currentMove).from_to()]
@@ -1461,9 +1498,8 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta,
assert(PvNode || (alpha == beta - 1));
assert(depth <= 0);
// Check if we have an upcoming move that draws by repetition, or if
// the opponent had an alternative move earlier to this position. (~1 Elo)
if (alpha < VALUE_DRAW && pos.has_game_cycle(ss->ply))
// Check if we have an upcoming move that draws by repetition. (~1 Elo)
if (alpha < VALUE_DRAW && pos.upcoming_repetition(ss->ply))
{
alpha = value_draw(this->nodes);
if (alpha >= beta)
@@ -1633,7 +1669,7 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta,
// If static exchange evaluation is much worse than what is needed to not
// fall below alpha we can prune this move.
if (futilityBase > alpha && !pos.see_ge(move, (alpha - futilityBase) * 2 - 30))
if (futilityBase > alpha && !pos.see_ge(move, (alpha - futilityBase) * 4))
{
bestValue = alpha;
continue;