From 343e6316337e33275fa482318454f2277df3fdab Mon Sep 17 00:00:00 2001 From: Davide Cappellini Date: Sun, 20 Sep 2026 22:07:49 +0200 Subject: [PATCH] fix(gun_harness): random tiebreak + drop AntiSurfer + raise MinObsBeforeCompete - bestGun: replace first-index-wins argmax with random pick among guns within TieMargin (2%) of best rate. HeadOn at index 0 was silently winning every tie, starving Tsetlin/Linear/etc. - MinObsBeforeCompete 15 -> 50 (Pattern entered competition on noise) - add MinHitRateFloor 0.10: if no gun clears it, fall back to HeadOn instead of selecting the best of a bad field - ModularBot: remove AntiSurfer gun (0% virtual hit rate everywhere), 14 -> 13 guns, renumber ids and selection counters --- ModularBot_garage/src/ModularBot.nim | 77 +++++++++------------ common_libs/gun_harness/virtual_bullets.nim | 26 +++++-- 2 files changed, 55 insertions(+), 48 deletions(-) diff --git a/ModularBot_garage/src/ModularBot.nim b/ModularBot_garage/src/ModularBot.nim index 458b9c7..c633fc0 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), StopShotGun (9), DisplacementGun (10), AveragedLeadGun (11), DecayGFGun (12) via GunHarness. +## Guns: HeadOnGun (0), LinearGun (1), TsetlinGun (2), CircularGun (3), GFGun (4), PatternMatcherGun (5), WallBounceGun (6), AccelGun (7), StopShotGun (8), DisplacementGun (9), AveragedLeadGun (10), DecayGFGun (11), KNNGun (12) via GunHarness. ## Radar: RadarLockModule (1v1) / MeleeScanModule (2+ enemies), auto-switched per tick. ## Movement: OscillatorModule (perpendicular strafing). @@ -17,7 +17,6 @@ import guns/circular import guns/tsetlin import guns/guess_factor import guns/pattern_matcher -import guns/anti_surfer import guns/wall_bounce import guns/accel_predictor import guns/stop_shot @@ -36,7 +35,7 @@ import targeting/target_selector const botJsonPath = currentSourcePath().parentDir / "ModularBot.json" const DebugVBullets = false const DebugCircular = false -const GunNames = ["HeadOn", "Linear", "Tsetlin", "Circular", "GuessFactor", "Pattern", "AntiSurf", "WallBounce", "Accel", "StopShot", "Displace", "AvgLead", "DecayGF", "KNN"] +const GunNames = ["HeadOn", "Linear", "Tsetlin", "Circular", "GuessFactor", "Pattern", "WallBounce", "Accel", "StopShot", "Displace", "AvgLead", "DecayGF", "KNN"] const CLR_GUN = "\e[33m" # yellow @@ -64,7 +63,6 @@ type tsetlin: TsetlinGun guessFactor: GFGun patternMatcher: PatternMatcherGun - antiSurfer: AntiSurferGun wallBounce: WallBounceGun accelGun: AccelGun stopShot: StopShotGun @@ -92,7 +90,7 @@ type roundNumber: int realShotsFired: int realHits: int - gunSelectionCount: array[14, int] + gunSelectionCount: array[13, int] lastKnownTargetId: int ## persists through death, used for round-end stats proc printConfig(bot: ModularBot, changed: string = "") = @@ -184,7 +182,7 @@ method onRoundEnded*(bot: ModularBot, e: RoundEndedEventForBot) = f var gunsArr = newJArray() - for gid in 0..<14: + for gid in 0..<13: var totalShots = 0 var totalHits = 0 for binIdx in 0..= 0 and data exists; else aggregate. + ## Ties (within TieMargin) are broken randomly to avoid index-0 bias. ## ponytail: O(n*bins), fine for small gun counts let fit = t.fitnessFor(targetId) proc bestAmong(fit: seq[GunFitness], requireMin: bool): GunId = var bestRate = -1.0 - result = 0 for gunId in 0.. bestRate: - bestRate = r - result = gunId + if r > bestRate: bestRate = r + # Floor check: if nothing hits well enough, HeadOn is the safe default + if bestRate < MinHitRateFloor: return 0 + var tied: seq[GunId] + for gunId in 0..= bestRate - TieMargin: + tied.add(gunId) + break + if tied.len == 0: return 0 + return tied[rand(tied.len - 1)] var anyQualifies = false for gunId in 0..