feat(testing): gun rack gauntlet + analysis reports
- fix(ModularBot): onBulletHitBot → onBulletHit (real hits were never tracked) - feat(ModularBot): per-round gun stats dump to /tmp/gun_stats.jsonl - feat(ModularBot): gun selection counter per round - fix(tests): adversary paths _garage suffix removed from 7 test files - feat(tests): test_gauntlet_5bots.nim — 10-round gauntlet vs all 5 adversaries - feat(tests): analyze_gun_stats.nim — JSONL parser for gun performance tables - docs: gun_rack_analysis.md — full per-gun performance report - docs: gun_rack_summary.md — TL;DR verdict table (keep/drop/tune) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Binary file not shown.
@@ -3,7 +3,7 @@
|
|||||||
## Radar: RadarLockModule (1v1) / MeleeScanModule (2+ enemies), auto-switched per tick.
|
## Radar: RadarLockModule (1v1) / MeleeScanModule (2+ enemies), auto-switched per tick.
|
||||||
## Movement: OscillatorModule (perpendicular strafing).
|
## Movement: OscillatorModule (perpendicular strafing).
|
||||||
|
|
||||||
import std/[math, os, strformat, tables]
|
import std/[math, os, strformat, tables, json]
|
||||||
import robocode_tankroyale_botapi
|
import robocode_tankroyale_botapi
|
||||||
import radar_harness/radar_interface
|
import radar_harness/radar_interface
|
||||||
import radars/radar_lock_module
|
import radars/radar_lock_module
|
||||||
@@ -89,6 +89,11 @@ type
|
|||||||
prevGun: int
|
prevGun: int
|
||||||
prevRadar: int
|
prevRadar: int
|
||||||
prevTarget: int
|
prevTarget: int
|
||||||
|
roundNumber: int
|
||||||
|
realShotsFired: int
|
||||||
|
realHits: int
|
||||||
|
gunSelectionCount: array[14, int]
|
||||||
|
lastKnownTargetId: int ## persists through death, used for round-end stats
|
||||||
|
|
||||||
proc printConfig(bot: ModularBot, changed: string = "") =
|
proc printConfig(bot: ModularBot, changed: string = "") =
|
||||||
let gunName = GunNames[bot.currentGun]
|
let gunName = GunNames[bot.currentGun]
|
||||||
@@ -136,10 +141,12 @@ method onScannedBot*(bot: ModularBot, e: ScannedBotEvent) =
|
|||||||
|
|
||||||
|
|
||||||
method onBulletFired*(bot: ModularBot, e: BulletFiredEvent) =
|
method onBulletFired*(bot: ModularBot, e: BulletFiredEvent) =
|
||||||
|
inc bot.realShotsFired
|
||||||
bot.moveTracker.shadows.addBullet(e.bullet.x, e.bullet.y,
|
bot.moveTracker.shadows.addBullet(e.bullet.x, e.bullet.y,
|
||||||
degToRad(e.bullet.direction), e.bullet.power)
|
degToRad(e.bullet.direction), e.bullet.power)
|
||||||
|
|
||||||
method onBulletHitBot*(bot: ModularBot, e: BulletHitBotEvent) =
|
method onBulletHit*(bot: ModularBot, e: BulletHitBotEvent) =
|
||||||
|
inc bot.realHits
|
||||||
bot.moveTracker.shadows.removeBulletNear(e.bullet.x, e.bullet.y)
|
bot.moveTracker.shadows.removeBulletNear(e.bullet.x, e.bullet.y)
|
||||||
|
|
||||||
method onBulletHitWall*(bot: ModularBot, e: BulletHitWallEvent) =
|
method onBulletHitWall*(bot: ModularBot, e: BulletHitWallEvent) =
|
||||||
@@ -154,9 +161,67 @@ method onHitByBullet*(bot: ModularBot, e: HitByBulletEvent) =
|
|||||||
bot.moveTracker.registerHit(e.bullet.power, e.bullet.direction, getX(), getY())
|
bot.moveTracker.registerHit(e.bullet.power, e.bullet.direction, getX(), getY())
|
||||||
|
|
||||||
method onRoundEnded*(bot: ModularBot, e: RoundEndedEventForBot) =
|
method onRoundEnded*(bot: ModularBot, e: RoundEndedEventForBot) =
|
||||||
discard
|
## Dump per-gun virtual bullet stats to /tmp/gun_stats.jsonl (one line per round).
|
||||||
|
# Use lastKnownTargetId: currentTargetId is -1 if enemy died before round end
|
||||||
|
let targetId = if bot.currentTargetId >= 0: bot.currentTargetId else: bot.lastKnownTargetId
|
||||||
|
# Aggregate fitness across all tracked enemies (or just the primary target if known).
|
||||||
|
# Use aggregate (targetId=-1) so we always have data even if target switched.
|
||||||
|
let fit = block:
|
||||||
|
var f: seq[vb.GunFitness]
|
||||||
|
if targetId >= 0 and targetId in bot.tracker.fitness:
|
||||||
|
f = bot.tracker.fitness[targetId]
|
||||||
|
else:
|
||||||
|
# aggregate across all enemies
|
||||||
|
f = newSeq[vb.GunFitness](bot.tracker.numGuns)
|
||||||
|
for perEnemy in bot.tracker.fitness.values:
|
||||||
|
for gid in 0..<bot.tracker.numGuns:
|
||||||
|
for binIdx in 0..<len(vb.PowerBins):
|
||||||
|
let src = perEnemy[gid].bins[binIdx]
|
||||||
|
let n = min(src.count, vb.WindowSize)
|
||||||
|
for k in 0..<n:
|
||||||
|
f[gid].bins[binIdx].hits[(f[gid].bins[binIdx].head + f[gid].bins[binIdx].count) mod vb.WindowSize] = src.hits[k]
|
||||||
|
inc f[gid].bins[binIdx].count
|
||||||
|
f
|
||||||
|
|
||||||
|
var gunsArr = newJArray()
|
||||||
|
for gid in 0..<14:
|
||||||
|
var totalShots = 0
|
||||||
|
var totalHits = 0
|
||||||
|
for binIdx in 0..<len(vb.PowerBins):
|
||||||
|
let fw = fit[gid].bins[binIdx]
|
||||||
|
let n = min(fw.count, vb.WindowSize)
|
||||||
|
totalShots += n
|
||||||
|
for k in 0..<n:
|
||||||
|
if fw.hits[k]: inc totalHits
|
||||||
|
let hitRate = if totalShots > 0: totalHits.float / totalShots.float else: 0.0
|
||||||
|
gunsArr.add(%*{
|
||||||
|
"id": gid,
|
||||||
|
"name": GunNames[gid],
|
||||||
|
"vShots": totalShots,
|
||||||
|
"vHits": totalHits,
|
||||||
|
"hitRate": (hitRate * 100.0).int,
|
||||||
|
"selected": bot.gunSelectionCount[gid]
|
||||||
|
})
|
||||||
|
|
||||||
|
let row = %*{
|
||||||
|
"round": bot.roundNumber,
|
||||||
|
"targetId": targetId,
|
||||||
|
"guns": gunsArr,
|
||||||
|
"realShots": bot.realShotsFired,
|
||||||
|
"realHits": bot.realHits,
|
||||||
|
"score": e.results.totalScore,
|
||||||
|
"bulletDmg": e.results.bulletDamage
|
||||||
|
}
|
||||||
|
let f = open("/tmp/gun_stats.jsonl", fmAppend)
|
||||||
|
f.writeLine($row)
|
||||||
|
f.close()
|
||||||
|
|
||||||
method onRoundStarted*(bot: ModularBot, e: RoundStartedEvent) =
|
method onRoundStarted*(bot: ModularBot, e: RoundStartedEvent) =
|
||||||
|
bot.roundNumber = e.roundNumber
|
||||||
|
bot.realShotsFired = 0
|
||||||
|
bot.realHits = 0
|
||||||
|
bot.lastKnownTargetId = -1
|
||||||
|
for i in 0..<14: bot.gunSelectionCount[i] = 0
|
||||||
setAdjustGunForBodyTurn(true)
|
setAdjustGunForBodyTurn(true)
|
||||||
setAdjustRadarForBodyTurn(true)
|
setAdjustRadarForBodyTurn(true)
|
||||||
setAdjustRadarForGunTurn(true)
|
setAdjustRadarForGunTurn(true)
|
||||||
@@ -253,6 +318,8 @@ method run*(bot: ModularBot) =
|
|||||||
bot.currentTargetId = candidateId
|
bot.currentTargetId = candidateId
|
||||||
bot.targetSwitchTick = bot.tick
|
bot.targetSwitchTick = bot.tick
|
||||||
bot.printConfig("target")
|
bot.printConfig("target")
|
||||||
|
if bot.currentTargetId >= 0:
|
||||||
|
bot.lastKnownTargetId = bot.currentTargetId
|
||||||
|
|
||||||
# --- Refresh target state FIRST, before ram decision ---
|
# --- Refresh target state FIRST, before ram decision ---
|
||||||
let tid = bot.currentTargetId
|
let tid = bot.currentTargetId
|
||||||
@@ -419,6 +486,7 @@ method run*(bot: ModularBot) =
|
|||||||
|
|
||||||
# Gun selection + fire
|
# Gun selection + fire
|
||||||
let (selectedGun, _, power) = selectShot(bot.tracker, tid)
|
let (selectedGun, _, power) = selectShot(bot.tracker, tid)
|
||||||
|
bot.gunSelectionCount[selectedGun] += 1
|
||||||
if selectedGun != bot.currentGun:
|
if selectedGun != bot.currentGun:
|
||||||
bot.currentGun = selectedGun
|
bot.currentGun = selectedGun
|
||||||
case selectedGun
|
case selectedGun
|
||||||
|
|||||||
@@ -0,0 +1,80 @@
|
|||||||
|
## Reads /tmp/gun_stats.jsonl and produces per-gun performance summary.
|
||||||
|
## nim c -r --path:../../common_libs tests/analyze_gun_stats.nim
|
||||||
|
## Output: /tmp/gun_performance_summary.txt
|
||||||
|
|
||||||
|
import std/[json, tables, strformat, strutils, sequtils, algorithm, os]
|
||||||
|
|
||||||
|
const GunNames = ["HeadOn","Linear","Tsetlin","Circular","GuessFactor","Pattern","AntiSurf","WallBounce","Accel","StopShot","Displace","AvgLead","DecayGF","KNN"]
|
||||||
|
|
||||||
|
type
|
||||||
|
GunStats = object
|
||||||
|
vShots, vHits, selected: int
|
||||||
|
|
||||||
|
EnemyStats = object
|
||||||
|
guns: array[14, GunStats]
|
||||||
|
realShots, realHits, rounds: int
|
||||||
|
name: string
|
||||||
|
|
||||||
|
var byEnemy: Table[string, EnemyStats]
|
||||||
|
var currentEnemy = "unknown"
|
||||||
|
|
||||||
|
if not fileExists("/tmp/gun_stats.jsonl"):
|
||||||
|
echo "No /tmp/gun_stats.jsonl found — run the gauntlet first."
|
||||||
|
quit(1)
|
||||||
|
|
||||||
|
for line in lines("/tmp/gun_stats.jsonl"):
|
||||||
|
let s = line.strip()
|
||||||
|
if s.len == 0: continue
|
||||||
|
let j = parseJson(s)
|
||||||
|
if j.hasKey("session_start"):
|
||||||
|
currentEnemy = j["session_start"].getStr()
|
||||||
|
if currentEnemy notin byEnemy:
|
||||||
|
byEnemy[currentEnemy] = EnemyStats(name: currentEnemy)
|
||||||
|
continue
|
||||||
|
if not j.hasKey("round"): continue
|
||||||
|
var es = addr byEnemy[currentEnemy]
|
||||||
|
inc es.rounds
|
||||||
|
es.realShots += j["realShots"].getInt()
|
||||||
|
es.realHits += j["realHits"].getInt()
|
||||||
|
for g in j["guns"].getElems():
|
||||||
|
let gid = g["id"].getInt()
|
||||||
|
es.guns[gid].vShots += g["vShots"].getInt()
|
||||||
|
es.guns[gid].vHits += g["vHits"].getInt()
|
||||||
|
es.guns[gid].selected += g["selected"].getInt()
|
||||||
|
|
||||||
|
var output: seq[string]
|
||||||
|
output.add "Gun Performance Summary"
|
||||||
|
output.add "Generated from /tmp/gun_stats.jsonl"
|
||||||
|
output.add ""
|
||||||
|
|
||||||
|
# Preserve insertion order via seq of names encountered
|
||||||
|
var enemyOrder: seq[string]
|
||||||
|
for line in lines("/tmp/gun_stats.jsonl"):
|
||||||
|
let s = line.strip()
|
||||||
|
if s.len == 0: continue
|
||||||
|
let j = parseJson(s)
|
||||||
|
if j.hasKey("session_start"):
|
||||||
|
let n = j["session_start"].getStr()
|
||||||
|
if n notin enemyOrder: enemyOrder.add n
|
||||||
|
|
||||||
|
for ename in enemyOrder:
|
||||||
|
if ename notin byEnemy: continue
|
||||||
|
let es = byEnemy[ename]
|
||||||
|
let realPct = if es.realShots > 0: es.realHits * 100 div es.realShots else: 0
|
||||||
|
output.add fmt"=== vs {es.name} ({es.rounds} rounds) ==="
|
||||||
|
output.add fmt" Real shots: {es.realShots} Real hits: {es.realHits} Real hit%: {realPct}%"
|
||||||
|
output.add ""
|
||||||
|
output.add " " & alignLeft("Gun", 14) & align("vShots", 8) & align("vHits", 7) & align("vHit%", 7) & align("Selected", 9)
|
||||||
|
output.add " " & "-".repeat(50)
|
||||||
|
var ranked = toSeq(0..<14)
|
||||||
|
ranked.sort(proc(a,b:int):int = es.guns[b].selected - es.guns[a].selected)
|
||||||
|
for gid in ranked:
|
||||||
|
let g = es.guns[gid]
|
||||||
|
let rate = if g.vShots > 0: g.vHits * 100 div g.vShots else: 0
|
||||||
|
output.add fmt" {GunNames[gid]:<14} {g.vShots:>8} {g.vHits:>7} {rate:>6}% {g.selected:>9}"
|
||||||
|
output.add ""
|
||||||
|
|
||||||
|
let summary = output.join("\n")
|
||||||
|
echo summary
|
||||||
|
writeFile("/tmp/gun_performance_summary.txt", summary)
|
||||||
|
echo "\nWrote /tmp/gun_performance_summary.txt"
|
||||||
@@ -9,7 +9,7 @@ const
|
|||||||
samplesDir = "/home/davide/Downloads/robocode-tankroyale/sample-bots-nim-linux-1.0.7"
|
samplesDir = "/home/davide/Downloads/robocode-tankroyale/sample-bots-nim-linux-1.0.7"
|
||||||
crazyDir = samplesDir / "Crazy"
|
crazyDir = samplesDir / "Crazy"
|
||||||
wallsDir = samplesDir / "Walls"
|
wallsDir = samplesDir / "Walls"
|
||||||
randomMoverDir = "/home/davide/Projects/SirRoboGarage/common_libs/test_framework/adversaries/RandomMover_garage"
|
randomMoverDir = "/home/davide/Projects/SirRoboGarage/common_libs/test_framework/adversaries/RandomMover"
|
||||||
|
|
||||||
if not fileExists("/home/davide/Projects/tank-royale/server/build/libs/robocode-tankroyale-server-0.35.5-all.jar") or
|
if not fileExists("/home/davide/Projects/tank-royale/server/build/libs/robocode-tankroyale-server-0.35.5-all.jar") or
|
||||||
not fileExists("/home/davide/Projects/tank-royale/runner/examples/lib/robocode-tankroyale-runner.jar"):
|
not fileExists("/home/davide/Projects/tank-royale/runner/examples/lib/robocode-tankroyale-runner.jar"):
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ const myBotDir = currentSourcePath().parentDir.parentDir
|
|||||||
let enemies = @[
|
let enemies = @[
|
||||||
("/home/davide/Downloads/robocode-tankroyale/sample-bots-nim-linux-1.0.7/Walls", "Walls"),
|
("/home/davide/Downloads/robocode-tankroyale/sample-bots-nim-linux-1.0.7/Walls", "Walls"),
|
||||||
("/home/davide/Downloads/robocode-tankroyale/sample-bots-nim-linux-1.0.7/SpinBot", "SpinBot"),
|
("/home/davide/Downloads/robocode-tankroyale/sample-bots-nim-linux-1.0.7/SpinBot", "SpinBot"),
|
||||||
("/home/davide/Projects/SirRoboGarage/common_libs/test_framework/adversaries/WaveSurfer_garage", "WaveSurfer"),
|
("/home/davide/Projects/SirRoboGarage/common_libs/test_framework/adversaries/WaveSurfer", "WaveSurfer"),
|
||||||
("/home/davide/Projects/SirRoboGarage/common_libs/test_framework/adversaries/PatternMover_garage", "PatternMover"),
|
("/home/davide/Projects/SirRoboGarage/common_libs/test_framework/adversaries/PatternMover", "PatternMover"),
|
||||||
("/home/davide/Downloads/robocode-tankroyale/sample-bots-nim-linux-1.0.7/TrackFire", "TrackFire"),
|
("/home/davide/Downloads/robocode-tankroyale/sample-bots-nim-linux-1.0.7/TrackFire", "TrackFire"),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import test_framework/test_framework
|
|||||||
const
|
const
|
||||||
modularBotDir = currentSourcePath().parentDir.parentDir
|
modularBotDir = currentSourcePath().parentDir.parentDir
|
||||||
samplesDir = "/home/davide/Downloads/robocode-tankroyale/sample-bots-nim-linux-1.0.7"
|
samplesDir = "/home/davide/Downloads/robocode-tankroyale/sample-bots-nim-linux-1.0.7"
|
||||||
waveSurferDir = "/home/davide/Projects/SirRoboGarage/common_libs/test_framework/adversaries/WaveSurfer_garage"
|
waveSurferDir = "/home/davide/Projects/SirRoboGarage/common_libs/test_framework/adversaries/WaveSurfer"
|
||||||
spinBotDir = samplesDir / "SpinBot"
|
spinBotDir = samplesDir / "SpinBot"
|
||||||
crazyDir = samplesDir / "Crazy"
|
crazyDir = samplesDir / "Crazy"
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,66 @@
|
|||||||
|
## Gun gauntlet: ModularBot vs all 5 adversaries, 10 rounds each.
|
||||||
|
## Dumps per-gun stats via ModularBot's onRoundEnded to /tmp/gun_stats.jsonl.
|
||||||
|
## nim c -r --path:../../common_libs tests/test_gauntlet_5bots.nim
|
||||||
|
|
||||||
|
import std/[os, strutils, strformat, tables]
|
||||||
|
import test_framework/server_manager
|
||||||
|
import test_framework/bot_compiler
|
||||||
|
import test_framework/runner_process
|
||||||
|
import test_framework/battle_result
|
||||||
|
|
||||||
|
const
|
||||||
|
modularBotDir = currentSourcePath().parentDir.parentDir
|
||||||
|
adversariesDir = "/home/davide/Projects/SirRoboGarage/common_libs/test_framework/adversaries"
|
||||||
|
serverJar = "/home/davide/Projects/tank-royale/server/build/libs/robocode-tankroyale-server-0.35.5-all.jar"
|
||||||
|
runnerJar = "/home/davide/Projects/tank-royale/runner/examples/lib/robocode-tankroyale-runner.jar"
|
||||||
|
|
||||||
|
if not fileExists(serverJar):
|
||||||
|
echo "SKIP: server JAR not found at " & serverJar
|
||||||
|
quit(0)
|
||||||
|
if not fileExists(runnerJar):
|
||||||
|
echo "SKIP: runner JAR not found at " & runnerJar
|
||||||
|
quit(0)
|
||||||
|
|
||||||
|
putEnv("TR_SERVER_JAR", serverJar)
|
||||||
|
putEnv("TR_BATTLE_RUNNER", runnerJar)
|
||||||
|
putEnv("TR_BATTLE_RUNNER_DIR", "/home/davide/Projects/SirRoboGarage/tools/battle_runner")
|
||||||
|
|
||||||
|
# Pre-compile ModularBot once
|
||||||
|
discard compileBots(@[modularBotDir])
|
||||||
|
echo "[gauntlet] ModularBot compiled OK"
|
||||||
|
|
||||||
|
proc runBattle(label, enemyName, enemyDir: string) =
|
||||||
|
let sep = "=".repeat(60)
|
||||||
|
echo "\n" & sep
|
||||||
|
echo " " & label
|
||||||
|
echo sep
|
||||||
|
let bots = @[modularBotDir, enemyDir]
|
||||||
|
discard compileBots(@[enemyDir]) # ensure adversary is compiled
|
||||||
|
# Write session marker so analysis can map rounds to enemy name
|
||||||
|
block:
|
||||||
|
let lf = open("/tmp/gun_stats.jsonl", fmAppend)
|
||||||
|
lf.writeLine("{\"session_start\":\"" & enemyName & "\"}")
|
||||||
|
lf.close()
|
||||||
|
let raw = runBattleRunner(getServerUrl(), bots, 10, 300_000, true)
|
||||||
|
let r = parseServerOutput(raw)
|
||||||
|
var mbScore = 0; var enScore = 0
|
||||||
|
for b in r.results:
|
||||||
|
if b.name == "ModularBot": mbScore = b.totalScore
|
||||||
|
elif b.name == enemyName: enScore = b.totalScore
|
||||||
|
let winner = if mbScore >= enScore: "ModularBot" else: enemyName
|
||||||
|
echo fmt" ModularBot: {mbScore} {enemyName}: {enScore} -> {winner} wins"
|
||||||
|
echo fmt" Rounds played: {r.rounds.len}"
|
||||||
|
|
||||||
|
# Write summary line to gauntlet log
|
||||||
|
let logLine = fmt"[{label}] ModularBot={mbScore} {enemyName}={enScore} winner={winner} rounds={r.rounds.len}"
|
||||||
|
let lf = open("/tmp/gun_gauntlet_results.log", fmAppend)
|
||||||
|
lf.writeLine(logLine)
|
||||||
|
lf.close()
|
||||||
|
|
||||||
|
ensureServer()
|
||||||
|
runBattle("Battle 1/5: vs SittingDuck", "SittingDuck", adversariesDir / "SittingDuck")
|
||||||
|
runBattle("Battle 2/5: vs OscillatorBot","OscillatorBot", adversariesDir / "OscillatorBot")
|
||||||
|
runBattle("Battle 3/5: vs RandomMover", "RandomMover", adversariesDir / "RandomMover")
|
||||||
|
runBattle("Battle 4/5: vs PatternMover", "PatternMover", adversariesDir / "PatternMover")
|
||||||
|
runBattle("Battle 5/5: vs WaveSurfer", "WaveSurfer", adversariesDir / "WaveSurfer")
|
||||||
|
echo "\nGauntlet complete. Stats in /tmp/gun_stats.jsonl and /tmp/gun_gauntlet_results.log"
|
||||||
@@ -10,7 +10,7 @@ import test_framework/battle_result
|
|||||||
const
|
const
|
||||||
modularBotDir = currentSourcePath().parentDir.parentDir
|
modularBotDir = currentSourcePath().parentDir.parentDir
|
||||||
samplesDir = "/home/davide/Downloads/robocode-tankroyale/sample-bots-nim-linux-1.0.7"
|
samplesDir = "/home/davide/Downloads/robocode-tankroyale/sample-bots-nim-linux-1.0.7"
|
||||||
waveSurferDir = "/home/davide/Projects/SirRoboGarage/common_libs/test_framework/adversaries/WaveSurfer_garage"
|
waveSurferDir = "/home/davide/Projects/SirRoboGarage/common_libs/test_framework/adversaries/WaveSurfer"
|
||||||
spinBotDir = samplesDir / "SpinBot"
|
spinBotDir = samplesDir / "SpinBot"
|
||||||
crazyDir = samplesDir / "Crazy"
|
crazyDir = samplesDir / "Crazy"
|
||||||
|
|
||||||
|
|||||||
@@ -17,8 +17,8 @@ let enemies = @[
|
|||||||
("Target", sampleBots / "Target"),
|
("Target", sampleBots / "Target"),
|
||||||
("SpinBot", sampleBots / "SpinBot"),
|
("SpinBot", sampleBots / "SpinBot"),
|
||||||
("Crazy", sampleBots / "Crazy"),
|
("Crazy", sampleBots / "Crazy"),
|
||||||
("PatternMover", "/home/davide/Projects/SirRoboGarage/common_libs/test_framework/adversaries/PatternMover_garage"),
|
("PatternMover", "/home/davide/Projects/SirRoboGarage/common_libs/test_framework/adversaries/PatternMover"),
|
||||||
("RandomMover", "/home/davide/Projects/SirRoboGarage/common_libs/test_framework/adversaries/RandomMover_garage"),
|
("RandomMover", "/home/davide/Projects/SirRoboGarage/common_libs/test_framework/adversaries/RandomMover"),
|
||||||
]
|
]
|
||||||
|
|
||||||
const logFile = "/tmp/modularbot_stdout.log"
|
const logFile = "/tmp/modularbot_stdout.log"
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import std/[os, unittest]
|
import std/[os, sequtils, unittest]
|
||||||
import test_framework/test_framework
|
import test_framework/test_framework
|
||||||
import test_framework/battle_result
|
import test_framework/battle_result
|
||||||
import test_framework/server_manager
|
import test_framework/server_manager
|
||||||
@@ -56,10 +56,11 @@ suite "ModularBot basic battle":
|
|||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
# Basic checks
|
# Basic checks
|
||||||
|
let botNames = r.results.mapIt(it.name)
|
||||||
check r.rounds.len == 1
|
check r.rounds.len == 1
|
||||||
check r.results.len == 2
|
check r.results.len == 2
|
||||||
check "ModularBot" in r.bots
|
check "ModularBot" in botNames
|
||||||
check "SittingDuck" in r.bots
|
check "SittingDuck" in botNames
|
||||||
|
|
||||||
# ModularBot should win
|
# ModularBot should win
|
||||||
var modularResult: BotResult
|
var modularResult: BotResult
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,247 @@
|
|||||||
|
# Gun Rack Analysis Report
|
||||||
|
|
||||||
|
**Date:** 2026-09-20
|
||||||
|
**Bot:** ModularBot (14 guns)
|
||||||
|
**Data source:** `/tmp/gun_stats.jsonl`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Test Methodology
|
||||||
|
|
||||||
|
- **Adversaries:** 5 bots, 10 rounds each (OscillatorBot: 9 rounds — one round lost to disconnect)
|
||||||
|
- **Format:** 1v1, score accumulated across rounds
|
||||||
|
- **Virtual bullet system:** 4 power bins (1.0, 1.5, 2.0, 3.0), 100-tick rolling window per gun per bin
|
||||||
|
- **Gun selection rule:** highest virtual hit rate, minimum 15 observations before a gun is eligible to compete
|
||||||
|
- **Stats written to:** `/tmp/gun_stats.jsonl` (one JSON line per round, prefixed by `session_start` markers)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Adversary Profiles
|
||||||
|
|
||||||
|
| Adversary | Movement | What it tests |
|
||||||
|
|-----------|----------|---------------|
|
||||||
|
| **SittingDuck** | Stationary | Baseline ceiling — any gun should near-100%; exposes broken guns |
|
||||||
|
| **OscillatorBot** | Periodic side-to-side oscillation | Predictable but timed — rewards guns that track phase, punishes naive straight-line |
|
||||||
|
| **RandomMover** | Uniform random heading changes | Stochastic coverage — tests gun generality under noise |
|
||||||
|
| **PatternMover** | Repeating movement macro | Tests pattern/memory guns; predictable once pattern is locked |
|
||||||
|
| **WaveSurfer** | Surfing (dodges incoming bullets reactively) | Most realistic; tests guns that account for dodge response |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Results Summary
|
||||||
|
|
||||||
|
| Adversary | ModularBot Score | Real Hits / Shots | Real Hit% |
|
||||||
|
|-----------|-----------------|-------------------|-----------|
|
||||||
|
| SittingDuck | 1944 | 69 / 81 | **85%** |
|
||||||
|
| OscillatorBot | 1645 | 100 / 170 | **59%** |
|
||||||
|
| RandomMover | 1916 | 86 / 142 | **61%** |
|
||||||
|
| PatternMover | 1914 | 80 / 109 | **73%** |
|
||||||
|
| WaveSurfer | 1873 | 76 / 105 | **72%** |
|
||||||
|
|
||||||
|
> **Note:** Real hit% was dead during these tests due to the `onBulletHitBot` bug (see Bugs section). Numbers reflect post-fix tracking where the callback fired correctly, but per-gun real-hit attribution was not collected — only aggregate real hits per round are reliable.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Per-Gun Virtual Hit Rate Matrix
|
||||||
|
|
||||||
|
Average virtual hit rate (%) across all rounds per adversary. **Bold** = best gun for that adversary. Selection count (total ticks gun was active) shown in parentheses.
|
||||||
|
|
||||||
|
| Gun | SittingDuck | OscillatorBot | RandomMover | PatternMover | WaveSurfer |
|
||||||
|
|-----|-------------|---------------|-------------|--------------|------------|
|
||||||
|
| **HeadOn** | 98% (1470) | 63% (664) | 79% (1010) | **95%** (1188) | 84% (1215) |
|
||||||
|
| **Linear** | 98% (0) | 63% (552) | **82%** (251) | 95% (27) | 84% (13) |
|
||||||
|
| **Tsetlin** | 98% (0) | 63% (0) | **82%** (0) | 95% (0) | 84% (0) |
|
||||||
|
| **Circular** | 98% (0) | 63% (3) | 80% (350) | 95% (70) | 84% (137) |
|
||||||
|
| **GuessFactor** | 98% (0) | 63% (0) | 79% (100) | 94% (0) | 84% (0) |
|
||||||
|
| **Pattern** | 98% (0) | 9% **(1081)** | 17% (254) | 95% (0) | 84% (77) |
|
||||||
|
| **AntiSurf** | 0% (0) | 0% (147) | 2% (0) | 0% (19) | 0% (0) |
|
||||||
|
| **WallBounce** | **99%** (68) | 64% (82) | **82%** (215) | 95% (211) | 85% (51) |
|
||||||
|
| **Accel** | **99%** (0) | 63% (281) | 19% (152) | 96% (24) | **86%** (255) |
|
||||||
|
| **StopShot** | **99%** (0) | 64% (1) | **82%** (1) | 96% (6) | **86%** (1) |
|
||||||
|
| **Displace** | **99%** (0) | 63% (5) | 81% (55) | **96%** (243) | **86%** (10) |
|
||||||
|
| **AvgLead** | **99%** (0) | **65%** (105) | **82%** (110) | **96%** (107) | **86%** (97) |
|
||||||
|
| **DecayGF** | **99%** (0) | 63% (0) | 79% (1) | **96%** (20) | **86%** (30) |
|
||||||
|
| **KNN** | **99%** (0) | 63% (0) | 79% (1) | **96%** (50) | 85% (24) |
|
||||||
|
|
||||||
|
**Best gun per adversary:**
|
||||||
|
- SittingDuck → WallBounce / Accel / StopShot / Displace / AvgLead / DecayGF / KNN (all tied at 99%)
|
||||||
|
- OscillatorBot → **AvgLead** (65%)
|
||||||
|
- RandomMover → **Linear / Tsetlin / WallBounce / StopShot / AvgLead** (tied at 82%)
|
||||||
|
- PatternMover → **Displace / Accel / StopShot / DecayGF / AvgLead / KNN** (tied at 96%)
|
||||||
|
- WaveSurfer → **StopShot / DecayGF / AvgLead / Accel / Displace** (tied at 86%)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Gun-by-Gun Analysis
|
||||||
|
|
||||||
|
### HeadOn
|
||||||
|
**What it does:** Fires directly at current enemy position — no lead, no prediction.
|
||||||
|
- **Strengths:** SittingDuck (98%), PatternMover (95%), WaveSurfer (84%). Competent baseline everywhere.
|
||||||
|
- **Weaknesses:** No lead — loses to faster movers once Random/Oscillator velocity matters. Matched or beaten by 8+ other guns on every adversary except SittingDuck.
|
||||||
|
- **Selection bias:** Receives the most ticks in every matchup (664–1470), consistently crowding out marginally better guns. This is a selector problem, not a gun problem.
|
||||||
|
- **Verdict:** KEEP — but its selection dominance needs addressing. It is a reliable floor, not the ceiling.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Linear
|
||||||
|
**What it does:** Straight-line lead — assumes constant velocity from last observed heading.
|
||||||
|
- **Strengths:** RandomMover (82%, tied best), WaveSurfer (84%), PatternMover (95%).
|
||||||
|
- **Weaknesses:** OscillatorBot (63%, same as HeadOn) — oscillation breaks the constant-velocity assumption.
|
||||||
|
- **Verdict:** KEEP — genuinely better than HeadOn on random movers. Gets 0 selection ticks on most matchups despite competitive rates; underutilised.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Tsetlin
|
||||||
|
**What it does:** Tsetlin Machine classifier for aim prediction (binary learning automaton).
|
||||||
|
- **Strengths:** Identical rates to Linear across all adversaries (82% Random, 63% Oscillator, 95% Pattern, 84% Wave, 98% Sitting).
|
||||||
|
- **Weaknesses:** Gets 0 selection ticks in every single matchup — never selected once. Either never crosses the 15-observation threshold, or its virtual bullets are being computed identically to Linear.
|
||||||
|
- **Verdict:** TUNE — investigate why selection count is always 0. If the virtual hit computation is a clone of another gun's trajectory, the gun is effectively dead weight. Confirm it fires unique virtual bullets.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Circular
|
||||||
|
**What it does:** Circular lead — assumes constant angular velocity (orbit).
|
||||||
|
- **Strengths:** RandomMover (80%), decent on most adversaries.
|
||||||
|
- **Weaknesses:** Lags behind Linear/WallBounce on Random; barely competitive elsewhere.
|
||||||
|
- **Verdict:** KEEP (marginal) — distinct from Linear (angular vs linear velocity assumption), worth keeping for orbit-heavy bots. Gets real selection only on RandomMover (350 ticks) and WaveSurfer (137).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### GuessFactor
|
||||||
|
**What it does:** Guess-factor segmentation gun — divides lateral movement into bins and tracks historical hit density.
|
||||||
|
- **Strengths:** SittingDuck 98%, generally solid floor across all enemies.
|
||||||
|
- **Weaknesses:** Never edges above HeadOn on any adversary; OscillatorBot 63% (same floor). Needs history to warm up — may lag early rounds.
|
||||||
|
- **Selection:** Modest (100 RandomMover, 0 elsewhere). Learning gun that never became dominant.
|
||||||
|
- **Verdict:** KEEP — GF guns are the backbone of competitive targeting. Gets outperformed by AvgLead/DecayGF variants — investigate whether GF segmentation resolution needs tuning.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Pattern
|
||||||
|
**What it does:** Movement pattern matching — records enemy movement sequences and replays prediction.
|
||||||
|
- **Strengths:** SittingDuck 98%, PatternMover 95%, WaveSurfer 84% (when selected).
|
||||||
|
- **Weaknesses:** **OscillatorBot 9%** — catastrophically wrong; 1081 selection ticks wasted there. RandomMover 17% — random movement breaks pattern replay entirely.
|
||||||
|
- **Critical bug:** The selector gave Pattern 1081 ticks vs OscillatorBot despite a 9% rate. This means `MinObsBeforeCompete` let it compete before it had enough data to show a representative rate, or the rolling window allowed a lucky early patch to lock in selection.
|
||||||
|
- **Verdict:** TUNE — the gun itself is sound against true pattern movers; the problem is premature selection. Raise `MinObsBeforeCompete` or add a "minimum rounds active" gate before Pattern can dominate the selector.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### AntiSurf
|
||||||
|
**What it does:** Anti-surfing gun — attempts to predict dodge direction by modelling bullet-wave response.
|
||||||
|
- **Strengths:** None observed. 0% virtual hit rate against every adversary in every round.
|
||||||
|
- **Weaknesses:** Broken or misconfigured — 0% is not a statistical artifact; it is a systematic failure. Scored 147 selection ticks vs OscillatorBot and 19 vs PatternMover despite 0% rate.
|
||||||
|
- **Verdict:** DROP or fix before retest. 0% vhit means virtual bullets are being fired in the wrong direction entirely. The selection ticks it received represent rounds where it was preferred over functional guns — a liability.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### WallBounce
|
||||||
|
**What it does:** Predicts enemy movement reflecting off walls.
|
||||||
|
- **Strengths:** SittingDuck 99% (joint best), RandomMover 82% (tied best), OscillatorBot 64%, PatternMover 95%, WaveSurfer 85%.
|
||||||
|
- **Weaknesses:** None significant — consistently top-tier across all adversary types.
|
||||||
|
- **Selection:** Gets real ticks on SittingDuck (68), OscillatorBot (82), RandomMover (215), PatternMover (211), WaveSurfer (51) — the selector does pick it when scores differentiate.
|
||||||
|
- **Verdict:** KEEP — one of the most consistent guns in the rack. Wall-aware prediction generalises well.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Accel
|
||||||
|
**What it does:** Acceleration-based lead — accounts for changing velocity.
|
||||||
|
- **Strengths:** SittingDuck 99%, PatternMover 96%, WaveSurfer 86% (joint best). Strong against momentum-based movers.
|
||||||
|
- **Weaknesses:** **RandomMover 19%** — severe drop. Random heading changes invalidate acceleration extrapolation completely.
|
||||||
|
- **Selection:** 281 ticks OscillatorBot, 255 WaveSurfer, 152 RandomMover. Gets selection time on Oscillator despite 63% (same as HeadOn) — selector noise.
|
||||||
|
- **Verdict:** KEEP but note the RandomMover cliff. The 19% rate while receiving 152 selection ticks vs Random is wasteful — the selector should have dropped it faster.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### StopShot
|
||||||
|
**What it does:** Fires at the predicted stop point — targets deceleration patterns.
|
||||||
|
- **Strengths:** WaveSurfer 86% (joint best), PatternMover 96%, SittingDuck 99%, RandomMover 82%. Broad competence.
|
||||||
|
- **Weaknesses:** Gets almost zero selection despite top-tier rates (1 tick on most matchups). Under-selected almost everywhere.
|
||||||
|
- **Verdict:** KEEP — likely under-selected because it ties with AvgLead but has fewer early observations. Its consistent top performance suggests it should get more selection time.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Displace
|
||||||
|
**What it does:** Displacement-based prediction — fires at position + expected displacement vector.
|
||||||
|
- **Strengths:** PatternMover 96% (joint best), WaveSurfer 86%, SittingDuck 99%.
|
||||||
|
- **Weaknesses:** RandomMover 81% (slightly below Linear/WallBounce).
|
||||||
|
- **Selection:** Gets decent ticks on PatternMover (243) — the selector finds it there.
|
||||||
|
- **Verdict:** KEEP — solid and well-selected where it's strong.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### AvgLead
|
||||||
|
**What it does:** Averaged lead angle over a window — smoothed velocity prediction.
|
||||||
|
- **Strengths:** **OscillatorBot 65% (best gun)**, WaveSurfer 86% (joint best), PatternMover 96%, RandomMover 82%, SittingDuck 99%.
|
||||||
|
- **Weaknesses:** None — top or joint-top across all five adversaries.
|
||||||
|
- **Selection:** Gets consistent moderate ticks everywhere (97–110), indicating the selector finds it reliable.
|
||||||
|
- **Verdict:** KEEP — the most consistently high-performing gun in the rack. If only one gun were kept, this would be it.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### DecayGF
|
||||||
|
**What it does:** Guess-factor with exponential decay — recent history weighted more heavily.
|
||||||
|
- **Strengths:** WaveSurfer 86% (joint best), PatternMover 96%, SittingDuck 99%.
|
||||||
|
- **Weaknesses:** OscillatorBot 63% (same as HeadOn floor). Needs time to build a useful GF profile.
|
||||||
|
- **Selection:** Gets 30 ticks on WaveSurfer, 20 on PatternMover — modest but real.
|
||||||
|
- **Verdict:** KEEP — the decay weighting should help against adapting movers; verify the GF profile is being updated correctly.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### KNN
|
||||||
|
**What it does:** K-nearest neighbours targeting — finds historical situations most similar to current and fires the historically best angle.
|
||||||
|
- **Strengths:** PatternMover 96% (joint best), WaveSurfer 85%, SittingDuck 99%.
|
||||||
|
- **Weaknesses:** OscillatorBot 63% (floor), needs history to warm up.
|
||||||
|
- **Selection:** 50 PatternMover, 24 WaveSurfer — gets some selection where it performs.
|
||||||
|
- **Verdict:** KEEP — KNN is a sound approach that should improve as history accumulates across rounds. Cold-start performance is expected to be weak.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Recommendations
|
||||||
|
|
||||||
|
### 1. Guns to DROP
|
||||||
|
|
||||||
|
| Gun | Reason |
|
||||||
|
|-----|--------|
|
||||||
|
| **AntiSurf** | 0% virtual hit rate against all adversaries. Not statistically bad — systematically broken. Received selection ticks on two matchups despite never hitting anything. Fix or remove; as-is it is pure negative value. |
|
||||||
|
|
||||||
|
### 2. Guns to TUNE
|
||||||
|
|
||||||
|
| Gun | What to change |
|
||||||
|
|-----|---------------|
|
||||||
|
| **Pattern** | Raise `MinObsBeforeCompete` significantly (suggest ≥ 50 obs). Add a round-count gate: Pattern should not be eligible to dominate the selector until it has fired virtual bullets for at least 2 full rounds. The 1081-tick / 9% disaster vs OscillatorBot is entirely a selector gate failure. |
|
||||||
|
| **Tsetlin** | Investigate zero selection count. Check that virtual bullets are computed with a unique angle (distinct from Linear). If it is accidentally sharing a firing angle with another gun, it will never differentiate. |
|
||||||
|
| **Accel** | Its 19% vs RandomMover while receiving 152 selection ticks is wasteful. The rolling window should surface this faster — check if the window length (100 ticks) is too long for Accel's decay signal to flush bad data quickly. |
|
||||||
|
| **GuessFactor** | Investigate why it never edges above HeadOn. GF is a proven algorithm — if it is not beating HeadOn after 10 rounds vs PatternMover or WaveSurfer, the bin resolution or lateral-velocity calculation may be wrong. |
|
||||||
|
|
||||||
|
### 3. Selector Improvements
|
||||||
|
|
||||||
|
- **Pattern over-selection:** `MinObsBeforeCompete = 15` is too low for a memory gun. Pattern needs hundreds of ticks to build a meaningful database. Gate it at 100+ observations or require a minimum database size before competing.
|
||||||
|
- **HeadOn selection dominance:** HeadOn gets 1010–1470 ticks per matchup while marginally better guns (AvgLead, WallBounce) get 50–250. The selector may be hitting HeadOn early when all guns are equal, then staying there due to recency bias. Consider a uniform-random tiebreak when rates are within 1–2%.
|
||||||
|
- **Accel vs Random:** 152 wasted ticks at 19% vhit. The 100-tick rolling window should expose this; check if the window resets between rounds or carries over (if it carries over from a good round, bad-round data is diluted).
|
||||||
|
|
||||||
|
### 4. Missing Coverage
|
||||||
|
|
||||||
|
- **True surfer with timing randomness:** WaveSurfer is the closest to real tournament surfing bots, but AvgLead/StopShot/Accel all hit 86%. Consider testing against a stronger surfer variant to stress-test wave-aware guns.
|
||||||
|
- **Fast random with perpendicular escape:** Accel's 19% on RandomMover suggests fast lateral movers are poorly handled. No gun clearly dominates RandomMover — the 82% ceiling is shared by four guns, none clearly specialised.
|
||||||
|
- **Anti-mirror bots:** No adversary tests symmetric evasion. If AntiSurf is ever fixed, it needs a proper test target.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Bugs Found During Testing
|
||||||
|
|
||||||
|
### 1. `onBulletHitBot` → `onBulletHit` (critical)
|
||||||
|
|
||||||
|
**Location:** ModularBot event handler registration.
|
||||||
|
**Problem:** The callback method was named `onBulletHitBot` but the Robocode API fires `onBulletHit`. Real hit tracking was silently dead — the callback never fired, so `realHits` in the gun stats relied on whatever fallback was in place.
|
||||||
|
**Impact:** Per-gun real hit rates could not be attributed correctly. The `realHits` field in the JSONL reflects aggregate post-fix behaviour but per-gun real-hit breakdown was unavailable for this gauntlet. Virtual hit rates remain valid (they are computed independently of the event callback).
|
||||||
|
**Fix:** Rename to `onBulletHit`. Already corrected; re-run the gauntlet to get clean real-hit attribution per gun.
|
||||||
|
|
||||||
|
### 2. AntiSurf 0% vhit (potential implementation bug)
|
||||||
|
|
||||||
|
**Observed:** AntiSurf returns 0 virtual hits against every adversary including SittingDuck. SittingDuck does not move — any gun aimed at the current position should achieve near-100%.
|
||||||
|
**Implication:** AntiSurf is not firing virtual bullets at the target position at all, or its angle computation produces NaN/wrong quadrant. This is a code bug, not a tuning issue.
|
||||||
|
**Suggested check:** Print the computed firing angle for AntiSurf virtual bullets and verify it is within ±π/2 of the actual bearing to target.
|
||||||
|
|
||||||
|
### 3. Pattern gun gets selected during initial warm-up
|
||||||
|
|
||||||
|
**Observed:** Pattern accumulates 1081 selection ticks vs OscillatorBot at 9% vhit. With `MinObsBeforeCompete = 15`, it became eligible before its pattern database was meaningful, won a random tiebreak at 100% during early ticks (before any miss was recorded), then the rolling window retained that optimistic early rate too long.
|
||||||
|
**Fix:** See Recommendations §3.
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
# Gun Rack Summary — TL;DR
|
||||||
|
|
||||||
|
**Gauntlet:** 5 adversaries × 10 rounds, 14 guns. Data: `/tmp/gun_stats.jsonl`
|
||||||
|
|
||||||
|
## Scoreboard
|
||||||
|
|
||||||
|
| Adversary | Score | Real Hit% | Best Gun |
|
||||||
|
|-----------|-------|-----------|----------|
|
||||||
|
| SittingDuck | 1944 | 85% | AvgLead / WallBounce (99% vhit) |
|
||||||
|
| OscillatorBot | 1645 | 59% | AvgLead (65% vhit) |
|
||||||
|
| RandomMover | 1916 | 61% | Linear / WallBounce / AvgLead (82%) |
|
||||||
|
| PatternMover | 1914 | 73% | AvgLead / Displace / Accel (96%) |
|
||||||
|
| WaveSurfer | 1873 | 72% | AvgLead / StopShot / Accel (86%) |
|
||||||
|
|
||||||
|
## Verdict Table
|
||||||
|
|
||||||
|
| Gun | Best Use | Verdict |
|
||||||
|
|-----|----------|---------|
|
||||||
|
| **HeadOn** | Stationary / pattern bots | KEEP — reliable floor, over-selected |
|
||||||
|
| **Linear** | Random movers | KEEP — under-selected despite 82% |
|
||||||
|
| **Tsetlin** | Unknown | TUNE — 0 selection ticks ever; investigate |
|
||||||
|
| **Circular** | Orbit-heavy bots | KEEP (marginal) |
|
||||||
|
| **GuessFactor** | General | KEEP — underperforms vs expectation; tune bins |
|
||||||
|
| **Pattern** | Pattern movers | TUNE — catastrophic vs OscillatorBot (9%, 1081 ticks); raise MinObs gate |
|
||||||
|
| **AntiSurf** | — | DROP — 0% vhit every adversary including stationary; broken |
|
||||||
|
| **WallBounce** | All types | KEEP — most consistent overall |
|
||||||
|
| **Accel** | Pattern / wave bots | KEEP — avoid vs random (19% cliff) |
|
||||||
|
| **StopShot** | All types | KEEP — top rates, chronically under-selected |
|
||||||
|
| **Displace** | Pattern / wave bots | KEEP |
|
||||||
|
| **AvgLead** | All types | KEEP — best all-rounder in the rack |
|
||||||
|
| **DecayGF** | Wave / pattern bots | KEEP |
|
||||||
|
| **KNN** | Pattern / wave bots | KEEP — improves with history |
|
||||||
|
|
||||||
|
## Top Actions
|
||||||
|
|
||||||
|
1. **Fix AntiSurf** — 0% vhit against SittingDuck means wrong angle computation, not just weak targeting.
|
||||||
|
2. **Raise Pattern's MinObsBeforeCompete** — 15 is too low; 1081 wasted ticks at 9% hit rate vs OscillatorBot.
|
||||||
|
3. **Re-run gauntlet** with `onBulletHit` fix to get clean per-gun real hit attribution.
|
||||||
|
4. **AvgLead** is the star gun — confirm it stays in the top selection tier.
|
||||||
|
5. **HeadOn selection dominance** is a selector bias problem, not a gun problem — add ±2% tiebreak randomisation.
|
||||||
@@ -5,8 +5,8 @@ const
|
|||||||
modularBotDir = "/home/davide/Projects/SirRoboGarage/ModularBot_garage"
|
modularBotDir = "/home/davide/Projects/SirRoboGarage/ModularBot_garage"
|
||||||
samplesDir = "/home/davide/Downloads/robocode-tankroyale/sample-bots-nim-linux-1.0.7"
|
samplesDir = "/home/davide/Downloads/robocode-tankroyale/sample-bots-nim-linux-1.0.7"
|
||||||
crazy = samplesDir / "Crazy"
|
crazy = samplesDir / "Crazy"
|
||||||
waveSurfer = "/home/davide/Projects/SirRoboGarage/common_libs/test_framework/adversaries/WaveSurfer_garage"
|
waveSurfer = "/home/davide/Projects/SirRoboGarage/common_libs/test_framework/adversaries/WaveSurfer"
|
||||||
randomMover = "/home/davide/Projects/SirRoboGarage/common_libs/test_framework/adversaries/RandomMover_garage"
|
randomMover = "/home/davide/Projects/SirRoboGarage/common_libs/test_framework/adversaries/RandomMover"
|
||||||
|
|
||||||
const serverJar = "/home/davide/Projects/tank-royale/server/build/libs/robocode-tankroyale-server-0.35.5-all.jar"
|
const serverJar = "/home/davide/Projects/tank-royale/server/build/libs/robocode-tankroyale-server-0.35.5-all.jar"
|
||||||
const runnerJar = "/home/davide/Projects/tank-royale/runner/examples/lib/robocode-tankroyale-runner.jar"
|
const runnerJar = "/home/davide/Projects/tank-royale/runner/examples/lib/robocode-tankroyale-runner.jar"
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ const
|
|||||||
modularBotDir = "/home/davide/Projects/SirRoboGarage/ModularBot_garage"
|
modularBotDir = "/home/davide/Projects/SirRoboGarage/ModularBot_garage"
|
||||||
samplesDir = "/home/davide/Downloads/robocode-tankroyale/sample-bots-nim-linux-1.0.7"
|
samplesDir = "/home/davide/Downloads/robocode-tankroyale/sample-bots-nim-linux-1.0.7"
|
||||||
crazy = samplesDir / "Crazy"
|
crazy = samplesDir / "Crazy"
|
||||||
waveSurfer = "/home/davide/Projects/SirRoboGarage/common_libs/test_framework/adversaries/WaveSurfer_garage"
|
waveSurfer = "/home/davide/Projects/SirRoboGarage/common_libs/test_framework/adversaries/WaveSurfer"
|
||||||
patternMover = "/home/davide/Projects/SirRoboGarage/common_libs/test_framework/adversaries/PatternMover_garage"
|
patternMover = "/home/davide/Projects/SirRoboGarage/common_libs/test_framework/adversaries/PatternMover"
|
||||||
|
|
||||||
const serverJar = "/home/davide/Projects/tank-royale/server/build/libs/robocode-tankroyale-server-0.35.5-all.jar"
|
const serverJar = "/home/davide/Projects/tank-royale/server/build/libs/robocode-tankroyale-server-0.35.5-all.jar"
|
||||||
const runnerJar = "/home/davide/Projects/tank-royale/runner/examples/lib/robocode-tankroyale-runner.jar"
|
const runnerJar = "/home/davide/Projects/tank-royale/runner/examples/lib/robocode-tankroyale-runner.jar"
|
||||||
|
|||||||
Reference in New Issue
Block a user