Delete the callerless portal-BFS research graph and spent renderer probe families while retaining the production RetailFrameWalk path, terrain diagnostics, membership invariant, and walk transcript. Mutation evidence (all restored): 1. Restored PortalVisibilityBuilder type -> AppAssembly_ContainsNoSupersededPortalGraphTypes first failed Assert.Empty with AcDream.App.Rendering.PortalVisibilityBuilder. 2. Restored ACDREAM_PROBE_FACILITY_STAIRS -> ProductionSource_ContainsNoDeletedRendererProbe_AndRetainsWalkTranscriptProof first failed Assert.Empty on RenderingDiagnostics.cs. 3. Added a second RetailFrameWalk field -> WalkFrameOwners_AreUnique first failed Assert.Single with _frameWalk and _mutatedSecondFrameWalk. 4. Added OrderBy to OrderedStream -> OrderedWalkStream_HasNoCrossStreamReorder first failed Assert.DoesNotContain on OrderBy(. 5. Added IDatReaderWriter parameter -> FrameTimeWalkOwners_HaveNoRawDatDependency first failed Assert.Empty on RetailFrameWalk.MutatedRawDatParameter.
463 lines
20 KiB
C#
463 lines
20 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;
|
|
using AcDream.Core.World;
|
|
|
|
namespace AcDream.App.Tests.Rendering.Walk;
|
|
|
|
/// <summary>
|
|
/// Campaign OVERHAUL S2 chunk 5: <see cref="WalkProductionWorldData"/> no
|
|
/// longer sweeps the scene to rebuild membership into bucket dictionaries —
|
|
/// <c>GetCellStatics</c>/<c>GetCellDynamics</c>/<c>GetOutdoorStatics</c>/
|
|
/// <c>GetOutdoorDynamics</c> are borrowed, on-demand views over
|
|
/// <see cref="ShadowObjectRegistry"/>'s retail per-cell part-entry product,
|
|
/// resolved back to a <see cref="RenderProjectionRecord"/> through
|
|
/// <see cref="RenderSceneQuery.TryGetByLocalEntityId"/>. Every test below
|
|
/// drives the REAL production path end to end — a real (bare, no-DAT)
|
|
/// <see cref="ShadowObjectRegistry"/> and a real <see cref="ArchRenderScene"/>
|
|
/// — rather than a hand-fed bucket dictionary, since the resolution itself IS
|
|
/// the thing under test now.
|
|
/// </summary>
|
|
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));
|
|
}
|
|
|
|
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,
|
|
},
|
|
// Every production entity record carries its MeshRefs
|
|
// (RenderProjectionRecordFactory); only an EnvCell SHELL has no
|
|
// payload, and ArchRenderScene keeps shells out of the
|
|
// LocalEntityId index on exactly that distinction.
|
|
EntityPayload = new RenderEntityPayload() with
|
|
{
|
|
MeshRefs = [new MeshRef(sourceId, Matrix4x4.Identity)],
|
|
},
|
|
};
|
|
|
|
private static RenderProjectionRecord DynamicRecord(
|
|
uint entityId, uint sourceId, uint parentCellId) =>
|
|
IndoorStaticRecord(entityId, sourceId, parentCellId) with
|
|
{
|
|
Id = RenderProjectionId.FromRaw(0x0100_0000_0000_0000u | entityId),
|
|
ProjectionClass = RenderProjectionClass.LiveDynamicRoot,
|
|
};
|
|
|
|
private static RenderProjectionRecord OutdoorStaticRecord(
|
|
uint entityId, uint sourceId, uint cellId, bool isBuildingShell = false) =>
|
|
new RenderProjectionRecord() with
|
|
{
|
|
Id = RenderProjectionId.FromRaw(0x0200_0000_0000_0000u | entityId),
|
|
ProjectionClass = RenderProjectionClass.OutdoorStatic,
|
|
OwnerIncarnation = RenderOwnerIncarnation.FromRaw(1),
|
|
Transform = new RenderTransform(Matrix4x4.Identity),
|
|
PreviousTransform = new PreviousRenderTransform(Matrix4x4.Identity),
|
|
Residency = new RenderSpatialResidency(
|
|
RenderSpatialBucket.FromRaw(cellId), cellId & 0xFFFF0000u, cellId),
|
|
Flags = RenderProjectionFlags.Draw,
|
|
Source = new RenderSourceMetadata() with
|
|
{
|
|
LocalEntityId = entityId,
|
|
SourceId = sourceId,
|
|
ParentCellId = cellId,
|
|
},
|
|
EntityPayload = new RenderEntityPayload() with
|
|
{
|
|
IsBuildingShell = isBuildingShell,
|
|
},
|
|
};
|
|
|
|
private static void Register(
|
|
ShadowObjectRegistry shadows,
|
|
uint entityId,
|
|
uint seedCellId,
|
|
uint landblockId)
|
|
{
|
|
IReadOnlyList<ShadowShape> parts = new[] { Bsp(0x01001234u) };
|
|
shadows.RegisterMultiPart(
|
|
entityId,
|
|
Vector3.Zero,
|
|
Quaternion.Identity,
|
|
parts,
|
|
state: 0u,
|
|
flags: EntityCollisionFlags.None,
|
|
worldOffsetX: 0f,
|
|
worldOffsetY: 0f,
|
|
landblockId: landblockId,
|
|
seedCellId: seedCellId,
|
|
isStatic: true,
|
|
partArray: parts);
|
|
}
|
|
|
|
[Fact]
|
|
public void GetCellStatics_RegisteredEntityIsResolvedByLocalEntityIdAtTheRetailCell()
|
|
{
|
|
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
|
|
// per-cell view actually consulted the registry rather than the
|
|
// authored parent (deleted alongside the sweep this test used to
|
|
// exercise).
|
|
const uint decoyParentCellId = 0x8A0201C1u;
|
|
|
|
var shadows = new ShadowObjectRegistry();
|
|
Register(shadows, entityId, retailCellId, landblockId: 0x8A020000u);
|
|
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.UnregisteredRenderMembershipCount);
|
|
}
|
|
|
|
[Fact]
|
|
public void GetCellStatics_RegistryAheadOfSceneContributesToNoCellAndCountsFallback()
|
|
{
|
|
const uint entityId = 0x48A02002u;
|
|
const uint retailCellId = 0x8A02015Fu;
|
|
|
|
// The registry HAS flooded this entity into its retail CELLARRAY —
|
|
// exactly the streaming-window race chunk 5 keeps as the ONE
|
|
// remaining fallback: the physics publisher (registry) ran before
|
|
// the presentation journal applied this frame's projected record.
|
|
var shadows = new ShadowObjectRegistry();
|
|
Register(shadows, entityId, retailCellId, landblockId: 0x8A020000u);
|
|
Assert.True(shadows.TryGetRetailCellArray(entityId, out _));
|
|
|
|
RenderSceneGeneration generation = RenderSceneGeneration.FromRaw(1);
|
|
using var scene = new ArchRenderScene(generation);
|
|
// Deliberately no scene.Apply — the projected record does not exist
|
|
// yet this frame.
|
|
|
|
var worldData = new WalkProductionWorldData(new WalkBuildingRegistry(), shadows);
|
|
worldData.BeginFrame(
|
|
scene.OpenQuery(), 0x8A020000u, renderCenterLbX: 0x8A, renderCenterLbY: 0x02);
|
|
|
|
Assert.Equal(0, worldData.GetCellStatics(retailCellId).Records.Count);
|
|
Assert.Equal(1, worldData.UnregisteredRenderMembershipCount);
|
|
}
|
|
|
|
[Fact]
|
|
public void GetCellStatics_UnregisteredEntityContributesToNoCellWithoutCounting()
|
|
{
|
|
// The OPPOSITE race: the scene has a projected record, but the
|
|
// registry never registered a retail CELLARRAY for this entity at
|
|
// all. Under the borrowed-view model this entity is never visited
|
|
// (nothing in the registry names it), so it correctly appears in NO
|
|
// cell and does not inflate the one remaining fallback counter —
|
|
// that counter only tracks entities the REGISTRY has flooded.
|
|
const uint entityId = 0x48A02003u;
|
|
const uint parentCellId = 0x8A02015Fu;
|
|
|
|
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: 0x02000003u, 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.Equal(0, worldData.GetCellStatics(parentCellId).Records.Count);
|
|
Assert.Equal(0, worldData.UnregisteredRenderMembershipCount);
|
|
}
|
|
|
|
[Fact]
|
|
public void UnregisteredRenderMembershipCount_CountsDistinctEntitiesNotCellVisits()
|
|
{
|
|
// One entity crossing SEVERAL cells — outdoor, since the bbox flood's
|
|
// fixed outdoor expansion reliably crosses cells even against a bare
|
|
// registry with no portal DAT data to cross INDOOR cells with (see
|
|
// GetOutdoorStatics_UsesTheSameRegistryDrivenViewAsIndoor) — all
|
|
// unresolved in the scene, must count once total, not once per cell
|
|
// the registry flooded it into.
|
|
const uint entityId = 0x87640002u;
|
|
const uint seedCellId = 0x87640030u;
|
|
|
|
IReadOnlyList<ShadowShape> parts = new[] { Bsp(0x01001234u) };
|
|
var shadows = new ShadowObjectRegistry();
|
|
shadows.RegisterMultiPart(
|
|
entityId,
|
|
Vector3.Zero,
|
|
Quaternion.Identity,
|
|
parts,
|
|
state: 0u,
|
|
flags: EntityCollisionFlags.None,
|
|
worldOffsetX: 0f,
|
|
worldOffsetY: 0f,
|
|
landblockId: 0x87640000u,
|
|
seedCellId: seedCellId,
|
|
isStatic: true,
|
|
partArray: parts);
|
|
Assert.True(shadows.TryGetRetailCellArray(entityId, out IReadOnlyList<uint> cells));
|
|
Assert.True(cells.Count >= 2, "fixture must actually cross more than one cell");
|
|
|
|
RenderSceneGeneration generation = RenderSceneGeneration.FromRaw(1);
|
|
using var scene = new ArchRenderScene(generation);
|
|
|
|
var worldData = new WalkProductionWorldData(new WalkBuildingRegistry(), shadows);
|
|
worldData.BeginFrame(
|
|
scene.OpenQuery(), 0x87640000u, renderCenterLbX: 0x87, renderCenterLbY: 0x64);
|
|
|
|
foreach (uint cellId in cells)
|
|
Assert.Equal(0, worldData.GetOutdoorStatics(cellId).Records.Count);
|
|
|
|
Assert.Equal(1, worldData.UnregisteredRenderMembershipCount);
|
|
}
|
|
|
|
[Fact]
|
|
public void GetCellDynamics_OnlyReturnsDynamicClassRecordsFromTheSameCell()
|
|
{
|
|
const uint staticEntityId = 0x48A02010u;
|
|
const uint dynamicEntityId = 0x48A02011u;
|
|
const uint sharedCellId = 0x8A02015Fu;
|
|
|
|
var shadows = new ShadowObjectRegistry();
|
|
Register(shadows, staticEntityId, sharedCellId, landblockId: 0x8A020000u);
|
|
Register(shadows, dynamicEntityId, sharedCellId, landblockId: 0x8A020000u);
|
|
|
|
RenderSceneGeneration generation = RenderSceneGeneration.FromRaw(1);
|
|
using var scene = new ArchRenderScene(generation);
|
|
RenderProjectionRecord staticProjection =
|
|
IndoorStaticRecord(staticEntityId, sourceId: 0x02000010u, sharedCellId);
|
|
RenderProjectionRecord dynamicProjection =
|
|
DynamicRecord(dynamicEntityId, sourceId: 0x02000011u, sharedCellId);
|
|
scene.Apply([
|
|
RenderProjectionDelta.Register(generation, 1, staticProjection),
|
|
RenderProjectionDelta.Register(generation, 2, dynamicProjection),
|
|
]);
|
|
|
|
var worldData = new WalkProductionWorldData(new WalkBuildingRegistry(), shadows);
|
|
worldData.BeginFrame(
|
|
scene.OpenQuery(), 0x8A020000u, renderCenterLbX: 0x8A, renderCenterLbY: 0x02);
|
|
|
|
Assert.Contains(
|
|
worldData.GetCellStatics(sharedCellId).Records,
|
|
record => record.Id == staticProjection.Id);
|
|
Assert.DoesNotContain(
|
|
worldData.GetCellStatics(sharedCellId).Records,
|
|
record => record.Id == dynamicProjection.Id);
|
|
|
|
Assert.Contains(
|
|
worldData.GetCellDynamics(sharedCellId).Records,
|
|
record => record.Id == dynamicProjection.Id);
|
|
Assert.DoesNotContain(
|
|
worldData.GetCellDynamics(sharedCellId).Records,
|
|
record => record.Id == staticProjection.Id);
|
|
}
|
|
|
|
[Fact]
|
|
public void GetCellStatics_ExcludesBuildingShellRecordsFromTheSameCell()
|
|
{
|
|
const uint shellEntityId = 0x48A02020u;
|
|
const uint ordinaryEntityId = 0x48A02021u;
|
|
const uint sharedCellId = 0x8A02015Fu;
|
|
|
|
var shadows = new ShadowObjectRegistry();
|
|
Register(shadows, shellEntityId, sharedCellId, landblockId: 0x8A020000u);
|
|
Register(shadows, ordinaryEntityId, sharedCellId, landblockId: 0x8A020000u);
|
|
|
|
RenderSceneGeneration generation = RenderSceneGeneration.FromRaw(1);
|
|
using var scene = new ArchRenderScene(generation);
|
|
RenderProjectionRecord shellProjection =
|
|
IndoorStaticRecord(shellEntityId, sourceId: 0x02000020u, sharedCellId) with
|
|
{
|
|
EntityPayload = new RenderEntityPayload() with { IsBuildingShell = true },
|
|
};
|
|
RenderProjectionRecord ordinaryProjection =
|
|
IndoorStaticRecord(ordinaryEntityId, sourceId: 0x02000021u, sharedCellId);
|
|
scene.Apply([
|
|
RenderProjectionDelta.Register(generation, 1, shellProjection),
|
|
RenderProjectionDelta.Register(generation, 2, ordinaryProjection),
|
|
]);
|
|
|
|
var worldData = new WalkProductionWorldData(new WalkBuildingRegistry(), shadows);
|
|
worldData.BeginFrame(
|
|
scene.OpenQuery(), 0x8A020000u, renderCenterLbX: 0x8A, renderCenterLbY: 0x02);
|
|
|
|
Assert.DoesNotContain(
|
|
worldData.GetCellStatics(sharedCellId).Records,
|
|
record => record.Id == shellProjection.Id);
|
|
Assert.Contains(
|
|
worldData.GetCellStatics(sharedCellId).Records,
|
|
record => record.Id == ordinaryProjection.Id);
|
|
}
|
|
|
|
[Fact]
|
|
public void GetOutdoorStatics_UsesTheSameRegistryDrivenViewAsIndoor()
|
|
{
|
|
const uint entityId = 0x87640001u;
|
|
const uint outdoorCellId = 0x87640030u;
|
|
|
|
var shadows = new ShadowObjectRegistry();
|
|
Register(shadows, entityId, outdoorCellId, landblockId: 0x87640000u);
|
|
Assert.True(shadows.TryGetRetailCellArray(entityId, out IReadOnlyList<uint> retailCells));
|
|
// The outdoor bbox flood's fixed expansion crosses more than the
|
|
// seed cell even for a small radius; only the seed cell's own
|
|
// membership matters for this test.
|
|
Assert.Contains(outdoorCellId, retailCells);
|
|
|
|
RenderSceneGeneration generation = RenderSceneGeneration.FromRaw(1);
|
|
using var scene = new ArchRenderScene(generation);
|
|
RenderProjectionRecord projection =
|
|
OutdoorStaticRecord(entityId, sourceId: 0x02000030u, outdoorCellId);
|
|
scene.Apply([RenderProjectionDelta.Register(generation, 1, projection)]);
|
|
|
|
var worldData = new WalkProductionWorldData(new WalkBuildingRegistry(), shadows);
|
|
worldData.BeginFrame(
|
|
scene.OpenQuery(), 0x87640000u, renderCenterLbX: 0x87, renderCenterLbY: 0x64);
|
|
|
|
Assert.Contains(
|
|
worldData.GetOutdoorStatics(outdoorCellId).Records,
|
|
record => record.Id == projection.Id);
|
|
}
|
|
|
|
[Fact]
|
|
public void GetCellStatics_CachesTheResultForTheRestOfTheFrame()
|
|
{
|
|
const uint entityId = 0x48A02030u;
|
|
const uint retailCellId = 0x8A02015Fu;
|
|
|
|
var shadows = new ShadowObjectRegistry();
|
|
Register(shadows, entityId, retailCellId, landblockId: 0x8A020000u);
|
|
|
|
RenderSceneGeneration generation = RenderSceneGeneration.FromRaw(1);
|
|
using var scene = new ArchRenderScene(generation);
|
|
RenderProjectionRecord projection =
|
|
IndoorStaticRecord(entityId, sourceId: 0x02000030u, retailCellId);
|
|
scene.Apply([RenderProjectionDelta.Register(generation, 1, projection)]);
|
|
|
|
var worldData = new WalkProductionWorldData(new WalkBuildingRegistry(), shadows);
|
|
worldData.BeginFrame(
|
|
scene.OpenQuery(), 0x8A020000u, renderCenterLbX: 0x8A, renderCenterLbY: 0x02);
|
|
|
|
WalkFrameStaticRecords first = worldData.GetCellStatics(retailCellId);
|
|
WalkFrameStaticRecords second = worldData.GetCellStatics(retailCellId);
|
|
|
|
Assert.Equal(first.Records.Array, second.Records.Array);
|
|
Assert.Equal(first.Records.Offset, second.Records.Offset);
|
|
Assert.Equal(first.Records.Count, second.Records.Count);
|
|
}
|
|
|
|
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 },
|
|
};
|
|
}
|