using System.Collections.Immutable; using System.Numerics; using AcDream.App.Rendering; using AcDream.App.Rendering.Wb; using AcDream.App.Streaming; using AcDream.Core.Terrain; using AcDream.Core.World; using DatReaderWriter.DBObjs; using Xunit; namespace AcDream.App.Tests.Streaming; public sealed class LandblockPresentationPipelineTests { [Fact] public void Loaded_PreservesPresentationSpatialPinReplayAndLiveRecoveryOrder() { const uint landblockId = 0x2020FFFFu; var calls = new List(); var meshes = new RecordingMeshAdapter(calls); var state = new GpuWorldState(new LandblockSpawnAdapter(meshes)); LandblockBuild build = BuildWithShell(landblockId); var pipeline = new LandblockPresentationPipeline( publishBeforeSpatialCommit: (accepted, _) => { Assert.Same(build, accepted); Assert.False(state.IsLoaded(landblockId)); calls.Add("presentation"); }, state, onLandblockLoaded: id => { Assert.Equal(landblockId, id); Assert.True(state.IsLoaded(id)); calls.Add("live-recovery"); }, ensureEnvCellMeshes: envCells => { Assert.Same(build.EnvCells, envCells); Assert.True(state.IsLoaded(landblockId)); calls.Add("mesh-replay-after-pin"); }); pipeline.PublishLoaded(new LandblockStreamResult.Loaded( landblockId, LandblockStreamTier.Near, build, EmptyMesh())); Assert.Equal( ["presentation", "pin", "mesh-replay-after-pin", "live-recovery"], calls); Assert.True(state.IsNearTier(landblockId)); } [Fact] public void Promoted_ExistingFar_MergesNearLayerBeforeReplayAndRecovery() { const uint landblockId = 0x3030FFFFu; var calls = new List(); var meshes = new RecordingMeshAdapter(calls); var state = new GpuWorldState(new LandblockSpawnAdapter(meshes)); state.AddLandblock( new LoadedLandblock( landblockId, new LandBlock(), Array.Empty(), PhysicsDatBundle.Empty), tier: LandblockStreamTier.Far); LandblockBuild build = BuildWithShell(landblockId, Entity(7)); var pipeline = new LandblockPresentationPipeline( publishBeforeSpatialCommit: (_, _) => calls.Add("presentation"), state, onLandblockLoaded: _ => { Assert.True(state.IsNearTier(landblockId)); Assert.Contains(state.Entities, entity => entity.Id == 7); calls.Add("live-recovery"); }, ensureEnvCellMeshes: _ => { Assert.True(state.IsNearTier(landblockId)); Assert.Contains(state.Entities, entity => entity.Id == 7); calls.Add("mesh-replay-after-pin"); }); pipeline.PublishPromoted(new LandblockStreamResult.Promoted( landblockId, build, EmptyMesh()), mergeIntoExistingLandblock: true); Assert.Equal( ["presentation", "pin", "mesh-replay-after-pin", "live-recovery"], calls); } [Fact] public void PublishAsFar_StripsNearPayloadAndSkipsEnvCellReplay() { const uint landblockId = 0x4040FFFFu; var state = new GpuWorldState(); var origin = new LandblockBuildOrigin(0x40, 0x41); LandblockBuild source = BuildWithShell(landblockId, Entity(9)) with { Origin = origin, }; LandblockBuild? published = null; int replayCount = 0; int recoveryCount = 0; var pipeline = new LandblockPresentationPipeline( publishBeforeSpatialCommit: (build, _) => published = build, state, onLandblockLoaded: _ => recoveryCount++, ensureEnvCellMeshes: _ => replayCount++); var accepted = new LandblockStreamResult.Loaded( landblockId, LandblockStreamTier.Near, source, EmptyMesh()); pipeline.PublishAsFar(accepted, source, accepted.MeshData); Assert.NotNull(published); Assert.Empty(published.Landblock.Entities); Assert.Same(PhysicsDatBundle.Empty, published.Landblock.PhysicsDats); Assert.Null(published.EnvCells); Assert.Equal(origin, published.Origin); Assert.True(state.IsLoaded(landblockId)); Assert.False(state.IsNearTier(landblockId)); Assert.Equal(0, replayCount); Assert.Equal(1, recoveryCount); } [Fact] public void EnvCellReplayFailure_ResumesSuffixWithoutReplayingCommittedPrefix() { const uint landblockId = 0x5151FFFFu; var calls = new List(); var state = new GpuWorldState(new LandblockSpawnAdapter( new RecordingMeshAdapter(calls))); bool failReplayOnce = true; var pipeline = new LandblockPresentationPipeline( publishBeforeSpatialCommit: (_, _) => calls.Add("presentation"), state, onLandblockLoaded: _ => calls.Add("live-recovery"), ensureEnvCellMeshes: _ => { calls.Add("mesh-replay"); if (failReplayOnce) { failReplayOnce = false; throw new InvalidOperationException("injected replay failure"); } }); LandblockBuild build = BuildWithShell(landblockId); var result = new LandblockStreamResult.Loaded( landblockId, LandblockStreamTier.Near, build, EmptyMesh()); Assert.Throws(() => pipeline.PublishLoaded(result)); Assert.True(state.IsNearTier(landblockId)); Assert.True(pipeline.HasPendingPublication(result)); pipeline.PublishLoaded(result); Assert.Equal( ["presentation", "pin", "mesh-replay", "mesh-replay", "live-recovery"], calls); Assert.False(pipeline.HasPendingPublication(result)); } [Fact] public void PromotionReplayFailure_RetainsOriginalSelfContainedModeAcrossRetry() { const uint landblockId = 0x5252FFFFu; var calls = new List(); var state = new GpuWorldState(new LandblockSpawnAdapter( new RecordingMeshAdapter(calls))); bool failReplayOnce = true; var pipeline = new LandblockPresentationPipeline( publishBeforeSpatialCommit: (_, _) => calls.Add("presentation"), state, onLandblockLoaded: _ => calls.Add("live-recovery"), ensureEnvCellMeshes: _ => { calls.Add("mesh-replay"); if (failReplayOnce) { failReplayOnce = false; throw new InvalidOperationException("injected replay failure"); } }); LandblockBuild build = BuildWithShell(landblockId, Entity(17)); var result = new LandblockStreamResult.Promoted( landblockId, build, EmptyMesh()); Assert.Throws(() => pipeline.PublishPromoted( result, mergeIntoExistingLandblock: false)); Assert.True(state.IsNearTier(landblockId)); // Current residency changed after the first attempt. The transaction // must retain the original self-contained publication command rather // than switching policy mid-retry. pipeline.PublishPromoted(result, mergeIntoExistingLandblock: true); Assert.Equal( ["presentation", "pin", "mesh-replay", "mesh-replay", "live-recovery"], calls); Assert.Single(state.Entities, entity => entity.Id == 17); } [Fact] public void SpatialPinFailure_ControllerRetriesExactCompletionWithoutReplayingPresentation() { const uint landblockId = 0x6060FFFFu; var meshes = new FailFirstPreparedPinAdapter(); var state = new GpuWorldState(new LandblockSpawnAdapter(meshes)); var outbox = new Queue(); LandblockBuild build = BuildWithShell(landblockId); outbox.Enqueue(new LandblockStreamResult.Loaded( landblockId, LandblockStreamTier.Near, build, EmptyMesh())); int presentationCalls = 0; int replayCalls = 0; int liveRecoveryCalls = 0; var controller = new StreamingController( enqueueLoad: (_, _) => { }, enqueueUnload: _ => { }, drainCompletions: max => Drain(outbox, max), applyTerrain: (_, _) => presentationCalls++, state, nearRadius: 0, farRadius: 0, onLandblockLoaded: _ => liveRecoveryCalls++, ensureEnvCellMeshes: _ => replayCalls++); Assert.Throws(() => controller.Tick(0x60, 0x60)); Assert.True(state.IsNearTier(landblockId)); Assert.Equal(1, presentationCalls); Assert.Equal(0, replayCalls); Assert.Equal(0, liveRecoveryCalls); controller.Tick(0x60, 0x60); Assert.Equal(1, presentationCalls); Assert.Equal(2, meshes.PinAttempts); Assert.Equal(1, replayCalls); Assert.Equal(1, liveRecoveryCalls); } [Fact] public void PromotionPinFailure_RetriesActivationWithoutAppendingEntitiesAgain() { const uint landblockId = 0x6161FFFFu; var meshes = new FailFirstPreparedPinAdapter(); var state = new GpuWorldState(new LandblockSpawnAdapter(meshes)); state.AddLandblock( new LoadedLandblock( landblockId, new LandBlock(), Array.Empty()), tier: LandblockStreamTier.Far); int presentationCalls = 0; var pipeline = new LandblockPresentationPipeline( publishBeforeSpatialCommit: (_, _) => presentationCalls++, state); WorldEntity promotedEntity = Entity(31); LandblockBuild build = BuildWithShell(landblockId, promotedEntity); var result = new LandblockStreamResult.Promoted( landblockId, build, EmptyMesh()); Assert.Throws(() => pipeline.PublishPromoted( result, mergeIntoExistingLandblock: true)); Assert.Single(state.Entities, entity => ReferenceEquals(entity, promotedEntity)); pipeline.PublishPromoted(result, mergeIntoExistingLandblock: true); Assert.Equal(1, presentationCalls); Assert.Equal(2, meshes.PinAttempts); Assert.Single(state.Entities, entity => ReferenceEquals(entity, promotedEntity)); Assert.False(pipeline.HasPendingPublication(result)); } [Fact] public void LoadedPinFailure_RetryPreservesPendingLiveProjectionMergedAtSpatialCommit() { const uint landblockId = 0x6262FFFFu; var meshes = new FailFirstPreparedPinAdapter(); var state = new GpuWorldState(new LandblockSpawnAdapter(meshes)); var pendingLive = new WorldEntity { Id = 77, ServerGuid = 0x80000077u, SourceGfxObjOrSetupId = 0x01000077u, Position = Vector3.Zero, Rotation = Quaternion.Identity, MeshRefs = [new MeshRef(0x01000077u, Matrix4x4.Identity)], }; state.PlaceLiveEntityProjection(0x62620100u, pendingLive); LandblockBuild build = BuildWithShell(landblockId, Entity(32)); var result = new LandblockStreamResult.Loaded( landblockId, LandblockStreamTier.Near, build, EmptyMesh()); var pipeline = new LandblockPresentationPipeline( static (_, _) => { }, state); Assert.Throws(() => pipeline.PublishLoaded(result)); Assert.Contains(state.Entities, entity => ReferenceEquals(entity, pendingLive)); Assert.Equal(0, state.PendingLiveEntityCount); pipeline.PublishLoaded(result); Assert.Contains(state.Entities, entity => ReferenceEquals(entity, pendingLive)); Assert.Single(state.Entities, entity => ReferenceEquals(entity, pendingLive)); Assert.Equal(0, state.PendingLiveEntityCount); Assert.Equal(2, meshes.PinAttempts); } [Fact] public void PriorityFailure_PreservesFailingResultAndUntouchedDrainedSuffix() { const uint priorityId = 0x7070FFFFu; const uint laterId = 0x7071FFFFu; const uint unloadId = 0x1010FFFFu; var meshes = new FailFirstPreparedPinAdapter(); var state = new GpuWorldState(new LandblockSpawnAdapter(meshes)); state.AddLandblock(new LoadedLandblock( unloadId, new LandBlock(), Array.Empty())); var outbox = new Queue( [ new LandblockStreamResult.Loaded( priorityId, LandblockStreamTier.Near, BuildWithShell(priorityId), EmptyMesh()), new LandblockStreamResult.Loaded( laterId, LandblockStreamTier.Near, BuildWithShell(laterId), EmptyMesh()), new LandblockStreamResult.Unloaded(unloadId), ]); var calls = new List(); var controller = new StreamingController( enqueueLoad: (_, _) => { }, enqueueUnload: _ => { }, drainCompletions: max => Drain(outbox, max), applyTerrain: (build, _) => calls.Add($"publish:{build.LandblockId:X8}"), state, nearRadius: 0, farRadius: 2, removeTerrain: id => calls.Add($"unload:{id:X8}")); controller.PriorityLandblockId = priorityId; Assert.Throws(() => controller.Tick(0x70, 0x70)); Assert.Empty(outbox); Assert.False(state.IsLoaded(laterId)); Assert.True(state.IsLoaded(unloadId)); controller.PriorityLandblockId = 0; controller.Tick(0x70, 0x70); Assert.True(state.IsLoaded(priorityId)); Assert.True(state.IsLoaded(laterId)); Assert.False(state.IsLoaded(unloadId)); Assert.Equal( [ $"publish:{priorityId:X8}", $"publish:{laterId:X8}", $"unload:{unloadId:X8}", ], calls); } [Fact] public void HardRecenter_ConvergesPendingSpatialPublicationBeforeRetiringIt() { const uint landblockId = 0x8080FFFFu; var meshes = new FailFirstPreparedPinAdapter(); var state = new GpuWorldState(new LandblockSpawnAdapter(meshes)); var outbox = new Queue(); var result = new LandblockStreamResult.Loaded( landblockId, LandblockStreamTier.Near, BuildWithShell(landblockId), EmptyMesh()); outbox.Enqueue(result); int presentationCalls = 0; int replayCalls = 0; int recoveryCalls = 0; var controller = new StreamingController( enqueueLoad: (_, _) => { }, enqueueUnload: _ => { }, drainCompletions: max => Drain(outbox, max), applyTerrain: (_, _) => presentationCalls++, state, nearRadius: 0, farRadius: 0, onLandblockLoaded: _ => recoveryCalls++, ensureEnvCellMeshes: _ => replayCalls++); Assert.Throws(() => controller.Tick(0x80, 0x80)); Assert.True(state.IsNearTier(landblockId)); controller.ForceReloadWindow(); Assert.False(state.IsLoaded(landblockId)); Assert.Equal(1, presentationCalls); Assert.Equal(1, replayCalls); Assert.Equal(1, recoveryCalls); Assert.Equal(2, meshes.PinAttempts); } [Fact] public void DesiredNearToFarChurn_ResumesOriginalPublicationThenDemotes() { const uint landblockId = 0x9090FFFFu; var calls = new List(); var state = new GpuWorldState(new LandblockSpawnAdapter( new RecordingMeshAdapter(calls))); var outbox = new Queue(); var result = new LandblockStreamResult.Loaded( landblockId, LandblockStreamTier.Near, BuildWithShell(landblockId, Entity(23)), EmptyMesh()); outbox.Enqueue(result); bool failReplayOnce = true; var controller = new StreamingController( enqueueLoad: (_, _) => { }, enqueueUnload: _ => { }, drainCompletions: max => Drain(outbox, max), applyTerrain: (_, _) => calls.Add("presentation"), state, nearRadius: 0, farRadius: 2, demoteNearLayer: _ => calls.Add("demote"), ensureEnvCellMeshes: _ => { calls.Add("mesh-replay"); if (failReplayOnce) { failReplayOnce = false; throw new InvalidOperationException("injected replay failure"); } }); Assert.Throws(() => controller.Tick(0x90, 0x90)); Assert.True(state.IsNearTier(landblockId)); controller.Tick(0x90, 0x93); Assert.True(state.IsLoaded(landblockId)); Assert.False(state.IsNearTier(landblockId)); Assert.DoesNotContain(state.Entities, entity => entity.Id == 23); Assert.Equal(1, calls.Count(call => call == "presentation")); Assert.Equal(2, calls.Count(call => call == "mesh-replay")); Assert.Contains("demote", calls); } [Fact] public void CoarsePresentationFailure_IsFailFastAndNotRetainedForAutomaticReplay() { const uint landblockId = 0xA0A0FFFFu; var state = new GpuWorldState(); int presentationCalls = 0; var pipeline = new LandblockPresentationPipeline( publishBeforeSpatialCommit: (_, _) => { presentationCalls++; throw new StreamingMutationException( "injected committed coarse failure", mutationCommitted: true); }, state); var result = new LandblockStreamResult.Loaded( landblockId, LandblockStreamTier.Near, BuildWithShell(landblockId), EmptyMesh()); Assert.Throws(() => pipeline.PublishLoaded(result)); Assert.Equal(1, presentationCalls); Assert.False(pipeline.HasPendingPublication(result)); Assert.Equal(0, pipeline.PendingPublicationCount); Assert.False(state.IsLoaded(landblockId)); } [Fact] public void DeferredCommittedPresentationFailure_DropsOnlyFailedResultAndPreservesTail() { const uint failedId = 0xB0B0FFFFu; const uint tailId = 0xB0B1FFFFu; var outbox = new Queue( [ new LandblockStreamResult.Loaded( failedId, LandblockStreamTier.Near, new LandblockBuild(new LoadedLandblock( failedId, new LandBlock(), Array.Empty())), EmptyMesh()), new LandblockStreamResult.Loaded( tailId, LandblockStreamTier.Near, new LandblockBuild(new LoadedLandblock( tailId, new LandBlock(), Array.Empty())), EmptyMesh()), ]); int failedCalls = 0; int tailCalls = 0; var state = new GpuWorldState(); var controller = new StreamingController( enqueueLoad: (_, _) => { }, enqueueUnload: _ => { }, drainCompletions: max => Drain(outbox, max), applyTerrain: (build, _) => { if (build.LandblockId == failedId) { failedCalls++; throw new StreamingMutationException( "injected committed presentation failure", mutationCommitted: true); } tailCalls++; }, state, nearRadius: 0, farRadius: 2); Assert.Throws(() => controller.Tick(0xB0, 0xB0)); Assert.Equal(1, failedCalls); Assert.Equal(0, tailCalls); Assert.Equal(1, controller.DeferredApplyBacklog); controller.Tick(0xB0, 0xB0); Assert.Equal(1, failedCalls); Assert.Equal(1, tailCalls); Assert.False(state.IsLoaded(failedId)); Assert.True(state.IsLoaded(tailId)); Assert.Equal(0, controller.DeferredApplyBacklog); } [Fact] public void RetirementFacade_DetachesFirstAndReportsPendingLedger() { const uint landblockId = 0x5050FFFFu; var state = new GpuWorldState(); state.AddLandblock(new LoadedLandblock( landblockId, new LandBlock(), Array.Empty())); bool failOnce = true; var retirements = new LandblockRetirementCoordinator( state, ticket => { ticket.RunOnce( LandblockRetirementStage.EntityLighting, () => { if (failOnce) { failOnce = false; throw new InvalidOperationException("injected failure"); } }); }, _ => LandblockRetirementStage.EntityLighting); var pipeline = new LandblockPresentationPipeline( static (_, _) => { }, state, retirementCoordinator: retirements); pipeline.BeginFullRetirement(landblockId); Assert.False(state.IsLoaded(landblockId)); Assert.True(pipeline.IsRetirementPending(landblockId)); Assert.Equal(1, pipeline.PendingRetirementCount); pipeline.AdvanceRetirements(); Assert.False(pipeline.IsRetirementPending(landblockId)); Assert.Equal(0, pipeline.PendingRetirementCount); } [Fact] public void Pipeline_DoesNotOwnStreamingPolicyOrLiveIdentityMaps() { string[] forbiddenFragments = [ "generation", "desiredtier", "streamingregion", "serverguid", "liveentity", ]; string[] fields = typeof(LandblockPresentationPipeline) .GetFields(System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic) .Select(field => $"{field.Name}:{field.FieldType.FullName}") .ToArray(); foreach (string field in fields) foreach (string forbidden in forbiddenFragments) Assert.DoesNotContain(forbidden, field, StringComparison.OrdinalIgnoreCase); } private static LandblockBuild BuildWithShell( uint landblockId, params WorldEntity[] entities) { uint cellId = (landblockId & 0xFFFF0000u) | 0x0100u; var shell = new EnvCellShellPlacement( cellId, 0x2_0000_0001UL, 0x0D000001u, 1, ImmutableArray.Empty, Vector3.Zero, Quaternion.Identity, Matrix4x4.Identity, new WbBoundingBox(Vector3.Zero, Vector3.One), new WbBoundingBox(Vector3.Zero, Vector3.One)); var envCells = new EnvCellLandblockBuild( landblockId, Array.Empty(), [shell]); return new LandblockBuild( new LoadedLandblock( landblockId, new LandBlock(), entities, PhysicsDatBundle.Empty), envCells); } private static WorldEntity Entity(uint id) => new() { Id = id, SourceGfxObjOrSetupId = 0x01000000u + id, Position = Vector3.Zero, Rotation = Quaternion.Identity, MeshRefs = Array.Empty(), }; private static LandblockMeshData EmptyMesh() => new( Array.Empty(), Array.Empty()); private static IReadOnlyList Drain( Queue queue, int max) { var drained = new List(max); while (drained.Count < max && queue.TryDequeue(out LandblockStreamResult? result)) drained.Add(result); return drained; } private sealed class RecordingMeshAdapter(List calls) : IWbMeshAdapter { public void IncrementRefCount(ulong id) { } public void PinPreparedRenderData(ulong id) => calls.Add("pin"); public void DecrementRefCount(ulong id) { } } private sealed class FailFirstPreparedPinAdapter : IWbMeshAdapter { public int PinAttempts { get; private set; } public void IncrementRefCount(ulong id) { } public void PinPreparedRenderData(ulong id) { PinAttempts++; if (PinAttempts == 1) throw new InvalidOperationException("injected pin failure"); } public void DecrementRefCount(ulong id) { } } }