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:
Erik 2026-09-04 16:27:46 +02:00
parent efb0756190
commit 94a6b5ef39
18 changed files with 352 additions and 269 deletions

View file

@ -1,58 +1,42 @@
using System.Numerics;
using AcDream.App.Rendering;
using AcDream.App.Tests.Rendering.Walk;
namespace AcDream.App.Tests.Rendering;
/// <summary>
/// S3 landing hygiene (H2): <see cref="TerrainModernRenderer.CollectVisibleCells"/>
/// is frustum-only now — its clip-plane-list and NDC-AABB scissor
/// parameters and the CPU/GPU clip-region equivalence check they fed had
/// exactly one production caller (<see cref="TerrainModernRenderer.Draw"/>),
/// and that caller never actually passed either (grep). The doorway-clip/scissor
/// rejection cases this file used to pin (<c>RejectsCellsOutsideDoorwayClipPlanes</c>,
/// <c>RejectsCellsOutsideDoorwayScissorAabb</c>, <c>UnionsCellsFromEveryLandscapeSlice</c>)
/// pinned a mechanism nothing in production ever fed — deleted with the
/// parameters. What remains still pins the one thing this method's callers
/// actually rely on: it publishes the visible terrain cell set per landblock,
/// respecting an explicit frustum when one is given.
/// Campaign OVERHAUL S5-c1: the null-root terrain path remains a safety draw,
/// but it may not reconstruct or publish a competing cell-visibility answer.
/// The same architecture pin protects the deleted point-light feedback seam.
/// </summary>
public sealed class TerrainParticleCellVisibilityTests
{
[Fact]
public void CollectVisibleCells_PublishesLandscapeCellsWithoutEntitySurvivors()
public void ProductionRetainsNoReconstructedVisibilityOrLightFeedbackSymbols()
{
var cells = new HashSet<uint>();
string sourceRoot = Path.Combine(WalkOracleTraceRepoRoot.Find(), "src");
string[] production = Directory.GetFiles(
sourceRoot,
"*.cs",
SearchOption.AllDirectories);
string[] forbidden =
[
"CollectVisibleCells",
"TerrainVisibleCellIds",
"ObserveDrawableCells",
"ClearDrawableCells",
];
TerrainModernRenderer.CollectVisibleCells(
cells,
0xA9B4FFFFu,
Vector3.Zero,
zMin: 0f,
zMax: 20f,
frustum: null);
Assert.Equal(64, cells.Count);
Assert.Contains(0xA9B40001u, cells);
Assert.Contains(0xA9B40040u, cells);
}
[Fact]
public void CollectVisibleCells_ForceDrawnParentStillUsesCellFrustum()
{
var cells = new HashSet<uint>();
FrustumPlanes frustum = FrustumPlanes.FromViewProjection(Matrix4x4.Identity);
// The parent landblock may have entered TerrainModernRenderer's draw
// list through neverCullLandblockId. The cell collector deliberately
// receives no such bypass and still rejects its off-frustum cells.
TerrainModernRenderer.CollectVisibleCells(
cells,
0xA9B4FFFFu,
new Vector3(100f, 100f, 100f),
zMin: 100f,
zMax: 120f,
frustum);
Assert.Empty(cells);
foreach (string symbol in forbidden)
{
string[] owners = production
.Where(path => File.ReadAllText(path).Contains(
symbol,
StringComparison.Ordinal))
.Select(path => Path.GetRelativePath(sourceRoot, path))
.OrderBy(path => path, StringComparer.Ordinal)
.ToArray();
Assert.True(
owners.Length == 0,
$"Production symbol {symbol} remains in: {string.Join(", ", owners)}");
}
}
}

View file

@ -28,7 +28,7 @@ public sealed class ParticleVisibilityControllerTests
controller.BeginFrame(Vector3.Zero);
controller.UseWorldView();
controller.MarkVisibleCells(new HashSet<uint> { 0x01010001u });
controller.MarkVisibleLandscapeCells(new HashSet<uint> { 0x01010001u });
controller.CompleteFrame();
controller.Apply(particles, 1f);
@ -84,15 +84,125 @@ public sealed class ParticleVisibilityControllerTests
controller.BeginFrame(Vector3.Zero);
controller.UseWorldView();
controller.MarkVisibleCells([0x01010001u]);
controller.MarkVisibleLandscapeCells([0x01010001u]);
controller.CompleteFrame();
controller.BeginFrame(new Vector3(1000f, 1000f, 0f));
controller.UseWorldView();
controller.MarkVisibleCells([0x02020001u]);
controller.MarkVisibleLandscapeCells([0x02020001u]);
controller.AbortFrame();
controller.Apply(particles, 1f);
Assert.True(Assert.Single(particles.EnumerateEmitters()).ViewEligible);
}
[Fact]
public void CompleteAbortAndResetPublishOnlyCompleteLandscapeTransactions()
{
var particles = new ParticleSystem(new EmitterDescRegistry(), new Random(1));
int first = particles.SpawnEmitter(
new EmitterDesc
{
DatId = 0x32000004u,
Type = ParticleType.Still,
MaxDegradeDistance = 100f,
MaxParticles = 1,
},
Vector3.Zero,
attachedObjectId: 43u);
int second = particles.SpawnEmitter(
new EmitterDesc
{
DatId = 0x32000005u,
Type = ParticleType.Still,
MaxDegradeDistance = 100f,
MaxParticles = 1,
},
Vector3.Zero,
attachedObjectId: 44u);
particles.UpdateEmitterOwnerCell(first, 0x0101_0001u);
particles.UpdateEmitterOwnerCell(second, 0x0101_0002u);
var controller = new ParticleVisibilityController();
controller.BeginFrame(Vector3.Zero);
controller.UseWorldView();
controller.MarkVisibleLandscapeCells([0x0101_0001u]);
controller.CompleteFrame();
controller.BeginFrame(new Vector3(1000f, 0f, 0f));
controller.UseWorldView();
controller.MarkVisibleLandscapeCells([0x0101_0002u]);
controller.AbortFrame();
controller.Apply(particles, 1f);
Assert.True(particles.EnumerateEmitters().Single(e => e.Handle == first).ViewEligible);
Assert.False(particles.EnumerateEmitters().Single(e => e.Handle == second).ViewEligible);
controller.BeginFrame(Vector3.Zero);
controller.UseWorldView();
controller.MarkVisibleLandscapeCells([0x0101_0002u]);
controller.CompleteFrame();
controller.Apply(particles, 1f);
Assert.False(particles.EnumerateEmitters().Single(e => e.Handle == first).ViewEligible);
Assert.True(particles.EnumerateEmitters().Single(e => e.Handle == second).ViewEligible);
controller.Reset();
controller.Apply(particles, 1f);
Assert.All(particles.EnumerateEmitters(), emitter => Assert.False(emitter.ViewEligible));
}
[Theory]
[InlineData(0u)]
[InlineData(0x0101_0100u)]
public void MarkVisibleLandscapeCells_RejectsNonLandscapeIds(uint cellId)
{
var controller = new ParticleVisibilityController();
controller.BeginFrame(Vector3.Zero);
controller.UseWorldView();
ArgumentException error = Assert.Throws<ArgumentException>(
() => controller.MarkVisibleLandscapeCells([cellId]));
Assert.Contains($"0x{cellId:X8}", error.Message, StringComparison.Ordinal);
}
[Fact]
public void ProductionApply_WarmedParticleViewPathDoesNotAllocate()
{
var particles = new ParticleSystem(new EmitterDescRegistry(), new Random(1));
int outdoor = particles.SpawnEmitter(
new EmitterDesc
{
DatId = 0x32000006u,
Type = ParticleType.Still,
MaxDegradeDistance = 100f,
MaxParticles = 1,
},
Vector3.Zero,
attachedObjectId: 45u);
int environment = particles.SpawnEmitter(
new EmitterDesc
{
DatId = 0x32000007u,
Type = ParticleType.Still,
MaxDegradeDistance = 100f,
MaxParticles = 1,
},
Vector3.Zero,
attachedObjectId: 46u);
particles.UpdateEmitterOwnerCell(outdoor, 0x0101_0001u);
particles.UpdateEmitterOwnerCell(environment, 0x0101_0100u);
var controller = new ParticleVisibilityController();
controller.BeginFrame(Vector3.Zero);
controller.UseWorldView();
controller.MarkVisibleLandscapeCells([0x0101_0001u]);
controller.CompleteFrame();
long allocated = ZeroAllocationProbe.MeasureWarmed(
() => controller.Apply(particles, 1f),
batchSize: 256,
warmupBatches: 2,
samples: 4);
Assert.True(allocated == 0, $"Particle view allocated {allocated} bytes.");
}
}

View file

@ -316,7 +316,7 @@ public sealed class WalkStaticStreamPopulatorTests
Assert.DoesNotContain(
"one stable far-to-near stream keyed", architecture, StringComparison.OrdinalIgnoreCase);
Assert.Contains("162 active rows", register, StringComparison.Ordinal);
Assert.Contains("161 active rows", register, StringComparison.Ordinal);
int apSectionStart = register.IndexOf(
"## 3. Documented approximation (AP)", StringComparison.Ordinal);
int apSectionEnd = register.IndexOf(
@ -338,7 +338,7 @@ public sealed class WalkStaticStreamPopulatorTests
Assert.Single(File.ReadLines(registerPath), static line =>
line.StartsWith("| AP-243 |", StringComparison.Ordinal));
Assert.Equal(
162,
161,
File.ReadLines(registerPath).Count(static line =>
line.StartsWith("| AP-", StringComparison.Ordinal)));
Assert.Contains("greater than 50 m", register, StringComparison.Ordinal);

View file

@ -52,9 +52,6 @@ public sealed class WorldRenderFrameBuilderTests
in foundation,
waitingForLogin: true,
activeDayGroup: null);
var drawableCells = new HashSet<uint> { 0x01010001u };
builder.ObserveDrawableCells(drawableCells);
builder.ClearDrawableCells();
Assert.Equal(
[
@ -74,8 +71,6 @@ public sealed class WorldRenderFrameBuilderTests
Assert.Null(result.ClipRoot);
Assert.True(visibility.WaitingForLogin);
Assert.Equal(foundation, environment.Foundation);
Assert.Same(drawableCells, environment.DrawableCells);
Assert.Equal(1, environment.ClearCount);
}
[Fact]
@ -254,7 +249,7 @@ public sealed class WorldRenderFrameBuilderTests
}
[Fact]
public void Runtime_environment_uses_resident_lights_independent_of_drawable_cells()
public void Runtime_environment_uses_all_resident_lights()
{
const uint visibleCell = 0x01010100u;
const uint hiddenCell = 0x01010101u;
@ -280,10 +275,6 @@ public sealed class WorldRenderFrameBuilderTests
lightingUbo: null,
new WorldRenderRangeState(4, 12),
skyPes: null);
var borrowed = new HashSet<uint> { visibleCell };
environment.ObserveDrawableCells(borrowed);
borrowed.Clear();
borrowed.Add(hiddenCell);
WorldCameraFrame camera = CameraFrame(new FlyCamera());
WorldRootFrame roots = default;
RenderFrameFoundation foundation = default;
@ -293,7 +284,6 @@ public sealed class WorldRenderFrameBuilderTests
Assert.Contains(visibleLight, lighting.PointSnapshot);
Assert.Contains(hiddenLight, lighting.PointSnapshot);
environment.ClearDrawableCells();
environment.Prepare(in camera, in roots, in foundation, activeDayGroup: null);
Assert.Contains(visibleLight, lighting.PointSnapshot);
@ -544,10 +534,6 @@ public sealed class WorldRenderFrameBuilderTests
{
public RenderFrameFoundation Foundation { get; private set; }
public IReadOnlySet<uint>? DrawableCells { get; private set; }
public int ClearCount { get; private set; }
public void Prepare(
in WorldCameraFrame camera,
in WorldRootFrame roots,
@ -558,10 +544,6 @@ public sealed class WorldRenderFrameBuilderTests
Foundation = foundation;
}
public void ObserveDrawableCells(IReadOnlySet<uint> drawableCells) =>
DrawableCells = drawableCells;
public void ClearDrawableCells() => ClearCount++;
}
private sealed class RecordingAnimated(

View file

@ -194,9 +194,7 @@ public sealed class WorldSceneRendererTests
"flat:clip",
"flat:sky",
"flat:terrain",
"visibility:mark",
"flat:entities",
"frame:clear-cells",
"passes:disable-clip",
"particles:global",
"flat:weather",
@ -220,7 +218,7 @@ public sealed class WorldSceneRendererTests
}
[Fact]
public void PViewWorld_PublishesTheWalkVisibleCellsOnce()
public void PViewWorld_PublishesTheWalkLandscapeCellsOnce()
{
var root = new LoadedCell
{
@ -240,7 +238,6 @@ public sealed class WorldSceneRendererTests
"passes:begin",
"pview:draw",
"visibility:mark",
"frame:observe-cells",
"diagnostics:pview",
"passes:disable-clip",
"particles:none",
@ -265,7 +262,7 @@ public sealed class WorldSceneRendererTests
}
[Fact]
public void PViewWorld_PublishesLookInCellsToParticleAndLightVisibility()
public void PViewWorld_PublishesOnlyLandscapeCellsToParticleVisibility()
{
var root = new LoadedCell
{
@ -276,15 +273,11 @@ public sealed class WorldSceneRendererTests
rig.Renderer.Render(default);
// Retail gates effects on cell->IsInView() (ShouldDrawParticles
// @0x0050FE60), and a cell entered through a building portal
// (DrawBuilding @0x0059F2A0 -> PView::ConstructView @0x005A57B0) is
// drawn by the same traversal as a flooded cell. The particle gate and
// light-candidate scope must therefore receive the walk's one visited
// set, including look-ins.
Assert.Contains(0x01010003u, rig.Visibility.MarkedCells);
Assert.NotNull(rig.Frames.ObservedCells);
Assert.Contains(0x01010003u, rig.Frames.ObservedCells!);
// The borrowed result deliberately has three distinct products:
// DrawableCells contains the EnvCell, VisibleCells is their diagnostic
// union, and VisibleLandscapeCells contains only this outdoor id.
Assert.Equal([0x0101_0003u], rig.Visibility.MarkedCells);
Assert.DoesNotContain(0x0101_0100u, rig.Visibility.MarkedCells);
}
[Fact]
@ -925,15 +918,6 @@ public sealed class WorldSceneRendererTests
return frame;
}
public IReadOnlySet<uint>? ObservedCells { get; private set; }
public void ObserveDrawableCells(IReadOnlySet<uint> drawableCells)
{
calls.Add("frame:observe-cells");
ObservedCells = new HashSet<uint>(drawableCells);
}
public void ClearDrawableCells() => calls.Add("frame:clear-cells");
}
private sealed class EntitySource : IWorldSceneEntitySource
@ -995,7 +979,7 @@ public sealed class WorldSceneRendererTests
{
public HashSet<uint> MarkedCells { get; } = [];
public void MarkVisibleCells(HashSet<uint> cellIds)
public void MarkVisibleLandscapeCells(HashSet<uint> cellIds)
{
calls.Add("visibility:mark");
MarkedCells.UnionWith(cellIds);
@ -1015,13 +999,14 @@ public sealed class WorldSceneRendererTests
public PViewRenderer(List<string> calls)
{
_calls = calls;
// Distinct flood-only vs in-view sets: 0x01010003 is a look-in
// cell that is drawn but never part of the main flood.
// Distinct typed products: EnvCell shell preparation, diagnostic
// union, and the outdoor landscape IsInView answer.
_interiorResult = new RetailPViewFrameResult().Reset(
ClipFrameAssembler.BeginWalkFrame(
ClipFrame.NoClip(), outdoorRoot: false),
[0x01010001u],
[0x01010001u, 0x01010003u],
[0x0101_0100u],
[0x0101_0100u, 0x0101_0003u],
[0x0101_0003u],
default,
default,
diagnosticPartition: null);
@ -1030,6 +1015,7 @@ public sealed class WorldSceneRendererTests
ClipFrame.NoClip(), outdoorRoot: true),
[],
[],
[],
default,
default,
diagnosticPartition: null);
@ -1062,8 +1048,6 @@ public sealed class WorldSceneRendererTests
public bool ThrowOnAbort { get; set; }
public HashSet<uint>? TerrainVisibleCellIds { get; } = [0x01010001u];
public RenderFrameFoundation? SkyFoundation { get; private set; }
public RenderFrameFoundation? WeatherFoundation { get; private set; }

View file

@ -529,6 +529,62 @@ public sealed class ParticleSystemTests
Assert.False(Assert.Single(sys.EnumerateEmitters()).ViewEligible);
}
[Fact]
public void ApplyRetailView_UsesLandscapeMembershipButEnvCellConstantVirtual()
{
var sys = MakeSystem();
var desc = new EmitterDesc
{
DatId = 0x3200007Bu,
Type = ParticleType.Still,
MaxDegradeDistance = 10f,
MaxParticles = 1,
};
int outdoor = sys.SpawnEmitter(desc, Vector3.Zero, attachedObjectId: 21u);
int environment = sys.SpawnEmitter(desc, Vector3.Zero, attachedObjectId: 22u);
int cellLess = sys.SpawnEmitter(desc, Vector3.Zero, attachedObjectId: 23u);
sys.UpdateEmitterOwnerCell(outdoor, 0x0101_0001u);
sys.UpdateEmitterOwnerCell(environment, 0x0101_0100u);
sys.ApplyRetailView(Vector3.Zero, new HashSet<uint> { 0x0101_0001u }, true);
ParticleEmitter[] first = sys.EnumerateEmitters().ToArray();
Assert.True(first.Single(emitter => emitter.Handle == outdoor).ViewEligible);
Assert.True(first.Single(emitter => emitter.Handle == environment).ViewEligible);
Assert.False(first.Single(emitter => emitter.Handle == cellLess).ViewEligible);
sys.ApplyRetailView(Vector3.Zero, new HashSet<uint>(), true);
ParticleEmitter[] second = sys.EnumerateEmitters().ToArray();
Assert.False(second.Single(emitter => emitter.Handle == outdoor).ViewEligible);
Assert.True(second.Single(emitter => emitter.Handle == environment).ViewEligible);
Assert.False(second.Single(emitter => emitter.Handle == cellLess).ViewEligible);
}
[Fact]
public void ApplyRetailView_NoCompletedViewRejectsBothCellFamilies()
{
var sys = MakeSystem();
var desc = new EmitterDesc
{
DatId = 0x3200007Du,
Type = ParticleType.Still,
MaxDegradeDistance = 10f,
MaxParticles = 1,
};
int outdoor = sys.SpawnEmitter(desc, Vector3.Zero, attachedObjectId: 31u);
int environment = sys.SpawnEmitter(desc, Vector3.Zero, attachedObjectId: 32u);
sys.UpdateEmitterOwnerCell(outdoor, 0x0101_0001u);
sys.UpdateEmitterOwnerCell(environment, 0x0101_0100u);
sys.ApplyRetailView(
Vector3.Zero,
new HashSet<uint> { 0x0101_0001u },
hasCompletedView: false);
Assert.All(sys.EnumerateEmitters(), emitter => Assert.False(emitter.ViewEligible));
}
[Fact]
public void ApplyRetailView_UnorderedOwnerDistanceMatchesRetailX87Comparison()
{