acdream/tests/AcDream.App.Tests/Rendering/Walk/WalkProductionWorldDataTests.cs
Erik 707d2803a4 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>
2026-09-02 22:31:46 +02:00

396 lines
14 KiB
C#

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;
public sealed class WalkProductionWorldDataTests
{
[Fact]
public void BuildingShellBucketCellId_PortalLessBuildingUsesLandscapePositionCell()
{
const uint positionCellId = 0xF4180011u;
RenderProjectionRecord record = Record(
id: 0xCF418060u,
position: new Vector3(53.57f, 13.874f, 160f)) with
{
Source = new RenderSourceMetadata() with
{
LocalEntityId = 0xCF418060u,
SourceId = 0x01001FD3u,
EffectCellId = positionCellId,
BuildingShellAnchorCellId = 0,
},
EntityPayload = new RenderEntityPayload() with
{
IsBuildingShell = true,
},
};
var building = new WalkBuilding
{
PositionCellId = positionCellId,
Portals = [],
};
Assert.Equal(
positionCellId,
WalkProductionWorldData.BuildingShellBucketCellId(in record));
Assert.Equal(
positionCellId,
WalkProductionWorldData.BuildingShellBucketCellId(building));
}
[Fact]
public void BuildingShellBucketCellId_PortalBearingBuildingKeepsInteriorAnchor()
{
const uint anchorCellId = 0xF4180112u;
RenderProjectionRecord record = Record(
id: 0xCF41805Fu,
position: new Vector3(36f, 13.8349f, 160f)) with
{
Source = new RenderSourceMetadata() with
{
LocalEntityId = 0xCF41805Fu,
SourceId = 0x01001FB7u,
EffectCellId = 0xF4180009u,
BuildingShellAnchorCellId = anchorCellId,
},
EntityPayload = new RenderEntityPayload() with
{
IsBuildingShell = true,
},
};
var building = new WalkBuilding
{
PositionCellId = 0xF4180009u,
Portals =
[
new WalkBldPortal { OtherCellId = anchorCellId },
],
};
Assert.Equal(
anchorCellId,
WalkProductionWorldData.BuildingShellBucketCellId(in record));
Assert.Equal(
anchorCellId,
WalkProductionWorldData.BuildingShellBucketCellId(building));
}
[Fact]
public void BucketOutdoorRecord_UsesEveryOutdoorPhysicsShadowCellAcrossLandblockEdge()
{
RenderProjectionRecord record = Record(
id: 0x1234u,
position: new Vector3(191f, 191f, 0f));
var buckets = new Dictionary<uint, List<RenderProjectionRecord>>();
WalkProductionWorldData.BucketOutdoorRecord(
in record,
[0xF07F0040u, 0xF0800001u, 0xF4180101u],
buckets,
renderCenterLbX: 0xF0,
renderCenterLbY: 0x7F);
Assert.Equal([0xF07F0040u, 0xF0800001u], buckets.Keys.Order());
Assert.All(buckets.Values, bucket => Assert.Equal(record, Assert.Single(bucket)));
}
[Fact]
public void BucketOutdoorRecord_UnregisteredEffectFallsBackToRootPositionCell()
{
RenderProjectionRecord record = Record(
id: 0x5678u,
position: new Vector3(193f, 25f, 0f));
var buckets = new Dictionary<uint, List<RenderProjectionRecord>>();
WalkProductionWorldData.BucketOutdoorRecord(
in record,
Array.Empty<uint>(),
buckets,
renderCenterLbX: 0xEF,
renderCenterLbY: 0x7F);
Assert.Equal([0xF07F0002u], buckets.Keys);
Assert.Equal(record, Assert.Single(buckets[0xF07F0002u]));
}
[Fact]
public void BucketIndoorRecord_InstallsCrossCellPartInBothInteriorCells()
{
RenderProjectionRecord record = Record(
id: 0x48A02035u,
position: new Vector3(55.25f, -46.47f, -3f)) with
{
Source = new RenderSourceMetadata() with
{
LocalEntityId = 0x48A02035u,
ParentCellId = 0x8A02015Fu,
},
};
var indoor = new Dictionary<uint, List<RenderProjectionRecord>>();
var outdoor = new Dictionary<uint, List<RenderProjectionRecord>>();
WalkProductionWorldData.BucketIndoorRecord(
in record,
[0x8A02015Fu, 0x8A02015Eu],
indoor,
outdoor);
Assert.Equal([0x8A02015Eu, 0x8A02015Fu], indoor.Keys.Order());
Assert.All(indoor.Values, bucket => Assert.Equal(record, Assert.Single(bucket)));
Assert.Empty(outdoor);
}
[Fact]
public void BucketIndoorRecord_CanCrossAnExitIntoLandscapeCell()
{
RenderProjectionRecord record = Record(
id: 0x48A02035u,
position: Vector3.Zero) with
{
Source = new RenderSourceMetadata() with
{
LocalEntityId = 0x48A02035u,
ParentCellId = 0x8A02015Fu,
},
};
var indoor = new Dictionary<uint, List<RenderProjectionRecord>>();
var outdoor = new Dictionary<uint, List<RenderProjectionRecord>>();
WalkProductionWorldData.BucketIndoorRecord(
in record,
[0x8A02015Fu, 0x8A020021u],
indoor,
outdoor);
Assert.Equal(record, Assert.Single(indoor[0x8A02015Fu]));
Assert.Equal(record, Assert.Single(outdoor[0x8A020021u]));
}
[Fact]
public void BucketDynamicRecord_InstallsMultipartPlayerInEveryCrossedInteriorCell()
{
RenderProjectionRecord record = Record(
id: 0x000F4243u,
position: new Vector3(58.81f, -49.42f, -0.85f)) with
{
Source = new RenderSourceMetadata() with
{
LocalEntityId = 0x000F4243u,
ParentCellId = 0x8A02015Eu,
},
};
var indoor = new Dictionary<uint, List<RenderProjectionRecord>>();
var outdoor = new Dictionary<uint, List<RenderProjectionRecord>>();
WalkProductionWorldData.BucketDynamicRecord(
in record,
[0x8A02015Eu, 0x8A02015Fu, 0x8A0201C1u],
indoor,
outdoor,
renderCenterLbX: 0x8A,
renderCenterLbY: 0x02);
Assert.Equal(
[0x8A02015Eu, 0x8A02015Fu, 0x8A0201C1u],
indoor.Keys.Order());
Assert.All(
indoor.Values,
bucket => Assert.Equal(record, Assert.Single(bucket)));
Assert.Empty(outdoor);
}
[Fact]
public void BucketDynamicRecord_UnregisteredInteriorEffectFallsBackToParentCell()
{
RenderProjectionRecord record = Record(
id: 0x00001234u,
position: Vector3.Zero) with
{
Source = new RenderSourceMetadata() with
{
LocalEntityId = 0x00001234u,
ParentCellId = 0x8A02015Fu,
},
};
var indoor = new Dictionary<uint, List<RenderProjectionRecord>>();
var outdoor = new Dictionary<uint, List<RenderProjectionRecord>>();
WalkProductionWorldData.BucketDynamicRecord(
in record,
Array.Empty<uint>(),
indoor,
outdoor,
renderCenterLbX: 0x8A,
renderCenterLbY: 0x02);
Assert.Equal(record, Assert.Single(indoor[0x8A02015Fu]));
Assert.Empty(outdoor);
}
[Fact]
public void ResolveDynamicRenderCells_EquippedDescendantInheritsRootCellArray()
{
RenderProjectionRecord wand = Record(
id: 0x800045EEu,
position: Vector3.Zero) with
{
Source = new RenderSourceMetadata() with
{
LocalEntityId = 0x800045EEu,
ParentCellId = 0xF4180104u,
},
EntityPayload = new RenderEntityPayload() with
{
CasterIdentity = RenderCasterIdentityKind.EquippedChild,
},
};
var owners = new Dictionary<uint, IReadOnlyList<uint>>
{
[0x5000000Au] = [0xF4180106u, 0xF4180104u],
};
var parents = new Dictionary<uint, uint>
{
[0x800045EEu] = 0x80000461u,
[0x80000461u] = 0x5000000Au,
};
IReadOnlyList<uint> cells =
WalkProductionWorldData.ResolveDynamicRenderCells(
in wand,
id => owners.TryGetValue(id, out IReadOnlyList<uint>? value)
? value
: Array.Empty<uint>(),
id => parents.TryGetValue(id, out uint value)
? value
: null);
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
{
Id = RenderProjectionId.FromRaw(id),
Transform = new RenderTransform(Matrix4x4.CreateTranslation(position)),
Source = new RenderSourceMetadata() with { LocalEntityId = id },
};
}