diff --git a/SNNBot_garage/src/SNNBot.nim b/SNNBot_garage/src/SNNBot.nim index c4f1092..6ae37b0 100644 --- a/SNNBot_garage/src/SNNBot.nim +++ b/SNNBot_garage/src/SNNBot.nim @@ -33,7 +33,6 @@ const LEAK = 0.9 # LIF membrane leak factor THRESH = 0.08 # ponytail: THRESH=0.08 tuned for 80-input layer; raise if firing saturates MAX_GUN_TURN = 20.0 # max gun turn per tick (degrees) - AIM_TOL = 2.0 # arrive tolerance (degrees) ETA = 0.1 # SuperSpike learning rate for hidden→output weights (r_0 from paper, bumped 2x) ETA_IH = 0.02 # ponytail: ETA_IH=0.02 bumped 4x for input→hidden (was 0.005, too conservative) # ponytail: separate input→hidden rate; add RMaxProp optimizer if convergence still unstable @@ -215,7 +214,6 @@ 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 - lastError: float # last aiming error (for jump detection) # ── aimTo helper ────────────────────────────────────────────────────────────── @@ -274,7 +272,6 @@ method onRoundStarted*(bot: SNNBot, e: RoundStartedEvent) = bot.velSpeed = 0.0 bot.phase = DECIDE bot.tick = 0 - bot.lastError = -1.0 # sentinel: no error yet setTargetSpeed(0.0) setTurnRate(0.0) @@ -385,21 +382,18 @@ method run*(bot: SNNBot) = aimTo(bot.targetAngle, gunDir) let err = abs(normalizeRelativeAngle(bot.targetAngle - gunDir)) - # Bidirectional fire gate: fire only when error within tolerance - if err < AIM_TOL: + # Adaptive dead zone: scale learning threshold by enemy velocity + # Stationary → 3° (stable), full speed (8 units/tick) → 0.5° (fast tracking) + let t = (bot.velSpeed / 8.0).clamp(0.0, 1.0) + let adaptiveDeadZone = 3.0 + (0.5 - 3.0) * t # lerp(3.0, 0.5, t) + + # Fire gate: only fire when error within adaptive dead zone + if err < adaptiveDeadZone: discard setFire(FIRE_POWER) bot.phase = EVALUATE else: discard setFire(0.0) - # Fast recovery: detect error jump (regime change) and reset ring buffer - # ponytail: >5° jump triggers reset; tune threshold if target moves frequently - if bot.lastError >= 0.0 and err - bot.lastError > 5.0: - bot.res.count = 0 - bot.res.nextSlot = 0 - - bot.lastError = err - of EVALUATE: # Predictive error signal: extrapolate enemy position at bullet impact time. if bot.hasLastPos: @@ -409,12 +403,17 @@ method run*(bot: SNNBot) = let futureY = bot.lastEnemyY + sin(velRad) * bot.velSpeed * travelTime # correctAngle: absolute world-frame bearing [0,360) to predicted enemy position let correctAngle = directionTo(myX, myY, futureX, futureY) + + # Adaptive learning dead zone: scale by enemy velocity (same as fire gate) + let t = (bot.velSpeed / 8.0).clamp(0.0, 1.0) + let adaptiveDeadZone = 3.0 + (0.5 - 3.0) * t # lerp(3.0, 0.5, t) + when USE_RESERVOIR: let aimRaw = bot.res.forward(bot.lastBinInput) - # Dead zone: only learn if aim error > 1.5° (prevents ring buffer churn) + # Only learn if aim error exceeds adaptive dead zone if aimRaw >= 0.0: let aimErr = abs(normalizeRelativeAngle(aimRaw - correctAngle)) - if aimErr > 1.5: + if aimErr > adaptiveDeadZone: bot.res.learn(bot.lastBinInput, correctAngle) else: # Cold start: always learn