fix(render): S2 chunk 6 review round — one particle turn per cell per stamp, cheap empty cells, dead owner-set stub deleted

Retail-lens review of chunk 6 (no blocking finding). Fixed:
- a cell that gets two object-list turns in one frame (a chamber reached
  through two portals) submitted its emitters twice; retail's particle parts
  sit in the same shadow_part_list as every other part and CPhysicsPart::Draw's
  frame stamp suppresses the second draw, so the walk now dedupes the particle
  turn with the same frame-scoped set that dedupes the cell shell;
- every visited land cell paid the full per-cell draw setup even with no
  emitter; DrawForCell now returns after the cell lookup, retail's own cost
  (DrawPartCell 0x005a07a0 `num_shadow_parts > 0`);
- CopyRenderableEmittersInCell maintains LastRenderScopeEmitterVisitCount;
- the OutdoorSceneParticleEntityIds / outdoorOwnerIds stub chain (permanently
  empty, never read) is deleted through IWorldSceneRenderer,
  WorldScenePViewRenderer, IWorldScenePasses and the composition root;
- AD-117 item 4 names the two behavioral residuals (owner-cell substitution;
  no per-emission AddPartToShadowCells);
- ParticleHookSinkTests pins that an emitter's draw cell is its owner's pose
  cell and survives the projection-visibility switch across the per-frame
  view pass.

Gates: Core 4,988/4,988 (Vfx 108/108), App hermetic 6,760/6,760, Runtime
1,884/1,884.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
Erik 2026-09-03 06:52:28 +02:00
parent 7969c2e6ad
commit 6d5afcccde
11 changed files with 891 additions and 800 deletions

View file

@ -624,12 +624,14 @@ public sealed class ParticleSystem : IParticleSystem
{
ArgumentNullException.ThrowIfNull(destination);
destination.Clear();
LastRenderScopeEmitterVisitCount = 0;
int passIndex = RenderPassIndex(renderPass);
if (!_cellHandlesByPass[passIndex].TryGetValue(cellId, out OwnerEmitterBucket? bucket))
return;
_scopeHandleScratch.Clear();
bucket.CopyRenderableHandlesTo(_scopeHandleScratch);
LastRenderScopeEmitterVisitCount = _scopeHandleScratch.Count;
foreach (int handle in _scopeHandleScratch)
{
if (_byHandle.TryGetValue(handle, out ParticleEmitter? emitter))