## Unit checks for the j133 STRAFE fire-detection fix (`TR_STRAFE_FIRE_FIX`). ## ## NO battle, NO Java, NO server. Run with: ## nim c -r --nimcache:/tmp/nc_j133 --path:common_libs \ ## common_libs/tests/test_strafe_fire_fix.nim ## ## The shipped detector classified the enemy energy delta `prev - cur` with the ## single window `0.09 <= drop <= 3.01`. Two SERVER facts break that window: ## ## 1. `BULLET_HIT_ENERGY_GAIN_FACTOR = 3` (server rules.kt): when an ENEMY ## bullet hits us, the SHOOTER's energy RISES by `3*power`. If it fired in ## the same tick the two cancel and the net delta reads as "no fire" -> ## the bullet gets NO heat. ## 2. our own bullet damaging the enemy in the same tick adds `damage` to the ## delta, so it can exceed 3.01 -> REJECTED, no heat for the enemy's shot. ## ## The fix undoes both from the events (`noteEnemyBulletHit` / `noteDamageDealt`) ## and splits any still-too-large delta instead of dropping it. These checks pin ## the four behaviours plus the OFF-switch parity with the shipped detector. import std/[math, os] import gun_harness/gun_interface # Private-field access: include (do NOT import) the mover, exactly as # `measure_strafe_heat_display.nim` does for the display check. include movements/strafe var failures = 0 proc check(name: string, ok: bool) = if ok: echo "PASS: ", name else: echo "FAIL: ", name; inc failures proc ws(ee: float): WorldState = WorldState( enemyX: 200.0, enemyY: 200.0, enemyEnergy: ee, selfX: 400.0, selfY: 300.0, selfHeading: 0.0, selfSpeed: 6.0, arenaWidth: 800.0, arenaHeight: 600.0, tick: 10, enemies: @[EnemyInfo(id: 1, x: 200.0, y: 200.0, energy: ee)]) proc freshModule(): StrafeModule = var m = initStrafe() m.initGrid(800.0, 600.0) m.resetRound() m # ── 1. plain fire (unchanged behaviour) ─────────────────────────────────────── block: let m = freshModule() var mm = m mm.detectFires(ws(100.0)) mm.detectFires(ws(97.95)) # fired 2.05? use a clean power # second reading: 100 -> 97.95 is a 2.05 drop, in range -> one wave check "plain fire creates exactly one wave", mm.bullets.len == 1 check "plain fire wave power == drop", mm.bullets.len == 1 and abs(mm.bullets[0].power - 2.05) < 1e-9 # ── 2. the +3*power bonus masks a fire (the owner's "bullet without heat") ──── block: # Enemy at 100 fires 0.35 while an EARLIER 0.45 bullet of its own hits us # (+3*0.45 = +1.35). Net energy = 100 - 0.35 + 1.35 = 101.0 -> raw drop = -1.0. let m = freshModule() var mm = m mm.noteEnemyBulletHit(0.45) mm.detectFires(ws(101.0)) check "masked fire: WITH fix a wave is created", mm.bullets.len == 1 check "masked fire: WITH fix the power is recovered", mm.bullets.len == 1 and abs(mm.bullets[0].power - 0.35) < 1e-9 # ── 3. our own damage inflates the drop past the cap ───────────────────────── block: # Enemy at 50 fires 2.0 and our bullet hits it for 1.8 -> energy 46.2, # raw drop 3.8 > 3.01 (shipped detector rejects it outright). let m = freshModule() var mm = m mm.prevEnergySet(1, 50.0) # last reading before the contested tick mm.noteDamageDealt(1.8) mm.detectFires(ws(46.2)) check "inflated drop: WITH fix a wave is created", mm.bullets.len == 1 check "inflated drop: WITH fix the power is recovered", mm.bullets.len == 1 and abs(mm.bullets[0].power - 2.0) < 1e-9 # ── 4. never silently drop a too-large delta (unobserved contamination) ─────── block: let m = freshModule() var mm = m mm.detectFires(ws(100.0)) mm.detectFires(ws(93.0)) # 7.0 with no events -> split, not dropped check "too-large drop is SPLIT, never dropped", mm.bullets.len == 3 check "split waves all within the power cap", mm.bullets.len == 3 and mm.bullets[0].power <= 3.0 and mm.bullets[1].power <= 3.0 check "split waves sum back to the drop", mm.bullets.len == 3 and abs(mm.bullets[0].power * 3.0 - 7.0) < 1e-9 # ── 5. OFF switch == shipped detector, byte for byte ───────────────────────── block: putEnv("TR_STRAFE_FIRE_FIX", "0") loadStrafeEnv() # (a) masked fire: shipped detector sees drop = -1.0 -> NO wave. var a = freshModule() a.noteEnemyBulletHit(0.45) # must be a no-op when the fix is off a.detectFires(ws(101.0)) check "OFF: masked fire is NOT detected (shipped behaviour)", a.bullets.len == 0 # (b) inflated drop: shipped detector sees 3.8 -> rejected -> NO wave. var b = freshModule() b.prevEnergySet(1, 50.0) b.noteDamageDealt(1.8) b.detectFires(ws(46.2)) check "OFF: inflated drop is rejected (shipped behaviour)", b.bullets.len == 0 # (c) in-range drop is still caught exactly once. var c = freshModule() c.detectFires(ws(100.0)) c.detectFires(ws(98.0)) check "OFF: plain fire still caught once", c.bullets.len == 1 and abs(c.bullets[0].power - 2.0) < 1e-9 delEnv("TR_STRAFE_FIRE_FIX") loadStrafeEnv() # ── 6. default is ON ───────────────────────────────────────────────────────── block: delEnv("TR_STRAFE_FIRE_FIX") loadStrafeEnv() check "default TR_STRAFE_FIRE_FIX is ON", StrafeFireFix if failures == 0: echo "\nAll STRAFE fire-detection fix checks passed." else: echo "\n", failures, " STRAFE fire-detection fix check(s) FAILED." quit(1)