5. World scale

The topic that was flagged for its own conversation, now largely settled: a finite world of configurable size, generated up front into a stored artifact. What that closes, what it leaves open, and the one thing people expect it to remove that it does not.

Settled

D7 — the world is finite, exactly as the reference is, and the extent is a runtime parameter rather than a hardcoded constant. The number that ships is deferred until there is real terrain to judge it against. At the boundary an invisible wall stops the player and only the skybox is drawn beyond it.

D9 — worlds are generated up front as a distinct step, producing a stored artifact on disk rather than terrain reconstituted on demand. Sharing or downloading pre-generated worlds is an idea kept in view but deferred; nothing in the plan depends on it.

This reverses the earlier "effectively infinite, streamed" position, which was the design's one knowing departure from the reference. That departure is withdrawn.

What this closes

What it does not close: streaming

The intuition that a finite world can simply be held in memory is worth checking against the arithmetic, because it does not survive it. Chunk residency is set by size, not by whether the world ends. At one voxel per metre, a 256-tall world, and four bytes per Voxel:

WorldVoxelsDense in RAMVerdict
512 m square67 M~0.27 GBFits comfortably
1 km square268 M~1.1 GBFits, wastefully
2 km square1.1 B~4.3 GBNo
4 km square4.3 B~17 GBNo

So anything at reference scale streams, and everything risk 2 asks for survives unchanged: async meshing, an explicit dirty set, the version counter, job cancellation, a bounded per-frame upload budget. Those were never consequences of an infinite world — they are consequences of D5 making remeshes frequent, which is untouched by any of this.

What phase 2 actually loses is unbounded addressing and generate-forever. What it keeps is the expensive part. The phase gets smaller, not cheap — and it gains world-generation-as-a-step and a world format in exchange.

Sub-decision A — World height Settled

384 to start, configurable on the same terms as the horizontal extent, so other heights can be generated and judged by feel. Budgeted from D10's stratigraphy — roughly 190 below the surface datum for the full sequence to read, and roughly 190 above it for terrain relief and build headroom:

BandVoxels
Topsoil, sand1–3
Subsoil, clay5–10
Weathered rock10–20
Sedimentary — the ore, cave and river band80–100
Igneous, metamorphic, deepest seams60–80
Bedrock2–4
Why this stopped being urgent

An earlier draft of this page argued the height could not be deferred, because strata and cave depth are functions of absolute Y and the whole generator gets tuned against them. D10 largely dissolves that: anchoring the layers relative to the surface and to the bedrock datum means changing the total height stretches the run of deep base rock and leaves everything else untouched. A working value is needed to tune against; a final one is not.

The trade to remember: height multiplies the resident working set directly, because residency is a 2D disc and the whole column is always loaded. At a 12-chunk radius, 256 tall is ~460 MB and 384 tall is ~695 MB — so height and view distance compete for the same memory in a way the horizontal extent does not.

And a gameplay counterweight to hold against the geology: players mostly dig the top 30–60 voxels. Strata at 150 down are generation cost that nobody sees, which argues for putting the variety within reach and using the deep range for rarity and the long run to bedrock — rather than spacing bands evenly for realism.

Sub-decision B — Storage, addressing, and size-independence

VoxelWorld already documents its own growth path:

Growth path (unbounded / streamed world): swap the dense vector for a hash map keyed by chunk coordinate and allocate chunks on first write. The public interface here […] is deliberately storage-agnostic so that swap doesn't ripple outward.

That prediction still holds — the world-space get/set, the dirty protocol and ChunkVoxelSource are all coordinate-based. Three things to settle:

The constraint that makes a deferred size actually work

Changing the world size must not change the terrain that was already there. Otherwise comparing a 1 km world against a 4 km world compares two different worlds and teaches you nothing about size. Three requirements follow:

  • Generation samples noise at absolute world position, never at a normalised or size-relative coordinate. The size is then a pure clip rectangle over an unbounded field.
  • Anchor at the centre, not a corner, so growing the world adds terrain at the edges rather than translating everything. This also matches the reference — and it is where the signed-coordinate requirement above comes from.
  • World size must not leak into the seed or the save format. Region files keyed by absolute chunk coordinate mean a resize keeps existing saves valid over the overlap.

Sub-decision C — Deterministic generation

Under the previous infinite-world position this was risk 7 and it was substantial: a chunk had to generate identically regardless of when it was first visited or which neighbours existed, which is free for pure noise and genuinely hard for anything spanning a boundary — ore veins, cave systems as connected structures, trees, prefabs. The standard resolution was a two-pass scheme with a bounded feature radius.

D9 collapses most of this. With the whole world generated in one pass, a feature spanning four chunks is written into four chunks that are all present. There is no consistency problem to solve, and the bounded-radius scheme becomes an available optimisation rather than a requirement — which also lifts the constraint that the world's largest possible feature had to be decided up front.

What survives is narrower, and worth stating so it is not assumed away:

Sub-decision D — The world format, and persistence

This grows in importance under D9. Previously persistence was an optional save system over a regenerable world; now the stored world is the world.

The engineering that follows from this — flush policy, the dirty-versus-modified distinction, the LOD version stamp, the directory layout — is settled in section 8, questions 4, 7 and 8.

Sub-decision E — Threading and frame budget

Unchanged by any of this, because it is driven by risk 2 rather than by world scale:

One addition from D9: generation is now a bulk up-front job rather than a trickle, so it wants the same worker pool but a different scheduling shape — a progress bar and a completion barrier rather than a steady background drip. And because iterating on the generator now means regenerating, generation throughput is a first-class concern.

Two things this list understates, both settled in section 8: a worker cannot simply read shared chunks while the main thread evicts them — jobs take a bordered snapshot instead (question 1) — and destroying a chunk's GPU buffers currently requires a full waitIdle, so IRenderer needs a deferred deletion queue before eviction can be continuous (question 2).

Sub-decision F — Distant terrain Settled

Still required. A few kilometres of visible terrain needs some form of LOD whether or not there is more terrain beyond it, so nothing here is removed by the world becoming finite.

What changed is the approach, and favourably. D9 makes pre-generated distant terrain viable: with the whole world in hand at build time, a coarse representation can be produced once, stored in the world artifact, and simply loaded. That is plausibly how the reference affords its own distant terrain, and it is unavailable to an unbounded world — it replaces the runtime chunked-LOD hierarchy that would otherwise have needed Transvoxel.

D11 settles it. LOD generation is a terminal step of the generation pipeline, so the coarse field is produced up front and stored in the artifact — and because it downsamples the finished voxel field rather than re-sampling a heightmap, it keeps the caves and overhangs D4 requires.

Decided: a pre-generated static distant mesh, and Transvoxel is dropped. Near the player, streamed voxel chunks; beyond, one coarse mesh loaded from the artifact and drawn behind and below them. No runtime level selection, no transition cells, no case tables — and no licensing question, since solutions requiring those tables are ruled out. What was the nastiest risk in the plan reduces to one tuning decision: where the handoff ring sits. Tracked in risk 5.

Two things it does not settle. The coarse field must be a genuine downsample of the fine one, or the surface changes shape as the near field takes over — which is why the density mip-chain is a phase 1 concern. And the LOD step covers terrain only, so generated cities built from construction voxels are invisible at distance until the near field reaches them.

Questions remaining for this conversation

  1. How much of the world format lands in phase 2? Format, versioning and the modified-chunk flag are cheapest designed in alongside streaming.
  2. Are hand-authored structures in scope? No longer gating — the world shape does not depend on the answer any more — but it still sets the feature pass's ambition.
  3. What sizes are worth experimenting with? The parameter is the point; a rough range to target still sets the streaming and LOD budgets.
  4. Single-player only, or is multiplayer a consideration? Unchanged by this, and still worth knowing early — it changes the chunk ownership and authority model, and D9's stored, shareable worlds would make the question slightly more live rather than less.