From 5b9d0260bba4b641fb8ab3e3a5449c85a2d559f5 Mon Sep 17 00:00:00 2001 From: Erik Date: Sun, 23 Aug 2026 21:46:05 +0200 Subject: [PATCH] =?UTF-8?q?docs=20#429:=20root=20cause=20=E2=80=94=2030-77?= =?UTF-8?q?MB/frame=20LINQ=20allocation=20in=20streamed-mesh=20completion?= =?UTF-8?q?=20on=20the=20render=20thread;=20fix=20plan=20+=20acceptance=20?= =?UTF-8?q?protocol?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Local commit for the implementation handoff; push withheld per owner direction until the fix session. Co-Authored-By: Claude Fable 5 --- docs/ISSUES.md | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/docs/ISSUES.md b/docs/ISSUES.md index fd4b9ed5..375816d1 100644 --- a/docs/ISSUES.md +++ b/docs/ISSUES.md @@ -148,6 +148,35 @@ of running per arm (`artifacts/owner-gate/player-present-429-packON.csv` frames spend their extra time (the pack's CPU stage profiler names stages) and where the player position is sampled relative to it. +**ROOT CAUSE FOUND 2026-08-23 (frame-history run, 33,350 frames with stage +attribution — `ACDREAM_FRAME_PROF=1` + `ACDREAM_FRAME_HISTORY`):** of 708 +stall frames (>12 ms), 701 allocate 30-77 MB IN THAT SINGLE FRAME (normal +frames: ~22 KB, p99 94 KB). The time and the allocation sit on the render +path outside the tracked stages. The bursts arrive in ~8-frame clusters +during movement — the streaming MESH COMPLETION path: each frame completes +up to `MaxCompletionsPerFrame` (quality High = 4) newly streamed meshes on +the render thread, and each completion in +`ObjectMeshManager.UploadGfxObjMeshData` (~line 2043) runs LINQ chains — +`TextureBatches.Values.SelectMany(...).Select(b => b.Indices.ToArray()) +.ToArray()`, plus the retained pick-support copies +`CPUPositions = Vertices.Select(v => v.Position).ToArray()` and +`CPUIndices = ...SelectMany(...).SelectMany(...).ToArray()` — megabytes of +enumerator/intermediate-List garbage per mesh, tens of MB per frame. +Gen0 runs 70-145 collections per 5 s during movement (vs ~1/6 s idle). + +**Fix shape (a bounded slice, not a quickie — this is the production mesh +pipeline):** +1. De-LINQ the conversion: direct pre-sized loops for the index batches and + the CPU pick copies (sizes are known up front from the batch counts). +2. Consider byte-budgeted completions (4 huge EnvCell meshes is not the + same frame cost as 4 fence posts) and/or moving the CPU-side conversion + onto the existing mesh-preparation scheduler thread so the render thread + only adopts finished arrays. +3. Allocation-gate test in the I1 style: a completion of a representative + mesh set must allocate near its retained-copy size, not multiples of it. +The pack-ON player-jump phase question remains as the second defect but +becomes mostly moot once the stalls themselves shrink. + **Next probes (in order):** 1. `ACDREAM_DUMP_MOTION=1` + a temporary inbound-position log for the LOCAL guid: does ACE send position sets for the local player every