## Pure unit guard for the ram decision (`movements/ram_decision.nim`). ## ## NO battle, NO Java, NO server. Run with: ## nim c -r common_libs/tests/test_ram_decision.nim ## ## These pin the two contracts the proactive-ram change depends on: ## 1. the opportunity trigger can fire at a distance the mover can actually ## close FROM (the old `dist < 50` gate could not — the closest measured ## approach vs DrussGT was 118.7px, so it never fired); ## 2. the bullet-rain abort only applies to an IN-PROGRESS ram and is driven by ## the windowed incoming-damage rate. import std/math import movements/ram_decision var failures = 0 proc check(name: string, ok: bool) = if ok: echo "PASS: ", name else: echo "FAIL: ", name; inc failures # ── 1. the shipped defaults are the RELAXED gates ──────────────────────────── proc testDefaults() = check "default opportunity distance is relaxed to 200 (was 50)", DefaultRamOppDist == 200.0 check "default opportunity margin is relaxed to 15 (was 30)", DefaultRamOppMargin == 15.0 check "default bullet-rain abort threshold is 0.5/turn", DefaultRamAbortDmg == 0.5 check "change-of-plan hit-rate bar defaults to 0.05", DefaultRamPlanHitRate == 0.05 # ── 2. opportunity fires at a closable distance (the user's scenario) ──────── proc testOpportunityAtClosableRange() = # The exact shape the diagnosis used: dist 143, we are clearly healthier. let inp = RamInputs(dist: 143.0, selfEnergy: 78.0, enemyEnergy: 41.0, gunHitRate: 0.0) check "opportunity fires at dist 143 with a 37-energy lead", ramTrigger(inp) == rrOpportunity # Same state under the OLD gate must NOT fire: this is the chicken-and-egg. check "old gate (dist<50, margin+30) does NOT fire at dist 143", ramTrigger(inp, oppDist = 50.0, oppMargin = 30.0) == rrNone # Just inside the new distance edge fires; just outside does not. check "opportunity fires at 199px", ramTrigger(inp, oppDist = 200.0) == rrOpportunity check "opportunity does NOT fire at 201px", ramTrigger(RamInputs(dist: 201.0, selfEnergy: 78.0, enemyEnergy: 41.0)) == rrNone # ── 3. the energy margin is env-tunable and enforced ───────────────────────── proc testMargin() = # 9-energy lead is below the default 15 margin, so no opportunity... let thin = RamInputs(dist: 143.0, selfEnergy: 50.0, enemyEnergy: 41.0) check "9-energy lead does NOT trigger at the default margin", ramTrigger(thin) == rrNone # ...but a looser margin (sweepable via TR_RAM_OPP_MARGIN) lets it fire. check "9-energy lead fires with a 5-energy margin", ramTrigger(thin, oppMargin = 5.0) == rrOpportunity # A stricter margin vetoes a lead that the default accepts. check "37-energy lead vetoed by a 40-energy margin", ramTrigger(RamInputs(dist: 143.0, selfEnergy: 78.0, enemyEnergy: 41.0), oppMargin = 40.0) == rrNone # ── 4. finisher + desperation keep working and take priority ───────────────── proc testFinisherAndDesperation() = # Finisher: enemy nearly dead, we are healthier, proactive distance. check "finisher fires at dist 250 against a 12-energy enemy", ramTrigger(RamInputs(dist: 250.0, selfEnergy: 60.0, enemyEnergy: 12.0)) == rrFinisher # Finisher outranks opportunity when both match. check "finisher takes priority over opportunity", ramTrigger(RamInputs(dist: 40.0, selfEnergy: 80.0, enemyEnergy: 12.0)) == rrFinisher # Desperation: both nearly dead, short range. check "desperation fires when both are nearly dead", ramTrigger(RamInputs(dist: 100.0, selfEnergy: 3.0, enemyEnergy: 3.0)) == rrDesperation # Dead enemy never triggers anything. check "zero enemy energy never triggers", ramTrigger(RamInputs(dist: 20.0, selfEnergy: 100.0, enemyEnergy: 0.0)) == rrNone # ── 5. change-of-plan trigger is opt-in and needs a failing gun ────────────── proc testPlanTrigger() = # dist 220 is OUTSIDE the opportunity gate (200) but inside the plan gate # (250), so only the change-of-plan trigger can match here. let inp = RamInputs(dist: 220.0, selfEnergy: 80.0, enemyEnergy: 50.0, gunHitRate: 0.01) # Disabled (the default): the plan trigger must NOT fire. check "plan trigger does not fire while disabled", ramTrigger(inp, planEnabled = false) == rrNone # Enabled + poor gun hit rate: fires. check "plan trigger fires when enabled and the gun rate is poor", ramTrigger(inp, planEnabled = true) == rrPlan # Enabled + good gun hit rate: does not fire. check "plan trigger does not fire when the gun rate is healthy", ramTrigger(RamInputs(dist: 220.0, selfEnergy: 80.0, enemyEnergy: 50.0, gunHitRate: 0.30), planEnabled = true) == rrNone # Enabled but not out-energising enough: does not fire. check "plan trigger does not fire without the energy lead", ramTrigger(RamInputs(dist: 220.0, selfEnergy: 55.0, enemyEnergy: 50.0, gunHitRate: 0.01), planEnabled = true) == rrNone # ── 6. the incoming-damage window ──────────────────────────────────────────── proc testDamageRate() = check "empty window -> 0.0/turn", damageRatePerTurn(newSeq[float]()) == 0.0 check "all-zero window -> 0.0/turn", damageRatePerTurn([0.0, 0.0, 0.0]) == 0.0 check "window mean is exact", abs(damageRatePerTurn([0.0, 0.6, 0.0, 1.2, 0.0]) - 0.36) < 1e-12 check "one hit in a 15-turn window reads low (0.04/turn)", abs(damageRatePerTurn([0.6, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]) - 0.04) < 1e-12 proc testAbort() = check "no abort before the ram has started", not shouldAbortRam(ramming = false, dmgRate = 5.0) check "no abort at exactly the threshold", not shouldAbortRam(ramming = true, dmgRate = 0.5) check "abort above the threshold", shouldAbortRam(ramming = true, dmgRate = 0.51) check "threshold is overridable", shouldAbortRam(ramming = true, dmgRate = 0.51, abortDmg = 0.6) == false and shouldAbortRam(ramming = true, dmgRate = 0.61, abortDmg = 0.6) == true proc testReasonNames() = check "reason names match the log vocabulary", reasonName(rrNone) == "none" and reasonName(rrFinisher) == "finisher" and reasonName(rrOpportunity) == "opportunity" and reasonName(rrDesperation) == "desperation" and reasonName(rrPlan) == "plan" # ── driver ─────────────────────────────────────────────────────────────────── testDefaults() testOpportunityAtClosableRange() testMargin() testFinisherAndDesperation() testPlanTrigger() testDamageRate() testAbort() testReasonNames() if failures > 0: echo "\n", failures, " check(s) FAILED" quit(1) echo "\nAll ram-decision checks passed."