feat(SNNBot): thermometer speed encoding for precise lead discrimination

3-bit speed encoding couldn't distinguish speed 3 from speed 5 —
9° of lead error baked into the input. Thermometer coding with 8 bits
gives 1-bit Hamming distance between adjacent speeds. MAX_K back to 128.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-09-16 23:10:04 +02:00
parent be4e9549ef
commit cc3c2649c2
2 changed files with 13 additions and 11 deletions
+12 -10
View File
@@ -1,6 +1,6 @@
# SNNBot — SNN core + aiming loop prototype (issue #152).
# 80-input population-coded → 12 LIF hidden → polar-coded (sin/cos) output decoded via atan2.
# Inputs: relVelDir [0-35], speed [36-38], distance thermometer [39-46]. Raw bearing removed (irrelevant to lead).
# Inputs: relVelDir [0-35], speed thermometer [36-43], distance thermometer [44-51]. Raw bearing removed (irrelevant to lead).
# No movement, no firing. SuperSpike three-factor rule on all weights (issue #158).
# Binary reservoir alternative toggled via USE_RESERVOIR const (issue #159).
#
@@ -325,9 +325,10 @@ proc toBinaryInput(bearing: float, velDir: float, velSpeed: float, distance: flo
## Physics-based encoding for lead generalization (issue #152).
## Raw bearing removed — irrelevant to lead offset.
## Bits 0-35: relVelDir = (velDir - bearing) normalized to [-180,180), 36 bins × 10°, 5 active (center ± 2 neighbors).
## Bits 36-38: speed level (3 bits, center ± 1 neighbor).
## Bits 39-46: distance thermometer, 8 bands of ~125px in [0,1000].
## Bits 47-79: unused (sparse is fine for popcount similarity).
## Bits 36-43: speed thermometer (8 bits): speedInt bits set from bit 36 upward, 0-8 active.
## Adjacent speeds differ by 1 bit (Hamming-1), giving smooth generalization.
## Bits 44-51: distance thermometer, 8 bands of ~125px in [0,1000].
## Bits 52-79: unused.
## ponytail: physics-based; revert to absolute bearing if convergence regresses
result = [0'u64, 0'u64]
@@ -342,17 +343,18 @@ proc toBinaryInput(bearing: float, velDir: float, velSpeed: float, distance: flo
let idx = (vCenter + offset + 36) mod 36 # bits 0-35
result[idx div 64] = result[idx div 64] or (1'u64 shl (idx mod 64))
# Speed: bits 36-38, center ± 1 neighbor clamped to [0,2]
let sCenter = clamp(int(velSpeed), 0, 2)
for offset in -1 .. 1:
let idx = 36 + clamp(sCenter + offset, 0, 2)
# Speed thermometer: bits 36-43; set bits 36..(36+speedInt-1)
# speedInt=0 → no bits; speedInt=8 → all 8 bits set
let speedInt = clamp(int(velSpeed + 0.5), 0, 8)
for b in 0 ..< speedInt:
let idx = 36 + b
result[idx div 64] = result[idx div 64] or (1'u64 shl (idx mod 64))
# Distance thermometer: bits 39-46, 8 bands of ~125px over [0,1000]
# Distance thermometer: bits 44-51, 8 bands of ~125px over [0,1000]
# All bits up to the current band set (inclusive)
let band = clamp(int(distance / 125.0), 0, 7)
for b in 0 .. band:
let idx = 39 + b
let idx = 44 + b
result[idx div 64] = result[idx div 64] or (1'u64 shl (idx mod 64))
# ── Main loop ─────────────────────────────────────────────────────────────────