Files
healer-man/docs/PERFORMANCE_CHECK_2026-10-04.md
2026-10-06 22:25:34 -04:00

47 lines
6.6 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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.