This piece — reverse-engineering how this game builds its procedural levels (no UE here; another piece covers rebuilding it in UE).
Labeling convention (used throughout):
– Confirmed: directly verifiable from unpacked data — “visible via structure unpacking.”
– Inferred: reverse-reasoned from data patterns, no direct evidence — “inferred from data patterns.”
– Black box: involves runtime algorithms or network traffic, neither reverse-engineered nor packet-captured — “no conclusion drawn.”
Intro
This is a four-player co-op top-down shooter. Each deployment drops players onto a planet whose map almost never repeats — terrain relief, enemy strongholds, spawn distribution differ every match. Yet within a single match, all four players see exactly the same map.
“Different every match” and “identical for all four,” taken together, point to a classic procedural-generation approach: the map isn’t hand-placed by artists one block at a time, but generated by an algorithm at the start of each match according to rules. From the data patterns, the seed clearly participates in this process: as we’ll see, planets under the same template differ only by a single random seed (detailed in Chapter 3). Whether the process is *fully* deterministic (same input, same result), and how several machines each compute an identical map — that’s a runtime algorithm, neither reverse-engineered nor packet-captured, so no conclusion is drawn.
But first, the evidence boundary. The conclusions here come almost entirely from unpacking the game’s data files; network traffic was not monitored. So: what’s directly verifiable from unpacking → “visible via structure unpacking”; what’s reverse-reasoned from data patterns → “inferred from data patterns”; anything involving runtime communication — who generates that seed, whether the host or a backend server distributes it, what actually travels over the wire — was not packet-captured, so no conclusion is drawn and it isn’t treated as fact. (This game’s matches are P2P, usually with one player as host, but there’s also a centralized campaign backend, so this isn’t self-evident.)
With that boundary set, let’s dig in.
1. Overall: A Level’s Three Stages
Start with a frame: a level’s “lifetime” spans three stages in time — Production, Startup, Runtime Assembly.
Production happens during development. Artists and designers don’t build individual maps; they build a library of reusable prefab assets (units, prefabs, level blocks) and a set of generation rules. These assets, along with the rule fields, are packed into the game and can be unpacked. This stage’s output holds only “raw materials”: assets carry block-local coordinates, but no finished map and no world coordinates for any objective.
Startup happens the instant each match begins. A seed comes in and decides the match, doing two things of differing solvability: first, selecting the recipe — which planet template, which region, which rules; this layer is visible in the data (Chapter 3 shows that planets under the same template differ, at the static-config layer, only by seed and a few other fields). Second, solving the layout — associating the recipe and seed with each objective’s final world coordinates; this algorithm is compiled into the program and has not been reverse-engineered. So Startup is half controllable, half black box.
Runtime Assembly happens at level load. Following the solved layout, prefab assets are placed at their world coordinates to assemble this match’s level; each objective’s world coordinates are finalized and instantiated only at this moment.
Only here does the level truly take shape — and that moment is the edge of what unpacking can reach.
The generated layout for this match (which blocks were chosen, their world coordinates) is not written to any file. Unpacked data holds only “raw materials” and “recipes”; no finished map exists. The layout can be confirmed to be runtime-produced; but how it’s produced, who solves it, how the four ends agree — neither reverse-engineered nor packet-captured, no conclusion. Even the most basic question can’t be answered: do the four see the same map via lockstep (each end independently solving with the same seed and algorithm), or via state synchronization (one end solves, then replicates the result to the others)? Both architectures explain “four ends identical,” and neither leaves world coordinates in static data — without packet capture, it can’t be decided. The only certainty is the phenomenon itself: all four players end up seeing exactly the same map.
So the through-line of this whole piece can be summed up as: the production side is transparent, the runtime product is a black box. What was built in production — which assets, what task/AI components are attached, what rule tiers exist — is almost fully solvable; but “what it finally assembles into” is runtime-produced and not saved to file. Every later chapter returns to this boundary: detail what’s unpackable, and mark the black-box parts “runtime-produced, no conclusion.” Chapter 7 consolidates these boundaries into the “data layering wall.”

2. Asset Hierarchy: From Unit to Resource Package
A level’s content is organized as reusable prefab assets. But first distinguish: the first three tiers are “produced content”; the fourth (package) is “saved packaging.” They are not the same kind of thing.
Three tiers of content assets: unit → prefab → level
- unit is the smallest grain, ~4,100 of them. It’s not an “art model” but an ECS entity: an appearance (mesh / material / texture) plus a set of components, and the components decide what it is in gameplay — attach AiSpawner and it’s a spawn point (bug hole); attach Behavior / Faction and it’s an enemy unit; attach Interactable / Objective and it’s a task terminal; attach Health / Deposit and it’s a destructible or resource node; with no gameplay component, it’s pure decoration.
- prefab is a reusable module made of a group of units — 582 of them, averaging ~55 instantiations, and they support nesting (358 nested references measured, no cycles). Only block-reusable content is packed into a prefab; loose units are placed directly.
- level (scene block) is a pre-built scene segment, 1,845 blocks. It’s a mixed container: both prefabs and units can be placed directly — mixed (prefab+unit) at 1,028 blocks is the majority, pure-unit at 701 is next.
The three form an assembly relationship: small to large, progressively composing fuller scene content.
package: saved packaging, not a fourth content tier
The easily-misread part is package: it looks like a fourth tier, but it’s fundamentally different from the first three. Those are actual content produced by artists/designers; package is merely a way to pack the produced levels by type for storage, carrying no new content of its own. 1,105 packages group the scene blocks by faction/type (Automaton 384 / Terminid 230 / Super Earth 205 / Illuminate 65…), and a single block can belong to multiple packages (~2.5 on average). Its use: what can be confirmed is packaging classification (measured); whether it is used for runtime on-demand loading can’t be judged from static config alone. And as for whether it participates in generation-time block selection — the entire config layer holds no direct reference to any specific package, so it reads more as a non-generation layer (a packaging dimension) than a link in the selection logic. That seam — “the index is stored, the content objects stay at runtime” — is the same one Chapter 7 consolidates into a single wall.
In short: content assets are “produced and assembled,” package is “saved and packed” — two different dimensions that should not be conflated.

Reuse is the foundation of this design
The 582 prefabs are instantiated tens of thousands of times across scene blocks; the most frequent prefab appears in 200+ distinct blocks; the most frequent unit is placed thousands of times. Artists maintain one standardized asset set and the runtime instantiates it repeatedly — cutting duplicate production cost and keeping a globally consistent style. This is the first layer of procedural generation’s leverage.
3. Generation: Planets and Terrain
Planets and terrain aren’t “made” — they’re “computed.”
Planet = Template + Seed
Behind 384 real planets there are only 25 templates. Planets under the same template share an identical generation config, differing only by name, resource node, and seed — at the static-config layer, planets under the same template differ only by name, resource node, and seed (planet_data, all 425 entities exported). From the data, the seed participates in generation, but its actual scope at runtime — and whether it is the sole driving variable — can’t be confirmed from static data, so no conclusion is drawn.
Each planet_setup also binds its content axes in one shot: 1 setup = 1 scenario + 1 scatter + 1 environment biome, with low/high-land config identical within a setup (26 setups checked row by row, zero mismatches). There are 29 regions; a planet’s region splits into low-land and high-land structures, but across 425 planets the two values are identical — so while there are theoretically 29×29 combinations, in the current data this dimension shows only a single value per planet, with no combinatorial spread observed. Roads are pre-set at the environment layer as 5 spline slots (1 base path + 4 faction roads), shared identically across all environments.

Terrain: Form / Skin / Props
Terrain has three layers. Form (terrain geometry) is 100% procedurally generated — Voronoi partition + displacement field + seed — depending on no art assets (the resource-reference count for terrain-geometry types is 0, i.e. STR=0; the heightmap and mesh are runtime-baked products, not inputs). Skin (surface material) is a single terrain_material, blended in-shader by height/slope (splatting), not “one material per height tier.” Props (surface objects) are where art lives: POIs are scene blocks hand-built in the level editor, the vegetation library has 256 species, scattered by seed at runtime and flattened onto the terrain via FlattenHeightmapAO on placement.
So art only enters “Skin” and “Props”; the terrain’s “Form” is computed by the program from the seed, not authored by artists.
What can be confirmed at this layer: terrain geometry is generated entirely procedurally (STR=0, no art-asset input); but “how those procedural pieces compose into the finished structure of that one planet” — the runtime instance layout — still doesn’t close at the data layer, and stays a black box.

The terrain system’s components
Viewed as four layers — rules → geometry → deformation → placement: the rules layer (GenerationRegionSettings / SubRegion) sets the parameter baseline; the geometry layer (Voronoi / noise) computes the shape; the deformation layer (DisplacementComponent) digs/raises/flattens on the base terrain; the placement layer (Stamp / LocationStampInfo) stamps terrain features and buildings on top. The types, byte sizes, fields, and enums of these components are almost fully unpacked; but among instance values, only DisplacementComponent was successfully exported (632B, e.g. radii 15/45/33/6). The rest of the recipe layer (GenerationRegionSettings / Stamp / Location) has structure only, instances unserialized — more on this in Chapter 7.

4. Tasks
A task isn’t a fixed list — it’s “candidate pool + rules + tags.”
How tasks are arranged
The candidate pool decides “which tasks can enter which blocks”: environment/faction scope the resource-package class, then the candidate levels and objectives (1,026 pairs → 203 distinct objectives, 339 levels, 81 families, 12 task_shapes). That 1,026→203 convergence tells you one thing: tasks aren’t “one map bound to one task” — 203 objectives match many-to-many against 339 blocks. It’s a combinatorial task graph, not a level-bound fixed list. And “which tasks a block can host” is carried by the block itself — each level’s Objective range marks, within the data, which objects in the block are task entities (165 range groups, covering 2,085 slots). A match’s tasks mostly combine as “core + support + delivery” three slots (destroy/upload + orbital cannon/SAM + fuel canister/escape pod). Placement specs come from LevelGenerationLocation: location type (primary / secondary / extraction / camp), default radius (camp ~90, extraction ~130); but with 0 instances — how many this match places, where, at what coordinates, is runtime.

How a task is completed
Completing a task is an “execution blueprint”: Objective (205 cards) → 8 stage slots per card → each active stage is one Stage Type (19 types); actually ~1.2 stages active on average, 75% single-stage. “8 slots but only ~1.2 active on average” isn’t waste — it’s a template that leaves room to expand and fills on demand: the vast majority of tasks finish in one step, while the few multi-stage ones reuse the same slot structure, with no separate data authored for long tasks. What best shows “modularity” is the terminal: one shared terminal component set, configured by signature into 11 distinct minigames (upload / drill / radar / SAM / ICBM…) — not 11 codebases, but 1 component set × different config. Failure is judged by a target unit’s “destroy = fail” flag (FailOnDestroyed_Always, 55 of them); stratagem requirements like Hellbomb, RaiseFlag.

5. Spawning
Spawning is a three-layer architecture: statically pre-set spawners + a runtime pressure-tuning director + pre-set enemy squads.
Architecture and three factions
Static spawner (66) pre-sets where to spawn (bug holes / factories / portals); the HiveMind director (38) tunes pressure by faction/difficulty; Squad (17) are pre-set enemy squad compositions. The three factions differ: Terminids use pre-placed bug holes (SpawnLane left/mid/right 481/482/483); Automatons use factories (gate 708) plus dropship drops; Illuminate use portal summoning (portals 1850/1851, with a unique active “pressure” initial value 300/600). Garrison size is set by GuardForce (faction × multiplier, bug 2× / bot 1.5×).

What / How / When to spawn
Viewed at three levels of precision:
- What to spawn (templates) — largely solved: the encounter-wave Squad rosters (Terminid 6 waves down to per-unit counts), the Encounter pool, the local lanes; but the garrison roster remains black box.
- How to spawn locally (timing) — newly solved: start/stop signals, lanes, the timing tuple (initial delay / wave interval / unit interval / cooldown, measured e.g. 40/40/4/0.2), counts; the local spawner’s “how often, how many per wave” framework is now clear.
- When/how-much globally (scheduling) — still black box: the HiveMind’s trigger timing, wave budget, and runtime call chain all live in runtime memory, not on disk.
So from the unpacked structure, the local spawner’s timing and layering rules are now largely clear; but the higher-level global scheduling mechanism still doesn’t appear in static data, and remains a runtime black box.

6. World
A planet’s “sense of place” is a mix of parameter config + hand placement + procedural scattering.
Environmental ambience
Weather is EnvironmentalEffectType, 15 kinds (snowstorm / rainstorm / sandstorm / ion storm / thick fog…), constrained by region — a desert planet won’t get a snowstorm. The sky is the most fully-solved block in the whole dataset: 35 sets × 42 parameters, each split into day / dusk / night (Rayleigh / Mie scattering, fog, cloud color, surface wetness, star brightness) — a planet’s “light” is exactly these 42 dials. Scattering is scatter (21 classification keys, not a list) plus StampGroup (density / spacing / probability) driving procedural scatter; it provides the “how to scatter” rules, not a “what was scattered” list, and the concrete scatter algorithm is runtime.

Cover and roads
Cover splits into three tiers, only one of which is true game semantics: A is real game cover (*_cover kits + CoverInfo components + AI’s height-tiered task_cover_125cm), hand-placed + AI-recognized at runtime; B is large solid objects (rocks / crates — functionally cover-like but not game-tagged); C is decoration. Cover is not procedurally scattered — the opposite of vegetation. Roads are a globally unified 5-slot spline set (base path + four faction roads, collision width 15 / mesh width 20), shared across all environments and selected by faction.

7. The Boundary: The Data Layering Wall
The boundary met again and again throughout comes down to a single wall.
Data splits into two layers: the serializable layer (indexes / classifications / rules / recipe structures / default templates) is packed into datalibrary and unpackable; the content-object layer (runtime instances / finished layout / world coordinates / chosen results) is packed by 64-bit hash plus runtime memory, structurally unreachable. No instance serialization appears in the static data for this wall (only the index and structure); the more reasonable reading of that is a “separation of runtime objects from static config,” rather than any encryption or hiding mechanism — “who uses which recipe” (the index) is stored, while “the recipe’s actual content objects” stay at runtime. But its concrete implementation, and whether it was deliberately designed to layer this way, can’t be judged from static data.
Four independent investigations point to the same wall: ① region/terrain (structure present, instances 0 hits across 14 dl_bins by type-hash) ② spawning (encounter waves + local timing present, garrison roster + global scheduling absent) ③ tasks (candidate pool + execution blueprint present, which-ones-actually-chosen + coordinates absent) ④ task placement (default template present, 0 instances).
Why is some of it reachable? PTR tiering: 0-PTR is a serializable recipe (structurally on-disk-able), and has-PTR is a runtime object graph (unreachable). The strongest evidence is DisplacementComponent — it’s 0-PTR and was successfully exported with real values, proving 0-PTR recipes can fully serialize to disk; the other 0-PTR types simply weren’t serialized (not “couldn’t decode,” but “didn’t store”).

8. Conclusion
Back to the opening line: This game has no “statically stored, finished map.” A level’s *components* — prefab assets, generation rules, planet recipes — are all in the files, but the assembled product — which blocks, which enemies, where each strongpoint lands — isn’t on disk; it’s computed in real time from a single seed at runtime. The production side — assets, rules, recipes, default templates — is almost fully unpackable; but “what a given match finally assembles into” is runtime-produced.
The payoff of this design is clear: artists build one set of prefab assets reused everywhere (lower cost), only the seed is sent rather than the whole map (less bandwidth), and adding a recipe extends the content (easy to extend). As for how all four ends see an identical map — deterministic generation solved independently per end, state synchronization pushed from one end, or a hybrid of the two — the algorithm wasn’t reverse-engineered and the traffic wasn’t captured, so this piece can’t confirm it. It turns “making levels” into “making a system that generates levels.”
So — having seen how this game does it, can it be rebuilt in UE? That’s what a companion piece answers.
*Every number in this piece is individually verified; anything marked “black box / runtime” was not reverse-engineered or packet-captured, and no unfounded claims are made.*
1 thought on “Procedural Level Generation: A Full Teardown of a Four-Player Co-op Shooter”