Chunk 5 (consumer cutover): WalkProductionWorldData's per-cell views are borrowed from ShadowObjectRegistry.GetRetailPartEntriesInCell and resolved through RenderSceneQuery.TryGetByLocalEntityId; every render-side sweep, bucket, parent-cell and root-position fallback is deleted (AD-116 for the one-frame registry→scene window, counted in UnregisteredRenderMembershipCount). A live entity with visual parts but no collision geometry registers render-only (LiveEntityCollisionBuilder computes the part array before the empty-shapes gate). Closeout fixes found while landing it: - RefloodOwnerForLandblock forwards the retained part array — a reflood is retail's recalc_cross_cells over the SAME CPartArray; without it every owner touched by a landblock replacement commit lost its render membership. - Non-colliding DAT statics register render-only from BOTH publishers (LandblockPhysicsPublisher.PublishStaticEntity, LandblockPhysicsContentBuilder.RegisterRenderOnlyStatic). The G2 self-gate pixel diff caught them vanishing (Facility Hub wall panels): retail floods every object regardless of collision (CEnvCell::init_static_objects 0x0052c350, add_shadows_to_cells 0x00514ae0). - S2 dual review fix batch (arch + retail lens, lead-verified): Suspend clears the retail product (remove_shadows_from_cells 0x00511230 is one transaction); AttachChild/DetachChild advance the mutation revision so a prepared SetPosition cannot clobber a child's rows; an attached child never floods on its own re-registration; RemoveLandblock and the non-rooted RetireOwnerFromLandblock prune retail rows (render-only statics end with their landblock); a render-only owner's no-cell-array commit republishes at its destination cell (AD-117); an empty non-null part array is treated as null; per-move closures/LINQ replaced by index loops; EnvCell shells stay out of the scene's LocalEntityId index (payload-less records); the index predicate compares the id; the dead per-cell scene indices are deleted. Register: AD-116 (chunk 5), AD-117 (four residual Contract A/B readings). Evidence: s2-membership-ownership-map.md §8 (chunk 5) and §9 (closeout). Gates (Release): Core 4,984/4,984; Content 214/214; Runtime 1,884/1,884; App hermetic lane 6,760/6,760; App InstalledDat lane 217 pass / 1 skip / 2 pre-existing #383 layout-fixture failures; App Windows lane 1/1. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
488 lines
21 KiB
C#
488 lines
21 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);
|
|
}
|
|
|
|
[Fact]
|
|
public void StaticBucketContains_FindsARegisteredSourceIdInItsRetailCell()
|
|
{
|
|
const uint entityId = 0x48A02040u;
|
|
const uint sourceId = 0x020009A2u;
|
|
const uint retailCellId = 0xF4180112u;
|
|
|
|
var shadows = new ShadowObjectRegistry();
|
|
Register(shadows, entityId, retailCellId, landblockId: 0xF4180000u);
|
|
|
|
RenderSceneGeneration generation = RenderSceneGeneration.FromRaw(1);
|
|
using var scene = new ArchRenderScene(generation);
|
|
RenderProjectionRecord projection =
|
|
IndoorStaticRecord(entityId, sourceId, retailCellId);
|
|
scene.Apply([RenderProjectionDelta.Register(generation, 1, projection)]);
|
|
|
|
var worldData = new WalkProductionWorldData(new WalkBuildingRegistry(), shadows);
|
|
worldData.BeginFrame(
|
|
scene.OpenQuery(), 0xF4180000u, renderCenterLbX: 0xF4, renderCenterLbY: 0x18);
|
|
|
|
Assert.True(worldData.StaticBucketContains(retailCellId, sourceId));
|
|
Assert.False(worldData.StaticBucketContains(retailCellId, sourceId + 1));
|
|
Assert.False(worldData.StaticBucketContains(0xF4180107u, sourceId));
|
|
}
|
|
|
|
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 },
|
|
};
|
|
}
|