Skip to content

[Chess] Fix stale INIT_ZOBRIST_HASH: derive it from the Zobrist tables at import time#1320

Open
gweber wants to merge 1 commit into
sotetsuk:mainfrom
gweber:chess-zobrist-init
Open

[Chess] Fix stale INIT_ZOBRIST_HASH: derive it from the Zobrist tables at import time#1320
gweber wants to merge 1 commit into
sotetsuk:mainfrom
gweber:chess-zobrist-init

Conversation

@gweber

@gweber gweber commented Jun 10, 2026

Copy link
Copy Markdown

Bug

The Zobrist tables in pgx/_src/games/chess.py are generated at import time from jax.random, whose outputs are not stable across JAX versions (e.g. the threefry_partitionable default change). The hardcoded constant

INIT_ZOBRIST_HASH = jnp.uint32([1455170221, 1478960862])

no longer matches _zobrist_hash(GameState()) under current JAX (0.10.1 gives [1875025768, 182641400]). Since GameState() seeds hash_history[0] with the stale constant while every later entry is recomputed, the initial position's history entry never matches:

  • Repetitions involving the start position are counted one short. Repro: the knight shuffle g1f3 g8f6 f3g1 f6g8 g1f3 g8f6 f3g1 f6g8 is a threefold repetition at ply 8, but pgx terminates only at ply 9 (when the position after 1.Nf3 repeats a third time instead).
  • The repetition observation planes (rep0/rep1) are wrong for the startpos history entry.
import jax.numpy as jnp
import pgx._src.games.chess as C
print(C.INIT_ZOBRIST_HASH)            # [1455170221 1478960862]
print(C._zobrist_hash(C.GameState())) # [1875025768  182641400]  <- mismatch

Fix

Compute the constant from the actual tables at import time (same XOR-reduce idiom as _zobrist_hash), so it can never go stale again, under any JAX version. On JAX versions where the old constant happened to match, this is a no-op.

Note: gardner_chess.py and animal_shogi.py hardcode similar init-hash constants and likely have the same issue; this PR fixes chess only.

The Zobrist tables are generated at import time from jax.random, whose
outputs are not stable across JAX versions (e.g. the threefry partitionable
change). The hardcoded INIT_ZOBRIST_HASH no longer matches
_zobrist_hash(GameState()) under current JAX, so the initial position's
hash_history entry never matches later recomputed hashes:

- repetitions involving the start position are counted one short
  (a g1f3/g8f6/f3g1/f6g8 knight shuffle terminates at ply 9 instead of 8)
- the repetition observation planes are wrong for the startpos entry

Compute the constant from the actual tables at import time so it can never
go stale again. No behavior change on JAX versions where the old constant
happened to match.
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