e5609a7d9b
Three bugs caused the radar to sweep continuously instead of locking: 1. run() loop set radar to Inf every tick, overwriting any lock → replaced with enemy_tracker.getRadarTurnRate() 2. onScannedBot used radarBearingTo() (math convention, east=0 CCW) → removed; run loop now handles radar via enemy_tracker 3. enemy_tracker.getRadarTurnRate() had arctan2(dx,dy) instead of arctan2(dy,dx) — introduced by fd22535; bearing was off by ~90° Also relaxed stale-lock threshold from 2 to 8 ticks to survive brief scan gaps without falling back to full sweep. Added tools/battle_runner for automated 1v1 testing. Result: 1303/1308 ticks with successful scan (was ~1 in 4).
95 lines
3.3 KiB
Nim
95 lines
3.3 KiB
Nim
## Enemy tracker — deterministic radar lock + dead reckoning for PPO_Bot.
|
|
## No bot API imports; takes plain floats.
|
|
|
|
import std/math
|
|
|
|
type
|
|
EnemyState* = object
|
|
x*, y*: float64
|
|
direction*: float64
|
|
speed*: float64
|
|
energy*: float64
|
|
ticksSinceLastScan*: int
|
|
hasFired*: bool
|
|
lastFirePower*: float64
|
|
|
|
EnemyTracker* = object
|
|
current*: EnemyState
|
|
history*: array[5, tuple[x, y, direction, speed: float64]] # sliding window
|
|
historyCount*: int # valid entries 0-5
|
|
prevEnergy*: float64
|
|
hasContact*: bool
|
|
|
|
proc initEnemyTracker*(): EnemyTracker = discard
|
|
|
|
proc update*(tracker: var EnemyTracker;
|
|
scanX, scanY, scanDir, scanSpeed, scanEnergy: float64) =
|
|
## Call on ScannedBotEvent. Detects enemy fire from energy delta.
|
|
|
|
# Shift history window
|
|
if tracker.historyCount > 0:
|
|
for i in countdown(min(tracker.historyCount, 4), 1):
|
|
tracker.history[i] = tracker.history[i - 1]
|
|
tracker.history[0] = (tracker.current.x, tracker.current.y,
|
|
tracker.current.direction, tracker.current.speed)
|
|
if tracker.historyCount < 5:
|
|
inc tracker.historyCount
|
|
|
|
# Detect firing: energy drop in [0.1, 3.0] means enemy fired
|
|
let delta = tracker.prevEnergy - scanEnergy
|
|
if tracker.hasContact and delta >= 0.1 and delta <= 3.0:
|
|
tracker.current.hasFired = true
|
|
tracker.current.lastFirePower = delta
|
|
else:
|
|
tracker.current.hasFired = false
|
|
|
|
tracker.prevEnergy = scanEnergy
|
|
tracker.current.x = scanX
|
|
tracker.current.y = scanY
|
|
tracker.current.direction = scanDir
|
|
tracker.current.speed = scanSpeed
|
|
tracker.current.energy = scanEnergy
|
|
tracker.current.ticksSinceLastScan = 0
|
|
tracker.hasContact = true
|
|
|
|
proc deadReckon*(tracker: var EnemyTracker) =
|
|
## Call on missed ticks. Predict position from last known velocity.
|
|
if not tracker.hasContact:
|
|
return
|
|
let rad = tracker.current.direction * PI / 180.0
|
|
tracker.current.x += tracker.current.speed * sin(rad)
|
|
tracker.current.y += tracker.current.speed * cos(rad)
|
|
inc tracker.current.ticksSinceLastScan
|
|
|
|
proc normalizeRelative(angle: float64): float64 {.inline.} =
|
|
result = angle mod 360.0
|
|
if result >= 180.0: result -= 360.0
|
|
elif result < -180.0: result += 360.0
|
|
|
|
proc getRadarTurnRate*(tracker: var EnemyTracker;
|
|
botX, botY, botDirection, radarDirection: float64): float64 =
|
|
## Returns radar turn rate (degrees/tick, positive = right).
|
|
## Before contact: full 45° sweep.
|
|
## After contact: lock with overshoot; widen if stale.
|
|
if not tracker.hasContact:
|
|
return 45.0
|
|
|
|
if tracker.current.ticksSinceLastScan >= 8:
|
|
# Lost lock — widen sweep
|
|
return 45.0
|
|
|
|
# Bearing from radar to enemy.
|
|
# Tank Royale radar directions use standard math convention (0=east, CCW+).
|
|
# arctan2(dy, dx) gives the standard math angle matching radarDirection units.
|
|
let dx = tracker.current.x - botX
|
|
let dy = tracker.current.y - botY
|
|
let absoluteDir = (180.0 * arctan2(dy, dx) / PI + 360.0) mod 360.0
|
|
var radarTurn = normalizeRelative(absoluteDir - radarDirection)
|
|
|
|
# Width Lock: overshoot proportional to arctan(36 / distance)
|
|
let distance = sqrt(dx * dx + dy * dy)
|
|
let extraTurn = min(arctan(36.0 / distance) * 180.0 / PI, 45.0)
|
|
if radarTurn < 0.0: radarTurn -= extraTurn
|
|
else: radarTurn += extraTurn
|
|
result = radarTurn.clamp(-45.0, 45.0)
|