acdream/tests/AcDream.App.Tests/Streaming/StreamingControllerReadinessTests.cs
Erik bcb66ccdf3 fix(test): cover the atlas-tier seam the D-1 fix depends on; correct AP-150's citation
Both items come from the D-1 fix review (both lenses PASS, D-1 genuinely
closed). No production behaviour changes.

L1 — THE SEAM HAD NO COVERAGE. The D-1 fix's "empty by construction" claim
rests on LandblockSpawnAdapter's atlas-tier filter (`if (entity.ServerGuid
!= 0) continue;`) skipping the live server projections DetachNearLayer
deliberately RETAINS across a demote. The reviewer removed that filter and
all 4,170 App tests passed — only two Core unit tests caught it, none
through a demote. So the invariant the re-assert depends on could have been
deleted silently, re-opening D-1 by another route: a non-empty re-assert
whose mesh reference is never satisfied leaves IsRenderReady false, which is
the portal hang again.

NearToFarDemote_WithALiveServerEntity_StaysRenderReady now demotes a
landblock that CARRIES a live server-spawned entity through the real
GpuWorldState + LandblockSpawnAdapter + LandblockPresentationPipeline, and
asserts the retained entity never enters the desired set.

Sabotage-verified: with the filter removed, exactly one test fails — this
one — and the other 25 pass, including all four D-1 regression tests. That
is the finding restated as a measurement: the D-1 tests genuinely do not
cover this seam, and now something does.

AP-150 citation corrected: the row cited 0x004D7064 as the
ECM_UI::SendNotice_DisplayStringInfo call site. That address is the
PStringBase construction of the "In Portal Space - Please Wait..." literal
(:219516); the actual call is 0x004D70A1 (-> 0x006925B0). Same class of slip
the #280 commit had just corrected for #326 — worth noting that a row filed
WITH a byte-level disassembly still mis-cited a neighbouring address.

Also refactored the existing pipeline demote test to keep its doc comment
attached to its own method (an earlier insertion had orphaned its [Fact]).

App.Tests 4,170 -> 4,171 passed / 3 skipped, net +1 for the new test. No new
skips; none of #302/#308/#321 surfaced. src/ is byte-unchanged (the sabotage
was reverted and verified).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-06 07:54:03 +02:00

898 lines
36 KiB
C#

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<ArgumentOutOfRangeException>(
() => 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<WorldEntity>(),
new[] { envCellGeometryId });
state.AddLandblock(new LoadedLandblock(id, new LandBlock(), Array.Empty<WorldEntity>()));
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<WorldEntity>()),
tier: LandblockStreamTier.Far);
Assert.False(controller.IsRenderNeighborhoodResident(id, 0, 0));
state.AddEntitiesToExistingLandblock(
id,
Array.Empty<WorldEntity>(),
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<WorldEntity>()),
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<LandblockStreamResult>();
int ensureCalls = 0;
var shell = new EnvCellShellPlacement(
0x12360001u,
geometryId,
0x0D000001u,
2,
ImmutableArray.Create<ushort>(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<LoadedCell>(),
new[] { shell });
var build = new LandblockBuild(
new LoadedLandblock(id, new LandBlock(), Array.Empty<WorldEntity>()),
envCells);
outbox.Enqueue(new LandblockStreamResult.Loaded(
id,
LandblockStreamTier.Near,
build,
new AcDream.Core.Terrain.LandblockMeshData(
Array.Empty<AcDream.Core.Terrain.TerrainVertex>(),
Array.Empty<uint>())));
var controller = new StreamingController(
enqueueLoad: (_, _) => { },
enqueueUnload: _ => { },
drainCompletions: max =>
{
var batch = new List<LandblockStreamResult>();
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<LandblockStreamResult>();
int ensureCalls = 0;
var shell = new EnvCellShellPlacement(
0x12360001u,
geometryId,
0x0D000001u,
2,
ImmutableArray.Create<ushort>(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<LoadedCell>(),
new[] { shell });
var nearBuild = new LandblockBuild(
new LoadedLandblock(id, new LandBlock(), Array.Empty<WorldEntity>()),
envCells);
var mesh = new AcDream.Core.Terrain.LandblockMeshData(
Array.Empty<AcDream.Core.Terrain.TerrainVertex>(),
Array.Empty<uint>());
outbox.Enqueue(new LandblockStreamResult.Promoted(id, nearBuild, mesh));
var controller = new StreamingController(
enqueueLoad: (_, _) => { },
enqueueUnload: _ => { },
drainCompletions: max =>
{
var batch = new List<LandblockStreamResult>();
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<WorldEntity>()));
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<LandblockStreamResult>();
int ensureCalls = 0;
var appliedBuilds = new List<LandblockBuild>();
var demotedAfterStaticDetach = new List<uint>();
var shell = new EnvCellShellPlacement(
0x12360001u,
geometryId,
0x0D000001u,
2,
ImmutableArray.Create<ushort>(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<LoadedCell>(),
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<AcDream.Core.Terrain.TerrainVertex>(),
Array.Empty<uint>());
outbox.Enqueue(new LandblockStreamResult.Promoted(id, nearBuild, mesh));
var controller = new StreamingController(
enqueueLoad: (_, _) => { },
enqueueUnload: _ => { },
drainCompletions: max =>
{
var batch = new List<LandblockStreamResult>();
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<WorldEntity>()));
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.
/// <summary>
/// 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.
/// </summary>
[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));
}
/// <summary>
/// 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.
/// </summary>
[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));
}
/// <summary>
/// Case (c): an absent outer-ring member still fails. The outer arm is a
/// real publication test, not a rubber stamp.
/// </summary>
[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));
}
/// <summary>
/// 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.
/// </summary>
[Fact]
public void TieredWindow_RejectsAFarRadiusBelowTheNearRadius()
{
StreamingController controller = CreateController(new GpuWorldState());
Assert.Throws<ArgumentOutOfRangeException>(
() => controller.IsRenderNeighborhoodResident(0x1236FFFFu, 3, 2));
}
/// <summary>
/// Retail parity (LScape::PreFetchCells' <c>&gt;= 0x7F8</c> 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.
/// </summary>
[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));
}
/// <summary>
/// #280 proof obligation P1 — the whole fix rests on this. A Far-tier
/// publication must satisfy <see cref="GpuWorldState.IsRenderReady"/>; 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
/// <c>PublicationKind.Far</c> path against a real
/// <see cref="LandblockSpawnAdapter"/>.
/// </summary>
[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 the streaming window has DOWNGRADED before
// publication: entities and physics payload are stripped by
// PublishAsFar, which is exactly the shape an outer-ring landblock
// ARRIVES in. This is not the demote transition — a landblock that was
// already published Near and is later retired to Far takes a different
// route (LandblockPresentationPipeline.BeginNearLayerRetirement) and is
// covered separately below.
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<AcDream.Core.Terrain.TerrainVertex>(),
Array.Empty<uint>());
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));
}
/// <summary>
/// #280 D-1 regression, the OTHER way a landblock reaches Far tier. A
/// Near&#8594;Far demote retires the Near mesh layer while
/// <c>GpuWorldState.DetachNearLayer</c> keeps the landblock loaded,
/// terrain-resident and drawn. Before the fix the retirement's
/// MeshReferences stage left <c>WantsLoaded == false</c> forever — nothing
/// re-publishes an already-loaded landblock — so the demoted block
/// satisfied NEITHER arm of the widened reveal gate and the client hung in
/// portal space until relog.
///
/// <para>
/// Driven through the real <see cref="GpuWorldState"/>, the real
/// <see cref="LandblockSpawnAdapter"/> and the real
/// <see cref="LandblockPresentationPipeline"/>, which is the production
/// route <c>StreamingController.DemoteLandblock</c> takes.
/// </para>
/// </summary>
[Fact]
public void NearToFarDemote_LeavesTheLandblockRenderReadyThroughTheRealPipeline()
=> NearToFarDemoteLeavesTheLandblockRenderReadyCore();
/// <summary>
/// L1, filed at the D-1 fix review: the fix's "empty by construction"
/// claim rests on <c>LandblockSpawnAdapter</c>'s atlas-tier filter
/// (<c>if (entity.ServerGuid != 0) continue;</c>) skipping the live
/// server projections that <c>DetachNearLayer</c> deliberately RETAINS.
/// That seam had no composition-level coverage — the review removed the
/// filter and all 4,170 App tests still passed, with only two Core unit
/// tests catching it and none through a demote. So the invariant the
/// re-assert depends on could have been deleted silently.
///
/// <para>
/// Here the demoted landblock CARRIES a live server-spawned entity. If
/// the filter stops skipping it, the re-asserted registration is no
/// longer empty, its mesh reference is never satisfied, and
/// <c>IsRenderReady</c> goes false — which is D-1 again by another
/// route. Sabotage that reddens this: delete the `ServerGuid != 0`
/// continue in <c>LandblockSpawnAdapter.OnLandblockLoaded</c>.
/// </para>
/// </summary>
[Fact]
public void NearToFarDemote_WithALiveServerEntity_StaysRenderReady()
{
const uint landblockId = 0x1237FFFFu;
const ulong staticGfxObjId = 0x01000011ul;
const ulong serverGfxObjId = 0x01000012ul;
var meshes = new ReadinessMeshAdapter();
var state = new GpuWorldState(new LandblockSpawnAdapter(meshes));
var pipeline = new LandblockPresentationPipeline(
publishBeforeSpatialCommit: (_, _) => { },
state);
meshes.ReadyIds.Add(staticGfxObjId);
meshes.ReadyIds.Add(serverGfxObjId);
// An ordinary atlas-tier prop, and a server-spawned entity of the
// kind DetachNearLayer retains across the demote.
var staticEntity = new WorldEntity
{
Id = 1,
ServerGuid = 0,
SourceGfxObjOrSetupId = (uint)staticGfxObjId,
Position = System.Numerics.Vector3.Zero,
Rotation = System.Numerics.Quaternion.Identity,
MeshRefs = [new MeshRef((uint)staticGfxObjId, System.Numerics.Matrix4x4.Identity)],
};
var serverEntity = new WorldEntity
{
Id = 2,
ServerGuid = 0x50000123u,
SourceGfxObjOrSetupId = (uint)serverGfxObjId,
Position = System.Numerics.Vector3.Zero,
Rotation = System.Numerics.Quaternion.Identity,
MeshRefs = [new MeshRef((uint)serverGfxObjId, System.Numerics.Matrix4x4.Identity)],
};
state.AddLandblock(new LoadedLandblock(
landblockId,
new LandBlock(),
new[] { staticEntity, serverEntity }));
// Only the atlas-tier prop ever drove a reference count.
Assert.True(state.IsRenderReady(landblockId));
Assert.Equal(1, meshes.ReferenceCounts[staticGfxObjId]);
Assert.DoesNotContain(serverGfxObjId, meshes.ReferenceCounts.Keys);
pipeline.BeginNearLayerRetirement(landblockId);
Assert.False(state.IsNearTier(landblockId));
Assert.True(state.IsLoaded(landblockId));
// The retained server entity must not have re-entered the desired
// set: the re-assert stays empty and the block stays render-ready.
Assert.DoesNotContain(serverGfxObjId, meshes.ReferenceCounts.Keys);
Assert.DoesNotContain(staticGfxObjId, meshes.ReferenceCounts.Keys);
Assert.True(state.IsRenderReady(landblockId));
}
private static void NearToFarDemoteLeavesTheLandblockRenderReadyCore()
{
const uint landblockId = 0x1236FFFFu;
const ulong gfxObjId = 0x01000010ul;
var meshes = new ReadinessMeshAdapter();
var state = new GpuWorldState(new LandblockSpawnAdapter(meshes));
var pipeline = new LandblockPresentationPipeline(
publishBeforeSpatialCommit: (_, _) => { },
state);
meshes.ReadyIds.Add(gfxObjId);
var entity = new WorldEntity
{
Id = 1,
ServerGuid = 0,
SourceGfxObjOrSetupId = (uint)gfxObjId,
Position = System.Numerics.Vector3.Zero,
Rotation = System.Numerics.Quaternion.Identity,
MeshRefs = [new MeshRef((uint)gfxObjId, System.Numerics.Matrix4x4.Identity)],
};
state.AddLandblock(
new LoadedLandblock(landblockId, new LandBlock(), new[] { entity }));
Assert.True(state.IsNearTier(landblockId));
Assert.True(state.IsRenderReady(landblockId));
Assert.Equal(1, meshes.ReferenceCounts[gfxObjId]);
pipeline.BeginNearLayerRetirement(landblockId);
// The demote really happened: Near layer gone, mesh reference released.
Assert.False(state.IsNearTier(landblockId));
Assert.DoesNotContain(gfxObjId, meshes.ReferenceCounts.Keys);
// ...and the landblock is still loaded and still drawn.
Assert.True(state.IsLoaded(landblockId));
// The demoted block must now be indistinguishable from one that
// arrived as Far: registered, empty desired set, render-ready.
Assert.True(state.IsRenderReady(landblockId));
}
/// <summary>
/// #280 D-1, at the gate rather than at the predicate. A full Near window
/// satisfies the tiered gate; demoting one OUTER-ring member — which is
/// what an ordinary region recenter or a mid-hold quality drop does — must
/// not make the gate permanently unsatisfiable.
/// </summary>
[Fact]
public void TieredWindow_StaysResidentAfterAnOuterRingDemote()
{
var meshes = new ReadinessMeshAdapter();
var state = new GpuWorldState(new LandblockSpawnAdapter(meshes));
var pipeline = new LandblockPresentationPipeline(
publishBeforeSpatialCommit: (_, _) => { },
state);
StreamingController controller = CreateController(state);
for (int dx = -2; dx <= 2; dx++)
for (int dy = -2; dy <= 2; dy++)
AddPublished(state, 0x12 + dx, 0x36 + dy);
Assert.True(controller.IsRenderNeighborhoodResident(0x12360022u, 1, 2));
// Chebyshev distance 2 from the destination: inside the gate's far
// window, outside its near ring — the exact band a demote can land in.
pipeline.BeginNearLayerRetirement(0x1434FFFFu);
Assert.True(state.IsLoaded(0x1434FFFFu));
Assert.False(state.IsNearTier(0x1434FFFFu));
Assert.True(controller.IsRenderNeighborhoodResident(0x12360022u, 1, 2));
}
/// <summary>
/// The same transition through the METERED retirement coordinator
/// production actually composes (<c>CreateBudgeted</c>), whose
/// MeshReferences stage is a separate call site from the legacy pipeline's.
/// </summary>
[Fact]
public void NearToFarDemote_LeavesTheLandblockRenderReadyUnderBudgetedRetirement()
{
const uint landblockId = 0x1236FFFFu;
const ulong gfxObjId = 0x01000010ul;
var meshes = new ReadinessMeshAdapter();
var state = new GpuWorldState(new LandblockSpawnAdapter(meshes));
meshes.ReadyIds.Add(gfxObjId);
var entity = new WorldEntity
{
Id = 1,
ServerGuid = 0,
SourceGfxObjOrSetupId = (uint)gfxObjId,
Position = System.Numerics.Vector3.Zero,
Rotation = System.Numerics.Quaternion.Identity,
MeshRefs = [new MeshRef((uint)gfxObjId, System.Numerics.Matrix4x4.Identity)],
};
state.AddLandblock(
new LoadedLandblock(landblockId, new LandBlock(), new[] { entity }));
LandblockRetirementCoordinator coordinator =
LandblockRetirementCoordinator.CreateBudgeted(
state,
AdvanceNoopPresentationStep,
static ticket =>
{
while (AdvanceNoopPresentationStep(ticket)
!= LandblockRetirementOperationResult.NoWork)
{
}
});
coordinator.BeginNearLayer(landblockId);
int frames = 0;
while (coordinator.PendingCount != 0 && frames++ < 64)
{
var meter = new StreamingWorkMeter(new StreamingWorkBudget(
maxUpdateTime: TimeSpan.FromSeconds(1),
maxCompletionAdmissions: 64,
maxAdoptedCpuBytes: 64 * 1024 * 1024,
maxEntityOperations: 64,
maxGpuUploadBytes: 64 * 1024 * 1024,
maxGlRetireOperations: 64,
destinationReserveFraction: 0.75f));
coordinator.Advance(meter);
meter.FinishFrame();
}
Assert.Equal(0, coordinator.PendingCount);
Assert.True(state.IsLoaded(landblockId));
Assert.False(state.IsNearTier(landblockId));
Assert.DoesNotContain(gfxObjId, meshes.ReferenceCounts.Keys);
Assert.True(state.IsRenderReady(landblockId));
}
private static LandblockRetirementOperationResult AdvanceNoopPresentationStep(
LandblockRetirementTicket ticket) =>
ticket.NextIncompleteStage switch
{
LandblockRetirementStage.EntityLighting
or LandblockRetirementStage.EntityTranslucency =>
ticket.RunEntityStep(
ticket.NextIncompleteStage,
static _ => true,
static _ => { }),
LandblockRetirementStage.PluginProjection =>
ticket.RunEntityStep(
LandblockRetirementStage.PluginProjection,
static entity => entity.ServerGuid == 0,
static _ => { }),
LandblockRetirementStage.Terrain
or LandblockRetirementStage.Physics
or LandblockRetirementStage.CellVisibility
or LandblockRetirementStage.BuildingRegistry
or LandblockRetirementStage.EnvironmentCells =>
ticket.RunOnceStep(ticket.NextIncompleteStage, static () => { }),
_ => LandblockRetirementOperationResult.NoWork,
};
private static StreamingController CreateController(GpuWorldState state)
=> new(
(_, _) => { },
_ => { },
_ => Array.Empty<LandblockStreamResult>(),
(_, _) => { },
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<WorldEntity>()),
tier: tier);
}
private sealed class ReadinessMeshAdapter : IWbMeshAdapter
{
public HashSet<ulong> ReadyIds { get; } = new();
public Dictionary<ulong, int> 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);
}
}