From a87be5f99f7a2bd341ee593ed7d74a8e08933e55 Mon Sep 17 00:00:00 2001 From: Davide Cappellini Date: Sun, 20 Sep 2026 01:37:45 +0200 Subject: [PATCH] feat(ModularBot): stop-shot gun, 10 guns total - Stop-shot gun: predicts enemy deceleration stop point - Catches bots at direction reversal pauses - Battle-tested vs WaveSurfer, Crazy, RandomMover Co-Authored-By: Claude Sonnet 4.6 --- ModularBot_garage/src/ModularBot.nim | 15 +++- common_libs/guns/stop_shot.nim | 100 +++++++++++++++++++++++++++ 2 files changed, 112 insertions(+), 3 deletions(-) create mode 100644 common_libs/guns/stop_shot.nim diff --git a/ModularBot_garage/src/ModularBot.nim b/ModularBot_garage/src/ModularBot.nim index de63f44..57eabfc 100644 --- a/ModularBot_garage/src/ModularBot.nim +++ b/ModularBot_garage/src/ModularBot.nim @@ -1,5 +1,5 @@ ## ModularBot — plugin gun architecture tracer bullet. -## Guns: HeadOnGun (0), LinearGun (1), TsetlinGun (2), CircularGun (3), GFGun (4), PatternMatcherGun (5), AntiSurferGun (6), WallBounceGun (7), AccelGun (8) via GunHarness. +## Guns: HeadOnGun (0), LinearGun (1), TsetlinGun (2), CircularGun (3), GFGun (4), PatternMatcherGun (5), AntiSurferGun (6), WallBounceGun (7), AccelGun (8), StopShotGun (9) via GunHarness. ## Radar: RadarLockModule via radar harness. ## Movement: OscillatorModule (perpendicular strafing). @@ -19,12 +19,13 @@ import guns/pattern_matcher import guns/anti_surfer import guns/wall_bounce import guns/accel_predictor +import guns/stop_shot import movements/phantom_meteor const botJsonPath = currentSourcePath().parentDir / "ModularBot.json" const DebugVBullets = false const DebugCircular = false -const GunNames = ["HeadOn", "Linear", "Tsetlin", "Circular", "GuessFactor", "Pattern", "AntiSurf", "WallBounce", "Accel"] +const GunNames = ["HeadOn", "Linear", "Tsetlin", "Circular", "GuessFactor", "Pattern", "AntiSurf", "WallBounce", "Accel", "StopShot"] type ModularBot = ref object of Bot @@ -42,6 +43,7 @@ type antiSurfer: AntiSurferGun wallBounce: WallBounceGun accelGun: AccelGun + stopShot: StopShotGun mover: PhantomMeteorModule virtualHits: int virtualMiss: int @@ -81,6 +83,7 @@ method onScannedBot*(bot: ModularBot, e: ScannedBotEvent) = var asPreds: array[len(PowerBins), GunPrediction] var wbPreds: array[len(PowerBins), GunPrediction] var acPreds: array[len(PowerBins), GunPrediction] + var ssPreds: array[len(PowerBins), GunPrediction] for i in 0.. g.prevTick + if isNew: + g.prevSpeed = state.enemySpeed + g.prevHeading = state.enemyHeading + g.prevTick = state.tick + inc g.frames + + # Need 2+ frames to detect deceleration + if g.frames < 2: + result = GunPrediction(x: state.enemyX, y: state.enemyY) + return + + let speed = state.enemySpeed + let prev = g.prevSpeed + + # Detect deceleration: |speed| is shrinking + let decelerating = abs(speed) < abs(prev) and abs(speed) > 0.01 + + if not decelerating: + # Linear fallback + let headRad = degToRad(state.enemyHeading) + let dist = hypot(state.enemyX - state.selfX, state.enemyY - state.selfY) + let t = dist / bulletSpeed + var px = state.enemyX + cos(headRad) * speed * t + var py = state.enemyY + sin(headRad) * speed * t + result = GunPrediction( + x: clamp(px, 0.0, state.arenaWidth), + y: clamp(py, 0.0, state.arenaHeight) + ) + return + + # Pick decel rate: braking (speed toward zero on same sign) = 2, else 1 + # ponytail: simplified; TR has exact rules per direction but this is close enough + let decel = if speed * prev > 0.0: BrakeDecel else: CoastDecel + + # Simulate stop position + let headRad = degToRad(state.enemyHeading) + var v = speed + var sx = state.enemyX + var sy = state.enemyY + while abs(v) > 0.001: + sx += v * cos(headRad) + sy += v * sin(headRad) + let step = if v > 0.0: -decel else: decel + v += step + if (v > 0.0) != (speed > 0.0): v = 0.0 # crossed zero + + # Bullet travel time to current pos, check against ticks to stop + let dist = hypot(state.enemyX - state.selfX, state.enemyY - state.selfY) + let bulletTicks = dist / bulletSpeed + let stopTicks = abs(speed) / decel + + # If bullet arrives well after stop, aim at stop; otherwise linear blend + let px = if bulletTicks >= stopTicks: sx else: state.enemyX + cos(headRad) * speed * bulletTicks + let py = if bulletTicks >= stopTicks: sy else: state.enemyY + sin(headRad) * speed * bulletTicks + + result = GunPrediction( + x: clamp(px, BotRadius, state.arenaWidth - BotRadius), + y: clamp(py, BotRadius, state.arenaHeight - BotRadius) + ) + +proc onResult*(g: var StopShotGun, e: FeedbackEvent) = + discard # analytical gun — no learning