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)
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);