feat(ModularBot): KNN gun, gunheat tracker, bullet shadows — inspired by DrussGT
New DrussGT-inspired modules: - KNNGun: K-nearest-neighbor statistical targeting using GF density peaks - GunheatTracker: dual-heat system (predicted + confirmed) for 1-2 tick lead - ShadowTracker: computes GF regions safe from in-flight bullets (enemy wave dodge) VirtualBodyTracker now integrates gunheat for earlier fire detection and shadows for safe-zone multiplier (90% reduction in danger zones). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -6,6 +6,8 @@
|
||||
import std/math
|
||||
import gun_harness/gun_interface
|
||||
import movement_harness/movement_interface
|
||||
import movement_harness/gunheat_tracker
|
||||
import movement_harness/bullet_shadows
|
||||
|
||||
const
|
||||
VB_BINS* = 31 ## GF bins from -1 to +1
|
||||
@@ -34,12 +36,13 @@ type
|
||||
waves: array[MaxWaves, VBWave]
|
||||
waveCount: int
|
||||
waveHead: int ## ring buffer head
|
||||
prevEnemyEnergy: float
|
||||
gunheat: GunheatTracker
|
||||
shadows*: ShadowTracker ## our bullets in flight for shadow computation
|
||||
arenaW, arenaH: float
|
||||
|
||||
proc initVirtualBodyTracker*(numModules: int): VirtualBodyTracker =
|
||||
result.numModules = numModules
|
||||
result.prevEnemyEnergy = 100.0
|
||||
result.numModules = numModules
|
||||
result.gunheat = initGunheatTracker()
|
||||
# Seed bins so we have a uniform prior before any real hits
|
||||
for i in 0..<VB_BINS: result.dangerBins[i] = 1.0
|
||||
|
||||
@@ -48,9 +51,9 @@ proc resetRound*(t: var VirtualBodyTracker, startX, startY, startHeading, startS
|
||||
for i in 0..<t.numModules:
|
||||
t.bodies[i] = VirtualBody(x: startX, y: startY,
|
||||
heading: startHeading, speed: startSpeed)
|
||||
t.waveCount = 0
|
||||
t.waveHead = 0
|
||||
t.prevEnemyEnergy = 100.0
|
||||
t.waveCount = 0
|
||||
t.waveHead = 0
|
||||
t.gunheat.resetRound()
|
||||
|
||||
proc registerHit*(t: var VirtualBodyTracker, bulletPower: float, bulletHeadingDeg: float,
|
||||
realX, realY: float) =
|
||||
@@ -92,22 +95,21 @@ proc tick*[N: static int](t: var VirtualBodyTracker, state: WorldState,
|
||||
## Per-tick update. cmds[i] is computeMove() output of module i.
|
||||
## Call AFTER collecting all module commands for this tick.
|
||||
|
||||
# --- Fire detection ---
|
||||
let drop = t.prevEnemyEnergy - state.enemyEnergy
|
||||
t.prevEnemyEnergy = state.enemyEnergy
|
||||
if drop >= 0.1 and drop <= 3.0:
|
||||
let bspeed = 20.0 - 3.0 * drop
|
||||
let bearingRad = arctan2(state.selfY - state.enemyY, state.selfX - state.enemyX)
|
||||
let d = hypot(state.selfX - state.enemyX, state.selfY - state.enemyY)
|
||||
# --- Fire detection via gunheat tracker ---
|
||||
let waveEvents = t.gunheat.tick(state)
|
||||
for ev in waveEvents:
|
||||
let bspeed = 20.0 - 3.0 * ev.bulletPower
|
||||
let bearingRad = arctan2(state.selfY - ev.fireY, state.selfX - ev.fireX)
|
||||
let d = hypot(state.selfX - ev.fireX, state.selfY - ev.fireY)
|
||||
let slot = t.waveHead mod MaxWaves
|
||||
t.waves[slot] = VBWave(
|
||||
fireX: state.enemyX,
|
||||
fireY: state.enemyY,
|
||||
fireX: ev.fireX,
|
||||
fireY: ev.fireY,
|
||||
fireBearingRad: bearingRad,
|
||||
speed: bspeed,
|
||||
radius: 0.0,
|
||||
fireTick: state.tick,
|
||||
startDist: d,
|
||||
speed: bspeed,
|
||||
radius: 0.0,
|
||||
fireTick: ev.tick,
|
||||
startDist: d,
|
||||
)
|
||||
t.waveHead = (t.waveHead + 1) mod MaxWaves
|
||||
if t.waveCount < MaxWaves: inc t.waveCount
|
||||
@@ -119,6 +121,9 @@ proc tick*[N: static int](t: var VirtualBodyTracker, state: WorldState,
|
||||
if i < N:
|
||||
advanceBody(t.bodies[i], cmds[i], state.arenaWidth, state.arenaHeight)
|
||||
|
||||
# --- Advance our bullets (for shadow tracking) ---
|
||||
t.shadows.tick()
|
||||
|
||||
# --- Advance waves and score ---
|
||||
# ponytail: O(waves * modules), small counts, fine
|
||||
for wi in 0..<t.waveCount:
|
||||
@@ -126,6 +131,9 @@ proc tick*[N: static int](t: var VirtualBodyTracker, state: WorldState,
|
||||
var w = addr t.waves[idx]
|
||||
if w.speed <= 0.0: continue
|
||||
w.radius += w.speed
|
||||
# Compute bullet shadows for this wave once; reuse across all virtual bodies
|
||||
let waveShadows = t.shadows.getShadows(w.fireX, w.fireY,
|
||||
w.fireBearingRad, w.radius, w.speed)
|
||||
for mi in 0..<t.numModules:
|
||||
let bx = t.bodies[mi].x
|
||||
let by = t.bodies[mi].y
|
||||
@@ -140,7 +148,9 @@ proc tick*[N: static int](t: var VirtualBodyTracker, state: WorldState,
|
||||
if maxA >= 1e-9:
|
||||
let gf = clamp(off / maxA, -1.0, 1.0)
|
||||
let bin = gfToBin(gf)
|
||||
t.dangerScore[mi] += t.dangerBins[bin]
|
||||
# Shadow zones are guaranteed safe — reduce danger by 90%
|
||||
let shadowMul = if isShadowed(waveShadows, gf): 0.1 else: 1.0
|
||||
t.dangerScore[mi] += t.dangerBins[bin] * shadowMul
|
||||
|
||||
proc bestMovement*(t: VirtualBodyTracker): int =
|
||||
## Returns index of module with lowest accumulated danger score.
|
||||
|
||||
Reference in New Issue
Block a user