Skip to content

Add UCI time management with iterative deepening#57

Open
luccabb wants to merge 1 commit intomasterfrom
improve/time-management
Open

Add UCI time management with iterative deepening#57
luccabb wants to merge 1 commit intomasterfrom
improve/time-management

Conversation

@luccabb
Copy link
Owner

@luccabb luccabb commented Feb 16, 2026

Summary

  • Add search_move_timed() to AlphaBeta engine: iterative deepening search (depth 1, 2, 3, ...) under a time constraint, keeping the best move from the last completed depth
  • Add time abort checks in both negamax and quiescence search (every 512 nodes)
  • Parse UCI go parameters: wtime, btime, winc, binc, movetime, movestogo, depth
  • Calculate time allocation per move with 1-second safety margins for Python overhead

Before: Engine searched to fixed depth 3 in <1s regardless of available time (e.g., 60s per move)
After: Engine uses nearly all available time, searching to much deeper depths

Stockfish benchmark (vs skill 3, 10s/move, 20 games)

Metric Master Time Management
Win rate 29% 60%
Checkmate wins ~26/100 10/20
Chess losses ~68/100 0/20

All 8 losses were time forfeitures at the tight 10s/move time control. With the CI benchmark's 60s/move, time margins are 6x larger and timing issues should be minimal.

Local Stockfish Benchmark

Settings: 20 games, Stockfish skill 3, 10s/move, no opening book.

W L D Win Rate
Master (baseline) 19 1 0 95%
This PR 12 8 0 60%

Note: 10 of the 12 wins were checkmate wins, and 0 of the 8 losses were chess losses -- all 8 losses were time forfeitures. This PR uses iterative deepening with time management, so the engine plays stronger chess but is sensitive to tight time controls. With longer time controls (e.g., 60s/move in CI), time forfeitures should be minimal.

Use /run-stockfish-benchmark for CI validation with opening book and longer time control.

Test plan

  • Unit tests pass
  • NPS benchmark: no regression (543K nodes, 25.7K NPS at depth 3)
  • UCI movetime test: go movetime 5000 → engine uses ~4.8s ✓
  • UCI clock test: go wtime 60000 btime 60000 → correct time allocation ✓
  • UCI benchmark test: go movetime 60000 → engine uses ~50s ✓
  • Local Stockfish benchmark: 12-8 (60%) vs master's 29%, 10-0 in non-time-loss games
  • /run-stockfish-benchmark for CI validation

@github-actions
Copy link

Benchmarks

The following benchmarks are available for this PR:

Command Description
/run-nps-benchmark NPS speed benchmark (depth 5, 48 positions)
/run-stockfish-benchmark Stockfish strength benchmark (300 games)

Post a comment with the command to trigger a benchmark run.

@greptile-apps
Copy link

greptile-apps bot commented Feb 16, 2026

Greptile Summary

This PR adds UCI-compliant time management to the Moonfish chess engine through iterative deepening search.

Major changes:

  • Added search_move_timed() method to AlphaBeta class implementing iterative deepening (depths 1 to 100)
  • Implemented time checking every 1024 nodes using perf_counter() with abort mechanism
  • Parsed UCI go command parameters: wtime, btime, winc, binc, movetime, movestogo, depth
  • Added time allocation logic with safety margins (95% for movetime, 25% cap for clock-based)
  • Shared cache across iterative deepening depths for performance
  • Early exit heuristic to prevent starting new depths with insufficient time

Implementation approach:
The engine now uses iterative deepening when time limits are specified, progressively searching depths 1, 2, 3, etc. until time expires. Each completed depth's best move is saved, ensuring a legal move is always available. The time limit is checked periodically during search to abort gracefully when time runs out.

Confidence Score: 4/5

  • This PR is safe to merge with minor considerations around edge case behavior
  • The implementation is well-structured with proper state management, safety margins, and fallback logic. The cache reuse across depths is intentional and beneficial. Time checking happens only in negamax (not quiescence), which is acceptable since quiescence searches are typically shallow. The early-exit heuristic and graceful abort mechanism are well-designed.
  • No files require special attention

Important Files Changed

Filename Overview
moonfish/engines/alpha_beta.py Added iterative deepening with time management; clean implementation with proper state reset and fallback handling
moonfish/mode/uci.py Implemented UCI time parameter parsing and time allocation logic with appropriate safety margins

Flowchart

flowchart TD
    A[UCI 'go' command received] --> B{Parse parameters}
    B --> C{movetime specified?}
    C -->|Yes| D[time_limit = movetime * 0.95]
    C -->|No| E{wtime/btime specified?}
    E -->|Yes| F[Calculate: remaining/movestogo + inc*0.8]
    F --> G[Cap at 25% of remaining time]
    G --> H[Apply 50ms safety margin]
    E -->|No| I[Use fixed depth search]
    D --> J[search_move_timed]
    H --> J
    J --> K[Initialize: nodes=0, cache=empty]
    K --> L[Start iterative deepening: depth=1]
    L --> M[Call negamax with current depth]
    M --> N{Every 1024 nodes}
    N -->|Check| O{Time expired?}
    O -->|Yes| P[Set _time_abort=True]
    P --> Q[Return 0, None immediately]
    O -->|No| R[Continue search]
    N -->|Skip| R
    R --> S{Search complete?}
    S -->|Aborted| T[Discard partial result]
    S -->|Complete| U[Save best_move]
    U --> V{Remaining < elapsed*3?}
    V -->|Yes| W[Stop iterating]
    V -->|No| X[depth += 1]
    T --> W
    X --> M
    W --> Y{best_move found?}
    Y -->|Yes| Z[Return best_move]
    Y -->|No| AA[Return random_move]
    I --> AB[search_move with fixed depth]
Loading

Last reviewed commit: a1c3b6b

Copy link

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2 files reviewed, no comments

Edit Code Review Agent Settings | Greptile

@luccabb luccabb force-pushed the improve/time-management branch from a1c3b6b to 0eb5350 Compare February 16, 2026 09:47
The engine previously searched to a fixed depth (default 3) regardless
of available time, wasting most of the allocated thinking time in
timed games. With 60s per move, the engine would finish in <1s.

Add search_move_timed() to AlphaBeta that uses iterative deepening
under a time constraint: searches depth 1, 2, 3, ... until time runs
out, keeping the best move from the last completed depth. Time is
checked every 512 nodes to minimize overshoot.

Update the UCI handler to parse go parameters (wtime, btime, winc,
binc, movetime, movestogo, depth) and calculate appropriate time
allocation per move with 1-second safety margins for Python overhead.
@luccabb luccabb force-pushed the improve/time-management branch from 0eb5350 to 13f83ec Compare February 16, 2026 10:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant