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);
}
}
}