64697f917e
The event queue's heap seq was the last GC'd block surviving across rounds: each round runs on a freshly spawned bot thread, so the N+1 thread realloc'd a block grown by dead thread N's allocator mid-round (at the next capacity doubling, ~turn 104) -> rawDealloc SIGSEGV in addEvent (7 gdb-confirmed coredumps). Replace with a static array[MAX_QUEUE_SIZE, BotEvent] + eventsLen: no heap block crosses threads, realloc can never happen. Also fix the harness aborting the final round mid-train: PPO_Bot's onRoundEnded trains synchronously after the runner's RoundEndedEvent, so the counter read right after awaitResults() is the stale pre-train value and System.exit killed the bot inside ppoUpdate. Poll up to 60s for the counter to catch up before declaring the battle incomplete. Verified: 72 consecutive rounds vs Fire, 100% wins, all rounds trained (counter advanced 1:1), zero coredumps since the fix.
80 lines
3.0 KiB
Nim
80 lines
3.0 KiB
Nim
## BotInfo: bot identification loaded from a JSON file or environment variables.
|
|
|
|
import std/[os, json, strutils, sequtils]
|
|
import ./schemas
|
|
|
|
type
|
|
BotInfo* = object
|
|
name*: string
|
|
version*: string
|
|
authors*: seq[string]
|
|
description*: string
|
|
homepage*: string
|
|
countryCodes*: seq[string]
|
|
gameTypes*: seq[string]
|
|
platform*: string
|
|
programmingLang*: string
|
|
initialPosition*: InitialPosition
|
|
isDroid*: bool
|
|
|
|
proc botInfoFromJson*(path: string): BotInfo =
|
|
let data = parseJson(readFile(path))
|
|
result.name = data{"name"}.getStr
|
|
result.version = data{"version"}.getStr
|
|
if data.hasKey("authors"):
|
|
for a in data["authors"]: result.authors.add a.getStr
|
|
result.description = data{"description"}.getStr
|
|
result.homepage = data{"homepage"}.getStr
|
|
if data.hasKey("countryCodes"):
|
|
for c in data["countryCodes"]: result.countryCodes.add c.getStr
|
|
if data.hasKey("gameTypes"):
|
|
for g in data["gameTypes"]: result.gameTypes.add g.getStr
|
|
result.platform = data{"platform"}.getStr("Nim " & NimVersion)
|
|
result.programmingLang = data{"programmingLang"}.getStr("Nim")
|
|
if data.hasKey("initialPosition"):
|
|
let ip = data["initialPosition"]
|
|
result.initialPosition.x = ip{"x"}.getFloat
|
|
result.initialPosition.y = ip{"y"}.getFloat
|
|
result.initialPosition.direction = ip{"direction"}.getFloat
|
|
result.isDroid = data{"isDroid"}.getBool(false)
|
|
|
|
proc botInfoFromEnv*(): BotInfo =
|
|
## Fall back to environment variables when no JSON file is given.
|
|
result.name = getEnv("BOT_NAME", "Unnamed Bot")
|
|
result.version = getEnv("BOT_VERSION", "1.0")
|
|
let authorsStr = getEnv("BOT_AUTHORS", "Unknown")
|
|
result.authors = authorsStr.split(',').mapIt(it.strip)
|
|
result.description = getEnv("BOT_DESCRIPTION", "")
|
|
result.homepage = getEnv("BOT_HOMEPAGE", "")
|
|
let ccStr = getEnv("BOT_COUNTRY_CODES", "")
|
|
if ccStr.len > 0:
|
|
result.countryCodes = ccStr.split(',').mapIt(it.strip)
|
|
let gtStr = getEnv("BOT_GAME_TYPES", "classic,melee,1v1")
|
|
result.gameTypes = gtStr.split(',').mapIt(it.strip)
|
|
result.platform = getEnv("BOT_PLATFORM", "Nim " & NimVersion)
|
|
result.programmingLang = getEnv("BOT_PROGRAMMING_LANG", "Nim")
|
|
result.isDroid = getEnv("BOT_IS_DROID", "false").toLowerAscii == "true"
|
|
|
|
proc loadBotInfo*(jsonFile: string = ""): BotInfo =
|
|
var resolved = ""
|
|
if jsonFile.len > 0:
|
|
if fileExists(jsonFile):
|
|
resolved = jsonFile
|
|
else:
|
|
# Try alongside the executable
|
|
let appPath = getAppDir() / jsonFile
|
|
if fileExists(appPath):
|
|
resolved = appPath
|
|
|
|
if resolved.len > 0:
|
|
result = botInfoFromJson(resolved)
|
|
else:
|
|
result = botInfoFromEnv()
|
|
# Ensure gameTypes has at least one entry
|
|
if result.gameTypes.len == 0:
|
|
result.gameTypes = @["classic", "melee", "1v1"]
|
|
if result.platform.len == 0:
|
|
result.platform = "Nim " & NimVersion
|
|
if result.programmingLang.len == 0:
|
|
result.programmingLang = "Nim"
|