diff options
| author | Scott Gasch <[email protected]> | 2026-09-25 08:44:25 -0700 |
|---|---|---|
| committer | Scott Gasch <[email protected]> | 2026-09-25 08:44:25 -0700 |
| commit | 9b28080f8b3dca4588cb2aaaf748451f45e3c626 (patch) | |
| tree | 54e0aff5d6ee1520c590c7c04a5ae2f8299e7497 /src/testrecogn.c | |
| parent | 66740dc89c7351c120c25788d0079055034f4942 (diff) | |
Two ideas:
1. Nullmove R=3 + depth/6 along with some ShouldWeDoNullmoveSearch
tweaking and calibrating.
2. Probe EGTB everywhere in the search as long as the table/position
is resident in memory.
Diffstat (limited to 'src/testrecogn.c')
| -rw-r--r-- | src/testrecogn.c | 273 |
1 files changed, 267 insertions, 6 deletions
diff --git a/src/testrecogn.c b/src/testrecogn.c index b64643e..10ee889 100644 --- a/src/testrecogn.c +++ b/src/testrecogn.c @@ -295,7 +295,7 @@ _TestRecognFenCases(IN OUT SEARCHER_THREAD_CONTEXT *ctx, IN OUT POSITION *pos) } ReInitializeSearcherContext(pos, ctx); - uVal = RecognLookup(ctx, &iScore, FALSE); + uVal = RecognLookup(ctx, &iScore, EGTB_PROBE_NEVER); if ((UNRECOGNIZED != uVal) && ((iScore <= -NMATE) || (iScore >= NMATE))) { @@ -387,7 +387,7 @@ Return value: uPawnB, uKnightB, uBishopB); ReInitializeSearcherContext(&pos, ctx); - uVal = RecognLookup(ctx, &iScore, FALSE); + uVal = RecognLookup(ctx, &iScore, EGTB_PROBE_NEVER); if (UNRECOGNIZED == uVal) { uUnrecognized++; @@ -624,6 +624,267 @@ Return value: } // +// Build a KQKP (one queen, one pawn) position directly from square +// placements. fBlackHasQueen selects which color plays the queen side +// (cStrongKing/cWeakKing name roles, not colors, in both cases) -- see +// TestRecognExhaustiveKQKP's header comment for why both assignments +// are enumerated explicitly rather than assumed symmetric. +// +static FLAG +_BuildKQKPPosition(OUT POSITION *pos, + IN COOR cStrongKing, IN COOR cWeakKing, + IN COOR cQueen, IN COOR cPawn, + IN ULONG uToMove, IN FLAG fBlackHasQueen) +{ + PIECE pBoard[128]; + CHAR szFen[256]; + CHAR *p; + ULONG x; + + memset(pBoard, 0, sizeof(pBoard)); + if (fBlackHasQueen) + { + pBoard[cStrongKing] = BLACK_KING; + pBoard[cWeakKing] = WHITE_KING; + pBoard[cQueen] = BLACK_QUEEN; + pBoard[cPawn] = WHITE_PAWN; + } + else + { + pBoard[cStrongKing] = WHITE_KING; + pBoard[cWeakKing] = BLACK_KING; + pBoard[cQueen] = WHITE_QUEEN; + pBoard[cPawn] = BLACK_PAWN; + } + + memset(szFen, 0, sizeof(szFen)); + p = szFen; + for (x = 0; x < 120; x++) + { + if ((x % 15 == 0) && (x != 0)) + { + *p++ = '/'; + } + if (!IS_ON_BOARD(x)) continue; + if (0 == pBoard[x]) + { + *p++ = '1'; + } + else + { + *p++ = *(PieceAbbrev(pBoard[x]) + 1); + } + } + *p++ = ' '; + *p++ = (WHITE == uToMove) ? 'w' : 'b'; + strcat(p, " - - 0 1"); + + if (FALSE == FenToPosition(pos, szFen)) + { + return(FALSE); + } + if (InCheck(pos, FLIP(pos->uToMove))) + { + return(FALSE); + } + return(TRUE); +} + +// +// Does the side to move in ctx's current position have at least one +// legal move? Used only to classify a mismatch as "the to-move side is +// simply stalemated" vs. something else -- not part of the recognizer +// itself. Mirrors root.c's _DetectPreMoveTerminalStates legal-move +// count idiom (GenerateMoves then MakeMove/UnmakeMove-filter), just +// short-circuiting on the first legal move found instead of counting +// them all. +// +static FLAG +_ToMoveHasLegalMove(IN OUT SEARCHER_THREAD_CONTEXT *ctx) +{ + ULONG x; + MOVE mv; + FLAG fInCheck = InCheck(&ctx->sPosition, ctx->sPosition.uToMove); + + GenerateMoves(ctx, NULLMOVE, + fInCheck ? GENERATE_ESCAPES : GENERATE_ALL_MOVES); + for (x = ctx->sMoveStack.uBegin[ctx->uPly]; + x < ctx->sMoveStack.uEnd[ctx->uPly]; + x++) + { + mv = ctx->sMoveStack.mvf[x].mv; + if (TRUE == MakeMove(ctx, mv)) + { + UnmakeMove(ctx, mv); + return(TRUE); + } + } + return(FALSE); +} + +void +TestRecognExhaustiveKQKP(void) +/** + +Routine description: + + Exhaustively enumerate every legal KQKP (one queen, one pawn) + position -- not a random sample -- and verify _RecognizeKQKP's + verdict against direct EGTB probes wherever coverage exists. Same + "no counterexamples in N random samples is false confidence" + rationale as TestRecognExhaustiveKNKP/KPK -- doubly warranted here + since _RecognizeKQKP's win/UNRECOGNIZED boundary (rook/bishop pawn + on the square before queening) was scoped down from memory of + classical endgame theory rather than derived from EGTB data, and + this file has real precedent (_RecognizeKBKP's 2026-09-18 history) + for a plausible-looking classical shortcut turning out unsound once + fuzzed. + + Learned from TestRecognExhaustiveKPK's own 2026-09-18 history: + fixing which COLOR plays the strong side is NOT a safe assumption + in this codebase, even though RecognLookup/_RecognizeKQKP determine + "strong side" dynamically from piece counts -- iterates both + fBlackHasQueen assignments explicitly rather than trusting that + symmetry. Unlike TestRecognExhaustiveKPK, this keeps the left/right + mirror-halving restriction (FILE(cStrongKing) <= D, same as + TestRecognExhaustiveKNKP) -- safe here because, unlike _RecognizeKPK's + hand-typed critical-square table, _RecognizeKQKP contains no + per-square lookup data at all: its only file-dependent test is + "FILE(cPawn) == A/C/F/H", which treats both rook files and both + bishop files identically by construction, so a left/right board + reflection cannot expose an asymmetric transcription bug the way it + did for KPK's table. + + Not wired into TestRecogn()'s automatic startup suite (same + precommit_check.sh budget reasoning as KNKP/KPK) -- call this on + demand when validating a KQKP-area change. + +Parameters: + + void + +Return value: + + void + +**/ +{ + SEARCHER_THREAD_CONTEXT *ctx; + POSITION pos; + COOR cStrongKing, cWeakKing, cQueen, cPawn; + ULONG uToMove; + SCORE iScore, iEgtb; + ULONG uVal; + ULONG uTotal = 0, uChecked = 0, uSkipped = 0, uWrong = 0; + ULONG uReportedWrong = 0; + ULONG uWrongStalemated = 0; + FLAG fStalemated; + + Trace("Exhaustive KQKP (1 queen, 1 pawn) verification against " + "EGTB ground truth -- this takes a while...\n"); + ctx = malloc(sizeof(SEARCHER_THREAD_CONTEXT)); + if (NULL == ctx) + { + return; + } + InitializeSearcherContext(&pos, ctx); + + FOREACH_SQUARE(cStrongKing) + { + if (!IS_ON_BOARD(cStrongKing)) continue; + if (FILE(cStrongKing) > D) continue; + + FOREACH_SQUARE(cWeakKing) + { + if (!IS_ON_BOARD(cWeakKing)) continue; + if (DISTANCE(cStrongKing, cWeakKing) < 2) continue; + + FOREACH_SQUARE(cQueen) + { + if (!IS_ON_BOARD(cQueen)) continue; + if ((cQueen == cStrongKing) || (cQueen == cWeakKing)) continue; + + FOREACH_SQUARE(cPawn) + { + if (!IS_ON_BOARD(cPawn)) continue; + if (RANK1(cPawn) || RANK8(cPawn)) continue; + if ((cPawn == cStrongKing) || (cPawn == cWeakKing) || + (cPawn == cQueen)) continue; + + for (uToMove = BLACK; uToMove <= WHITE; uToMove++) + { + ULONG uColorPass; + + for (uColorPass = 0; uColorPass < 2; uColorPass++) + { + uTotal++; + if (0 == (uTotal % 500000)) + { + Trace(" ... %lu raw combos so far (%lu checked, " + "%lu wrong)\n", uTotal, uChecked, uWrong); + } + if (FALSE == _BuildKQKPPosition(&pos, cStrongKing, + cWeakKing, cQueen, + cPawn, uToMove, + (1 == uColorPass))) + { + continue; + } + ReInitializeSearcherContext(&pos, ctx); + uVal = _RecognizeKQKP(ctx, &iScore); + if (UNRECOGNIZED == uVal) + { + continue; + } + if (FALSE == ProbeEGTB(ctx, &iEgtb)) + { + uSkipped++; + continue; + } + uChecked++; + if (FALSE == _RecognizerAgreesWithEGTB(iScore, uVal, + iEgtb)) + { + uWrong++; + fStalemated = + (FALSE == InCheck(&ctx->sPosition, + ctx->sPosition.uToMove)) && + (FALSE == _ToMoveHasLegalMove(ctx)); + if (TRUE == fStalemated) + { + uWrongStalemated++; + } + if (uReportedWrong < 20) + { + Trace("MISMATCH (blackHasQueen=%lu) iScore=%d " + "uVal=%lu iEgtb=%d toMove=%lu " + "stalemated=%lu\n", + uColorPass, iScore, uVal, iEgtb, + pos.uToMove, fStalemated); + DumpPosition(&pos); + uReportedWrong++; + } + } + } + } + } + } + } + } + + { + ULONG uWrongOther = uWrong - uWrongStalemated; + + free(ctx); + Trace("Exhaustive KQKP: %lu raw combos, %lu checked against EGTB, " + "%lu skipped (no EGTB coverage), %lu WRONG (%lu of those are " + "the weak side simply stalemated, %lu are something else).\n", + uTotal, uChecked, uSkipped, uWrong, uWrongStalemated, + uWrongOther); + } +} + +// // Build a KPK (one pawn, one lone king) position directly from square // placements. fBlackHasPawn selects which color plays the pawn side // (cStrongKing/cWeakKing name roles, not colors, in both cases). @@ -845,7 +1106,7 @@ Return value: continue; } ReInitializeSearcherContext(&pos, ctx); - uVal = RecognLookup(ctx, &iScore, FALSE); + uVal = RecognLookup(ctx, &iScore, EGTB_PROBE_NEVER); if (UNRECOGNIZED == uVal) { continue; @@ -1227,7 +1488,7 @@ Return value: continue; } ReInitializeSearcherContext(&pos, ctx); - uVal = RecognLookup(ctx, &iScore, FALSE); + uVal = RecognLookup(ctx, &iScore, EGTB_PROBE_NEVER); if (UNRECOGNIZED == uVal) { uUnrecognized++; @@ -1758,7 +2019,7 @@ Return value: _MakeRandomEndgame(&pos, uPawnA, 0, 0, uPawnB, 0, 0); ReInitializeSearcherContext(&pos, ctx); - uVal = RecognLookup(ctx, &iScore, FALSE); + uVal = RecognLookup(ctx, &iScore, EGTB_PROBE_NEVER); if (UNRECOGNIZED == uVal) { uUnrecognized++; @@ -2011,7 +2272,7 @@ Return value: // signature routing a real search would use. // { - ULONG uVal = RecognLookup(ctx, &iRecScore, FALSE); + ULONG uVal = RecognLookup(ctx, &iRecScore, EGTB_PROBE_NEVER); if (UNRECOGNIZED != uVal) { uRecognClaimed++; |
