Lesson 2 of N — SNNBot_garage/src/SNNBot.nim
In Lesson 1, you saw the architecture: 80 input neurons → 12 hidden LIF neurons → 2 output channels (sin/cos). This lesson breaks open each piece — how neurons fire, how inputs are encoded, how the network learns. Every code snippet is from YOUR implementation in SNNBot.nim.
The LIF model as implemented:
V[t] = LEAK × V[t-1] + weighted_input
if V >= THRESH: spike! V = 0
Key constants from the code:
LEAK = 0.9 — membrane leaks 10% per tick — neuron "forgets" over ~10 ticksTHRESH = 0.08 — very low threshold — easy to fireThe exact forward pass code for the hidden layer:
for h in 0 ..< N_HID:
var wsum = 0.0
for i in 0 ..< N_IN:
wsum += inputs[i] * snn.wih[i * N_HID + h]
snn.vHid[h] = LEAK * snn.vHid[h] + wsum
if snn.vHid[h] >= THRESH:
spikesOut[h] = 1.0
snn.vHid[h] = 0.0 # reset
else:
spikesOut[h] = 0.0
Continuous values (bearing, velocity direction, speed) must become spike patterns. The bearing encoder uses 36 neurons covering 360°, each neuron representing a 10° band:
proc encodeBearing(inputs: var array[N_IN, float], bearing: float, offset: int) =
let norm = ((bearing + 180.0) / BAND_DEG) # 0..36
let lo = int(norm) mod 36
let hi = (lo + 1) mod 36
let frac = norm - float(int(norm))
inputs[offset + lo] = 1.0 - frac # stronger for closer band
inputs[offset + hi] = frac # weaker for farther band
Triangular interpolation in action:
bearing = 25°
band 2 (20°): activation = 0.5 ▓▓▓▓▓░░░░░
band 3 (30°): activation = 0.5 ▓▓▓▓▓░░░░░
all others: activation = 0.0 ░░░░░░░░░░
The 80-neuron layout:
The output layer sums hidden spikes weighted by learned coefficients:
sinOut = 0.0; cosOut = 0.0
for h in 0 ..< N_HID:
sinOut += spikesOut[h] * snn.wSin[h]
cosOut += spikesOut[h] * snn.wCos[h]
Then decoded: angle = atan2(sinOut, cosOut) × 180/π
N_INFER = 10: the network runs 10 ticks on the SAME input, accumulating sin/cos outputs. This averaging stabilizes the output — a single tick's spikes are noisy (binary), but the average over 10 ticks is smooth.
A spike is binary: 0 or 1. You can't take the gradient of a step function — it's zero everywhere except at the threshold, where it's infinity. So backpropagation doesn't work directly.
SuperSpike replaces the true derivative with a smooth surrogate:
proc surrogateDerivative(v: float): float =
let x = BETA * (v - THRESH)
result = 1.0 / ((1.0 + abs(x)) * (1.0 + abs(x)))
This is a bell curve centered at v = THRESH. It is large when the voltage is NEAR the threshold (the neuron almost spiked or just barely spiked), and small when far from threshold (irrelevant neurons don't learn).
Each weight update is the product of THREE factors.
For hidden→output weights (wSin, wCos):
Δw = η × rate_h × σ'(V_h) × error
All three must be non-zero for learning to happen. A neuron that didn't fire (rate=0) doesn't learn. A neuron far from threshold (σ'≈0) doesn't learn. If the output is correct (error=0), nothing learns.
For input→hidden weights (wih):
Δw = η_ih × preTrace_i × σ'(V_h) × error_h
How does the hidden layer know its error? In backprop, you'd use the transpose of the output weights. SuperSpike uses RANDOM FIXED weights instead:
let errHid = snn.bFb[h * 2 + 0] * errSin + snn.bFb[h * 2 + 1] * errCos
These bFb weights are initialized randomly and NEVER updated. This is called feedback alignment — a controversial but effective shortcut. The hidden layer learns to align its representation with these random projections.
proc initSNN(snn: var SNN) =
for w in snn.wih.mitems: w = rand(0.2) - 0.1 # ±0.1
for w in snn.wSin.mitems: w = rand(0.2) - 0.1
for w in snn.wCos.mitems: w = rand(0.2) - 0.1
for b in snn.bFb.mitems: b = rand(2.0) - 1.0 # ±1.0, fixed forever
The honest answer:
targetAngle = gunDir + snnAngle (RELATIVE to gun), not absolute bearing. This is the feedback loop bug that was fixed for the grid path. The SNN path still has this bug.
bFb) instead of transposing the output weights?targetAngle = gunDir + snnAngle. As the gun turns, gunDir changes, which changes the bearing inputs, which changes snnAngle. The output feeds back into the input, creating instability.You now understand both paths in your bot. The grid works but can't scale. The SNN can learn but hasn't been tested with the pipeline fixes. In the next lesson, we'll activate the SNN, fix the feedback loop bug, and run it against Walls — your first live SNN training run.