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