Skip to content

Commit d4d04a8

Browse files
domino14claude
andcommitted
fix endgame solver timeout handling for also-solve-move
This fixes a bug where the endgame solver would fail when using SetAlsoSolveMove (used by the analyzer for volunteer mode) if the main solve timed out before completing all requested plies. The issue had three parts: 1. When the main solve timed out at (say) ply 7 after completing ply 6, it would try to solve the also-solve move using the expired context, which would immediately fail. 2. Even if it succeeded, the also-solve move would be solved at ply 7 while the best move was only solved to ply 6, making results incomparable. 3. The best move from the partial solve wasn't saved to variations, so the also-solve move would incorrectly become the "best" move. The fix: - Track lastCompletedPly in all three parallel algorithms (ABDADA, LazySMP, TreeSplit) to know what depth was actually completed - When solving also-solve move after a timeout, use the same completed ply depth as the best move to ensure comparable results - Create a fresh 60-second timeout context specifically for solving the also-solve move, since the original context has expired - Save the best move to variations before breaking on timeout, so it doesn't get lost when also-solve move is added to variations This ensures both moves are solved to the same depth and results are properly comparable for spread loss calculation. Also updated endgame.txt helptext to document the timeout behavior. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
1 parent 04a4768 commit d4d04a8

File tree

2 files changed

+47
-2
lines changed

2 files changed

+47
-2
lines changed

endgame/negamax/solver.go

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,9 @@ type Solver struct {
239239

240240
alsoSolveMove tinymove.TinyMove
241241

242+
// Track the last successfully completed ply depth
243+
lastCompletedPly int
244+
242245
// Metrics from last solve
243246
lastSolveTime float64
244247
lastTTableStats string
@@ -526,6 +529,8 @@ func (s *Solver) iterativelyDeepenLazySMP(ctx context.Context, plies int) error
526529
log.Err(err).Msg("lazySMP-possible-error")
527530
return err
528531
}
532+
// Successfully completed this ply
533+
s.lastCompletedPly = p
529534
justWon := s.principalVariation.Moves[0]
530535

531536
if s.preventSlowroll {
@@ -689,6 +694,8 @@ func (s *Solver) iterativelyDeepenABDADA(ctx context.Context, plies int) error {
689694
// Check if we're within the aspiration window
690695
if val > aspα && val < aspβ {
691696
lastIteration = val
697+
// Successfully completed this ply
698+
s.lastCompletedPly = p
692699
break aspirationLoop
693700
}
694701

@@ -911,6 +918,9 @@ func (s *Solver) iterativelyDeepenTreeSplit(ctx context.Context, plies int) erro
911918
s.bestPVValue = finalBest - int16(s.initialSpread)
912919
}
913920

921+
// Successfully completed this ply
922+
s.lastCompletedPly = p
923+
914924
nodes := s.nodes.Load()
915925
log.Info().Int16("spread", finalBest-int16(s.initialSpread)).Int("ply", p).Str("pv", s.principalVariation.NLBString()).Uint64("total-nodes", nodes).Msg("best-val")
916926
}
@@ -1145,6 +1155,12 @@ searchLoop:
11451155
err := algorithmFunc(ctx, plies)
11461156
if err != nil {
11471157
log.Err(err).Msg("algorithm-error-in-multiple-variations")
1158+
// Save the best move found so far before breaking
1159+
if s.principalVariation.numMoves > 0 {
1160+
s.variations = append(s.variations, s.principalVariation)
1161+
log.Info().Str("partial-result", s.principalVariation.NLBString()).
1162+
Msg("saved-partial-result-before-timeout")
1163+
}
11481164
break searchLoop
11491165
}
11501166

@@ -1181,11 +1197,29 @@ searchLoop:
11811197
}
11821198
if len(filtered) > 0 {
11831199
s.initialMoves[0] = filtered
1184-
err := algorithmFunc(ctx, plies)
1200+
1201+
// Use the same ply depth that was successfully completed for the best move
1202+
// to ensure comparable results
1203+
pliesToUse := plies
1204+
if s.lastCompletedPly > 0 && s.lastCompletedPly < plies {
1205+
pliesToUse = s.lastCompletedPly
1206+
log.Info().
1207+
Int("requested-plies", plies).
1208+
Int("using-plies", pliesToUse).
1209+
Msg("using-last-completed-ply-for-also-solve-move")
1210+
}
1211+
1212+
// Create fresh context with 1 minute timeout for solving just this move
1213+
freshCtx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
1214+
defer cancel()
1215+
1216+
err := algorithmFunc(freshCtx, pliesToUse)
11851217
if err == nil {
11861218
s.variations = append(s.variations, s.principalVariation)
11871219
log.Info().Str("also-solve-move", s.principalVariation.NLBString()).
11881220
Msg("solved-also-solve-var")
1221+
} else {
1222+
log.Warn().Err(err).Msg("failed-to-solve-also-solve-move")
11891223
}
11901224
}
11911225
}
@@ -1655,6 +1689,7 @@ func (s *Solver) Solve(ctx context.Context, plies int) (int16, []*move.Move, err
16551689
log.Debug().Int("plies", plies).Msg("alphabeta-solve-config")
16561690
s.requestedPlies = plies
16571691
s.variations = []PVLine{}
1692+
s.lastCompletedPly = 0
16581693

16591694
tstart := time.Now()
16601695
s.stmMovegen.SetSortingParameter(movegen.SortByNone)

shell/helptext/endgame.txt

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,10 @@ Options:
5757
iterative deepening by default, so a good enough solution should be found
5858
rapidly, and this solution will be improved upon.
5959

60+
Note: When using -also-solve-var (see below), if the main solve times out,
61+
the specified variant will be solved with a fresh 60-second timeout. This
62+
means the total solve time could be up to maxtime + 60 seconds.
63+
6064
-disable-id true
6165

6266
This option disables iterative deepening. Iterative Deepening allows the
@@ -127,4 +131,10 @@ Options:
127131

128132
This mode will always solve the variant you specify, in addition to also finding
129133
the top play. This mode will tell you how far the optimal sequence beginning
130-
with the specified move is from the optimal sequence.
134+
with the specified move is from the optimal sequence.
135+
136+
Note: The specified variant will be solved to the same ply depth as the optimal
137+
move to ensure comparable results. If the main solve times out before reaching
138+
the requested plies, the variant will be solved to the last completed ply depth.
139+
The variant solve gets a fresh 60-second timeout, so if the main solve reaches
140+
maxtime, total solve time may be up to maxtime + 60 seconds.

0 commit comments

Comments
 (0)