## MEASUREMENT ONLY — offline cost benchmark for "keep per-enemy virtual bullets ## for ALL enemies in parallel" (kill-stealing feature). ## ## Drives the REAL 14-gun rack from `common_libs/guns/*` and the REAL ## `VirtualTracker`/`spawnBullets`/`tickBullets` from ## `common_libs/gun_harness/virtual_bullets.nim`, over recorded DrussGT movement ## (`tools/fixtures/*.jsonl`, READ-ONLY). ## ## NO shipped source is edited; NO bot is rebuilt; offline only. ## ## Synthesised melee: there is no melee fixture in the repo, so N "enemies" are ## N DISTINCT recorded trajectories (different fixtures), all placed in one ## 800x600 arena with the shooter pose taken from fixture 0. Stated explicitly — ## it is not a real melee capture. ## ## Two rack topologies are measured: ## * SHARED — one 14-gun rack, predict() called once per (target, gun, bin). ## The literal minimal change against the current bot, whose guns ## are global. Per-tick caches (keyed on state.tick) are SHARED ## across targets. ## * PER-ENEMY — one 14-gun rack PER target (N racks), so each enemy gets a ## clean gun history. Gun ids are flattened to target*14+gun. ## ## RING CAP: VirtualTracker.MaxBullets = 8192 (shipped const). At N targets the ## spawn rate is 56*N/tick and a path-metric bullet lives until it leaves the ## arena (tens of ticks), so N>=2 overflows the ring and silently drops bullets. ## The `dropped/tick` column measures that. The full-pipeline cost for a WORKING ## ring is therefore reported as a PROJECTION: measured predict+spawn(N) plus ## N x the measured N=1 onResult cost per tick (onResult is per resolved bullet, ## and a working pipeline resolves every spawned bullet). ## ## Usage: ## GUN_SELECTOR_MODE=absolute nim c -r common_libs/tests/measure_parallel_vbullet_cost.nim import std/[os, strformat, times, tables, math, algorithm, monotimes, random] import gun_harness/offline_range import gun_harness/gun_interface import gun_harness/virtual_bullets as vb import range_guns const NGun = 14 NBins = len(vb.PowerBins) TmGun = 13 ## TMSelect index (shipped DISABLED) 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 # ── fixtures / state synthesis ──────────────────────────────────────────────── 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]] = ## Per-target WorldState streams. Target k keeps its own recorded enemy pose ## but shares the shooter pose of fixture 0, and all targets share a single ## continuous tick counter `si` so the guns' per-tick caches align. var ticks = maxTicks for f in fx: ticks = min(ticks, f.states.len) result = newSeq[seq[WorldState]](fx.len) for k in 0.. 0: var ds: seq[tuple[d: float, k: int]] for k in 0.. 0 and (si mod rotateMod) != (k mod rotateMod): continue let ws = states[k][si] let rack = racks[if perEnemy: k else: 0] for gi in 0..= warmup and si < warmup + measure: inc result.spawns, NBins let t1 = getMonoTime() if (si mod resolveEvery) == 0: let et = enemyTable(states, si) tracker.tickBullets(states[0][si], et, proc(gunId: GunId, binIdx: int, e: FeedbackEvent) = if includeTm or gunId mod NGun != TmGun: racks[gunId div NGun][gunId mod NGun].resultCb(e)) let t2 = getMonoTime() if si == warmup: dropStart = tracker.droppedBullets if si >= warmup and si < warmup + measure: inc result.ticks result.predictSpawnNs += (t1 - t0).inNanoseconds result.resolveNs += (t2 - t1).inNanoseconds dropEnd = tracker.droppedBullets # dropped during the MEASUREMENT window only result.dropped = dropEnd - dropStart # ── per-gun breakdown (N = 1, timed per call) ───────────────────────────────── type PerGun = object calls: array[NGun, array[NBins, int]] predNs: array[NGun, array[NBins, int64]] onCalls: array[NGun, int] onNs: array[NGun, int64] measure: int proc runPerGun(states: seq[seq[WorldState]], warmup, measure: int): ref PerGun = let rack = buildAllGunDrivers(seed = 1, enableTmSelector = true) var tracker = vb.initTracker(NGun, bmPath) let r = new(PerGun) r.measure = measure let ticks = states[0].len for si in 0..= warmup and si < warmup + measure: r.predNs[gi][b] += dt inc r.calls[gi][b] tracker.spawnBullets(gi, preds, ws, 1) let et = enemyTable(states, si) tracker.tickBullets(ws, et, proc(gunId: GunId, binIdx: int, e: FeedbackEvent) = let t0 = getMonoTime() rack[gunId].resultCb(e) let dt = (getMonoTime() - t0).inNanoseconds if si >= warmup and si < warmup + measure: r.onNs[gunId] += dt inc r.onCalls[gunId]) result = r const GunNames = ["HeadOn", "Linear", "Tsetlin", "Circular", "GuessFactor", "Pattern", "WallBounce", "Accel", "StopShot", "Displace", "AvgLead", "DecayGF", "KNN", "TMSelect"] proc printPerGun(pg: ref PerGun) = echo "" echo "=================== BASELINE PER-GUN (N=1, 14-gun rack) ===================" echo "(times include one getMonoTime pair ~50ns/call; cheap guns are overhead-bound)" echo "gun calls/tick bin0 ns/call bin1-3 ns/call predict ns/tick onResult ns/call onRes/tick" for gi in 0.. 0: pg.predNs[gi][0].float / c0.float else: 0.0 let b13 = if c13 > 0: (pg.predNs[gi][1] + pg.predNs[gi][2] + pg.predNs[gi][3]).float / c13.float else: 0.0 let pt = (pg.predNs[gi][0] + pg.predNs[gi][1] + pg.predNs[gi][2] + pg.predNs[gi][3]).float / max(1, pg.measure).float let onc = if pg.onCalls[gi] > 0: pg.onNs[gi].float / pg.onCalls[gi].float else: 0.0 let onpt = pg.onNs[gi].float / max(1, pg.measure).float echo fmt"{GunNames[gi]:<12} {float(c0 + c13) / max(1, pg.measure).float:10.1f} " & fmt"{b0:12.0f} {b13:14.0f} {pt:14.0f} {onc:16.0f} {onpt:9.0f}" proc onNsPerTick(pg: ref PerGun, includeTm: bool): float = ## Total onResult cost per tick at N=1 for the selected rack. for gi in 0.. 0: 1000.0 / projMs else: 0.0 echo fmt"{rk:<10} {topo} {n:>2} {psMs:11.2f} {rMs:11.2f} {dropPt:11.1f} " & fmt"{projMs:13.2f} {tps:7.0f} {projMs / BudgetMs * 100.0:6.0f}%" # ── mitigations at N = 6 (shipped 13-gun shared rack) ─────────────────────── echo "" echo "=================== MITIGATIONS at N=6 (shipped 13-gun, shared rack) ===================" var basePs = 1.0e18 for rep in 0..