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 <noreply@anthropic.com>
This commit is contained in:
Erik 2026-08-30 15:47:54 +02:00
parent 5d51b37ad8
commit 1e1a0d9318

View file

@ -65,14 +65,17 @@ internal sealed class WalkProductionWorldData : IWalkFrameWorldData
foreach (List<RenderProjectionRecord> bucket in _shellsByAnchor.Values) foreach (List<RenderProjectionRecord> bucket in _shellsByAnchor.Values)
bucket.Clear(); bucket.Clear();
int count; // CopyIndexTo THROWS on an undersized destination (ArchRenderScene
while (true) // 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); _sweepScratch = new RenderProjectionRecord[
if (count < _sweepScratch.Length) Math.Max(required, _sweepScratch.Length * 2)];
break;
_sweepScratch = new RenderProjectionRecord[_sweepScratch.Length * 2];
} }
int count = _scene.CopyIndexTo(RenderSceneIndex.OutdoorStatic, _sweepScratch);
for (int i = 0; i < count; i++) for (int i = 0; i < count; i++)
{ {
ref readonly RenderProjectionRecord record = ref _sweepScratch[i]; ref readonly RenderProjectionRecord record = ref _sweepScratch[i];
@ -111,14 +114,15 @@ internal sealed class WalkProductionWorldData : IWalkFrameWorldData
{ {
if (_cellCache.TryGetValue(cellId, out WalkFrameStaticRecords cached)) if (_cellCache.TryGetValue(cellId, out WalkFrameStaticRecords cached))
return cached; return cached;
int count; // Same up-front-validation contract as CopyIndexTo: presize from the
while (true) // 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); _cellScratch = new RenderProjectionRecord[
if (count < _cellScratch.Length) Math.Max(required, _cellScratch.Length * 2)];
break;
_cellScratch = new RenderProjectionRecord[_cellScratch.Length * 2];
} }
int count = _scene.CopyCellStaticsTo(cellId, _cellScratch);
WalkFrameStaticRecords records = count == 0 WalkFrameStaticRecords records = count == 0
? WalkFrameStaticRecords.Empty with { TupleLandblockId = _tupleLandblockId } ? WalkFrameStaticRecords.Empty with { TupleLandblockId = _tupleLandblockId }
: new WalkFrameStaticRecords(_cellScratch[..count], _tupleLandblockId); : new WalkFrameStaticRecords(_cellScratch[..count], _tupleLandblockId);