feat(A.5 T16): wire two-tier streaming into GameWindow

GameWindow now constructs StreamingController with nearRadius / farRadius
defaults of 4 / 12 (per spec acceptance criterion). Env vars:
- ACDREAM_NEAR_RADIUS (default 4)
- ACDREAM_FAR_RADIUS  (default 12)
- ACDREAM_STREAM_RADIUS (legacy; if set, treats as nearRadius and
  bumps farRadius to max(stream, default))

Fields _nearRadius / _farRadius added alongside legacy _streamingRadius
(kept so the debug overlay's getStreamingRadius callback stays valid).

ApplyLoadedTerrainLocked routes to TerrainModernRenderer.AddLandblockWithMesh
(T15) instead of AddLandblock directly, making the two-tier entry point
the canonical call path.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Erik 2026-05-10 07:58:12 +02:00
parent b8d80fe282
commit c4fd37384a

View file

@ -83,7 +83,9 @@ public sealed class GameWindow : IDisposable
private AcDream.App.Streaming.LandblockStreamer? _streamer;
private AcDream.App.Streaming.GpuWorldState _worldState = new();
private AcDream.App.Streaming.StreamingController? _streamingController;
private int _streamingRadius = 2; // default 5×5
private int _streamingRadius = 2; // default 5×5 (kept for debug overlay getStreamingRadius callback)
private int _nearRadius = 4; // Phase A.5 T16: two-tier near ring (default 4 → 9×9)
private int _farRadius = 12; // Phase A.5 T16: two-tier far ring (default 12 → 25×25)
private uint? _lastLivePlayerLandblockId;
// Phase B.3: physics engine — populated from the streaming pipeline.
@ -1575,13 +1577,30 @@ public sealed class GameWindow : IDisposable
// the player.
_particleRenderer = new ParticleRenderer(_gl, shadersDir, _textureCache, _dats);
// Phase A.1: replace the one-shot 3×3 preload with a streaming controller.
// Parse runtime radius from environment (default 2 → 5×5 window).
// Values outside [0, 8] fall back to the field default of 2.
var radiusEnv = Environment.GetEnvironmentVariable("ACDREAM_STREAM_RADIUS");
if (int.TryParse(radiusEnv, out var r) && r >= 0 && r <= 8)
_streamingRadius = r;
Console.WriteLine($"streaming: radius={_streamingRadius} (window={2*_streamingRadius+1}×{2*_streamingRadius+1})");
// Phase A.5 T16: two-tier radius env-var parsing.
// ACDREAM_NEAR_RADIUS / ACDREAM_FAR_RADIUS set the two rings independently.
// Legacy ACDREAM_STREAM_RADIUS is honoured for backward-compat: it sets
// nearRadius and bumps farRadius to max(streamRadius, default farRadius).
{
var nearEnv = Environment.GetEnvironmentVariable("ACDREAM_NEAR_RADIUS");
var farEnv = Environment.GetEnvironmentVariable("ACDREAM_FAR_RADIUS");
var legacyEnv = Environment.GetEnvironmentVariable("ACDREAM_STREAM_RADIUS");
if (int.TryParse(nearEnv, out var nr) && nr >= 0) _nearRadius = nr;
if (int.TryParse(farEnv, out var fr) && fr >= 0) _farRadius = fr;
// Legacy override: ACDREAM_STREAM_RADIUS acts as nearRadius and
// ensures farRadius >= streamRadius.
if (int.TryParse(legacyEnv, out var sr) && sr >= 0)
{
_nearRadius = sr;
_streamingRadius = sr; // keep debug overlay in sync
_farRadius = System.Math.Max(sr, _farRadius);
}
}
Console.WriteLine(
$"streaming: nearRadius={_nearRadius} (window={2*_nearRadius+1}x{2*_nearRadius+1})" +
$" farRadius={_farRadius} (window={2*_farRadius+1}x{2*_farRadius+1})");
// Phase A.5 T11+: the streamer now runs on a dedicated worker thread.
// loadLandblock acquires _datLock (T10) before touching DatCollection.
@ -1610,8 +1629,8 @@ public sealed class GameWindow : IDisposable
drainCompletions: _streamer.DrainCompletions,
applyTerrain: ApplyLoadedTerrain,
state: _worldState,
nearRadius: _streamingRadius,
farRadius: _streamingRadius,
nearRadius: _nearRadius,
farRadius: _farRadius,
removeTerrain: id =>
{
// Phase G.2: release any LightSources attached to entities
@ -5144,9 +5163,10 @@ public sealed class GameWindow : IDisposable
(lbY - _liveCenterY) * 192f,
0f);
// Phase A.5 T12: terrain mesh is pre-built by the worker thread and
// passed in via meshData. No longer rebuilt here on the render thread.
_terrain.AddLandblock(lb.LandblockId, meshData, origin);
// Phase A.5 T15/T16: route through AddLandblockWithMesh — the named
// two-tier entry point. Delegates to AddLandblock internally; both
// paths share one GPU upload path.
_terrain.AddLandblockWithMesh(lb.LandblockId, meshData, origin);
// Step 4: drain pending LoadedCells from the worker thread.
while (_pendingCells.TryTake(out var cell))