- Replace population/thermometer/signed-thermometer encoding with simple 10-bit binary per value
- 5 values × 10 bits = 50 bits total (was 112)
- Each value normalized to 0-1023, then bit-extracted MSB-first
- Output is now just tick and raw binary string (no field labels, no debug log)
- Remove file logging entirely; stdout only
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Pre-populate all LeadGrid cells with arcsin(vPerp/bulletSpeed) so the
grid starts warm instead of cold, avoiding the early-round 0-data
fallback to -999 (no-lead) firing.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Reverted fire gate from 1° back to 2°, removed gridHasData cold-start
guard so bot fires on straight aim during cold-start. Added
/tmp/snnbot_bias_debug.log BIAS entries in EVALUATE phase logging
gridOffset, correctOffset, bias, vPerp, dist per cycle.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- P3 at err<0.5°, P1 at err<2.0° (was fixed P2); no fire at err>=2.0°
- Scale grid lead offset by bullet speed ratio (sin(θ) ∝ 1/v_bullet)
- ENERGY_GUARD 5→15 to survive Walls' sustained fire
- Suppress fire when |vPerp delta| > 2.0 (enemy changing direction)
- Store lastBulletSpeed = 20-3*chosenPower for correct EVALUATE travel time
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Reverts reservoir.nim to the neighbor-blending forward() + single-cell
learn() version (pre-bilinear-interpolation). Adds /tmp/snnbot_round_stats.log
and /tmp/snnbot_aim_debug.log for diagnostics independent of test framework stdout.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Each learn() call now updates a 3×3 neighborhood (center w=1.0, cardinal w=0.3,
diagonal w=0.1). count field changed to float64 to support fractional weights.
Fills grid ~5× faster and eliminates one-sided interpolation at bin boundaries.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Ring buffer had amnesia — cycled out all data every 128 ticks,
preventing convergence. Grid accumulator permanently stores average
lead offsets indexed by (v_perp, distance). 136 cells, 1.5 KB.
Knowledge accumulates across rounds → convergence guaranteed for
stationary velocity patterns.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
relVelDir ±5° shared 80% of bits despite needing opposite lead.
Now encodes v_perp = speed×sin(relVelDir) directly with signed
thermometer coding — positive and negative crossing velocities
have zero bit overlap. Also adds v_parallel for approach/recede.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
3-bit speed encoding couldn't distinguish speed 3 from speed 5 —
9° of lead error baked into the input. Thermometer coding with 8 bits
gives 1-bit Hamming distance between adjacent speeds. MAX_K back to 128.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
P1 (0/10, score 320) and P2 (0/10, score 593) both tested.
P2 scores ~85% higher than P1 despite same win rate, making it
the better base. Loosened fire gate to 5°, PATIENCE_TICKS=5,
COLD_K=3, travel time now uses actual bullet speed.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- ENERGY_GUARD 15→10 to squeeze more shots out
- Remove power ladder (always P3, bulletSpeed=11)
- Add PATIENCE_TICKS=15: first 15 ticks per round use loose 5° gate (collect exemplars)
- After warmup (≥10 exemplars AND ≥15 ticks): strict 3° gate to avoid wasted shots
- roundTick counter resets each round; BinaryAimer exemplars persist across rounds
- Remove dead selectFirePower proc
Result: 30% win rate vs Walls (was 0%), survival in 7/10 rounds
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace (bearing, velDir, speed) with (relVelDir, speed, distance).
Raw bearing is irrelevant to lead offset — the correction depends on
how the target crosses the line of fire, not where it is. This lets
exemplars from one position generalize to all positions with similar
geometry.
Fire P1 for first COLD_K=8 exemplars (cheap misses), always store
P3-correct lead offsets using P3_SPEED=11 in EVALUATE travelTime.
Switches to P3 once exemplar buffer has enough data. Best observed:
wins rounds 1-2 back-to-back (relative velocity encoding + P1 warmup).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Changes:
- Fixed fire power at 3.0 (removes hitRateEMA power ladder noise that
cleared exemplars mid-battle and corrupted learning)
- Relative velocity encoding: input uses (velDir - bearing) instead of
absolute velDir, so exemplars generalize across Walls' starting walls
- Fix test winner detection to use per-round score delta instead of
rank field (rank in round_ended is cumulative battle rank, not round winner)
- Keep exemplars across power changes (no longer relevant with fixed power)
- Store lastBulletSpeed at fire time for accurate EVALUATE lead prediction
Result: 2/10 rounds won vs Walls (Nim); first-round win now possible from
round 1 when aimer generalizes from relative velocity patterns.
Bottleneck: sparse exemplars in early rounds; energy bleeds at P3 cold-start.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Match "Walls (Nim)" instead of "WallsBot" — sample bot reports with "(Nim)" suffix
- Guard against unmatched bot (rank=0) so a missing entry doesn't silently flip the winner
- The rank comparison itself (lower = better) was correct; the stale name was the root cause
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add maxSpeed param (default true) to runBattleRunner/runBattle
- Drain stdout in poll loop — Java blocked on full pipe buffer causing timeout
- TestBattleRunner.java already had --max-speed; .class was stale and needed recompile
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds TR_SAMPLE_BOTS env var support to point to external sample bots (Walls, Fire, SpinBot, etc). Updates test_bullet_economy.nim to use Walls from the sample-bots directory instead of custom WallsBot, removing the hardcoded path dependency.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add --max-speed flag to TestBattleRunner (sets defaultTurnsPerSecond=-1 for unlimited TPS).
Add SNNBot_garage/tests/test_bullet_economy.nim: 10-round vs WallsBot, prints per-round and summary stats for tuning.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Power ladder: 1.0→2.0→3.0 based on rolling hit rate EMA.
Higher power = more damage per energy when hitting.
Energy guard at 15. Exemplar buffer clears on power change
to re-learn lead offset for new bullet speed.
Break-even hit rate is 33% — below that, every shot is a net drain.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Reset bulletsFired, bulletsHit, lastEnemyX/Y, lastAbsBearing, lastDecideGunDir, targetAngle, and lastInput at round start. BinaryAimer exemplar buffer persists across rounds to maintain learning continuity.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
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>