diff options
Diffstat (limited to 'src/generate.c')
| -rwxr-xr-x | src/generate.c | 113 |
1 files changed, 110 insertions, 3 deletions
diff --git a/src/generate.c b/src/generate.c index 3e3c3a5..765c082 100755 --- a/src/generate.c +++ b/src/generate.c @@ -2433,6 +2433,56 @@ Routine description: We have just generated all the moves, now score them. See comments inline below about order. + Full move-ordering hierarchy for a normal (not-in-check) node, from + highest-scored to lowest -- "vNext", the dynamic-move-ordering + overhaul from the 2026-08-29 session (see CLAUDE.md's "Dynamic move + ordering experiments" section for the methodology behind it): + + 1. Hash move -- already handled by Search(), never (re)generated + here; excluded from this list entirely. + 2. Killer that also threatens/delivers mate -- FIRST_KILLER (or + whichever killer slot matched) OR'd with SORT_THESE_FIRST. + 3. Winning/even captures & promotions -- SORT_THESE_FIRST, MVV/LVA + tiebreak; strictly-winning-by-raw-material trades skip SEE, an + even or ambiguous trade (or any promotion) gets the full + exchange walk. + 4. Killers, this ply's slot 0/1, then ply-2's slot 0/1 (Crafty-style + ordering, this ply's own pair before either ply-2-back one) -- + FIRST_KILLER/SECOND_KILLER/THIRD_KILLER/FOURTH_KILLER. + 5. A countermove-table match (exact move, keyed by whatever the + opponent just played) *whose own accumulated history+ + continuation-history evidence clears COUNTERMOVE_EVIDENCE_ + THRESHOLD* -- COUNTERMOVE_TIER_BONUS, placed below both ply-2 + killer slots (conservative default; never validated as + deserving to outrank them). An *unevidenced* match gets no + special treatment at all and falls through to tier 6 -- see + chess.h's COUNTERMOVE_TIER_BONUS/COUNTERMOVE_EVIDENCE_THRESHOLD + comments for why the promotion is evidence-gated rather than + automatic on a bare match. + 6. GOOD_MOVE boundary. Below this, "leftover" (see search.c's + fIsLeftoverMove) -- eligible for EFP pruning, but every leftover + is still fully ranked via SelectBestWithHistory (movesup.c); the + old NumLeftoverMovesToSelect depth-indexed selection budget was + retired once evidence showed the leftover pool has real, + findable signal a bailout was discarding. An ordinary leftover + quiet move's score is: + PSQT (g_iPSQT[piece][to], 0..1000) + + g_HistoryCounters[piece][to] (classic butterfly history, + (depth+1)^2 growth on fail-high, decays on a miss or global + aging pass) + + CONTINUATION_SCALE * g_ContinuationHistory[(prev move, this + move)] (same growth/decay math as history, additionally + keyed by the previous move -- self-calibrating, no magic + constant needed; CONTINUATION_SCALE=1 found flat/sufficient + across a 256x sweep) + + FLEE_BONUS if this move's origin square is currently en + prise (flat same-tier nudge; the old unconditional GOOD_ + MOVE-tier promotion for these moves was retired after + evidence-calibration showed most such moves, unbacked by + real track record, perform identically to an ordinary + leftover -- see the RETIRED comment below). + 7. Losing captures -- negative, SEE-verified. + Parameters: IN MOVE_STACK *pStack, @@ -2454,7 +2504,9 @@ Return value: ULONG uHashMoveLoc = (ULONG)-1; ULONG uColor = pos->uToMove; PRECOMP_KILLERS sKillers[4]; - COOR cEnprise = FindEnprisePiece(ctx, uColor); + ULONG uCounterMoveIdx = 0; + ULONG uCounterMoveContKey = 0; + FLAG fHaveCounterMove = FALSE; // // We have generated all moves here. We also know that we are not @@ -2506,6 +2558,18 @@ Return value: sKillers[3].uBonus |= (SORT_THESE_FIRST * (IS_KILLERMATE_MOVE(sKillers[3].mv) != 0)); + // EXPERIMENT: countermove-match tier. Precompute the previous + // move's countermove-table index once per node (mirrors killer + // precompute above) so the per-move loop below is just an + // IS_SAME_MOVE check, not a recompute. + fHaveCounterMove = FALSE; + if ((uPly > 0) && (0 != (ctx->sPlyInfo[uPly - 1]).mv.uMove)) + { + uCounterMoveIdx = MOVE_TO_INDEX((ctx->sPlyInfo[uPly - 1]).mv); + uCounterMoveContKey = MOVE_TO_CONT_KEY((ctx->sPlyInfo[uPly - 1]).mv); + fHaveCounterMove = TRUE; + } + // // Score moves // @@ -2587,8 +2651,51 @@ Return value: s |= (IS_SAME_MOVE(sKillers[1].mv, mv) * sKillers[1].uBonus); s |= (IS_SAME_MOVE(sKillers[2].mv, mv) * sKillers[2].uBonus); s |= (IS_SAME_MOVE(sKillers[3].mv, mv) * sKillers[3].uBonus); - s |= ((GOOD_MOVE + PIECE_VALUE(mv.pMoved) / 2) * - (mv.cFrom == cEnprise)); + + // RETIRED: hung-piece-escape used to get an unconditional + // (GOOD_MOVE + PIECE_VALUE/2) promotion here for any move + // whose origin square was flagged en-prise, regardless of + // any track record. Evidence-calibration data showed the + // overwhelming majority of triggers (the "zero accumulated + // history/continuation evidence" bucket -- 250-1000x more + // populous than the equivalent countermove-match bucket) + // had a fail-high rate statistically identical to an + // ordinary, unprivileged leftover move (~0.22-0.25% vs. + // ~0.19-0.21% baseline). The escape motif alone, with no + // verification the destination is actually safe or the + // threat was real, isn't a trustworthy enough signal to + // justify an unconditional tier promotion -- unlike + // killers (self-evidencing by construction) or a + // sufficiently-evidenced countermove match. Retired + // rather than evidence-gated: a well-evidenced escape + // still gets ranked via the existing, already-validated + // history/continuation scoring below, same as any other + // leftover -- no separate mechanism needed for that case. + + // Countermove-match tier, evidence-gated -- see chess.h's + // COUNTERMOVE_TIER_BONUS/COUNTERMOVE_EVIDENCE_THRESHOLD + // comment. Calibration data showed a raw match, with no + // track record, performs identically to an ordinary + // leftover (~0.6-0.85% FH) -- promoting on match alone + // repeats hung-piece-escape's mistake. Only promote once + // the same history+continuation evidence already used + // to score ordinary leftovers clears a threshold where + // it demonstrably beats killer-ply2's own average. + // Written into iValue itself (not a selection-time-only + // nudge) so a qualifying match actually escapes + // GOOD_MOVE/leftover classification. + if ((TRUE == fHaveCounterMove) && + (IS_SAME_MOVE(mv, ctx->mvCounter[uCounterMoveIdx][0]) || + IS_SAME_MOVE(mv, ctx->mvCounter[uCounterMoveIdx][1]))) + { + ULONG uCMEvidence = g_HistoryCounters[mv.pMoved][mv.cTo] + + g_ContinuationHistory[(uCounterMoveContKey * CONT_KEY_RANGE) + + MOVE_TO_CONT_KEY(mv)]; + if (uCMEvidence >= COUNTERMOVE_EVIDENCE_THRESHOLD) + { + s |= COUNTERMOVE_TIER_BONUS; + } + } ASSERT(s >= 0); } pStack->mvf[u].iValue = s; |
