Compare commits
29 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d3e4540e7f | |||
| 259e67d7ff | |||
| c5115fdf61 | |||
| e276548223 | |||
| 2097c2e6fa | |||
| 06ed905033 | |||
| eab85be8b3 | |||
| b509195ee9 | |||
| f8c0c871c6 | |||
| b9877bfff5 | |||
| 8ba4bae21a | |||
| f5b1ade48c | |||
| 12a0eca44a | |||
| 08d1c44ca8 | |||
| f47925eeb0 | |||
| 55d69a9352 | |||
| 4a6e3347c6 | |||
| 8872be3ff6 | |||
| 8eb7c53dd0 | |||
| 51e7f33ecb | |||
| d2d205e6f6 | |||
| 3851f28cc5 | |||
| 6241c41e52 | |||
| 156b4ae8db | |||
| eee48dec38 | |||
| eb8faa7c99 | |||
| cb1bbc35dc | |||
| b7b10af811 | |||
| e455566c8e |
+36
@@ -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/
|
||||
@@ -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.
|
||||
@@ -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
@@ -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
|
||||
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"
|
||||
@@ -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.
|
||||
Executable
+3
@@ -0,0 +1,3 @@
|
||||
#!/bin/sh
|
||||
cd "$(dirname "$0")"
|
||||
exec ./out/OscillatorBot 2>> /tmp/oscillatorbot_stderr.log
|
||||
@@ -0,0 +1,2 @@
|
||||
--path:"../common_libs"
|
||||
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)
|
||||
Binary file not shown.
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")
|
||||
@@ -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
|
||||
@@ -0,0 +1,2 @@
|
||||
switch("path", "../src")
|
||||
switch("path", "../../common_libs/tankroyale_botapi")
|
||||
Executable
BIN
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)
|
||||
Executable
BIN
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:
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user