Conversation
Board.remove_unsafe XORed ZOBRIST_STONE[opp][loc] for each captured stone, i.e. the key of the other color rather than the captured stone's own color. After any capture Board.zobrist no longer matched the stones on the board, undo() of a capturing move did not restore it, and distinct positions could share a hash. The C++ Board::removeChain uses the stone's own color (ZOBRIST_BOARD_HASH[cur][colors[cur]]). Add tests checking the incremental hash against a from-scratch recomputation after a capture, over random games, and across playRecordedUnsafe/undo. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
22nsuk
added a commit
to 22nsuk/KataGo
that referenced
this pull request
Oct 6, 2026
Backport the one-line fix proposed by zliu1022 in lightvector#1256 (upstream commit 0008dd7). The fork's original board blob matches that PR's base, so this retains the exact upstream fixed board without touching liberties or C++ search. The unchanged focused regressions on the previous tests-only commit reproduced five hash failures while the pass control succeeded.
22nsuk
added a commit
to 22nsuk/KataGo
that referenced
this pull request
Oct 6, 2026
Apply the one-line removed-stone-color correction proposed by zliu1022 in lightvector#1256 (0008dd7). Use the current-base compatible board, not the PR's older whole-file snapshot. The resulting production diff against fork master is only opp -> pla in remove_unsafe; neighbor liberties and all other board logic are preserved. The unchanged focused tests reproduced five hash failures and one passing non-capture control against the original fork board.
22nsuk
added a commit
to 22nsuk/KataGo
that referenced
this pull request
Oct 6, 2026
…t-1256 Backport upstream lightvector#1256 Python Board capture hash fix with focused regressions
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Board.remove_unsafeinpython/katago/game/board.pyremoves a captured chain like this:It XORs the key of the other color, so the captured stone's own key stays in the hash and the opposite color's key is added. The C++
Board::removeChainuses the stone's own color (pos_hash ^= ZOBRIST_BOARD_HASH[cur][colors[cur]];).Consequences:
board.zobristdiffers from the hash of the same stones placed directly. The error is exactlyZOBRIST_STONE[BLACK][p] ^ ZOBRIST_STONE[WHITE][p]for each captured pointp.undo()of a capturing move does not restore the hash (the refill inundoXORs the correct color, so the two no longer cancel). The python ladder search usesplayRecordedUnsafe/undo.pos_zobrist(): a five-point eye shape was solved as seki in one orientation and as dead in the others.As far as I can tell nothing inside this repo relies on the python hash being correct (the assert in
features.pycompares two hashes computed along the same path), so this mostly affects external users of the pythonBoard.Minimal reproduction:
Fix
One line: XOR
ZOBRIST_STONE[pla][loc].Tests
python/tests/test_board_zobrist.pychecks the incremental hash against a from-scratch recomputation after a single capture, after every move of 100 random 5x5 games, and acrossplayRecordedUnsafe/undo. All three fail before the fix and pass after it. (tests/test_export_prune_dead_ffn.pyhas 3 failures in my environment both with and without this change; they are unrelated.)🤖 Generated with Claude Code