## Bullet shadow tracker — computes GF regions guaranteed safe because ## our in-flight bullets would intercept an enemy bullet traveling there. ## ## Geometry: 0° = East, X = East, Y = North (Tank Royale). ## Algorithm mirrors DrussGT EnemyWave.logShadow: simulate each of our bullets ## forward tick-by-tick, find where it intersects the expanding enemy wave ring, ## convert intersection points to GF values. import std/math import gun_harness/gun_interface const MaxBullets* = 32 ## slots; ponytail: simple array, 1 bullet/tick max type MyBullet* = object x*, y*: float headingRad*: float speed*: float alive*: bool ShadowTracker* = object bullets*: array[MaxBullets, MyBullet] numSlots: int ## high-water mark BulletShadow* = object ## GF range [gfLow, gfHigh] shadowed by one of our bullets for a wave. gfLow*, gfHigh*: float # ── bullet lifecycle ────────────────────────────────────────────────────────── proc addBullet*(st: var ShadowTracker, x, y, headingRad, power: float) = for i in 0..= st.numSlots: st.numSlots = i + 1 return proc removeBullet*(st: var ShadowTracker, idx: int) {.inline.} = if idx >= 0 and idx < MaxBullets: st.bullets[idx].alive = false proc removeBulletNear*(st: var ShadowTracker, x, y: float) = ## Kill the live bullet slot closest to (x, y). Used when a hit event fires ## with the bullet's last known position. var bestIdx = -1 var bestDist = 1e18 for i in 0..= 0: st.bullets[bestIdx].alive = false proc tick*(st: var ShadowTracker) = ## Advance all live bullets one tick (call once per game tick). for i in 0.. waveAt and curDist < prevDist: # Angular half-width of bot at crossing distance let ringR = (waveAt + waveNext) * 0.5 let angHalf = arctan(BotRadius / max(ringR, 1.0)) # Center angle of intersection point (absolute bearing from wave origin) let centerAngle = arctan2(by - waveFireY, bx - waveFireX) # Convert to GF var off = centerAngle - waveBearingRad while off > PI: off -= 2.0 * PI while off < -PI: off += 2.0 * PI let gfCenter = clamp(off / maxEA, -1.0, 1.0) let gfHalf = angHalf / maxEA result.add BulletShadow( gfLow: clamp(gfCenter - gfHalf, -1.0, 1.0), gfHigh: clamp(gfCenter + gfHalf, -1.0, 1.0), ) break # one shadow per bullet per wave if curDist > prevDist: break # bullet diverging — no future crossing prevDist = curDist proc isShadowed*(shadows: openArray[BulletShadow], gf: float): bool {.inline.} = for s in shadows: if gf >= s.gfLow and gf <= s.gfHigh: return true false