From fd22535f5b36434b03c7b0e44aceb566d9159942 Mon Sep 17 00:00:00 2001 From: Davide Cappellini Date: Sun, 16 Aug 2026 16:38:15 +0200 Subject: [PATCH] =?UTF-8?q?fix(PPO=5FBot):=20radar=20lock=20oscillation=20?= =?UTF-8?q?bug=20=E2=80=94=20arctan2=20arg=20order=20wrong=20for=20Tank=20?= =?UTF-8?q?Royale=20coords?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit arctan2(dy, dx) gives east-based math bearing; Tank Royale uses north=0°, CW+. Swapping to arctan2(dx, dy) gives the correct game-space bearing. Symptom: radar commanded 45°/tick away from a target directly ahead. Adds test_radar_lock.nim as regression test (20-tick lock, ±15° tolerance). Co-Authored-By: Claude Sonnet 4.6 --- PPO_Bot/enemy_tracker.nim | 3 ++- PPO_Bot/tests/test_radar_lock.nim | 42 +++++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+), 1 deletion(-) create mode 100644 PPO_Bot/tests/test_radar_lock.nim diff --git a/PPO_Bot/enemy_tracker.nim b/PPO_Bot/enemy_tracker.nim index abf3fe2..7f50ebc 100644 --- a/PPO_Bot/enemy_tracker.nim +++ b/PPO_Bot/enemy_tracker.nim @@ -82,9 +82,10 @@ proc getRadarTurnRate*(tracker: var EnemyTracker; return 45.0 # Bearing from radar to enemy + # Tank Royale: 0°=north, CW+. arctan2(dx, dy) gives north-based CW bearing. 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 + let absoluteDir = (180.0 * arctan2(dx, dy) / PI + 360.0) mod 360.0 let radarBearing = normalizeRelative(absoluteDir - radarDirection) # Overshoot by 10°, alternate direction each tick diff --git a/PPO_Bot/tests/test_radar_lock.nim b/PPO_Bot/tests/test_radar_lock.nim new file mode 100644 index 0000000..e93de1e --- /dev/null +++ b/PPO_Bot/tests/test_radar_lock.nim @@ -0,0 +1,42 @@ +## Regression test: radar lock must hold on a stationary target. +## Run: nim c -r tests/test_radar_lock.nim + +import std/[math, strformat] +import "../enemy_tracker" + +template check(cond: bool, msg: string) = + if not cond: + quit("FAIL: " & msg, 1) + +# Bot at arena center; stationary enemy due north (same x, higher y). +# Tank Royale: y increases northward. +# True bearing to enemy = 0° (north = 0° in game coords). + +let botX = 400.0 +let botY = 300.0 +let enemyX = 400.0 # same x → dx = 0 +let enemyY = 500.0 # north of bot → dy > 0 +let trueBearing = 0.0 # north + +# Radar starts pointing at the enemy (radarDirection = 0°, due north). +var radarDir = 0.0 + +var tracker = initEnemyTracker() +# Prime with contact at the known position +tracker.update(enemyX, enemyY, 0.0, 0.0, 100.0) + +echo "Tick | radarDir | trueBearing | bearingErr" +for tick in 1 .. 20: + let rate = tracker.getRadarTurnRate(botX, botY, 0.0, radarDir) + radarDir = (radarDir + rate + 360.0) mod 360.0 + + # Simulate a successful scan every tick (enemy is stationary) + tracker.update(enemyX, enemyY, 0.0, 0.0, 100.0) + + # Bearing error: signed difference, wrapped to [-180, 180] + let err = ((radarDir - trueBearing) + 540.0) mod 360.0 - 180.0 + echo &" {tick:2d} | {radarDir:8.3f}° | {trueBearing:8.3f}° | {err:+.3f}°" + + check abs(err) <= 15.0, &"tick {tick}: radar {radarDir:.1f}° drifted > 15° from target {trueBearing:.1f}°" + +echo "All radar lock tests passed"