perf(render): bound animation and alpha scratch residency

Complete Slice D3 by replacing the unbounded animation dictionary with a concurrent byte/count LRU and by putting the three retail alpha scratch owners behind one typed aggregate budget. Preserve immediate growth and draw order while reclaiming one-frame density spikes after sustained under-use. Close stale bounds-cache issue evidence without inventing a cache.
This commit is contained in:
Erik 2026-07-24 16:34:28 +02:00
parent 3e18fc2730
commit f2644d42c2
18 changed files with 857 additions and 34 deletions

View file

@ -360,7 +360,7 @@ ack-per-packet).
## #240 — RetailAnimationLoader caches every parsed Animation forever
**Status:** OPEN
**Status:** DONE — 2026-07-24, Modern Runtime Slice D3
**Severity:** MEDIUM
**Filed:** 2026-07-24
**Component:** content / animation
@ -373,12 +373,14 @@ creature/object motion tables grows this monotonically — same class as the
audio caches bounded on 2026-07-24, and structurally incapable of the
plateau behavior the audit measured for entities/particles.
**Root cause / status:** 2026-07-24 audit review finding. Bound with the
same LRU/byte-budget shape as `DatSoundCache` (2026-07-24) /
`BoundedDatObjectCache`. Note sequencers retain references to loaded
animations — eviction must not invalidate an Animation a live sequencer
still holds (they keep the object alive; the cache only drops its own
reference).
**Fix:** `RetailAnimationLoader` now has one concurrent in-flight gate plus a
64 MiB / 512-entry LRU (startup-configurable through
`ResidencyBudgetOptions`). Duplicate requests coalesce, unrelated reads remain
parallel, null and oversize results cannot grow the cache, and eviction only
drops the cache reference. Live sequencers therefore keep their immutable
`Animation` object valid. Deterministic count, byte-pressure, oversize, LRU,
and concurrent-load tests cover the policy; the unified residency snapshot
reports decoded bytes, budget, and evictions.
**Files:** `src/AcDream.Content/Vfx/RetailAnimationLoader.cs:19-53`.
@ -435,7 +437,7 @@ the ordered array on the publication object.
## #243 — ObjectMeshManager._boundsCache grows unbounded
**Status:** OPEN
**Status:** DONE — 2026-07-24, stale audit finding closed by Slice D3
**Severity:** LOW
**Filed:** 2026-07-24
**Component:** render
@ -446,8 +448,12 @@ but the one unbounded container in a class where every other cache is
deliberately budgeted, and another no-plateau curve for long-uptime
sessions.
**Root cause / status:** 2026-07-24 audit review finding. Bound with the
existing entry-count LRU shape; plan Slice D's accounting covers it.
**Resolution:** The named field and insertion site do not exist in the current
tree: a repository-wide search finds no `_boundsCache` and
`ObjectMeshManager` has no bounds memoization owner. The review appears to
have described an older or different tree. Slice D deliberately does not add
a cache merely to bound it; current mesh/prepared/staging/arena owners are all
reported through the residency ledger.
**Files:** `src/AcDream.App/Rendering/Wb/ObjectMeshManager.cs` (`_boundsCache`,
insert site ~1757).

View file

@ -1,6 +1,6 @@
# Modern Runtime Slice D — Typed Asset Handles and Unified Residency
**Status:** active — D1 and D2 complete
**Status:** active — D1 through D3 complete
**Program:** `docs/plans/2026-07-24-modern-runtime-architecture.md`
**Baseline:** Slice C closeout commit `a564c4b7`
**Behavior contract:** no visual-quality or retail-behavior change
@ -227,6 +227,19 @@ residence.
- Bring deferred-alpha retained scratch behind a bounded capacity owner.
- Close stale #243 after proving no current bounds cache exists.
**Complete 2026-07-24.** `RetailAnimationLoader` now uses a concurrent
in-flight load gate feeding a byte-and-count bounded LRU. Cache eviction drops
only the cache reference, so active sequencers retain valid immutable
animations; duplicate DID requests coalesce while unrelated reads remain
parallel. The shared retail alpha path now partitions its 16 MiB startup budget
between the queue, object dispatcher, and particle renderer. Each physical
owner grows immediately when a frame requires it, reports exact retained
backing capacity, and shrinks only after repeated severe under-use. This bounds
one-frame density spikes without changing draw order, particle content, or
visible quality. Issue #240 is closed. Issue #243 is closed as a stale audit
finding: no `_boundsCache`, bounds memo, or cited insertion site exists in the
current source, so no replacement cache was invented.
### D4 — Diagnostics and forced pressure
- Publish aggregate and per-domain budgets/occupancy/fragmentation through the