forked from phenom/healer-man
642 lines
33 KiB
Markdown
642 lines
33 KiB
Markdown
# 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/<job-id>/
|
|
```
|
|
|
|
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 <path>` for a stable
|
|
comparison location. `npm run balance:lab:regression -- <options>` 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/<run>
|
|
```
|
|
|
|
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 <json>`. 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/<timestamp>/` 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/<party-id>/` 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.
|