feat(render): S2 chunk 2 - render statics borrow the registry's retail cell array

WalkProductionWorldData no longer floods; its indoor and outdoor static
sweeps read ShadowObjectRegistry.TryGetRetailCellArray (retail's
calc_cross_cells_static @0x00515160 -> AddPartsShadow @0x00517e40
CELLARRAY, computed once at registration). The App-owned render flood
(ResolveStaticRenderCells, its fingerprint cache, the primitive-Setup
special case) and Core's ComputeStaticRenderCells are deleted. The one
remaining fallback, an entity the physics publisher has not registered yet
while the projection journal already published it, buckets to the authored
parent cell and is counted per frame (UnregisteredStaticRenderFallbackCount)
for chunk 5 to judge on the connected route.

The Facility stair pin now registers at the projection's own entity id
(the old pure-function test never carried identity) and reads the retail
array; the installed-DAT comparator compares retail against collision.

Gates (run in the implementer's isolated worktree at identical content):
Release build 0/0; Core Physics 2,202/2,202; App hermetic 6,760/6,760 (the
two added WalkProductionWorldData tests); installed-DAT walk/flood/stair
family 24/24; Runtime 1,884/1,884.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
Erik 2026-09-02 22:31:46 +02:00
parent 2b40c0bb38
commit 707d2803a4
10 changed files with 392 additions and 227 deletions

View file

@ -167,8 +167,29 @@ public class Issue177StairDescentCameraFloodTests
}
}
/// <summary>
/// Campaign OVERHAUL S2 chunk 2: retargeted from the deleted
/// <c>ShadowObjectRegistry.ComputeStaticRenderCells</c> pure-function
/// side channel to the production
/// <see cref="ShadowObjectRegistry.RegisterMultiPart"/> transaction
/// (the SAME BSP-collision + <c>partArray:</c> recipe
/// <c>LandblockPhysicsPublisher.PublishStaticEntity</c> uses, and the
/// same one <c>RetailCellArrayComparatorInstalledDatTests</c>' fixture 1
/// exercises for this exact Setup) plus
/// <see cref="ShadowObjectRegistry.TryGetRetailCellArray"/>. The entity
/// id now MUST match the projection's
/// <see cref="RenderSourceMetadata.LocalEntityId"/> — chunk 2's
/// <see cref="WalkProductionWorldData"/> indoor sweep looks the retail
/// CELLARRAY up BY that id, unlike the deleted method's seed+position
/// pure function, which never carried entity identity. The former
/// "WithoutCollisionRows" name no longer describes production: a part
/// array is always supplied at the SAME registration as the collision
/// shapes now (chunk 1b), so <c>GetOwnerCells</c> for this BSP-bearing
/// Setup reflects that registration — and, per the comparator's fixture-1
/// evidence, equals the retail CELLARRAY exactly.
/// </summary>
[Fact]
public void FacilityStairAssembly_RegisterAcross015FTo015EWithoutCollisionRows()
public void FacilityStairAssembly_RegisterAcross015FTo015E()
{
string? datDir = ResolveDatDir();
if (datDir is null)
@ -199,6 +220,8 @@ public class Issue177StairDescentCameraFloodTests
stair.Frame.Origin.Z);
Quaternion rootRotation = stair.Frame.Orientation;
List<ShadowShape> bspShapes = ShadowShapeBuilder.FromLandblockBspParts(
meshRefs, isBuildingShell: false, cache.GetGfxObj);
List<ShadowShape> parts = ShadowShapeBuilder.FromStaticRenderParts(
meshRefs,
cache.GetGfxObj,
@ -206,14 +229,25 @@ public class Issue177StairDescentCameraFloodTests
out bool hasPhysicsBsp);
Assert.True(hasPhysicsBsp);
Assert.NotEmpty(parts);
Assert.NotEmpty(bspShapes);
const uint syntheticOwner = 0x7F00DEADu;
Assert.Empty(engine.ShadowObjects.GetOwnerCells(syntheticOwner));
IReadOnlyList<uint> cells = engine.ShadowObjects.ComputeStaticRenderCells(
FacilityHub | 0x015Fu,
const uint entityId = 0x48A02000u;
engine.ShadowObjects.RegisterMultiPart(
entityId,
rootPosition,
rootRotation,
parts);
bspShapes,
state: 0u,
flags: EntityCollisionFlags.None,
worldOffsetX: 0f,
worldOffsetY: 0f,
landblockId: FacilityHub,
seedCellId: FacilityHub | 0x015Fu,
isStatic: true,
partArray: parts);
Assert.True(engine.ShadowObjects.TryGetRetailCellArray(
entityId, out IReadOnlyList<uint> cells));
ShadowShape part = parts[0];
ShadowPartBox box = ShadowPartBox.FromShape(
@ -229,11 +263,21 @@ public class Issue177StairDescentCameraFloodTests
Assert.Contains(FacilityHub | 0x015Fu, cells);
Assert.Contains(FacilityHub | 0x015Eu, cells);
Assert.Empty(engine.ShadowObjects.GetOwnerCells(syntheticOwner));
// The registration carries collision shapes at the SAME transaction
// now (production always does — chunk 1b threaded the part array
// through the SAME RegisterMultiPart call the collision dispatch
// uses), so GetOwnerCells reflects that registration instead of
// staying empty; retail == collision for this BSP-bearing Setup
// (RetailCellArrayComparatorInstalledDatTests fixture 1), so the two
// sets agree exactly.
Assert.Equal(
cells.OrderBy(id => id),
engine.ShadowObjects.GetOwnerCells(entityId).OrderBy(id => id));
RenderSceneGeneration generation = RenderSceneGeneration.FromRaw(1);
using var scene = new ArchRenderScene(generation);
RenderProjectionId projectionId = RenderProjectionId.FromRaw(0x48A02000u);
RenderProjectionId projectionId = RenderProjectionId.FromRaw(entityId);
RenderTransform transform = RenderTransform.FromRoot(
rootPosition,
rootRotation,
@ -252,7 +296,7 @@ public class Issue177StairDescentCameraFloodTests
Flags = RenderProjectionFlags.Draw,
Source = new RenderSourceMetadata() with
{
LocalEntityId = 0x48A02000u,
LocalEntityId = entityId,
SourceId = setupId,
ParentCellId = FacilityHub | 0x015Fu,
},
@ -272,6 +316,7 @@ public class Issue177StairDescentCameraFloodTests
Assert.Contains(
worldData.GetCellStatics(FacilityHub | 0x015Eu).Records,
record => record.Id == projectionId);
Assert.Equal(0, worldData.UnregisteredStaticRenderFallbackCount);
}
/// <summary>

View file

@ -1,6 +1,8 @@
using System.Numerics;
using AcDream.App.Rendering.Scene;
using AcDream.App.Rendering.Scene.Arch;
using AcDream.App.Rendering.Walk;
using AcDream.Core.Physics;
namespace AcDream.App.Tests.Rendering.Walk;
@ -269,6 +271,121 @@ public sealed class WalkProductionWorldDataTests
Assert.Equal([0xF4180106u, 0xF4180104u], cells);
}
// -----------------------------------------------------------------
// Campaign OVERHAUL S2 chunk 2: BeginFrame's indoor static sweep now
// borrows membership from ShadowObjectRegistry.TryGetRetailCellArray
// instead of rebuilding it (the deleted ResolveStaticRenderCells /
// ShadowObjectRegistry.ComputeStaticRenderCells pair). These two tests
// drive the REAL production path end to end — a real (bare, no-DAT)
// ShadowObjectRegistry, a real ArchRenderScene, and BeginFrame itself —
// rather than the hardcoded-cell-array style of the Bucket* tests above,
// which exercise only the (unchanged) bucketing primitive.
// -----------------------------------------------------------------
private static ShadowShape Bsp(uint gfxObjId, float radius = 1f) =>
ShadowShape.Bsp(
gfxObjId,
Vector3.Zero,
Quaternion.Identity,
scale: 1f,
localGeometry: ShadowPartGeometry.Create(
new FlatCollisionSphere(Vector3.Zero, radius), null));
private static RenderProjectionRecord IndoorStaticRecord(
uint entityId, uint sourceId, uint parentCellId) =>
new RenderProjectionRecord() with
{
Id = RenderProjectionId.FromRaw(entityId),
ProjectionClass = RenderProjectionClass.IndoorCellStatic,
OwnerIncarnation = RenderOwnerIncarnation.FromRaw(1),
Transform = new RenderTransform(Matrix4x4.Identity),
PreviousTransform = new PreviousRenderTransform(Matrix4x4.Identity),
Residency = new RenderSpatialResidency(
RenderSpatialBucket.FromRaw(parentCellId), 0x8A020000u, parentCellId),
Flags = RenderProjectionFlags.Draw,
Source = new RenderSourceMetadata() with
{
LocalEntityId = entityId,
SourceId = sourceId,
ParentCellId = parentCellId,
},
};
[Fact]
public void BeginFrame_IndoorStatic_RegisteredEntityUsesRegistryRetailCellArray()
{
const uint entityId = 0x48A02001u;
const uint retailCellId = 0x8A02015Fu;
// A DECOY parent cell, deliberately different from the registered
// retail array's cell, so the assertions below can only pass if the
// indoor sweep actually consulted the registry rather than falling
// back to the authored parent.
const uint decoyParentCellId = 0x8A0201C1u;
var shadows = new ShadowObjectRegistry();
IReadOnlyList<ShadowShape> parts = new[] { Bsp(0x01001234u) };
shadows.RegisterMultiPart(
entityId,
Vector3.Zero,
Quaternion.Identity,
parts,
state: 0u,
flags: EntityCollisionFlags.None,
worldOffsetX: 0f,
worldOffsetY: 0f,
landblockId: 0x8A020000u,
seedCellId: retailCellId,
isStatic: true,
partArray: parts);
Assert.True(shadows.TryGetRetailCellArray(entityId, out IReadOnlyList<uint> retailCells));
Assert.Equal(new[] { retailCellId }, retailCells);
RenderSceneGeneration generation = RenderSceneGeneration.FromRaw(1);
using var scene = new ArchRenderScene(generation);
RenderProjectionRecord projection =
IndoorStaticRecord(entityId, sourceId: 0x02000001u, decoyParentCellId);
scene.Apply([RenderProjectionDelta.Register(generation, 1, projection)]);
var worldData = new WalkProductionWorldData(new WalkBuildingRegistry(), shadows);
worldData.BeginFrame(
scene.OpenQuery(), 0x8A020000u, renderCenterLbX: 0x8A, renderCenterLbY: 0x02);
Assert.Contains(
worldData.GetCellStatics(retailCellId).Records,
record => record.Id == projection.Id);
Assert.DoesNotContain(
worldData.GetCellStatics(decoyParentCellId).Records,
record => record.Id == projection.Id);
Assert.Equal(0, worldData.UnregisteredStaticRenderFallbackCount);
}
[Fact]
public void BeginFrame_IndoorStatic_UnregisteredEntityFallsBackToParentCellAndCountsFallback()
{
const uint entityId = 0x48A02002u;
const uint parentCellId = 0x8A02015Fu;
// Bare, empty registry: entityId is never registered, so
// TryGetRetailCellArray must answer false for it.
var shadows = new ShadowObjectRegistry();
Assert.False(shadows.TryGetRetailCellArray(entityId, out _));
RenderSceneGeneration generation = RenderSceneGeneration.FromRaw(1);
using var scene = new ArchRenderScene(generation);
RenderProjectionRecord projection =
IndoorStaticRecord(entityId, sourceId: 0x02000002u, parentCellId);
scene.Apply([RenderProjectionDelta.Register(generation, 1, projection)]);
var worldData = new WalkProductionWorldData(new WalkBuildingRegistry(), shadows);
worldData.BeginFrame(
scene.OpenQuery(), 0x8A020000u, renderCenterLbX: 0x8A, renderCenterLbY: 0x02);
Assert.Contains(
worldData.GetCellStatics(parentCellId).Records,
record => record.Id == projection.Id);
Assert.Equal(1, worldData.UnregisteredStaticRenderFallbackCount);
}
private static RenderProjectionRecord Record(uint id, Vector3 position) =>
new RenderProjectionRecord() with
{