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>(); 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>(); WalkProductionWorldData.BucketOutdoorRecord( in record, Array.Empty(), 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>(); var outdoor = new Dictionary>(); 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>(); var outdoor = new Dictionary>(); 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>(); var outdoor = new Dictionary>(); 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>(); var outdoor = new Dictionary>(); WalkProductionWorldData.BucketDynamicRecord( in record, Array.Empty(), indoor, outdoor, renderCenterLbX: 0x8A, renderCenterLbY: 0x02); Assert.Equal(record, Assert.Single(indoor[0x8A02015Fu])); Assert.Empty(outdoor); } // Campaign OVERHAUL S2 chunk 4: ResolveDynamicRenderCells is now a direct // TryGetRetailCellArray read for EVERY dynamic record, children included // — ShadowObjectRegistry.AttachChild owns Contract B's child-inheritance // recursion at the registry, so the render-side parent-chain walk this // test used to exercise no longer exists. private static ShadowShape ChildBsp(uint gfxObjId) => ShadowShape.Bsp( gfxObjId, Vector3.Zero, Quaternion.Identity, scale: 1f, localGeometry: ShadowPartGeometry.Create( new FlatCollisionSphere(Vector3.Zero, 1f), null)); [Fact] public void ResolveDynamicRenderCells_AttachedChildReadsTheRegistrysRetailCellArrayDirectly() { var shadows = new ShadowObjectRegistry(); const uint rootId = 0x5000000Au; const uint childId = 0x800045EEu; IReadOnlyList rootParts = new[] { ChildBsp(0x02000001u) }; shadows.RegisterMultiPart( rootId, new Vector3(12f, 12f, 50f), Quaternion.Identity, rootParts, state: 0u, flags: EntityCollisionFlags.None, worldOffsetX: 0f, worldOffsetY: 0f, landblockId: 0xF4180000u, seedCellId: 0xF4180104u, isStatic: false, partArray: rootParts); Assert.True(shadows.TryGetRetailCellArray(rootId, out IReadOnlyList rootCells)); Assert.NotEmpty(rootCells); // the fixture must actually exercise a flood // No render-side parent walk any more: Contract B's inheritance is // owned by the registry at attach time. Assert.True(shadows.AttachChild(childId, rootId, new[] { ChildBsp(0x02000002u) })); RenderProjectionRecord wand = Record( id: childId, position: Vector3.Zero) with { Source = new RenderSourceMetadata() with { LocalEntityId = childId, ParentCellId = 0xF4180104u, }, EntityPayload = new RenderEntityPayload() with { CasterIdentity = RenderCasterIdentityKind.EquippedChild, }, }; IReadOnlyList cells = WalkProductionWorldData.ResolveDynamicRenderCells( in wand, id => shadows.TryGetRetailCellArray(id, out IReadOnlyList c) ? (true, c) : (false, Array.Empty()), shadows.GetOwnerCells, out bool usedFallback); Assert.False(usedFallback); Assert.Equal(rootCells, cells); } [Fact] public void ResolveDynamicRenderCells_UnregisteredEntityFallsBackToOwnerCells() { var shadows = new ShadowObjectRegistry(); const uint entityId = 0x00099Fu; IReadOnlyList shapes = new[] { ChildBsp(0x02000003u) }; shadows.RegisterMultiPart( entityId, new Vector3(12f, 12f, 50f), Quaternion.Identity, shapes, state: 0u, flags: EntityCollisionFlags.None, worldOffsetX: 0f, worldOffsetY: 0f, landblockId: 0xF4180000u, seedCellId: 0xF4180104u, isStatic: false); // No partArray supplied above -> no retail cell array registered for // this entity (item E of the S2 chunk-1 contract), so the ordinary // collision-flood answer is the conservative fallback. Assert.False(shadows.TryGetRetailCellArray(entityId, out _)); Assert.NotEmpty(shadows.GetOwnerCells(entityId)); RenderProjectionRecord record = Record( id: entityId, position: Vector3.Zero) with { Source = new RenderSourceMetadata() with { LocalEntityId = entityId, ParentCellId = 0xF4180104u, }, }; IReadOnlyList cells = WalkProductionWorldData.ResolveDynamicRenderCells( in record, id => shadows.TryGetRetailCellArray(id, out IReadOnlyList c) ? (true, c) : (false, Array.Empty()), shadows.GetOwnerCells, out bool usedFallback); Assert.True(usedFallback); Assert.Equal(shadows.GetOwnerCells(entityId), 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 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 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 }, }; }