## Offline chatter measurement for the gun selector's hysteresis. ## ## Replays real recorded DrussGT movement (and the synthetic set) through the 13 ## ModularBot guns and drives the SAME stateful selector the live bot uses ## (`selectGun`), counting how often the chosen gun changes. This is the ## deterministic analogue of counting the live `[config]` gun-switch lines: the ## replay feeds the identical per-tick fitness the live tracker builds, so the ## switch sequence is the same up to the seeded tie-break RNG. ## ## The env knobs (`GUN_SELECTOR_DWELL`, `GUN_SELECTOR_MARGIN`) are read once per ## process at module init, so sweep by running the binary under different values: ## ## nim c -r common_libs/tests/measure_hysteresis.nim # shipped ## GUN_SELECTOR_DWELL=0 GUN_SELECTOR_MARGIN=0 \ ## common_libs/tests/measure_hysteresis # pre-hysteresis baseline ## ## This is a MEASUREMENT tool, not a test; it always exits 0. import std/[os, strformat, algorithm, random] import gun_harness/offline_range import gun_harness/virtual_bullets import range_guns const repoRoot = currentSourcePath().parentDir.parentDir.parentDir fixturesDir = repoRoot / "tools" / "fixtures" type RunStat = object name: string ticks: int switches: int proc measure(path: string, seed: int): RunStat = let fx = loadFixture(path) let drivers = buildAllGunDrivers(seed = seed, enableTmSelector = false) var last = -1 var t = 0 var switches = 0 discard replayFixture(fx, drivers, liveActual = (fx.meta.source == "live"), tickCb = proc(tr: ptr VirtualTracker) = let g = selectGun(tr[], fx.enemyId, t) if last >= 0 and g != last: inc switches last = g inc t) RunStat(name: extractFilename(path), ticks: fx.states.len, switches: switches) proc main() = randomize(12345) var paths: seq[string] for f in walkFiles(fixturesDir / "drussgt_vs_*.jsonl"): paths.add f for f in walkFiles(fixturesDir / "tr_drussgt_vs_*.jsonl"): paths.add f paths.sort() var totalTicks = 0 var totalSwitches = 0 echo fmt"dwell={ActiveDwellTicks} ticks margin={ActiveSwitchMargin:.3f} " & fmt"mode={ActiveSelectorMode} rank={ActiveRank}" echo "fixture ticks switches sw/100t" echo "----------------------------------------------------------------" var stats: seq[RunStat] for p in paths: let s = measure(p, seed = 12345) stats.add s totalTicks += s.ticks totalSwitches += s.switches echo fmt"{s.name:<34}{s.ticks:>7}{s.switches:>10}{100.0*float(s.switches)/float(max(1,s.ticks)):>9.2f}" echo "----------------------------------------------------------------" let label = "TOTAL" echo fmt"{label:<34}{totalTicks:>7}{totalSwitches:>10}{100.0*float(totalSwitches)/float(max(1,totalTicks)):>9.2f}" when isMainModule: main()