fix(SNNBot): binary-native input encoding with ~13 active bits
Float population code produced 0-1 active bits after binarization — not enough to discriminate 72 bins. Replace with direct binary encoding: 5 bits per angular channel (center ± 2 neighbors), 3 bits for speed. Adjacent bearings share 4/5 bits (smooth); distant bearings share 0 (clean separation). Fix circular error display.
This commit is contained in:
@@ -380,12 +380,38 @@ method onGameStarted*(bot: SNNBot, e: GameStartedEventForBot) =
|
||||
|
||||
# ── Reservoir helpers ─────────────────────────────────────────────────────────
|
||||
|
||||
proc toBitVec80(inputs: array[N_IN, float]): BitVec80 =
|
||||
proc toBinaryInput(bearing: float, velDir: float, velSpeed: float, hasVel: bool): BitVec80 =
|
||||
## Binary-native encoding: ~5 bits per channel, ~13 total active bits
|
||||
result = [0'u64, 0'u64]
|
||||
for i in 0 ..< N_IN:
|
||||
if inputs[i] > 0.5:
|
||||
let word = i div 64
|
||||
let bit = i mod 64
|
||||
|
||||
# Bearing: bits 0-35 (36 bits, 10° bands)
|
||||
# Activate center band + 2 neighbors on each side = 5 bits
|
||||
let bNorm = (bearing + 180.0) / 10.0 # 0..36
|
||||
let bCenter = int(bNorm) mod 36
|
||||
for offset in -2 .. 2:
|
||||
let idx = (bCenter + offset + 36) mod 36
|
||||
let word = idx div 64
|
||||
let bit = idx mod 64
|
||||
result[word] = result[word] or (1'u64 shl bit)
|
||||
|
||||
if hasVel:
|
||||
# Velocity direction: bits 36-71 (36 bits, 10° bands)
|
||||
# Same scheme: 5 bits active
|
||||
let vNorm = (velDir + 180.0) / 10.0
|
||||
let vCenter = int(vNorm) mod 36
|
||||
for offset in -2 .. 2:
|
||||
let idx = 36 + (vCenter + offset + 36) mod 36
|
||||
let word = idx div 64
|
||||
let bit = idx mod 64
|
||||
result[word] = result[word] or (1'u64 shl bit)
|
||||
|
||||
# Speed: bits 72-79 (8 bits, 1 unit/tick bands)
|
||||
# Activate center + 1 neighbor on each side = 3 bits
|
||||
let sCenter = clamp(int(velSpeed), 0, 7)
|
||||
for offset in -1 .. 1:
|
||||
let idx = 72 + clamp(sCenter + offset, 0, 7)
|
||||
let word = idx div 64
|
||||
let bit = idx mod 64
|
||||
result[word] = result[word] or (1'u64 shl bit)
|
||||
|
||||
# ── Main loop ─────────────────────────────────────────────────────────────────
|
||||
@@ -409,9 +435,8 @@ method run*(bot: SNNBot) =
|
||||
of DECIDE:
|
||||
let relBearing = normalizeRelativeAngle(bot.enemyBearing - gunDir)
|
||||
bot.lastRelBearing = relBearing
|
||||
let inputs = encodeInputFull(relBearing, bot.velDirDeg, bot.velSpeed, bot.hasLastPos)
|
||||
when USE_RESERVOIR:
|
||||
let binInput = toBitVec80(inputs)
|
||||
let binInput = toBinaryInput(relBearing, bot.velDirDeg, bot.velSpeed, bot.hasLastPos)
|
||||
let winBin = bot.res.forward(binInput)
|
||||
let aimAngle = bot.res.interpolatedAngle(winBin)
|
||||
bot.lastBinInput = binInput
|
||||
@@ -419,6 +444,7 @@ method run*(bot: SNNBot) =
|
||||
bot.lastDecideGunDir = gunDir
|
||||
echo "RES tick=" & $bot.tick & " bin=" & $winBin & " aim=" & formatFloat(aimAngle, ffDecimal, 1)
|
||||
else:
|
||||
let inputs = encodeInputFull(relBearing, bot.velDirDeg, bot.velSpeed, bot.hasLastPos)
|
||||
# Multi-tick inference: accumulate sin/cos and spike counts over N_INFER ticks
|
||||
var totalSin = 0.0; var totalCos = 0.0
|
||||
var tickSpikes: array[N_HID, float]
|
||||
@@ -477,7 +503,7 @@ method run*(bot: SNNBot) =
|
||||
echo "RES tick=" & $bot.tick & " bin=" & $winBin &
|
||||
" aim=" & formatFloat(aimAngle, ffDecimal, 1) &
|
||||
" target=" & formatFloat(targetRel, ffDecimal, 1) &
|
||||
" err=" & formatFloat(abs(aimAngle - targetRel), ffDecimal, 1) &
|
||||
" err=" & formatFloat(abs(normalizeRelativeAngle(aimAngle - targetRel)), ffDecimal, 1) &
|
||||
" readout_pop_correct=" & $readoutPopCorrect
|
||||
else:
|
||||
let err = abs(normalizeRelativeAngle(gunDir - bot.enemyBearing))
|
||||
|
||||
Reference in New Issue
Block a user