29 Commits

Author SHA1 Message Date
SirStone d3e4540e7f chore: add missing OscillatorBot.nimble 2026-08-27 18:42:35 +02:00
SirStone 259e67d7ff chore: standardize internal bot dir structure (src/, tests/, out/)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-08-27 18:42:03 +02:00
SirStone c5115fdf61 chore: standardize Nim builds to out/ subdir, simplify .gitignore
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-08-27 18:35:54 +02:00
SirStone e276548223 chore: remove exported/ from AGENTS.md, generalize binary ignores in .gitignore
- Removed non-existent exported/ directory from layout docs
- Replaced hardcoded bot binary entries with *_garage/out/ pattern for build directories
- Added all known bot binaries (GotoTest, OscillatorBot, PPO_Bot, QBot, SAC_LSTM_Bot)
  to .gitignore with clarifying comment about Nim's compilation target structure

Nim places compiled binaries at bot root (no extension) and in out/ subdirs.
The out/ pattern catches all build outputs; specific bot binaries listed for
root-level executables since gitignore lacks a reliable "no-extension files" glob.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-08-27 18:32:09 +02:00
SirStone 2097c2e6fa chore: gitignore binaries/artifacts, remove drafts and stale files 2026-08-27 18:28:21 +02:00
SirStone 06ed905033 docs(AGENTS): generalize layout rules, remove hardcoded bot list 2026-08-27 18:22:38 +02:00
SirStone eab85be8b3 docs(AGENTS): update directory layout to match current structure 2026-08-27 18:19:37 +02:00
SirStone b509195ee9 chore: rename libs→common_libs, all bot dirs to _garage suffix, fix all path refs
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-08-27 18:18:41 +02:00
SirStone f8c0c871c6 chore: remove unused prototypes/ and spike/ dirs 2026-08-27 18:16:34 +02:00
SirStone b9877bfff5 chore: add .gitignore 2026-08-27 18:12:08 +02:00
SirStone 8ba4bae21a chore: rename CLAUDE.md → AGENTS.md, move research doc to docs/ 2026-08-27 18:11:56 +02:00
SirStone f5b1ade48c fix(dashboard): harden generation against common failures 2026-08-24 11:10:39 +02:00
SirStone 12a0eca44a docs(dashboard): add health-check reference section 2026-08-24 10:18:43 +02:00
SirStone 08d1c44ca8 docs(dashboard): add how-to-read guide per panel 2026-08-24 09:25:14 +02:00
SirStone f47925eeb0 fix(dashboard): remove redundant standalone alpha panel 2026-08-24 09:22:54 +02:00
SirStone 55d69a9352 fix(dashboard): dual y-axis for losses+alpha panel 2026-08-24 09:21:43 +02:00
SirStone 4a6e3347c6 feat(dashboard): max-score-per-eval-cycle panel 2026-08-24 09:15:24 +02:00
SirStone 8872be3ff6 fix(dashboard): add alpha temperature to training losses panel 2026-08-24 09:10:46 +02:00
SirStone 8eb7c53dd0 fix(dashboard): remove broken unused win%-per-opponent panel 2026-08-24 09:05:41 +02:00
SirStone 51e7f33ecb fix(dashboard): plot live campaign-4 logs; derive ROOT from script location 2026-08-24 08:30:04 +02:00
SirStone d2d205e6f6 Merge branch 'research/ga-parameters' into research/goto-controller 2026-08-24 08:23:25 +02:00
SirStone 3851f28cc5 Merge branch 'worktree-agent-a556517b' into research/goto-controller 2026-08-24 08:23:22 +02:00
SirStone 6241c41e52 Merge branch 'worktree-agent-a4c06ae3' into research/goto-controller 2026-08-24 08:23:19 +02:00
SirStone 156b4ae8db Merge branch 'worktree-agent-af671e69' into research/goto-controller 2026-08-24 08:23:12 +02:00
SirStone eee48dec38 prototype: GA evolution spike -- sin(x) prediction validates pipeline (#66)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-08-24 08:14:36 +02:00
SirStone eb8faa7c99 prototype: GA evolution spike -- sin(x) prediction validates pipeline (#66)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-08-24 08:14:32 +02:00
SirStone cb1bbc35dc docs(adr): Evo_Bot neuroevolution gun architecture (#63)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-08-24 00:02:47 +02:00
SirStone b7b10af811 feat: OscillatorBot sparring partner for GA gun testing (#64)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-08-24 00:01:38 +02:00
SirStone e455566c8e docs: CONTEXT.md — Evo_Bot domain vocabulary (#62)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-08-24 00:00:21 +02:00
849 changed files with 5711 additions and 1451 deletions
+36
View File
@@ -0,0 +1,36 @@
# Compiled output
*.out
# Python cache
__pycache__/
*.pyc
*.pyo
# Log files
*.log
# Training logs
*.jsonl
# Test binaries (but keep .nim source files)
**/tests/test_*
!**/tests/test_*.nim
!**/tests/test_*.nims
# User-specific dev environment
.envrc
devbox.json
devbox.lock
# Compiled bot binaries — all builds go to *_garage/out/
*_garage/out/
# Java compiled classes
*.class
# Log and snapshot directories
**/logs/
**/snapshots/
# Git worktrees
worktrees/
+47
View File
@@ -0,0 +1,47 @@
## Agent skills
### Issue tracker
Gitea Issues at git.fossellini.top:SirStone/SirRoboGarage. See `docs/agents/issue-tracker.md`.
### Triage labels
Default label vocabulary. See `docs/agents/triage-labels.md`.
### Domain docs
Single-context layout. See `docs/agents/domain.md`.
### Folders
## SirGarage Directory Layout
**SirGarage** (root directory)
- `botname_garage/` — each bot gets its own directory following this pattern
- src/
- botname.nim
- botname.json
- botname.sh
- config.nim
- botname/ — bot-specific modules
- *.nim files
- tests/
- test*.nim files
- out/
- compiled binaries
- prototypes or spikes for this bot (not at root)
- docs/ — generic project documentation
- tools/ — generic tools valid for all bots (battle runners, scripts, etc.)
- common_libs/ — shared libraries valid for all bots
- worktrees/ — git worktrees (gitignored)
- AGENTS.md
- CONTEXT.md
- README.md
- .gitignore
## How to use
- These indications are for files that are always present, for files not mentioned here should be claer from context where belongs too.
- Each bot development must be done in each folder in isolation.
- If the bot requires extra folders during runtime, these must be created by the bot itself if not present already.
-13
View File
@@ -1,13 +0,0 @@
## Agent skills
### Issue tracker
Gitea Issues at git.fossellini.top:SirStone/SirRoboGarage. See `docs/agents/issue-tracker.md`.
### Triage labels
Default label vocabulary. See `docs/agents/triage-labels.md`.
### Domain docs
Single-context layout. See `docs/agents/domain.md`.
+59
View File
@@ -0,0 +1,59 @@
# Evo_Bot
Robocode Tank Royale bot with a modular gun system where evolved neural networks learn to predict enemy dodge behavior.
## Language
**Evo_Bot**:
The bot itself — handles movement and firing discipline. Guns are pluggable modules.
_Avoid_: robot, tank
**Guess Factor (GF)**:
A value from -1 to +1 representing where on the maximum escape angle arc the enemy is. 0 = directly ahead, -1 = full left dodge, +1 = full right dodge. The gun's prediction target.
_Avoid_: aim offset, dodge index
**Max Escape Angle (MEA)**:
The widest angle the enemy can reach before a bullet arrives, computed from distance and bullet speed. Guess factor is multiplied by MEA to get the aim offset.
**Lateral Velocity**:
Enemy speed projected perpendicular to the line between you and them. The primary signal for guess factor prediction.
_Avoid_: tangential speed, sideways velocity
**Sliding Window**:
The last N ticks (default 30) of enemy state fed as input to the network. Each tick contains lateral velocity, heading delta, and wall distance ahead.
_Avoid_: observation buffer, input history
**Replay Tape**:
Rolling buffer of recorded enemy states (~2000 ticks). The evolution thread evaluates gun fitness against this tape.
_Avoid_: experience buffer, replay buffer
**Virtual Gun**:
A gun that runs in parallel without actually firing. It tracks where it would have aimed and whether a simulated bullet would have hit. Used to compare gun variants.
**Virtual Bullet**:
A simulated bullet fired by a virtual gun. Never actually sent to the game engine.
**TOPO_Gun**:
Fixed-topology ANN gun evolved by GA. Network shape is predetermined (e.g., 91-8-1), only weights are evolved.
_Avoid_: static gun, fixed gun
**NEAT_Gun**:
Variable-topology ANN gun where evolution can add/remove neurons and connections (NEAT algorithm). Deferred — only built if TOPO_Gun hits a ceiling.
**Population**:
The set of candidate networks (default 64-200) being evolved. Each member is a complete set of ANN weights.
**Champion**:
The best-performing network in the current GA population. The champion's weights are pushed to the inference side when it beats the current best.
_Avoid_: best, winner, elite
**Fitness**:
Hit count when a network's aim predictions are evaluated as virtual bullets against sampled ticks from the replay tape.
_Avoid_: score, reward
**Cold Start**:
The first-ever battle with no saved weights. The gun does not fire until the evolution thread produces its first champion. Subsequent battles load persisted weights.
**Weight Persistence**:
Saving evolved weights to disk. Load order: per-opponent file, then global fallback, then random initialization.
_Avoid_: model saving, checkpointing
BIN
View File
Binary file not shown.
@@ -3,6 +3,7 @@ version = "0.1.0"
author = "Davide Cappellini"
description = "GotoTest — throwaway goto(x,y) controller prototype"
license = "MIT"
srcDir = "src"
bin = @["GotoTest"]
# Dependencies
@@ -1,3 +1,4 @@
switch("outdir", "out")
# begin Nimble config (version 2)
when withDir(thisDir(), system.fileExists("nimble.paths")):
include "nimble.paths"
View File
+12
View File
@@ -0,0 +1,12 @@
# Package
version = "0.1.0"
author = "Davide Cappellini"
description = "Oscillator pattern Tank Royale bot"
license = "MIT"
srcDir = "src"
bin = @["OscillatorBot"]
# Dependencies
requires "nim >= 2.0.0"
# tankroyale_botapi and radar_lock are vendored in-tree (common_libs/) and wired via
# config.nims --path; no nimble dependency so builds never touch ~/.nimble/pkgs2.
+3
View File
@@ -0,0 +1,3 @@
#!/bin/sh
cd "$(dirname "$0")"
exec ./out/OscillatorBot 2>> /tmp/oscillatorbot_stderr.log
+2
View File
@@ -0,0 +1,2 @@
--path:"../common_libs"
switch("outdir", "out")
View File
@@ -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)
View File
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
@@ -3,10 +3,11 @@ version = "0.1.0"
author = "Davide Cappellini"
description = "PPO-trained Tank Royale bot"
license = "MIT"
srcDir = "src"
bin = @["PPO_Bot"]
# Dependencies
requires "nim >= 2.0.0"
# tankroyale_botapi is vendored in-tree (libs/tankroyale_botapi) and wired via
# tankroyale_botapi is vendored in-tree (common_libs/tankroyale_botapi) and wired via
# config.nims --path; no nimble dependency so builds never touch ~/.nimble/pkgs2.
requires "arraymancer >= 0.7.0"
@@ -4,4 +4,4 @@
# which deadlock when called from within a multi-threaded Nim bot process.
export OPENBLAS_NUM_THREADS=1
cd -- "$(dirname -- "$0")"
exec "./PPO_Bot"
exec "./out/PPO_Bot"
@@ -2,12 +2,14 @@
# ponytail: adjust path per machine, or use pkg-config
switch("passL", "-L/nix/store/v07svn2y92bvzjl51aj7c9ca1cwg7rw7-openblas-0.3.32/lib -lopenblas")
switch("threads", "on")
switch("outdir", "out")
switch("path", thisDir() & "/src")
# begin Nimble config (version 2)
when withDir(thisDir(), system.fileExists("nimble.paths")):
include "nimble.paths"
# end Nimble config
# Use the repo-vendored Tank Royale bot API (libs/) instead of the nimble pkg.
# Use the repo-vendored Tank Royale bot API (common_libs/) instead of the nimble pkg.
# The pkg copy lived in ~/.nimble/pkgs2 and was patched ad-hoc; vendoring makes
# the build self-contained and keeps the cross-thread Channel fix in-tree.
# Must come AFTER the nimble.paths include: later --path wins the import search.
switch("path", thisDir() & "/../libs/tankroyale_botapi")
switch("path", thisDir() & "/../common_libs/tankroyale_botapi")
View File
@@ -4,12 +4,12 @@
import std/[os, strformat, strutils, math, times, algorithm]
import arraymancer
import tankroyale_botapi
import network
import actions
import training
import weights
import ./enemy_tracker
import ./state_vector
import PPO_Bot/network
import PPO_Bot/actions
import PPO_Bot/training
import PPO_Bot/weights
import PPO_Bot/enemy_tracker
import PPO_Bot/state_vector
# ── Hyperparameters from env vars (PPOB_ prefix) ─────────────────────────────
# All optional; defaults match ppoUpdate signature in training.nim.
@@ -64,7 +64,7 @@ proc hyperparmSnapshot(): string =
&"\"logStdFloor\":{logStdFloor},\"logStdCeiling\":{logStdCeiling},\"initialLogStd\":{initialLogStd}"
const botJsonPath = currentSourcePath().parentDir / "PPO_Bot.json"
const weightsRoot = currentSourcePath().parentDir / "weights"
const weightsRoot = currentSourcePath().parentDir / "../weights"
type
InFlightBullet = object
+2
View File
@@ -0,0 +1,2 @@
switch("path", "../src")
switch("path", "../../common_libs/tankroyale_botapi")
Binary file not shown.
@@ -0,0 +1,36 @@
## diag_ppo_fullbuffer.nim — run ppoUpdate on a FULL 8192-transition buffer.
## Verifies whether the round-10 death (first ppoUpdate at buffer capacity)
## is a real crash in ppoUpdate or purely the saveAdamStates empty-shape bug.
import std/[math, random]
import arraymancer
import PPO_Bot/network
import PPO_Bot/training
var ac = initActorCritic()
var adam: ACAdamStates # uninitialised → ppoUpdate must reinit (fresh-process path)
var buf = initTrajectoryBuffer()
randomize(1)
while buf.len < MAX_TRANSITIONS:
var t: Transition
for i in 0..<STATE_DIM: t.state[i] = rand(1.0'f32) - 0.5'f32
for i in 0..<ACTION_DIM: t.action[i] = rand(1.0'f32) - 0.5'f32
t.logProb = rand(1.0'f32) - 1.0'f32
t.reward = rand(0.02'f32) - 0.01'f32
t.value = rand(0.1'f32)
t.done = buf.len mod 300 == 299
buf.add(t)
echo "buffer.len = ", buf.len, " (MAX=", MAX_TRANSITIONS, ")"
var ac2 = ac
var adam2: ACAdamStates
try:
let m = ppoUpdate(ac2, buf, lastValue = 0.0'f32, adamStates = adam2)
echo "ppoUpdate OK: aLoss=", m.actorLoss, " vLoss=", m.valueLoss, " gNorm=", m.gradNorm
except CatchableError as e:
echo "CAUGHT CatchableError: ", e.msg
echo getStackTrace(e)
except Defect as e:
echo "CAUGHT Defect: ", e.msg
echo getStackTrace(e)
Binary file not shown.
@@ -0,0 +1,31 @@
## diag_savecheckpoint.nim — reproduce the startup+saveCheckpoint path that
## crashes with `io_npy.nim(143, 3) `0 < t.shape.len`` under the boot server.
## Run: nim c -d:release tests/diag_savecheckpoint.nim && ./tests/diag_savecheckpoint
import std/[os]
import arraymancer
import PPO_Bot/network
import PPO_Bot/training
import PPO_Bot/weights
const weightsRoot = currentSourcePath().parentDir.parentDir / "weights"
var ac = initActorCritic()
var adam: ACAdamStates
let loadResult = loadBestAvailable(ac, adam, weightsRoot)
echo "loaded=", loadResult.loaded, " roundNum=", loadResult.roundNum
echo "adam.initialized=", adam.initialized
echo "adam.aw1.m.shape=", adam.aw1.m.shape, " aw1.v.shape=", adam.aw1.v.shape
echo "adam.logStd.m.shape=", adam.logStd.m.shape
echo "adam.aw1.t=", adam.aw1.t
try:
saveCheckpoint(ac, adam, weightsRoot, 1)
echo "saveCheckpoint OK"
except CatchableError as e:
echo "CAUGHT CatchableError: ", e.msg
echo getStackTrace(e)
except Defect as e:
echo "CAUGHT Defect: ", e.msg
echo getStackTrace(e)
@@ -3,7 +3,7 @@
import arraymancer
import std/math
import "../actions"
import PPO_Bot/actions
template check(cond: bool, msg: string) =
if not cond:
@@ -2,7 +2,7 @@
## Run: nim c -r tests/test_controllers.nim
import std/math
import "../controllers"
import PPO_Bot/controllers
template check(cond: bool, msg: string) =
if not cond:
@@ -3,8 +3,8 @@
import arraymancer
import std/[math, strformat]
import ../network
import ../actions
import PPO_Bot/network
import PPO_Bot/actions
func isFiniteF(x: float32): bool = classify(x) notin {fcNan, fcInf, fcNegInf}
func isNaNF(x: float32): bool = classify(x) == fcNan
@@ -2,7 +2,7 @@
## Run: nim c -r tests/test_radar_lock.nim
import std/[math, strformat]
import "../enemy_tracker"
import PPO_Bot/enemy_tracker
template check(cond: bool, msg: string) =
if not cond:
@@ -5,8 +5,8 @@ import std/[math, strformat]
import arraymancer
# Import from parent dir
import "../enemy_tracker"
import "../state_vector"
import PPO_Bot/enemy_tracker
import PPO_Bot/state_vector
template check(cond: bool, msg: string) =
if not cond:
@@ -3,8 +3,8 @@
import std/[math, random]
import arraymancer
import "../network"
import "../training"
import PPO_Bot/network
import PPO_Bot/training
template check(cond: bool, msg: string) =
if not cond:
@@ -3,9 +3,9 @@
import std/[os, math]
import arraymancer
import "../network"
import "../training"
import "../weights"
import PPO_Bot/network
import PPO_Bot/training
import PPO_Bot/weights
template check(cond: bool, msg: string) =
if not cond:

Some files were not shown because too many files have changed in this diff Show More