2025, Nov 23 19:00

How to Keep Infinite Chess Engine Analysis Truly Continuous in python-chess (No Time Jumps)

Learn why your chess engine timer jumps during infinite analysis, and fix it with one continuous python-chess session, InfoDict updates, and GUI threading.

Infinite engine analysis in a chess GUI looks simple on paper: start the engine, let it think without limits, and stream the best line and score as they improve. In practice, the clock you display may jump backward, which is counterintuitive when you expect an ever-growing elapsed time. This guide explains why that happens and how to implement truly continuous analysis without time resets.

Problem demonstration

The following snippet launches a new analysis thread every loop iteration and reads the reported time from the engine. The intention is to show a steadily increasing time value during infinite analysis, but the output jumps up and down instead.

import queue
import time
import chess.engine
import threading
import chess
pos = chess.Board()
data_pipe = queue.Queue()
uci_engine = chess.engine.SimpleEngine.popen_uci("C:/Program Files/ChessX/data/engines/stockfish/stockfish-windows-x86-64.exe")
lines_cache = []
def run_probe():
    global pos
    global data_pipe
    global lines_cache
    global uci_engine
    with uci_engine.analysis(pos, chess.engine.Limit(time=None), multipv=1) as stream:
        for payload in stream:
            try:
                if "multipv" in payload and "pv" in payload:
                    lines_cache.append(payload)
                    if len(lines_cache) == 1:
                        with data_pipe.mutex:
                            data_pipe.queue.clear()
                        data_pipe.put(lines_cache)
                        lines_cache = []
            except chess.engine.EngineTerminatedError:
                break
while True:
    worker = threading.Thread(target=run_probe)
    worker.start()
    time.sleep(2)
    if not data_pipe.empty():
        batch = data_pipe.get()
        payload = batch[0]
        spent = payload.get("time")
        print(f"Engine thinking time: {spent:.3f} seconds")

The printed times fluctuate because each new thread starts a fresh analysis session.

What’s actually going on

Every call to uci_engine.analysis(...) opens a new analysis run. The engine reports a time value that represents the elapsed time inside that particular run. When you spawn a new thread in each loop iteration, you restart analysis from scratch. The timer resets, so your display can go from 5 seconds back down to 1–2 seconds, and so on. The time isn’t cumulative across runs because you are not keeping a single long-lived session.

This is distinct from the one-shot interface. In continuous mode, you hold an analysis session open and consume a stream of InfoDict updates as the engine keeps thinking. In one-shot mode, you ask for a fixed limit and get a single result when it completes.

analysis vs analyse

There are two ways to drive the engine, and the difference matters for UI behavior and threading. The interface that keeps running and yields updates as the engine thinks is intended for ongoing analysis. The interface that runs once with a fixed time or depth and returns a single result is intended for one-off evaluations. If you need the timer to monotonically increase, you must keep the continuous session open rather than repeatedly starting new ones.

Use the continuous stream when you want infinite analysis that keeps yielding InfoDict updates. Use the single-shot call when you want a one-time result after a fixed limit.

Fix: keep a single analysis session alive

Start one background worker that opens a single analysis context and keeps pushing updates to a queue. Your GUI loop should only read from that queue. This avoids resetting the timer and achieves the desired infinite analysis behavior.

import queue
import time
import chess.engine
import threading
import chess
pos = chess.Board()
data_pipe = queue.Queue()
uci_engine = chess.engine.SimpleEngine.popen_uci("C:/Program Files/ChessX/data/engines/stockfish/stockfish-windows-x86-64.exe")
def analysis_worker():
    with uci_engine.analysis(pos, chess.engine.Limit(time=None), multipv=1) as stream:
        cache = []
        for payload in stream:
            if "multipv" in payload and "pv" in payload:
                cache.append(payload)
                if len(cache) == 1:
                    with data_pipe.mutex:
                        data_pipe.queue.clear()
                    data_pipe.put(cache)
                    cache = []
threading.Thread(target=analysis_worker, daemon=True).start()
while True:
    if not data_pipe.empty():
        batch = data_pipe.get()
        payload = batch[0]
        spent = payload.get("time")
        print(f"Engine thinking time: {spent:.3f} seconds")
    time.sleep(0.1)

This approach aligns the engine’s reported time with your expectations. Because the analysis context is not recreated, the engine keeps thinking indefinitely, and the elapsed time reflects a single continuous session.

Why this matters

In a chess GUI, consistency of the analysis stream is critical. Restarting analysis not only disrupts the timer but can also cause jumpy depth, nodes, and pv output. A single long-lived session produces stable, monotonic progress that users can follow in real time, and it simplifies the threading model because there is exactly one producer of engine updates.

Takeaways

If the time field appears to jump backward during “infinite” analysis, you are likely reopening analysis repeatedly. Keep one analysis session open, forward its InfoDict updates to your UI, and reserve the single-shot interface for fixed-limit evaluations. That setup gives you the expected upward-only timer and clean, continuous engine feedback.