qproj eye icon qproj design docs

Quell world generation: from hex editor to playable levels

design · draft · Procedural generation · edited 2026-11-04

Status: draft, 2026-10-16. For review.

Follows the hex terrain work (Quell hex terrain: terrain-first trail generation) and the async generation issue (.docs/issues/quell-worldgen-0-async.md). The hex_paths screen becomes the level editor; WorldScreen becomes the place where levels are played. The greybox scene stays with its own screen for controller work.

Terms

Idea

Erosion is global (Region::simulate evolves the whole region at once), so the skeleton cannot be chunked. Everything finer than the skeleton can be chunked. The pipeline has two stages:

  1. Skeleton, once per level, async. This is existing code, unchanged in behaviour. Loading a level waits for it.
  2. Detail, per chunk, async, on demand. This is a deterministic function of (skeleton, seed, chunk coordinate, LOD). Chunks near the player stream in and out; LODs swap with distance.

Since detail is a pure function of the skeleton and seed, the world never needs to be stored. A save holds the inputs and the player’s state, and loading regenerates everything else.

Layout regeneration is a game mechanic. “Regenerate here” and Fear (Layout::regenerate) rewrite the layout around the player during a level. The terrain is fixed per level, but the layout is not. So:

Editor → play

Moving reusable code to services

screen/dev/hex_paths is compiled only with the dev feature, but play needs the generator in release builds. Everything reusable moves to non-dev services:

The editor keeps its panel, preview mesh, markers, and camera. This move is a pure refactor and should be its own PR, with no behaviour change.

Vegetation preview in the editor

Vegetation algorithms need the same iterate-and-look loop as pathing, before any 3D detail or final assets exist. The editor gets a top-down vegetation view:

Detail per chunk

All randomness comes from hash(seed, chunk, layer) so that chunks are independent of load order.

Boundaries

Each level is bounded. The playable window sits inside the terrain margin (HexTerrainTuning::margin), which today is scenery:

Passability

The player roams freely, off-path included. Movement is restricted by large features that cannot be crossed, not by the trails:

This keeps teases meaningful, but changes what the generator must check. Today tease_detour and the pace statistics measure walks along trails. Under free roaming, a tease holds only if the passability graph (terrain edges minus barriers) forces the detour. So the generator gains:

The editor shows the mask as a TerrainView, so barriers can be tuned like paths and vegetation. Barrier thresholds and boulder placement are settings assets. Play builds colliders and boulder props from the same mask, so the editor’s preview of passable ground matches what the player can actually reach.

Streaming and LOD

Saves

A save is a RON asset written with RonAssetSaver, like the presets:

Geometry never changes during play, so nothing about the terrain is saved. If regenerating the skeleton on load proves too slow, cache it as a binary asset keyed by a hash of (generator_version, preset, seed). The cache is an optimisation; the save stays authoritative.

Determinism is load-bearing:

Screens

Out of scope

Proposed issues

Each one is a PR and follows the async generation issue.

  1. Move reusable code to services: the generator to service/worldgen, and billboards to their own service (refactor, no behaviour change).
  2. Editor vegetation preview: VegetationSettings asset, placement, squircle boundary, raster overlay, billboard toggle.
  3. Editor passability: barrier mask, boulder placement, tease and pace validation over passable ground.
  4. WorldScreen drops the greybox scene (greybox_arena keeps it) and loads a generated level skeleton and spawns the player on a single LOD 0 ground mesh with a collider. Not chunked yet.
  5. Chunked ground, streaming, and LOD distances.
  6. Path carving and path splat, including chunk invalidation on regeneration.
  7. Vegetation in play: per-chunk placement, boundary collider, impostors.
  8. Node props and boulders from Jackdaw scenes; dry creek banks and crossings.
  9. Save and load: the save asset, regeneration history, and the generator version.

Open questions

  1. Chunk size and LOD distances: tune on your GPU after issue 5.
  2. What drives each vegetation layer: decided while playing with the preview.
  3. Water in creeks: deferred; dry banks for now.