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>
This commit is contained in:
2026-09-16 07:57:42 +02:00
parent 581bfb532c
commit b4e2cb5b3c
+12 -2
View File
@@ -214,6 +214,7 @@ type
velSpeed: float64 # speed (units/tick) from last scan delta velSpeed: float64 # speed (units/tick) from last scan delta
lastDecideGunDir: float # gun heading captured at DECIDE time for EVALUATE lastDecideGunDir: float # gun heading captured at DECIDE time for EVALUATE
lastBinInput: BitVec80 # DECIDE-time binary input, reused in EVALUATE lastBinInput: BitVec80 # DECIDE-time binary input, reused in EVALUATE
lastInput: BitVec80 # previous EVALUATE-time input (for change detection)
# ── aimTo helper ────────────────────────────────────────────────────────────── # ── aimTo helper ──────────────────────────────────────────────────────────────
@@ -410,10 +411,14 @@ method run*(bot: SNNBot) =
when USE_RESERVOIR: when USE_RESERVOIR:
let aimRaw = bot.res.forward(bot.lastBinInput) let aimRaw = bot.res.forward(bot.lastBinInput)
# Only learn if aim error exceeds adaptive dead zone # Detect significant input change via Hamming distance
let hammingDist = popcount(bot.lastBinInput[0] xor bot.lastInput[0]) + popcount(bot.lastBinInput[1] xor bot.lastInput[1])
let inputChanged = hammingDist > 2 # 3+ bits flipped = situation changed
# Learn if: error > dead zone OR input pattern changed significantly
if aimRaw >= 0.0: if aimRaw >= 0.0:
let aimErr = abs(normalizeRelativeAngle(aimRaw - correctAngle)) let aimErr = abs(normalizeRelativeAngle(aimRaw - correctAngle))
if aimErr > adaptiveDeadZone: if aimErr > adaptiveDeadZone or inputChanged:
bot.res.learn(bot.lastBinInput, correctAngle) bot.res.learn(bot.lastBinInput, correctAngle)
else: else:
# Cold start: always learn # Cold start: always learn
@@ -423,6 +428,8 @@ method run*(bot: SNNBot) =
" aim=" & (if aimRaw >= 0.0: formatFloat(aimRaw, ffDecimal, 1) else: "cold") & " aim=" & (if aimRaw >= 0.0: formatFloat(aimRaw, ffDecimal, 1) else: "cold") &
" correct=" & formatFloat(correctAngle, ffDecimal, 1) & " correct=" & formatFloat(correctAngle, ffDecimal, 1) &
" err=" & (if aimRaw >= 0.0: formatFloat(aimErr, ffDecimal, 1) else: "n/a") & " err=" & (if aimRaw >= 0.0: formatFloat(aimErr, ffDecimal, 1) else: "n/a") &
" hammingDist=" & $hammingDist &
" inputChanged=" & $inputChanged &
" exemplars=" & $bot.res.count " exemplars=" & $bot.res.count
else: else:
let targetRel = normalizeRelativeAngle(correctAngle - bot.lastDecideGunDir) let targetRel = normalizeRelativeAngle(correctAngle - bot.lastDecideGunDir)
@@ -445,6 +452,9 @@ method run*(bot: SNNBot) =
meanWout += abs(w) meanWout += abs(w)
meanWout /= float(2 * N_HID) meanWout /= float(2 * N_HID)
echo "tick=" & $bot.tick & " err=" & formatFloat(err, ffDecimal, 1) & "° infer_spk=" & $spikeCount & "/" & $(N_INFER * N_HID) & " maxV=" & formatFloat(maxV, ffDecimal, 3) & " |wih|=" & formatFloat(meanWih, ffDecimal, 4) & " |wOut|=" & formatFloat(meanWout, ffDecimal, 4) & " sin=" & formatFloat(bot.snn.lastSinOut, ffDecimal, 3) & " cos=" & formatFloat(bot.snn.lastCosOut, ffDecimal, 3) & " snnAngle=" & formatFloat(bot.snn.lastSnnAngle, ffDecimal, 1) echo "tick=" & $bot.tick & " err=" & formatFloat(err, ffDecimal, 1) & "° infer_spk=" & $spikeCount & "/" & $(N_INFER * N_HID) & " maxV=" & formatFloat(maxV, ffDecimal, 3) & " |wih|=" & formatFloat(meanWih, ffDecimal, 4) & " |wOut|=" & formatFloat(meanWout, ffDecimal, 4) & " sin=" & formatFloat(bot.snn.lastSinOut, ffDecimal, 3) & " cos=" & formatFloat(bot.snn.lastCosOut, ffDecimal, 3) & " snnAngle=" & formatFloat(bot.snn.lastSnnAngle, ffDecimal, 1)
# Update lastInput for next EVALUATE cycle's change detection
bot.lastInput = bot.lastBinInput
bot.phase = DECIDE bot.phase = DECIDE
# Radar lock # Radar lock