Categories
Software Development

Procedural Hex Map Generation with Wave Function Collapse

Learn how to generate realistic hex maps using Wave Function Collapse, including tile definitions, implementation tips, and trade-offs for game developers.

If you need to generate large, believable hex maps for games or simulations—without hand-authoring every tile—combining procedural generation with the Wave Function Collapse (WFC) algorithm delivers striking results. This guide dives deep into how WFC can be adapted for hex grids, the real trade-offs, and how to build your own map generator that creates rich, varied worlds from a compact tile set.

Key Takeaways:

  • Understand how Wave Function Collapse (WFC) can be extended from square to hex grids, and why this matters for procedural map generation.
  • See real-world tile definition approaches for hex tiles—including how to encode edge constraints, weighting, rotations, and elevation.
  • Walk through an implementation of procedural hex map generation, with practical code patterns and optimization techniques.
  • Get a clear-eyed view of WFC’s limitations, performance bottlenecks, and alternatives for large or complex maps.
  • Learn actionable pro tips for debugging and tuning WFC-based hex map generators in real projects.

Why Use Wave Function Collapse for Procedural Hex Maps?

Procedural map generation has long been a staple in games and simulations, but most mainstream algorithms (Perlin noise, cellular automata, random walkers) struggle to produce the kind of tile-to-tile pattern coherence that makes a map feel hand-crafted. Wave Function Collapse (WFC), pioneered by Maxim Gumin (official repo), approaches the problem differently: it samples example tile arrangements and synthesizes new maps that obey the same local adjacency constraints.

On a hex grid, the challenge and potential are amplified. Each tile now has six neighbors instead of four, which increases the constraints and expressiveness, but also the computational complexity. As detailed in Felix Turner’s procedural hex map project, this enables the emergence of natural-looking coastlines, branching rivers, connected road networks, and variable terrain—all from a compact, data-driven tile definition set.

Why does this matter? For game developers and simulation practitioners, WFC unlocks:

  • Rapid prototyping of large, consistent worlds without hand-authoring every region
  • Control over style and rules by tweaking tile definitions and weights
  • Generation diversity—every map is different, but always valid

Hex grids are particularly valuable in tactical games, biological simulations, and resource management tools, where adjacency and flow matter. The WFC approach goes far beyond basic noise-based terrain, delivering structure and visual appeal that’s hard to hand-code.

Core Concepts: Hex Tiles, Tile Definitions, and WFC Mechanics

To implement WFC on a hex grid, you need to rethink the fundamentals:

  • Tile Set: Each tile encodes the possible terrain (grass, water, road, river, etc.) on each of its six edges. Tiles may support rotations and elevations, multiplying the effective state space. For example, Felix Turner’s generator used 30 base tile types, each with 6 rotations and 5 elevation levels, totaling 900 possible states per cell (source).
  • Edge Constraints: Tiles can only be placed adjacent to others if their touching edges match (e.g., a road edge must meet a road edge).
  • Weighting: Each tile can be assigned a weight to bias how frequently it appears, enabling rare features or common terrain patterns.
  • Superposition: Every cell starts as a “superposition” of all possible tile states. At each step, the cell with the fewest options (“lowest entropy”) is collapsed to a definite state, and constraints are propagated to its neighbors.

Here’s a representative hex tile definition (from Turner’s post):

// Example hex tile definition with edge constraints and weighting
{
  name: 'ROAD_D',
  mesh: 'hex_road_D',
  edges: {
    NE: 'road',
    E: 'grass',
    SE: 'road',
    SW: 'grass',
    W: 'road',
    NW: 'grass'
  },
  weight: 2
}
{
  name: 'ROAD_D',
  mesh: 'hex_road_D',
  edges: {
    NE: 'road',   // Northeast edge is a road
    E:  'grass',  // East edge is grass
    SE: 'road',   // Southeast edge is a road
    SW: 'grass',  // Southwest edge is grass
    W:  'road',   // West edge is a road
    NW: 'grass'   // Northwest edge is grass
  },
  weight: 2      // More common than rare tiles
}

In practice, you build an array of such tile definitions, including all required rotations and elevations. The WFC algorithm then handles the combinatorial explosion by only considering valid adjacencies at each collapse step.

You landed the Cloud Storage of the future internet. Cloud Storage Services Sesame Disk by NiHao Cloud

Use it NOW and forever!

Support the growth of a Team File sharing system that works for people in China, USA, Europe, APAC and everywhere else.

WFC Mechanics: Observation, Collapse, and Propagation

  1. Start with all cells in a fully unconstrained state (all tiles possible).
  2. Find the cell with minimal nonzero entropy (fewest legal tile choices).
  3. Randomly choose a valid tile for this cell, weighted by its frequency.
  4. Propagate the constraint to neighbors: any tile that cannot legally border the chosen tile is eliminated as a possibility.
  5. Continue until either all cells are resolved, or a contradiction occurs and backtracking/retry is needed.

This process is described in the official WFC documentation as:

// Pseudocode summary drawn directly from the documentation
Read input bitmap and count NxN patterns. (optional: augment with rotations/reflections)
Create output array ("wave")—each element is a superposition of NxN patterns.
Initialize wave with all patterns possible (fully unobserved).
Repeat:
    - Find cell with minimal nonzero entropy.
    - Collapse this cell: pick one state according to pattern distribution.
    - Propagate constraints to neighbors.
Until: all cells observed (success) or contradiction found (failure).
Read input bitmap and count NxN patterns. (optional: augment with rotations/reflections)
Create output array ("wave")—each element is a superposition of NxN patterns.
Initialize wave with all patterns possible (fully unobserved).
Repeat:
    - Find cell with minimal nonzero entropy.
    - Collapse this cell: pick one state according to pattern distribution.
    - Propagate constraints to neighbors.
Until: all cells observed (success) or contradiction found (failure).

On a hex grid, edge matching and propagation need to account for hex adjacency. The core logic stays the same, but the adjacency lookup and rotation math are more complex than in square grids.

Implementation Walkthrough: Building a Procedural Hex Map with WFC

Step 1: Define Your Tile Set
Build a library of hex tile definitions, covering all terrain and feature types you want. Each definition should specify the edge types for all six edges, a mesh or visual representation, and a weight. Automate the generation of rotated versions if possible.

// Example: Programmatic generation of rotated tile variants
const baseTile = {
  name: 'RIVER_BEND',
  edges: ['river', 'grass', 'grass', 'river', 'grass', 'grass'],
  weight: 3
};

function rotateTile(tile, rotation) {
  // Returns a new tile definition rotated by 'rotation' steps (0-5)
  return {
    name: tile.name + '_rot' + rotation,
    edges: tile.edges.slice(rotation).concat(tile.edges.slice(0, rotation)),
    weight: tile.weight
  };
}

This lets you generate the full set of tile possibilities without manual duplication. For a real-world map, you’ll want at least 20–40 base tiles, each with 6 rotations and any relevant elevation or variant states.

Step 2: Build the Hex Grid Structure

Represent the map as an array of cells, each storing its current possible states (all tiles at start). Use a data structure that makes it efficient to query and update neighboring cells, as constraints will need to be propagated in all six directions.

// Example: Hex grid cell with state tracking
class HexCell {
  constructor(q, r) { // q,r are axial hex coordinates
    this.q = q;
    this.r = r;
    this.possibleTiles = new Set(allTileVariants); // All tile states possible at start
    this.collapsed = false;
    this.selectedTile = null;
  }
}

Step 3: Implement WFC Core Loop

The main loop repeatedly selects the cell with the lowest entropy and collapses it. After each collapse, propagate constraints to all neighboring cells, eliminating impossible tile states. If a contradiction (no legal tiles) is found, backtrack or restart as needed. For large maps, backtracking heuristics and early-abort strategies are crucial for performance (detailed discussion).

// Minimal WFC core loop (simplified)
while (!allCellsCollapsed()) {
  let cell = findCellWithLowestEntropy();
  if (!cell) break; // All cells collapsed
  let tile = chooseRandomWeighted(cell.possibleTiles);
  cell.selectedTile = tile;
  cell.collapsed = true;
  propagateConstraints(cell, tile);
}

For a working, production-grade hex WFC, refer to the live implementation and code.

Step 4: Render and Post-Process

After all cells are resolved, render the map using your preferred engine (Three.js, Unity, custom WebGPU). Post-processing steps can add dynamic shadows, highlight features, or smooth coastlines. See Turner’s article for a complete post-processing pipeline and WebGPU optimization tips.

AspectSquare WFCHex WFC
Neighbor Count46
Tile Variant ExplosionModerateHigh (rotations & elevations)
Visual AppealGrid-like patternsOrganic, natural
Implementation ComplexityLowerHigher (adjacency, rotation math)

For more on the architecture and real-world deployment of dashboards and data-driven maps, see our coverage of real-time OSINT dashboard architectures.

Considerations and Trade-offs

No algorithm is a silver bullet. WFC for hex maps has important limitations and trade-offs that must be considered before using it in production:

  • Performance and Scalability: The combinatorial explosion from six neighbors per tile, multiple rotations, and elevations leads to heavy memory and CPU use. As reported by practitioners, large hex maps (e.g., 4,100 tiles) can take 20+ seconds to generate, even with WebGPU acceleration (source).
  • Backtracking Overhead: Brute-force backtracking gets slow fast. As noted on Hacker News, heuristics to minimize backtracking (e.g., prioritizing cells with lowest entropy or most constrained neighbors) are essential for anything beyond toy maps.
  • Local vs. Global Coherence: WFC is excellent at enforcing local adjacency rules, but doesn’t guarantee global structure (e.g., continent shapes, river sources). Hybrid approaches (combining WFC with high-level macro constraints or post-processing) may be required for certain map types (see research).

Alternatives: If your maps don’t require strict adjacency constraints or you need faster generation at scale, consider:

  • Cellular automata (for organic cave or landmass generation)
  • Perlin/simplex noise (for elevation and terrain blending)
  • Hand-authored “stamps” or region templates for guaranteed global structure

For a full discussion of the foundational concepts behind wave function collapse, see Wikipedia’s entry. For practical tips, the tips and tricks guide by BorisTheBrave is highly recommended.

Common Pitfalls and Pro Tips

  • Tile Set Completeness: Incomplete or poorly designed tile sets can cause deadlocks or ugly artifacts. Always test your tile combinations in small grids before scaling up.
  • Edge Consistency: Ensure all tile edges have clear, unambiguous labels—typos or mismatches will create unsolvable constraints. Automate validation of your tile set where possible.
  • Debugging: Visualize cell entropy (number of possible states) during generation. Highlight contradictions and backtracking steps to pinpoint problematic tiles.
  • Heuristic Tweaks: Experiment with different heuristics for cell selection—lowest entropy is standard, but sometimes prioritizing cells near already-collapsed regions helps avoid contradictions.
  • Performance: Profile your code. For large maps, parallelize constraint propagation or offload to GPU if possible, as in the WebGPU implementation described by Turner.

For more advice on building robust procedural pipelines, see our post on effective log messages for software operations.

Conclusion and Next Steps

Wave Function Collapse enables a new level of procedural map generation, especially when adapted to hex grids for organic, varied terrain. While the approach carries real costs in complexity and performance, it pays off in map diversity and visual coherence—making it a top choice for practitioners who need believable worlds at scale. Start with small tile sets, add rotation and elevation as needed, and profile early to handle performance bottlenecks.

Next steps: Study live demos and open source implementations (see here), experiment with your own tile sets, and explore hybrid techniques for balancing local and global map structure. For broader context on procedural content generation and its trade-offs, our analysis of real-time dashboard architectures offers additional insights.

Sources and References

This article was researched using a combination of primary and supplementary sources:

Supplementary References

These sources provide additional context, definitions, and background information to help clarify concepts mentioned in the primary source.

Critical Analysis

Sources providing balanced perspectives, limitations, and alternative viewpoints.

By Rafael

I am Just Rafael, but with AI I feel like I have supper powers.

Start Sharing and Storing Files for Free

You can also get your own Unlimited Cloud Storage on our pay as you go product.
Other cool features include: up to 100GB size for each file.
Speed all over the world. Reliability with 3 copies of every file you upload. Snapshot for point in time recovery.
Collaborate with web office and send files to colleagues everywhere; in China & APAC, USA, Europe...
Tear prices for costs saving and more much more...
Create a Free Account Products Pricing Page