using AcDream.App.Streaming; using AcDream.App.Rendering; using AcDream.App.Rendering.Wb; using AcDream.Core.World; using DatReaderWriter.DBObjs; using System.Collections.Immutable; namespace AcDream.App.Tests.Streaming; public sealed class StreamingControllerReadinessTests { [Fact] public void RenderNeighborhoodResident_RequiresEveryPublishedLandblockInRing() { var state = new GpuWorldState(); StreamingController controller = CreateController(state); for (int dx = -1; dx <= 1; dx++) for (int dy = -1; dy <= 1; dy++) { if (dx == 1 && dy == 1) continue; AddPublished(state, 0x12 + dx, 0x36 + dy); } Assert.False(controller.IsRenderNeighborhoodResident(0x12360022u, 1, 1)); AddPublished(state, 0x13, 0x37); Assert.True(controller.IsRenderNeighborhoodResident(0x12360022u, 1, 1)); } [Fact] public void RenderNeighborhoodResident_RadiusZeroAcceptsEnvCellId() { var state = new GpuWorldState(); StreamingController controller = CreateController(state); AddPublished(state, 0x8C, 0x04); Assert.True(controller.IsRenderNeighborhoodResident(0x8C0401ADu, 0, 0)); Assert.False(controller.IsRenderNeighborhoodResident(0x8D0401ADu, 0, 0)); } [Fact] public void RenderNeighborhoodResident_SkipsOffMapNeighborsLikePhysicsGate() { var state = new GpuWorldState(); StreamingController controller = CreateController(state); AddPublished(state, 0, 0); AddPublished(state, 0, 1); AddPublished(state, 1, 0); AddPublished(state, 1, 1); Assert.True(controller.IsRenderNeighborhoodResident(0x00000001u, 1, 1)); } [Fact] public void RenderNeighborhoodResident_RejectsNegativeRadius() { StreamingController controller = CreateController(new GpuWorldState()); Assert.Throws( () => controller.IsRenderNeighborhoodResident(0x1236FFFFu, -1, -1)); } [Theory] [InlineData(0xFF0401ADu)] [InlineData(0x04FF01ADu)] [InlineData(0x12360000u)] [InlineData(0x12360041u)] [InlineData(0x1236FFFEu)] public void RenderNeighborhoodResident_RejectsInvalidMapEdgeDestination(uint cellId) { StreamingController controller = CreateController(new GpuWorldState()); Assert.False(controller.IsRenderNeighborhoodResident(cellId, 0, 0)); } [Fact] public void RenderNeighborhoodResident_WaitsForActualMeshUpload() { var meshes = new ReadinessMeshAdapter(); var state = new GpuWorldState(new LandblockSpawnAdapter(meshes)); StreamingController controller = CreateController(state); uint id = 0x1236FFFFu; const ulong envCellGeometryId = 0x2_0000_1234ul; var entity = new WorldEntity { Id = 1, ServerGuid = 0, SourceGfxObjOrSetupId = 0x01000010u, Position = System.Numerics.Vector3.Zero, Rotation = System.Numerics.Quaternion.Identity, MeshRefs = new[] { new MeshRef(0x01000010u, System.Numerics.Matrix4x4.Identity) }, }; state.AddLandblock( new LoadedLandblock(id, new LandBlock(), new[] { entity }), new[] { envCellGeometryId }); Assert.False(controller.IsRenderNeighborhoodResident(id, 0, 0)); meshes.ReadyIds.Add(0x01000010ul); Assert.False(controller.IsRenderNeighborhoodResident(id, 0, 0)); meshes.ReadyIds.Add(envCellGeometryId); Assert.True(controller.IsRenderNeighborhoodResident(id, 0, 0)); } [Fact] public void RenderNeighborhoodResident_PreservesShellGateWhenPromotionPrecedesBaseLoad() { var meshes = new ReadinessMeshAdapter(); var state = new GpuWorldState(new LandblockSpawnAdapter(meshes)); StreamingController controller = CreateController(state); uint id = 0x1236FFFFu; const ulong envCellGeometryId = 0x2_0000_1234ul; state.AddEntitiesToExistingLandblock( id, Array.Empty(), new[] { envCellGeometryId }); state.AddLandblock(new LoadedLandblock(id, new LandBlock(), Array.Empty())); Assert.True(state.IsNearTier(id)); Assert.False(controller.IsRenderNeighborhoodResident(id, 0, 0)); meshes.ReadyIds.Add(envCellGeometryId); Assert.True(controller.IsRenderNeighborhoodResident(id, 0, 0)); } [Fact] public void RenderNeighborhoodResident_FarTerrainDoesNotSatisfyPortalGate() { var meshes = new ReadinessMeshAdapter(); var state = new GpuWorldState(new LandblockSpawnAdapter(meshes)); StreamingController controller = CreateController(state); uint id = 0x1236FFFFu; const ulong envCellGeometryId = 0x2_0000_1234ul; state.AddLandblock( new LoadedLandblock(id, new LandBlock(), Array.Empty()), tier: LandblockStreamTier.Far); Assert.False(controller.IsRenderNeighborhoodResident(id, 0, 0)); state.AddEntitiesToExistingLandblock( id, Array.Empty(), new[] { envCellGeometryId }); Assert.False(controller.IsRenderNeighborhoodResident(id, 0, 0)); meshes.ReadyIds.Add(envCellGeometryId); Assert.True(controller.IsRenderNeighborhoodResident(id, 0, 0)); } [Fact] public void StaleFarPublication_DoesNotReplaceNearEntitiesOrReadiness() { var meshes = new ReadinessMeshAdapter(); var state = new GpuWorldState(new LandblockSpawnAdapter(meshes)); StreamingController controller = CreateController(state); uint id = 0x1236FFFFu; const ulong envCellGeometryId = 0x2_0000_1234ul; var entity = new WorldEntity { Id = 1, ServerGuid = 0, SourceGfxObjOrSetupId = 0x01000010u, Position = System.Numerics.Vector3.Zero, Rotation = System.Numerics.Quaternion.Identity, MeshRefs = [new MeshRef(0x01000010u, System.Numerics.Matrix4x4.Identity)], }; meshes.ReadyIds.UnionWith([0x01000010ul, envCellGeometryId]); state.AddLandblock( new LoadedLandblock(id, new LandBlock(), new[] { entity }), new[] { envCellGeometryId }, LandblockStreamTier.Near); Assert.True(controller.IsRenderNeighborhoodResident(id, 0, 0)); state.AddLandblock( new LoadedLandblock(id, new LandBlock(), Array.Empty()), tier: LandblockStreamTier.Far); Assert.True(state.IsNearTier(id)); Assert.Contains(entity, state.Entities); Assert.True(controller.IsRenderNeighborhoodResident(id, 0, 0)); } [Fact] public void EnvCellPublication_RearmsSpecializedPreparationAfterPin() { uint id = 0x1236FFFFu; const ulong geometryId = 0x2_0000_1234ul; var meshes = new ReadinessMeshAdapter(); var state = new GpuWorldState(new LandblockSpawnAdapter(meshes)); var outbox = new Queue(); int ensureCalls = 0; var shell = new EnvCellShellPlacement( 0x12360001u, geometryId, 0x0D000001u, 2, ImmutableArray.Create(3, 4), System.Numerics.Vector3.Zero, System.Numerics.Quaternion.Identity, System.Numerics.Matrix4x4.Identity, new WbBoundingBox(System.Numerics.Vector3.Zero, System.Numerics.Vector3.One), new WbBoundingBox(System.Numerics.Vector3.Zero, System.Numerics.Vector3.One)); var envCells = new EnvCellLandblockBuild( id, Array.Empty(), new[] { shell }); var build = new LandblockBuild( new LoadedLandblock(id, new LandBlock(), Array.Empty()), envCells); outbox.Enqueue(new LandblockStreamResult.Loaded( id, LandblockStreamTier.Near, build, new AcDream.Core.Terrain.LandblockMeshData( Array.Empty(), Array.Empty()))); var controller = new StreamingController( enqueueLoad: (_, _) => { }, enqueueUnload: _ => { }, drainCompletions: max => { var batch = new List(); while (batch.Count < max && outbox.Count > 0) batch.Add(outbox.Dequeue()); return batch; }, applyTerrain: (_, _) => { }, state: state, nearRadius: 0, farRadius: 0, ensureEnvCellMeshes: completed => { // The specialized request is replayed only after the synthetic // id is pinned, closing existing-at-schedule -> evicted-before- // publication without falling through generic GfxObj decode. Assert.Equal(1, meshes.ReferenceCounts[geometryId]); Assert.Equal(shell, Assert.Single(completed.Shells)); ensureCalls++; }); for (int frame = 0; frame < 32 && ensureCalls == 0; frame++) controller.Tick(0x12, 0x36); Assert.Equal(1, ensureCalls); } [Fact] public void PromotionWithoutBase_PublishesSelfContainedNearAndPinsBeforeReplay() { uint id = 0x1236FFFFu; const ulong geometryId = 0x2_0000_5678ul; var meshes = new ReadinessMeshAdapter(); var state = new GpuWorldState(new LandblockSpawnAdapter(meshes)); var outbox = new Queue(); int ensureCalls = 0; var shell = new EnvCellShellPlacement( 0x12360001u, geometryId, 0x0D000001u, 2, ImmutableArray.Create(3, 4), System.Numerics.Vector3.Zero, System.Numerics.Quaternion.Identity, System.Numerics.Matrix4x4.Identity, new WbBoundingBox(System.Numerics.Vector3.Zero, System.Numerics.Vector3.One), new WbBoundingBox(System.Numerics.Vector3.Zero, System.Numerics.Vector3.One)); var envCells = new EnvCellLandblockBuild( id, Array.Empty(), new[] { shell }); var nearBuild = new LandblockBuild( new LoadedLandblock(id, new LandBlock(), Array.Empty()), envCells); var mesh = new AcDream.Core.Terrain.LandblockMeshData( Array.Empty(), Array.Empty()); outbox.Enqueue(new LandblockStreamResult.Promoted(id, nearBuild, mesh)); var controller = new StreamingController( enqueueLoad: (_, _) => { }, enqueueUnload: _ => { }, drainCompletions: max => { var batch = new List(); while (batch.Count < max && outbox.Count > 0) batch.Add(outbox.Dequeue()); return batch; }, applyTerrain: (_, _) => { }, state: state, nearRadius: 0, farRadius: 0, ensureEnvCellMeshes: completed => { Assert.Equal(1, meshes.ReferenceCounts[geometryId]); Assert.Equal(shell, Assert.Single(completed.Shells)); ensureCalls++; }); for (int frame = 0; frame < 32 && ensureCalls == 0; frame++) controller.Tick(0x12, 0x36); Assert.Equal(1, ensureCalls); Assert.Equal(1, meshes.ReferenceCounts[geometryId]); Assert.True(state.IsNearTier(id)); // A Far job that had already started may still finish after the // self-contained promotion. It must not replace or replay the Near tier. var farBase = new LandblockBuild( new LoadedLandblock(id, new LandBlock(), Array.Empty())); outbox.Enqueue(new LandblockStreamResult.Loaded( id, LandblockStreamTier.Far, farBase, mesh)); controller.Tick(0x12, 0x36); Assert.Equal(1, ensureCalls); Assert.Equal(1, meshes.ReferenceCounts[geometryId]); Assert.True(state.IsNearTier(id)); } [Fact] public void PromotionBeforeBase_DemotedBeforeBase_DropsPendingNearPresentation() { uint id = 0x1236FFFFu; const ulong geometryId = 0x2_0000_9ABCul; var meshes = new ReadinessMeshAdapter(); var state = new GpuWorldState(new LandblockSpawnAdapter(meshes)); var outbox = new Queue(); int ensureCalls = 0; var appliedBuilds = new List(); var demotedAfterStaticDetach = new List(); var shell = new EnvCellShellPlacement( 0x12360001u, geometryId, 0x0D000001u, 2, ImmutableArray.Create(3, 4), System.Numerics.Vector3.Zero, System.Numerics.Quaternion.Identity, System.Numerics.Matrix4x4.Identity, new WbBoundingBox(System.Numerics.Vector3.Zero, System.Numerics.Vector3.One), new WbBoundingBox(System.Numerics.Vector3.Zero, System.Numerics.Vector3.One)); var envCells = new EnvCellLandblockBuild( id, Array.Empty(), new[] { shell }); var staticEntity = new WorldEntity { Id = 1, ServerGuid = 0, SourceGfxObjOrSetupId = 0x01000010u, Position = System.Numerics.Vector3.Zero, Rotation = System.Numerics.Quaternion.Identity, MeshRefs = [new MeshRef(0x01000010u, System.Numerics.Matrix4x4.Identity)], }; var nearBuild = new LandblockBuild( new LoadedLandblock(id, new LandBlock(), new[] { staticEntity }), envCells); var mesh = new AcDream.Core.Terrain.LandblockMeshData( Array.Empty(), Array.Empty()); outbox.Enqueue(new LandblockStreamResult.Promoted(id, nearBuild, mesh)); var controller = new StreamingController( enqueueLoad: (_, _) => { }, enqueueUnload: _ => { }, drainCompletions: max => { var batch = new List(); while (batch.Count < max && outbox.Count > 0) batch.Add(outbox.Dequeue()); return batch; }, applyTerrain: (build, _) => appliedBuilds.Add(build), state: state, nearRadius: 0, farRadius: 3, demoteNearLayer: demotedId => { Assert.DoesNotContain(staticEntity, state.Entities); demotedAfterStaticDetach.Add(demotedId); }, ensureEnvCellMeshes: _ => ensureCalls++); controller.Tick(0x12, 0x36); // self-contained promotion publishes Near. Assert.Single(appliedBuilds); Assert.NotNull(appliedBuilds[0].EnvCells); controller.Tick(0x12, 0x39); // normal Near->Far demotion retires presentation. var farBase = new LandblockBuild( new LoadedLandblock(id, new LandBlock(), Array.Empty())); outbox.Enqueue(new LandblockStreamResult.Loaded( id, LandblockStreamTier.Far, farBase, mesh)); controller.Tick(0x12, 0x39); // Slice E retains retirement as a cursor-budgeted transaction. A // generous frame can retire Near and resume Far immediately; a frame // that exhausts its elapsed budget resumes on the next Tick. Pin the // converged state rather than wall-clock-dependent frame count. for (int i = 0; i < 4 && appliedBuilds.Count < 2; i++) controller.Tick(0x12, 0x39); Assert.Equal(1, ensureCalls); Assert.DoesNotContain(geometryId, meshes.ReferenceCounts); Assert.DoesNotContain(staticEntity, state.Entities); Assert.Equal(new[] { id }, demotedAfterStaticDetach); Assert.True(state.IsLoaded(id)); Assert.False(state.IsNearTier(id)); Assert.Equal(2, appliedBuilds.Count); Assert.Null(appliedBuilds[1].EnvCells); Assert.Empty(appliedBuilds[1].Landblock.Entities); } // ── #280: the tiered reveal window ─────────────────────────────────── // // Retail loads, draws, and blocks on ONE square (LScape::mid_radius, // LScape::PreFetchCells @0x00505660). acdream's loaded landscape is // tiered, so the equivalent predicate is tiered: full publication inside // the Near ring, terrain publication out to the Far radius. Requiring Near // tier across the whole window is unsatisfiable — nothing outside the Near // ring is ever promoted — and holds the reveal forever. /// /// Case (a), the discriminating one: an INNER-ring member that only has /// its Far-tier terrain must still fail. If this passes while the near arm /// has been loosened into the far arm, the gate silently stops requiring /// scenery/statics at the destination itself. /// [Fact] public void TieredWindow_InnerRingFarTierMemberIsNotResident() { var state = new GpuWorldState(); StreamingController controller = CreateController(state); for (int dx = -2; dx <= 2; dx++) for (int dy = -2; dy <= 2; dy++) { AddPublished( state, 0x12 + dx, 0x36 + dy, dx == 1 && dy == 0 ? LandblockStreamTier.Far : LandblockStreamTier.Near); } Assert.False( controller.IsRenderNeighborhoodResident(0x12360022u, 1, 2)); } /// /// Case (b): an OUTER-ring member at Far tier satisfies the gate. This is /// the half that makes a derived Far-radius reveal window reachable at all. /// [Fact] public void TieredWindow_OuterRingFarTierMemberIsResident() { var state = new GpuWorldState(); StreamingController controller = CreateController(state); for (int dx = -2; dx <= 2; dx++) for (int dy = -2; dy <= 2; dy++) { bool inner = Math.Abs(dx) <= 1 && Math.Abs(dy) <= 1; AddPublished( state, 0x12 + dx, 0x36 + dy, inner ? LandblockStreamTier.Near : LandblockStreamTier.Far); } Assert.True( controller.IsRenderNeighborhoodResident(0x12360022u, 1, 2)); } /// /// Case (c): an absent outer-ring member still fails. The outer arm is a /// real publication test, not a rubber stamp. /// [Fact] public void TieredWindow_AbsentOuterRingMemberIsNotResident() { var state = new GpuWorldState(); StreamingController controller = CreateController(state); for (int dx = -2; dx <= 2; dx++) for (int dy = -2; dy <= 2; dy++) { if (dx == 2 && dy == -2) continue; bool inner = Math.Abs(dx) <= 1 && Math.Abs(dy) <= 1; AddPublished( state, 0x12 + dx, 0x36 + dy, inner ? LandblockStreamTier.Near : LandblockStreamTier.Far); } Assert.False( controller.IsRenderNeighborhoodResident(0x12360022u, 1, 2)); AddPublished(state, 0x14, 0x34, LandblockStreamTier.Far); Assert.True( controller.IsRenderNeighborhoodResident(0x12360022u, 1, 2)); } /// /// A far radius below the near radius is a caller bug, not something to /// clamp silently inside the predicate — the barrier clamps before it gets /// here. /// [Fact] public void TieredWindow_RejectsAFarRadiusBelowTheNearRadius() { StreamingController controller = CreateController(new GpuWorldState()); Assert.Throws( () => controller.IsRenderNeighborhoodResident(0x1236FFFFu, 3, 2)); } /// /// Retail parity (LScape::PreFetchCells' >= 0x7F8 bounds skip): a /// map-corner destination converges because the out-of-bounds members are /// skipped, not required. Exercised at a WIDE window, which is where the /// derived radius now puts the gate. /// [Fact] public void TieredWindow_MapCornerDestinationConvergesAtAWideRadius() { var state = new GpuWorldState(); StreamingController controller = CreateController(state); for (int x = 0; x <= 4; x++) for (int y = 0; y <= 4; y++) { bool inner = x <= 2 && y <= 2; AddPublished( state, x, y, inner ? LandblockStreamTier.Near : LandblockStreamTier.Far); } Assert.True( controller.IsRenderNeighborhoodResident(0x00000001u, 2, 4)); } /// /// #280 proof obligation P1 — the whole fix rests on this. A Far-tier /// publication must satisfy ; if /// it did not, the tiered gate's outer arm could never be satisfied and /// the reveal would hang. Driven through the real presentation pipeline's /// PublicationKind.Far path against a real /// . /// [Fact] public void FarPublication_IsRenderReadyThroughTheRealPipeline() { const uint landblockId = 0x1236FFFFu; var meshes = new ReadinessMeshAdapter(); var state = new GpuWorldState(new LandblockSpawnAdapter(meshes)); var pipeline = new LandblockPresentationPipeline( publishBeforeSpatialCommit: (_, _) => { }, state); // A Near-shaped completion that the streaming window has since demoted // to Far: entities and physics payload are stripped by PublishAsFar, // which is exactly the shape an outer-ring landblock is published in. var entity = new WorldEntity { Id = 1, ServerGuid = 0, SourceGfxObjOrSetupId = 0x01000010u, Position = System.Numerics.Vector3.Zero, Rotation = System.Numerics.Quaternion.Identity, MeshRefs = [new MeshRef(0x01000010u, System.Numerics.Matrix4x4.Identity)], }; var source = new LandblockBuild( new LoadedLandblock(landblockId, new LandBlock(), new[] { entity })); var mesh = new AcDream.Core.Terrain.LandblockMeshData( Array.Empty(), Array.Empty()); var accepted = new LandblockStreamResult.Loaded( landblockId, LandblockStreamTier.Near, source, mesh); pipeline.PublishAsFar(accepted, source, mesh); Assert.True(state.IsLoaded(landblockId)); Assert.False(state.IsNearTier(landblockId)); // The Far registration carries an EMPTY desired mesh set, so it is // render-ready without any IWbMeshAdapter upload ever completing — // note ReadinessMeshAdapter.ReadyIds is untouched below. Assert.Empty(meshes.ReadyIds); Assert.True(state.IsRenderReady(landblockId)); // ...and the tier check is therefore the SOLE thing keeping it out of // the inner arm, which is what makes D2's split meaningful rather than // decorative. StreamingController controller = CreateController(state); Assert.False( controller.IsRenderNeighborhoodResident(landblockId, 0, 0)); } private static StreamingController CreateController(GpuWorldState state) => new( (_, _) => { }, _ => { }, _ => Array.Empty(), (_, _) => { }, state, nearRadius: 1, farRadius: 2); private static void AddPublished( GpuWorldState state, int x, int y, LandblockStreamTier tier = LandblockStreamTier.Near) { uint id = ((uint)x << 24) | ((uint)y << 16) | 0xFFFFu; state.AddLandblock( new LoadedLandblock(id, new LandBlock(), Array.Empty()), tier: tier); } private sealed class ReadinessMeshAdapter : IWbMeshAdapter { public HashSet ReadyIds { get; } = new(); public Dictionary ReferenceCounts { get; } = new(); public void IncrementRefCount(ulong id) => ReferenceCounts[id] = ReferenceCounts.GetValueOrDefault(id) + 1; public void DecrementRefCount(ulong id) { int next = ReferenceCounts.GetValueOrDefault(id) - 1; if (next <= 0) ReferenceCounts.Remove(id); else ReferenceCounts[id] = next; } public bool IsRenderDataReady(ulong id) => ReadyIds.Contains(id); } }