fix(render): particles draw unclipped, once, in their retail stage
Retail never clips a particle to a portal view: each emitter's polys
join the ONE alpha list during its owner cell's far-to-near walk turn
(LScape::draw @0x00506330 iterates block_draw_list reversed; DrawBlock
@0x005A17C0 walks cells; ShouldDrawParticles @0x0050FE60 gates by cell
and distance), and occlusion is the depth test at FlushAlphaList
@0x0059D2E0 (its float is a COUNT threshold - 0f = flush all). The
1d2f2f73 architecture instead re-submitted particles once per
OutsideView slice under that slice's hardware clip slot, which cut
effects at aperture boundaries and drew nothing when no outside slice
was in view (the cathedral look-north disappearance).
Now: unattached emitters submit once per frame by owner-cell kind
(outdoor landcells in the landscape stage, interior EnvCells in the
final world scope - new UnattachedEmitterCellScope filter); cell,
shell-route, barrier-static, and late-stage owners submit their
per-slice cone-cull UNION once with clipSlot 0; and particles emit in
the stage matching their PARENT CELL - an interior dynamic whose
sphere straddles an exit-portal plane keeps its mesh in both stages
(#118) but its particles move to the final pass, so the interior
stage can no longer repaint over them (the aperture-band star cut).
Also lands the inert Change-2 primitives for the AP-236 retirement
(candle-behind-door): RetailAlphaQueue.FlushFartherThan drains only
the far prefix without resetting sources, plus the executor
passthrough and the conservative look-in threshold helper - nothing
calls them yet.
User-gated 2026-08-29 round 2 at the Sanctuary Cathedral: spell and
recall stars cover the whole room at every camera direction including
north; waterfall containment holds on retail's depth/seal mechanism;
adjacent-room particles/lights, walls, Holtburg, recall unregressed
(paperdoll remains pre-existing intermittent #443). Register: AP-236
filed for the remaining barrier-order divergence.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
85530c0b7e
commit
684380d421
10 changed files with 518 additions and 85 deletions
|
|
@ -444,20 +444,9 @@ internal sealed class RetailPViewPassExecutor :
|
|||
animatedEntityIds: frame.AnimatedEntityIds);
|
||||
}
|
||||
|
||||
_particleClassifications.ReplaceOutdoor(context.ParticleOwnerIds);
|
||||
|
||||
if (_particleClassifications.Outdoor.Count > 0
|
||||
&& _particles is not null
|
||||
&& _particleRenderer is not null)
|
||||
{
|
||||
_particleRenderer.DrawForOwners(
|
||||
frame.Camera,
|
||||
frame.CameraWorldPosition,
|
||||
ParticleRenderPass.Scene,
|
||||
_particleClassifications.Outdoor,
|
||||
clipSlot: (uint)context.Slice.Slot);
|
||||
}
|
||||
|
||||
// Late-stage particle owners submit ONCE per frame through
|
||||
// DrawLandscapeStaticParticles after the slice loop (retail: one
|
||||
// unclipped alpha-list insertion per emitter), not per slice here.
|
||||
EnableClipDistances();
|
||||
if (frame.RenderSky && frame.RenderWeather)
|
||||
{
|
||||
|
|
@ -492,8 +481,12 @@ internal sealed class RetailPViewPassExecutor :
|
|||
RetailPViewFrameInput frame,
|
||||
RetailPViewLandscapeStaticParticleContext context)
|
||||
{
|
||||
bool scissor = BeginDoorwayScissor(context.Slice.NdcAabb);
|
||||
_surface.BindTerrainClip();
|
||||
// One unclipped submission per owner per frame. Retail never clips a
|
||||
// particle to a portal view — its polys join the one alpha list during
|
||||
// the owner cell's walk turn and the depth test at the flush decides
|
||||
// occlusion (FlushAlphaList @0x0059D2E0). The former per-slice call
|
||||
// with the slice's clip slot both hardware-cut effects at aperture
|
||||
// boundaries and double-submitted owners visible in two slices.
|
||||
DisableClipDistances();
|
||||
|
||||
_particleClassifications.ReplaceOutdoor(context.ParticleOwnerIds);
|
||||
|
|
@ -506,11 +499,9 @@ internal sealed class RetailPViewPassExecutor :
|
|||
frame.CameraWorldPosition,
|
||||
ParticleRenderPass.Scene,
|
||||
_particleClassifications.Outdoor,
|
||||
clipSlot: (uint)context.Slice.Slot);
|
||||
clipSlot: 0);
|
||||
}
|
||||
|
||||
if (scissor)
|
||||
_surface.EndScissor();
|
||||
_entities.ClearClipRouting();
|
||||
DisableClipDistances();
|
||||
}
|
||||
|
|
@ -573,11 +564,19 @@ internal sealed class RetailPViewPassExecutor :
|
|||
|
||||
public void DrawUnattachedSceneParticles(
|
||||
RetailPViewFrameInput frame,
|
||||
ClipViewSlice slice)
|
||||
bool outdoorCells)
|
||||
{
|
||||
if (_particles is null || _particleRenderer is null)
|
||||
return;
|
||||
|
||||
// Retail draws an unattached emitter once, during its owner CELL's
|
||||
// walk turn, with NO portal-view clip (CPhysicsObj::ShouldDrawParticles
|
||||
// @0x0050FE60 gates by cell in-view + distance; occlusion is the depth
|
||||
// test at FlushAlphaList @0x0059D2E0). Outdoor-cell emitters submit in
|
||||
// the landscape stage, interior-cell emitters in the final world stage.
|
||||
// The former once-per-OutsideView-slice submission with that slice's
|
||||
// hardware clip slot made effects vanish by view direction (zero
|
||||
// outside slices in view = zero submissions) — invented behavior.
|
||||
DisableClipDistances();
|
||||
_particleRenderer.DrawForOwners(
|
||||
frame.Camera,
|
||||
|
|
@ -585,11 +584,17 @@ internal sealed class RetailPViewPassExecutor :
|
|||
ParticleRenderPass.Scene,
|
||||
_noSceneParticleEntityIds,
|
||||
includeUnattached: true,
|
||||
clipSlot: (uint)slice.Slot);
|
||||
clipSlot: 0,
|
||||
unattachedCellScope: outdoorCells
|
||||
? UnattachedEmitterCellScope.OutdoorCells
|
||||
: UnattachedEmitterCellScope.InteriorCells);
|
||||
}
|
||||
|
||||
public void FlushLandscapeAlpha() => _alpha.Flush();
|
||||
|
||||
public void FlushLandscapeAlphaFartherThan(float minViewerDistance) =>
|
||||
_alpha.FlushFartherThan(minViewerDistance);
|
||||
|
||||
public void DrawCellParticles(
|
||||
RetailPViewFrameInput frame,
|
||||
RetailPViewCellSliceContext context)
|
||||
|
|
@ -608,12 +613,14 @@ internal sealed class RetailPViewPassExecutor :
|
|||
return;
|
||||
|
||||
DisableClipDistances();
|
||||
// Retail never clips cell particles to a portal view: the owner
|
||||
// cell's walls own occlusion via the depth test at the alpha flush.
|
||||
_particleRenderer.DrawForOwners(
|
||||
frame.Camera,
|
||||
frame.CameraWorldPosition,
|
||||
ParticleRenderPass.Scene,
|
||||
visible,
|
||||
clipSlot: (uint)context.Slice.Slot);
|
||||
clipSlot: 0);
|
||||
DisableClipDistances();
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue