The event handler was named `onBulletHitBotEvent` but the Tank Royale API
calls `onBulletHit`. Renamed to match the actual dispatch signature used by
all other bots, so bullets hit counter will now increment correctly.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Adds bulletsFired and bulletsHit fields to track shots, increments on fire and hit events, and logs hit rate (hits/shots × 100%) in both SNN and reservoir aiming paths.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Exemplars now store lead correction offsets instead of absolute angles.
The system learns 'how much lead to apply' independently of bearing,
so one learned lead pattern generalizes to all positions on the
battlefield. Converges in a few ticks for constant-velocity targets.
Exponential decay (0.95^age) makes newest exemplars dominate the
weighted mean. Old stale exemplars fade naturally, so re-learning
after target movement is always fast regardless of buffer history.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When enemy starts moving after stationary phase, the dead zone keeps error
low and skips learning. Now learn when input pattern changes significantly
(Hamming distance > 2 bits), forcing immediate re-adaptation regardless of
error magnitude. Balances stability vs. responsiveness.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Settled gun error at ~2.6° was within the 3.0° stationary dead zone,
too generous for reliable hits. Tightened bounds from [3.0°, 0.5°] to
[1.5°, 0.3°] across the lerp—revert to proven AIM_TOL=1.5° baseline
for stationary targets, 0.3° for full-speed movers (8 units/tick).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Stationary targets get large dead zone (3°) for stable aim. Fast movers
get small dead zone (0.5°) for rapid adaptation. Replaces fixed
threshold and jump-reset hack.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Problem 1: Dead zone (skip learn when error < 1.5°) means stale exemplars
linger in ring buffer after target moves. Fix: detect error jump >5° and
reset ring buffer (aimer.count, aimer.nextSlot = 0) for fresh start.
Problem 2: Fire gate only checked start condition (err < threshold), never
stopped firing when error worsened. Fix: explicit setFire(0.0) when error
exceeds tolerance, making gate bidirectional (start and stop).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The input encoded relative bearing, which changes when the bot aims,
creating a closed-loop oscillation (aim→input→aim). Switching to
absolute bearing (world frame) makes the input independent of aim
commands, eliminating the feedback-induced jitter.
Bot now conserves ammo by firing only when aimLocked=true (error < 1.5°).
During WAITING phase, checks aimLocked flag before firing FIRE_POWER; calls
setFire(0.0) when not locked. Prevents wasted shots on moving targets.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Adds aim-lock mechanism to prevent re-running forward() pass during DECIDE when the
error is already within 1.5° dead zone. Lock is set in EVALUATE when error < 1.5°,
reusing the previous targetAngle in DECIDE. Lock releases on fresh enemy scan or when
error exceeds threshold, eliminating the ±0.9° wobble from input pattern drift.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Eliminates aim jitter by switching from 360-bin WTA to weighted circular
mean over stored exemplars. Continuous float output, no bins, no
hysteresis, no saturation. K=128 ring buffer, Hamming similarity with
quadratic weighting.
OR-only reinforcement caused all bins to fill with set bits over time,
making all scores similar and the winner random. Now the correct bin
is REPLACED with the exact input pattern (no accumulation). Neighbors
still OR for generalization.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Smoothing masked jitter, hysteresis removes it at the source.
Gun stays on current bin unless a new bin wins by >1 point.
Removes all EMA/smoothing code.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Far targets need more smoothing (small jitter = big miss at distance).
Close targets need less (fast tracking). Linear ramp α=0.3..0.9 over 0..600 units.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Adjacent 1° bins have similar scores, causing the winner to flip
tick to tick. Circular EMA (α=0.6) on sin/cos dampens oscillation
while preserving tracking response.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Float population code produced 0-1 active bits after binarization —
not enough to discriminate 72 bins. Replace with direct binary encoding:
5 bits per angular channel (center ± 2 neighbors), 3 bits for speed.
Adjacent bearings share 4/5 bits (smooth); distant bearings share 0
(clean separation). Fix circular error display.
Delete 1024-neuron reservoir — it added noise, not features.
Direct 80-bit input → 72 output bins via popcount + WTA Hebbian.
Same learning rule (OR reinforce, AND NOT punish), zero indirection.
Reservoir adds value for temporal features (step 3), not now.
Dense firing (~50%) caused all readout bins to saturate identically.
Replace threshold-based firing with k-WTA: only top 50 neurons fire
per tick (~5% sparsity). Sparse patterns give low inter-angle overlap
-> readout bins can discriminate between different bearings.
- Threshold 4→1: neurons now fire (~50% rate) instead of never firing
- Remove aggressive decay that erased learning signal immediately
- Remove EVALUATE re-forward that corrupted reservoir state before learn()
New architecture: 1024 binary neurons in fixed random reservoir,
72-bin population-coded output, WTA Hebbian learning with binary ops.
Forward pass: AND + popcount. Learning: OR (reinforce) / AND NOT (punish).
No backprop, no floats in hot path. Toggle via USE_RESERVOIR const.
Forecast: ~200-400 ticks to learn stationary target aiming.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Delete ring buffer, aim snapshots, maturation loop (~80 lines).
Replace with simple velocity extrapolation: predict enemy position at
bullet impact time, use bearing to predicted position as SuperSpike
target. Immediate error signal every EVALUATE — no delay, no snapshot
state storage. Mathematically equivalent to retroactive for linear movers.
Fire during WAITING phase using FIRE_POWER constant (matches error
signal's bullet speed calculation). Always shooting — fire timing
optimization is out of scope for this map.
Multiple superSpikeUpdate calls per EVALUATE caused oscillation by
effectively multiplying the learning rate. Now: accumulate retroTarget
via circular mean across all matured snapshots, apply one update with
the averaged target and most recent snapshot's SNN state.
Multiple snapshots can mature by EVALUATE time but only the last one
triggered a superSpikeUpdate. Earlier matured snapshots were silently
discarded — wasting learning signal. Now each matured snapshot with a
valid ring buffer hit gets its own weight update.
Weights were barely moving: |wih| +0.14%, |wOut| +7.7% over full session.
Bump output ETA 2x and input→hidden ETA 4x to accelerate convergence.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Lower THRESH 0.2→0.08 to increase hidden firing rates (output layer was frozen due to rate≈0 in weight updates)
- Add ETA_IH=0.005 for input→hidden updates (10x smaller than output ETA=0.05 to prevent weight thrashing from large preTrace magnitudes)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The retroactive error signal matures many ticks after the snapshot was taken,
but superSpikeUpdate was using bot.lastSpikes/lastVSnap which had been
overwritten by subsequent DECIDE cycles. Learning was applied against wrong
neural activity — effectively random weight perturbations.
Fix: AimSnapshot now captures spikes, vSnap, and preTrace at DECIDE time.
superSpikeUpdate receives these saved values at maturation instead of the
stale current state.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Expand input layer from 36 to 80 neurons: bearing (0-35) + velocity direction (36-71) + speed (72-79)
- Population-code velocity direction (same 10° band scheme as bearing) and speed (8 bands, 1 unit/tick)
- Compute 1-tick velocity from position deltas in onScannedBot
- Add 100-slot ring buffer with per-slot tick tracking for enemy positions
- Replace instantaneous bearing error with retroactive would-have-hit signal
- Skip learning until first aim snapshot matures (buffer fill period)
- Delayed-target SuperSpike: weight updates use most recently matured retroactive bearing
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- 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>
Implements:
- BattleResult type and JSON-lines parser (#127)
- TR server lifecycle manager (#128)
- Bot compiler using nim c (#129)
- runBattle() orchestrator (#130)
- Example test in OscillatorBot_garage (#131)
- Framework usage guide (#132)
- TestBattleRunner.java for external server (#133)
- BattleRunner process lifecycle (#134)
Fix: runner_process.nim was redefining TimeoutError locally; now
uses std/net.TimeoutError consistently with server_manager.nim.
- Deleted constants.nim, event_queue.nim, graphics.nim, json_parse.nim, schemas.nim, utils.nim, and ws_client.nim files.
- This cleanup removes unused code and simplifies the codebase, focusing on essential functionalities.
- 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>