fix(ModularBot): ram loop prevention, dead-target guards, cleaner logging
- 30-tick cooldown after ghost-stuck/timeout ram exit prevents re-entry loop - enemy_tracker.update() skips dead bots to prevent same-tick scan resurrection - TFIL graphics cleared when ramming is active movement - [config] logs: white base with green-highlighted changes only - [ram:enter] logs trigger reason and key values on false→true transition - [death] and [target-invalid] logs retained for diagnostics
This commit is contained in:
Binary file not shown.
@@ -27,6 +27,7 @@ import guns/decay_gf
|
||||
import guns/knn_gun
|
||||
import movements/phantom_meteor
|
||||
import movements/rammer
|
||||
import movements/the_floor_is_lava
|
||||
import movement_harness/virtual_bodies as mvb
|
||||
import movement_harness/bullet_shadows
|
||||
import targeting/enemy_tracker
|
||||
@@ -71,9 +72,12 @@ type
|
||||
avgLead: AveragedLeadGun
|
||||
decayGF: DecayGFGun
|
||||
knnGun: KNNGun
|
||||
mover: PhantomMeteorModule
|
||||
mover: TFILModule
|
||||
rammer: RammerModule
|
||||
isRamming: bool
|
||||
ramStuckTicks: int
|
||||
ramDurationTicks: int
|
||||
ramCooldownTicks: int
|
||||
moveTracker: VirtualBodyTracker
|
||||
virtualHits: int
|
||||
virtualMiss: int
|
||||
@@ -82,14 +86,26 @@ type
|
||||
currentTargetId: int
|
||||
targetSwitchTick: int
|
||||
enemyTracker: EnemyTracker
|
||||
prevGun: int
|
||||
prevRadar: int
|
||||
prevTarget: int
|
||||
|
||||
proc printConfig(bot: ModularBot, changed: string = "") =
|
||||
let gunName = GunNames[bot.currentGun]
|
||||
let gunColor = if changed == "gun" or changed == "all": CLR_CHANGE else: CLR_RST
|
||||
let radarColor = if changed == "radar" or changed == "all": CLR_CHANGE else: CLR_RST
|
||||
let gunName = GunNames[bot.currentGun]
|
||||
let radarName = if bot.radarMode == 0: "radar_lock" else: "melee_scan"
|
||||
let moveName = if bot.isRamming: "rammer" else: "phantom_meteor"
|
||||
echo CLR_ROUND & "[config]" & CLR_RST & " " & gunColor & "gun=" & gunName & CLR_RST & " | " & CLR_MOVE & "move=" & moveName & CLR_RST & " | " & radarColor & "radar=" & radarName & CLR_RST & " | enemies=" & $bot.enemyTracker.allAlive().len & " target=#" & $bot.currentTargetId
|
||||
let moveName = if bot.isRamming: "rammer" else: "tfil"
|
||||
let gc = if bot.currentGun != bot.prevGun or changed == "all": CLR_CHANGE else: ""
|
||||
let rc = if bot.radarMode != bot.prevRadar or changed == "all": CLR_CHANGE else: ""
|
||||
let tc = if bot.currentTargetId != bot.prevTarget or changed == "all": CLR_CHANGE else: ""
|
||||
let rst = CLR_RST
|
||||
echo "[config] " & gc & "gun=" & gunName & (if gc != "": rst else: "") &
|
||||
" | move=" & moveName &
|
||||
" | " & rc & "radar=" & radarName & (if rc != "": rst else: "") &
|
||||
" | enemies=" & $bot.enemyTracker.allAlive().len &
|
||||
" target=" & tc & "#" & $bot.currentTargetId & (if tc != "": rst else: "")
|
||||
bot.prevGun = bot.currentGun
|
||||
bot.prevRadar = bot.radarMode
|
||||
bot.prevTarget = bot.currentTargetId
|
||||
|
||||
proc buildState(bot: ModularBot, ex, ey, espeed, eheading, eenergy: float): WorldState =
|
||||
var ei: seq[EnemyInfo]
|
||||
@@ -128,9 +144,11 @@ method onBulletHitBot*(bot: ModularBot, e: BulletHitBotEvent) =
|
||||
|
||||
method onBulletHitWall*(bot: ModularBot, e: BulletHitWallEvent) =
|
||||
bot.moveTracker.shadows.removeBulletNear(e.bullet.x, e.bullet.y)
|
||||
bot.mover.removeBulletNear(e.bullet.x, e.bullet.y)
|
||||
|
||||
method onBulletHitBullet*(bot: ModularBot, e: BulletHitBulletEvent) =
|
||||
bot.moveTracker.shadows.removeBulletNear(e.bullet.x, e.bullet.y)
|
||||
bot.mover.removeBulletNear(e.bullet.x, e.bullet.y)
|
||||
|
||||
method onHitByBullet*(bot: ModularBot, e: HitByBulletEvent) =
|
||||
bot.moveTracker.registerHit(e.bullet.power, e.bullet.direction, getX(), getY())
|
||||
@@ -147,6 +165,9 @@ method onRoundStarted*(bot: ModularBot, e: RoundStartedEvent) =
|
||||
bot.mover.resetRound()
|
||||
bot.enemyTracker.resetRound()
|
||||
bot.isRamming = false
|
||||
bot.ramStuckTicks = 0
|
||||
bot.ramDurationTicks = 0
|
||||
bot.ramCooldownTicks = 0
|
||||
bot.hasContact = false
|
||||
bot.tick = 0
|
||||
bot.currentGun = -1
|
||||
@@ -171,11 +192,15 @@ method onRoundStarted*(bot: ModularBot, e: RoundStartedEvent) =
|
||||
# tracker fitness persists across rounds (rolling window carries over)
|
||||
|
||||
method onBotDeath*(bot: ModularBot, e: BotDeathEvent) =
|
||||
echo "[death] victimId=", e.victimId, " wasTarget=", (e.victimId == bot.currentTargetId),
|
||||
" trackerAlive=", (if bot.enemyTracker.enemies.contains(e.victimId): $bot.enemyTracker.enemies[e.victimId].alive else: "notInTracker")
|
||||
echo CLR_MOVE & "[death] victimId=" & $e.victimId & " wasTarget=" & $(e.victimId == bot.currentTargetId) & CLR_RST
|
||||
bot.enemyTracker.markDead(e.victimId)
|
||||
if bot.enemyCount > 0:
|
||||
dec bot.enemyCount
|
||||
if e.victimId == bot.currentTargetId:
|
||||
bot.isRamming = false
|
||||
bot.currentTargetId = -1
|
||||
|
||||
method onGameStarted*(bot: ModularBot, e: GameStartedEventForBot) =
|
||||
# minNumberOfParticipants == maxNumberOfParticipants for fixed battles; self is -1
|
||||
@@ -201,9 +226,18 @@ method run*(bot: ModularBot) =
|
||||
while isRunning():
|
||||
inc bot.tick
|
||||
|
||||
# Debug: print enemy state on first tick
|
||||
if bot.tick == 1:
|
||||
echo "[debug] enemyCount=" & $bot.enemyCount & " tracker=" & $bot.enemyTracker.allAlive().len & " radarMode=" & $bot.radarMode
|
||||
# Ram cooldown countdown
|
||||
if bot.ramCooldownTicks > 0:
|
||||
dec bot.ramCooldownTicks
|
||||
|
||||
# Safety: reset invalid target before any logic
|
||||
if bot.currentTargetId >= 0:
|
||||
if not bot.enemyTracker.enemies.contains(bot.currentTargetId) or
|
||||
not bot.enemyTracker.enemies[bot.currentTargetId].alive:
|
||||
echo CLR_MOVE & "[target-invalid] id=" & $bot.currentTargetId & " resetting" & CLR_RST
|
||||
bot.currentTargetId = -1
|
||||
bot.isRamming = false
|
||||
|
||||
|
||||
if not bot.hasContact:
|
||||
setTargetSpeed(0.0)
|
||||
@@ -212,25 +246,65 @@ method run*(bot: ModularBot) =
|
||||
go()
|
||||
continue
|
||||
|
||||
# Ram decision — harness decides, not the movement module
|
||||
# --- Target selection (sticky) ---
|
||||
let candidateId = selectTarget(bot.enemyTracker, getX(), getY(), tmClosest)
|
||||
if bot.shouldSwitchTarget(candidateId):
|
||||
if candidateId != bot.currentTargetId:
|
||||
bot.currentTargetId = candidateId
|
||||
bot.targetSwitchTick = bot.tick
|
||||
bot.printConfig("target")
|
||||
|
||||
# --- Refresh target state FIRST, before ram decision ---
|
||||
let tid = bot.currentTargetId
|
||||
if tid >= 0 and bot.enemyTracker.enemies.contains(tid) and
|
||||
bot.enemyTracker.enemies[tid].alive:
|
||||
let tgt = bot.enemyTracker.getEnemy(tid)
|
||||
bot.enemyBearing = directionTo(getX(), getY(), tgt.x, tgt.y)
|
||||
bot.lastState = bot.buildState(tgt.x, tgt.y, tgt.speed, tgt.heading, tgt.energy)
|
||||
|
||||
# Ram decision — harness decides, not the movement module (uses fresh lastState)
|
||||
let ws = bot.lastState
|
||||
let ramDist = hypot(ws.enemyX - ws.selfX, ws.enemyY - ws.selfY)
|
||||
let ramFinisher = ramDist < 300.0 and ws.enemyEnergy < 20.0 and ws.selfEnergy > ws.enemyEnergy
|
||||
let ramOpportunity = ramDist < 80.0 and ws.selfEnergy > ws.enemyEnergy + 10.0
|
||||
let ramDesperation = ws.selfEnergy < 5.0 and ws.enemyEnergy < 5.0 and ramDist < 150.0
|
||||
let shouldRam = ramFinisher or ramOpportunity or ramDesperation
|
||||
let ramFinisher = bot.ramCooldownTicks == 0 and ws.enemyEnergy > 0 and ramDist < 300.0 and ws.enemyEnergy < 20.0 and ws.selfEnergy > ws.enemyEnergy
|
||||
let ramOpportunity = bot.ramCooldownTicks == 0 and ws.enemyEnergy > 0 and ramDist < 50.0 and ws.selfEnergy > ws.enemyEnergy + 30.0
|
||||
let ramDesperation = bot.ramCooldownTicks == 0 and ws.enemyEnergy > 0 and ws.selfEnergy < 5.0 and ws.enemyEnergy < 5.0 and ramDist < 150.0
|
||||
let shouldRam = bot.currentTargetId >= 0 and
|
||||
(ramFinisher or ramOpportunity or ramDesperation)
|
||||
|
||||
if shouldRam and not bot.isRamming:
|
||||
bot.isRamming = true
|
||||
let tag = if ramDesperation: "desperation" elif ramFinisher: "finisher" else: "opportunity"
|
||||
echo CLR_MOVE & "[ram:" & tag & "] dist=" & $int(ramDist) &
|
||||
" selfE=" & $int(ws.selfEnergy) & " enemyE=" & $int(ws.enemyEnergy) & CLR_RST
|
||||
bot.printConfig("move")
|
||||
bot.ramStuckTicks = 0
|
||||
bot.ramDurationTicks = 0
|
||||
if ramFinisher:
|
||||
echo "[ram:enter] reason=finisher dist=", ramDist.int, " selfEnergy=", ws.selfEnergy.int, " enemyEnergy=", ws.enemyEnergy.int
|
||||
elif ramOpportunity:
|
||||
echo "[ram:enter] reason=opportunity dist=", ramDist.int, " energyAdv=", (ws.selfEnergy - ws.enemyEnergy).int
|
||||
elif ramDesperation:
|
||||
echo "[ram:enter] reason=desperation selfEnergy=", ws.selfEnergy.int
|
||||
elif not shouldRam and bot.isRamming:
|
||||
bot.isRamming = false
|
||||
bot.printConfig("move")
|
||||
|
||||
if bot.isRamming:
|
||||
inc bot.ramDurationTicks
|
||||
if ramDist < 5.0:
|
||||
inc bot.ramStuckTicks
|
||||
else:
|
||||
bot.ramStuckTicks = 0
|
||||
if bot.ramStuckTicks > 10:
|
||||
bot.isRamming = false
|
||||
bot.currentTargetId = -1
|
||||
bot.ramStuckTicks = 0
|
||||
bot.ramDurationTicks = 0
|
||||
bot.ramCooldownTicks = 30
|
||||
elif bot.ramDurationTicks > 60:
|
||||
bot.isRamming = false
|
||||
bot.currentTargetId = -1
|
||||
bot.ramStuckTicks = 0
|
||||
bot.ramDurationTicks = 0
|
||||
bot.ramCooldownTicks = 30
|
||||
|
||||
let (spd, tr) = if shouldRam:
|
||||
bot.mover.clearGraphics()
|
||||
bot.rammer.computeMove(ws)
|
||||
else:
|
||||
bot.mover.computeMove(ws)
|
||||
@@ -258,23 +332,9 @@ method run*(bot: ModularBot) =
|
||||
setRadarTurnRate(radarRate)
|
||||
go()
|
||||
|
||||
# --- Target selection (sticky) --- lock target during ram to prevent dangerous switch
|
||||
if not bot.isRamming:
|
||||
let candidateId = selectTarget(bot.enemyTracker, getX(), getY(), tmClosest)
|
||||
if bot.shouldSwitchTarget(candidateId):
|
||||
if candidateId != bot.currentTargetId:
|
||||
bot.currentTargetId = candidateId
|
||||
bot.targetSwitchTick = bot.tick
|
||||
bot.printConfig("target")
|
||||
|
||||
# --- Aim + fire from tracked state (not scan event) ---
|
||||
let tid = bot.currentTargetId
|
||||
if tid >= 0 and bot.enemyTracker.enemies.contains(tid) and
|
||||
bot.enemyTracker.enemies[tid].alive:
|
||||
let tgt = bot.enemyTracker.getEnemy(tid)
|
||||
bot.enemyBearing = directionTo(getX(), getY(), tgt.x, tgt.y)
|
||||
bot.lastState = bot.buildState(tgt.x, tgt.y, tgt.speed, tgt.heading, tgt.energy)
|
||||
|
||||
# Spawn virtual bullets for all guns
|
||||
var headsUp: array[len(PowerBins), GunPrediction]
|
||||
var linPreds: array[len(PowerBins), GunPrediction]
|
||||
@@ -361,7 +421,6 @@ method run*(bot: ModularBot) =
|
||||
let (selectedGun, _, power) = selectShot(bot.tracker, tid)
|
||||
if selectedGun != bot.currentGun:
|
||||
bot.currentGun = selectedGun
|
||||
bot.printConfig("gun")
|
||||
case selectedGun
|
||||
of 0: setTurretColor("#FF3333"); setBulletColor("#FF6666")
|
||||
of 1: setTurretColor("#3366FF"); setBulletColor("#6699FF")
|
||||
@@ -427,7 +486,7 @@ when isMainModule:
|
||||
knnGun: initKNNGun(),
|
||||
radar: RadarLockModule(),
|
||||
meleeScan: initMeleeScan(),
|
||||
mover: initPhantomMeteor(),
|
||||
mover: TFILModule(debugGraphics: true),
|
||||
rammer: initRammer(),
|
||||
moveTracker: mvb.initVirtualBodyTracker(1),
|
||||
currentGun: -1,
|
||||
|
||||
Reference in New Issue
Block a user