Skip to content

Debris populator walks a live chunk enumerator while RoomFill writes tiles into it #21

Description

@rebaserHEAD

Found while scoping issue #10 on feat/worldgen-sensed-tier, but this is upstream code and pre-existing — nothing to do with the sensed tier, which is why it is filed separately. Established by code reading only; see the caveat at the bottom.

Mechanism

SimpleFloorPlanPopulatorSystem.OnFloorPlanBuilt holds a GridTileEnumerator open across its whole tile loop:

var enumerator = _map.GetAllTilesEnumerator(uid, grid);
while (enumerator.MoveNext(out var tile))
{
    ...
    Spawn(proto, coords);   // <-- can be a RoomFill marker
}

GetAllTilesEnumerator is a thin wrapper over grid.Chunks.GetEnumerator(), a raw Dictionary<Vector2i, MapChunk>.Enumerator (SharedMapSystem.Grid.cs:808).

LocalStructureLoadedEvent is raised from LocalityLoaderSystem.Update, long after the grid is map-inited, so Spawn of a room marker initializes it immediately, RoomFillSystem.OnRoomFillMapInit runs synchronously, and DungeonSystem.SpawnRoom calls _maps.SetTiles — all while that enumerator is live.

Two consequences

1. Crash when the room reaches a chunk the blob never filled. Writing into existing chunks mutates MapChunk objects in place and the dictionary version is untouched, so the enumerator survives. But a new chunk goes through grid.Chunks[chunkIndex] = new MapChunk(...) (SharedMapSystem.Grid.cs:842), which bumps the version and makes the next MoveNext() throw InvalidOperationException: Collection was modified. Needs a marker within the room half-extent of a chunk boundary whose neighbour has no filled tiles.

2. Rooms can seed rooms, order-dependently. Room tiles written into a chunk the enumerator has not reached yet get populated in turn. On NFBaseWreckDebris the room marker is ungrouped at prob: 0.01 per plating/steel tile, so 25 fresh room tiles carry roughly a 1-in-5 chance of spawning another room. Whether it happens at all depends on chunk iteration order and where the write lands relative to the cursor, which makes debris population quietly non-reproducible even before any seeding is involved.

Exposure

RoomFill markers appear in the NF debris tables for all seven asteroid families (orGroup, prob: 0.0002, so roughly 4-6% of rocks) and for wrecks (ungrouped, prob: 0.01 per qualifying tile, so most wrecks and often more than one). Also used by non-worldgen dungeon generation, which may or may not hold a live enumerator the same way.

Fix

Materialize the tile list before the loop rather than walking a live enumerator. That closes the crash and makes room cascades impossible in one change. Note it is a small live behaviour change: room tiles would no longer receive populator rolls, which today they sometimes do.

Caveat

Not reproduced, and not confirmed against production. The prod Grafana credentials in .mcp.json return 401 on both the MCP and the HTTP-direct datasource proxy, so the Loki check for Collection was modified could not be run. Worth a look once those creds are refreshed before anyone spends time on a fix.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions