## Unit tests for EnemyTracker's death invariant: once an enemy is marked dead ## it stays dead, and every alive view excludes it. ## ## This is the invariant that actually matters in production. The earlier ## premise — "BotDeathEvent never reaches the bot, so corpses persist" — was ## MEASURED false (0 dropped BotDeath events, 0 corpse ticks; see ## docs/tracker_death_events.md). The reconciliation feature that used to be ## tested here was retired with it. The event-drop MECHANISM is real and is ## covered separately by test_event_drop_mechanism.nim. ## ## Headless: no Java, no server, no battle. ## nim c -r common_libs/tests/test_enemy_tracker_death.nim import std/[tables, algorithm] import targeting/enemy_tracker var failures = 0 proc check(name: string, ok: bool) = if ok: echo "PASS: ", name else: echo "FAIL: ", name; inc failures proc see(et: var EnemyTracker, id, tick: int) = et.update(id, float(id) * 100.0, 0.0, 0.0, 0.0, 100.0, tick) proc testMarkDeadImmediate() = var et: EnemyTracker et.see(5, 10) check "precondition: a scanned enemy is alive", et.isAlive(5) et.markDead(5) # Same tick: no intervening update, no reconciliation. check "immediate: markDead flips the flag in the same tick", not et.isAlive(5) check "immediate: aliveCount drops the dead enemy at once", et.aliveCount == 0 check "immediate: allAlive drops the dead enemy at once", et.allAlive.len == 0 proc testNoResurrection() = var et: EnemyTracker et.see(5, 10) et.markDead(5) # A later scan of the same id (queued/duplicate scan) must not revive it. et.update(5, 1.0, 2.0, 3.0, 4.0, 100.0, 11) check "no-resurrect: a later update does not revive a dead enemy", not et.isAlive(5) check "no-resurrect: the dead enemy is not counted alive", et.aliveCount == 0 check "no-resurrect: allAlive still excludes it", et.allAlive.len == 0 proc testAliveViewsExcludeDead() = var et: EnemyTracker et.see(1, 10) et.see(2, 10) et.see(3, 10) et.markDead(2) check "views: isAlive is false only for the dead id", et.isAlive(1) and not et.isAlive(2) and et.isAlive(3) check "views: aliveCount counts only the living", et.aliveCount == 2 var ids: seq[int] for s in et.allAlive(): ids.add s.id ids.sort() check "views: allAlive returns exactly the living ids", ids == @[1, 3] proc testMarkDeadUnknownIsNoop() = var et: EnemyTracker et.see(1, 10) et.markDead(99) check "noop: markDead on an unknown id creates nothing", et.enemies.len == 1 and not et.isAlive(99) and et.aliveCount == 1 proc testDoubleMarkDead() = var et: EnemyTracker et.see(7, 10) et.markDead(7) et.markDead(7) check "idempotent: marking the same enemy dead twice is harmless", not et.isAlive(7) and et.aliveCount == 0 proc testResetRoundClears() = var et: EnemyTracker et.see(1, 10) et.see(2, 10) et.markDead(2) et.resetRound() check "resetRound: clears every tracked enemy (alive and dead)", et.enemies.len == 0 and et.aliveCount == 0 and not et.isAlive(1) and not et.isAlive(2) testMarkDeadImmediate() testNoResurrection() testAliveViewsExcludeDead() testMarkDeadUnknownIsNoop() testDoubleMarkDead() testResetRoundClears() if failures > 0: echo "\n", failures, " check(s) FAILED" quit(1) echo "\nAll enemy-tracker death checks passed."