feat(BNNBot): scaffold binary neural network bot with scan data collection and binary encoding visualization

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-09-17 22:15:51 +02:00
parent 85005c1c27
commit 808ae44703
7 changed files with 254 additions and 0 deletions
+127
View File
@@ -0,0 +1,127 @@
## binary_encoding — scan data → binary vector for BNNBot.
## Encoding scheme:
## bearing/heading: population code (2 adjacent bits, triangular interpolation)
## distance/speed: thermometer (all bits below threshold active)
## velocity: signed thermometer (first half = negative, second half = positive)
import std/math
const
BEARING_BITS* = 36 # 360° / 10° per bit
DISTANCE_BITS* = 16 # 0-2000 units, ~125 per bit (thermometer)
VELOCITY_BITS* = 16 # -8 to +8, signed thermometer
HEADING_BITS* = 36 # 360° / 10° per bit
SPEED_BITS* = 8 # 0-8, 1 per bit (thermometer)
TOTAL_BITS* = BEARING_BITS + DISTANCE_BITS + VELOCITY_BITS + HEADING_BITS + SPEED_BITS # 112
type
BinaryVector* = array[TOTAL_BITS, uint8] # 0 or 1
ScanData* = object
bearing*: float # absolute bearing to enemy (degrees)
distance*: float # distance to enemy
velocity*: float # enemy velocity (negative = moving backward)
heading*: float # enemy heading (degrees)
speed*: float # enemy speed (always positive)
proc encodeBearing*(degrees: float, bits: int): seq[uint8] =
## Population code: 2 adjacent bits active via triangular interpolation.
result = newSeq[uint8](bits)
let norm = ((degrees mod 360.0) + 360.0) mod 360.0
let bandWidth = 360.0 / float(bits)
let idx = norm / bandWidth
let lo = int(idx) mod bits
let hi = (lo + 1) mod bits
result[lo] = 1
result[hi] = 1
proc encodeThermometer*(value, minVal, maxVal: float, bits: int): seq[uint8] =
## Thermometer code: all bits below threshold are 1.
result = newSeq[uint8](bits)
let clamped = clamp(value, minVal, maxVal)
let norm = (clamped - minVal) / (maxVal - minVal)
let active = int(norm * float(bits))
for i in 0..<active:
result[i] = 1
proc encodeSignedThermometer*(value, maxAbs: float, bits: int): seq[uint8] =
## Signed thermometer: first half = negative magnitude, second half = positive magnitude.
result = newSeq[uint8](bits)
let halfBits = bits div 2
let clamped = clamp(value, -maxAbs, maxAbs)
if clamped >= 0:
let norm = clamped / maxAbs
let active = int(norm * float(halfBits))
for i in 0..<active:
result[halfBits + i] = 1
else:
let norm = -clamped / maxAbs
let active = int(norm * float(halfBits))
for i in countdown(halfBits - 1, halfBits - active):
result[i] = 1
proc encode*(scan: ScanData): BinaryVector =
## Encode full scan data into a 112-bit binary vector.
var offset = 0
let bearBits = encodeBearing(scan.bearing, BEARING_BITS)
for i in 0..<BEARING_BITS:
result[offset + i] = bearBits[i]
offset += BEARING_BITS
let distBits = encodeThermometer(scan.distance, 0.0, 2000.0, DISTANCE_BITS)
for i in 0..<DISTANCE_BITS:
result[offset + i] = distBits[i]
offset += DISTANCE_BITS
let velBits = encodeSignedThermometer(scan.velocity, 8.0, VELOCITY_BITS)
for i in 0..<VELOCITY_BITS:
result[offset + i] = velBits[i]
offset += VELOCITY_BITS
let headBits = encodeBearing(scan.heading, HEADING_BITS)
for i in 0..<HEADING_BITS:
result[offset + i] = headBits[i]
offset += HEADING_BITS
let spdBits = encodeThermometer(scan.speed, 0.0, 8.0, SPEED_BITS)
for i in 0..<SPEED_BITS:
result[offset + i] = spdBits[i]
proc formatBinary*(vec: BinaryVector): string =
## Pretty-print binary vector with labeled sections.
result = "BIN ["
result &= "B:"
for i in 0..<BEARING_BITS:
result &= (if vec[i] == 1: "█" else: "░")
result &= "|"
var offset = BEARING_BITS
result &= "D:"
for i in 0..<DISTANCE_BITS:
result &= (if vec[offset + i] == 1: "█" else: "░")
result &= "|"
offset += DISTANCE_BITS
result &= "V:"
for i in 0..<VELOCITY_BITS:
result &= (if vec[offset + i] == 1: "█" else: "░")
result &= "|"
offset += VELOCITY_BITS
result &= "H:"
for i in 0..<HEADING_BITS:
result &= (if vec[offset + i] == 1: "█" else: "░")
result &= "|"
offset += HEADING_BITS
result &= "S:"
for i in 0..<SPEED_BITS:
result &= (if vec[offset + i] == 1: "█" else: "░")
result &= "]"
proc popcount*(vec: BinaryVector): int =
## Count active bits.
for b in vec:
result += int(b)
proc hammingDistance*(a, b: BinaryVector): int =
## XOR + popcount.
for i in 0..<TOTAL_BITS:
result += int(a[i] xor b[i])