feat(ModularBot): 6 guns, pattern matcher, melee modules, adversarial bots
- New guns: guess-factor (GF histogram), pattern-matcher (movement tape replay) - New modules: minimum-risk melee movement, spinning melee radar - New test bots: PatternMover, RandomMover, WaveSurfer - Fixed: FeedbackEvent now carries actualX/actualY for proper GF learning - Fixed: TM gun warmup gating + directional residuals - Fixed: circular gun integrated formula + multi-bin omega cache - Fixed: oscillator wall-bounce lockout - Fixed: phantom meteor perpendicular body orientation - 6/6 battle wins across all enemy types
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
## Self-contained: includes binary encoding and TM predictor inline.
|
||||
## Implements Gun interface: predict(state, bulletSpeed) → GunPrediction, onResult(FeedbackEvent).
|
||||
|
||||
import std/[math, random]
|
||||
import std/[math, random, strformat]
|
||||
import gun_harness/gun_interface
|
||||
|
||||
# ── Binary encoding (adapted from BNNBot_garage/src/binary_encoding.nim) ─────
|
||||
@@ -190,6 +190,7 @@ proc tmLearnOne(net: var TmNet, outIdx: int, lits: array[TM_N_LITERALS, uint8],
|
||||
const
|
||||
TM_TRACE_SLOTS = 64 # ring buffer of pending traces
|
||||
# ponytail: 64 slots >> TRACE_MAX_AGE=40 ticks, safe margin; grow if many guns/bins
|
||||
DebugTM* = false # set true to print [tm-dbg] lines per onResult call
|
||||
|
||||
type
|
||||
TmTrace = object
|
||||
@@ -204,12 +205,16 @@ type
|
||||
bufferCount: int
|
||||
traces: array[TM_TRACE_SLOTS, TmTrace]
|
||||
traceHead: int
|
||||
shotCount: int ## total onResult calls received
|
||||
|
||||
proc initTsetlinGun*(): TsetlinGun =
|
||||
# states init at 0 (boundary); one Type I step crosses into Include
|
||||
for s in result.net.states.mitems: s = 0'i16
|
||||
randomize()
|
||||
|
||||
proc isWarmedUp*(g: TsetlinGun): bool {.inline.} =
|
||||
g.bufferCount >= TM_WINDOW_SIZE
|
||||
|
||||
proc predict*(g: var TsetlinGun, state: WorldState, bulletSpeed: float): GunPrediction =
|
||||
# Encode current frame and push into window
|
||||
let dist = hypot(state.enemyX - state.selfX, state.enemyY - state.selfY)
|
||||
@@ -259,28 +264,21 @@ proc predict*(g: var TsetlinGun, state: WorldState, bulletSpeed: float): GunPred
|
||||
GunPrediction(x: predX, y: predY)
|
||||
|
||||
proc onResult*(g: var TsetlinGun, e: FeedbackEvent) =
|
||||
inc g.shotCount
|
||||
# Find matching trace by prediction coords
|
||||
for i in 0..<TM_TRACE_SLOTS:
|
||||
var t = addr g.traces[i]
|
||||
if not t.alive: continue
|
||||
if abs(t.predX - e.prediction.x) > 0.01 or abs(t.predY - e.prediction.y) > 0.01:
|
||||
continue
|
||||
# Shaped reward: residual = (actual enemy pos) - (bullet impact pos)
|
||||
# FeedbackEvent carries missDistance but not the direction.
|
||||
# We reconstruct: aimed at (predX, predY); miss is distance to current enemy.
|
||||
# Use miss distance as magnitude; direction unknown → scale back along aim vector.
|
||||
# ponytail: zero-direction residual when miss=0 is fine; TM learns from magnitude via pFeedback
|
||||
let missSign = if e.hit: 0.0 else: 1.0
|
||||
let residualX = (e.prediction.x - t.predX) * missSign # trivially 0; real signal is missDistance
|
||||
# Better: treat miss distance as residual magnitude along (enemy - pred) direction
|
||||
# We don't have enemy pos here directly, but we can scale correction proportionally.
|
||||
# Simplest correct signal: pass missDistance as residual magnitude for both dims.
|
||||
let rMag = e.missDistance * missSign
|
||||
# Directional residual: actual enemy pos minus our prediction
|
||||
# On hit residual is 0 (we were right); on miss we push toward actual position.
|
||||
let rx = if e.hit: 0.0 else: clamp(e.actualX - t.predX, -TM_RESID_MAX, TM_RESID_MAX)
|
||||
let ry = if e.hit: 0.0 else: clamp(e.actualY - t.predY, -TM_RESID_MAX, TM_RESID_MAX)
|
||||
let lits = tmMakeLiterals(t.input)
|
||||
# Apply residual equally to both axes (we don't know direction split)
|
||||
# ponytail: split 50/50; upgrade to directional when FeedbackEvent carries enemy pos
|
||||
let r = rMag / sqrt(2.0)
|
||||
g.net.tmLearnOne(0, lits, t.cache, r)
|
||||
g.net.tmLearnOne(1, lits, t.cache, r)
|
||||
g.net.tmLearnOne(0, lits, t.cache, rx)
|
||||
g.net.tmLearnOne(1, lits, t.cache, ry)
|
||||
when DebugTM:
|
||||
echo fmt"[tm-dbg] shot={g.shotCount} miss={e.missDistance:.1f}px predicted=({t.predX:.0f},{t.predY:.0f}) actual=({e.actualX:.0f},{e.actualY:.0f}) rx={rx:.1f} ry={ry:.1f} hit={e.hit}"
|
||||
t.alive = false
|
||||
break
|
||||
|
||||
Reference in New Issue
Block a user