Commit Graph

419 Commits

Author SHA1 Message Date
SirStone 86dfd672b1 fix(SNNBot): revert grid to working version, add file-based stats logging
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>
2026-09-17 08:18:59 +02:00
SirStone 34f952ce20 fix(reservoir): proper bilinear interpolation in forward(), revert neighbor spread in learn()
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-17 08:16:20 +02:00
SirStone 10a0696176 feat(reservoir): spread learning to neighbor grid cells for smoother interpolation
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>
2026-09-17 08:13:29 +02:00
SirStone 6274c0d29c fix(SNNBot): use 2° error gate for firing, remove unreliable gunTurnRemaining check
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-17 08:05:33 +02:00
SirStone 20a6132530 fix(SNNBot): add gun-settled guard before firing — only fire when gun turn remaining < 1°
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-17 08:01:51 +02:00
SirStone 039b47b151 feat(SNNBot): grid accumulator replaces exemplar ring buffer
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>
2026-09-16 23:22:00 +02:00
SirStone d7e499209c tune(SNNBot): P2 fast bullets vs Walls — 3/20 wins
PATIENCE_TICKS 15→5, ENERGY_GUARD 10→5, fire gate 5° always, power 2.0
(speed 14). P1 (power 1) scored 0/20; P2 compromise wins 3/20.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-16 23:17:35 +02:00
SirStone 4bc7baee04 feat(SNNBot): encode perpendicular velocity for unambiguous lead
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>
2026-09-16 23:15:34 +02:00
SirStone 929fe5a696 tune(SNNBot): restore P3 + patience config with improved encoding
Set currentFirePower=3.0, bulletSpeed=P3_SPEED (11.0), PATIENCE_TICKS=15.
Fire gate: strict 3° post-warmup, loose 5° cold-start.
Thermometer speed encoding (bits 36-43) unchanged.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-16 23:12:49 +02:00
SirStone cc3c2649c2 feat(SNNBot): thermometer speed encoding for precise lead discrimination
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>
2026-09-16 23:10:04 +02:00
SirStone be4e9549ef fix(test_framework): pass --tps -1 to server for uncapped headless speed
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-09-16 22:57:28 +02:00
SirStone 6da42fb06d tune(SNNBot): P2 volume strategy vs Walls — 0/10 wins
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>
2026-09-16 22:11:58 +02:00
SirStone 82ec84142c feat(SNNBot): patience-first strategy — P3 always, strict fire gate after warmup
- 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>
2026-09-16 22:01:57 +02:00
SirStone 802e0e0056 feat(SNNBot): physics-based input encoding for lead generalization
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.
2026-09-16 21:48:06 +02:00
SirStone 57cbcc5699 tune(SNNBot): P1 cold-start with P3-target learning — 2/10 wins
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>
2026-09-16 21:43:23 +02:00
SirStone 0384e2ba6a tune(SNNBot): bullet economy tuning vs Walls — 2/10 wins
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>
2026-09-16 21:23:15 +02:00
SirStone cd0a19dbc5 fix(test_bullet_economy): correct bot name and rank comparison for winner detection
- 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>
2026-09-16 09:06:02 +02:00
SirStone 8f454285aa fix(test_framework): pass --max-speed to Java runner and drain stdout to prevent pipe stall
- 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>
2026-09-16 09:02:20 +02:00
SirStone c671e0b0e3 feat(test_framework): use TR_SAMPLE_BOTS env var for external bot directory
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>
2026-09-16 08:43:46 +02:00
SirStone 67bba96da1 remove(test_framework): delete custom WallsBot, will use original bots 2026-09-16 08:43:16 +02:00
SirStone ffa277e31d feat(SNNBot): add bullet economy test against WallsBot
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>
2026-09-16 08:41:25 +02:00
SirStone 94eb805d04 feat(test_framework): add WallsBot adversary for lead prediction testing
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-16 08:40:26 +02:00
SirStone c91871464f feat(SNNBot): adaptive fire power economy with hit rate EMA
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>
2026-09-16 08:34:27 +02:00
SirStone dbc73f15ef feat(SNNBot): reset per-round stats and stale state, keep learning
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>
2026-09-16 08:25:52 +02:00
SirStone 21ed6e50e1 fix(SNNBot): correct bullet hit event handler registration
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>
2026-09-16 08:20:55 +02:00
SirStone 7c0c304418 feat(SNNBot): track and log bullet hit rate statistics
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>
2026-09-16 08:18:44 +02:00
SirStone 69ffe4c44e feat(SNNBot): offset-based exemplar learning for fast lead generalization
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.
2026-09-16 08:12:54 +02:00
SirStone 01a30b7570 feat(SNNBot): recency-weighted exemplars for fast re-acquisition
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>
2026-09-16 08:02:06 +02:00
SirStone b4e2cb5b3c feat(SNNBot): force learning on input change for fast re-acquisition
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>
2026-09-16 07:57:42 +02:00
SirStone 581bfb532c tune(SNNBot): tighten stationary dead zone to 0.5°
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-09-15 08:46:06 +02:00
SirStone 1790dd0ca4 tune(SNNBot): tighten adaptive dead zone to 1.5°–0.3°
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>
2026-09-15 08:43:34 +02:00
SirStone 3066a0c72b feat(SNNBot): adaptive learning dead zone scaled by enemy velocity
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>
2026-09-15 08:40:03 +02:00
SirStone 87bcd3df6b fix(SNNBot): fast recovery on target move + bidirectional fire gate
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>
2026-09-15 08:35:10 +02:00
SirStone 94fbde2934 fix(SNNBot): use absolute bearing to break aim feedback loop
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.
2026-09-15 08:23:08 +02:00
SirStone b843a79313 feat(SNNBot): fire only when aim is locked on target
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>
2026-09-15 08:15:29 +02:00
SirStone c05fb7a32b fix(SNNBot): hold aim when error is within dead zone
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>
2026-09-15 08:14:28 +02:00
SirStone 8685f0e794 fix(SNNBot): skip learning when aim error < 1.5° (dead zone)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-09-15 08:10:31 +02:00
SirStone 844abe0bb7 feat(SNNBot): replace bin-based aimer with exemplar kernel regression
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.
2026-09-15 07:52:32 +02:00
SirStone 445c873316 fix(SNNBot): replace correct bin weights instead of OR to prevent saturation
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>
2026-09-14 23:31:31 +02:00
SirStone be032e4ebd feat(SNNBot): replace aim smoothing with bin hysteresis
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>
2026-09-14 23:28:50 +02:00
SirStone 9cb2727fef feat(SNNBot): distance-adaptive aim smoothing
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>
2026-09-14 23:25:50 +02:00
SirStone 21c974412c chore(SNNBot): remove SNN graph overlay, keep aiming lines
Deleted neuron panel visualization (input layer, hidden layer neurons, weight lines, output channels) from drawOverlay. Retain aiming lines: enemy bearing (green), gun direction (red), SNN target (yellow).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-09-14 23:23:42 +02:00
SirStone ac5301acb1 feat(SNNBot): exponential smoothing on aim angle to stop bin-flickering
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>
2026-09-14 23:22:43 +02:00
SirStone fc00e448a1 feat(SNNBot): 1° bin resolution (360 bins) + neighbor reinforcement
5° bins caused visible jumping. 360 bins at 1° costs 5.7KB.
Reinforce ±2 neighbors per learn call for faster coverage.
Punish only bins >2° away from correct target.
2026-09-14 23:19:25 +02:00
SirStone e9ddc7673d fix(SNNBot): binary-native input encoding with ~13 active bits
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.
2026-09-14 23:18:10 +02:00
SirStone f70d9e6d1e refactor(SNNBot): replace reservoir with direct binary readout
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.
2026-09-14 23:13:01 +02:00
SirStone 2963917072 fix(SNNBot): enforce sparse reservoir state via k-Winners-Take-All
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.
2026-09-14 23:08:06 +02:00
SirStone 45b167f2b3 fix(SNNBot): fix reservoir threshold, remove decay, stop re-forward in EVALUATE
- 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()
2026-09-14 23:04:45 +02:00
SirStone dbfb58ca1e feat(SNNBot): add binary reservoir aimer as alternative to SuperSpike (#159)
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>
2026-09-14 23:00:22 +02:00
SirStone 17178158b2 refactor(SNNBot): replace retroactive error with predictive lead target
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.
2026-09-14 22:39:28 +02:00