using AcDream.App.Rendering.Wb; using AcDream.App.Streaming; using AcDream.Core.Physics; using AcDream.Core.World; using AcDream.Runtime.World; using DatReaderWriter.DBObjs; namespace AcDream.App.Tests.Streaming; /// /// #280 end-to-end: the derived reveal window, the tiered render predicate, /// the terrain-residency gate, the destination reservation, and the loosened /// Runtime shape invariant, wired together with the REAL /// , , and /// rather than fakes. /// /// /// This is the test that fails if any single piece is missing: raising the /// radius without the tier-aware predicate hangs forever (T2), and loosening /// neither the Runtime invariant nor the predicate makes /// AcknowledgeDestinationReadiness fail /// invalid-readiness-shape (T1). /// /// public sealed class WorldRevealDerivedWindowIntegrationTests { private const int CenterX = 0x40; private const int CenterY = 0x40; private const uint DestinationCell = (uint)CenterX << 24 | (uint)CenterY << 16 | 0x0021u; [Theory] [InlineData(1, 2)] [InlineData(2, 4)] public void OutdoorReveal_HoldsUntilTheWholeDerivedWindowIsPublished( int nearRadius, int farRadius) { GpuWorldState world = CreateWorld(); var physics = new PhysicsEngine(); var transit = new RuntimeWorldTransitState(); var streaming = new RecordingReservations(); StreamingController controller = CreateController( world, nearRadius, farRadius); WorldRevealCoordinator coordinator = CreateCoordinator( transit, controller, physics, streaming); long generation = coordinator.BeginLogin(DestinationCell); // The reservation opens on the same square the gate measures. Assert.Equal( (generation, DestinationCell, farRadius), Assert.Single(streaming.Begins)); // Publish the Near window only. Pre-#280 this was the entire gate; it // must no longer be enough. for (int radius = 0; radius <= nearRadius; radius++) PublishRing(world, physics, radius, LandblockStreamTier.Near); Assert.False(coordinator.Evaluate(DestinationCell).IsReady); // Fill the outer rings with Far-tier terrain publication, one ring at // a time, so the hold is proven to track the outer boundary and not // just the first missing member. for (int radius = nearRadius + 1; radius < farRadius; radius++) { PublishRing(world, physics, radius, LandblockStreamTier.Far); Assert.False(coordinator.Evaluate(DestinationCell).IsReady); } PublishRing(world, physics, farRadius, LandblockStreamTier.Far); WorldRevealReadinessSnapshot ready = coordinator.Evaluate(DestinationCell); Assert.True(ready.IsReady); Assert.Equal(farRadius, ready.RequiredRenderRadius); Assert.Equal(nearRadius, ready.RequiredNearRadius); // The loosened Runtime shape invariant accepted the derived radius. Assert.Equal(0, transit.Snapshot.InvariantFailureCount); Assert.True(transit.Snapshot.IsReady); } /// /// P7/§9-8: login and portal share the barrier, so first login gets the /// same widened gate. Asserted through the production readiness predicate /// the login auto-entry context calls. /// [Fact] public void LoginReveal_UsesTheSameWidenedGateAsPortalArrival() { const int nearRadius = 1; const int farRadius = 3; GpuWorldState world = CreateWorld(); var physics = new PhysicsEngine(); var transit = new RuntimeWorldTransitState(); StreamingController controller = CreateController( world, nearRadius, farRadius); WorldRevealCoordinator coordinator = CreateCoordinator( transit, controller, physics, streaming: null); coordinator.BeginLogin(DestinationCell); for (int radius = 0; radius < farRadius; radius++) { PublishRing( world, physics, radius, radius <= nearRadius ? LandblockStreamTier.Near : LandblockStreamTier.Far); } Assert.False(coordinator.Evaluate(DestinationCell).IsReady); PublishRing(world, physics, farRadius, LandblockStreamTier.Far); Assert.True(coordinator.Evaluate(DestinationCell).IsReady); Assert.Equal(0, transit.Snapshot.InvariantFailureCount); } /// /// Indoor destinations keep retail's EnvCell arm: no landscape ring at /// all, regardless of how wide the streaming window is. /// [Fact] public void IndoorReveal_IgnoresTheStreamingWindowEntirely() { const uint indoorCell = (uint)CenterX << 24 | (uint)CenterY << 16 | 0x0100u; GpuWorldState world = CreateWorld(); var physics = new PhysicsEngine(); var transit = new RuntimeWorldTransitState(); StreamingController controller = CreateController(world, 2, 6); WorldRevealCoordinator coordinator = CreateCoordinator( transit, controller, physics, streaming: null); coordinator.BeginLogin(indoorCell); Assert.False(coordinator.Evaluate(indoorCell).IsReady); // Only the destination's own landblock, at Near tier. PublishRing(world, physics, 0, LandblockStreamTier.Near); WorldRevealReadinessSnapshot snapshot = coordinator.Evaluate(indoorCell); Assert.True(snapshot.IsRenderNeighborhoodReady); Assert.Equal(0, snapshot.RequiredRenderRadius); Assert.Equal(0, transit.Snapshot.InvariantFailureCount); } /// /// #280 D-1, end to end. The reveal gate opens on a fully published /// window, then a Near→Far demote lands on an outer-ring member — /// the ordinary outcome of a region recenter that does not move the world /// origin, and of a mid-hold quality-preset drop. Before the fix, the /// demoted member could never become IsRenderReady again, so the /// coordinator never returned IsReady and the client stayed in /// portal space until relog. /// [Fact] public void OutdoorReveal_SurvivesAnOuterRingDemoteDuringTheHold() { const int nearRadius = 1; const int farRadius = 3; var meshes = new RecordingMeshAdapter(); var world = new GpuWorldState(new LandblockSpawnAdapter(meshes)); var physics = new PhysicsEngine(); var transit = new RuntimeWorldTransitState(); var pipeline = new LandblockPresentationPipeline( publishBeforeSpatialCommit: (_, _) => { }, world); StreamingController controller = CreateController( world, nearRadius, farRadius); WorldRevealCoordinator coordinator = CreateCoordinator( transit, controller, physics, streaming: null); coordinator.BeginLogin(DestinationCell); // Publish the WHOLE window at Near tier, entities included. That is // what a region centred on the destination actually produces before it // starts trimming its trailing edge. for (int radius = 0; radius <= farRadius; radius++) PublishRing(world, physics, radius, LandblockStreamTier.Near, withEntity: true); Assert.True(coordinator.Evaluate(DestinationCell).IsReady); // A single outer-ring member demotes. Chebyshev 3: inside the far // window, outside the near ring. uint demoted = ((uint)(CenterX + farRadius) << 24) | ((uint)(CenterY - farRadius) << 16) | 0xFFFFu; pipeline.BeginNearLayerRetirement(demoted); Assert.True(world.IsLoaded(demoted)); Assert.False(world.IsNearTier(demoted)); Assert.True(coordinator.Evaluate(DestinationCell).IsReady); Assert.Equal(0, transit.Snapshot.InvariantFailureCount); } private sealed class RecordingMeshAdapter : IWbMeshAdapter { 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) => true; } private sealed class RecordingReservations : IWorldRevealStreamingScheduler { public List<(long Generation, uint Cell, int Radius)> Begins { get; } = []; public List Ends { get; } = []; public void BeginDestinationReservation( long revealGeneration, uint destinationCell, int requiredRenderRadius) => Begins.Add((revealGeneration, destinationCell, requiredRenderRadius)); public void EndDestinationReservation(long revealGeneration) => Ends.Add(revealGeneration); } private static WorldRevealCoordinator CreateCoordinator( RuntimeWorldTransitState transit, StreamingController controller, PhysicsEngine physics, IWorldRevealStreamingScheduler? streaming) => new( transit, () => new StreamingRevealWindow( controller.NearRadius, controller.FarRadius), controller.IsRenderNeighborhoodResident, physics.IsSpawnCellReady, physics.IsNeighborhoodTerrainResident, () => true, (_, _) => { }, () => { }, _ => false, streaming: streaming); private static StreamingController CreateController( GpuWorldState state, int nearRadius, int farRadius) => new( (_, _, _) => { }, (_, _) => { }, _ => Array.Empty(), (_, _) => { }, state, nearRadius: nearRadius, farRadius: farRadius); /// /// The gate's most load-bearing predicate is /// GpuWorldState.IsRenderReady, which degenerates to /// IsLoaded when no spawn adapter is wired. Every fixture here owns /// a real so the predicate is actually /// under test. /// private static GpuWorldState CreateWorld() => new(new LandblockSpawnAdapter(new RecordingMeshAdapter())); private static void PublishRing( GpuWorldState world, PhysicsEngine physics, int radius, LandblockStreamTier tier, bool withEntity = false) { for (int dx = -radius; dx <= radius; dx++) for (int dy = -radius; dy <= radius; dy++) { if (Math.Abs(dx) != radius && Math.Abs(dy) != radius) continue; uint id = ((uint)(CenterX + dx) << 24) | ((uint)(CenterY + dy) << 16) | 0xFFFFu; WorldEntity[] entities = withEntity ? [ 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), ], }, ] : Array.Empty(); world.AddLandblock( new LoadedLandblock(id, new LandBlock(), entities), tier: tier); // The Far tier publishes terrain COLLISION as well as terrain // render (LandblockPhysicsPublisher, reached for // PublicationKind.Far), which is what makes the outer arm of the // reveal gate satisfiable on the collision side too. physics.AddLandblock( id, new TerrainSurface(new byte[81], new float[256]), Array.Empty(), Array.Empty(), worldOffsetX: 0f, worldOffsetY: 0f); } } }