fix(botapi): static event queue storage + end-of-battle train wait
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.
This commit is contained in:
@@ -0,0 +1,92 @@
|
||||
## SVG debug graphics API for Robocode Tank Royale Nim bot API.
|
||||
## Module-level procs write SVG into a buffer that is flushed into
|
||||
## BotIntent.debugGraphics each tick and cleared afterward.
|
||||
|
||||
import std/strformat
|
||||
import ./color
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Module-level state (single bot per process)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
var gSvgBuffer: string
|
||||
var gStrokeColor: Color = WHITE
|
||||
var gFillColor: Color = WHITE
|
||||
var gStrokeWidth: float = 1.0
|
||||
var gFontFamily: string = "Arial"
|
||||
var gFontSize: float = 12.0
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Internal helpers
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
proc svgAttrs(): string =
|
||||
## Current stroke/fill/width as SVG attribute string.
|
||||
&"stroke=\"{gStrokeColor.toHex}\" fill=\"{gFillColor.toHex}\" stroke-width=\"{gStrokeWidth}\""
|
||||
|
||||
proc svgOutput*(): string =
|
||||
## Returns the SVG fragment for this tick, or "" if nothing was drawn.
|
||||
if gSvgBuffer.len == 0: return ""
|
||||
"<g>" & gSvgBuffer & "</g>"
|
||||
|
||||
proc clearGraphics*() =
|
||||
## Reset buffer and all style globals to defaults. Called after each tick.
|
||||
gSvgBuffer = ""
|
||||
gStrokeColor = WHITE
|
||||
gFillColor = WHITE
|
||||
gStrokeWidth = 1.0
|
||||
gFontFamily = "Arial"
|
||||
gFontSize = 12.0
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# State setters
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
proc setStrokeColor*(c: Color) = gStrokeColor = c
|
||||
proc setFillColor*(c: Color) = gFillColor = c
|
||||
proc setStrokeWidth*(w: float) = gStrokeWidth = w
|
||||
proc setFont*(family: string; size: float) =
|
||||
gFontFamily = family
|
||||
gFontSize = size
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Draw procs — append raw SVG elements
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
proc drawLine*(x1, y1, x2, y2: float) =
|
||||
gSvgBuffer.add &"<line x1=\"{x1}\" y1=\"{y1}\" x2=\"{x2}\" y2=\"{y2}\" {svgAttrs()}/>"
|
||||
|
||||
proc drawRectangle*(x, y, w, h: float) =
|
||||
let attrs = &"stroke=\"{gStrokeColor.toHex}\" fill=\"none\" stroke-width=\"{gStrokeWidth}\""
|
||||
gSvgBuffer.add &"<rect x=\"{x}\" y=\"{y}\" width=\"{w}\" height=\"{h}\" {attrs}/>"
|
||||
|
||||
proc fillRectangle*(x, y, w, h: float) =
|
||||
let attrs = &"stroke=\"none\" fill=\"{gFillColor.toHex}\""
|
||||
gSvgBuffer.add &"<rect x=\"{x}\" y=\"{y}\" width=\"{w}\" height=\"{h}\" {attrs}/>"
|
||||
|
||||
proc drawCircle*(x, y, r: float) =
|
||||
let attrs = &"stroke=\"{gStrokeColor.toHex}\" fill=\"none\" stroke-width=\"{gStrokeWidth}\""
|
||||
gSvgBuffer.add &"<circle cx=\"{x}\" cy=\"{y}\" r=\"{r}\" {attrs}/>"
|
||||
|
||||
proc fillCircle*(x, y, r: float) =
|
||||
let attrs = &"stroke=\"none\" fill=\"{gFillColor.toHex}\""
|
||||
gSvgBuffer.add &"<circle cx=\"{x}\" cy=\"{y}\" r=\"{r}\" {attrs}/>"
|
||||
|
||||
proc drawText*(text: string; x, y: float) =
|
||||
gSvgBuffer.add &"<text x=\"{x}\" y=\"{y}\" font-family=\"{gFontFamily}\" font-size=\"{gFontSize}\">{text}</text>"
|
||||
|
||||
proc drawPolygon*(points: seq[(float, float)]) =
|
||||
var pts = ""
|
||||
for (px, py) in points:
|
||||
if pts.len > 0: pts.add ' '
|
||||
pts.add &"{px},{py}"
|
||||
let attrs = &"stroke=\"{gStrokeColor.toHex}\" fill=\"none\" stroke-width=\"{gStrokeWidth}\""
|
||||
gSvgBuffer.add &"<polygon points=\"{pts}\" {attrs}/>"
|
||||
|
||||
proc fillPolygon*(points: seq[(float, float)]) =
|
||||
var pts = ""
|
||||
for (px, py) in points:
|
||||
if pts.len > 0: pts.add ' '
|
||||
pts.add &"{px},{py}"
|
||||
let attrs = &"stroke=\"none\" fill=\"{gFillColor.toHex}\""
|
||||
gSvgBuffer.add &"<polygon points=\"{pts}\" {attrs}/>"
|
||||
Reference in New Issue
Block a user