docs(test_framework): add comprehensive integration test guide and fix runner blocking issue

- Document exception handling, zero-value BotResult trap, shared adversary bots
- Add offline parsing example using parseServerOutput
- Skip tests gracefully when JARs missing (guard before suite blocks)
- Fix blocking readLine in runner_process.nim: poll with 50ms sleep + atEnd check
  (was preventing timeout enforcement, now blocks correctly during battle)
- Add test task to QBot.nimble and config.nims setup docs to AGENTS.md
- Add debug logging to TestBattleRunner for bot identity tracking

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-09-13 10:54:56 +02:00
parent 2bfa8eb6a5
commit 7e1f1483a4
19 changed files with 281 additions and 6 deletions
@@ -0,0 +1,11 @@
{
"name": "OscillatorBot",
"version": "0.1.0",
"authors": ["Davide Cappellini"],
"description": "Predictable zigzag sparring partner for gun testing",
"homepage": "",
"countryCodes": ["IT"],
"gameTypes": ["classic", "1v1"],
"platform": "Nim",
"programmingLang": "Nim"
}
@@ -0,0 +1,3 @@
#!/bin/sh
cd "$(dirname "$0")"
exec ./out/OscillatorBot 2>> /tmp/oscillatorbot_stderr.log
@@ -0,0 +1,2 @@
--path:"../../.."
switch("outdir", "out")
@@ -0,0 +1,11 @@
{
"name": "OscillatorBot",
"version": "0.1.0",
"authors": ["Davide Cappellini"],
"description": "Predictable zigzag sparring partner for gun testing",
"homepage": "",
"countryCodes": ["IT"],
"gameTypes": ["classic", "1v1"],
"platform": "Nim",
"programmingLang": "Nim"
}
@@ -0,0 +1,48 @@
# OscillatorBot — predictable zigzag sparring partner for GA gun testing.
# Reverses direction + turn every PERIOD ticks. Head-on targeting only.
import std/[math, os]
import tankroyale_botapi
const botJsonPath = currentSourcePath().parentDir / "OscillatorBot.json"
const
SPEED = 7.0 # forward/backward speed
PERIOD = 25 # ticks between direction reversals
type OscillatorBot = ref object of Bot
tickCount: int
moveSign: float # +1 forward, -1 backward
turnSign: float # +1 right, -1 left
method onRoundStarted*(bot: OscillatorBot, e: RoundStartedEvent) =
setAdjustGunForBodyTurn(true)
setAdjustRadarForBodyTurn(true)
setAdjustRadarForGunTurn(true)
bot.tickCount = 0
bot.moveSign = 1.0
bot.turnSign = 1.0
method onScannedBot*(bot: OscillatorBot, e: ScannedBotEvent) =
# Head-on targeting: aim gun directly at enemy, fire medium power
let bearing = directionTo(getX(), getY(), e.x, e.y)
let gunDelta = normalizeRelativeAngle(bearing - getGunDirection())
setGunTurnRate(gunDelta.clamp(-MAX_GUN_TURN_RATE, MAX_GUN_TURN_RATE))
if abs(gunDelta) < 10.0 and getGunHeat() <= 0.0:
discard setFire(2.0)
method run*(bot: OscillatorBot) =
while isRunning():
inc bot.tickCount
if bot.tickCount mod PERIOD == 0:
bot.moveSign *= -1.0
bot.turnSign *= -1.0
setTargetSpeed(bot.moveSign * SPEED)
setTurnRate(bot.turnSign * 4.0)
setRadarTurnRate(45.0) # spin radar to keep scanning
go()
when isMainModule:
var bot = OscillatorBot(moveSign: 1.0, turnSign: 1.0)
start(bot, botJsonPath)