From b4e2cb5b3c4c347c9a83ac2787b7b7d7cfa3cd35 Mon Sep 17 00:00:00 2001 From: Davide Cappellini Date: Wed, 16 Sep 2026 07:57:42 +0200 Subject: [PATCH] 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 --- SNNBot_garage/src/SNNBot.nim | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/SNNBot_garage/src/SNNBot.nim b/SNNBot_garage/src/SNNBot.nim index 66db60e..690f358 100644 --- a/SNNBot_garage/src/SNNBot.nim +++ b/SNNBot_garage/src/SNNBot.nim @@ -214,6 +214,7 @@ type 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 + lastInput: BitVec80 # previous EVALUATE-time input (for change detection) # ── aimTo helper ────────────────────────────────────────────────────────────── @@ -410,10 +411,14 @@ method run*(bot: SNNBot) = when USE_RESERVOIR: 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: let aimErr = abs(normalizeRelativeAngle(aimRaw - correctAngle)) - if aimErr > adaptiveDeadZone: + if aimErr > adaptiveDeadZone or inputChanged: bot.res.learn(bot.lastBinInput, correctAngle) else: # Cold start: always learn @@ -423,6 +428,8 @@ method run*(bot: SNNBot) = " aim=" & (if aimRaw >= 0.0: formatFloat(aimRaw, ffDecimal, 1) else: "cold") & " correct=" & formatFloat(correctAngle, ffDecimal, 1) & " err=" & (if aimRaw >= 0.0: formatFloat(aimErr, ffDecimal, 1) else: "n/a") & + " hammingDist=" & $hammingDist & + " inputChanged=" & $inputChanged & " exemplars=" & $bot.res.count else: let targetRel = normalizeRelativeAngle(correctAngle - bot.lastDecideGunDir) @@ -445,6 +452,9 @@ method run*(bot: SNNBot) = meanWout += abs(w) 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) + + # Update lastInput for next EVALUATE cycle's change detection + bot.lastInput = bot.lastBinInput bot.phase = DECIDE # Radar lock