From 1b30c3be393d25712c8743d3d9057207f8bbb67c Mon Sep 17 00:00:00 2001 From: Janis Eccarius Date: Tue, 23 Jun 2026 23:42:15 +0200 Subject: [PATCH] fix(official-bots): use ThreadLocalRandom in PolyglotBook for native image MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A stored java.util.Random field is reachable from BotController's static openingBook, so GraalVM baked it into the image heap and aborted the native build (Random in image heap has a cached seed). Use ThreadLocalRandom.current() at call time instead — no stored instance, nothing in the image heap, still thread-safe. Co-Authored-By: Claude Opus 4.8 --- .../src/main/scala/de/nowchess/bot/util/PolyglotBook.scala | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/modules/official-bots/src/main/scala/de/nowchess/bot/util/PolyglotBook.scala b/modules/official-bots/src/main/scala/de/nowchess/bot/util/PolyglotBook.scala index 8906bfc..58042aa 100644 --- a/modules/official-bots/src/main/scala/de/nowchess/bot/util/PolyglotBook.scala +++ b/modules/official-bots/src/main/scala/de/nowchess/bot/util/PolyglotBook.scala @@ -5,7 +5,7 @@ import de.nowchess.api.game.GameContext import de.nowchess.api.move.{Move, MoveType, PromotionPiece} import java.io.{DataInputStream, FileInputStream, InputStream} -import java.util.Random +import java.util.concurrent.ThreadLocalRandom import scala.collection.mutable /** Reads a Polyglot opening book (.bin file) and probes it for moves. @@ -18,8 +18,6 @@ import scala.collection.mutable */ final class PolyglotBook private (entries: Map[Long, Vector[BookEntry]]): - private val rng = Random() - /** Probe the book for a move in the given position. Returns a weighted random move, or None if not in book. */ def probe(context: GameContext): Option[Move] = val hash = PolyglotHash.hash(context) @@ -95,7 +93,7 @@ final class PolyglotBook private (entries: Map[Long, Vector[BookEntry]]): if entries.length == 1 then entries.head else val totalWeight = entries.map(_.weight).sum - val pick = rng.nextInt(totalWeight.max(1)) // NOSONAR + val pick = ThreadLocalRandom.current().nextInt(totalWeight.max(1)) // NOSONAR @scala.annotation.tailrec def select(remaining: Int, idx: Int): BookEntry =