diff options
Diffstat (limited to 'src/searchsup.c')
| -rw-r--r-- | src/searchsup.c | 83 |
1 files changed, 57 insertions, 26 deletions
diff --git a/src/searchsup.c b/src/searchsup.c index 8bc74ef..6f0d5f9 100644 --- a/src/searchsup.c +++ b/src/searchsup.c @@ -611,11 +611,13 @@ Return value: **/ { - ULONG x; + ULONG x, uBegin, uEnd; MOVE mv; SCORE iScore; SCORE iBestScore = -INFINITY; ULONG uBest; + FLAG fFailHigh = FALSE; + SCORE rgScores[MAX_MOVES_PER_PLY]; // // Preconditions @@ -643,11 +645,20 @@ Return value: ASSERT(uDepth < MAX_DEPTH_PER_SEARCH); // - // Search the moves and remember the scores + // Search the moves and remember the scores in a scratch array, NOT + // directly into mvf[].iValue -- we don't yet know if we'll get an + // honest score for every move at this ply (a fail-high partway + // through means we stop early, deliberately, without searching the + // rest). Committing partial data into iValue and then patching the + // gaps with a sentinel (this function used to write -INFINITY into + // every unsearched move) makes the whole ply look "rescored" to + // downstream code when it isn't really -- see the fail-high handling + // below for why that matters. // - for (x = uBest = ctx->sMoveStack.uBegin[ctx->uPly]; - x < ctx->sMoveStack.uEnd[ctx->uPly]; - x++) + uBegin = ctx->sMoveStack.uBegin[ctx->uPly]; + uEnd = ctx->sMoveStack.uEnd[ctx->uPly]; + ASSERT((uEnd - uBegin) <= MAX_MOVES_PER_PLY); + for (x = uBest = uBegin; x < uEnd; x++) { SelectBestNoHistory(ctx, x); mv = ctx->sMoveStack.mvf[x].mv; @@ -667,40 +678,60 @@ Return value: } } UnmakeMove(ctx, mv); - ctx->sMoveStack.mvf[x].iValue = iScore; + rgScores[x - uBegin] = iScore; if (iScore > iBestScore) { uBest = x; iBestScore = iScore; if (iScore >= iBeta) { - goto end; + fFailHigh = TRUE; + break; } } } + else + { + // Genuinely illegal (pseudo-legal but leaves king in check) + // -- this isn't "unknown," we know for a fact it's worthless + // and the caller will skip it too when MakeMove fails again. + rgScores[x - uBegin] = -INFINITY; + } } - end: - // - // Clear the values from the current move to the end of the - // moves in the list so that if we failed high we don't have - // stale move values in the list for moves we did not search - // the subtrees for. - // - while(x < ctx->sMoveStack.uEnd[ctx->uPly]) + if (FALSE == fFailHigh) + { + // We have an honest, fully-searched score for every move at + // this ply -- commit them all and let downstream code + // (ComputeMoveScore, the main search loop's move-selection + // call) trust iValue as a real eval-axis score. + for (x = uBegin; x < uEnd; x++) + { + ctx->sMoveStack.mvf[x].iValue = rgScores[x - uBegin]; + } + ctx->sPlyInfo[ctx->uPly].fMovesRescoredByIID = TRUE; + } + else { - ctx->sMoveStack.mvf[x].iValue = -INFINITY; - x++; + // We only know one honest thing: uBest is good enough to have + // failed high at reduced depth. We do NOT have honest scores + // for the rest (deliberately -- searching them would burn nodes + // solely to confirm what we've already decided to do, which is + // play uBest). Rather than inventing eval-axis values for moves + // we never examined (the old code wrote -INFINITY into all of + // them, including -- until fixed -- overwriting uBest's own + // just-computed score), leave every move's original generate.c + // ordering value completely untouched and just bump uBest into + // killer-tier territory so the normal (non-rescored) selection + // path -- SelectBestWithHistory/NoHistory -- picks it first, the + // same trick generate.c uses for a real killer move. Do NOT set + // fMovesRescoredByIID: this ply's iValue is back to being + // generate.c's ordering encoding, not real scores, for every + // move including uBest (whose bump is itself just an ordering + // flag, not part of its own eval-axis value). + ctx->sMoveStack.mvf[uBest].iValue |= FIRST_KILLER; } - // uBest already holds the largest real score in the list (iBestScore - // tracked the running max as we went) -- SelectBest{With,No}History - // just compare raw magnitude, so it naturally sorts first without - // needing a flag. OR-ing in SORT_THESE_FIRST here used to corrupt - // that real score into looking like generate.c's biased-capture- - // ordering format to any later ComputeMoveScore() caller; removed. - // fMovesRescoredByIID (below) is the correct, non-destructive way to - // signal "trust this ply's iValue as a real eval-axis score." - ctx->sPlyInfo[ctx->uPly].fMovesRescoredByIID = TRUE; + ASSERT(IS_VALID_SCORE(iBestScore)); return(iBestScore); } |
