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

@ -313,8 +313,6 @@ public sealed class WorldSceneRendererTests
Assert.True(result.NormalWorldDrawn);
Assert.Contains("particles:pviewScoped", rig.Calls);
Assert.Same(rig.PView.OutdoorSceneParticleEntityIds, rig.Passes.ParticleOwners);
Assert.Equal([0xCAFEu], rig.Passes.ParticleOwners);
Assert.DoesNotContain("flat:weather", rig.Calls);
}
@ -756,9 +754,6 @@ public sealed class WorldSceneRendererTests
diagnosticPartition: null);
}
public IReadOnlySet<uint> OutdoorSceneParticleEntityIds { get; } =
new HashSet<uint> { 0xCAFEu };
public RetailPViewFrameInput? LastInput { get; private set; }
public bool ThrowOnDraw { get; set; }
@ -800,7 +795,6 @@ public sealed class WorldSceneRendererTests
public float WeatherDayFraction { get; private set; }
public IReadOnlySet<uint>? ParticleOwners { get; private set; }
public void BeginFrame() => calls.Add("passes:begin");
@ -837,10 +831,8 @@ public sealed class WorldSceneRendererTests
LoadedCell? clipRoot,
ClipFrameAssembly? clipAssembly,
in WorldCameraFrame camera,
IReadOnlySet<uint> outdoorOwnerIds,
string currentSignature)
{
ParticleOwners = outdoorOwnerIds;
string kind = clipRoot switch
{
null => "global",