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>
This commit is contained in:
2026-09-16 08:18:44 +02:00
parent 69ffe4c44e
commit 7c0c304418
+14 -4
View File
@@ -216,6 +216,8 @@ type
lastBinInput: BitVec80 # DECIDE-time binary input, reused in EVALUATE
lastInput: BitVec80 # previous EVALUATE-time input (for change detection)
lastAbsBearing: float # absolute bearing to enemy captured at DECIDE time
bulletsFired: int # count of bullets fired (power > 0)
bulletsHit: int # count of bullets that hit
# ── aimTo helper ──────────────────────────────────────────────────────────────
@@ -281,6 +283,9 @@ method onGameStarted*(bot: SNNBot, e: GameStartedEventForBot) =
initSNN(bot.snn)
bot.res = initBinaryAimer()
method onBulletHitBotEvent*(bot: SNNBot, e: BulletHitBotEvent) =
inc bot.bulletsHit
# ── Reservoir helpers ─────────────────────────────────────────────────────────
proc toBinaryInput(bearing: float, velDir: float, velSpeed: float, hasVel: bool): BitVec80 =
@@ -345,14 +350,15 @@ method run*(bot: SNNBot) =
bot.lastBinInput = binInput
bot.lastDecideGunDir = gunDir
bot.lastAbsBearing = absBearing
let rate = if bot.bulletsFired > 0: (float(bot.bulletsHit) / float(bot.bulletsFired) * 100.0) else: 0.0
if aimOffset <= -999.0:
# cold start: aim directly at enemy (no lead)
bot.targetAngle = absBearing
echo "RES tick=" & $bot.tick & " cold-start aim=" & formatFloat(absBearing, ffDecimal, 1)
echo "RES tick=" & $bot.tick & " cold-start aim=" & formatFloat(absBearing, ffDecimal, 1) & " fired=" & $bot.bulletsFired & " hit=" & $bot.bulletsHit & " rate=" & formatFloat(rate, ffDecimal, 1) & "%"
else:
# apply lead correction offset to current bearing
bot.targetAngle = (absBearing + aimOffset + 360.0) mod 360.0
echo "RES tick=" & $bot.tick & " offset=" & formatFloat(aimOffset, ffDecimal, 1) & " aim=" & formatFloat(bot.targetAngle, ffDecimal, 1)
echo "RES tick=" & $bot.tick & " offset=" & formatFloat(aimOffset, ffDecimal, 1) & " aim=" & formatFloat(bot.targetAngle, ffDecimal, 1) & " fired=" & $bot.bulletsFired & " hit=" & $bot.bulletsHit & " rate=" & formatFloat(rate, ffDecimal, 1) & "%"
else:
let inputs = encodeInputFull(absBearing, bot.velDirDeg, bot.velSpeed, bot.hasLastPos)
# Multi-tick inference: accumulate sin/cos and spike counts over N_INFER ticks
@@ -393,6 +399,7 @@ method run*(bot: SNNBot) =
# Fire gate: only fire when error within adaptive dead zone
if err < adaptiveDeadZone:
discard setFire(FIRE_POWER)
inc bot.bulletsFired
bot.phase = EVALUATE
else:
discard setFire(0.0)
@@ -428,13 +435,15 @@ method run*(bot: SNNBot) =
# Cold start: always learn
bot.res.learn(bot.lastBinInput, correctOffset)
let aimErr = if producedOffset > -999.0: abs(normalizeRelativeAngle(producedOffset - correctOffset)) else: -1.0
let rate = if bot.bulletsFired > 0: (float(bot.bulletsHit) / float(bot.bulletsFired) * 100.0) else: 0.0
echo "RES tick=" & $bot.tick &
" offset=" & (if producedOffset > -999.0: formatFloat(producedOffset, ffDecimal, 1) else: "cold") &
" correctOffset=" & formatFloat(correctOffset, ffDecimal, 1) &
" err=" & (if producedOffset > -999.0: formatFloat(aimErr, ffDecimal, 1) else: "n/a") &
" hammingDist=" & $hammingDist &
" inputChanged=" & $inputChanged &
" exemplars=" & $bot.res.count
" exemplars=" & $bot.res.count &
" fired=" & $bot.bulletsFired & " hit=" & $bot.bulletsHit & " rate=" & formatFloat(rate, ffDecimal, 1) & "%"
else:
let targetRel = normalizeRelativeAngle(correctAngle - bot.lastDecideGunDir)
bot.snn.superSpikeUpdate(bot.lastSpikes, bot.lastVSnap, bot.snn.preTrace, targetRel)
@@ -455,7 +464,8 @@ method run*(bot: SNNBot) =
for w in bot.snn.wCos:
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)
let rate = if bot.bulletsFired > 0: (float(bot.bulletsHit) / float(bot.bulletsFired) * 100.0) else: 0.0
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) & " fired=" & $bot.bulletsFired & " hit=" & $bot.bulletsHit & " rate=" & formatFloat(rate, ffDecimal, 1) & "%"
# Update lastInput for next EVALUATE cycle's change detection
bot.lastInput = bot.lastBinInput