diff --git a/src/AcDream.App/Rendering/GameWindow.cs b/src/AcDream.App/Rendering/GameWindow.cs index c2aae70..7c53a03 100644 --- a/src/AcDream.App/Rendering/GameWindow.cs +++ b/src/AcDream.App/Rendering/GameWindow.cs @@ -111,7 +111,9 @@ public sealed class GameWindow : IDisposable // LandblockMesh.Build without re-deriving these each time. private float[]? _heightTable; private AcDream.Core.Terrain.TerrainBlendingContext? _blendCtx; - private Dictionary? _surfaceCache; + // Was: Dictionary. ConcurrentDictionary so the off-thread + // mesh builder (A.5 T11+) can call LandblockMesh.Build without a lock. + private System.Collections.Concurrent.ConcurrentDictionary? _surfaceCache; // Phase A.1 Task 8: worker thread pre-builds EnvCell room-mesh sub-meshes // (CPU only) and stores them here. ApplyLoadedTerrain (render thread) drains @@ -1465,7 +1467,7 @@ public sealed class GameWindow : IDisposable RoadAlphaRCodes: terrainAtlas.RoadAlphaRCodes); _heightTable = heightTable; - _surfaceCache = new Dictionary(); + _surfaceCache = new System.Collections.Concurrent.ConcurrentDictionary(); // (Bindless detection moved above — must precede TerrainAtlas.Build / // TerrainModernRenderer ctor so they can consume BindlessSupport.) diff --git a/src/AcDream.Core/Terrain/LandblockMesh.cs b/src/AcDream.Core/Terrain/LandblockMesh.cs index 573acf5..81e6724 100644 --- a/src/AcDream.Core/Terrain/LandblockMesh.cs +++ b/src/AcDream.Core/Terrain/LandblockMesh.cs @@ -46,7 +46,7 @@ public static class LandblockMesh uint landblockY, float[] heightTable, TerrainBlendingContext ctx, - Dictionary surfaceCache) + System.Collections.Generic.IDictionary surfaceCache) { ArgumentNullException.ThrowIfNull(block); ArgumentNullException.ThrowIfNull(heightTable); @@ -105,6 +105,10 @@ public static class LandblockMesh uint palCode = TerrainBlending.GetPalCode( rBL, rBR, rTR, rTL, ttBL, ttBR, ttTR, ttTL); + // Lookup-or-build pattern. Not atomic under concurrent access + // (TryGetValue then assign), but BuildSurface is deterministic — + // two workers building the same palCode produce equal SurfaceInfo, + // last-write-wins is benign. if (!surfaceCache.TryGetValue(palCode, out var surf)) { surf = TerrainBlending.BuildSurface(palCode, ctx);