diff --git a/src/AcDream.App/Streaming/StreamingController.cs b/src/AcDream.App/Streaming/StreamingController.cs
index 67ed631..c320429 100644
--- a/src/AcDream.App/Streaming/StreamingController.cs
+++ b/src/AcDream.App/Streaming/StreamingController.cs
@@ -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);
}
diff --git a/src/AcDream.App/Streaming/StreamingRegion.cs b/src/AcDream.App/Streaming/StreamingRegion.cs
index bcebe44..7d0fc57 100644
--- a/src/AcDream.App/Streaming/StreamingRegion.cs
+++ b/src/AcDream.App/Streaming/StreamingRegion.cs
@@ -87,13 +87,47 @@ public sealed class StreamingRegion
internal static uint EncodeLandblockId(int lbX, int lbY)
=> ((uint)lbX << 24) | ((uint)lbY << 16) | 0xFFFFu;
+ ///
+ /// 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 on the first call before any
+ /// RecenterTo.
+ ///
+ public TwoTierDiff ComputeFirstTickDiff()
+ {
+ var near = new List();
+ var far = new List();
+ 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(),
+ ToDemote: System.Array.Empty(),
+ ToUnload: System.Array.Empty());
+ }
+
///
/// 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 Radius + 2 from the new center,
/// so boundary crossings don't thrash.
///
- 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(_resident);
diff --git a/tests/AcDream.Core.Tests/Streaming/StreamingRegionTests.cs b/tests/AcDream.Core.Tests/Streaming/StreamingRegionTests.cs
index 741ea2b..899291e 100644
--- a/tests/AcDream.Core.Tests/Streaming/StreamingRegionTests.cs
+++ b/tests/AcDream.Core.Tests/Streaming/StreamingRegionTests.cs
@@ -36,7 +36,7 @@ public class StreamingRegionTests
{
var region = new StreamingRegion(cx: 50, cy: 50, radius: 2);
- var diff = region.RecenterTo(50, 50);
+ var diff = region.RecenterToSingleTier(50, 50);
Assert.Empty(diff.ToLoad);
Assert.Empty(diff.ToUnload);
@@ -52,7 +52,7 @@ public class StreamingRegionTests
// the radius+2 threshold, so it stays loaded (hysteresis keeps radius+2).
var region = new StreamingRegion(cx: 50, cy: 50, radius: 2);
- var diff = region.RecenterTo(51, 50);
+ var diff = region.RecenterToSingleTier(51, 50);
Assert.Equal(5, diff.ToLoad.Count);
Assert.Empty(diff.ToUnload);
@@ -71,7 +71,7 @@ public class StreamingRegionTests
// x=48 is now 5 away, > radius+2 = 4 → unload. x=49 is 4 away, not > 4 → keep. x=50 is 3 away, not > 4 → keep.
var region = new StreamingRegion(cx: 50, cy: 50, radius: 2);
- var diff = region.RecenterTo(53, 50);
+ var diff = region.RecenterToSingleTier(53, 50);
Assert.Equal(15, diff.ToLoad.Count);
Assert.Equal(5, diff.ToUnload.Count);
@@ -82,7 +82,7 @@ public class StreamingRegionTests
{
var region = new StreamingRegion(cx: 50, cy: 50, radius: 2);
- var diff = region.RecenterTo(200, 200);
+ var diff = region.RecenterToSingleTier(200, 200);
Assert.Equal(25, diff.ToLoad.Count);
Assert.Equal(25, diff.ToUnload.Count);
diff --git a/tests/AcDream.Core.Tests/Streaming/StreamingRegionTwoTierTests.cs b/tests/AcDream.Core.Tests/Streaming/StreamingRegionTwoTierTests.cs
index 65df093..105b224 100644
--- a/tests/AcDream.Core.Tests/Streaming/StreamingRegionTwoTierTests.cs
+++ b/tests/AcDream.Core.Tests/Streaming/StreamingRegionTwoTierTests.cs
@@ -20,4 +20,18 @@ public class StreamingRegionTwoTierTests
// becomes (NearRadius+2) for the far-tier unload, which is wrong.
Assert.Equal(region.FarRadius, region.Radius);
}
+
+ [Fact]
+ public void RecenterTo_FirstTick_SplitsLoadIntoNearAndFar()
+ {
+ // near=1, far=3 → near window is 3×3=9, far window is 7×7-3×3=40 LBs.
+ var region = new StreamingRegion(centerX: 100, centerY: 100, nearRadius: 1, farRadius: 3);
+ var diff = region.ComputeFirstTickDiff();
+
+ Assert.Equal(9, diff.ToLoadNear.Count);
+ Assert.Equal(40, diff.ToLoadFar.Count);
+ Assert.Empty(diff.ToPromote);
+ Assert.Empty(diff.ToDemote);
+ Assert.Empty(diff.ToUnload);
+ }
}