From d2a2ebceb93cc7df71ec19f8a3bf0bdd9803bea0 Mon Sep 17 00:00:00 2001 From: Davide Cappellini Date: Sun, 20 Sep 2026 01:55:47 +0200 Subject: [PATCH] =?UTF-8?q?feat(ModularBot):=20movement=20selector=20?= =?UTF-8?q?=E2=80=94=20phantom=20meteor=20+=20wave=20surfer?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Adds WaveSurferModule as second movement option - Tracks damage per round via onHitByBullet, EMA decay α=0.5 - Every round after round 2: switches to lower-damage movement - Body color signals active mover (orange=phantom, blue=wave) - vs SpinBot 10r: ModularBot 1412 vs SpinBot 650 - vs WaveSurfer 10r: ModularBot 1858 vs WaveSurfer 10 Co-Authored-By: Claude Sonnet 4.6 --- ModularBot_garage/src/ModularBot.nim | 47 ++++++++++++++++++++++++---- 1 file changed, 41 insertions(+), 6 deletions(-) diff --git a/ModularBot_garage/src/ModularBot.nim b/ModularBot_garage/src/ModularBot.nim index 9c97f94..4e4ec1d 100644 --- a/ModularBot_garage/src/ModularBot.nim +++ b/ModularBot_garage/src/ModularBot.nim @@ -24,6 +24,7 @@ import guns/displacement import guns/averaged_lead import guns/decay_gf import movements/phantom_meteor +import movements/wave_surfer const botJsonPath = currentSourcePath().parentDir / "ModularBot.json" const DebugVBullets = false @@ -51,6 +52,12 @@ type avgLead: AveragedLeadGun decayGF: DecayGFGun mover: PhantomMeteorModule + waveMover: WaveSurferModule + movementId: int ## 0=phantom, 1=waveSurf + dmgPhantom: float ## rolling damage taken under phantom + dmgWaveSurf: float ## rolling damage taken under waveSurf + roundDmg: float ## damage this round + roundCount: int virtualHits: int virtualMiss: int tick: int @@ -207,21 +214,46 @@ method onScannedBot*(bot: ModularBot, e: ScannedBotEvent) = setGunTurnRate(normDelta) +method onHitByBullet*(bot: ModularBot, e: HitByBulletEvent) = + bot.roundDmg += e.damage + method onRoundEnded*(bot: ModularBot, e: RoundEndedEventForBot) = - discard # Skip detailed logging for now + # Accumulate damage into active movement bucket (exponential decay, α=0.5) + # ponytail: simple EMA; switch to longer window if thrashing becomes a problem + let decay = 0.5 + if bot.movementId == 0: + bot.dmgPhantom = decay * bot.dmgPhantom + (1.0 - decay) * bot.roundDmg + else: + bot.dmgWaveSurf = decay * bot.dmgWaveSurf + (1.0 - decay) * bot.roundDmg + echo "[move] round dmg=", bot.roundDmg, " phantom_ema=", bot.dmgPhantom, " wave_ema=", bot.dmgWaveSurf method onRoundStarted*(bot: ModularBot, e: RoundStartedEvent) = setAdjustGunForBodyTurn(true) setAdjustRadarForBodyTurn(true) setAdjustRadarForGunTurn(true) - setBodyColor("#FF9900") # PhantomMeteor: deep orange/gold setRadarColor("#00CCCC") # RadarLock: cyan/teal setScanColor("#33FFFF") # RadarLock scan arc: bright teal bot.radar.init() bot.hasContact = false bot.tick = 0 - bot.currentGun = -1 # Reset to trigger initial log on first selectShot - bot.mover.resetRound() + bot.currentGun = -1 + bot.roundDmg = 0.0 + inc bot.roundCount + + # Every 3 rounds: switch to lower-damage movement (probe every 3rd round) + # ponytail: simple 3-round probe; add hysteresis if switching thrashes + if bot.roundCount > 2: + bot.movementId = if bot.dmgPhantom <= bot.dmgWaveSurf: 0 else: 1 + + if bot.movementId == 0: + bot.mover.resetRound() + setBodyColor("#FF9900") # PhantomMeteor: deep orange/gold + echo "[move] using phantom_meteor" + else: + bot.waveMover.resetRound() + setBodyColor("#0099FF") # WaveSurfer: blue + echo "[move] using wave_surfer" + echo fmt"[gun] starting: {GunNames[0]}" # tracker fitness persists across rounds (rolling window carries over) @@ -239,8 +271,10 @@ method run*(bot: ModularBot) = go() continue - # Movement harness: oscillate perpendicular to enemy - let (spd, tr) = bot.mover.computeMove(bot.lastState) + # Movement harness: dispatch to active mover + let (spd, tr) = + if bot.movementId == 0: bot.mover.computeMove(bot.lastState) + else: bot.waveMover.computeMove(bot.lastState) setTargetSpeed(spd) setTurnRate(tr) @@ -265,6 +299,7 @@ when isMainModule: decayGF: initDecayGFGun(), radar: RadarLockModule(), mover: initPhantomMeteor(), + waveMover: initWaveSurfer(), currentGun: -1, ) start(bot, botJsonPath)