## Displacement-vector gun: predict using average velocity over a sliding window. ## Captures drift/tendency that instantaneous velocity misses ## (e.g. oscillating enemy slowly drifting east has v=0 at midpoints but ## displacement still pointing east). import std/math import gun_harness/gun_interface 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 proc predict*(g: var DisplacementGun, state: WorldState, bulletSpeed: float): GunPrediction = # Per-tick cache: same tick + same speed => same prediction if state.tick == g.lastTick and bulletSpeed == g.cacheSpeed: return g.cachePred # Push current position into ring buffer g.head = (g.head + 1) mod (WindowSize + 1) g.posX[g.head] = state.enemyX g.posY[g.head] = state.enemyY if g.count < WindowSize + 1: inc g.count g.lastTick = state.tick g.cacheSpeed = bulletSpeed # Need at least N+1 frames; fall back to head-on if not enough if g.count < WindowSize + 1: g.cachePred = GunPrediction(x: state.enemyX, y: state.enemyY) return g.cachePred # Oldest frame is (head + 1) mod (WindowSize + 1) let oldest = (g.head + 1) mod (WindowSize + 1) let dx = (state.enemyX - g.posX[oldest]) / WindowSize.float let dy = (state.enemyY - g.posY[oldest]) / WindowSize.float # Iterate time estimate 5 times let dist0 = hypot(state.enemyX - state.selfX, state.enemyY - state.selfY) var ticks = dist0 / bulletSpeed var px = state.enemyX var py = state.enemyY for _ in 0..4: px = state.enemyX + dx * ticks py = state.enemyY + dy * ticks ticks = hypot(px - state.selfX, py - state.selfY) / bulletSpeed px = clamp(px, 0.0, state.arenaWidth) py = clamp(py, 0.0, state.arenaHeight) g.cachePred = GunPrediction(x: px, y: py) g.cachePred proc onResult*(g: var DisplacementGun, e: FeedbackEvent) = discard # analytical gun — no learning proc initDisplacementGun*(): DisplacementGun = DisplacementGun(count: 0, head: 0, lastTick: -1)