Phase by phase.

Map generation runs at startup and on regeneration (R key in debug builds). Each phase builds on the output of the previous one. The pipeline is designed so phases 3 and 4 could have been implement, but due to time constraints only the first two were completed.

01

Voronoi Regionalization Done

Bridson's Poisson disk sampling algorithm (2007) places seed sites with a minimum separation distance derived from map area ÷ site count. This prevents site clustering while allowing the Poisson algorithm to fill the space properly. A spatial hash grid then assigns every tile to its nearest site in O(k) per tile rather than O(n). Each region gets a unique ID used for terrain assignment and POI placement.

02

Perlin Noise Object Placement Done

Perlin's improved noise (2002) is sampled at every 2nd tile with configurable frequency, octave count, and persistence. Tiles above the placement threshold get a world object, tree stump, root, tree canopy. POI exclusion zones and terrain type filtering prevent objects overlapping structures. Frequency, octaves, and threshold are all tunable in MapGenerator::GenerationSettings.

03

Cellular Automata Planned

A terrain smoothing pass to remove isolated tiles and create more organic region edges. Stubbed in MapGenerator::generate(), the phase is called but does nothing. Would run after Perlin phase.

04

Connectivity Check Planned

Dijkstra flood fill from the player hideout to verify all POIs are reachable. Stubbed in the same place.

Why Poisson replaced rejection sampling.

The original site placement used rejection sampling, generate a random position, check it against all existing sites, discard if too close, try again. It works, but it degrades badly as the map fills up. The replacement: Bridson's Poisson disk sampling with a spatial grid, is still in the codebase as generateSitesRejection() for comparison. It's not called in production.

Rejection Sampling (original)

Generates random positions and discards those that violate minimum distance. Each new attempt checks against all existing sites. Gets slower the more sites are placed.

84 ms
O(n²) — 80x80 tile map

Poisson Disk Sampling (current)

Bridson (2007) uses an active list and background grid to guarantee minimum separation in expected O(n) time. Implementing it correctly required careful study of the paper and a fair bit of debugging.

11 ms
O(k) per tile

Fixed anchors in a procedural world.

The player hideout is placed at map centre before generation starts. Voronoi sites and Perlin noise placement both respect POI exclusion zones. The site placement step explicitly avoids generating sites within 400px of the hideout.

Farm and village POIs are placed at the Voronoi sites furthest from the hideout, this is done by sorting sites by distance before placement, so the nearest sites are never picked for POIs.

Player Hideout

Always at map centre. Collision polygons loaded from a .tmx Tiled template file. Exclusion radius keeps Voronoi sites away from it so the player spawn area is clear.

Farms

Up to 2 per map, configurable. Placed at the furthest available Voronoi sites. Large sprite, 1948x2016px, with collision from a Tiled template.

Villages (not yet)

Config registered, sprite and template paths empty. The POIConfigRegistry entry is there, it just has no art yet. Adding content means filling in those fields, the generation logic doesn't need changing.