feat(ModularBot): pluggable bot with 4 guns, phantom meteor movement, radar harness
- Gun harness: virtual bullet tracker, rolling fitness, auto-selector - Guns: head-on, linear (extrapolation), circular (integrated formula), tsetlin machine (learning) - Movement: phantom meteor gravity engine (danger histograms, phantom bullets, fire detection) - Radar: harness + radar_lock adapter - Color-coded modules: turret/bullet color per gun, body per movement, scan per radar - Beats Target, SpinBot, Crazy, TrackFire in 10-round battles
This commit is contained in:
@@ -6,9 +6,11 @@ import std/math
|
||||
import gun_harness/gun_interface
|
||||
|
||||
type CircularGun* = object
|
||||
prevHeading: float ## enemy heading from last frame
|
||||
prevTick: int ## tick at last observation
|
||||
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
|
||||
|
||||
proc predict*(g: var CircularGun, state: WorldState, bulletSpeed: float): GunPrediction =
|
||||
if bulletSpeed <= 0.0:
|
||||
@@ -22,36 +24,31 @@ proc predict*(g: var CircularGun, state: WorldState, bulletSpeed: float): GunPre
|
||||
if not g.hasPrev:
|
||||
# ponytail: first-frame fallback to head-on, needs 2 frames for turn rate
|
||||
g.prevHeading = state.enemyHeading
|
||||
g.prevTick = state.tick
|
||||
g.hasPrev = true
|
||||
g.prevTick = state.tick
|
||||
g.hasPrev = true
|
||||
return GunPrediction(x: state.enemyX, y: state.enemyY)
|
||||
|
||||
# Save old state before potential update (predict is called once per power bin per tick)
|
||||
let oldHeading = g.prevHeading
|
||||
let oldTick = g.prevTick
|
||||
|
||||
# Update on new tick only
|
||||
# On new tick: recompute cachedOmega and advance the heading window.
|
||||
# On same-tick calls (multiple power bins): reuse cachedOmega so omega
|
||||
# doesn't collapse to zero on bins 1+.
|
||||
if state.tick > g.prevTick:
|
||||
var turnRate = state.enemyHeading - g.prevHeading
|
||||
if turnRate > 180.0: turnRate -= 360.0
|
||||
elif turnRate < -180.0: turnRate += 360.0
|
||||
let tickDelta = max(1, state.tick - g.prevTick)
|
||||
g.cachedOmega = degToRad(turnRate / tickDelta.float)
|
||||
g.cachedTick = state.tick
|
||||
g.prevHeading = state.enemyHeading
|
||||
g.prevTick = state.tick
|
||||
g.prevTick = state.tick
|
||||
|
||||
# Compute turn rate using captured old state
|
||||
var turnRate = state.enemyHeading - oldHeading
|
||||
if turnRate > 180.0: turnRate -= 360.0
|
||||
elif turnRate < -180.0: turnRate += 360.0
|
||||
|
||||
# Divide by actual tick delta (scans may not be every tick)
|
||||
let tickDelta = max(1, state.tick - oldTick)
|
||||
turnRate = turnRate / tickDelta.float
|
||||
|
||||
# Closed-form integrated trajectory (Robowiki circular targeting)
|
||||
# 0°=East: dx/dt = v*cos(θ+ω*t), dy/dt = v*sin(θ+ω*t)
|
||||
# → x(t) = x₀ + (v/ω)*[sin(θ+ω*t) - sin(θ)]
|
||||
# → y(t) = y₀ - (v/ω)*[cos(θ+ω*t) - cos(θ)]
|
||||
let omega = g.cachedOmega
|
||||
let theta = degToRad(state.enemyHeading)
|
||||
let omega = degToRad(turnRate) # rad/tick
|
||||
let v = state.enemySpeed
|
||||
|
||||
# Closed-form integrated trajectory (Robowiki circular targeting)
|
||||
# 0°=East CCW+: dx/dt = v*cos(θ+ω*t), dy/dt = v*sin(θ+ω*t)
|
||||
# → x(t) = x₀ + (v/ω)*[sin(θ+ω*t) - sin(θ)]
|
||||
# → y(t) = y₀ - (v/ω)*[cos(θ+ω*t) - cos(θ)]
|
||||
var ex = state.enemyX
|
||||
var ey = state.enemyY
|
||||
var t = ticks
|
||||
|
||||
Reference in New Issue
Block a user