One region of the systems map, expanded: how a template becomes a graph of zones, how those zones get carved and filled by a dependency graph of ~15 pluggable steps, and why the whole thing only ever runs once, on the server, from a single seed.
Entirely server-side, entirely inside game setup — not a separate tool, not something the client ever runs.
When a host picks a random map instead of a .vmap file, CGameState::initNewGame() checks scenarioOps->createRandomMap() and, if true, builds a CMapGenerator seeded from the server's own RNG (randomGenerator.nextInt() — itself fixed if the host set settings.server.seed) and calls generate(). That's it: one seed, one call, one thread of control.
Nothing downstream knows the difference. generate() returns an ordinary CMap — the same type a loaded .vmap file would produce — and from that point on it's just the map. RMG doesn't bypass the systems map's central rule that the server is the sole writer of state; it's simply one of the two ways CGameState gets its opening content, finished before that rule has anything to broadcast yet.
CMapGenerator::generate(), in the order it actually runs.
players, difficulty, and a blank terrain grid the right size
CZonePlacer::placeZones() — lays the zone graph onto the grid
every zone gets its list of fill-steps registered — nothing runs yet
the registered modifiers actually execute, as a scheduled job pool
CMap is handed back — same shape as any other loaded map.Before anything is filled, the zone graph has to be laid out on the grid — four passes, each one refining the last.
findPathsBetweenZones()Dijkstra over the template's connection graph — who's adjacent to whom, and how farmoveOneZone() — zones as soft spheres: connections pull like springs, overlaps push back; simulated annealing cools them from squishy to rigid over iterationsZone filling isn't a fixed sequence of function calls. It's a dependency graph of roughly fifteen step types, scheduled purely by readiness.
Every fill-step derives from Modificator. Its init() declares who it needs to finish first (dependency(), or the DEPENDENCY / DEPENDENCY_ALL macros); its process(), invoked through run(), does the actual work. fillZones() flattens every zone's modifier list into one queue and repeatedly scans it: anything isReady() — no unfinished preceder left — is handed to a tbb::task_group (or run in place, in declared order, when config.singleThread is set for reproducible output); once isFinished(), it drops out and whatever depended on it moves closer to ready. No global order exists beyond what the dependencies encode.
RockPlacer::init() depends on TreasurePlacer in every zone sharing its level, so rock never buries a treasure that hasn't been placed yet. RockFiller::init() then declares DEPENDENCY_ALL(RockPlacer) — the one shared filler for a level waits on every underground zone's RockPlacer, wherever the scheduler happened to run them. It's a hand-written topological sort, not a call order anyone could read off top to bottom.Registration happens once, in RmgMap::addModificators(), and which steps a zone gets depends entirely on what kind of zone it is:
baseline fill-steps, no zone is without them
ObjectManagerTreasurePlacerObstaclePlacerTerrainPainter
singleton steps, registered on the first zone only
ObjectDistributorPrisonHeroPlacer
everything a non-water zone needs to be playable
TownPlacerMinePlacerObjectPlacerQuestArtifactPlacerConnectionsPlacerRoadPlacerRiverPlacer
the one water zone, plus every zone once it exists
WaterAdopterWaterProxyWaterRoutes
rock placed after treasure, one filler per level
RockPlacerRockFiller
What actually happens inside these steps is described well enough in prose already — adjacent zones get a guard and a road, overlapping zones on different levels get a Subterranean Gate, distant zones route through the single water zone or seal their coast; every zone fractalizes outward from a center tile into a web of free paths; treasures fill in from highest value to lowest, each pile kept a minimum distance from the last; whatever's left over is blocked and packed with obstacles, biggest pieces first.
A CRmgTemplate is the graph plus the knobs — nothing about how to fill a zone, only what it should end up being.
Two collections carry almost everything: zones (size, type, terrain, treasure value range, town hints) and connections (the edges both CZonePlacer's Dijkstra pass and the ConnectionsPlacer modifier consume). Zones can reference each other instead of repeating themselves — afterLoad() resolves inheritTerrainType(), inheritMineTypes(), inheritTreasureInfo() and inheritTownProperties() recursively, so a template can say “zone 4: same terrain as zone 1” instead of spelling it out. It's the same by-reference idiom that shows up again inside TownPlacer::init(), which can make one zone's town choice depend on another's — templates and modifiers both lean on the same trick.
The schema lives at config/schemas/template.json; the numbers that tune the algorithm itself — the Fruchterman–Reingold cooling schedule, treasure density curves — sit in config/randomMap.json, not hardcoded into the C++.