1. The decided design

Eleven decisions, their reasoning, and what each one rules out. Plus the finding that falls straight out of the digging model and simplifies the data layer.

Background: the three axes, now answered

"Organic terrain" bundles three things that can move independently: the silhouette (how the surface is shaped), the content (what the generator produces), and the interaction (what a dig does). The all-cubes simplification in commit 4656681 only reset the first; the other two were still on placeholder implementations. All three are now decided, and they are decided in the same direction: parity with the reference.

The decisions

Locked D1 — Real generation, on the cube renderer first

fBm with domain warping for the surface, 3D coherent noise for caves and overhangs, genuine strata for material layering, and ambient occlusion in the mesher.

Built and validated on the existing cube mesher before any isosurface work. The generator is the same either way — it writes density and material into cells, and cubes or Marching Cubes are two ways of looking at the same field. Building it first means the mesher change can be evaluated against terrain that actually has cliffs, cave mouths and overhangs, rather than against a sine hill that looks acceptable under any mesher.

Replaces: the single-octave 12 + 7·sin(0.1x)·cos(0.1z) heightmap in fillDensityTerrain.

Locked D2 — Terrain silhouette is Marching Cubes

Specifically Marching Cubes, not Surface Nets and not Dual Contouring.

The reasoning is parity: the goal is to match the reference's results, and Marching Cubes is what the reference is understood to use. Surface Nets is cheaper and was what this repo built before, but its vertex placement gives a subtly different surface. Dual Contouring is arguably better — it preserves sharp cliff creases that MC rounds away — but "better" is a divergence, and divergence comes after parity.

Accepts: MC's rounding of every sharp feature, its higher triangle count, and its ambiguous-case problem (see risk 4). Note: an earlier draft cited Transvoxel as MC's natural LOD companion. Distant terrain is now a pre-generated static mesh and Transvoxel is dropped, so MC is chosen purely on parity grounds (risk 5).

Locked D3 — Parity on look and feel, before anything else

Not "the look" or "the feel" — both, to reference parity, first. Optimisation and deliberate divergence are later phases.

This is the decision that removes the sequencing ambiguity from the previous draft of this wiki. It means the isosurface work is not optional, and it also means structural integrity and the digging model are not optional, because they are what "feel" consists of. It sets a clear bar: a change is done when it matches the reference, not when it is interesting.

Locked D4 — Caves and overhangs are required

Which forces genuinely 3D generation. A heightmap cannot express either — every column has exactly one surface by construction — so the generator writes a full 3D density field, with the surface as one contributor among several rather than as the definition of the terrain.

Rules out: any heightfield-based silhouette shortcut, and any storage scheme that assumes one surface per column.

Locked D5 — Terrain digging shrinks the voxel

A pickaxe-style tool damages a terrain voxel, and the damage reduces its density. Because the surface is marched from that density, the geometry visibly shrinks under the tool, hit by hit, until the cell is gone. No brush, no radius — the edit is still per-cell, but its effect is continuous.

This is exactly the reference behaviour and it is the reason a smooth surface is needed at all: on a cube renderer there is nothing for the shrink to be expressed in. It also explains why the density brush deleted in 4656681 was the wrong tool — the reference does not brush. It quantises the edit to a cell while leaving the surface continuous.

Locked D6 — Construction damage is texture, never shape

A hammer-style tool damages a construction voxel. Its geometry never changes — a construction block is discrete by definition. The damage is communicated entirely through its texture, progressing through crack stages until the block breaks.

Two representations, two damage models, matched to what each representation can express. This is a clean split, and as the next section shows, it also splits the data that backs them.

Locked D7 — Finite world, size a runtime parameter

Bounded exactly as the reference is — a map of fixed extent, not a world that continues indefinitely. The extent itself is not hardcoded: it is a parameter to experiment with, and the number that ships is deferred until there is real terrain to judge it against.

This reverses an earlier decision. A previous draft chose a Minecraft-style effectively infinite world and flagged it as the design's one deliberate divergence from the reference. That divergence is now withdrawn, which makes the design fully consistent with D3 — there is no longer any point at which parity is knowingly given up.

Unblocks: coherent hand-authored content, global generation passes, and precomputed distant terrain — all things a bounded map affords and an unbounded one does not. Does not remove: streaming. At reference scale the world is far too large to hold densely in memory, so chunk residency, async meshing and a bounded upload budget all survive unchanged. See section 5.

The constraint that makes a deferred size work

Changing the size must not change the terrain that was already there, or two sizes cannot be compared — you would be looking at two different worlds. So generation samples noise at absolute world position and the size is a pure clip rectangle over an unbounded field, and the world is anchored at its centre rather than a corner, so growing it adds terrain at the edges instead of translating everything. Signed coordinates and floor-division throughout follow from the centre anchor.

At the boundary: an invisible wall stops the player, and beyond it only the skybox is drawn — no cliff face, no sealed wall, nothing rendered past the edge. The simple option deliberately; something more considered can replace it later.

Height starts at 384, configurable on the same terms as the horizontal extent so other heights can be generated and judged by feel. Budgeted from the stratigraphy in D10: roughly 190 below the surface datum for the full sequence to read, and roughly 190 above it for relief and build headroom. One trade to know — height multiplies the resident working set directly, since residency is a 2D disc and the whole column is always loaded, so height and view distance compete for the same memory. At a 12-chunk radius, 256 tall is ~460 MB and 384 tall is ~695 MB.

In scope, deferred D8 — Structural integrity on both representations

Terrain and construction both participate: undermine a wall and the structure above collapses; hollow out a cliff and the overhang gives way. Design deferred to its own discussion.

Worth noting now: structural integrity on terrain is the more unusual half, and it interacts with D5 — a partially-mined cell has partial density, so "how much support does a 40%-dense cell provide?" is a question the digging model creates. Flagged so it is not discovered late.

Locked D9 — Worlds are generated up front, as a stored artifact

World generation is a distinct step that runs to completion and produces a stored world, rather than terrain being generated lazily as the player walks into it. A generated world is therefore an artifact on disk rather than something reconstituted on demand.

Distribution is a maybe, deferred

Sharing or downloading pre-generated worlds is an idea worth keeping, not a committed feature — the discussion is deferred. Nothing in the plan depends on it.

Importantly, the format requirements below do not rest on it. They were first argued from distribution, but each stands independently on the fact that the world is stored rather than regenerated: a player's own saved world must not mutate underneath them when the generator improves, which needs exactly the same guarantees.

This is reference behaviour, and it is what D7 buys. With the whole world in hand at generation time, anything needing global knowledge becomes ordinary work rather than a distributed-consistency problem.

Collapses: most of risk 7 — cross-chunk features no longer need a bounded-radius scheme to generate identically without their neighbours, because their neighbours are all present. Enables: pre-generated terrain LOD, which reshaped risk 5 from the plan's nastiest problem into a tuning decision, and removed the need for Transvoxel entirely.

The cost, and the thing to design against: world creation becomes a visible up-front wait, and iterating on the generator means regenerating. That makes generation speed a first-class concern rather than an afterthought, and it argues for a fast regenerate path from the start.

Storage imposes its own requirements: a versioned world format, and terrain reproducible from the artifact rather than from the running build — so the format carries the generated data and not merely the seed. Reconstituting a world by re-running the generator would make every generator improvement silently alter worlds the player already has, which is the opposite of what a stored world means. A version mismatch has to fail loudly rather than render subtly wrong terrain.

And the artifact is large: at 1 m voxels a 2 km world 384 tall is ~6.4 GB uncompressed, a 4 km world ~25 GB. Compression is therefore not optional, and density fields are unusually friendly to it — long runs of 0 and 255 mean the win should be large rather than marginal. Which scheme is an open phase-2 item; see section 8.

Locked D10 — Layered geology, from soil to unbreakable bedrock

The world has real vertical composition rather than three material bands by depth: types of soil and sand near the surface, distinct rock types below (sedimentary, igneous, metamorphic — marbles and the rest), seams of minerals occurring within specific host rock types, caves and underground rivers cutting through, and bedrock always present at the bottom.

Crucially, the sequence is not uniform across the map: a stratum may sit at different depths in different places, or be absent entirely. Bedrock is the sole exception — it always exists at the base, so the world cannot be dug through.

Two anchoring modes, not one

The layers divide into two kinds that behave differently, and conflating them is what makes strata look painted on:

  • Surface-following — topsoil, sand, subsoil, weathered rock. Defined by depth below the local surface, so a hill's topsoil sits on top of the hill.
  • Datum-anchored — the rock strata. Defined by absolute Y, warped by low-frequency noise so bands undulate, tilt and pinch out. This is what real stratigraphy does: deposited flat, then deformed.

A stratum is therefore a datum Y plus a thickness field. Where the thickness field falls to zero the layer pinches out and is simply not present in that column — so "different depths, or absent entirely" comes from the same mechanism that places the layer, with no per-region tables and no special cases.

And it makes the height parameter safe. Because the layers are expressed relative to the surface and to the bedrock datum rather than as absolute coordinates, changing the world height stretches the run of deep base rock and leaves everything else untouched — which is why the height in D7 can stay tunable rather than having to be settled before the generator is written.

Bedrock is a hardcoded special case

Not a stratum and not a material with a very high toughness — a special case, by decision. Bedrock cannot be damaged and never deviates from full density, enforced directly rather than falling out of the arithmetic.

The reason for making it explicit: under D5 a strike removes (255 - 128) / toughness density, so a sufficiently large toughness rounds to zero removed and the voxel is already effectively invulnerable. That is the right behaviour arriving by accident, and it reads as a bug to whoever meets it next. An explicit unmineable flag says what is meant.

It is also an unconditional clamp applied last in generation, not a layer written early. If bedrock were written as a layer, a later cave or river pass could carve through it and the "cannot fall out of the world" guarantee would quietly stop holding.

What this forces elsewhere

  • Generation becomes an ordered pipeline. Ore seams that occur only within specific host rock must read the strata pass's output, so the generator is a sequence with barriers rather than a bag of independent functions. See risk 7.
  • Four-way vertex material blending stops being a judgement call. Many thin bands stacked vertically means a dug wall cuts across them constantly, and a pinch-out is a point where three bands meet. That is precisely where a two-material blend fails, and it is where the player is looking most closely. See question 6.
  • Material count grows. colorIndex is a byte, so 256 materials is not the ceiling — texture-array memory and the cost of authoring art for each material is.
Deferred by decision — liquids and gases

Natural gas, petroleum and underground rivers are wanted, but how they behave is a separate discussion, deliberately not settled here. The fork is large: a pocket that is merely a material which looks like oil and sits there until mined costs one more entry in the material table, while a fluid that flows when the cap rock is breached is a simulation subsystem that pulls underground rivers in with it. Both are legitimate; they are different projects. Nothing else in the design currently assumes either, so this can wait.

Locked D11 — Generation is a staged pipeline of steps and generators

The generator is an ordered list of steps, and each step holds one or more generators that run concurrently within it. Steps are separated by barriers; a step reads the world state every earlier step produced.

Flexible, but curated. New generation systems are added deliberately — which step they join, and in what order the steps run, is a decision each time rather than an open plugin surface.

The immediate reason is D10: mineral seams occur only within specific host rock, and caves and rivers cut through finished strata, so those passes must read earlier output rather than being independent functions of position. The larger reason is that the same mechanism carries everything that comes later — roads and railways, cities, points of interest — without redesigning the generator each time.

StepGeneratorsWritesStatus
1Base density, strata assignmentDensity, materialPhase 1
2Cave generationDensityPhase 1
3Mineral seamsMaterialPhase 1
4Top infrastructure — roads, railways, cities, POIsDensity and materialAnticipated, not scheduled
Last − 1Bedrock clampDensityTerminal, always
LastTerrain LOD generation, construction LOD generationThe LOD artifacts — disjoint, so safely concurrentTerminal, always

The contract that makes concurrency within a step safe

"Parallelisable within a step" is not a property of the step — it is a property of what its generators write. Two generators in the same step must either write disjoint fields or combine through a commutative operator. Otherwise the result depends on scheduling, which breaks the determinism D9 needs for a stored world.

Two density-subtracting carves commute if they are combined with min(); two generators both assigning material to the same cell do not.

But safe to share a step is not a reason to share one

An earlier draft of this table put seams and caves together in step 2, on exactly that reasoning: seams write material (ore instead of granite, still solid) and caves write density (they carve), so they are disjoint and may run concurrently. They are, and they may. It is still the wrong arrangement, because the test for sharing a step is not whether two passes can run together — it is whether either needs to read the other's output.

A generator only ever reads what an earlier step produced. Putting caves beside seams therefore denies caves any read of the material the strata assign, and conditioning caves on host rock is precisely what D10 describes when it has caves cutting through specific rock types. Separated and ordered strata → caves → seams, every pass reads what it needs: caves may condition on the material, and seams read both the material and the carved density, so ore is placed against the rock that is actually there rather than against rock a later pass removes.

And the concurrency given up is worth very little. Chunks already run concurrently within a generator, and there are far more chunks than workers, so the pool is saturated inside a single step regardless. All an extra barrier costs is one short tail as a step drains. Generator-level concurrency only pays when a step's generators are each too small to fill the pool alone — which is why the two terminal LOD generators still share a step, and why the rule above is worth keeping even though little now depends on it.

Top infrastructure is a step of its own for the stronger reason: a road and a cave both write density, so a cave passing under a road has no defined winner if they run together. Given its own later step, infrastructure can instead read what is beneath it and respond — bridge the chasm, fill the void, or route around it. That is exactly the dependency step ordering exists to express.

The last two steps are terminal and cannot be scheduled around

  • The bedrock clamp. "After the carving stages" stops being sufficient once new steps can be added — railway tunnels and city foundations carve too. If anything can run after the clamp, the "cannot dig through the world" guarantee is only as good as whoever registered last.
  • LOD generation. It consumes the finished world, so anything added after it is absent at distance — including the terrain modifications infrastructure makes, such as road cuts and flattened city ground.

And these two are ordered relative to each other, which is why they are separate steps rather than one. Terrain LOD reads the density the bedrock clamp writes, so they do not commute and cannot share a step under the rule above. The two LOD generators do commute — they write separate artifacts — so they share the final step quite happily.

Terrain LOD and construction LOD are separate outputs

The pre-generated terrain LOD is a downsample of the terrain field only. Construction sitting on top of it is not meshed into it — a coarse voxel field is the wrong representation for a building.

Instead, construction gets its own simplified LOD models, generated separately by the terminal step and stored alongside the terrain LOD. Generated structures — cities, POIs, the infrastructure step's output — therefore remain visible at distance rather than popping in when the near field reaches them, which matters because a city's silhouette is one of the reference's most recognisable features.

Deferred by decision — dynamic construction LOD

The same mechanism should eventually regenerate LOD models for player-built structures, so a base the player raises is visible from a distance too. How that works is its own discussion — it means producing simplified models at runtime rather than at world-build time, on content that changes. Until then, player-built construction is culled at a horizon and only generated construction has a distant representation.

What the structure imposes

  • Parallelism is per-step, twice over. Chunks run concurrently within a generator, and generators run concurrently within a step — but a barrier separates steps, since a seam or a road originating in one chunk may reach into another.
  • Every generator must be deterministic given the seed and the prior state, since the pipeline's output is what D9 stores and distributes.
  • Generators write band-limited density, never binary. Recorded here because it is a property of the generator contract rather than of any one generator — see the note below.

The iteration payoff: cache each stage's output and re-run from the first changed stage. Under D9, tuning the generator means regenerating; if only the ore pass changed, strata should not be paid for again. This is the main thing that makes D9's up-front-generation cost tolerable in development, and it comes free with the staged structure provided stage outputs are storable.

Decided — the band-limited density contract

Steps fix the order, and the disjointness rule above fixes concurrency. Neither fixes what a generator writes into the field — and that turns out to be load-bearing for D2.

A cave generator can carve as if (caveNoise > t) density = 0: the wall goes 255 → 0 between adjacent cells, Marching Cubes has nothing to interpolate, and the cave renders as cubes. Or it can subtract a smooth amount so density ramps across the wall over 2–4 voxels, and the cave renders smooth. Every generator faces that choice independently, and D10 puts caves, rivers and ore pockets throughout the world.

So it is a contract on the generator interface: generators write smoothly varying density, never binary. Stated once for all generators rather than remembered per generator, and enforced by a single max-Δdensity test at the pipeline level — sample 6-adjacent cell pairs across a generated world and assert the difference stays within a bound.

Why the test matters more than the rule: on the cube renderer a binary field and a smooth one render identically, because isVoxelSolid thresholds at 128. So phase 1's own acceptance test — fly around and find caves — cannot see a violation, and nothing would surface it until Marching Cubes lands two phases later.

The finding: damage and density are the same thing for terrain

D5 and D6 together produce a simplification worth calling out, because it changes the data model rather than just the rendering.

Today there is one DamageMap — a sparse map from world cell to accumulated damage, deliberately kept out of the dense voxel array because almost no cell is damaged at once. Under D5, terrain damage is not a separate quantity: it is the density byte that already exists on every voxel. A struck terrain cell has its density reduced, and that single value drives the surface, the collision solidity, the material blend and the persistence.

TerrainConstruction
Damage is stored inThe dense grid, as Voxel::densityA sparse map, as today
Damage is expressed asGeometry — the surface shrinksTexture — crack stages
A strike triggersA remesh of the chunkNo remesh — shader state only
PersistenceFree — density is already the saved stateMust be saved separately
Toughness controlsDensity removed per strikeCrack stages per strike

Three consequences follow, and all three are load-bearing:

Open sub-question

Density runs 0–255 and the isovalue is 128, so a cell stops counting as solid halfway down its range. Whether a tool should drive density 255→0, 255→128, or something else is a real question with a visible consequence — it decides whether a terrain voxel shrinks smoothly to nothing or shrinks partway and then pops. Covered as question 1.