From ab355400359dddb993cb7ae3c428c170256f9c2f Mon Sep 17 00:00:00 2001 From: Davide Cappellini Date: Mon, 21 Sep 2026 08:50:44 +0200 Subject: [PATCH] fix(logging): [config] reported a stale gun - it printed before selection ran The user spotted this from the game itself: the [config] line always said gun=HeadOn while the in-game turret and bullet COLOURS varied. The colours are set at the selection site, so they were truthful and the log was not. MEASURED, one 2-round battle, same process: [config] output : 6 lines, ALL gun=HeadOn tracker selection : Displace 25.1%, HeadOn 20.8%, Pattern 15.5%, KNN 14.1%, Tsetlin 6.3%, WallBounce 6.1%, ... Root cause: printConfig did GunNames[bot.currentGun] but EVERY call site ran before the tick's gun selection - onRoundStarted right after currentGun = 0 (so HeadOn by construction), and the target/radar-change prints. The selection that sets currentGun is ~490 lines later in the same tick. radarMode and currentTargetId ARE updated before those sites, which is exactly why the radar and target columns looked plausible while the gun column did not. WHERE IT CAME FROM: git history shows commit 2cc2a3b ('cleaner logging') removed the original printConfig call at the selection site while leaving the prevGun highlight logic in place. That removal is when the regression appeared - before it, the bot logged on round start AND on gun switch. FIX: one emission at the end of the tick, after selectShot has run, gated by a cfgDirty flag set on gun switch / target change / radar change. The index is guarded (currentGun may be -1), the round-start line omits the gun field rather than inventing one, and the existing output contract is preserved (all white, only changed fields green, enemies= and target= kept). VERIFIED: before 6 lines all HeadOn; after 782 lines over 2118 ticks with 13 distinct guns and ZERO violations - no printed gun was one that had not been selected. Counts differ from the selection totals because the log prints only on change, which is the behaviour the user asked for. Also removes the 'currentGun = 0' initialisation in onRoundStarted, keeping the existing -1 sentinel so 'no gun chosen yet' is representable. --- ModularBot_garage/src/ModularBot.nim | 46 +++++++++++++++++++--------- 1 file changed, 31 insertions(+), 15 deletions(-) diff --git a/ModularBot_garage/src/ModularBot.nim b/ModularBot_garage/src/ModularBot.nim index a8ba324..8c61453 100644 --- a/ModularBot_garage/src/ModularBot.nim +++ b/ModularBot_garage/src/ModularBot.nim @@ -137,6 +137,7 @@ type prevGun: int prevRadar: int prevTarget: int + cfgDirty: bool ## gun/target/radar changed this tick; emit one config line at tick end roundNumber: int realShotsFired: int realHits: int @@ -205,19 +206,25 @@ proc resolveOwnBullet(bot: ModularBot, bulletId: int): int = if result >= 0: bot.bulletGun.del(bulletId) -proc printConfig(bot: ModularBot, changed: string = "") = - let gunName = GunNames[bot.currentGun] +proc printConfig(bot: ModularBot, forceAll: bool = false) = + ## One line describing the config in use for the tick being reported. Only the + ## fields that changed since the previous line are highlighted green. The gun + ## field is omitted until a gun has actually been selected this round, so a + ## round-start line never claims a gun that was not chosen. + let rst = CLR_RST + let gc = if bot.currentGun != bot.prevGun or forceAll: CLR_CHANGE else: "" + let rc = if bot.radarMode != bot.prevRadar or forceAll: CLR_CHANGE else: "" + let tc = if bot.currentTargetId != bot.prevTarget or forceAll: CLR_CHANGE else: "" let radarName = if bot.radarMode == 0: "radar_lock" else: "melee_scan" 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: "") + var line = "[config] " + if bot.currentGun >= 0 and bot.currentGun < GunNames.len: + line &= gc & "gun=" & GunNames[bot.currentGun] & (if gc != "": rst else: "") & " | " + line &= "move=" & moveName & + " | " & rc & "radar=" & radarName & (if rc != "": rst else: "") & + " | enemies=" & $bot.enemyTracker.allAlive().len & + " target=" & tc & "#" & $bot.currentTargetId & (if tc != "": rst else: "") + echo line bot.prevGun = bot.currentGun bot.prevRadar = bot.radarMode bot.prevTarget = bot.currentTargetId @@ -448,7 +455,8 @@ method onRoundStarted*(bot: ModularBot, e: RoundStartedEvent) = # ponytail: default to melee until confirmed 1v1 via scans, avoids stale enemyCount issue bot.radarMode = 1 # Start in melee_scan; will switch to radar_lock (0) if tracker confirms 1v1 bot.moveTracker.resetRound(getX(), getY(), getDirection(), getSpeed()) - bot.currentGun = 0 + # currentGun stays -1 ("no gun chosen yet") until run() picks one; printConfig + # omits the gun field while it is negative. # Apply radar colors for initial mode if bot.radarMode == 0: setRadarColor("#004444") @@ -456,7 +464,8 @@ method onRoundStarted*(bot: ModularBot, e: RoundStartedEvent) = else: setRadarColor("#443300") setScanColor("#4D3D0D") - bot.printConfig("all") + bot.cfgDirty = false + bot.printConfig(forceAll = true) # tracker fitness persists across rounds (rolling window carries over) method onBotDeath*(bot: ModularBot, e: BotDeathEvent) = @@ -520,7 +529,7 @@ method run*(bot: ModularBot) = if candidateId != bot.currentTargetId: bot.currentTargetId = candidateId bot.targetSwitchTick = bot.tick - bot.printConfig("target") + bot.cfgDirty = true # emit at tick end, after gun selection if bot.currentTargetId >= 0: bot.lastKnownTargetId = bot.currentTargetId @@ -593,7 +602,7 @@ method run*(bot: ModularBot) = else: setRadarColor("#443300") setScanColor("#4D3D0D") - bot.printConfig("radar") + bot.cfgDirty = true # emit at tick end, after gun selection let radarRate = if bot.radarMode == 0: bot.radar.computeScan(bot.lastState) @@ -694,6 +703,7 @@ method run*(bot: ModularBot) = bot.gunSelectionCount[selectedGun] += 1 if selectedGun != bot.currentGun: bot.currentGun = selectedGun + bot.cfgDirty = true # gun switch: emit the config line at tick end case selectedGun of 0: setTurretColor("#FF3333"); setBulletColor("#FF6666") of 1: setTurretColor("#3366FF"); setBulletColor("#6699FF") @@ -754,6 +764,12 @@ method run*(bot: ModularBot) = setGunTurnRate(normDelta) + # Emit exactly one config line per tick, at the END of the tick, so the gun + # field reflects the selection made above rather than the previous tick's. + if bot.cfgDirty: + bot.printConfig() + bot.cfgDirty = false + proc seedSelectorRng() = ## Seed the process-global RNG exactly once at bot startup so the gun ## selector's "random" tie-break (`chooseFromFit` -> rand) actually varies