## Task 1 + Task 3 measurement: wave-pairing audit and before/after hit rates for ## the three learned GF guns (GuessFactor / DecayGF / KNN). ## ## Replays the committed DrussGT fixtures through the REAL VirtualTracker, exactly ## as common_libs/gun_harness/offline_range.replayFixture does, but keeps handles ## to the concrete guns so it can read their pairing-audit counters and dump the ## raw per-bullet hit booleans for a later bullet-level permutation test. ## ## Run: ## nim c -r --path:common_libs common_libs/tests/audit_wave_pairing.nim [metric] ## tag = label written into /tmp/wavepair__.txt ## metric = path (default, shipped) | point | both import std/[os, math, strformat, tables, algorithm, random] import gun_harness/gun_interface import gun_harness/virtual_bullets as vb import gun_harness/offline_range import guns/guess_factor import guns/decay_gf import guns/knn_gun const fixturesDir = currentSourcePath().parentDir.parentDir.parentDir / "tools" / "fixtures" const GunNames = ["GuessFactor", "DecayGF", "KNN"] type Ref[G] = ref object g: G proc mkRef[G](v: G): Ref[G] = Ref[G](g: v) proc driver[G](name: string, r: Ref[G]): GunDriver = result.name = name result.predictCb = proc(s: WorldState, sp: float): GunPrediction = r.g.predict(s, sp) result.resultCb = proc(e: FeedbackEvent) = r.g.onResult(e) result.readyCb = nil proc collectOne(fx: Fixture, drivers: seq[GunDriver], metric: BulletMetric, perGunHits: ref seq[seq[bool]]): seq[GunReport] = let tid = fx.enemyId var tracker = vb.initTracker(drivers.len, metric) for si in 0..= 0: lst = fx.lastSeen[si] enemyPositions[tid] = (x: act.enemyX, y: act.enemyY, lastSeenTick: lst, alive: true) let dref = drivers tracker.tickBullets(state, enemyPositions, proc(gunId: GunId, binIdx: int, e: FeedbackEvent) = dref[gunId].resultCb(e) perGunHits[gunId].add e.hit) let fit = tracker.fitnessFor(tid) for gi in 0..4}/{r.shots:<4}" echo line totResolved[0] += gfRef.g.waveResolved; totMispaired[0] += gfRef.g.waveMispaired totMispaired[0] += gfRef.g.waveMispaired; totStarved[0] += gfRef.g.waveStarved totPushes[0] += gfRef.g.wavePushes totResolved[1] += decRef.g.waveResolved; totMispaired[1] += decRef.g.waveMispaired totStarved[1] += decRef.g.waveStarved totPushes[1] += decRef.g.wavePushes totResolved[2] += knnRef.g.waveResolved; totMispaired[2] += knnRef.g.waveMispaired totStarved[2] += knnRef.g.waveStarved totPushes[2] += knnRef.g.wavePushes echo "" echo "── pooled hit rate (", mname, ") ──" for gi in 0..<3: echo fmt"{GunNames[gi]:<12} {pooled[gi]:>5}/{pooledN[gi]:<6} {rate(pooled[gi], pooledN[gi]):>6.2f}% per-fixture min/max {min(perFixtureRate[gi]):.1f}/{max(perFixtureRate[gi]):.1f}" echo "" echo "── pairing audit (", mname, ") ──" echo "gun resolved mispaired mispair% starved pushes" for gi, name in GunNames: echo fmt"{name:<12} {totResolved[gi]:>8} {totMispaired[gi]:>10} {rate(totMispaired[gi], totResolved[gi]):>8.2f} {totStarved[gi]:>7} {totPushes[gi]:>6}" # Dump per-gun hit booleans for the cross-build permutation test. for gi in 0..<3: let path = fmt"/tmp/wavepair_{tag}_{mname}_{GunNames[gi]}.txt" var f = open(path, fmWrite) defer: f.close() for h in perGunHits[gi]: f.writeLine(if h: "1" else: "0") echo fmt"dumped {perGunHits[gi].len} outcomes -> {path}" proc main() = let tag = if paramCount() >= 1: paramStr(1) else: "run" let metricArg = if paramCount() >= 2: paramStr(2) else: "path" case metricArg of "point": runMetric("point", bmPoint, tag) of "both": (runMetric("path", bmPath, tag); runMetric("point", bmPoint, tag)) else: runMetric("path", bmPath, tag) echo "\ndone." when isMainModule: randomize(1) main()