feat(BNNBot): 10-tick sliding window of enemy data + self state — 870 bits total

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-09-17 23:35:57 +02:00
parent 89f63540bc
commit 50e3c6fae9
2 changed files with 122 additions and 84 deletions
+63 -43
View File
@@ -1,27 +1,32 @@
import std/math
const
TOTAL_BITS* = 123
FRAME_BITS* = 83
SELF_BITS* = 40
WINDOW_SIZE* = 10
TOTAL_BITS* = FRAME_BITS * WINDOW_SIZE + SELF_BITS # 870
type
BinaryVector* = array[TOTAL_BITS, uint8]
ScanData* = object
bearing*: float # 0-360 degrees
distance*: float # 0-1414 pixels (will be converted to 0-100%)
velocity*: float # -8 to +8 integer steps
heading*: float # 0-360 degrees
myWallN*: float # our distance to north wall
myWallS*: float # our distance to south wall
myWallE*: float # our distance to east wall
myWallW*: float # our distance to west wall
enemyWallN*: float # enemy distance to north wall
enemyWallS*: float # enemy distance to south wall
enemyWallE*: float # enemy distance to east wall
enemyWallW*: float # enemy distance to west wall
myEnergy*: float # 0-150
enemyEnergy*: float # 0-150
canFire*: bool # gun heat == 0
EnemyScanFrame* = object
bearing*: float # → sin/cos, 8+8 bits
distance*: float # → %, 7 bits
velocity*: float # → 5 bits
heading*: float # → sin/cos, 8+8 bits
enemyWallN*: float # 7 bits
enemyWallS*: float # 7 bits
enemyWallE*: float # 7 bits
enemyWallW*: float # 7 bits
enemyEnergy*: float # 11 bits
SelfState* = object
myWallN*: float # 7 bits
myWallS*: float # 7 bits
myWallE*: float # 7 bits
myWallW*: float # 7 bits
myEnergy*: float # 11 bits
canFire*: bool # 1 bit
const MAX_DISTANCE = 1414.0 # diagonal of 1000x1000 arena
@@ -34,73 +39,88 @@ proc toBits(value: int, bits: int): seq[uint8] =
for i in 0..<bits:
result[bits - 1 - i] = uint8((gray shr i) and 1)
proc encode*(scan: ScanData): BinaryVector =
proc encodeFrame*(frame: EnemyScanFrame): array[FRAME_BITS, uint8] =
var offset = 0
# 1. bearing_sin: sin(bearing), -1..+1 → 0..199 → 8 bits
let bSin = int((sin(degToRad(scan.bearing)) + 1.0) / 2.0 * 199.0)
# bearing sin: 8 bits
let bSin = int((sin(degToRad(frame.bearing)) + 1.0) / 2.0 * 199.0)
let bSinBits = toBits(clamp(bSin, 0, 199), 8)
for i in 0..<8: result[offset + i] = bSinBits[i]
offset += 8
# 2. bearing_cos: cos(bearing), -1..+1 → 0..199 → 8 bits
let bCos = int((cos(degToRad(scan.bearing)) + 1.0) / 2.0 * 199.0)
# bearing cos: 8 bits
let bCos = int((cos(degToRad(frame.bearing)) + 1.0) / 2.0 * 199.0)
let bCosBits = toBits(clamp(bCos, 0, 199), 8)
for i in 0..<8: result[offset + i] = bCosBits[i]
offset += 8
# 3. distance %: 0-100% of max → 0..99 → 7 bits
let distPct = int(clamp(scan.distance / MAX_DISTANCE * 99.0, 0.0, 99.0))
# distance %: 7 bits
let distPct = int(clamp(frame.distance / MAX_DISTANCE * 99.0, 0.0, 99.0))
let distBits = toBits(distPct, 7)
for i in 0..<7: result[offset + i] = distBits[i]
offset += 7
# 4. velocity: -8..+8 → 0..16 → 5 bits
let vel = int(clamp(scan.velocity + 8.0, 0.0, 16.0))
# velocity: 5 bits
let vel = int(clamp(frame.velocity + 8.0, 0.0, 16.0))
let velBits = toBits(vel, 5)
for i in 0..<5: result[offset + i] = velBits[i]
offset += 5
# 5. heading_sin: sin(heading), -1..+1 → 0..199 → 8 bits
let hSin = int((sin(degToRad(scan.heading)) + 1.0) / 2.0 * 199.0)
# heading sin: 8 bits
let hSin = int((sin(degToRad(frame.heading)) + 1.0) / 2.0 * 199.0)
let hSinBits = toBits(clamp(hSin, 0, 199), 8)
for i in 0..<8: result[offset + i] = hSinBits[i]
offset += 8
# 6. heading_cos: cos(heading), -1..+1 → 0..199 → 8 bits
let hCos = int((cos(degToRad(scan.heading)) + 1.0) / 2.0 * 199.0)
# heading cos: 8 bits
let hCos = int((cos(degToRad(frame.heading)) + 1.0) / 2.0 * 199.0)
let hCosBits = toBits(clamp(hCos, 0, 199), 8)
for i in 0..<8: result[offset + i] = hCosBits[i]
offset += 8
# 7. My wall distances (7 bits each, 0-100% of arena dimension)
for wall in [scan.myWallN, scan.myWallS, scan.myWallE, scan.myWallW]:
# enemy wall distances: 4×7 bits
for wall in [frame.enemyWallN, frame.enemyWallS, frame.enemyWallE, frame.enemyWallW]:
let pct = int(clamp(wall / 1000.0 * 99.0, 0.0, 99.0))
let bits = toBits(pct, 7)
for i in 0..<7: result[offset + i] = bits[i]
offset += 7
# 8. Enemy wall distances (7 bits each)
for wall in [scan.enemyWallN, scan.enemyWallS, scan.enemyWallE, scan.enemyWallW]:
# enemy energy: 11 bits
let enE = int(clamp(frame.enemyEnergy * 10.0, 0.0, 1500.0))
let enEBits = toBits(enE, 11)
for i in 0..<11: result[offset + i] = enEBits[i]
# offset += 11 # last field, no need
proc encodeSelf*(self: SelfState): array[SELF_BITS, uint8] =
var offset = 0
# my wall distances: 4×7 bits
for wall in [self.myWallN, self.myWallS, self.myWallE, self.myWallW]:
let pct = int(clamp(wall / 1000.0 * 99.0, 0.0, 99.0))
let bits = toBits(pct, 7)
for i in 0..<7: result[offset + i] = bits[i]
offset += 7
# 9. My energy (11 bits, 0-150 → 0-1500 with 0.1 HP precision)
let myE = int(clamp(scan.myEnergy * 10.0, 0.0, 1500.0))
# my energy: 11 bits
let myE = int(clamp(self.myEnergy * 10.0, 0.0, 1500.0))
let myEBits = toBits(myE, 11)
for i in 0..<11: result[offset + i] = myEBits[i]
offset += 11
# 10. Enemy energy (11 bits, 0-150 → 0-1500 with 0.1 HP precision)
let enE = int(clamp(scan.enemyEnergy * 10.0, 0.0, 1500.0))
let enEBits = toBits(enE, 11)
for i in 0..<11: result[offset + i] = enEBits[i]
offset += 11
# canFire: 1 bit
result[offset] = if self.canFire: 1'u8 else: 0'u8
# 11. Can fire (1 bit)
result[offset] = if scan.canFire: 1'u8 else: 0'u8
proc encodeFullVector*(window: array[WINDOW_SIZE, array[FRAME_BITS, uint8]],
self: array[SELF_BITS, uint8]): BinaryVector =
var offset = 0
# LIFO: index 0 = newest frame
for i in 0..<WINDOW_SIZE:
for j in 0..<FRAME_BITS:
result[offset] = window[i][j]
inc offset
for j in 0..<SELF_BITS:
result[offset] = self[j]
inc offset
proc formatBinary*(vec: BinaryVector): string =
for b in vec: