fix(ModularBot): ram loop prevention, dead-target guards, cleaner logging

- 30-tick cooldown after ghost-stuck/timeout ram exit prevents re-entry loop
- enemy_tracker.update() skips dead bots to prevent same-tick scan resurrection
- TFIL graphics cleared when ramming is active movement
- [config] logs: white base with green-highlighted changes only
- [ram:enter] logs trigger reason and key values on false→true transition
- [death] and [target-invalid] logs retained for diagnostics
This commit is contained in:
2026-09-20 20:44:45 +02:00
parent 1f8574db1d
commit 2cc2a3bd87
30 changed files with 927 additions and 161 deletions
+9 -8
View File
@@ -9,17 +9,18 @@ import gun_harness/gun_interface
const MaxSpeed = 8.0 ## TR bot max speed (px/tick)
type AccelGun* = object
prevSpeed: float
prevHeading: float
prevPrevSpeed: float
prevTick: int
frames: int ## how many observations we have
cachedAccel: float ## accel (px/tick²) cached per tick
prevSpeed: float
prevHeading: float
prevPrevSpeed: float
prevTick: int
frames: int ## how many observations we have
cachedAccel: float ## accel (px/tick²) cached per tick
cachedTurnRate: float ## turn rate (deg/tick) cached per tick
cachedTick: int
cachedTick: int
debugGraphics*: bool
proc initAccelGun*(): AccelGun =
AccelGun()
AccelGun(debugGraphics: false)
proc predict*(g: var AccelGun, state: WorldState, bulletSpeed: float): GunPrediction =
if bulletSpeed <= 0.0:
+2
View File
@@ -21,9 +21,11 @@ type
waves: seq[ASWave]
cachedTick: int
cachedWaveStored: bool
debugGraphics*: bool
proc initAntiSurferGun*(): AntiSurferGun =
result.cachedTick = -1
result.debugGraphics = false
# Same triangular head-on prior as GFGun — cold-start aims head-on.
let center = (ASBins - 1) div 2 # = 15
for i in 0..<ASBins:
+7 -6
View File
@@ -7,14 +7,15 @@ import guns/circular
import guns/wall_bounce
type AveragedLeadGun* = object
linear: LinearGun
circular: CircularGun
wallBounce: WallBounceGun
cachedTick: int
cachedPred: GunPrediction # per-tick cache; ponytail: single cache, extend if multi-power needed
linear: LinearGun
circular: CircularGun
wallBounce: WallBounceGun
cachedTick: int
cachedPred: GunPrediction # per-tick cache; ponytail: single cache, extend if multi-power needed
debugGraphics*: bool
proc initAveragedLeadGun*(): AveragedLeadGun =
AveragedLeadGun(wallBounce: initWallBounceGun())
AveragedLeadGun(wallBounce: initWallBounceGun(), debugGraphics: false)
proc predict*(g: var AveragedLeadGun, state: WorldState, bulletSpeed: float): GunPrediction =
if state.tick == g.cachedTick:
+6 -5
View File
@@ -6,11 +6,12 @@ import std/math
import gun_harness/gun_interface
type CircularGun* = object
prevHeading: float ## enemy heading from the tick before current
prevTick: int ## tick of prevHeading observation
hasPrev: bool
cachedOmega: float ## omega (rad/tick) computed on first call this tick
cachedTick: int ## tick for which cachedOmega was computed
prevHeading: float ## enemy heading from the tick before current
prevTick: int ## tick of prevHeading observation
hasPrev: bool
cachedOmega: float ## omega (rad/tick) computed on first call this tick
cachedTick: int ## tick for which cachedOmega was computed
debugGraphics*: bool
proc predict*(g: var CircularGun, state: WorldState, bulletSpeed: float): GunPrediction =
if bulletSpeed <= 0.0:
+2
View File
@@ -20,9 +20,11 @@ type
waves: seq[DWave]
cachedTick: int
cachedWaveStored: bool
debugGraphics*: bool
proc initDecayGFGun*(): DecayGFGun =
result.cachedTick = -1
result.debugGraphics = false
let center = (GFBins - 1) div 2
for i in 0..<GFBins:
let d = abs(i - center)
+9 -8
View File
@@ -10,13 +10,14 @@ const WindowSize = 15 # ticks; N+1 frames stored
type
DisplacementGun* = object
posX: array[WindowSize + 1, float]
posY: array[WindowSize + 1, float]
count: int # frames collected so far
head: int # ring-buffer head
lastTick: int # for per-tick cache
cacheSpeed: float
cachePred: GunPrediction
posX: array[WindowSize + 1, float]
posY: array[WindowSize + 1, float]
count: int # frames collected so far
head: int # ring-buffer head
lastTick: int # for per-tick cache
cacheSpeed: float
cachePred: GunPrediction
debugGraphics*: bool
proc predict*(g: var DisplacementGun, state: WorldState, bulletSpeed: float): GunPrediction =
# Per-tick cache: same tick + same speed => same prediction
@@ -62,4 +63,4 @@ proc onResult*(g: var DisplacementGun, e: FeedbackEvent) =
discard # analytical gun — no learning
proc initDisplacementGun*(): DisplacementGun =
DisplacementGun(count: 0, head: 0, lastTick: -1)
DisplacementGun(count: 0, head: 0, lastTick: -1, debugGraphics: false)
+5 -3
View File
@@ -17,14 +17,16 @@ type
# mea not stored — recomputed from FeedbackEvent.bulletPower at resolution time
GFGun* = object
bins: array[GFBins, float]
waves: seq[Wave] # pending unresolved waves
bins: array[GFBins, float]
waves: seq[Wave] # pending unresolved waves
# per-tick cache: store wave only once across multiple power-bin calls
cachedTick: int
cachedTick: int
cachedWaveStored: bool
debugGraphics*: bool
proc initGFGun*(): GFGun =
result.cachedTick = -1
result.debugGraphics = false
# Seed with a head-on prior: triangular bump at bin 15 (GF=0).
# Prevents the cold-start tie-break to GF=-1 (bin 0) that poisons early fitness.
let center = (GFBins - 1) div 2 # = 15
+1 -1
View File
@@ -4,7 +4,7 @@
import gun_harness/gun_interface
type HeadOnGun* = object
discard
debugGraphics*: bool
proc predict*(g: var HeadOnGun, state: WorldState, bulletSpeed: float): GunPrediction =
GunPrediction(x: state.enemyX, y: state.enemyY)
+12 -10
View File
@@ -23,23 +23,25 @@ type
feat: array[7, float]
KNNGun* = object
obs: seq[Obs]
obsHead: int # ring-buffer write index
waves: seq[KNNWave]
obs: seq[Obs]
obsHead: int # ring-buffer write index
waves: seq[KNNWave]
# per-tick cache
cachedTick: int
cachedWaveStored: bool
cachedTick: int
cachedWaveStored: bool
# rolling normalization ranges
featMin: array[7, float]
featMax: array[7, float]
featMin: array[7, float]
featMax: array[7, float]
# state for feature extraction
lastSpeed: float
lastDirection: float # +1 or -1
lastSpeed: float
lastDirection: float # +1 or -1
timeSinceDirChange: int
debugGraphics*: bool
proc initKNNGun*(): KNNGun =
result.cachedTick = -1
result.cachedTick = -1
result.lastDirection = 1.0
result.debugGraphics = false
for i in 0..6:
result.featMin[i] = 1e18
result.featMax[i] = -1e18
+1 -1
View File
@@ -5,7 +5,7 @@ import std/math
import gun_harness/gun_interface
type LinearGun* = object
discard
debugGraphics*: bool
proc predict*(g: var LinearGun, state: WorldState, bulletSpeed: float): GunPrediction =
let dist = hypot(state.enemyX - state.selfX, state.enemyY - state.selfY)
+12 -11
View File
@@ -16,18 +16,19 @@ type
headingDelta: float ## heading change in radians this tick
PatternMatcherGun* = object
buf: array[HistorySize, MoveTick]
head: int ## next write index (circular)
count: int ## filled entries (capped at HistorySize)
prevHeading: float
prevSpeed: float
prevTick: int
hasPrev: bool
buf: array[HistorySize, MoveTick]
head: int ## next write index (circular)
count: int ## filled entries (capped at HistorySize)
prevHeading: float
prevSpeed: float
prevTick: int
hasPrev: bool
# per-tick cache — avoid re-searching for multiple power bins
cacheTick: int
cacheX: float
cacheY: float
cacheValid: bool
cacheTick: int
cacheX: float
cacheY: float
cacheValid: bool
debugGraphics*: bool
# --- circular buffer helpers ---
+9 -8
View File
@@ -11,15 +11,16 @@ const BrakeDecel = 2.0 ## same-direction stop
const CoastDecel = 1.0 ## cross-zero coasting stop
type StopShotGun* = object
prevSpeed: float
prevHeading: float
prevTick: int
frames: int
cachedTick: int
cachedPredX: float
cachedPredY: float
prevSpeed: float
prevHeading: float
prevTick: int
frames: int
cachedTick: int
cachedPredX: float
cachedPredY: float
debugGraphics*: bool
proc initStopShotGun*(): StopShotGun = StopShotGun()
proc initStopShotGun*(): StopShotGun = StopShotGun(debugGraphics: false)
proc predict*(g: var StopShotGun, state: WorldState, bulletSpeed: float): GunPrediction =
if bulletSpeed <= 0.0:
+8 -6
View File
@@ -200,17 +200,19 @@ type
alive: bool
TsetlinGun* = object
net: TmNet
frameBuffer: array[TM_WINDOW_SIZE, TmFrameEncoded]
bufferCount: int
traces: array[TM_TRACE_SLOTS, TmTrace]
traceHead: int
shotCount: int ## total onResult calls received
net: TmNet
frameBuffer: array[TM_WINDOW_SIZE, TmFrameEncoded]
bufferCount: int
traces: array[TM_TRACE_SLOTS, TmTrace]
traceHead: int
shotCount: int ## total onResult calls received
debugGraphics*: bool
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()
result.debugGraphics = false
proc isWarmedUp*(g: TsetlinGun): bool {.inline.} =
g.bufferCount >= TM_WINDOW_SIZE
+2 -2
View File
@@ -6,9 +6,9 @@ import std/math
import gun_harness/gun_interface
type WallBounceGun* = object
discard
debugGraphics*: bool
proc initWallBounceGun*(): WallBounceGun = WallBounceGun()
proc initWallBounceGun*(): WallBounceGun = WallBounceGun(debugGraphics: false)
proc simulateBounce(ex, ey, heading, speed: float, ticks: int,
arenaW, arenaH: float): (float, float) =