Thanks for releasing the solver and the Cho Chikun tsumego set — reproduction was smooth.
While reproducing on a 2026 toolchain I hit a crash in a documented option, and tracked it down to a small, self-contained bug with what looks like a one-line-per-site fix. Sharing the diagnosis + patch in case it's useful.
Summary
Enabling the documented option USE_POTENTIAL_RZONE=true causes a deterministic segmentation fault early in the search, on both cfg/RZS-TT.cfg and cfg/RZS-PT.cfg, for every problem I tried. With the flag at its default (false) everything runs fine.
Repro
- Take
cfg/RZS-TT.cfg, set USE_POTENTIAL_RZONE=true, leave everything else default.
- Put a single problem in
candidate.list (e.g. chao_vol2_p156.json).
Release/CGI -conf_file cfg/RZS-TT.cfg -mode tsumego_solver
- → SIGSEGV within a few seconds. (Same with
cfg/RZS-PT.cfg.)
Environment: native build (no Docker) on WSL2 Ubuntu 24.04, libtorch 2.11.0+cu130 / CUDA 13.2 / gcc 13.3, RTX 5090. The fault is board-logic-level and looks toolchain-independent.
Backtrace (gdb)
#0 WeichiBoard::updateSiblings WeichiBoard.cpp:351 (grid.getBlock() is null)
#1 WeichiBoard::playLight WeichiBoard.cpp:189
#2 WeichiBoard::play WeichiBoard.cpp:169
#3 WeichiQuickWinHandler::hasPotentialRZone / hasBensonSequence WeichiQuickWinHandler.cpp
#4 WeichiQuickWinHandler::hasConnectorPotentialRZone WeichiQuickWinHandler.cpp:89
#5 WeichiUctAccessor::selectNode WeichiUctAccessor.cpp:93
Root cause
hasPotentialRZone and hasBensonSequence contain four connector-play loops that call m_board.play(WeichiMove(winColor, pos)) for each pos scanned from a connector/test bitboard, without checking the point is empty first. When a pos is already occupied (e.g. it becomes occupied after an earlier connector play in the same loop), play() produces a null block, which is then dereferenced in WeichiBoard::updateSiblings (WeichiBoard.cpp:351, gridBlock->getLiberty()).
The same file already has exactly this guard in another function (around WeichiQuickWinHandler.cpp:138): if (grid.getColor() != COLOR_NONE) { continue; }. It's just missing in these four loops.
Fix
Add the existing guard before each of the four m_board.play(move) calls:
if (m_board.getGrid(pos).getColor() != COLOR_NONE) { continue; }
Diff:
diff --git a/CGI/WeichiQuickWinHandler.cpp b/CGI/WeichiQuickWinHandler.cpp
--- a/CGI/WeichiQuickWinHandler.cpp
+++ b/CGI/WeichiQuickWinHandler.cpp
@@ -217,6 +217,7 @@ bool WeichiQuickWinHandler::hasPotentialRZone(const WeichiDragon& dragon, Weichi
int pos = 0;
while ((pos = bmTestStones.bitScanForward()) != -1) {
WeichiMove move(winColor, pos);
+ if (m_board.getGrid(pos).getColor() != COLOR_NONE) { continue; } // skip occupied connector (guard from line ~138); avoids null block in play()->updateSiblings
m_board.play(move);
bmPlayed.SetBitOn(pos);
if (m_board.getGrid(pos).getBlock()->getStatus() == LAD_LIFE) {
@@ -255,6 +256,7 @@ bool WeichiQuickWinHandler::hasBensonSequence(const WeichiDragon& dragon, Weichi
int pos = 0;
while ((pos = bmPossibleConnector.bitScanForward()) != -1) {
WeichiMove move(winColor, pos);
+ if (m_board.getGrid(pos).getColor() != COLOR_NONE) { continue; } // skip occupied connector (guard from line ~138); avoids null block in play()->updateSiblings
m_board.play(move);
bmRetPlayed.SetBitOn(pos);
if (m_board.getGrid(pos).getBlock()->getStatus() == LAD_LIFE) {
@@ -287,6 +289,7 @@ bool WeichiQuickWinHandler::hasBensonSequence(const WeichiDragon& dragon, Weichi
int pos = 0;
while ((pos = bmPossibleConnector.bitScanForward()) != -1) {
WeichiMove move(winColor, pos);
+ if (m_board.getGrid(pos).getColor() != COLOR_NONE) { continue; } // skip occupied connector (guard from line ~138); avoids null block in play()->updateSiblings
m_board.play(move);
bmRetPlayed.SetBitOn(pos);
if (m_board.getGrid(pos).getBlock()->getStatus() == LAD_LIFE) {
@@ -334,6 +337,7 @@ bool WeichiQuickWinHandler::hasBensonSequence(const WeichiDragon& dragon, Weichi
int pos = 0;
while ((pos = bmPossibleConnector.bitScanForward()) != -1) {
WeichiMove move(winColor, pos);
+ if (m_board.getGrid(pos).getColor() != COLOR_NONE) { continue; } // skip occupied connector (guard from line ~138); avoids null block in play()->updateSiblings
m_board.play(move);
bmRetPlayed.SetBitOn(pos);
if (m_board.getGrid(pos).getBlock()->getStatus() == LAD_LIFE) {
Validation
After the patch, USE_POTENTIAL_RZONE=true no longer crashes. I re-ran 9 problems that already solve soundly under the default config; all 9 returned the identical verdict (UCT_WIN) with real search (3.7k–34k simulations) — i.e. the fix removes the crash without changing results.
To be clear about scope: this is a correctness/crash fix, not a capability gain. On my hardest set (problems unproven at 300 s by both TT and PT), enabling the now-working potential-RZ did not prove any additional problems within the same time budget. So the patch just makes the documented flag usable and sound; it isn't claimed to solve more.
Happy to open this as a PR instead, or share the full core dump / logs.
Thanks for releasing the solver and the Cho Chikun tsumego set — reproduction was smooth.
While reproducing on a 2026 toolchain I hit a crash in a documented option, and tracked it down to a small, self-contained bug with what looks like a one-line-per-site fix. Sharing the diagnosis + patch in case it's useful.
Summary
Enabling the documented option
USE_POTENTIAL_RZONE=truecauses a deterministic segmentation fault early in the search, on bothcfg/RZS-TT.cfgandcfg/RZS-PT.cfg, for every problem I tried. With the flag at its default (false) everything runs fine.Repro
cfg/RZS-TT.cfg, setUSE_POTENTIAL_RZONE=true, leave everything else default.candidate.list(e.g.chao_vol2_p156.json).Release/CGI -conf_file cfg/RZS-TT.cfg -mode tsumego_solvercfg/RZS-PT.cfg.)Environment: native build (no Docker) on WSL2 Ubuntu 24.04, libtorch 2.11.0+cu130 / CUDA 13.2 / gcc 13.3, RTX 5090. The fault is board-logic-level and looks toolchain-independent.
Backtrace (gdb)
Root cause
hasPotentialRZoneandhasBensonSequencecontain four connector-play loops that callm_board.play(WeichiMove(winColor, pos))for eachposscanned from a connector/test bitboard, without checking the point is empty first. When aposis already occupied (e.g. it becomes occupied after an earlier connector play in the same loop),play()produces a null block, which is then dereferenced inWeichiBoard::updateSiblings(WeichiBoard.cpp:351,gridBlock->getLiberty()).The same file already has exactly this guard in another function (around
WeichiQuickWinHandler.cpp:138):if (grid.getColor() != COLOR_NONE) { continue; }. It's just missing in these four loops.Fix
Add the existing guard before each of the four
m_board.play(move)calls:Diff:
Validation
After the patch,
USE_POTENTIAL_RZONE=trueno longer crashes. I re-ran 9 problems that already solve soundly under the default config; all 9 returned the identical verdict (UCT_WIN) with real search (3.7k–34k simulations) — i.e. the fix removes the crash without changing results.To be clear about scope: this is a correctness/crash fix, not a capability gain. On my hardest set (problems unproven at 300 s by both TT and PT), enabling the now-working
potential-RZdid not prove any additional problems within the same time budget. So the patch just makes the documented flag usable and sound; it isn't claimed to solve more.Happy to open this as a PR instead, or share the full core dump / logs.