import argparse import json import sys import tempfile import unittest from pathlib import Path from scripts.balance_lab_brackets import ( add_growth_metrics, bracket_levels, build_balance_lab_command, evaluate_rows, summarize_report, ) class BalanceLabBracketTests(unittest.TestCase): def test_bracket_levels_match_the_gm_schedules(self) -> None: self.assertEqual(bracket_levels(10), [1, 10, 20, 30, 40, 50, 60, 70, 80]) self.assertEqual(bracket_levels(5)[:4], [1, 5, 10, 15]) self.assertEqual(bracket_levels(1), list(range(1, 81))) def test_command_uses_the_existing_live_balance_lab_controller(self) -> None: args = argparse.Namespace( source="wow", spec_selection="all", party_seed=7, party_configurations=10, simulations_per_party=3, duration=80, boss_category="wow", seeds="1,2,3", active_slot=None, spec=[], boss=["stormwind-stockade/entry-1717"], seat_sweep=True, cooldown_policy=None, companion_readiness_policy=Path("policy.json"), include_beta_specs=True, ready_only=True, ) command = build_balance_lab_command(args, 40, Path("output/level-40")) self.assertEqual(command[0], sys.executable) self.assertTrue(command[1].endswith("scripts\\balance_lab.py") or command[1].endswith("scripts/balance_lab.py")) self.assertIn("automatic", command) self.assertEqual(command[command.index("--level") + 1], "40") self.assertEqual(command[command.index("--seeds") + 1], "1,2,3") self.assertIn("--seat-sweep", command) self.assertIn("--include-beta-specs", command) self.assertIn("--ready-only", command) self.assertEqual( command[command.index("--companion-readiness-policy") + 1], str(Path("policy.json").resolve()), ) def test_summary_measures_healer_requirement_from_raw_runs(self) -> None: with tempfile.TemporaryDirectory() as temporary: root = Path(temporary) report_path = root / "suite-report.json" report = { "level": 20, "summary": {"partyConfigurations": 1, "bosses": 1}, "bosses": [{ "runs": 1, "bossMaxHealth": 1000, "defeatRate": 1, "averageTimeToDefeat": 10, }], "classSpecs": [{ "role": "healer", "totalHealing": 150, "totalAbsorbs": 25, }], } report_path.write_text(json.dumps(report), encoding="utf-8") raw_path = root / "parties" / "wow-party-001" / "raw-runs.json" raw_path.parent.mkdir(parents=True) raw_path.write_text(json.dumps({"runs": [{ "elapsedSeconds": 10, "partyDamage": 1200, "bossDamage": 1000, "incomingDamage": 200, "rawIncomingDamage": 250, "outcome": "defeated", "pressureWindows": 2, "healerThroughputWindows": 2, "healerResponseWindows": 2, "members": [ {"role": "healer", "healing": 150, "absorbs": 25, "minimumHealthPercent": 0.6}, {"role": "tank", "healing": 0, "absorbs": 0, "minimumHealthPercent": 0.4}, ], }]}), encoding="utf-8") summary = summarize_report(report_path) self.assertEqual(summary["healthToDpsSeconds"], 10) self.assertEqual(summary["incomingDtps"], 20) self.assertEqual(summary["healerHps"], 15) self.assertEqual(summary["healingCoverageRate"], 0.875) self.assertEqual(summary["incomingRunRate"], 1) self.assertEqual(summary["healingRequiredRunRate"], 1) self.assertEqual(summary["healerResponseRate"], 1) self.assertEqual(summary["averageMinimumHealthPercent"], 0.4) def test_growth_check_flags_health_that_outpaces_damage(self) -> None: rows = [ {"level": 10, "bossHealth": 1000, "bossDps": 100}, {"level": 20, "bossHealth": 2500, "bossDps": 150}, ] for row in rows: row.update({ "healthToDpsSeconds": row["bossHealth"] / row["bossDps"], "incomingDtps": 10, "healerHps": 8, "incomingRunRate": 1, "healingRequiredRunRate": 1, }) add_growth_metrics(rows) args = argparse.Namespace( min_health_to_dps_seconds=5, max_health_to_dps_seconds=70, max_durability_growth=1.4, min_incoming_run_rate=0.95, min_healing_required_run_rate=0.8, min_healer_response_rate=0.6, ) self.assertFalse(evaluate_rows(rows, args)) self.assertIn("boss health outgrew party DPS", rows[1]["flags"]) if __name__ == "__main__": unittest.main()