Commit Graph

53 Commits

Author SHA1 Message Date
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 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 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 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
SirStone a78105c894 feat(SNNBot): fire bullets every tick at fixed power
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.
2026-09-14 22:30:28 +02:00
SirStone 42b6cebd8e fix(SNNBot): average matured snapshot targets instead of multiple updates
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.
2026-09-14 22:28:41 +02:00
SirStone d5777fba07 fix(SNNBot): apply weight update for every matured snapshot, not just last
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.
2026-09-14 22:25:39 +02:00
SirStone a80fd8d656 fix(SNNBot): increase learning rates — ETA 0.05→0.1, ETA_IH 0.005→0.02
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>
2026-09-14 22:21:29 +02:00
SirStone 80dbf81479 fix(SNNBot): tune SNN hyperparameters for 80-input layer
- 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>
2026-09-14 22:16:51 +02:00
SirStone 0ee5c21ef5 fix(SNNBot): store SNN state in aim snapshots for correct retroactive learning
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>
2026-09-14 22:08:22 +02:00
SirStone 5740c56a9f feat(SNNBot): add velocity input encoding and retroactive error signal (#159)
- 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>
2026-09-14 21:34:59 +02:00
SirStone 23bdf8acd6 research(SNNBot): survey existing lead-targeting and enemy tracking code
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-13 20:30:10 +02:00