DrawCellObjectLists drew cell particles PER visible cell, and each call
(DrawRetailPViewCellParticles -> ParticleRenderer.Draw) re-walked the ENTIRE
live particle set to filter by owner id — O(cells x particles). Measured via
[CPU-PHASE] at dense Arwic this was the cellobjects sink (~5.4 ms CPU; the
phase's GPU share is 0.01 ms — pure CPU). gpu is 0.5 ms; the dense town is
~96% CPU-bound (the earlier "6.7 ms GPU" was a moving/streaming transient).
Static owners are disjoint per cell (InteriorEntityPartition.ByCell partitions
by ParentCellId), so the UNION of survivors (= _allCellStatics, already
accumulated in loop 1 for the batched entity draw) draws EXACTLY the same
emitters in ONE pass: the callback gates on owner id, the renderer sorts
globally back-to-front, and the per-cell slice was never used for clipping
(scissor gate deleted in T3; DisableClipDistances). Runs after the batched
static draw so emitters still depth-test against same-cell statics. Deletes the
loop-2 re-cull and collapses the per-cell BuildDrawList allocations N -> 1
(also eases the GC spikes).
Adversarial review (3 angles) confirmed emitter-set equivalence, no double-
draw, equal-or-better compositing, and no lost per-cell side effect — and found
the OLD code DOUBLE-DREW additive particles for multi-view-polygon cells (one
DrawCellParticles per slice, same owner set each time; the #121 over-bright
class). Consolidation draws each emitter once, fixing that latent bug.
Pixels identical-or-better; draw-mechanism speed only. Build + full suite green
(pview replay tests incl. HouseExitWalkReplay/TowerAscent/Issue130 pass).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>