From 1e1a0d93186fb94b6b391a1cefd6e52c813ad2c3 Mon Sep 17 00:00:00 2001 From: Erik Date: Sun, 30 Aug 2026 15:47:54 +0200 Subject: [PATCH] fix(render) Campaign FW3.2b-2: presize scene copies - CopyIndexTo throws, never truncates The first connected gate run of the static cutover crashed at Aerlinthe: WalkProductionWorldData grew its scratch by probing with an undersized span, but ArchRenderScene validates destination size UP FRONT and throws (5,040 outdoor statics vs the 1,024-record probe; the sanctuary shadow run never had enough outdoor statics to trip it). Presize from the query's own counts instead - IndexCounts.For for the outdoor sweep, GetCellStaticCount for per-cell copies. Suites: full Release build 0 warnings; hermetic 6,750/0. Co-Authored-By: Claude Fable 5 --- .../Rendering/Walk/WalkProductionWorldData.cs | 28 +++++++++++-------- 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/src/AcDream.App/Rendering/Walk/WalkProductionWorldData.cs b/src/AcDream.App/Rendering/Walk/WalkProductionWorldData.cs index 17392765..a9ef6195 100644 --- a/src/AcDream.App/Rendering/Walk/WalkProductionWorldData.cs +++ b/src/AcDream.App/Rendering/Walk/WalkProductionWorldData.cs @@ -65,14 +65,17 @@ internal sealed class WalkProductionWorldData : IWalkFrameWorldData foreach (List bucket in _shellsByAnchor.Values) bucket.Clear(); - int count; - while (true) + // CopyIndexTo THROWS on an undersized destination (ArchRenderScene + // validates up front — the first connected gate run of the FW3.2b-2 + // cutover crashed on exactly this at Aerlinthe's 5,040 outdoor + // statics), so presize from the query's own index counts. + int required = _scene.IndexCounts.For(RenderSceneIndex.OutdoorStatic); + if (required > _sweepScratch.Length) { - count = _scene.CopyIndexTo(RenderSceneIndex.OutdoorStatic, _sweepScratch); - if (count < _sweepScratch.Length) - break; - _sweepScratch = new RenderProjectionRecord[_sweepScratch.Length * 2]; + _sweepScratch = new RenderProjectionRecord[ + Math.Max(required, _sweepScratch.Length * 2)]; } + int count = _scene.CopyIndexTo(RenderSceneIndex.OutdoorStatic, _sweepScratch); for (int i = 0; i < count; i++) { ref readonly RenderProjectionRecord record = ref _sweepScratch[i]; @@ -111,14 +114,15 @@ internal sealed class WalkProductionWorldData : IWalkFrameWorldData { if (_cellCache.TryGetValue(cellId, out WalkFrameStaticRecords cached)) return cached; - int count; - while (true) + // Same up-front-validation contract as CopyIndexTo: presize from the + // query's own count rather than probing with an undersized span. + int required = _scene.GetCellStaticCount(cellId); + if (required > _cellScratch.Length) { - count = _scene.CopyCellStaticsTo(cellId, _cellScratch); - if (count < _cellScratch.Length) - break; - _cellScratch = new RenderProjectionRecord[_cellScratch.Length * 2]; + _cellScratch = new RenderProjectionRecord[ + Math.Max(required, _cellScratch.Length * 2)]; } + int count = _scene.CopyCellStaticsTo(cellId, _cellScratch); WalkFrameStaticRecords records = count == 0 ? WalkFrameStaticRecords.Empty with { TupleLandblockId = _tupleLandblockId } : new WalkFrameStaticRecords(_cellScratch[..count], _tupleLandblockId);