feat(SNNBot): exponential smoothing on aim angle to stop bin-flickering
Adjacent 1° bins have similar scores, causing the winner to flip tick to tick. Circular EMA (α=0.6) on sin/cos dampens oscillation while preserving tracking response. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -215,6 +215,8 @@ 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
|
||||||
|
smoothSin: float # exponential smoothing of sin channel
|
||||||
|
smoothCos: float # exponential smoothing of cos channel
|
||||||
|
|
||||||
# ── aimTo helper ──────────────────────────────────────────────────────────────
|
# ── aimTo helper ──────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
@@ -377,6 +379,8 @@ method onRoundStarted*(bot: SNNBot, e: RoundStartedEvent) =
|
|||||||
method onGameStarted*(bot: SNNBot, e: GameStartedEventForBot) =
|
method onGameStarted*(bot: SNNBot, e: GameStartedEventForBot) =
|
||||||
initSNN(bot.snn)
|
initSNN(bot.snn)
|
||||||
bot.res = initAimer()
|
bot.res = initAimer()
|
||||||
|
bot.smoothSin = 0.0
|
||||||
|
bot.smoothCos = 1.0
|
||||||
|
|
||||||
# ── Reservoir helpers ─────────────────────────────────────────────────────────
|
# ── Reservoir helpers ─────────────────────────────────────────────────────────
|
||||||
|
|
||||||
@@ -439,10 +443,16 @@ method run*(bot: SNNBot) =
|
|||||||
let binInput = toBinaryInput(relBearing, bot.velDirDeg, bot.velSpeed, bot.hasLastPos)
|
let binInput = toBinaryInput(relBearing, bot.velDirDeg, bot.velSpeed, bot.hasLastPos)
|
||||||
let winBin = bot.res.forward(binInput)
|
let winBin = bot.res.forward(binInput)
|
||||||
let aimAngle = bot.res.interpolatedAngle(winBin)
|
let aimAngle = bot.res.interpolatedAngle(winBin)
|
||||||
|
const AIM_SMOOTH = 0.6 # 60% old, 40% new — dampens flicker
|
||||||
|
# ponytail: AIM_SMOOTH=0.6 balances tracking vs stability; lower if gun lags, raise if gun dances
|
||||||
|
let aimRad = degToRad(aimAngle)
|
||||||
|
bot.smoothSin = AIM_SMOOTH * bot.smoothSin + (1.0 - AIM_SMOOTH) * sin(aimRad)
|
||||||
|
bot.smoothCos = AIM_SMOOTH * bot.smoothCos + (1.0 - AIM_SMOOTH) * cos(aimRad)
|
||||||
|
let smoothedAim = radToDeg(arctan2(bot.smoothSin, bot.smoothCos))
|
||||||
bot.lastBinInput = binInput
|
bot.lastBinInput = binInput
|
||||||
bot.targetAngle = gunDir + aimAngle
|
bot.targetAngle = gunDir + smoothedAim
|
||||||
bot.lastDecideGunDir = gunDir
|
bot.lastDecideGunDir = gunDir
|
||||||
echo "RES tick=" & $bot.tick & " bin=" & $winBin & " aim=" & formatFloat(aimAngle, ffDecimal, 1)
|
echo "RES tick=" & $bot.tick & " bin=" & $winBin & " aim=" & formatFloat(aimAngle, ffDecimal, 1) & " smoothed=" & formatFloat(smoothedAim, ffDecimal, 1)
|
||||||
else:
|
else:
|
||||||
let inputs = encodeInputFull(relBearing, bot.velDirDeg, bot.velSpeed, bot.hasLastPos)
|
let inputs = encodeInputFull(relBearing, bot.velDirDeg, bot.velSpeed, bot.hasLastPos)
|
||||||
# Multi-tick inference: accumulate sin/cos and spike counts over N_INFER ticks
|
# Multi-tick inference: accumulate sin/cos and spike counts over N_INFER ticks
|
||||||
|
|||||||
Reference in New Issue
Block a user