- GF gun: triangular head-on prior replaces flat bins (was always aiming at GF=-1)
- GF gun: recompute MEA per bullet power in onResult (was using stale first-bin value)
- Scores improved: SpinBot 1805-48, Crazy 1557-46, WaveSurfer 1868-10, PatternMover 1846-4
- All 6 guns active in selection across gauntlet
- Cold-start bug: uniform bins[0..30]=0.1 made peakBin() always return
0 (first-wins tie), giving GF=-1 (max CW escape) before any learning.
Fixed with a triangular head-on bump at bin 15 (GF=0) as the prior.
- onResult now recomputes mea from FeedbackEvent.bulletPower instead of
the stale first-bin mea cached by predict; correct per-power-bin GF.
- Add DebugGF const (default false) with [gf-dbg] echoes in predict/onResult.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Two bugs fixed:
1. Multi-tick gaps: turn rate assumed 1 tick between observations, but scans can be 5+ ticks apart. Now divides by actual tickDelta.
2. Per-power-bin state corruption: predict() called 4x per tick (per power bin). After first call, prevHeading was already updated, causing subsequent calls to compute 0° delta. Now captures oldHeading/oldTick before updating.
Verified: OscillatorBot at 4°/tick captured correctly; normalization [-180°,180°] works; tickDelta=1 typical; first bin gets delta, subsequent bins see 0 (expected).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace binary hit/miss reward with exponential decay based on miss distance:
- reward = 2.0 * exp(-missDistance / 36.0) - 1.0
- At 0px: +1.0 (perfect hit)
- At 36px: -0.26 (near miss, small penalty)
- At 100px: -0.87 (big miss, large penalty)
Maintains virtual hit/miss counters for display (threshold: 36px).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Swap sin/cos in virtual bullet position calc to match Tank Royale convention
- Change exploration from flipping each bit with 50% chance to flipping exactly 1 random bit
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Added myEnergy, enemyEnergy, and canFire to ScanData. Energy fields quantize 0-150 → 8 bits each; gun ready is 1 bit (1 = can fire, 0 = can't). Updated total encoding from 100 to 117 bits.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
8 new fields in ScanData (myWallN/S/E/W, enemyWallN/S/E/W), 7 bits each via Gray code,
appended after existing 44 bits. TOTAL_BITS 44 → 100.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Output format: <tick> <44 bits> <hamming> <similarity> <overlap>
- Similarity: bits that are the same (TOTAL_BITS - hamming)
- Overlap: bits that are 1 in both vectors (popcount of bitwise AND)
- First tick: all metrics printed as 0
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- 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>