6.5 KiB
Performance check — 2026-10-04
Future QA default (2026-10-04): Use Stormwind Stockade for new routine live tests. The Wailing captures and commands in this dated report are historical; follow the current test policy and root
AGENTS.md. A new Wailing reproduction needs an explicit owner request.
Findings and changes
- CPU: The production CPU profile spent about 925 ms of an eight-second sample inside
filesForContentPack, which repeatedly scanned and sorted the asset catalog.src/content/contentManifest.tsnow indexes each immutable file map once through a WeakMap.src/content/contentManager.tscaches usable/unavailable pack results until the next content publication; install, removal, validation, and catalog changes invalidate those results. - Idle CPU/rendering:
src/ui/usePresentationClock.tsstops HUD, aura, and periodic-effect clocks while locally paused, viewing the map/companion, or hidden. Visible shared-online timers still run behind menus.DungeonRaceHud.tsxno longer starts its two clocks during ordinary dungeon play. Aura and periodic strips use shallow target-filtered subscriptions, and aura timer effects no longer restart on every new filtered array. - Retained RAM:
src/game/combatAudio.tspreviously retained every decoded cue for the tab lifetime. Its least-recently-used cache now holds at most 64 entries or 32 MiB of decoded PCM. Active playback keeps its own buffer, failed requests can retry, and simultaneous requests share a decode. This limits cache retention, not total browser memory or all in-flight audio allocations. - GPU: Local pause and hidden-page handling already switch the scene to demand rendering and stop physics. The checked stable pause/background samples made zero WebGL draw calls. No rendering-resolution, texture-quality, lighting, or culling reductions were applied.
Measurements
Production builds were exercised in headless Edge on the AMD Radeon RX 9070 XT, at a 1280 × 800 viewport with a 1920 × 1200 drawing buffer. scripts/playtest-performance.cjs creates a temporary local account/database, enters Wailing Caverns, samples entry combat and movement, pauses/resumes, exercises synthetic page visibility, and repeats entry/exit three times. The historical label active-idle means no player input; companions may fight during that sample.
| Sample | Before | After | Confirmation |
|---|---|---|---|
| Entry main-thread busy time | 77.7% | 44.2% | 61.7% |
| Movement-following main-thread busy time | 79.2% | 69.0% | 55.6% |
| Stable 20-second pause busy time | 5.41% | 0.49% | 0.46% |
| Stable pause draw calls/second | 0 | 0 | 0 |
| Stable pause JavaScript heap | 192.57 MiB | 173.26 MiB | 173.06 MiB |
| Third menu return JavaScript heap | 59.88 MiB | 59.59 MiB | 59.71 MiB |
These are individual route samples, not a deterministic combat benchmark. NPC appearances, combat progression, loaded effects, and concurrent project work vary. The heap difference and active draw-call difference must not be attributed entirely to these fixes. The CPU profile confirms removal of the catalog-scan hotspot; the stable pause results reproduce.
A separate lookup benchmark used the same 21,585-file manifest and 2,181-file combat-media pack for both algorithms: 100 old scan/sort lookups took 746.06 ms; building the new index took 14.91 ms once; 100 cached lookups took 0.023 ms. This isolates the catalog work, not total game speed.
The after run's synthetic hidden sample used 0.43% main-thread busy time and zero draw calls; resume restored continuous rendering. JavaScript heap changed only 0.024 MiB over its stable paused 20-second window. Menu returns released most scene heap and backing storage across three cycles. No page errors occurred.
One third-cycle pause window still showed a transient workload (12.3% main-thread busy time and 226 draw calls/second) despite demand mode. The confirmation run adds a paused CPU profile and an additional 20-second settled sample: both its initial third-cycle pause and settled window had zero draws and no layouts. The settled window used 0.50% main-thread busy time; the paused profile was overwhelmingly idle. This transient was not reproduced or conclusively attributed, so pause is not certified draw-free during every asynchronous transition.
Validation and evidence
- 59 focused tests passed across content validation/persistence, audio, aura/periodic displays, scene lifecycle, and disposal. Audio regressions specifically cover byte/count eviction, concurrent reuse, failure retry, and active playback.
npm run buildpassed TypeScript, Vite, KTX2 and environment-performance audits, compression, and release-bundle verification. The environment audit reported zero regressions across 106 assets, with 60 existing budget exceptions.npm run release:checkpassed. An initial attempt raced another task writing a document; the subsequent check passed without changing unrelated work.- Visually inspected active and resumed gameplay and pause screenshots: character bodies, starter gear, enemies, effects, and HUD remained visible. This is not an equipment or character-rendering change.
- Raw measurements/screenshots/profiles:
artifacts/playtest/performance/before/,after/, andconfirmation/. Each run containsresults.json,renderer.json, active/pause screenshots, andactive.cpuprofile; confirmation also containspaused.cpuprofile. - Repeat with
node scripts/playtest-performance.cjs; setPERFORMANCE_LABELfor a separate evidence directory.PERFORMANCE_URLcan point the same UI route at a local preview, but production mode is necessary to exercise installed-content checks.
Limits and remaining targets
Main-thread busy percentage is one browser thread's occupied wall time, not whole-system CPU utilization. Forced-GC JavaScript heap and backing storage are not total process RAM, GPU memory, or VRAM. Draw calls measure submitted rendering work, not device GPU utilization. Hidden-page handling is a synthetic lifecycle test, not an OS background-throttling measurement.
This pass covers one desktop dungeon route, not every dungeon, mobile/Android, high-shadow/resolution settings, shared-online play, or a long combat soak. Original-effect graph and texture caches still retain distinct media for the tab lifetime; safe eviction needs a separate varied-spell/resource-ownership check. Remaining active profile cost is primarily rendering, transforms, and layout; larger scene and device-level GPU profiles should guide any further visual changes.