ASCII terrain maps generated with a from-scratch Wave Function Collapse solver — no external dependencies.
Wave Function Collapse (WFC) is a constraint-propagation algorithm usually used to generate tile-based levels or textures from a small rule set. Here it's applied to five terrain tiles (water, sand, grass, forest, mountain) under one rule: adjacent cells may differ by at most one elevation band.
That single local rule, enforced everywhere via arc-consistency-style propagation, is enough to make coastlines, plains, and mountain ranges emerge globally — nobody tells the algorithm "put a beach around the water," it falls out of the constraint. A small neighbor-matching bias is layered on top of the raw WFC pick to favor forming contiguous patches (oceans and forests) over speckled noise, which pure unweighted WFC on such a permissive rule tends to produce.
Requires Node.js >= 18. No dependencies to install.
git clone <this-repo>
cd loomscape
node bin/loomscape.js --width 60 --height 20 --seed atlas$ node bin/loomscape.js --width 20 --height 12 --seed harbor
~~~~~~~~~.~~..,#####
~~~~~~~~~.~~..,####,
~~~~~...~.~~..,,###,
~~~~~~~.~.~~..,,,,,.
.~~.......~~..,,,,,,
...,,,,,,,..,...,,..
...,,,,,,,,,,...,,..
...,,,#,,,,,,.......
..,,,##,,,,,,.....,.
.....,,,,,,,,,,,,.,,
~~~~~..,,,,,,###,,,,
~~~~~..,,.,,,####,,,
legend: ~=water .=sand ,=grass #=forest ^=mountain
seed: harbor
A larger sample lives at
examples/atlas-60x20.txt (generated with
--width 60 --height 20 --seed atlas).
Options:
-w, --width <n> map width in cells (default: 50)
-h, --height <n> map height in cells (default: 20)
-s, --seed <s> seed (number or word) for reproducible output
--help show this help
Same seed always produces the same map. A pre-generated sample lives at
examples/atlas-60x20.txt.
- Every cell starts "uncollapsed": a bitmask of all 5 possible tiles.
- Repeatedly pick the uncollapsed cell with the fewest remaining options (lowest entropy — ties broken randomly), and collapse it to one tile, chosen by weighted random pick among its options. The pick is biased toward tiles matching already-settled neighbors so terrain forms patches instead of noise.
- Propagate: for each of that cell's neighbors, remove any tile that isn't compatible (elevation diff > 1) with the collapsed cell. If a neighbor's mask changes, push it onto a queue and propagate further — this is a standard arc-consistency (AC-3-style) pass.
- If propagation ever empties a cell's mask (a contradiction — can happen near tight corners), the whole grid is discarded and generation retries with a fresh draw from the seeded RNG (up to 50 attempts).
- Repeat until every cell is collapsed, then render each cell's tile symbol into a grid.
Randomness comes from a seeded mulberry32 PRNG (src/rng.js) so any run
is fully reproducible from its --seed.
npm testChecks: every cell ends up filled, no two adjacent cells violate the elevation-band rule, identical seeds reproduce identical maps, and different seeds (usually) differ.
MIT — see LICENSE.