## Pure unit test for the fireTick-keyed wave pairing in the learned GF guns ## (guess_factor.nim, decay_gf.nim, knn_gun.nim). No battle, no Java, no fixtures. ## ## The defect this pins: the guns used to pop the OLDEST queued wave on every ## resolution (FIFO). Under the shipped bmPath metric a later-fired bullet can ## resolve first, so the outcome was attached to the wrong wave. The fix keys ## each wave by (fireTick, powerBin) exactly, as tsetlin.nim / tm_selector.nim do. ## ## The discriminating property is ORDER-INDEPENDENCE: with exact keying, learning ## from the same set of (wave, resolution) pairs must be identical no matter what ## order the resolutions arrive in. Under FIFO, resolving in reverse order pairs ## each event with the wrong wave and the learned state diverges. ## ## Run: nim c -r --path:common_libs common_libs/tests/test_wave_pairing.nim import std/math import gun_harness/gun_interface import guns/guess_factor import guns/decay_gf import guns/knn_gun var failures = 0 proc check(name: string, ok: bool) = if ok: echo "PASS: ", name else: echo "FAIL: ", name; inc failures const Spd = 17.0 # bulletSpeed(PowerBins[0]); all tests use power bin 0 const N = 6 # 6 waves, enough for KNN to leave its <5 cold-start fallback var Px: array[N, float] var Py: array[N, float] for i in 0.. starved, nothing resolved", g.waveStarved == 1 and g.waveResolved == 0 g.onResult(ev(0, Px[0], Py[0])) check "GF: the real wave still resolves after an unknown-tick event", g.waveResolved == 1 and g.waveStarved == 1 g.onResult(ev(0, Px[0], Py[0])) check "GF: a second resolve of a consumed wave is counted, not applied", g.waveResolved == 1 and g.waveStarved == 2 # ── DecayGF ─────────────────────────────────────────────────────────────────── proc testDecayGF() = var fwd = initDecayGFGun() var rev = initDecayGFGun() for i in 0.. starved, nothing resolved", g.waveStarved == 1 and g.waveResolved == 0 g.onResult(ev(0, Px[0], Py[0])) check "DecayGF: the real wave still resolves after an unknown-tick event", g.waveResolved == 1 and g.waveStarved == 1 # ── KNN ─────────────────────────────────────────────────────────────────────── proc testKNN() = var fwd = initKNNGun() var rev = initKNNGun() for i in 0.. starved, nothing resolved", g.waveStarved == 1 and g.waveResolved == 0 g.onResult(ev(0, Px[0], Py[0])) check "KNN: the real wave still resolves after an unknown-tick event", g.waveResolved == 1 and g.waveStarved == 1 # ── the ring slot is the (fireTick, bin) key ────────────────────────────────── proc testNonFifoResolveOrder() = ## Explicit non-FIFO sequence: fire 0,1,2; resolve 2,0,1. All three must find ## their own wave (0 starved), which FIFO cannot distinguish but exact keying ## must satisfy alongside the order-independence property above. var g = initGFGun() for i in 0..2: discard g.predict(ws(i, Px[i], Py[i]), Spd) g.onResult(ev(2, Px[2], Py[2])) g.onResult(ev(0, Px[0], Py[0])) g.onResult(ev(1, Px[1], Py[1])) check "GF: resolve order 2,0,1 -> all three waves found, none starved", g.waveResolved == 3 and g.waveStarved == 0 testGF() testGFMissingWave() testDecayGF() testDecayGFMissingWave() testKNN() testKNNMissingWave() testNonFifoResolveOrder() if failures > 0: echo "\n", failures, " check(s) FAILED" quit(1) echo "\nAll wave-pairing checks passed."