Release Healer Man 0.1.46
This commit is contained in:
1 parent
f82daf1c13
commit
9ff574ac0b
73 files changed
+33486
-174
No files matched your search
@@ -0,0 +1,346 @@
|
||||
# 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 can select all three;
|
||||
3. a level from 1 through 80;
|
||||
4. how many unique 1-tank/1-healer/3-damage configurations to generate per source;
|
||||
5. all bosses, WoW bosses, RoM bosses, or a searchable multi-boss selection;
|
||||
6. how many deterministic simulations to run for every party/boss pairing; and
|
||||
7. the maximum duration of each fight.
|
||||
|
||||
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.
|
||||
|
||||
## 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,
|
||||
level, unique parties per source, simulations per party/boss, duration, random
|
||||
seed, boss category, and a searchable multi-boss picker.
|
||||
|
||||
**Run on server** sends only that allow-listed configuration to the authenticated
|
||||
GM API. The server places it in a one-at-a-time queue, 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;
|
||||
- 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, HPS, DTPS, absolute
|
||||
damage taken, damage/healing/absorb totals, deaths, and source/role-median
|
||||
tuning flags; 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.
|
||||
|
||||
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.
|
||||
|
||||
`BALANCE_LAB_MAX_CONCURRENT` defaults to `1` and is capped at four.
|
||||
`BALANCE_LAB_MAX_QUEUED` defaults to eight and is capped at 50. Raising
|
||||
concurrency permits parallel server processes but can make both the game server
|
||||
and simulations less responsive. Requests are capped before launch, 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.
|
||||
|
||||
## Repeatable command-line runs
|
||||
|
||||
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
|
||||
```
|
||||
|
||||
Generate ten parties for each of WoW, CoA, and RoM, but test only RoM bosses:
|
||||
|
||||
```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. |
|
||||
| `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.
|
||||
- Ability `activeRuns` counts simulations with at least one cast, hit, damage,
|
||||
healing, overheal, or absorb 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.
|
||||
- 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.
|
||||
|
||||
## Coverage and limitations
|
||||
|
||||
The selectable roster includes every authored WoW and CoA specialization plus
|
||||
every supported RoM class-role pair. 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.
|
||||
|
||||
## 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 authenticated
|
||||
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 GM Balance Lab API client.
|
||||
- `server/balance-lab-jobs.mjs`: validated job specifications, bounded
|
||||
one-at-a-time process queue, progress parsing, `job.json` persistence/startup
|
||||
restoration, report-summary extraction, and artifact allow-list.
|
||||
- `server/server.mjs`: GM-authenticated job/status/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, resources, and appearance for an
|
||||
exact companion.
|
||||
- `src/game/bossTesting.ts`: the authoritative all-boss catalog and isolated
|
||||
room construction.
|
||||
|
||||
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 unittest scripts.test_balance_lab
|
||||
node --test 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 test -- --run src/game/benchmark src/game/partyStore.test.ts src/game/partyRuntime.test.ts
|
||||
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.
|
||||
@@ -78,6 +78,47 @@ searches:
|
||||
npm run dungeon:boss-effects:audit
|
||||
```
|
||||
|
||||
For reports that one boss is unusually hard to heal, regenerate the normalized
|
||||
party-pressure ranking instead of comparing native dungeon levels or relying on
|
||||
memory:
|
||||
|
||||
```powershell
|
||||
npm run dungeon:boss-pressure:audit
|
||||
```
|
||||
|
||||
This writes `dungeon-pipeline/WOW_BOSS_PRESSURE_REPORT.md` for a quick top-30
|
||||
view and `dungeon-pipeline/WOW_BOSS_PRESSURE_REPORT.json` for the complete
|
||||
per-boss, per-attack breakdown. The default comparison holds every boss at
|
||||
player level 80 for 60 seconds with five party members inside party-wide
|
||||
effects. It intentionally reports an unmitigated direct-damage ceiling, so use
|
||||
it to find bad cooldowns, duplicate spells, wrong targets, or accidental damage
|
||||
effects. It does not claim exact combat-log DPS: armor, resistance, movement,
|
||||
interrupts, summons, debuff amplification, and phase uptime are excluded.
|
||||
|
||||
When a boss is an outlier, inspect its source script before tuning numbers. In
|
||||
particular, distinguish setup/trigger spells and one-shot enrage auras from
|
||||
repeatable damaging attacks, and preserve the source `initialCooldownMs`,
|
||||
`repeatCooldownMs`, `maximumCasts`, and target selection. Re-run the audit after
|
||||
the fix and record both the boss rank and its per-attack rows.
|
||||
|
||||
### Imported animated boss is invisible
|
||||
|
||||
If the encounter marker, health bar, or effects render but the creature body
|
||||
does not, inspect the shipped GLB before replacing the model:
|
||||
|
||||
```powershell
|
||||
.\node_modules\.bin\gltf-transform.cmd inspect public/assets/game/dungeons/<dungeon>/creatures/<model>.glb
|
||||
```
|
||||
|
||||
Compare the scene bounds with the entity's `visual.model.groundOffset`,
|
||||
`labelHeight`, and `markerRadius`. Skinned bind-pose bounds can include vertices
|
||||
far below the animated root. The packaging formula normally uses `-bounds.minY`
|
||||
as the floor offset; for those assets that can lift the posed creature entirely
|
||||
out of the gameplay camera. Add the narrowest source-reviewed runtime metadata
|
||||
override in the dungeon content adapter, keep the original model URL, and add a
|
||||
regression assertion for the corrected offset. Prove the result with a rendered
|
||||
Boss Testing screenshot—the GLB existing on disk is not visual evidence.
|
||||
|
||||
The compiler reads effect, aura, target, duration, radius, and summon-creature
|
||||
fields from `Spell.dbc`, with `SpellDuration.dbc` and `SpellRadius.dbc` from the
|
||||
sibling support DBC directory. `spellSemantic()` maps source-backed controls,
|
||||
|
||||
Reference in new issue
Block a user