tilemap/

Tilemap & World Gen

Procedural map generation — path-guaranteed walkers, seeded noise maps, corridor utilities, and bitmask tile resolving.

Tilemap & World Gen

Four modules for building procedural 2D worlds. The core design principle: MapWalkerA guarantees a connected, walkable path between every area. Noise is great for terrain shapes but can’t promise connectivity — use walkers when a player must always be able to reach the destination.

Include

<script src="/js/dn/tilemap/mapWalkers.js"></script>
<script src="/js/dn/tilemap/noiseMapGen.js"></script>
<script src="/js/dn/tilemap/worldGenUtils.js"></script>
<script src="/js/dn/tilemap/tileMapBitmask.js"></script>

MapWalkerA — mapWalkers.js

A grid-based walker that claims tiles as it moves. Three modes:

ModeBehavior
'num'Walk until N tiles are claimed. Use for area blobs (lakes, rooms, forests).
'target'Bias toward a target tile. Use for corridors connecting two areas.
'numtarget'Claim N tiles while biasing toward a target (hybrid).

Constructor

new MapWalkerA({
  mapData:        [],         // flat tile array (writes startTileValue to claimed tiles)
  mapWidth:       64,
  mapHeight:      64,
  startRow:       0,
  startCol:       0,
  startTileValue: 1,          // tile value written to every claimed tile

  // num / numtarget modes
  numTiles:       200,        // total tiles to claim

  // target / numtarget modes
  targetRow:      32,
  targetCol:      32,
  likelihoodFactor: 0.7,      // 0–1: how strongly the walker biases toward target

  // optional
  radiusConstraint: 15,       // max distance from start (num mode only)
  onTileDiscovered: (row, col) => {},  // callback after each step
})

Methods

MethodDescription
walk()Run synchronously. Returns when complete.
generateAsync(stepCallback)Step one tile at a time, calling stepCallback each step. Use for animated generation.

Stuck Backtracking

When a walker is boxed in with no free neighbors, it backtracks along its own discoveredTiles stack (DFS unwind) until it finds a tile with a free neighbor. Never teleports — teleporting would break the path guarantee. Treat map edges as walls.

Key Rule: No Teleporting

The walker’s core value is a guaranteed connected path. If you need path-guaranteed corridors between two areas, use target mode. If connectivity doesn’t matter, use NoiseMapGen instead (simpler and faster).


NoiseMapGen — noiseMapGen.js

Seeded, layered noise for organic terrain shapes. Three noise types:

TypeOutput rangeBest for
ValueNoise[0, 1]Elevation, moisture, cave density
PerlinNoise~[-1, 1]Smooth gradients, wind, fog
SimplexNoise~[-1, 1]Faster than Perlin, similar quality

Constructor

new NoiseMapGen({
  seed:        'world-a',   // string or number — same seed = same map
  type:        'simplex',   // 'value' | 'perlin' | 'simplex'
  octaves:     4,           // layering passes (more = more detail)
  scale:       0.05,        // zoom level (smaller = zoomed out, more features)
  persistence: 0.5,         // amplitude decay per octave (0–1)
  lacunarity:  2.0,         // frequency multiplier per octave
  amplitude:   1.0,         // overall output scale
  threshold:   0.5,         // values above this become land (1), below become ocean (0)
})

Methods

MethodDescription
generate(width, height)Returns a flat tile array (thresholded to 0/1 by default).
generate(width, height, { raw: true })Returns raw float array — useful for layering.
applyToArea(area, mapData, w, h, opts)Drop-in for walkArea() — populates mapData in-place.
NoiseMapGen.combine(generators, w, h)Sum raw float layers, then threshold.

When to Use Noise vs Walkers

  • Noise → organic terrain shapes (islands, cave density, elevation). Fast. No path guarantee.
  • Walkers → guaranteed connectivity. Use for corridors between named locations.
  • Together → use noise for base terrain, then walkers for feature placement and connections on top.

worldGenUtils — worldGenUtils.js

Layer-2 utilities that sit on top of MapWalkerA.

connectAreas(mapData, mapWidth, mapHeight, areaA, areaB, opts)

Runs a targeted walker from areaA.spawnCol/spawnRow to areaB.spawnCol/spawnRow. Optionally applies zone-coloring after: tiles closer to areaA get areaA.tileValue, tiles closer to areaB get areaB.tileValue. Used to carve corridors between two area blobs.

connectAreas(mapData, 64, 64,
  { spawnRow: 10, spawnCol: 10, tileValue: TILE.FOREST },
  { spawnRow: 50, spawnCol: 50, tileValue: TILE.DESERT },
  { connectorTileValue: TILE.PATH, likelihoodFactor: 0.75 }
);

walkArea(mapData, mapWidth, mapHeight, opts)

Convenience wrapper for a num-mode MapWalkerA. Place a terrain blob around a center point.

sealDeadEnds(mapData, mapWidth, mapHeight, opts)

Finds corridor entry tiles with only one open cardinal neighbor (dead-end stubs) and fills them with a wall tile. Cleans up generation artifacts.

identifyCorridors(mapData, mapWidth, mapHeight, opts)

Returns land tiles with fewer than threshold cardinal land neighbors — i.e., narrow 1-tile-wide paths. Use to detect and optionally widen natural chokepoints.


tileMapBitmask — tileMapBitmask.js

Resolves the correct sprite-sheet index for a tile based on its 8 neighbors.

How It Works

Each tile has 8 neighbors (cardinal + diagonal). The bitmask key is an 8-bit string describing which neighbors are “matching” terrain. The resolver looks up that key in a bmKey table you provide, returning the sprite-sheet index.

const bmKey = {
  '00000000': 46,   // isolated
  '01010101': 12,   // fully surrounded
  // ... 256 possible combinations
};

setupMapBitmaskCSV(mapData, mapWidth, mapHeight, bmKey, targetTileValue);

This is how FF1-style tilesets achieve smooth terrain borders — each grass tile knows whether it’s next to water on each side and picks the right corner/edge/center sprite.