# Boss and party balance lab `scripts/balance_lab.py` runs a chosen World of Warcraft, Conquest of Azeroth, or Runes of Magic five-player party against the authored boss catalog. It is an interactive Python controller and report generator, not a second combat model. The actual fights execute through `scripts/balance-lab/runtime.ts`, which imports the game's live TypeScript combat store, party AI, abilities, talents, gear, threat, mitigation, healing, boss scaling, and enemy attacks. This split is intentional. Balance formulas stay in the game runtime. Python owns selection, repeatable run configuration, aggregation, CSV/Markdown/HTML output, and outlier flags. Do not copy combat coefficients into the Python script. ## Quick start Install the normal project dependencies, then start the guided prompt: ```powershell npm run balance:lab ``` The prompt selects: 1. one random party, five manual members, or an automatic multi-party sweep; 2. a WoW, CoA, or RoM party catalog; automatic mode only uses entries marked Ready (plus Beta when explicitly enabled); 3. a level from 1 through 80; 4. random Ready parties, guaranteed coverage of all Ready class/spec builds, or a searchable targeted selection that may include Needs Testing builds; 5. how many unique 1-tank/1-healer/3-damage configurations to generate per source when random selection is used; 6. all bosses, WoW bosses, RoM bosses, or a searchable multi-boss selection; 7. how many deterministic simulations to run for every party/boss pairing; and 8. the maximum duration of each fight. Automatic class/spec selection has three strategies: - **Random parties** preserves the original seeded behavior and generates up to the requested number of unique parties per source. If a Ready/Beta pool can form fewer unique 1/1/3 rosters, Balance Lab runs each available roster once and reports the cap instead of failing or duplicating identical data. - **All Ready specs** creates the minimum deterministic set of valid parties needed to include every Ready build for the selected source at least once. At present this is the five certified WoW specs; CoA and RoM have no Ready entries and therefore do not participate in automatic runs. - **Specific specs** treats each selected build as required coverage and fills the remaining tank, healer, and damage slots with distinct compatible builds. When alternatives exist, required builds are not reused as fillers. Coverage parties always contain one tank, one healer, and three different damage builds. Their composition is deterministic for the party seed, and no build is duplicated inside one party. The active member is driven by the shared benchmark policy while the other four use the live companion AI. The healer is the default active member. Use `--active-slot` to change it. Use `--seat-sweep` to run the same composition five times with each seat active exactly once. Seat sweeps produce dedicated `npc_class_specs_across_parties.csv` and `npc_class_abilities_across_parties.csv` artifacts, so companion comparisons exclude the active benchmark controller. Classic specializations expose two named, non-destructive talent profiles: the default PvE build translated from the pinned AzerothCore Playerbots configuration at commit `531282e`, and the prior Healer Man executable-first build. Each profile has a stable generated character name and unique build ID. Adding another profile therefore does not overwrite either existing build. Random parties and unselected filler seats use the declared default profile; named alternatives remain available to specific selection and full coverage. Run `npm run talents:generate` after intentionally changing the Playerbots mapping or source; the checked-in snapshot keeps production independent from the historical checkout. Runtime selection mirrors PlayerbotFactory: it starts with the closest configured link at or below the tested level (or the first future link at low levels), then consumes later links if talent points remain; level 80 starts from the level-80 link. Profile metadata records the applicable source point count, ranks retained after mapping to the imported Healer Man talent catalog, and legal fallback points at the tested level. This makes patched-tree mismatch visible instead of presenting the translation as a literal Blizzard build. ## GM app and server execution A server-authorized GM sees **Balance Lab** directly underneath **GM Tools** on the main menu. That screen exposes the automatic sweep controls: party source, a single level or an all-brackets schedule, class/spec selection strategy, a searchable spec picker, simulations per party/boss, duration, random seed, boss category, and a searchable multi-boss picker. The bracket schedules cover level 1 plus every 10 levels, level 1 plus every 5 levels, or every level from 1 through 80. Random mode exposes the requested parties per source; coverage modes show the derived minimum party count before submission. The optional **NPC seat audit** multiplies each generated composition across all five active-seat assignments and makes in-app class/ability analysis prefer the NPC-only report tables. **Run on server** sends one allow-listed configuration to the authenticated GM API. **Run all level brackets** submits the complete schedule atomically and creates one independently persisted job and report per level. The bounded worker pool runs several jobs at the same time, launches the same Python controller and live TypeScript runtime used by the command line, and writes artifacts under: ```text DATA_DIR/balance-lab/jobs// ``` The browser polls lightweight status records; it does not execute combat ticks. Closing the browser does not stop a running server job. Completed jobs and their metadata remain in **Run history** across server restarts, with the newest run selected automatically. The history shows the most recent 100 jobs and can be searched or filtered by status. A job that was still queued or running when the server stopped is restored as failed (it cannot safely resume a half-finished deterministic sweep) and can be rerun. Each job directory contains a private `job.json` status record alongside its report artifacts. On startup, `server/balance-lab-jobs.mjs` rebuilds history from those records and treats a directory containing `suite-report.json` as complete. Completed server-job directories from the pre-history implementation are imported from `suite-report.json` plus `suite-config.json`, then receive a new `job.json`; their requester is shown as `unknown (legacy run)` because that was not previously recorded. Do not add `job.json` to the artifact allow-list: it is server state, not a GM download. The standalone `npm run balance:lab` path remains the way to spend the local development device's CPU. ## In-app analysis and previous runs Select any completed entry in **Run history**, then choose **View graphs and filterable data**. The app reads that run's `suite-report.json` through the authenticated artifact endpoint and caches it for the current screen session. The dashboard includes: - boss damage to the whole party (incoming DTPS); - party damage applied directly to each boss; - average successful kill time; - boss DPS and damage taken for every class/specialization; - effective healing done and effective healing received per second for every class/specialization; - complete boss tables with pressure versus median, total incoming damage, defeat/wipe rates, deaths, and health; - complete class/spec tables with total and boss DPS, healing done/received per second, DTPS, absolute damage taken, healing done/received and absorb totals, deaths, source/role-median tuning flags, and a direct ability-damage drill-down for every build; and - a complete Abilities tab with learned/available abilities (including zero-use rows), exact runtime IDs, AI categories, active-run coverage, casts, hits, casts per minute, effective/raw damage, DPS, damage share, damage per hit, healing, overheal, absorbs, and cooldown; and - complete party-composition tables with output, incoming pressure, outcomes, and deaths. The overview graphs intentionally show at most the 15 highest visible values so labels remain readable. They never discard report data: the Bosses, Class/spec, and Parties tabs retain every matching row. Search, party-source, boss-source, role, metric, and sort-direction controls filter the display without modifying the saved report. **View ability damage** on a class/spec row opens the Abilities tab with that exact build selected, damaging abilities ranked by effective damage, and its casts, hits, DPS, damage share, and damage-per-hit details visible in the table. **Show all specs** clears that drill-down. Server requirements: - the normal checkout and development dependencies must be present because the combat bridge uses `vite-node`; - Python 3 must be installed, or `BALANCE_LAB_PYTHON` must name its executable; and - the signed-in account must have the server-issued `gm` role. The GM screen calls `/api/gm/balance-lab/runtime` when it opens and shows the resolved Python version. If Python 3 is unavailable, **Run on server** is disabled and no job is queued. On the canonical TrueNAS deployment, the service command installs `python3`, prints `python3 --version` during startup, and sets `BALANCE_LAB_PYTHON=python3`; use the exact YAML in `DEPLOYMENT.md` rather than the older Node-only command. `BALANCE_LAB_MAX_CONCURRENT` defaults to four and is capped at 16. `BALANCE_LAB_MAX_QUEUED` defaults to 100 and is capped at 200, which leaves room for an 80-job every-level sweep. Raising concurrency permits more parallel server processes but can make both the game server and simulations less responsive. A batch contains at most 80 jobs and is validated against the remaining queue capacity before any of its jobs are created. Subprocess arguments are passed as an array without a shell, artifact names are allow-listed, and every job and artifact endpoint authenticates the account's server-issued GM role. Each Python worker starts the TypeScript bridge with `vite-node --mode balance-lab`. That dedicated mode disables Vite's public directory and ignores all filesystem-watch paths because a headless, one-shot simulation does not need hot reload. Keep this isolation in place when changing `vite.config.ts`: parallel workers must not consume one watcher per shipped asset. If an older deployment exhausts the host watcher limit, the job now reports that specific startup failure instead of only the subprocess exit code; deploy the no-watch runtime fix rather than increasing the host's watcher allowance. ## Local graphical runs On Windows, double-click `scripts/Launch Balance Lab GUI.cmd`. The launcher starts the local Balance Lab service, opens the existing Balance Lab screen in the default browser, and keeps the worker service alive in its command window. Leave that window open until queued simulations finish. The equivalent terminal command is: ```powershell npm run balance:gui ``` This development-only mode binds Vite to `127.0.0.1`, accepts API requests only from the loopback interface, and does not require an online account or GM login. It uses the same Python controller, TypeScript combat runtime, configuration form, progress UI, report dashboard, and artifact downloads as the server GM tool. Job history and reports persist beneath `artifacts/balance-lab-gui-runtime`; closing the browser does not stop work, but closing the launcher does. Production and ordinary development modes do not expose these unauthenticated local routes. Use **All brackets** in the GUI for every-10-level, every-5-level, or every-level runs. Use **Run locally** for one level. The runtime status at the top confirms that Python is available before a run is submitted. ## Repeatable command-line runs Run the same every-10-level schedule used by the GM screen entirely on the local development machine: ```powershell npm run balance:lab:brackets ``` The bracket runner launches the existing Python controller and live TypeScript runtime; it does not contain a second combat implementation. By default it tests every WoW specialization at levels 1, 10, 20, and so on through 80, uses three seeds, allows 80 seconds per fight, and runs up to four level processes concurrently. Normal Balance Lab options can narrow the run: ```powershell npm run balance:lab:brackets -- ` --levels 40,50,60,70,80 ` --simulations-per-party 5 ` --boss "stormwind-stockade/entry-1717" ` --boss "stormwind-stockade/entry-1663" ``` Use `--step 5` or `--step 1` for the other GM schedules, `--jobs 1` when the machine should remain responsive, and `--output-dir ` for a stable comparison location. `npm run balance:lab:regression -- ` runs the same suite but returns a failing exit code when configured pressure or durability limits are exceeded. The default checks flag boss durability below 20 or above 70 boss-damage seconds, bracket-to-bracket boss-health growth more than 1.5 times party boss-DPS growth once the preceding bracket reaches that minimum window, missing incoming damage, and local raw runs where healers are not required often enough or answer fewer than 60% of five-second pressure windows. The bracket root contains `bracket-summary.json`, `.csv`, and `.md`. These show boss health beside direct boss DPS, incoming DTPS, effective healer HPS, mitigation, healing coverage, five-second pressure windows answered by healer throughput, outcomes, and growth relative to the previous tested level. `heirloom-summary.json` and `.csv` record the resolved item level, slot count, attributes, ratings, armor, and weapon DPS for every tested build. Existing reports can be re-analyzed without rerunning combat: ```powershell python scripts/balance_lab_brackets.py --analyze-existing artifacts/balance-lab-brackets/ ``` Balance Lab builds use the `virtual-heirloom` preset. Empty persisted inventory and equipment assignments mean that no earned drops override the virtual set; they do not mean that the character is ungeared. Virtual heirlooms fill every compatible slot with specialization-specific synthetic stats, scale through level 79, and remain at item level 79 for every party member at level 80. They are not literal Blizzard heirloom item templates: their custom stat budget is normalized slightly above the best compatible same-level item in the authored WotLK-derived loot catalog. The saved catalog and heirloom summary make that resolved loadout explicit for each local run. Damage and healer heirloom profiles include both critical-strike rating and haste rating. The WotLK stat resolver converts those ratings at the simulated character level into the appropriate melee, ranged, or spell percentages. Protection tank heirlooms intentionally spend that budget on defense, avoidance, block, stamina, and expertise instead. Run selected brackets as a companion-only seat audit: ```powershell npm run balance:lab:npc-audit -- --levels 20,40,60,80 --simulations-per-party 1 ``` NPC raw rows include the named talent build and character, selected rotation profile/rule/reason counts, profile priority for every available ability, missing profile actions, GCD uptime, and sampled resource starvation/capping. Use these diagnostics before changing class coefficients. ## Companion cooldown policy GM Tools includes a revisioned **Companion cooldown policy** editor. Generic rules control normal defensive, emergency defensive, healer mitigation, and major offensive cooldown thresholds. Mechanic rules bind an authored boss attack to tank-only or party-wide pre-emptive mitigation with configurable lead time and minimum defensive cooldown length. Clients load the published policy from `/api/companion-cooldown-policy`; GM reads/writes use `/api/gm/companion-cooldown-policy`. Server Balance Lab jobs snapshot the published policy before launch. Local runs use built-in defaults unless `--cooldown-policy` points to a JSON policy file. ## Companion specialization readiness The readiness catalog shows every WoW and CoA specialization plus each RoM class/role profile as Ready, Beta, or Needs Testing. The revisioned GM policy currently publishes the 31 WoW specialization statuses; all CoA and RoM entries are fixed at Needs Testing until their certification work begins. Shipped defaults certify only Protection Warrior/Aldren, Discipline Priest/Quorin, Marksmanship Hunter/Liora, Fire Mage/Xavian, and Assassination Rogue/Orin. The main menu, Dungeon setup, and Balance Lab link to the full Companion Readiness catalog. Players can inspect it; authenticated GMs can edit and atomically publish the complete policy. Published policies must retain at least one Ready tank, one Ready healer, and three distinct Ready damage specializations. Random and all-eligible runs use Ready specs by default across every source. Because no CoA or RoM entry is currently Ready, those catalogs contribute no automatic parties. **Include Beta Specs** adds Beta specs and is saved per device; explicit targeted diagnostics may still select a Needs Testing spec, but `--ready-only` rejects those targets for certification and regression runs. Filler seats always come from the eligible Ready/Beta pool. Server jobs snapshot the published policy and beta choice. Local GUI runs use the revisioned loopback policy saved under `artifacts/balance-lab-gui-runtime`; corrupt or missing policy files fall back to shipped defaults. CLI parity is available through `--ready-only`, `--include-beta-specs`, and `--companion-readiness-policy `. The JSON may be the published envelope or its policy config. The standard bracket, regression, and NPC-audit npm commands pass `--ready-only`. For focused code validation use `npm run test:ready-five`; do not use a full-repository or all-class balance sweep unless the project owner explicitly expands the certification scope. List exact party build IDs at a level: ```powershell python scripts/balance_lab.py --source wow --level 60 --list-members ``` List boss keys: ```powershell python scripts/balance_lab.py --source wow --level 60 --list-bosses ``` Run a random party against every authored boss: ```powershell python scripts/balance_lab.py --source coa --level 60 --party-mode random --party-seed 42 --seeds 1,2,3 --boss-category all ``` Generate 25 unique WoW parties and run five simulations per party against all bosses: ```powershell python scripts/balance_lab.py ` --source wow ` --level 60 ` --party-mode automatic ` --party-configurations 25 ` --simulations-per-party 5 ` --boss-category all ``` Run every currently Ready source against only RoM bosses. Until a CoA or RoM build is promoted, `--source all` still produces only the five Ready WoW specs: ```powershell python scripts/balance_lab.py ` --source all ` --level 60 ` --party-mode automatic ` --party-configurations 10 ` --simulations-per-party 3 ` --boss-category rom ``` Automatically test a selected group of bosses: ```powershell python scripts/balance_lab.py ` --source coa ` --party-mode automatic ` --party-configurations 20 ` --simulations-per-party 5 ` --boss "gundrak/entry-29932" ` --boss "utgarde-keep/entry-23954" ``` Run an exact party against Eck only: ```powershell python scripts/balance_lab.py ` --source wow ` --level 40 ` --party-mode manual ` --member "warrior:Protection" ` --member "priest:Holy" ` --member "mage:Fire" ` --member "rogue:Combat" ` --member "hunter:Marksmanship" ` --active-slot 2 ` --boss "gundrak/entry-29932" ` --seeds 1,2,3 ` --duration 60 ``` Manual selectors accept an exact generated build ID or a unique `class-id:specialization`/`class-name:specialization` value. Repeat `--boss` to run any selected subset. Omit it to run every boss in the selected boss category. `--seeds 4,8,15` is an advanced exact-seed override for `--simulations-per-party`. ## Output Each run creates `artifacts/balance-lab//` unless `--output-dir` is provided. | File | Contents | | --- | --- | | `report.html` | Searchable and sortable tables containing every selected member and every boss. | | `report.md` | Human-readable complete party and boss tables. | | `report.json` | Aggregated machine-readable report. | | `raw-runs.json` | Lossless per-seed, per-boss, per-member, and per-ability runtime results. | | `bosses.csv` | One aggregate row for every boss, ordered by party damage taken per second. | | `party_members.csv` | One aggregate row for every selected party member. | | `member_by_boss.csv` | Every party member against every boss. | | `member_abilities.csv` | Every observed player/companion ability for every run. | | `boss_abilities.csv` | Every observed boss ability for every run. | | `runs.csv` | One row per boss and deterministic seed. | | `config.json` | Exact rerunnable selection. | | `catalog.json` | Party and boss choices shown to the controller. | No report is truncated to a top-N list. Automatic mode creates a complete report directory for every generated party under `parties//` and adds these suite-level files: | File | Contents | | --- | --- | | `suite-report.html` | Searchable/sortable tables for every tested class/spec, party configuration, and boss. | | `suite-report.md` / `suite-report.json` | Complete human- and machine-readable suite summaries. | | `party_configurations.csv` | Every generated party, its full composition, party DPS, DTPS, deaths, and outcomes. | | `class_specs_across_parties.csv` | Every class/spec observed across every configuration, compared with its source/role median. | | `class_abilities_across_parties.csv` | Every learned/available and observed class/spec ability, including unused rows, usage frequency, damage contribution, healing, and absorbs. | | `npc_class_specs_across_parties.csv` | NPC-only class/spec results, excluding each run's active benchmark-controlled seat. | | `npc_class_abilities_across_parties.csv` | NPC-only ability, rotation-priority, decision-count, and missing-profile-action results. | | `class_spec_by_boss.csv` | Every observed class/spec broken down against every boss it fought. | | `bosses_across_parties.csv` | Every boss aggregated across all generated parties. | | `boss_by_party.csv` | Every boss and party-configuration pairing. | | `suite_runs.csv` | Every individual seed run with its party configuration ID. | | `suite-config.json` | The exact automatic-generation seed, counts, filters, and generated party build IDs. | ## Metrics and interpretation - Boss `incomingDtps` is effective damage received by all five members divided by active fight time. Damage from summoned hostile actors is included. - `averageIncomingDamage` is total healer workload and therefore includes fight length. Read it beside DTPS, wipe rate, deaths, and time to defeat. - Member `dps` includes damage to the boss and adds. `bossDps` includes only direct boss damage. Suite class/spec `dtps` is damage taken per active second; `totalDamageTaken` is the absolute healer/survivability burden across every included run. - Member `hps` is effective healing done per active second and excludes overhealing. `hpsReceived` is effective healing received per active second, attributed from each healing event's target (including self-heals). `totalHealing` and `totalHealingReceived` are the corresponding absolute values across the included runs. Reports created before target attribution was added remain readable but require a rerun to populate healing received. - Member and ability `absorbs` count shield capacity granted by both direct shield effects and named absorb auras such as Power Word: Shield. Member `deathEvents` counts each alive-to-dead transition, so a member that is resurrected and defeated again contributes another death. - Ability `activeRuns` counts simulations with at least one cast, completed non-throughput release, hit, damage, healing, overheal, or absorb event. Completed resource, aura, pet, and summon actions are recorded as casts even though they create no immediate throughput event. `usageRate` divides that by every run for that build; `damageShare` divides the ability's effective damage by the class/spec's total effective damage. `available ยท unused` means the ability was learned and executable at the tested level/talent setup but emitted no combat event. This is a review signal, not proof of an AI defect: resurrection, execute, interrupt, dispel, emergency, and long-cooldown abilities can be legitimately unused when their condition never occurs. - Companion AI currently applies several instant attacks directly and therefore may emit hits/damage without a separate cast event. Use **Active runs** and **Hits** as the authoritative usage indicators; use **Casts** and **Casts/min** as additional detail, especially for the active-player benchmark controller and non-throughput actions. - Boss pressure flags in a party report compare DTPS with the non-zero median boss for that exact party. Suite boss flags compare across all generated parties. Suite class/spec flags compare DPS with every observed spec from the same source and role. `high` begins at 1.25 times the median and `very high` at 1.5; `low` begins at 0.75 and `very low` at 0.5. - Flags are triage signals, not automatic tuning decisions. A boss can have high DTPS but low total damage because it dies quickly. A member can have low boss DPS because it spends time healing, tanking, or damaging adds. The isolated boss room deliberately removes campaign-final health promotion so all bosses use the same testing-room durability rule at the chosen level. Runtime IDs include the simulation seed, which deterministically varies the game's stable target and cooldown rolls. Shared curves remain the baseline, but confirmed encounter outliers belong in `src/game/bossBalanceOverrides.ts`. Each source-controlled entry identifies one dungeon boss and may apply at one exact level, across a bounded level bracket, or at every level. Health and damage multipliers are independent and compose; isolated `boss-testing:` rooms resolve back to their source dungeon so the same exception is exercised by Balance Lab and live dungeon runs. Record the audit evidence in the entry reason and prefer this layer over another global curve change when the problem is confined to particular encounters. ## Coverage and limitations The selectable roster includes every authored WoW and CoA specialization plus every supported RoM class-role pair, but automatic certification is Ready-only. Needs Testing entries remain selectable solely for an intentional targeted diagnostic. Existing generated party talent plans are used when available. Missing WoW plans are legally allocated through their matching live talent tree; CoA builds allocate the chosen specialization tree and class tree through the live essence/prerequisite rules. Headless simulation executes combat-store boss attacks and their authored damage, healing, control, aura, summon, threat, and timing effects. It does not mount the Three.js scene, play visuals or sounds, or run scene-owned world objects. Encounter rules that require movement through the original dungeon, clicking an object, or a renderer-owned trigger require a live Boss Testing playtest in addition to this numeric audit. One selected member must occupy the active player slot because the production runtime distinguishes the player from four companions. The report marks that member. When a suspected class outlier could be policy-sensitive, rerun with a different `--active-slot`; do not compare active and companion results without noting that control-policy difference. Companion builds initialize every resource pool required by their learned abilities, not only the class's primary pool. Secondary Energy and Focus regenerate, and any companion with a Rage pool gains Rage from landed weapon attacks; this is what makes druid Cat and Bear rotations executable while Mana remains the class's primary resource. A completed companion cast resolves the member's currently learned rank before applying its effects, so cast-time spell damage and healing cannot fall back to rank one. `playerbots-inspired-v6-reactive-ready-five` keeps level-10-and-up non-healer builds on their specialization's authored damage priority whenever that build knows a profile damage action. Reactive interrupts, taunts, control, and defenses still take precedence. Bear and Cat actions are isolated to their matching Feral specs; Overpower and Victory Rush require an explicitly modeled trigger, while Protection companions derive Revenge's five-second trigger from incoming dodge, parry, and block combat events; Drain Soul, Hammer of Wrath, and Kill Shot obey their target-health gates. Off-profile control, interrupt, and debuff buttons cannot consume ordinary rotation GCDs after their reactive conditions have been checked. Incidental DBC aura categories cannot outrank explicit profile actions. Rage and Runic Power generated during an action persist without erasing that action's cooldown/GCD; short-range melee abilities use companion combat reach; and pets move every combat tick while retaining their own attack cadence. Untargeted cooldown resets exclude the source ability, Discipline mitigation/absorbs precede direct triage and moderate injuries receive a pending-HoT-aware periodic-heal pass, and Assassination banks Energy for Mutilate while using Envenom as its primary four-plus-point finisher, matching the pinned Playerbots strategy. Rogue weapon hits now execute the maintained Instant/Deadly coatings: Deadly Poison stacks a 12-second DoT to five source-specific doses, Envenom uses one dose per combo point for Nature damage and grants its temporary poison-frequency buff, Mutilate offers two poison application opportunities, and Shiv guarantees the off-hand Deadly application at four-plus combo points only while the pending Envenom lacks enough doses. Envenom normally consumes the used doses; Orin's pinned 3/3 Master Poisoner build preserves them 100% of the time as specified by the talent. Ready DPS pack logic is deliberately thresholded and cluster-aware: Marksmanship uses Multi-Shot at two nearby engaged targets and Volley at three, while Fire uses Flamestrike and Assassination uses Fan of Knives at three. Volley's six Arcane pulses are recovered from its DBC trigger child, Fan of Knives is centered on the Rogue, active ground effects are not replaced early, and companion AoE does not pull unengaged enemies. ## Code map - `scripts/balance_lab.py`: prompts, command-line parsing, random/manual/automatic party selection, searchable boss selection, process orchestration, aggregates, flags, and all party/suite report formats. - `scripts/balance-lab/runtime.ts`: live headless fight setup, controlled clock, exact party construction, boss execution, and lossless event attribution. - `src/ui/BalanceLabScreen.tsx`: GM form, searchable boss selection, persistent run-history selection, progress display, report loading, and server or local artifact opening/downloading. - `src/ui/BalanceLabResults.tsx`: in-app graphs, report filters, sorting, summary cards, and complete boss/class-spec/party tables. Add new dashboard metrics here only after they exist in `suite-report.json`. - `src/app/balanceLabClient.ts`: typed Balance Lab API client with authenticated server and loopback-local execution targets. - `server/balance-lab-jobs.mjs`: validated single and batch job specifications, bounded parallel process queue, progress parsing, `job.json` persistence/startup restoration, report-summary extraction, and artifact allow-list. - `server/local-balance-lab-api.mjs`: loopback-only, no-login development API that owns the local job service and persists GUI runs under `artifacts/`. - `server/server.mjs`: GM-authenticated runtime-preflight, job, status, and artifact HTTP routes. - `src/game/benchmark/benchmarkBuilds.ts`: selectable spec builds and legal talent allocation. The older canonical cohort remains unchanged. - `src/game/partyStore.ts`: `createConfiguredPartyMember`, the narrow factory that preserves production stats, equipment, all learned-ability resource pools, and appearance for an exact companion. - `src/game/bossTesting.ts`: the authoritative all-boss catalog and isolated room construction. - `src/game/bossBalanceOverrides.ts`: audited boss-specific health and damage exceptions, optionally scoped to an exact level or level bracket. When extending the tool, add fields to `raw-runs.json` first, aggregate them in Python second, and document the metric here. Keep formula ownership in `src/game` and keep reports complete rather than introducing top-N filtering. ## Validation For tool changes, run at minimum: ```powershell python -m py_compile scripts/balance_lab.py python -m py_compile scripts/balance_lab_brackets.py python -m unittest scripts.test_balance_lab scripts.test_balance_lab_brackets node --test server/local-balance-lab-api.test.mjs server/balance-lab-jobs.test.mjs server/server.test.mjs python scripts/balance_lab.py --source wow --level 20 --party-mode automatic --party-configurations 2 --boss "gundrak/entry-29932" --simulations-per-party 1 --duration 5 --output-dir artifacts/balance-lab-smoke npm run test:ready-five npm run build npm run balance:lab:playtest npm run release:check ``` The Playwright smoke test uses an isolated temporary database, logs in as a GM, checks that Balance Lab is below GM Tools, runs one real Eck server simulation, verifies all eleven suite artifacts, exercises the overview and ability graphs plus every complete table, checks history, role, class/spec, and unused-ability filtering, and captures desktop/mobile screenshots under `playtest-artifacts/balance-lab/`. The server unit tests separately restart the job service and prove that completed history is restored while interrupted work is marked failed. Use a longer all-boss run before making production tuning changes.