47 lines
6.6 KiB
Markdown
47 lines
6.6 KiB
Markdown
# Performance check — 2026-10-04
|
||
|
||
> **Current QA policy (2026-10-05):** Routine live tests use Stormwind Stockade: Cellblock Rush (`bite-sized-stormwind-stockade`); record the boss selected from the run ID. Full-source Stockade campaigns require explicit route selection. The Wailing captures and commands in this dated report remain historical; follow [the current test policy](TEST_DUNGEON_POLICY.md) 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.ts` now indexes each immutable file map once through a WeakMap. `src/content/contentManager.ts` caches usable/unavailable pack results until the next content publication; install, removal, validation, and catalog changes invalidate those results.
|
||
- **Idle CPU/rendering:** `src/ui/usePresentationClock.ts` stops HUD, aura, and periodic-effect clocks while locally paused, viewing the map/companion, or hidden. Visible shared-online timers still run behind menus. `DungeonRaceHud.tsx` no 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.ts` previously 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 build` passed 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:check` passed. 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/`, and `confirmation/`. Each run contains `results.json`, `renderer.json`, active/pause screenshots, and `active.cpuprofile`; confirmation also contains `paused.cpuprofile`.
|
||
- Repeat with `node scripts/playtest-performance.cjs`; set `PERFORMANCE_LABEL` for a separate evidence directory. `PERFORMANCE_URL` can 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.
|