feat(render): implement Campaign AR and terrain fidelity

This commit is contained in:
Erik 2026-08-22 13:13:29 +02:00
parent 99cf26e00c
commit 7a5f96ede5
368 changed files with 50611 additions and 950 deletions

View file

@ -163,11 +163,13 @@ public sealed class GpuWorldState : ILiveEntitySpatialQuery
private int _mutationDepth;
private long _visibilityCommitCount;
private ulong _flatViewGeneration;
private ulong _residentWindowRevision;
private bool _flatMembershipDirty;
public IReadOnlyList<WorldEntity> Entities => _flatEntities;
public ulong FlatViewGeneration => _flatViewGeneration;
public IReadOnlyCollection<uint> LoadedLandblockIds => _loaded.Keys;
internal ulong ResidentWindowRevision => _residentWindowRevision;
public event Action<RuntimeEntityKey, bool>? LiveProjectionVisibilityChanged;
public bool IsLoaded(uint landblockId) => _loaded.ContainsKey(landblockId);
@ -428,6 +430,7 @@ public sealed class GpuWorldState : ILiveEntitySpatialQuery
throw new InvalidOperationException(
$"Landblock 0x{landblockId:X8} already owns a render traversal slot.");
}
_residentWindowRevision = checked(_residentWindowRevision + 1);
InvalidateLandblockRenderViews(entries: true, bounds: true);
}
@ -449,10 +452,81 @@ public sealed class GpuWorldState : ILiveEntitySpatialQuery
_renderTraversalLandblockSlots[slot] = 0;
_freeRenderTraversalLandblockSlots.Push(slot);
_residentWindowRevision = checked(_residentWindowRevision + 1);
InvalidateLandblockRenderViews(entries: true, bounds: true);
return true;
}
/// <summary>
/// Captures the largest complete, actually-published square around the
/// current render centre. This is a presentation-only read: it neither
/// consults the desired <see cref="StreamingRegion"/> nor requests missing
/// landblocks. Hysteresis-retained cells outside a gap cannot inflate the
/// result, which is what makes it safe during recenter and portal turnover.
/// </summary>
internal ResidentStreamingWindowFact CaptureResidentStreamingWindow(
int centerX,
int centerY)
{
if ((uint)centerX > byte.MaxValue || (uint)centerY > byte.MaxValue)
return ResidentStreamingWindowFact.Unavailable(_residentWindowRevision);
uint center = StreamingRegion.EncodeLandblockId(centerX, centerY);
if (!_loaded.ContainsKey(center))
return ResidentStreamingWindowFact.Unavailable(_residentWindowRevision);
int maximumCandidate = 0;
foreach (uint landblockId in _loaded.Keys)
{
int x = (int)((landblockId >> 24) & 0xFFu);
int y = (int)((landblockId >> 16) & 0xFFu);
maximumCandidate = Math.Max(
maximumCandidate,
Math.Max(Math.Abs(x - centerX), Math.Abs(y - centerY)));
}
int completeRadius = 0;
for (int radius = 1; radius <= maximumCandidate; radius++)
{
if (!ContainsCompleteRing(radius))
break;
completeRadius = radius;
}
return new ResidentStreamingWindowFact(
_residentWindowRevision,
centerX,
centerY,
completeRadius,
_loaded.Count,
HasPublishedCenter: true);
bool ContainsCompleteRing(int radius)
{
int minX = Math.Max(0, centerX - radius);
int maxX = Math.Min(byte.MaxValue, centerX + radius);
int minY = Math.Max(0, centerY - radius);
int maxY = Math.Min(byte.MaxValue, centerY + radius);
for (int x = minX; x <= maxX; x++)
{
if (!_loaded.ContainsKey(StreamingRegion.EncodeLandblockId(x, minY))
|| !_loaded.ContainsKey(StreamingRegion.EncodeLandblockId(x, maxY)))
{
return false;
}
}
for (int y = minY + 1; y < maxY; y++)
{
if (!_loaded.ContainsKey(StreamingRegion.EncodeLandblockId(minX, y))
|| !_loaded.ContainsKey(StreamingRegion.EncodeLandblockId(maxX, y)))
{
return false;
}
}
return true;
}
}
/// <summary>
/// Total live entities currently parked in the pending bucket waiting
/// for their landblock to arrive. Useful diagnostic for verifying the
@ -1482,6 +1556,8 @@ public sealed class GpuWorldState : ILiveEntitySpatialQuery
_loadedLiveByLandblock.Clear();
_liveProjectionByKey.Clear();
if (_loaded.Count != 0)
_residentWindowRevision = checked(_residentWindowRevision + 1);
_loaded.Clear();
_renderTraversalLandblockSlots.Clear();
_freeRenderTraversalLandblockSlots.Clear();

View file

@ -0,0 +1,31 @@
namespace AcDream.App.Streaming;
/// <summary>
/// Read-only presentation fact describing the complete portion of the live
/// two-tier landblock window. A landblock is 192 metres wide. Radius zero means
/// only the centre is published and therefore offers no safe camera-relative
/// cascade reach beyond that landblock.
/// </summary>
internal readonly record struct ResidentStreamingWindowFact(
ulong Revision,
int CenterX,
int CenterY,
int CompleteRadiusLandblocks,
int PublishedLandblockCount,
bool HasPublishedCenter)
{
internal const float LandblockSizeMeters = 192f;
internal float MaximumReachMeters => HasPublishedCenter
? CompleteRadiusLandblocks * LandblockSizeMeters
: 0f;
internal static ResidentStreamingWindowFact Unavailable(ulong revision) =>
new(
revision,
CenterX: 0,
CenterY: 0,
CompleteRadiusLandblocks: 0,
PublishedLandblockCount: 0,
HasPublishedCenter: false);
}