## Virtual body tracker — mirrors virtual_bullets.nim but for movement modules. ## Tracks simulated positions for each movement candidate under enemy waves. ## Scores modules by how often they sit in high-danger GF bins. ## Caller feeds actual hit data via registerHit() to build the danger profile. import std/math import gun_harness/gun_interface import movement_harness/movement_interface const VB_BINS* = 31 ## GF bins from -1 to +1 MaxWaves* = 64 ## hard cap; ponytail: ring buffer, resize if needed MaxModules* = 8 ## max movement modules tracked type VBWave = object fireX, fireY: float fireBearingRad: float ## bearing from fireOrigin to REAL bot at fire time (radians) speed: float radius: float fireTick: int startDist: float VirtualBody = object x, y: float heading: float ## degrees (Tank Royale / bot API) speed: float ## current speed px/tick VirtualBodyTracker* = object numModules: int bodies: array[MaxModules, VirtualBody] dangerScore: array[MaxModules, float] ## accumulated danger hits dangerBins: array[VB_BINS, float] ## populated from registerHit waves: array[MaxWaves, VBWave] waveCount: int waveHead: int ## ring buffer head prevEnemyEnergy: float arenaW, arenaH: float proc initVirtualBodyTracker*(numModules: int): VirtualBodyTracker = result.numModules = numModules result.prevEnemyEnergy = 100.0 # Seed bins so we have a uniform prior before any real hits for i in 0..= 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) let slot = t.waveHead mod MaxWaves t.waves[slot] = VBWave( fireX: state.enemyX, fireY: state.enemyY, fireBearingRad: bearingRad, speed: bspeed, radius: 0.0, fireTick: state.tick, startDist: d, ) t.waveHead = (t.waveHead + 1) mod MaxWaves if t.waveCount < MaxWaves: inc t.waveCount # --- Advance virtual bodies --- for i in 0.. MaxModules: {.error: "cmds array exceeds MaxModules".} if i < N: advanceBody(t.bodies[i], cmds[i], state.arenaWidth, state.arenaHeight) # --- Advance waves and score --- # ponytail: O(waves * modules), small counts, fine for wi in 0.. PI: off -= 2.0 * PI while off < -PI: off += 2.0 * PI let maxA = mea(w.speed) if maxA >= 1e-9: let gf = clamp(off / maxA, -1.0, 1.0) let bin = gfToBin(gf) t.dangerScore[mi] += t.dangerBins[bin] proc bestMovement*(t: VirtualBodyTracker): int = ## Returns index of module with lowest accumulated danger score. result = 0 var best = t.dangerScore[0] for i in 1..