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.
This commit is contained in:
@@ -9,12 +9,12 @@
|
||||
# WAITING → aimTo() each tick; when error < 2° → EVALUATE
|
||||
# EVALUATE → measure error, compute SuperSpike/reservoir update, log, → DECIDE
|
||||
|
||||
import std/[math, random, os, strutils]
|
||||
import std/[math, random, os, strutils, bitops]
|
||||
import robocode_tankroyale_botapi
|
||||
import radar_lock/radar_lock as radar_lock
|
||||
import reservoir
|
||||
|
||||
const USE_RESERVOIR* = true
|
||||
const USE_RESERVOIR* = true # ponytail: kept for SNN path fallback; remove when binary aimer is validated
|
||||
|
||||
# ── Constants ──────────────────────────────────────────────────────────────────
|
||||
|
||||
@@ -198,7 +198,7 @@ type
|
||||
|
||||
SNNBot = ref object of Bot
|
||||
snn: SNN
|
||||
res: Reservoir
|
||||
res: BinaryAimer
|
||||
phase: Phase
|
||||
targetAngle: float # SNN output (absolute bearing)
|
||||
enemyBearing: float # last known enemy bearing
|
||||
@@ -214,6 +214,7 @@ type
|
||||
velDirDeg: float64 # velocity direction (degrees) from last scan delta
|
||||
velSpeed: float64 # speed (units/tick) from last scan delta
|
||||
lastDecideGunDir: float # gun heading captured at DECIDE time for EVALUATE
|
||||
lastBinInput: BitVec80 # DECIDE-time binary input, reused in EVALUATE
|
||||
|
||||
# ── aimTo helper ──────────────────────────────────────────────────────────────
|
||||
|
||||
@@ -375,7 +376,7 @@ method onRoundStarted*(bot: SNNBot, e: RoundStartedEvent) =
|
||||
|
||||
method onGameStarted*(bot: SNNBot, e: GameStartedEventForBot) =
|
||||
initSNN(bot.snn)
|
||||
bot.res = initReservoir(42)
|
||||
bot.res = initAimer()
|
||||
|
||||
# ── Reservoir helpers ─────────────────────────────────────────────────────────
|
||||
|
||||
@@ -413,7 +414,8 @@ method run*(bot: SNNBot) =
|
||||
let binInput = toBitVec80(inputs)
|
||||
let winBin = bot.res.forward(binInput)
|
||||
let aimAngle = bot.res.interpolatedAngle(winBin)
|
||||
bot.targetAngle = gunDir + aimAngle
|
||||
bot.lastBinInput = binInput
|
||||
bot.targetAngle = gunDir + aimAngle
|
||||
bot.lastDecideGunDir = gunDir
|
||||
echo "RES tick=" & $bot.tick & " bin=" & $winBin & " aim=" & formatFloat(aimAngle, ffDecimal, 1)
|
||||
else:
|
||||
@@ -460,18 +462,23 @@ method run*(bot: SNNBot) =
|
||||
let absBearing = directionTo(myX, myY, futureX, futureY)
|
||||
let targetRel = normalizeRelativeAngle(absBearing - bot.lastDecideGunDir)
|
||||
when USE_RESERVOIR:
|
||||
# state/scores already set by DECIDE's forward(); re-forwarding would corrupt them
|
||||
let winBin = block:
|
||||
var best = 0; var bestS = -1
|
||||
for k in 0 ..< N_BINS:
|
||||
if bot.res.scores[k] > bestS: bestS = bot.res.scores[k]; best = k
|
||||
best
|
||||
let aimAngle = bot.res.interpolatedAngle(winBin)
|
||||
bot.res.learn(targetRel)
|
||||
bot.res.learn(bot.lastBinInput, targetRel)
|
||||
let correctBin = angleToBin(targetRel)
|
||||
let readoutPopCorrect = block:
|
||||
var c = 0
|
||||
for w in 0 ..< WORDS_IN: c += popcount(bot.res.readout[correctBin][w]).int
|
||||
c
|
||||
echo "RES tick=" & $bot.tick & " bin=" & $winBin &
|
||||
" aim=" & formatFloat(aimAngle, ffDecimal, 1) &
|
||||
" target=" & formatFloat(targetRel, ffDecimal, 1) &
|
||||
" err=" & formatFloat(abs(aimAngle - targetRel), ffDecimal, 1)
|
||||
" err=" & formatFloat(abs(aimAngle - targetRel), ffDecimal, 1) &
|
||||
" readout_pop_correct=" & $readoutPopCorrect
|
||||
else:
|
||||
let err = abs(normalizeRelativeAngle(gunDir - bot.enemyBearing))
|
||||
bot.snn.superSpikeUpdate(bot.lastSpikes, bot.lastVSnap, bot.snn.preTrace, targetRel)
|
||||
|
||||
Reference in New Issue
Block a user