## j177 guard: the aim capture actually contains every field it promises, and ## the default gun path is byte-for-byte unchanged. ## ## NO battle, NO Java, NO server, NO GUI. ## nim c -r --path:common_libs --path:ModularBot_garage/src \ ## --nimcache:/tmp/nc_j177 common_libs/tests/test_aim_capture.nim ## ## Part 1 (the guard): with the capture ON, build one `aim_scan` and one ## `aim_fire` row from a real recorded tick of ## tools/fixtures/tr_drussgt_vs_modularbot.jsonl, through the SAME ## `aim_capture` row builders ModularBot.nim calls, and assert every required ## key is present and carries the value that was passed in. A capture that ## silently omits a field is worse than none. ## ## Part 2 (default parity): replay the whole recorded fixture through the real ## VirtualTracker + the guns with the capture OFF and compare the per-gun ## fitness report against `fixtures/aim_capture_gunpath.golden`. That golden ## was generated from the PRE-CHANGE tree (`git archive 55e92bc`) with the ## knob unset — regenerating it from this code would defeat the check. ## Part 2b: the same replay over a fixture that ALSO carries aim_scan/aim_fire ## lines must give the identical report, i.e. the annotations are inert. import std/[json, os, strutils, sequtils, strformat] import gun_harness/offline_range import range_guns import ../../ModularBot_garage/src/aim_capture const repoRoot = currentSourcePath().parentDir.parentDir.parentDir fixtureRel = "tr_drussgt_vs_modularbot.jsonl" fixture = repoRoot / "tools" / "fixtures" / fixtureRel goldenPath = currentSourcePath().parentDir / "fixtures" / "aim_capture_gunpath.golden" annotated = "/tmp/j177_annotated_fixture.jsonl" ScanKeys = ["tick", "eid", "ex", "ey", "eh", "es", "ee", "sx", "sy", "sh", "ss", "gun", "bx", "by", "bh", "bs", "blst", "age", "rlock", "rdir", "lbear", "boff"] FireKeys = ["tick", "eid", "gun", "power", "aim", "turret", "terr", "heat", "ax", "ay", "tof", "ex", "ey", "eh", "es", "ee", "sx", "sy", "lst"] var failures = 0 proc check(name: string, ok: bool) = if ok: echo "PASS: ", name else: echo "FAIL: ", name inc failures proc replayReport(path: string): string = ## Per-gun shots/hits over the whole fixture, through the REAL tracker and ## the REAL guns, with the capture OFF. let res = replayFixture(loadFixture(path), buildAllGunDrivers(seed = 1)) for r in res: result.add r.name & " shots=" & $r.shots & " hits=" & $r.hits & "\n" # ── Part 1: the records carry every field ──────────────────────────────────── proc fieldCheck() = let states = loadFixture(fixture).states check("fixture loaded", states.len > 1000) let ws = states[900] let scan = scanRow(AimScan( tick: 900, eid: 7, ex: ws.enemyX, ey: ws.enemyY, eh: ws.enemyHeading, es: ws.enemySpeed, ee: ws.enemyEnergy, sx: ws.selfX, sy: ws.selfY, sh: ws.selfHeading, ss: ws.selfSpeed, gun: 5, bx: ws.enemyX - 8.0, by: ws.enemyY - 8.0, bh: ws.enemyHeading, bs: ws.enemySpeed, blst: 896, rlock: true, rdir: 42.5, lbear: bearing(ws.enemyX - 8, ws.enemyY - 8, ws.selfX, ws.selfY), boff: -12.25)) let fire = fireRow(AimFire( tick: 900, eid: 7, gun: 5, power: 1.6, aim: 88.25, turret: 74.0, terr: 14.25, heat: 0.31, ax: ws.enemyX + 40.0, ay: ws.enemyY - 15.0, tof: 12.5, ex: ws.enemyX, ey: ws.enemyY, eh: ws.enemyHeading, es: ws.enemySpeed, ee: ws.enemyEnergy, sx: ws.selfX, sy: ws.selfY, lst: 897)) for k in ScanKeys: check("aim_scan has " & k, scan[ScanRecordKey].hasKey(k)) for k in FireKeys: check("aim_fire has " & k, fire[FireRecordKey].hasKey(k)) check("aim_scan carries the gun id", scan[ScanRecordKey]["gun"].getInt() == 5) check("aim_fire carries the gun id", fire[FireRecordKey]["gun"].getInt() == 5) check("aim_scan records the scan parity age", scan[ScanRecordKey]["age"].getInt() == 4) check("aim_fire records the source tick (parity)", fire[FireRecordKey]["lst"].getInt() == 897) check("aim_fire carries the aim angle", fire[FireRecordKey]["aim"].getFloat() == 88.25) check("aim_fire carries the turret error", fire[FireRecordKey]["terr"].getFloat() == 14.25) check("aim_fire carries the WorldState the model consumed", fire[FireRecordKey]["ex"].getFloat() == ws.enemyX and fire[FireRecordKey]["ey"].getFloat() == ws.enemyY) echo "\n--- aim_scan record ---" echo $scan echo "--- aim_fire record ---" echo $fire echo "" # ── Part 2: default gun-path parity ────────────────────────────────────────── proc parityCheck() = let clean = replayReport(fixture) let ticks = loadFixture(fixture).states.len if defined(aimCapGenGolden): var g = "# j177 gun-path default-parity golden.\n" g.add "# Generated from the PRE-CHANGE tree (`git archive 55e92bc`) with\n" g.add "# TR_CAPTURE_AIM unset, over the whole " & fixtureRel & ".\n" g.add "# Format: shots= hits=, one line per rack gun\n" g.add clean createDir(goldenPath.parentDir) writeFile(goldenPath, g) echo "wrote ", goldenPath, " (", ticks, " ticks)" return check("golden exists", fileExists(goldenPath)) if not fileExists(goldenPath): return let g = lines(goldenPath).toSeq().filterIt(not it.startsWith("#")).join("\n").strip() check("gun path byte-for-byte identical over " & $ticks & " ticks", g == clean.strip()) # 2b: the annotation lines must be inert for the replay. let extra = @[ $scanRow(AimScan(tick: 0, eid: 1, ex: 1.0, ey: 2.0, eh: 3.0, es: 4.0, ee: 5.0, sx: 6.0, sy: 7.0, sh: 8.0, ss: 9.0, gun: 3, bx: 0.0, by: 0.0, blst: -1, rlock: true, rdir: 1.0, lbear: 2.0)), $fireRow(AimFire(tick: 1, eid: 1, gun: 3, power: 1.5, aim: 1.0, turret: 2.0, terr: 3.0, heat: 0.0, ax: 1.0, ay: 1.0, tof: 1.0, ex: 1.0, ey: 1.0, eh: 1.0, es: 1.0, ee: 1.0, sx: 1.0, sy: 1.0, lst: 0))] writeFile(annotated, (lines(fixture).toSeq() & extra).join("\n")) check("aim records do not perturb the replay", replayReport(annotated) == clean) removeFile(annotated) fieldCheck() parityCheck() echo (if failures == 0: "\nALL PASS" else: "\n" & $failures & " FAILURE(S)") quit(if failures == 0: 0 else: 1)