fix(PPO_Bot): radar lock — use enemy_tracker width-lock, fix arctan2 arg order

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).
This commit is contained in:
2026-08-17 11:52:36 +02:00
parent a8ee2a86e3
commit e5609a7d9b
5 changed files with 141 additions and 19 deletions
+4 -2
View File
@@ -64,6 +64,9 @@ method onScannedBot*(bot: PPOBot, e: ScannedBotEvent) =
bot.tracker.update(e.x, e.y, e.direction, e.speed, e.energy)
method onRoundStarted*(bot: PPOBot, e: RoundStartedEvent) =
setAdjustGunForBodyTurn(true)
setAdjustRadarForBodyTurn(true)
setAdjustRadarForGunTurn(true)
bot.tracker = initEnemyTracker()
bot.buffer = initTrajectoryBuffer()
bot.prevEnergy = 0.0'f32
@@ -130,8 +133,7 @@ method run(bot: PPOBot) =
while isRunning():
bot.tracker.deadReckon()
setRadarTurnRate(bot.tracker.getRadarTurnRate(
getX(), getY(), getDirection(), getRadarDirection()))
setRadarTurnRate(bot.tracker.getRadarTurnRate(getX(), getY(), getDirection(), getRadarDirection()))
let botData = BotStateData(
x: getX(),
+1 -1
View File
@@ -1,6 +1,6 @@
# Static-link OpenBLAS for portable deployment
# ponytail: adjust path per machine, or use pkg-config
switch("passL", "-lopenblas")
switch("passL", "-L/nix/store/v07svn2y92bvzjl51aj7c9ca1cwg7rw7-openblas-0.3.32/lib -lopenblas")
switch("threads", "on")
# begin Nimble config (version 2)
when withDir(thisDir(), system.fileExists("nimble.paths")):
+14 -16
View File
@@ -19,11 +19,8 @@ type
historyCount*: int # valid entries 0-5
prevEnergy*: float64
hasContact*: bool
## For radar overshoot reversal
lastOvershootDir*: float64 # +1 or -1
proc initEnemyTracker*(): EnemyTracker =
result.lastOvershootDir = 1.0
proc initEnemyTracker*(): EnemyTracker = discard
proc update*(tracker: var EnemyTracker;
scanX, scanY, scanDir, scanSpeed, scanEnergy: float64) =
@@ -77,20 +74,21 @@ proc getRadarTurnRate*(tracker: var EnemyTracker;
if not tracker.hasContact:
return 45.0
if tracker.current.ticksSinceLastScan >= 2:
# Lost lock — widen sweep proportional to staleness
if tracker.current.ticksSinceLastScan >= 8:
# Lost lock — widen sweep
return 45.0
# Bearing from radar to enemy
# Tank Royale: 0°=north, CW+. arctan2(dx, dy) gives north-based CW bearing.
# 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(dx, dy) / PI + 360.0) mod 360.0
let radarBearing = normalizeRelative(absoluteDir - radarDirection)
let absoluteDir = (180.0 * arctan2(dy, dx) / PI + 360.0) mod 360.0
var radarTurn = normalizeRelative(absoluteDir - radarDirection)
# Overshoot by 10°, alternate direction each tick
# ponytail: simple fixed overshoot; adaptive sweep if enemy is fast-turning
let overshoot = 10.0
let target = radarBearing + tracker.lastOvershootDir * overshoot
result = target.clamp(-45.0, 45.0)
tracker.lastOvershootDir *= -1.0
# 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)