fix(render): FW4 - static-owner particles submit at their walk turns

Ports the user-verified #132 invariant (e102fb36 on the old pipeline)
walk-natively, on the owner''s direction: retail''s falls containment
is POSITIONAL - an outdoor emitter''s polys join the ONE alpha list
during its owner cell''s DrawObjCell in the far-to-near landscape walk
(DrawSortCell @0x005a17c0), so every nearer building''s pre-punch
barrier (DrawBuilding @0x0059f2a0''s FlushAlphaList @0x0059f30b)
drains the already-queued FARTHER content against still-true depth
BEFORE its punch stamps far-Z into the aperture. The walk path had
kept the per-building AlphaBarrier events but batched ALL landscape
static-owner submission into one lump at the pre-clear closure - after
every punch had run: the barriers fired over an empty queue and the
falls drained against punched-far aperture pixels (phase=pre in every
probe line, which is why six rounds of phase-staging repairs could
never see it - the phase was right, the position within the phase was
wrong).

New WalkFrameEventKind.StaticParticles: each landscape cell''s and
each building shell''s emitter owners now submit AT THAT TURN during
Replay (marked so the owner''s meshes flush first - retail''s
per-object order); the batched SubmitWalkLandscapeStaticParticles and
its closure/post-replay call sites are deleted for both root kinds.
Two driver sequence pins adjudicated to the new turn order.

Hermetic 6,762/0; InstalledDat walk conformance 40/1.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Erik 2026-08-30 21:26:40 +02:00
parent f4522297cc
commit 280c054943
4 changed files with 123 additions and 47 deletions

View file

@ -60,6 +60,13 @@ public sealed class WalkFrameDriverTests
}
public void AlphaBarrier(float viewerDistance) => log.Add($"ALPHA:{viewerDistance:F2}");
public void DrawStaticParticles(IReadOnlySet<uint> ownerIds)
{
var sorted = new List<uint>(ownerIds);
sorted.Sort();
log.Add($"PARTICLES:{string.Join(",", sorted.ConvertAll(o => o.ToString("x")))}");
}
}
private sealed class RecordingTrace(List<string> log) : IWalkFrameDriverTrace
@ -405,7 +412,13 @@ public sealed class WalkFrameDriverTests
driver.Replay(draw.Frame, draw.Pass);
Assert.Equal(
new[] { "ALPHA:12.50", "PUNCH:4@v0", "SHELL:00000104", "FLUSH:1:LookInStatic", "FLUSH:1:BuildingShell" },
// FW4 #132 positional invariant: the building's shell emitters
// submit at the shell turn, after the shell content flushes.
new[]
{
"ALPHA:12.50", "PUNCH:4@v0", "SHELL:00000104",
"FLUSH:1:LookInStatic", "FLUSH:1:BuildingShell", "PARTICLES:c9",
},
log);
// The punch polygon reached the leaf renderer in WORLD space: the
@ -554,7 +567,9 @@ public sealed class WalkFrameDriverTests
Assert.Empty(log); // still nothing — EndFrame closes Collect, it does not Replay
driver.Replay(draw.Frame, draw.Pass);
Assert.Equal(new[] { "FLUSH:1:OutdoorStatic" }, log);
// FW4 #132 positional invariant: the cell's emitters submit at its
// own landscape turn, after its meshes flush.
Assert.Equal(new[] { "FLUSH:1:OutdoorStatic", "PARTICLES:12d" }, log);
GpuRecordedMultiDrawIndirect mdi = Assert.Single(fx.Device.Calls.OfType<GpuRecordedMultiDrawIndirect>());
Assert.Equal(1u, mdi.DrawCount);
}