## MEASUREMENT ONLY — per-tick cost of the virtual-bullet SPAWN gate ## (`TR_VBULLET_ADMIT_ONLY`), on the REAL 14-gun rack over recorded DrussGT ## movement. ## ## Gating SKIPS both `predict()` and `spawnBullets()` for a gun the current rack ## does not admit (shipped default rack = onlyPattern). This tool drives the same ## real guns + real `VirtualTracker` as `measure_parallel_vbullet_cost.nim`, but ## reports the ONE comparison the live bot cares about: ## ## OFF = pre-change: every non-TM gun predicts+spawns (ids 0..12) ## ON = shipped: only the rack-admitted guns predict+spawn (Pattern, id 5) ## ## Both arms run the SAME 13-gun rack (ids 0..12; TMSelect is disabled in the ## live bot via `EnableTmSelector=false`, TMPATTERN id 14 is separately gated). ## The gate predicate is the SHIPPED `vb.vBulletAdmitted` against the SHIPPED ## `DefaultRackMembership`, so this measures the real rule. ## ## Measured directly (not derived): predict+spawn wall time and the full ## `tickBullets` (resolve + onResult) wall time, per tick, for each arm. A ## per-gun breakdown names which costs disappear. ## ## Usage: ## nim c -r common_libs/tests/measure_vbullet_admit_gate.nim import std/[os, strformat, times, tables, math, monotimes, algorithm, random] import gun_harness/offline_range import gun_harness/gun_interface import gun_harness/virtual_bullets as vb import gun_harness/selector import range_guns const NGun = 14 NBins = len(vb.PowerBins) TmGun = 13 ## TMSelect — disabled in the live bot RepoRoot = currentSourcePath().parentDir.parentDir.parentDir FixturesDir = RepoRoot / "tools" / "fixtures" FixtureNames = [ "tr_drussgt_vs_corners.jsonl", "tr_drussgt_vs_crazy.jsonl", "tr_drussgt_vs_modularbot.jsonl", "tr_drussgt_vs_modularbot_shield.jsonl", "tr_drussgt_vs_spinbot.jsonl", "drussgt_vs_ramfire.jsonl", "drussgt_vs_drussgt.jsonl", ] MaxTicks = 2000 Warmup = 150 Measure = 400 const GunNames = ["HeadOn", "Linear", "Tsetlin", "Circular", "GuessFactor", "Pattern", "WallBounce", "Accel", "StopShot", "Displace", "AvgLead", "DecayGF", "KNN", "TMSelect"] proc loadFixtures(): seq[Fixture] = for n in FixtureNames: let p = FixturesDir / n if fileExists(p): result.add loadFixture(p) proc buildStates(fx: seq[Fixture], maxTicks: int): seq[seq[WorldState]] = var ticks = maxTicks for f in fx: ticks = min(ticks, f.states.len) result = newSeq[seq[WorldState]](fx.len) for k in 0..= Warmup and si < Warmup + Measure let t0 = getMonoTime() for gi in 0..= Warmup and si < Warmup + Measure for gi in 0.. 0: acc.pred[gi] /= counts[gi].float if onCalls[gi] > 0: acc.onRes[gi] /= onCalls[gi].float result = acc proc main() = randomize(1) let fx = loadFixtures() let states = buildStates(fx, MaxTicks) echo fmt"fixtures={fx.len} ticks={states[0].len} warmup={Warmup} measured={Measure}" echo "shipped rack: onlyPattern (DefaultRackMembership); predicate vb.vBulletAdmitted" # Two reps, take the min (least noise). var offPs, offRes, onPs, onRes: int64 var offSpawns, onSpawns, ticksRun: int for rep in 0..<2: let off = runArm(states, gateOn = false) let on = runArm(states, gateOn = true) if rep == 0: offPs = off.psNs; offRes = off.resNs; offSpawns = off.spawns onPs = on.psNs; onRes = on.resNs; onSpawns = on.spawns ticksRun = off.ticks else: offPs = min(offPs, off.psNs); offRes = min(offRes, off.resNs) onPs = min(onPs, on.psNs); onRes = min(onRes, on.resNs) offSpawns = min(offSpawns, off.spawns); onSpawns = min(onSpawns, on.spawns) let n = max(1, ticksRun).float let offPSms = offPs.float / 1.0e6 / n # ms/tick let offRSms = offRes.float / 1.0e6 / n let onPSms = onPs.float / 1.0e6 / n let onRSms = onRes.float / 1.0e6 / n let offTot = offPSms + offRSms let onTot = onPSms + onRSms echo "" echo "=================== PER-TICK COST: gate ON vs OFF (13-gun live rack) ===================" echo "arm predict+spawn ms/t resolve+onResult ms/t total ms/t ticks/s spawns/t" echo fmt"OFF {offPSms:>20.2f} {offRSms:>22.2f} {offTot:>12.2f} {1000.0/offTot:>7.0f} {offSpawns.float/n:>10.1f}" echo fmt"ON {onPSms:>20.2f} {onRSms:>22.2f} {onTot:>12.2f} {1000.0/onTot:>7.0f} {onSpawns.float/n:>10.1f}" echo fmt"reclaimed = {offTot - onTot:.2f} ms/tick ({100.0*(offTot-onTot)/offTot:.0f}% of the gate-OFF total)" echo fmt"implied ticks/s: OFF {1000.0/offTot:.0f} -> ON {1000.0/onTot:.0f} (x{offTot/onTot:.2f})" echo "(note: tickBullets also scans the fixed 8192-slot ring each tick, present in BOTH arms)" let pg = perGunCosts(states) echo "" echo "=================== PER-GUN COST (full rack, names what disappears) ===================" echo "gun predict+spawn ms/t onResult ms/t gateON" var sumPred, sumOn: float for gi in 0..24.3f}{pg.onRes[gi]/1.0e6:>18.3f}{($admitted):>10}" echo fmt"TOTAL {sumPred/1.0e6:>24.3f}{sumOn/1.0e6:>18.3f}" echo "" when isMainModule: main()