feat(overhaul): port S5 particle cell visibility
Publish the exact completed walk landscape set as the typed retained particle-view product. Apply retail CLandCell membership versus constant-true non-null CEnvCell eligibility while preserving the x87 distance and AP-116 behavior. Delete the null-root terrain visibility reconstruction and the dead drawable-cell point-light feedback chain. Retire AP-117, correct AP-85 and AD-21, and keep directional shadows, building degrade, AP-232, probes, RHI, shaders, and DAT outside this chunk. Automated return: Release 0 warnings/0 errors; Core VFX 111/111; App particle/frame/renderer/terrain 146/146; warmed production allocation 1/1 at 0 B. First official hermetic artifact s5-c1-hermetic-20260904 is preserved at 16759/16760 with only the stale 162-row assertion; after the bounded 161 correction, exact pin 1/1 and fresh s5-c1-hermetic-corrected-20260904 16760/16760. InstalledDat 385 pass, 10 documented failures, 1 documented skip, no new identity. Mutation 1: making EnvCell eligibility set-dependent first failed ParticleSystemTests.cs:553 Assert.True, expected true actual false. Mutation 2: making outdoor eligibility constant true first failed ParticleSystemTests.cs:559 Assert.False, expected false actual true. Mutation 3: feeding the diagnostic union first failed WorldSceneRendererTests.cs:279 HashSet equality, expected [16842755], actual [16843008, 16842755]. Mutation 4: restoring CollectVisibleCells first failed TerrainParticleCellVisibilityTests.cs:37 and named TerrainModernRenderer.cs. Mutation 5: restoring ObserveDrawableCells first failed TerrainParticleCellVisibilityTests.cs:37 and named WorldRenderFrameBuilder.cs. Mutation 6: changing the inclusive boundary from <= to < first failed ParticleSystemTests.cs:519 Assert.True, expected true actual false. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
efb0756190
commit
94a6b5ef39
18 changed files with 352 additions and 269 deletions
|
|
@ -20,7 +20,11 @@ public sealed class ParticleSystem : IParticleSystem
|
|||
// thousands of finite/fading emitters.
|
||||
private readonly SortedSet<int> _allHandles = [];
|
||||
private readonly SortedSet<int> _simulationHandles = [];
|
||||
private readonly SortedSet<int> _worldSimulationHandles = [];
|
||||
// ApplyRetailView is a per-frame production path. SortedSet's enumerator
|
||||
// allocates its traversal stack, so retain this subset as a sorted list;
|
||||
// lifecycle mutations pay the binary insertion/removal cost and the
|
||||
// warmed view walk stays allocation-free in emitter-spawn order.
|
||||
private readonly List<int> _worldSimulationHandles = [];
|
||||
private readonly SortedSet<int>[] _renderableHandlesByPass =
|
||||
[[], [], []];
|
||||
private readonly SortedSet<int>[] _renderableUnattachedHandlesByPass =
|
||||
|
|
@ -147,7 +151,7 @@ public sealed class ParticleSystem : IParticleSystem
|
|||
_allHandles.Add(handle);
|
||||
_simulationHandles.Add(handle);
|
||||
if (visibilityPolicy == ParticleVisibilityPolicy.World)
|
||||
_worldSimulationHandles.Add(handle);
|
||||
AddWorldSimulationHandle(handle);
|
||||
AddEmitterToRenderIndexes(emitter);
|
||||
|
||||
for (int i = 0; i < desc.InitialParticles; i++)
|
||||
|
|
@ -330,7 +334,7 @@ public sealed class ParticleSystem : IParticleSystem
|
|||
if (visibilityPolicy == ParticleVisibilityPolicy.World)
|
||||
{
|
||||
if (emitter.SimulationEnabled)
|
||||
_worldSimulationHandles.Add(handle);
|
||||
AddWorldSimulationHandle(handle);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
@ -343,22 +347,25 @@ public sealed class ParticleSystem : IParticleSystem
|
|||
/// <summary>
|
||||
/// Applies <c>CPhysicsObj::ShouldDrawParticles</c> (0x0050FE60) to every
|
||||
/// live emitter. The App layer supplies the previous completed retail
|
||||
/// PView's cell set, equivalent to <c>CObjCell::IsInView</c> when the next
|
||||
/// physics update runs.
|
||||
/// PView's exact landscape-cell set. The cell-family branch below invokes
|
||||
/// the matching retail virtual when the next physics update runs:
|
||||
/// <c>CLandCell::IsInView</c> reads that completed set, while
|
||||
/// <c>CEnvCell::IsInView</c> is constant true.
|
||||
/// </summary>
|
||||
public void ApplyRetailView(
|
||||
Vector3 viewerPosition,
|
||||
IReadOnlySet<uint> visibleCellIds,
|
||||
IReadOnlySet<uint> visibleLandscapeCellIds,
|
||||
bool hasCompletedView,
|
||||
float rangeMultiplier = 1f)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(visibleCellIds);
|
||||
ArgumentNullException.ThrowIfNull(visibleLandscapeCellIds);
|
||||
if (!float.IsFinite(rangeMultiplier) || rangeMultiplier <= 0f)
|
||||
rangeMultiplier = 1f;
|
||||
|
||||
LastRetailViewEmitterVisitCount = 0;
|
||||
foreach (int handle in _worldSimulationHandles)
|
||||
for (int i = 0; i < _worldSimulationHandles.Count; i++)
|
||||
{
|
||||
int handle = _worldSimulationHandles[i];
|
||||
if (!_byHandle.TryGetValue(handle, out ParticleEmitter? emitter))
|
||||
continue;
|
||||
LastRetailViewEmitterVisitCount++;
|
||||
|
|
@ -374,8 +381,12 @@ public sealed class ParticleSystem : IParticleSystem
|
|||
|
||||
float maxDistance = emitter.Desc.MaxDegradeDistance * rangeMultiplier;
|
||||
float distance = RetailDistance(emitter.OwnerPosition, viewerPosition);
|
||||
uint ownerCellLow = emitter.OwnerCellId & 0xFFFFu;
|
||||
bool ownerCellInView = ownerCellLow >= 0x0100u
|
||||
|| (ownerCellLow > 0u
|
||||
&& visibleLandscapeCellIds.Contains(emitter.OwnerCellId));
|
||||
emitter.ViewEligible = emitter.OwnerCellId != 0
|
||||
&& visibleCellIds.Contains(emitter.OwnerCellId)
|
||||
&& ownerCellInView
|
||||
// The x87 comparison in ShouldDrawParticles admits unordered
|
||||
// comparisons (NaN) and reject a negative authored range.
|
||||
&& (float.IsNaN(distance)
|
||||
|
|
@ -442,7 +453,7 @@ public sealed class ParticleSystem : IParticleSystem
|
|||
emitter.SimulationEnabled = true;
|
||||
_simulationHandles.Add(handle);
|
||||
if (emitter.VisibilityPolicy == ParticleVisibilityPolicy.World)
|
||||
_worldSimulationHandles.Add(handle);
|
||||
AddWorldSimulationHandle(handle);
|
||||
else
|
||||
emitter.ViewEligible = true;
|
||||
}
|
||||
|
|
@ -1007,6 +1018,13 @@ public sealed class ParticleSystem : IParticleSystem
|
|||
}
|
||||
}
|
||||
|
||||
private void AddWorldSimulationHandle(int handle)
|
||||
{
|
||||
int index = _worldSimulationHandles.BinarySearch(handle);
|
||||
if (index < 0)
|
||||
_worldSimulationHandles.Insert(~index, handle);
|
||||
}
|
||||
|
||||
private static bool IsRenderable(ParticleEmitter emitter)
|
||||
=> emitter.PresentationVisible && emitter.ViewEligible;
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue