From 985892dc29841d994dc46aae955b589e9e44e788 Mon Sep 17 00:00:00 2001 From: ApexFlex Date: Sun, 28 Jun 2026 05:16:58 +0200 Subject: [PATCH] fix: seed AI RNG deterministically to prevent replay desync The AI used two separate non-deterministic random sources: - AI::getRandomGenerator() in random.cpp was seeded with std::random_device on first use, so it started at a different state in replay mode than during the original game run, causing AI decisions to diverge - CheckSeaAttack in AIPlayerJH.cpp allocated its own std::mt19937 seeded with std::random_device()() every call, doubly breaking determinism Fix: seed AI::getRandomGenerator() from the game's random_init at StartGame (same point where RANDOM.Init is called), so replay and live game use the same AI RNG sequence. Replace the local mt19937 with the shared AI generator. --- libs/s25main/ai/aijh/AIPlayerJH.cpp | 2 +- libs/s25main/network/GameClient.cpp | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/libs/s25main/ai/aijh/AIPlayerJH.cpp b/libs/s25main/ai/aijh/AIPlayerJH.cpp index dae7f5c070..02fab01c44 100644 --- a/libs/s25main/ai/aijh/AIPlayerJH.cpp +++ b/libs/s25main/ai/aijh/AIPlayerJH.cpp @@ -1661,7 +1661,7 @@ void AIPlayerJH::TrySeaAttack() // \n",gwb.GetHarborPoint(i).x,gwb.GetHarborPoint(i).y); } } - auto prng = std::mt19937(std::random_device()()); + auto& prng = AI::getRandomGenerator(); // any undefendedTargets? -> pick one by random if(!undefendedTargets.empty()) { diff --git a/libs/s25main/network/GameClient.cpp b/libs/s25main/network/GameClient.cpp index 0060d5964f..22460acc5a 100644 --- a/libs/s25main/network/GameClient.cpp +++ b/libs/s25main/network/GameClient.cpp @@ -3,6 +3,7 @@ // SPDX-License-Identifier: GPL-2.0-or-later #include "GameClient.h" +#include "ai/random.h" #include "CreateServerInfo.h" #include "EventManager.h" #include "Game.h" @@ -288,6 +289,7 @@ void GameClient::StartGame(const unsigned random_init) // Random-Generator initialisieren RANDOM.Init(random_init); + AI::getRandomGenerator().seed(random_init); if(!IsReplayModeOn() && mapinfo.savegame && !mapinfo.savegame->Load(mapinfo.filepath, SaveGameDataToLoad::All)) {