BUas Games
Eliminating tile candidates through local boundary constraints builds coherent 3D dioramas from small modular sets, provided pathfinding heuristics guide placement and generation contradictions trigger immediate restarts rather than backtracking.
AI-assisted summary of EPC2018 | Wave Function Collapse in Bad North | Oskar Stalberg — original source. Check the original for full context.
Source published:
Key ideas
- Oskar Stålberg adapts Maxim Gumin's 2D Wave Function Collapse algorithm into 3D, treating level generation as an act of candidate elimination over grid slots rather than constructive placement. (1:33)
- Bad North scales from an initial prototype of 16 modules to roughly 300 modular pieces, initializing around 100,000 candidate module states per 3D island generation pass. (14:50)
- When contradictions or gameplay validation failures occur, the engine avoids step-by-step backtracking and instead snapshots the post-boundary state to execute rapid full re-collapses. (17:53)
- A custom Unity asset pipeline parses raw geometry from a single imported FBX, automatically grouping triangles that cross tile boundaries into multi-tile modules to eliminate visible grid seams. (24:36)
Detailed notes
## Background This talk was published on YouTube by BUas Games on July 11, 2018, recorded at the Everything Procedural Conference (EPC2018) hosted by Breda University of Applied Sciences. In the presentation, developer Oskar Stålberg explains how he adapted the Wave Function Collapse (WFC) algorithm to procedurally assemble stylized 3D island dioramas for his strategy game Bad North, detailing the underlying theory, custom Unity pipelines, and artistic philosophies behind his workflow.
## TL;DR Oskar Stålberg outlines his journey from AAA technical art to indie game development, detailing how he translated Maxim Gumin's 2D Wave Function Collapse algorithm into a 3D level-generation engine for Bad North. He demonstrates how WFC operates as an elimination process over possibility space, resolving strict adjacency constraints extracted from modular geometry rather than following traditional combinatorial placement. To make generated spaces playable, he embeds navigability heuristics directly into tiles, manages multi-tile groupings and deliberate asymmetry, and handles contradictions through efficient full-state reloads. Finally, he explains how minimalist, high-contrast visual styling turns the procedural dioramas into clean, readable tactical canvases while combating procedural sameness through themed sets and multi-stage navmesh validation.
## Atmosphere & style The presentation feels intimate, candid, and refreshingly craft-focused, delivered with Stålberg's modest, dry humor and pragmatic engineering mindset. Working through live visual debuggers and wireframe collapse passes—some built just the day prior—he openly shares both elegant mathematical breakthroughs and scrappy real-world compromises, such as abandoning backtracking in favor of full retries. The room carries the attentive, appreciative quiet of a technical conference audience, punctuated by chuckles at his understated self-deprecation and honest reflections on art direction.
## Bulletin - [01:33](https://www.youtube.com/watch?v=0bcZb-SsnrA&t=93s) Oskar Stålberg adapts Maxim Gumin's 2D Wave Function Collapse algorithm into 3D, treating level generation as an act of candidate elimination over grid slots rather than constructive placement. - [14:50](https://www.youtube.com/watch?v=0bcZb-SsnrA&t=890s) Bad North scales from an initial prototype of 16 modules to roughly 300 modular pieces, initializing around 100,000 candidate module states per 3D island generation pass. - [17:53](https://www.youtube.com/watch?v=0bcZb-SsnrA&t=1073s) When contradictions or gameplay validation failures occur, the engine avoids step-by-step backtracking and instead snapshots the post-boundary state to execute rapid full re-collapses. - [24:36](https://www.youtube.com/watch?v=0bcZb-SsnrA&t=1476s) A custom Unity asset pipeline parses raw geometry from a single imported FBX, automatically grouping triangles that cross tile boundaries into multi-tile modules to eliminate visible grid seams.
## Key takeaways - [01:33](https://www.youtube.com/watch?v=0bcZb-SsnrA&t=93s) Wave Function Collapse operates as a content-agnostic solver that deduces spatial arrangements purely from boundary socket constraints rather than hardcoded architectural logic. - [05:50](https://www.youtube.com/watch?v=0bcZb-SsnrA&t=350s) Slot terminology distinguishes the coordinate spaces on the grid from the modules placed within them, resolving an ambiguous use of the word 'tile'. - [08:50](https://www.youtube.com/watch?v=0bcZb-SsnrA&t=530s) Placement in WFC functions through candidate subtraction; removing invalid modules cascades recursively across neighboring cells via constraint propagation. - [10:37](https://www.youtube.com/watch?v=0bcZb-SsnrA&t=637s) Adjacency rules can be extracted programmatically by sampling and hashing border color points along tile edges instead of hand-authoring connection tables. - [14:50](https://www.youtube.com/watch?v=0bcZb-SsnrA&t=890s) Island generation in Bad North starts by fixing external boundary conditions—sky on the perimeter and water at the base—which instantly prunes a vast portion of the initial possibility space. - [17:53](https://www.youtube.com/watch?v=0bcZb-SsnrA&t=1073s) WFC lacks inherent awareness of high-level gameplay concepts like path traversability, requiring metadata-driven heuristics to bias module selection along walkable graphs. - [17:53](https://www.youtube.com/watch?v=0bcZb-SsnrA&t=1073s) Exponential compute scaling with larger grid volumes drove Bad North's core aesthetic and design choice toward compact, isolated diorama islands. - [23:05](https://www.youtube.com/watch?v=0bcZb-SsnrA&t=1385s) Bottom-up generation lets developers evaluate foundational silhouettes and slope profiles with a minimal tile set before introducing specialized edge cases. - [24:36](https://www.youtube.com/watch?v=0bcZb-SsnrA&t=1476s) Unity import tooling parses combined FBX geometry into multi-tile modules without slicing triangles, preventing repetitive grid seams and enabling smooth vertical stairs. - [27:19](https://www.youtube.com/watch?v=0bcZb-SsnrA&t=1639s) Deliberate asymmetry and transitional adapter details break modular repetition and allow decorative seams to resolve geometric width discrepancies. - [30:00](https://www.youtube.com/watch?v=0bcZb-SsnrA&t=1800s) Restrained environmental detailing acts as a functional canvas for tactical clarity and permanent battle stains, reserving visual density for structural gameplay transitions. - [30:00](https://www.youtube.com/watch?v=0bcZb-SsnrA&t=1800s) WFC is grid-agnostic and can be applied to distorted cubic lattices, spherical triangles, or arbitrary topological graphs to produce organic architectural layouts. - [33:33](https://www.youtube.com/watch?v=0bcZb-SsnrA&t=2013s) Generation applies an explicit anti-symmetry constraint to prevent generic modules from pairing with identical or mirrored duplicates along matching sockets. - [36:14](https://www.youtube.com/watch?v=0bcZb-SsnrA&t=2174s) Validation operates across multiple tiers: mid-solve bailouts trigger if paths seal before reaching sea level, followed by post-collapse navmesh checks for Viking landing spots.
## Detailed walkthrough ### [00:00](https://www.youtube.com/watch?v=0bcZb-SsnrA&t=0s) Introduction, Background, and the Power of Playable Demos
Oskar Stalberg introduces himself to the audience at Breda University of Applied Sciences, describing his focus: “My name is Oskar Stalberg. I make procedural planets and pretty things.” He originally comes from an art background, having studied at The Game Assembly in Malmö. Over time, he found himself drawn deeper into technical disciplines, shaders, and procedural systems. He spent several years in AAA at Massive Entertainment working on *Tom Clancy's The Division*, where he created the game's iconic holographic map. Beside his studio work, he developed a habit of releasing experimental side projects as interactive web browser demos rather than simple screenshots or gameplay videos. Two prominent examples include his procedural city tool *Brick Block* and an interactive miniature planet generator.
Stalberg offers this approach as key advice to students in the audience: “That's a very good way to do projects as student too, by the way, just like make things that you can publish, that you don't just publish screenshots or videos, that publish like interactive things, because they're so much more likely to be shared and it's so much more impressive, so do that.” After working at Massive and spending time at Ubisoft Montreal, he transitioned to independent development to work on his own game, *Bad North*, a stylish procedural strategy game about defending islands against invading Vikings.
- Studied game art at The Game Assembly (Malmö, Sweden). - AAA experience: Massive Entertainment (*The Division* holographic map) and Ubisoft Montreal. - Published interactive web toys: *Brick Block* and procedural planet generators. - Current project: *Bad North*, an island-defense strategy game powered by procedural generation.
---
[01:33](https://www.youtube.com/watch?v=0bcZb-SsnrA&t=93s) Discovering Wave Function Collapse and Early 3D Experiments
The core level generation in *Bad North* relies on an algorithm known as Wave Function Collapse (WFC). Stalberg clarifies that he did not invent the name and jokingly warns the audience: “I didn't name it Wave Function Collapse, so you know, don't ask me about the connection to quantum mechanics.” The algorithm was originally developed by Maxim Gumin, who designed it to take a tiny example bitmap, analyze which pixels or patterns are permitted to sit adjacent to one another, and synthesize a much larger bitmap that strictly respects those derived adjacency rules.
Upon seeing Gumin's 2D texture synthesis results, Stalberg immediately realized the concept could be generalized into three dimensions to solve procedural level architecture: “Obviously my first idea when I saw this one was like, I bet you can use this for 3D. I bet you can use this for other things than just textures to build little larger works.” The primary appeal was that the algorithm is inherently content-agnostic; it does not need to understand architectural logic, architecture styles, or geography. Instead, an artist creates a modular tile set, and the algorithm automatically deduces how pieces fit together based purely on connection constraints.
Stalberg’s earliest prototype implemented WFC over a spherical triangular tile mesh. While reminiscent of his earlier planet project, this new setup allowed the system to solve complex continuous networks, such as roads and villages, that were previously impossible without hardcoded rules. Another experiment involved generating organic cliffside platforms using just a handful of handcrafted modules. Because the system is agnostic, an artist can introduce a chimney, an overhang, or a vertical divider tile into the palette, and the solver weaves them into coherent structures whenever the boundaries match.
Stalberg also notes an aesthetic design lesson learned from an abandoned side project involving a jumping cat: vibrant, busy environments make games visually unreadable. If the procedural background is heavily saturated with color, the gameplay units and UI elements struggle to stand out, forcing him toward the cleaner, restrained art direction that defines *Bad North*.
- Concept origin: Maxim Gumin's 2D Wave Function Collapse algorithm for pattern-based bitmap synthesis. - Core philosophy: Content-agnostic constraint solving—the algorithm does not know what an object is, only how its boundaries connect. - Early prototypes: Triangular spherical tiling for roads/villages and small modular cliff dioramas. - Design takeaway: High color saturation in procedural geometry interferes with gameplay clarity and UI hierarchy.
---
[05:50](https://www.youtube.com/watch?v=0bcZb-SsnrA&t=350s) The Mechanics of WFC: Slots, Modules, and Possibility Space
To clarify the inner workings of WFC, Stalberg walks through a custom 2D visual demonstration built for the talk. He highlights a fundamental ambiguity in procedural level design: the word "tile" can mean either the coordinate space where an object sits or the mesh piece placed inside it. To eliminate confusion, he establishes strict terminology:
> “I split up the terminology into modules and slots, so like each slot picks a module, that's the terminology.”
At the start of generation, the system initializes a grid of empty slots. Each slot begins in a state of maximum entropy, containing a possibility space where every single module in the tileset is considered a valid candidate. Stalberg demonstrates how boundary constraints dictate the outcome by feeding the solver an extremely restricted two-module set: pure sky and pure dirt, with no transition piece connecting them. Because the set lacks a transition tile that has sky on one side and dirt on the other, selecting dirt in a single slot forces every other slot to collapse into dirt, while selecting sky forces the entire board to turn into sky.
When a connector tile (dirt on the bottom, sky on the top) is introduced into the set, the solver gains flexibility. Placing a sky module forces the slot directly below it to be compatible (either another sky module or the transition module), but forbids solid dirt from touching sky directly. If an invalid module configuration is given—such as providing only corner modules without any straight edges or interior fillers—the solver immediately errors out because a continuous, self-consistent loop cannot be resolved.
```mermaid flowchart TD accTitle: Wave Function Collapse Slot Resolution accDescr: Diagram illustrating how slot states transition from an initial superstate of all modules to a single collapsed module.
subgraph PossibilitySpace["1. Initial Possibility Space"] S1["Slot [x, y]<br/>Contains all modules:<br/>{Sky, Dirt, Transition, Corner}"] end
subgraph Selection["2. Observation / Selection"] S2["Slot [x, y]<br/>Selects module: [Transition]<br/>(Removes all other candidates)"] end
subgraph ConstraintPropagation["3. Neighbor Constraints"] Top["Slot [x, y+1]<br/>Above Transition:<br/>Must match Sky border"] Bottom["Slot [x, y-1]<br/>Below Transition:<br/>Must match Dirt border"] end
PossibilitySpace --> Selection Selection --> ConstraintPropagation ```
---
[08:50](https://www.youtube.com/watch?v=0bcZb-SsnrA&t=530s) Collapse as Elimination and Constraint Propagation
Stalberg emphasizes that collapse is fundamentally an act of subtraction rather than placement: “The core idea is that, yeah, you have this huge possibility space where everything is possible everywhere, and then as soon as you place something, what you're doing is not really placing something, but what you're doing is you're removing all other possibilities from that tile until there's only one remaining.”
When an alternative is removed from a slot, it triggers a chain reaction across neighboring slots:
1. A module is removed from slot $A$. 2. The solver checks whether that removed module was the last remaining candidate in slot $A$ that offered a particular boundary interface (for example, a downward-facing sky edge). 3. If no remaining modules in slot $A$ provide that interface, the adjacent slot directly below ($B$) cannot legally present an upward-facing sky edge. 4. Slot $B$ must immediately prune every module in its own local possibility set that required an upward-facing sky edge. 5. Pruning those modules from slot $B$ may eliminate the last instance of another boundary type facing its own neighbors, continuing the cycle recursively.
In a tileset without overhangs, placing a dirt block forces every slot directly beneath it to resolve to dirt as well, organically generating pillars down to the bottom of the map. Stalberg notes that when coding WFC for the first time, a brute-force approach is acceptable: after every modification, scan every single slot in the grid to verify whether its remaining modules still have valid neighbors. However, a production-ready solver optimizes this process into an event-driven propagation model. Each slot tracks the counts of its available socket types; as soon as a candidate module is deleted, it checks if it was the last of its type, and only notifies its immediate neighbors when an edge possibility has genuinely dropped to zero.
- "Placing" a tile actually means eliminating all competing candidates until only one choice remains. - Incompatible candidate elimination cascades outward recursively across adjacent slots. - Basic implementation strategy: Check full grid consistency after every collapse. - Optimized implementation strategy: Event-driven notification where slots only alert neighbors when an edge connection type is completely exhausted.
[10:37](https://www.youtube.com/watch?v=0bcZb-SsnrA&t=637s) Deriving Constraints from Texture Sampling and Constraint-Based Programming
Oskar Stalberg demonstrates how adjacency constraints can be extracted automatically from 2D pixel art tiles rather than being manually authored. For a simple 2D tile image, the system inspects the edges to determine which modules can legally sit next to one another. Stalberg explains that he derives these rules by sampling colors at three distinct points along each border: top, bottom, and sides. These three sampled colors are concatenated and hashed. Any tile sharing identical color values at those sample locations receives the matching hash, establishing an immediate index for valid neighbors. On screen, the debug numbers displayed correspond directly to the hex values of the sampled pixels, which Stalberg notes can be helpful for debugging when two modules unexpectedly refuse to connect because of a slight accidental color mismatch. He adds that one could simply round off color values before hashing to avoid being overly tedious.
```mermaid flowchart LR accTitle: Constraint Extraction Pipeline accDescr: Pipeline showing how raw pixel tiles are sampled at edge points, hashed, and matched into adjacency constraints. subgraph Sampling["Edge Sampling"] A["2D Tile Edge"] --> B["Sample 3 Points"] end subgraph Hashing["Hashing & Rules"] B --> C["Hash Color Values"] C --> D["Adjacency Constraint Rules"] end ```
Stalberg connects this mechanic to the broader discipline of constraint-based programming, which he admits he had not encountered formally when he first started the project. In this paradigm, rather than scripting step-by-step procedural placement rules, the developer simply declares what states are permissible alongside one another. The engine's job is to discover an arrangement that satisfies all conditions. Stalberg emphasizes the philosophical shift of this approach: “You're starting with just declaring what are the constraints here like what things are allowed together with which things in this case of course is strictly adjacency constraints so it's just what things are allowed to what things and then like in all constraint based programming you just like ask how are we going to give you a solution that satisfies those constraints.” Hard constraints enforce strict rules, while heuristics—such as probability weights or Perlin noise randomness—guide selection whenever multiple valid possibilities remain.
- Color sampling takes three sample points per tile edge to form hash keys. - Debug views expose edge hex colors to quickly spot minute authoring mismatches. - Hard constraints define strict legal neighbors; heuristics guide choices among multiple valid candidates.
---
[13:37](https://www.youtube.com/watch?v=0bcZb-SsnrA&t=817s) A Minimal Wave Function Collapse Schematic
To make the mechanics of propagation concrete, Stalberg presents a schematic diagram illustrating the simplest possible system. It features only two adjacent slots and two boundary types: `0` representing grass and `1` representing road. Each slot contains four candidate modules that rely on the neighboring slot's available options to stay valid.
```mermaid flowchart TD accTitle: Constraint Propagation Dependency accDescr: Diagram illustrating how removing an option in Slot A forces dependent candidate modules in Slot B to collapse and be eliminated. subgraph SlotA["Slot A"] A1["Candidate Module"] end subgraph SlotB["Slot B"] B1["Dependent Module 1"] B2["Dependent Module 2"] end A1 -.->|"relies on"| B1 A1 -.->|"relies on"| B2 ```
Stalberg walks through what happens when an option is pruned: if a road tile in the first slot is eliminated, the connection feeding into dependent pieces in the second slot is broken. Deprived of a matching neighbor, those dependent modules in the adjacent slot must also be removed. The system resolves these implications transitively across the entire grid. Stalberg summarizes this core lifecycle as establishing a “big possibility space and then collapse down and resolving implications of your constraints.”
- Minimal demonstration uses two slots and two edge types: `0` (grass) and `1` (road). - Four potential modules populate each slot initially. - Removing a single candidate severs dependencies and cascades into removals in adjacent cells.
---
[14:50](https://www.youtube.com/watch?v=0bcZb-SsnrA&t=890s) Visualizing 3D Island Collapse in *Bad North*
Transitioning to *Bad North*, Stalberg pulls up the game's campaign map and activates a live debug visualization to show 3D level generation in real time. He notes that the generation is running far slower than normal because drawing the wireframes of all candidates within each slot introduces heavy rendering overhead. Across the grid, a single tile can harbor hundreds of module variants once rotations and mirrors are factored in, yielding roughly 100,000 total possibilities initially.
```mermaid flowchart TD accTitle: Level Generation Lifecycle accDescr: Step-by-step generation flow from initializing boundary constraints to final tile collapse in Bad North. subgraph Setup["Initialization"] I1["Initialize 3D Grid"] --> I2["Apply Boundaries: Sky Top/Sides & Water Base"] I2 --> I3["Propagate Boundary Constraints"] end subgraph Collapse["Interactive Collapse"] I3 --> C1["Place Key Features: House Foundations"] C1 --> C2["Wave Propagation Prunes Invalid Candidates"] C2 --> C3["Cascade Accelerates to Final Mesh"] end ```
Unlike the unconstrained 2D sandbox, an island level begins with mandatory external boundary conditions: the space outside must be sky, the bottom slice must be water, and the core cannot be pure solid rock throughout. Propagating these initial restrictions instantly prunes a vast swath of candidates, causing ghostly wireframe silhouettes of the island to emerge before any single module is definitively locked in. The first actual concrete placement is typically a foundation hole reserved for a house (the houses themselves are dynamic props placed later). As the house foundation settles, surrounding options prune immediately. Stalberg observes that while the system starts slowly while resolving initial implications across thousands of possibilities, it accelerates rapidly toward the end because the vast majority of conflicts have already been eliminated.
- Wireframe visualization exposes approximately 100,000 candidate module states during initialization. - Boundary rules mandate open sky along external boundaries and sea level at the base. - Island houses are placed as empty footprint sockets; actual building meshes are dynamically added post-generation.
---
[17:53](https://www.youtube.com/watch?v=0bcZb-SsnrA&t=1073s) Scaling Limits, Navigability Heuristics, and Retry Logic
As Stalberg attempts to generate larger islands, the computational cost escalates noticeably. Stalberg points out: “Of course that's with a lot of procedural things the generation time increases exponentially with size so the bigger the map the slower it is though we always resolve that by playing on small islands.” Beyond pure generation speed, he tackles a fundamental limitation of Wave Function Collapse: the algorithm possesses no inherent understanding of high-level gameplay concepts such as whether an island is traversable. While one can validate navigability post-generation by running pathfinding checks from the beach up to the houses, doing so naively can result in generating many unusable maps that must simply be discarded.
```mermaid flowchart TD accTitle: Island Generation with Navigability and Retries accDescr: Flowchart illustrating the navigability heuristics and contradiction reload loop in Bad North. subgraph Generation["Step 1: Expand Navigability"] G1["Seed Navigable Root at House"] --> G2["Heuristic Prefers Tiles Expanding Navigable Paths"] G2 --> G3["Propagate Connectivity Outwards via Green Links"] end subgraph Validation["Step 2: Check & Retry"] G3 --> V1{"Valid Level? (e.g. Sufficient Beach)"} V1 -->|"Contradiction or Validation Fail"| V2["Discard & Reload Initial Resolved State"] V2 --> G1 V1 -->|"Pass"| V3["Final Island Diorama"] end ```
To solve this, Stalberg embeds navigability data directly into each module. Modules contain simplified connectivity data—visualized in the debug tool as green lines—that indicate whether units can walk across them to adjacent cells. When the generator has free choices rather than forced deductions, it uses a navigability heuristic: it anchors generation at a house known to be walkable, and then biases module selection outward along those green navigation lines to ensure continuous paths down to the beaches.
When the generator hits an impossible contradiction or fails gameplay checks (such as not generating enough beach landing area for Viking longships), it must recover. Stalberg explores the dilemma of backtracking: > “That's also an interesting question of like backtracking in generation in general like would you when you make an error when you run into a contradiction do you go back and then try something else or do you... I tried that a bunch of times but the thing is that you don't know how far back you have to go you don't know if you like maybe the error was implied in the first tiles you placed so in my case I solved it by just I save the initial state when it's resolved all the initial contradictions and then we just if something fails if it runs into a contradiction it just reloads that and collapses again this way faster than symmetric.”
Stalberg concludes by noting that he had only built this wireframe collapse visualization the previous day. He points out how the system naturally constrains edges early: outer ocean corners exhibit very low entropy from the start, as their possibilities are immediately restricted to water, submerged reefs, or corner beach pieces.
- Procedural generation time scales exponentially with grid volume, motivating *Bad North*'s design choice of compact diorama islands. - Modules carry simplified topological metadata (displayed as green lines) to ensure pathfinding continuity. - Backtracking step-by-step proved ineffective; the engine instead snapshots the post-boundary state and restarts the collapse entirely whenever a contradiction occurs.
[22:20](https://www.youtube.com/watch?v=0bcZb-SsnrA&t=1340s) Navigability Heuristics and Expanding the Walkable Space
Observing a taller procedural generation pass, Oskar demonstrates how the algorithm resolves island geometry through initial contradictions and connectivity rules. A visible staircase emerges, showing the underlying navigable network snaking up across elevation changes and looping back down. To keep the play space coherent, the generation logic tracks bounding boxes that encapsulate the navigable graph.
```mermaid flowchart TD accTitle: Navigability Heuristic Tile Evaluation accDescr: Flowchart illustrating how candidate tiles are evaluated and scored based on their expansion of navigable space. subgraph TileEvaluation["Candidate Tile Scoring"] A["Candidate Tile Proposed"] --> B{"Does tile connect to network?"} B -->|"Yes"| C{"Expands navigable bounding box?"} B -->|"No"| D["Standard / Low Score"] C -->|"Yes"| E["High Heuristic Score"] C -->|"No"| D end ```
The algorithm actively prioritizes island connectivity by scoring potential tile placements against their contribution to the player's movement network. When a candidate tile would increase the size of an existing navigable bounding box, the system awards it a high score, steering the collapse toward broad, walkable multi-tiered dioramas rather than pinched, dead-end landmasses.
[23:05](https://www.youtube.com/watch?v=0bcZb-SsnrA&t=1385s) Bottom-Up Tilesets and Component-Driven Generation
Switching to an unshaded wireframe view, Oskar reveals the complete collection of modular building blocks used for *Bad North*. While the project eventually grew to include around 300 individual tiles, it started with a tiny subset of foundational pieces. Traditional combinatorial generation usually forces artists to hand-craft every conceivable permutation up front to satisfy high-level abstractions like voxels. By contrast, Wave Function Collapse works inversely from the ground up:
> “Because this algorithm, as I said, since it solves the constraints, because with a lot of combinatorial things you need to create a lot of different permutations, which is a pain, but since this one is driven from the components instead of the content being placed to sort of satisfy the higher-level abstraction... you can get it going with very, very few things.”
By starting with a minimalist core, Oskar could immediately evaluate key aesthetic baselines: - Defining the precise profile and silhouette of ground edges. - Determining the slope angles and corner treatments. - Dialing in the global target density for geometric detail.
Once those basics function, adding content becomes trivial. Highly specific elements like staircases inevitably demand numerous permutations to handle diverse elevations and alignments, whereas specialized environmental accents—such as isolated sea rocks that only border water—require only a single, specialized one-off tile.
[24:36](https://www.youtube.com/watch?v=0bcZb-SsnrA&t=1476s) In-Editor Adjacency Visualizers and Mesh Slicing
To verify edge compatibility across the expanding library without wrestling with strict organizational hierarchies in Maya, Oskar created custom tooling in Unity. Selecting an edge of a tile automatically highlights all compatible mating surfaces across the entire library. This allows instant visual confirmation that newly authored assets will mesh seamlessly with existing components.
```mermaid flowchart LR accTitle: Tile Asset Import and Slicing Pipeline accDescr: Flowchart showing the pipeline from full FBX import in Unity to analyzed, multi-tile modules. subgraph UnityImport["Unity FBX Processing"] direction TB M["Single Merged FBX Mesh"] --> P["Parse Polygons into Tile Grid"] P --> S{"Triangle spans across tile boundary?"} S -->|"Yes"| B["Merge into Multi-Tile Module"] S -->|"No"| A["Keep as Single-Tile Element"] B --> E["Analyze Edges & Adjacency Sockets"] A --> E end ```
Rather than meticulously cutting, positioning, and exporting individual tile meshes in 3D modeling software, Oskar imports everything as one combined FBX. The Unity import pipeline parses the raw geometry, sorts the triangles into distinct grid cells, and evaluates which parts belong together:
- The importer inspects the mesh without slicing triangles: if a triangle spans across adjacent tile coordinates, the pipeline groups those units into a single multi-tile piece. - Multi-tile blocks break up obvious grid repetition, allowing flowing terrain, sweeping corners, and connected staircases that climb smoothly between vertical tiers. - Subtle vertex displacements—such as nudging an internal vertex off-center—trick the eye into reading multi-part assemblies as a continuous, organic surface rather than segmented blocks. - Baked green line markers define embedded paths, feeding directly into the navigability heuristic during island generation.
[27:19](https://www.youtube.com/watch?v=0bcZb-SsnrA&t=1639s) Purposeful Asymmetry and the Philosophy of Detail
Oskar details how asymmetric modular assets reduce visual monotony. Modeling a corner column with one side subtly narrower than the other creates four distinct visual permutations simply by rotating and mirroring the piece across an elevated pillar.
This asymmetry also enables specialized transitional "adapters." Where mismatched edges meet, Oskar inserts a small crack or seam. The detail is decorative, but it serves a direct architectural function: it flips the mirror orientation from thick-to-thin, resolving the geometric discrepancy.
> “And that, I think that's a nice way of thinking about detail in general: like making detail that's meaningful, and that is changing things.”
Examining a fully generated island, Oskar argues that blank surfaces do not need heavy textural noise. An obvious tiling grass texture provides no real gameplay value. Instead, visual storytelling lives at structural boundaries:
- Clear grass boundaries immediately communicate flat, playable tactical plateaus. - Transition edges define the drop-off from grass to sheer cliff faces. - Procedurally scattered convex edge tufts break up hard mathematical boundaries. - Adapter seams and deliberate cracks transform simple alignments into lived-in, believable stone architecture.
> “You don't need detail everywhere; you need detail to tell small stories, and usually making transitions between things—that's what detail should do, typically.”
[30:00](https://www.youtube.com/watch?v=0bcZb-SsnrA&t=1800s) Environment as a Canvas and Q&A on Alternative Grids
The diorama's minimalist styling directly serves gameplay clarity. The environment acts as an uncluttered stage for tactical combat, leaving the focal weight on unit movement. Units are commanded as squads rather than individuals, so individual soldiers sport simple geometric silhouettes that emphasize collective group cohesion. The island dioramas function as literal canvases: during battles, blood from clashes permanently stains the white cliffs and green grass. Oskar briefly jokes about the game's physics not being fully frame-rate independent due to earlier technical oversights before concluding his presentation to applause.
Opening the floor to questions, an attendee asks about applying Wave Function Collapse to alternative grids. Oskar notes that while he has experimented with spherical triangular grids, his primary ambition is exploring distorted cubic modules: - Cubes can be warped to meet at irregular vertex junctions, creating intersections where five or three cubes converge at a single vertex. - Distorting the interior geometry of those irregular cells would naturally produce curving, organic layouts ideal for medieval streets and alleyways. - Triangular and hexagonal lattices naturally favor sprawling planetary and continental silhouettes, whereas cubic grids provide the structured, orthogonal support needed for architectural interiors and small buildings. - The algorithm remains grid-agnostic: developers can generate across arbitrary topological graphs, mixing 2D planes, triangles, and 3D cubes within the same framework.
A following question focuses on the demo's interactive placement mode, prompting Oskar to outline the concept of "mixed-initiative" procedural generation, where user input establishes high-level intent while the system algorithmically handles constraint propagation.
[33:33](https://www.youtube.com/watch?v=0bcZb-SsnrA&t=2013s) Module Selection Rules, Seeding, and Mirror Constraints
Addressing an audience question about whether content is placed manually or if the algorithm operates with full initiative, Oskar Stålberg explains that the game itself makes all module selections procedurally rather than through manual authoring. While a simple demo might feature only sixteen possible module options, *Bad North* contains roughly 300 modules, making an interactive manual picker impractical to present without dedicated tools: “I'm sure there's a really good proper interactive tool in this but I haven't built it so no the game the game just like picks them according to different rules.”
The generation sequence begins by placing essential gameplay elements, starting with houses because the core defense mechanics fundamentally rely on their presence. From there, selection is driven by metadata, thematic groupings, and bounding boxes that enforce specific placement restrictions. One critical constraint prevents certain tiles from appearing adjacent to copies of themselves or direct mirrored duplicates. Because identical or mirrored sockets fit against each other with mathematical perfection, the algorithm naturally tends to pair them up, producing jarring, unnatural symmetry across generic pieces: “Quite an important rule is for modules I don't want to be next to a mirror of themselves because that usually looks quite bad when you have generic pieces next to each other and how comes quite often because of course a piece fits perfectly next to the mirror of itself.”
- **Module Count:** Scales from roughly 16 tiles in simple toy demonstrations up to around 300 modular pieces in the full game. - **Initial Anchor:** Generation starts by placing houses first, ensuring vital strategic targets exist before surrounding terrain settles. - **Anti-Symmetry Safeguard:** Explicit rules block tiles from neighboring identical or mirrored instances of themselves to avoid repetitive visual artifacts.
---
[34:39](https://www.youtube.com/watch?v=0bcZb-SsnrA&t=2079s) Thematic Variety and Combating Procedural Sameness
To ensure that generated levels feel distinct across an entire campaign, Stålberg organizes modules into thematic sets with customized rules. Navigating his project—noting wryly that he is presenting from an unfamiliar workstation—he demonstrates several tile categories, including dark cave modules reserved exclusively for specific islands, ruined structures, and rugged cliff sections with massive boulders. In one active level preview demonstrating the rock theme, the system resolves a dramatic, cliffside overhang acting as a natural bridge across the terrain.
These thematic groupings directly address one of procedural generation's most persistent pitfalls: aesthetic monotony. Swapping out geometry sets, adjusting color palettes, and modifying surface foliage allow the engine to produce dramatically different moods while relying on the same underlying solver logic. Answering an aside about mesh complexity, Stålberg remarks that because everything in the game is stylized low-poly, he models the assets himself with just a handful of triangles, keeping geometric overhead minimal.
> “That's also always a challenge with procedural generation that think things can look to seemy and this is one of the ways I approach that.” — Oskar Stålberg
---
[36:14](https://www.youtube.com/watch?v=0bcZb-SsnrA&t=2174s) Validation Stages, Navmesh Checks, and Failure Recovery
An audience member asks about handling failed generation runs—specifically referencing cases where the algorithm must restart because it failed to generate enough valid landing beaches—and whether the engine shifts tile weights between attempts or merely rerolls the seed. Stålberg clarifies that the solver operates under two distinct layers: hard adjacency constraints that must always be satisfied, and soft probability weights that bias how frequently certain tiles appear within specific spatial volumes. When a failure occurs, the engine does not necessarily need to reweight the tiles; rather, the process is governed by a series of mid-generation and post-generation validation checks.
```mermaid flowchart TD accTitle: Island Generation and Validation Pipeline accDescr: Flowchart illustrating the step-by-step validation of island generation from initial constraints to navmesh beach verification.
subgraph Generation ["Wave Function Collapse"] A["Seed Anchors & Themes\n(Houses, Spatial Weights)"] --> B["Propagate Tile Adjacency\n(Hard Constraints)"] B --> C{"Check In-Progress\nNavigable Island"} end
subgraph Validation ["Island Validation Checks"] C -->|"Closed before reaching bottom"| D["Discard & Restart Generation"] C -->|"Pathways Open"| E["Construct Navmesh"] E --> F{"Sufficient Landing Beaches\nfor Viking Boats?"} F -->|"Too Few Beaches"| D F -->|"Requirements Met"| G["Final Island Diarama Accepted"] end ```
The beach requirement is verified after the primary Wave Function Collapse pass completes. Once the geometric island is fully resolved, the engine generates the navigation mesh and measures whether there are sufficient shoreline entry points where incoming Viking longships can land. If the beach count is insufficient, the level is discarded and restarted. However, Stålberg also implements early-exit validations during the wave propagation itself: if the system detects that the navigable island graph has closed off prematurely before reaching the bottom of the grid, it abandons the attempt immediately, saving valuable compute time by avoiding downstream navigation mesh calculations.
- **Constraint Hierarchy:** Strict adjacency rules act as absolute, inviolable constraints, while regional probabilities nudge aesthetic variety without breaking connectivity. - **Mid-Generation Bailout:** If the traversable network closes off before expanding to the base level, the generator aborts mid-solve. - **Post-Generation Navmesh Check:** Viking landing beaches are validated on the generated navmesh; failing to meet landing quota triggers an automatic restart.
## Notable quotes > [27:19](https://www.youtube.com/watch?v=0bcZb-SsnrA&t=1639s) You don't need detail everywhere; you need detail to tell small stories, and usually making transitions between things—that's what detail should do, typically. — Oskar Stalberg
## Mentioned Oskar Stalberg, Bad North, Wave Function Collapse, Maxim Gumin, Massive Entertainment, Ubisoft Montreal, The Game Assembly, Tom Clancy's The Division, Brick Block, Breda University of Applied Sciences, Unity, Maya
## Video description Bad North is a procedurally generated strategy game about fighting Vikings. Oskar Stalberg will talk about how the Wave Function Collapse algorithm is employed to magically assemble idyllic island dioramas from handcrafted tilesets. You'll learn about the theory behind WFC, its implementation and further development in Bad North, as well as the tools and pipeline surrounding it. Lastly, Oskar will cover how he approaches the integration of art and tech aesthetically.
Everything Procedural is a yearly conference organised by the Games programmes of Breda University of Applied Sciences (BUas). The conference brings together experts and developers in the field of procedural generation and focuses on procedural game content and its application in AAA or innovative games.
https://www.everythingprocedural.com/
https://www.buas.nl/en/programmes/creative-media-and-game-technologies https://www.buas.nl/en/programmes/master-game-technology
[Source](https://www.youtube.com/watch?v=0bcZb-SsnrA)