- 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>
Extracted concrete numbers from 13 papers in docs/papers/neuroevolution/.
Key findings: use CMA-ES or mutation-only truncation GA, mutate ALL weights
(not 5%), sigma=0.005-0.01, pop=64-200, no crossover, single elite.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
$(cat f) inside a command redirected to f saw the already-truncated file,
so every eval cycle wiped ma_history_*.txt back to one leading-space value
and degraded the composite best-gate to last-cycle mean. Read is hoisted
into its own statement; unquoted expansion + tail -n keeps exactly the last
MA_WINDOW values. Verified: fresh/5+/6+ cycle edges reproduce sliding window.
SACLSTM_EVAL_MODE=1 in SacTwin.sh suppresses all sendTrainingMsg traffic
(lever-4 gate), so the twin never trains — not even in-RAM within a battle.
Required now that the main bot's SACLSTM_SAVE_INTERVAL drops to 1 (v2 relaunch
after checkpoint-cadence diagnosis): without the gate the twin would persist
per-battle drift and stop being the frozen reproducible opponent #54 specifies.
Lever 2 (#59): x1.25 aggression mult on damage dealt, flat +0.5 hit bonus,
-3.0 per bot-bot collision (server deals RAM_DAMAGE=0.6 to both parties but
only notifies the hitter), escalating proximity deterrent below 12% arena
diagonal suppressed while dealing damage. Win/loss terminals unchanged and
dominant. All weights TUNABLE consts marked ponytail. SACLSTM_REWARD_DEBUG=1
env-gated reward_debug.log for calibration greps.
Lever 5 (#59): no code needed — SACLSTM_LR_ACTOR/LR_CRITIC/LR_ALPHA (3e-4)
and SACLSTM_TARGET_ENTROPY (-4.0) were already env-overridable in training.nim.
Smoke vs RamFire+Crazy (hidden=32, random init, isolated weights): 75 ram
penalties, 381 charge events, hit bonuses firing, 0 crashes, metrics JSONL
flowing. Tests: 8/8 suites green incl. new assert-level term math.
Levers 3, 4, 1 of the #57 sign-off (execution order 3->4->1), tracked in #59.
- Lever 3 (#59): one JSONL line per trainPass in training_metrics.jsonl with
exactly the scalars sacUpdate already exposes (SACMetrics: critic/actor/alpha
losses + alpha, averaged per pass) plus epoch, buffer size (replay_buffer.len),
cumulative steps and drained count. No trainer change needed.
- Lever 4 (#59): sendTrainingMsg drops all training input while SACLSTM_EVAL_MODE=1
(existing #49 harness mechanism) — eval battles can neither pollute the replay
buffer nor trigger gradient updates; one-time stderr notice at bot init.
- Lever 1 (#59): sac_train.sh evaluates every SAC_EVAL_OPPONENTS entry per cycle
(results carry opponent name in eval_log.jsonl); best-gating now uses a
composite = mean over opponents of the last-5-evals moving average per
opponent. best_score.txt format change: float composite replaces the
single-opponent integer win rate semantics (retired).
- Tests: metricsLine JSONL scalars + eval-mode suppression asserts.
Refs: #59, #57
Launch finding during campaign-v1 verification: at production sizes
(hidden 256, ~1s/step, ~13s trainer CPU per ~40s chunk process) the
save check ran only between drain-burst passes, so stepCount never
crossed nextSave before the process died — zero checkpoints persisted
across entire runs (masked at #49/#54 smoke sizes where steps were
sub-millisecond). Check now fires mid-loop; with SAVE_INTERVAL<=5
(within the per-process step budget) every chunk persists its chain.
- make_twin.sh: generates self-contained SacTwin dir in the sample-bots
archive (own json/sh identity, own weights dir seeded from a frozen
sac_best.zip copy, own round_counter) so RunTraining.java resolves it
like any sample bot; re-running resets the twin to the frozen baseline.
- src/SAC_LSTM_Bot.nim: SACLSTM_BOT_JSON env overrides the baked-in bot
json (loadBotInfo gives json total precedence, #49) so the same binary
boots under the twin's name.
- sac_train.sh: chunk loop is a while, not for-over-seq — a crash on the
FINAL chunk previously fell through ((chunk--);continue on an exhausted
seq list) and exited 0 with budget incomplete; observed live vs SacTwin.
Readiness dry-run (#54): weighted pool Corners:1,SacTwin:3 picked the twin
in 3/4 chunks; all battles counter-checked; deterministic eval parsed;
main sac_best.zip/counter untouched by twin (twin counter advanced
independently); crash-restart proven end-to-end incl. final-chunk retry.
sac_train.sh orchestrates chunked self-play via tools/training_runner/
RunTraining.java: weighted opponent sampling per chunk, deterministic
eval (SACLSTM_EVAL_MODE=1) every N chunks with win-rate tracking, best
checkpoint (weights/sac_best.zip) by eval score, crash-restart loop on
the runner's liveness detection.
Supporting changes:
- integration.nim: opponentKey() keys the NewBattle buffer-clear rule on
getBotName(id) with numeric-id fallback (#49 Q14 follow-up);
bumpRoundCounter() emits the per-round liveness signal.
- SAC_LSTM_Bot.nim: onRoundEnded -> bumpRoundCounter().
- RunTraining.java: BOT_NAME env parameterizes result matching
(default PPO_Bot, unchanged behavior for PPO).
- Launch packaging: root SAC_LSTM_Bot.json + .sh for the booter;
src json name aligned to 'SAC_LSTM_Bot' so self-reported identity
matches the booted identity (mismatch = runner connect timeout).
Syncs libs/tankroyale_botapi with SirStone/robocode_tankroyale_botapi
v1.0.1 (extracted from tank-royale nim branch @ 03195a814). The local
SIGSEGV fixes (static event queue/SVG/intent buffers) were already
ported upstream in issue #24 — content is otherwise identical.
New capability (upstream #23): opponent name exposure for ticket #49.
- bot.nim: gBotNames id→name table + getBotName(id) / updateBotNames()
- umbrella module: dispatch BotListUpdate messages to updateBotNames()
Vendored layout and wiring unchanged (--path via config.nims, module
name stays tankroyale_botapi).
Save/load all SAC-LSTM tensors (actor, 2 critics, 2 target critics,
alpha, Adam states) into a single .zip of .npy files. Atomic write
via temp path + rename. Adam types (AdamVar, SACAdamStates) defined
here for training.nim to use.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
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.
Some files were not shown because too many files have changed in this diff
Show More
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.