Files
OpenFrontIO/src/core/execution/BotSpawner.ts
T
FloPinguin cf3e3a7a74 Fix bot/nation player ID collisions causing missing players 🔧 (#3354)
## Description:

BotSpawner used the same PRNG seed (simpleHash(gameID)) as
createGameRunner, causing bot IDs to collide with nation IDs. When a
bot's SpawnExecution found a nation with the same ID via hasPlayer(), it
silently reused that nation instead of creating a new player - resulting
in far fewer players than configured (e.g. ~670 instead of 800 with 400
bots + 400 nations) with no console warnings.

Offsets the BotSpawner seed by +2 to avoid the shared PRNG sequence
(matching the +1 pattern already used by Executor).

## Please complete the following:

- [X] I have added screenshots for all UI updates
- [X] I process any text displayed to the user through translateText()
and I've added it to the en.json file
- [X] I have added relevant tests to the test directory
- [X] I confirm I have thoroughly tested these changes and take full
responsibility for any bugs introduced

## Please put your Discord username so you can be contacted if a bug or
regression is found:

FloPinguin
2026-03-04 16:54:29 -08:00

44 lines
1.4 KiB
TypeScript

import { Game, PlayerInfo, PlayerType } from "../game/Game";
import { PseudoRandom } from "../PseudoRandom";
import { GameID } from "../Schemas";
import { simpleHash } from "../Util";
import { SpawnExecution } from "./SpawnExecution";
import { BOT_NAME_PREFIXES, BOT_NAME_SUFFIXES } from "./utils/BotNames";
export class BotSpawner {
private random: PseudoRandom;
private bots: SpawnExecution[] = [];
constructor(
private gs: Game,
private gameID: GameID,
) {
// Use a different seed than createGameRunner (which uses simpleHash(gameID))
// to avoid bot IDs colliding with nation/human IDs from the same PRNG sequence.
this.random = new PseudoRandom(simpleHash(gameID) + 2);
}
spawnBots(numBots: number): SpawnExecution[] {
for (let i = 0; i < numBots; i++) {
const name = this.randomBotName();
const spawn = this.spawnBot(name);
this.bots.push(spawn);
}
return this.bots;
}
spawnBot(botName: string): SpawnExecution {
return new SpawnExecution(
this.gameID,
new PlayerInfo(botName, PlayerType.Bot, null, this.random.nextID()),
);
}
private randomBotName(): string {
const prefixIndex = this.random.nextInt(0, BOT_NAME_PREFIXES.length);
const suffixIndex = this.random.nextInt(0, BOT_NAME_SUFFIXES.length);
return `${BOT_NAME_PREFIXES[prefixIndex]} ${BOT_NAME_SUFFIXES[suffixIndex]}`;
}
}