From 7f395d34f1cecca7b092a9d30d6aca0422ca1aa0 Mon Sep 17 00:00:00 2001 From: Nick Date: Wed, 11 Nov 2026 04:31:57 -0600 Subject: [PATCH 1/4] Suppress SPRT per-ply progress messages without hiding completed games --- src/tunex/cli.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/tunex/cli.py b/src/tunex/cli.py index 49dd26d..40d1ac0 100644 --- a/src/tunex/cli.py +++ b/src/tunex/cli.py @@ -43,9 +43,13 @@ def command_probe(config: Config) -> int: return 0 -def _game_progress(label: str, lock: threading.Lock): +def _game_progress( + label: str, lock: threading.Lock, *, show_playing: bool = True +): """Progress callback is called on worker threads; serialize console lines.""" def report(spec: GameSpec, stage: str, plies: int) -> None: + if stage == "playing" and not show_playing: + return with lock: team = "Red+Yellow" if spec.candidate_team == 0 else "Blue+Green" if stage == "starting": @@ -136,7 +140,7 @@ def completed(record: GameRecord, batch_wdl: WDL) -> None: batch_wdl, _ = runner.run( games=batch, offset=played, - on_progress=_game_progress("SPRT", output_lock), + on_progress=_game_progress("SPRT", output_lock, show_playing=False), on_game_complete=completed, ) total.extend(batch_wdl) From 6e3374b452625e22821e84946b475b5182559814 Mon Sep 17 00:00:00 2001 From: Nick Date: Wed, 11 Nov 2026 04:32:16 -0600 Subject: [PATCH 2/4] Verify SPRT quiet output and retain fixed-match ply reporting --- tests/test_live_progress.py | 30 +++++++++++++++++++++++++++--- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/tests/test_live_progress.py b/tests/test_live_progress.py index 36f9ecd..9efb357 100644 --- a/tests/test_live_progress.py +++ b/tests/test_live_progress.py @@ -1,12 +1,13 @@ """Live progress covers games that have not finished and SPRT batches.""" +import threading from types import SimpleNamespace from unittest.mock import patch -from tunex.cli import command_sprt +from tunex.cli import _game_progress, command_sprt from tunex.config import SearchConfig, SprtConfig, VariantConfig from tunex.engine import SearchResult -from tunex.match import MatchRunner +from tunex.match import GameSpec, MatchRunner class FakeEngine: @@ -87,6 +88,27 @@ def test_legacy_match_without_callback_does_not_change_engine_contract(): assert len(records) == 2 +def test_sprt_quiet_progress_does_not_change_match_progress(capsys): + spec = GameSpec(game_id=7, candidate_team=1) + quiet = _game_progress("SPRT", threading.Lock(), show_playing=False) + quiet(spec, "starting", 0) + quiet(spec, "playing", 0) + quiet(spec, "playing", 16) + assert capsys.readouterr().out == ( + "[SPRT] Game 8 starting | candidate Blue+Green\\n" + ) + + verbose = _game_progress("Match", threading.Lock()) + verbose(spec, "starting", 0) + verbose(spec, "playing", 0) + verbose(spec, "playing", 16) + assert capsys.readouterr().out == ( + "[Match] Game 8 starting | candidate Blue+Green\\n" + "[Match] Game 8 playing | ply 0\\n" + "[Match] Game 8 playing | ply 16\\n" + ) + + def test_sprt_prints_start_per_game_and_batch_decision(capsys, tmp_path): cfg = config(workers=2, plies=17) cfg.match.results_file = str(tmp_path / "games.jsonl") @@ -100,7 +122,9 @@ def test_sprt_prints_start_per_game_and_batch_decision(capsys, tmp_path): assert "workers=2 | go movetime 250" in output assert "Game 1 starting" in output assert "Game 2 starting" in output - assert "ply 16" in output + assert "[SPRT] Game 1 playing | ply" not in output + assert "[SPRT] Game 2 playing | ply" not in output + assert " playing | ply " not in output assert "[SPRT] Completed 1/2" in output assert "[SPRT] Completed 2/2" in output assert "SPRT reached max_games" in output From e918497749fd8c2955783e5a1de37346097a4d34 Mon Sep 17 00:00:00 2001 From: Nick Date: Wed, 11 Nov 2026 04:32:33 -0600 Subject: [PATCH 3/4] Correct expected newline literals in SPRT progress tests --- tests/test_live_progress.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/test_live_progress.py b/tests/test_live_progress.py index 9efb357..c1f36a3 100644 --- a/tests/test_live_progress.py +++ b/tests/test_live_progress.py @@ -95,7 +95,7 @@ def test_sprt_quiet_progress_does_not_change_match_progress(capsys): quiet(spec, "playing", 0) quiet(spec, "playing", 16) assert capsys.readouterr().out == ( - "[SPRT] Game 8 starting | candidate Blue+Green\\n" + "[SPRT] Game 8 starting | candidate Blue+Green\n" ) verbose = _game_progress("Match", threading.Lock()) @@ -103,9 +103,9 @@ def test_sprt_quiet_progress_does_not_change_match_progress(capsys): verbose(spec, "playing", 0) verbose(spec, "playing", 16) assert capsys.readouterr().out == ( - "[Match] Game 8 starting | candidate Blue+Green\\n" - "[Match] Game 8 playing | ply 0\\n" - "[Match] Game 8 playing | ply 16\\n" + "[Match] Game 8 starting | candidate Blue+Green\n" + "[Match] Game 8 playing | ply 0\n" + "[Match] Game 8 playing | ply 16\n" ) From 622584bee9d6fdf183169af15ed1f08c94a63b92 Mon Sep 17 00:00:00 2001 From: Nick Date: Wed, 11 Nov 2026 04:32:49 -0600 Subject: [PATCH 4/4] Document quiet SPRT progress output --- README.md | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 1d2928c..86d621d 100644 --- a/README.md +++ b/README.md @@ -289,11 +289,13 @@ tunex -c configs/ironphoenix.json sprt ``` Tunex immediately prints the two engine paths and search settings, then -shows when each game starts, when its position reaches another 16 plies, and -as soon as each game finishes. In multi-worker tests, progress lines may be -interleaved; each line includes the game number. Game counters and W/D/L are -updated after each completed game; SPRT LLR, hypothesis boundaries, Elo -estimates and decisions are evaluated at **completed batch boundaries**. +shows when each game starts and as soon as each game finishes, without +printing repeated per-ply `Game ... playing | ply ...` messages. +In multi-worker tests, start and completion lines may be interleaved; each +includes the game number. Game counters and W/D/L are updated after each +completed game; SPRT LLR, hypothesis boundaries, Elo estimates and decisions +are evaluated at **completed batch boundaries**. Fixed matches retain +their existing per-ply progress messages. Progress output is unbuffered (`flush=True`). For older installs, run `git pull` and `python -m pip install -e .` before rerunning Tunex.