feat(A.5 T4): StreamingRegion ComputeFirstTickDiff

Adds the first-tick bootstrap diff: ToLoadNear for the (2*near+1)^2 inner
window, ToLoadFar for the outer annulus up to FarRadius. Uses Chebyshev
distance, matching existing Recenter convention.

Also renames the single-tier RecenterTo → RecenterToSingleTier to free
the canonical name for the upcoming two-tier overload (T5). Updates
StreamingRegionTests and StreamingController to call the renamed method.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Erik 2026-05-09 22:34:55 +02:00
parent 378f32ac7a
commit 7bcababf82
4 changed files with 54 additions and 6 deletions

View file

@ -79,7 +79,7 @@ public sealed class StreamingController
}
else if (_region.CenterX != observerCx || _region.CenterY != observerCy)
{
var diff = _region.RecenterTo(observerCx, observerCy);
var diff = _region.RecenterToSingleTier(observerCx, observerCy);
foreach (var id in diff.ToLoad) _enqueueLoad(id);
foreach (var id in diff.ToUnload) _enqueueUnload(id);
}

View file

@ -87,13 +87,47 @@ public sealed class StreamingRegion
internal static uint EncodeLandblockId(int lbX, int lbY)
=> ((uint)lbX << 24) | ((uint)lbY << 16) | 0xFFFFu;
/// <summary>
/// First-tick bootstrap: emit ToLoadNear for every LB in the inner ring,
/// ToLoadFar for every LB in the outer ring (between near and far). Used
/// by <see cref="StreamingController.Tick"/> on the first call before any
/// RecenterTo.
/// </summary>
public TwoTierDiff ComputeFirstTickDiff()
{
var near = new List<uint>();
var far = new List<uint>();
for (int dx = -FarRadius; dx <= FarRadius; dx++)
{
for (int dy = -FarRadius; dy <= FarRadius; dy++)
{
int nx = CenterX + dx;
int ny = CenterY + dy;
if (nx < 0 || nx > 0xFF || ny < 0 || ny > 0xFF) continue;
int absDx = System.Math.Abs(dx);
int absDy = System.Math.Abs(dy);
var id = EncodeLandblockId(nx, ny);
if (absDx <= NearRadius && absDy <= NearRadius)
near.Add(id);
else
far.Add(id);
}
}
return new TwoTierDiff(
ToLoadFar: far,
ToLoadNear: near,
ToPromote: System.Array.Empty<uint>(),
ToDemote: System.Array.Empty<uint>(),
ToUnload: System.Array.Empty<uint>());
}
/// <summary>
/// Recompute the visible window around a new center and return the
/// delta vs. the previous state. Hysteresis: landblocks aren't unloaded
/// until they're further than <c>Radius + 2</c> from the new center,
/// so boundary crossings don't thrash.
/// </summary>
public RegionDiff RecenterTo(int newCx, int newCy)
public RegionDiff RecenterToSingleTier(int newCx, int newCy)
{
// Snapshot the old resident set so we can diff against it.
var oldResident = new HashSet<uint>(_resident);