A GUIDE
How a coordinate becomes a world.
The demos open on the seed k2v-1 k2em — a nod to k·2^v − 1, the law that puts a ridge on every quadtree edge; type any words to get another world. Both demos are built from one small library, nedgelaw, which packages Lars Rocha's RangeBlends mathematics as a dependency-free toolkit with the same API in Python and JavaScript. This page explains the idea, shows the calls, walks through how each demo uses them, and ends with how to write a terrain law of your own.
The idea: ask, don't store
Most generated worlds are simulated: you run a process over a grid, and the result is a map you have to keep. Everything here is indexed instead. A tile, a house, a tree is a pure function of a seed and a coordinate. Nothing is stored, so nothing has to be loaded; the same call gives the same answer on any machine, in either language, without anyone having generated the tiles around it.
ned.World("k2v-1 k2em").tile(511, 127)
# {'x': 511, 'y': 127, 'height': 0.7288…, 'biome': 'rock', 'walkable': True,
# 'tree': False, 'village': {'min': 7, 'em': '🏡', 'n': 'cottage'}, 'name': 'Gevasu'}
That cottage at Gevasu exists because v2(512) and v2(128) — the number of trailing zeros in each coordinate plus one — are 9 and 7, and the lesser, 7, which the settlement ladder reads as cottage, and the rocky ground there is walkable. No one placed it. Zoom in and the same rule tells you where its walls stand; land and you can walk around it. A deeper zoom is a longer index, not a bigger download.
Locality is the rule. Every law in this library answers for one coordinate without looking at its neighbours. That is what makes an Earth-sized planet possible: level 20 of Planet One has a trillion cells, and any one of them costs the same to ask for.
The primitives
RangeBlends supplies a handful of indexed functions; everything else is composition. The same names exist on ned in both languages (Python uses snake_case).
| Function | What it is | Where it shows up |
|---|---|---|
at(seed, x, y, stream), unit(h) | The indexed hash: a 32-bit value for any integer coordinate and stream, and its 0…1 form. | Every decision — is there a tree, which way does a river bend. |
streamOf(name) | A stream id from a name. Streams keep unrelated decisions independent. | Every recipe names its streams, with its version in the name. |
v2(n) | The ruler sequence: trailing zeros of n. Divisible numbers rank high. | Village rank, tributary order, and — on the planet — mountain ridges. |
CUM, runAt(g) | Cumulative run lengths from the measured edge law (docs/EDGE_LAW.md). | Ridge amplitude by scale; the original world's north–south ranges. |
fbm1, fbm2, value2 | Value noise and its octave sums, all built on at. | Continents, wander, presence, the block-scale detail you walk on. |
squareRamp(k, v), buildRidge | The massif slide and the ridge profile: peaks at k·2^v − 1, half-width WIDTH, valleys deepened by RELIEF. | The ranges recipe, in 2-D on the atlas and on the cube-sphere. |
network(seed, order, meander), River | A drainage basin walked from its mouth, tributaries chosen by the ruler. | The library demo's river; the pattern behind the planet's channels. |
Quick start
PYTHON 3.10+
python -m pip install -e . # from the repository root
import nedgelaw as ned
world = ned.World("k2v-1 k2em")
world.tile(511, 127) # one tile, from nothing
ranges = ned.World("k2v-1 k2em", edge_mode="ranges",
edge_strength=1, extent=(1024, 512))
ranges.height(511, 127) # the same address, another law
ground = ned.Landscape("k2v-1 k2em") # 16 blocks to a tile
ground.column(383 * 16 + 5, 127 * 16 + 5)
ground.props(511, 127) # the cottage's boxes
planet = ned.Planet("k2v-1 k2em", recipe="ranges")
planet.sample(50.17, 154.04) # elevation, biome, address, settlement
planet.settlement(50.17, 154.04) # the houses and their plan
planet.trees("+Y", 15, 22405, 28587)JAVASCRIPT (NODE OR BROWSER)
const ned = require("./js"); // or import from "./js/index.mjs"
// in the browser: <script src="js/nedgelaw.js">
const world = new ned.World("k2v-1 k2em");
world.tile(511, 127);
const ranges = new ned.World("k2v-1 k2em",
{edgeMode: "ranges", edgeStrength: 1, extent: [1024, 512]});
ranges.height(511, 127);
const ground = new ned.Landscape("k2v-1 k2em");
ground.column(383 * 16 + 5, 127 * 16 + 5);
ground.props(511, 127);
const planet = new ned.Planet("k2v-1 k2em", {recipe: "ranges"});
planet.sample(50.17, 154.04);
planet.settlement(50.17, 154.04);
planet.trees("+Y", 15, 22405, 28587);The two implementations are held to agree: the test suite generates reference values with Node and asserts Python reproduces them — to twelve decimal places for the atlas laws, within a micrometre for the planet — including every house in a settlement and every tree in a patch.
How the Atlas works
- The world is a window.
World.height(x, y)is evaluated for a 1024×512 (or 512×256) window of tiles and wrapped onto a sphere. The renderer ray-marches real relief atLandscape's own scale — 64 blocks over a radius of16384 / 2πblocks — so ridges cast shadows and break the horizon. - Zoom generates. Past about 2.6× the atlas builds a detail window for the piece of world on screen, sampled with the same bilinear anchors and detail octaves as
Landscape.heightAt, snapped to a tile lattice so panning reuses it, never finer than one block. Trees and houses are painted fromLandscape.propswith a block-height test, so the roof you see from orbit is the roof the walker bumps into. - Landing is the same arithmetic. The ground view meshes
Landscape.chunkcolumns and props; the NEARBY map is the globe's own detail patch painted on a 2-D canvas. - Recipes are versions. Original is untouched. Ranges & rivers is
buildRidge's law read in two dimensions and bent by a per-tile warp, with segmented ranges, cross-spurs, basins that flood, and indexed channels — a trunk per 64 tiles, tributaries on ruler rows. Compare Original here flips the law at one coordinate; that is the whole engine in one click.
How Planet One works
- Addresses. Six cube faces, each a quadtree to level 24. A patch is
(face, level, column, row);Planet.patchreturns its heights,Planet.height(direction)any single point. Nothing above a patch is ever materialised; two workers stream what the camera can see. - The mountains sit on the address. A face at level 20 is a chart of 2²⁰ cells (~9.5 m). A ridge of scale
vstands where the cell index isk·2^v − 1— exactly where the quadtree has an edge at level20 − v. The ruler sequence and the quadtree are the same counting, so a deeper zoom is more bits of the same number and the law never needs a new octave table. - Rivers, villages, woods, by the same rule. Trunks live in every 4,096-cell valley and are alive only where their own signal says so, giving each a source and a mouth; tributaries leave at hashed angles on ruler rows. Settlements sit on level-13 cells where
min(v2(column+1), v2(row+1)) ≥ 3and the ground agrees —World.villageAton the sphere. Trees fill level-17 cells by a forest field and their own hash. - Seams. Near a face edge every face within 8% of dominant is read and blended; the shared edge samples are bitwise equal. Colour, sky and fog are the renderer's; the law is only heights and props.
Write your own law
A terrain recipe is a class with one method. For the atlas it receives the original height and returns a new one; for the planet it receives a unit direction and returns metres. Four rules keep it honest:
- Local. Read only the coordinate you were given (and constants). Ridges, rivers and villages are all lines and cells you can measure your distance to from any point.
- Pure. No state between calls except caches whose contents are themselves recomputable.
- Named. Every stream carries the recipe's version —
streamOf("myhills-v1-warp"). A changed law is a new version, never an edit to an old one. - Twice. Write it in both languages and add a parity test. Mind
Math.round(halves up) versus Python's banker's rounding, and%on negative numbers.
JAVASCRIPT · js/hills.js
class HillsTerrain {
constructor(seed, strength = 0.65) {
this.key = streamOf(seed); this.strength = strength;
this.warp = streamOf("hills-v1-warp");
this.crest = streamOf("hills-v1-crest");
}
apply(x, y, original) {
const bend = (fbm2(this.key, x * 0.01, y * 0.01, 3, this.warp) - 0.5) * 40;
const per = 64, k = Math.floor((x + bend + 1) / per + 0.5);
const distance = Math.abs(x + bend - (k * per - 1)) / (per * 0.5 * WIDTH);
const ridge = distance < 1 ? smooth(1 - distance) * (CUM[6] / CUM[VMAX]) : 0;
const height = Math.min(1, original + 0.5 * ridge);
return original + this.strength * (height - original);
}
}PYTHON · src/nedgelaw/hills.py
class HillsTerrain:
def __init__(self, seed, strength=0.65):
self.key = stream_of(seed); self.strength = strength
self.warp = stream_of("hills-v1-warp")
self.crest = stream_of("hills-v1-crest")
def apply(self, x, y, original):
bend = (fbm2(self.key, x * 0.01, y * 0.01, 3, self.warp) - 0.5) * 40
per = 64
k = math.floor((x + bend + 1) / per + 0.5) # Math.round
distance = abs(x + bend - (k * per - 1)) / (per * 0.5 * WIDTH)
ridge = smooth(1 - distance) * (CUM[6] / CUM[VMAX]) if distance < 1 else 0
height = min(1, original + 0.5 * ridge)
return original + self.strength * (height - original)Then register the mode name in World (JavaScript: js/api.js; Python: src/nedgelaw/world.py), add it to the build in tools/build_js.py, add an option to the Atlas's recipe selector, and copy the parity test pattern from tests/test_extension.py: Node writes JSON, Python asserts equality. js/ranges.js and src/nedgelaw/ranges.py are the full worked example; js/planet.js and src/nedgelaw/planet.py show the same law on a sphere.
Limits, honestly
- These are fictional worlds. Nothing here is Earth's geography, climate, or ecology; biomes are elevation and latitude bands.
- Rivers are indexed lines that begin and end by a gate, not by catchment. Planet-scale drainage — rivers that reach the sea by construction — is the next law to write.
- The atlas's window has a longitude seam in Original; the ranges recipe closes it. Planet One has none.
- Houses stop a walker; trees stop a walker; there is no gravity, digging, or saved game. Planet One needs WebGL; the Atlas has a software fallback.
- Detail is bounded by the law, not the renderer: below one block (atlas) or one level-20 cell (planet) there is nothing more to know, by design.
Further reading
The repository documents each law: EDGE_LAW.md (the measured table everything rests on), EXTENSION.md (the atlas recipes, including ranges), GROUND.md (the block-scale contract you walk on), PLANET.md (addresses, units, the planet recipes, and the roadmap), and README.md for the full API in both languages. Demos: the Atlas, Planet One, the library.