## MINIMAL LIVE CHECK for the rack-registered TM pattern gun (id 14). ## ## Proves the new gun is a first-class rack member that the LIVE ModularBot ## actually predicts, spawns and can be forced alone with env vars only: ## ## TR_RACK_HEADON=off ... TR_RACK_TMSELECT=off TR_RACK_TMPATTERN=both ## ## One 1-round battle (deliberately minimal — the full A/B runs in a later job). ## Reads /tmp/tm_pattern_rack_stats.jsonl (the bot's per-round gun dump) and ## asserts gun 14 was selected and spawned virtual bullets while every other gun ## was deselected. ## ## Run with: ## TR_SERVER_JAR=... TR_BATTLE_RUNNER=... \ ## nim c -r common_libs/tests/test_tm_pattern_rack_live.nim ## ## Skips (exit 0) when the JARs are absent, matching the other live tests. import std/[os, json, strutils, tables] import test_framework/test_framework const repoRoot = currentSourcePath().parentDir.parentDir.parentDir modularBotDir = repoRoot / "ModularBot_garage" adversaryDir = repoRoot / "common_libs" / "test_framework" / "adversaries" / "SittingDuck" statsPath = "/tmp/tm_pattern_rack_stats.jsonl" const TmPatternId = 14 const RackGunNames = ["HEADON", "LINEAR", "TSETLIN", "CIRCULAR", "GUESSFACTOR", "PATTERN", "WALLBOUNCE", "ACCEL", "STOPSHOT", "DISPLACE", "AVGLEAD", "DECAYGF", "KNN", "TMSELECT", "TMPATTERN"] var failures = 0 proc check(name: string, ok: bool) = if ok: echo "PASS: ", name else: echo "FAIL: ", name; inc failures proc lastRound(path: string): JsonNode = result = nil for line in lines(path): let s = line.strip() if s.len == 0: continue let node = parseJson(s) if node.hasKey("guns"): result = node proc main() = let serverJar = getEnv("TR_SERVER_JAR", "") let runnerJar = getEnv("TR_BATTLE_RUNNER", "") if serverJar.len == 0 or runnerJar.len == 0: echo "Skipping: TR_SERVER_JAR / TR_BATTLE_RUNNER not set" quit(0) if not fileExists(serverJar) or not fileExists(runnerJar): echo "Skipping: TR JARs not found (server=", serverJar, ", runner=", runnerJar, ")" quit(0) if not fileExists(modularBotDir / "src" / "ModularBot.nim"): echo "Skipping: ModularBot source not found" quit(0) if fileExists(statsPath): removeFile(statsPath) # Force the new gun alone. Every other rack entry is removed; TMPATTERN is # admitted in both racks. With no other candidate, the selector must pick it. for i, name in RackGunNames: putEnv("TR_RACK_" & name, if i == TmPatternId: "both" else: "off") putEnv("GUN_VBULLET_METRIC", "point") # the metric the radial win lives on putEnv("GUN_STATS_PATH", statsPath) defer: for name in RackGunNames: delEnv("TR_RACK_" & name) delEnv("GUN_VBULLET_METRIC") delEnv("GUN_STATS_PATH") echo "=== live: ModularBot (only TMPATTERN racked) vs SittingDuck, 1 round ===" let battle = runBattle(@[modularBotDir, adversaryDir], rounds = 1, timeout = 180000, maxSpeed = true) for res in battle.results: echo " ", res.name, " rank=", res.rank, " score=", res.totalScore if not fileExists(statsPath): echo "FAIL: no gun_stats dump at ", statsPath quit(1) let round = lastRound(statsPath) check "gun_stats.jsonl has a round record", round != nil if round == nil: quit(1) var tmShots = 0 var tmSelected = 0 var otherSelected = 0 for g in round["guns"]: let id = g["id"].getInt() if id == TmPatternId: tmShots = g["vShots"].getInt() tmSelected = g["selected"].getInt() elif g["selected"].getInt() > 0: otherSelected += g["selected"].getInt() echo "gun 14 (TMPattern): vShots=", tmShots, " selected=", tmSelected, " other-gun selections=", otherSelected check "the live loop SPAWNED virtual bullets for gun 14 (vShots > 0)", tmShots > 0 check "the live selector SELECTED gun 14 (selected > 0)", tmSelected > 0 check "no other gun was selected while TMPATTERN was forced alone", otherSelected == 0 if failures > 0: echo "\n", failures, " check(s) FAILED" quit(1) echo "\nTM pattern rack live check passed." when isMainModule: main()