Files
last-hearth/docs/world-generation.md
T
2026-11-05 09:57:11 -05:00

302 lines
18 KiB
Markdown
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# World generation: watersheds, woodland, and bedrock
## Integration boundary
[biomes.lua](../mods/hearth/biomes.lua) is a lazy factory:
```lua
local worldgen = dofile(modpath .. "/biomes.lua")(hearth, api, register_node, construction)
hearth.biomes = worldgen.register()
```
The main content owner must replace the **entire old mapgen section**, from its
mapgen alias registrations through its old tree decoration, with this call.
Keep the subsequent wildlife load. Call after base nodes, resources, ecological
flora, and seasonal patches have registered, using content's **private**
`register_node` helper. Do not retain duplicate old biomes/decorations/iron seams.
This module does not load itself or edit the content/bootstrap/launcher/config.
`register()` returns the same module and is idempotent on that instance. Create
one instance per mod load, not one per player. Duplicate terrain node names fail
rather than override existing definitions. Nothing clears global registrations;
existing mapgen aliases/items retain precedence. Coexisting mods may therefore
alter the final landscape. Alias precedence is coexistence, not a promise that a
foreign mapgen-stone binding is compatible with this game's geology.
### Shared ecosystem API
- `species_order`: exactly `oak`, `birch`, `pine`, `willow`, `alder`, `beech`,
`aspen`, `rowan`, `hazel`, `spruce` (ten species).
- `profiles[species]`: height, width, crown, approximate heat/humidity preferences,
altitude limits, soils, biome names, shade and litter hints. Alder also has
`nitrogen_fixer=true`; willow and alder have `riparian=true`.
- `surface(short_name | qualified_name | {name=qualified_name})`: habitat table
or `nil`. Moisture, fertility and drainage are normalized 0–1 **preferences**,
not actual sampled soil water or mutable nutrient stores.
- `supports_species(species, surface)`: soil compatibility only; `false` for
unknown inputs. The ecology owner must also check climate, light, water,
claims/protection, loadedness and player-placed status before any growth.
- `tree_schematic(species [, canonical])`: fresh bounded table schematic.
`canonical=true` uses existing generic logs/leaves (legacy meadow resource
probes); normal calls use species blocks. Unknown species raises an error.
- `biome_profiles`, `surfaces`, `rocks`: declarative read-only-by-convention data.
This owner registers only species **log/leaves/planks**, not species seedling,
young, mature, seed, seasonal-variant or decay nodes. Ecological growth and
seasonal behavior belong to the ecosystem owner. Mapgen places full-sized trees,
not oversized plant sprites; existing understory nodes provide habitats, litter,
deadwood, fungi and seasonal forage for the ecological simulation. Registering a
canopy group alone does not make an older ecology module recognize species:
that owner must consume `profiles`/`hearth_canopy` and recognize these soils.
Seasonal retinting/leaf shedding of species canopies is likewise not implemented
by this registration module.
## Ten connected landscape communities
Heat and humidity below are **Voronoi centers**, not hard rectangular ranges.
The nearest eligible center wins; altitude limits are hard. These heat numbers
are mapgen climate coordinates, not Celsius or geographic latitude. Runtime
weather/seasons remain owned by the climate system.
| Registered biome | Y | Heat / humidity | Surface / bedrock | Community |
|---|---:|---:|---|---|
| `hearth_temperate` | 2–110 | 52 / 45 | Grass / limestone | Open meadow, grain, flowers, greens, sparse oak and canonical timber |
| `hearth_oak_woodland` | 3–110 | 65 / 62 | Forest floor / limestone | Oak, domed beech, coppice hazel, fern, litter, fungi, fallen wood |
| `hearth_birch_forest` | 6–150 | 36 / 58 | Forest floor / granite | Birch, columnar aspen, rowan and spruce; woodland forage |
| `hearth_pine_taiga` | 2–180 | 18 / 38 | Podzol / granite | Pine, spired spruce and rowan; acidic nutrient-poor litter, mushrooms |
| `hearth_wet_forest` | 2–65 | 42 / 90 | Peat, alluvium / slate | Moist fern/decomposer community; alder only at actual water edges |
| `hearth_floodplain` | 2–8 | 68 / 90 | Alluvium / sandstone | Fertile flood terraces, grain, greens, willow/alder beside water |
| `hearth_dry_scrub` | 2–135 | 83 / 18 | Grass, shallow earth / sandstone | Open grass/flower scrub, berries; no artificial woodland or springs |
| `hearth_alpine_meadow` | 111–210 | 30 / 48 | Alpine turf, scree / slate | Short grass/flowers above most woodland; occasional rowan through Y=180 |
| `hearth_rocky_peaks` | 211–31000 | 25 / 45 | Permanent snow, scree / granite | Snowline across all climates, exposed structurally unsupported rock |
| `hearth_riverbank` | −31000–1 | 50 / 50 | Sand, gravel / sandstone | Shore/channel substrate, riparian gathering and rare old weirs |
The last biome is also the underground altitude fallback, not a claim that the
deep world is literally a river. Explicit geological beds below correct that.
There is an eligible biome at every altitude. Woodland-to-alpine overlap allows
climate-dependent treelines through Y=180; above 210 there is only the peak
biome, with zero vertical blend. Snow is a stable full-block cap, **not** seasonal
snowfall or a falling node. Surface/altitude alone cannot distinguish every
steep slope: stock v7's structural-support checks expose bedrock where the node
below is air/water; this is not a custom slope/erosion simulator.
### Full-block trees
| Species | Schematic width × height × depth | Shape |
|---|---|---|
| Oak | 7 × 7 × 7 | Broad layered crown with lateral log branches |
| Birch | 5 × 8 × 5 | Narrow elevated diamond crown and slender trunk |
| Pine | 7 × 9 × 7 | Wide low branches narrowing to a single leafy tip |
| Willow | 9 × 7 × 9 | Spreading branches, hanging curtains down to the lower trunk |
| Alder | 5 × 6 × 5 | Short riparian trunk and compact upright crown |
| Beech | 9 × 8 × 9 | Broad domed crown |
| Aspen | 5 × 9 × 5 | Tall columnar crown |
| Rowan | 7 × 6 × 7 | Open crown |
| Hazel | 7 × 5 × 7 | Low coppice form |
| Spruce | 5 × 10 × 5 | Narrow spired evergreen crown |
All occupied cells use probability 255; empty cells use zero. Schematics are
x-fastest, then y, then z, centered horizontally with base offset +1 above the
support. They do not force placement or cut into terrain. Canopies register
before ground flora, preventing grass from blocking the base trunk. These are
distinct authored forms, not random blocks. Trees at steep/obstructed boundaries
can still be clipped by native non-forcing schematic placement; there is no
Lua post-generation scan/rebuild of partial crowns.
Fallen deadwood is an ordinary sideways full cube, not a plant sprite or a mesh.
It requires an axe and drops one canonical `hearth:log`; hands do not yield
sticks. Its existing metadata-preserving orientation fix and ecological
substrate identity remain separate from this documentation/process/UI pass.
The named botanical catalogue adds 80 species in eight categories of ten;
`/botany [search]` reads their real common/scientific names, habitat windows,
seasons, game toxicity flags and conditional roles. See the
[complete category list and caveats](content-expansion.md#eighty-species-botanical-field-guide).
Reeds, clay and weirs require nearby source/flowing water (at least one of the
native eight neighbors at support Y or Y−1). Willow/alder require the same
check as well as biome, altitude and soil compatibility. Wet climate alone is
not proof of a river. Floodplain selection itself is climate/altitude based,
not hydrological flood simulation.
## Geology and resource progression
Surface bedrock follows biome geology, then registered ores run in this order:
1. Replace sandstone/canonical stone with **granite basement below −80**,
**slate from −79 to −24**, and **limestone from −23 to −8**. Above that,
shallow sandstone and biome-specific rocks remain.
2. Rare coherent 24-node basalt intrusions in bedrock below −24 (48³ scarcity;
correlated 3D noise, not scattered basalt confetti).
3. Nested scatter clusters in the resulting actual host stones:
| Resource node | Allowed hosts | Y maximum | Cluster / spacing |
|---|---|---:|---|
| `hearth:quartz_vein` | Granite, slate | 160 | 6 attempts in 3³, scarcity 11³ |
| `hearth:copper_ore_vein` | Basalt, limestone, slate | 32 | 5 in 3³, scarcity 13³ |
| `hearth:coal_ore_vein` | Sandstone, slate, limestone | 70 | 9 in 3³, scarcity 10³ |
| Existing `hearth:iron_ore_vein` | Basalt, granite, limestone, canonical stone | −8 | 6 in 3³, scarcity 11³ |
| Same iron, sparse exposed/shallow seams | Same hosts | −7 through 85 | 3 in 2³, scarcity 19³ |
All deep ranges start at −31000 (actual mapgen limit still bounds the world).
Scarcity and cluster counts are expectations, not guaranteed yields. Ores
replace their host, never air/soil/leaves. They can be exposed by terrain/caves,
but are not fake simple decorations placed on cavern floors.
All ten species logs drop themselves; **one log crafts four matching species planks**.
Each is accepted by exact name as **180 seconds** of direct hearth fuel.
Planks, buildings and arbitrary `tree` groups are not direct fuels.
For canonical timber, place **two logs of the same species in separate crafting
slots**, shapelessly, to produce **two canonical `hearth:log`**. There is no
single-log canonical conversion. Species planks convert 1:1 to `hearth:planks`. This
preserves axe requirements and the existing charcoal-charge, sticks, wall,
storage and other exact-name recipes without destructive node aliases. There
is no reverse conversion that duplicates timber.
Every raw rock drops itself; **one rock produces four matching rock-brick
building blocks**. Place **two rocks of the same type in separate crafting
slots**, shapelessly, to produce **two canonical `hearth:stone`** for existing
foundation recipes. There is no single-rock canonical conversion. Both pair
conversions preserve mass (2:2), and cannot shadow the single-input plank/brick
recipes. Mixed species or rock types do not convert together.
Masonry uses `hearth_building=1` plus construction placement/ownership hooks,
so the private registration helper retains room invalidation and protection.
There are no new stair/wall variants or alternate protection callbacks here.
Loose gravel/scree drops canonical loose flint. New soils drop canonical soil;
hand placing that soil does not restore an intact woodland ecosystem.
Quartz/copper/coal ore nodes each drop two `hearth:quartz`, `hearth:copper_ore`,
or `hearth:coal` craftitems. Downstream integration supplies three-quartz windows,
copper charges (45-second smelt with 30 seconds reserved) and 240-second coal
fuel under the 600-second reserve cap. These are process/content contracts, not
additional biome-factory recipes. Existing iron definitions are reused.
Loose flint registers on **all** listed surfaces, and dry sticks on those
surfaces through Y=210. Meadow canonical grass/logs remain for old resource
probes; woodland berries, river reeds/clay and limited upland seeps retain
early-survival routes. This is coverage, not a verified guarantee for seed
731104 or every start. Main must update species/soil-aware spawn checks and
validate water, flint, sticks, timber, forage, reeds and clay after integration.
Do not silently hand players supplies or claim native seed validation from
mocked registration tests.
## Terrain material art contract: 64 PNGs
Every file has prefix **hearth_** and suffix **.png**. No downloads, texture
aliases, temporary inventory silhouettes or engine-default art are required.
**61 world tiles** (expand each listed brace alternative):
- `hearth_{granite,basalt,limestone,slate,sandstone}.png` — 5 opaque rock faces.
- `hearth_{granite,basalt,limestone,slate,sandstone}_bricks.png` — 5 opaque,
coursed masonry faces, one material per full block.
- `hearth_{forest_floor,podzol,alluvium,peat,alpine_turf}.png` — 5 opaque soil
**top** faces. Bottom and four sides reuse existing `hearth_soil.png`.
- `hearth_{gravel,scree,snow}.png` — 3 opaque full-block faces.
- `hearth_{quartz_vein,copper_ore_vein,coal_ore_vein}.png` — 3 complete opaque
ore-bearing rock faces (not transparent overlays).
- For **each** oak, birch, pine, willow, alder, beech, aspen, rowan, hazel, spruce: `hearth_<species>_log_top.png`,
`hearth_<species>_log_side.png`, `hearth_<species>_leaves.png`,
`hearth_<species>_planks.png` — 40 tiles total. Logs use top/top/side;
leaves use clipped alpha and full-block `allfaces_optional` rendering.
**3 inventory images:** `hearth_item_quartz.png`,
`hearth_item_copper_ore.png`, `hearth_item_coal.png`.
Canonical grass/log/leaves, iron ore, water, flora, seasonal forage, sand, dirt,
loose flint and weir textures are **existing** art. This module owns no art
generation scripts/assets. Species ecological-stage textures/meshes remain
the ecosystem/art owners' separate contract. Dynamic Lua tile names must be
explicitly enumerated by the art owner if its scanner cannot expand loops.
## Epic-scale landscape generator — NEW WORLDS ONLY
New Last Hearth worlds use stock v7 as a cave/ore substrate and then the original
`epic_terrain` Lua mapgen mod reshapes each freshly generated mapchunk with
seeded, world-coordinate noise. The mod lives in the separate
`luanti-mapgenerator-epic` workspace repository and is bundled into the managed
Last Hearth runtime package. It does not copy Epic Terrain code, JSON density
functions, textures, or other assets. Its feature mapping is inspired by the
public Epic Terrain description/source, not a byte-for-byte or mathematical
port. The launcher applies the generator preset only when a world is first
created; it never rewrites saved map metadata or generated mapblocks.
```conf
mg_name = v7
water_level = 32
mapgen_limit = 4096
mg_flags = caves,nodungeons,light,biomes,ores
mgv7_spflags = mountains,ridges,nofloatlands,caverns
mgv7_mount_zero_level = 0
mgv7_np_terrain_base = 12, 45, (900, 384, 900), 82341, 5, 0.6, 2.0
mgv7_np_terrain_alt = -8, 24, (900, 384, 900), 5934, 5, 0.6, 2.0
mgv7_np_terrain_persist = 0.6, 0.1, (1200, 1200, 1200), 539, 3, 0.6, 2.0
mgv7_np_height_select = -4, 14, (1400, 320, 1400), 4213, 6, 0.7, 2.0
mgv7_np_filler_depth = 0, 1.2, (150, 150, 150), 261, 3, 0.7, 2.0
mgv7_np_mount_height = 250, 100, (700, 700, 700), 72449, 3, 0.6, 2.0
mgv7_np_mountain = -0.6, 1, (320, 280, 320), 5333, 5, 0.63, 2.0
mgv7_np_ridge_uwater = 0, 1, (700, 700, 700), 85039, 5, 0.6, 2.0
mgv7_np_ridge = 0, 1, (100, 100, 100), 6467, 4, 0.75, 2.0
mg_biome_np_heat = 50, 50, (256, 256, 256), 5349, 3, 0.5, 2.0
mg_biome_np_humidity = 50, 50, (256, 256, 256), 842, 3, 0.5, 2.0
```
Design and source-grounded limits:
- Continentalness is shaped by 5100-node plate and 1750-node warped-shelf fields;
a protected 620-node radius around world origin gives first arrival a dry
meadow, while distant islands, peninsulas and ocean shelves continue to vary
by seed.
- Broad uplift plus ridged mountain fields creates plains, alpine massifs and
high snow surfaces. A separate coast band uses a sharper elevation step for
sea cliffs and indented shore profiles.
- Major and minor river corridors are carved using separate warped ridge fields.
Their water levels descend in short quantized steps, creating waterfalls;
lakes form in closed noise basins. This is intentionally stylized flow, not a
global watershed/drainage solver, so a river need not reach the ocean in every
seed.
- `epic_terrain` keeps native v7 cave voids and ore nodes where possible, then
writes its own soil, water, block-cube trees and understory. Native v7
decorations are disabled for newly created worlds so flora does not float
above the reshaped land.
- Epic Terrain's Minecraft source replaces a density-function graph and custom
surface/rivers rules. Luanti does not expose that graph, so this is a feature-
level parody implemented with the public `register_on_generated`/VoxelManip
path, not a direct port. It is not a physical erosion simulation.
Never call `settings:set_default`, change mapgen at runtime, rewrite existing
map metadata, delete chunks, or automatically regenerate a player's world.
New biome registrations can still change **future ungenerated** chunks in an
existing world; they do not retrofit old terrain and may create seams. Offer a
backed-up new world for the coherent preset and explicitly disclose seams for
existing saves. Keep existing world identity and backup protections intact.
## Validation and limitations
The standalone [biome tests](../tests/biomes_spec.lua) exercise an allowlisted
registration-only API mock, all material references and drops, canonical
crafting paths, non-forcing aliases, repeat registration, altitude coverage,
ordered geology, species silhouettes, riparian checks and art names. They do
not assert that mocked noise produced terrain.
Engine source/API reviewed locally: `MapgenV7Params::readParams`,
`MapgenV7::baseTerrainLevelAtPoint`, `getMountainTerrainAtPoint`,
`getRiverChannelFromMap`, `makeChunk`, `MapgenBasic::generateBiomes`,
`OreScatter::generate`, `OreStratum::generate`, and the documented biome/ore/
decoration/schematic definitions. In particular v7 generates biomes, caves,
ordered ores, then decorations; biome transitions must not be assumed to
extrude surface rock unchanged to the bottom of the map.
The native `test:seeds` gate creates eight disposable worlds, including seed
731104, and checks startup resources plus safe arrival; it is not a terrain
silhouette or biome-traversal test, and it does not require every seed to start
on dry land at the origin. A representative coastal, valley, river and alpine
visual survey is still needed before claiming those aesthetics. Never test by
opening the user's persistent world. Neither performance nor universal spawn
safety is certified by this preset or by the standalone checks.
Security review: no credentials, cryptography, certificates, network access,
filesystem writes, deserialization or player-supplied executable input are
introduced. Schematics are fixed-size bounded data; private node registration
preserves the caller's protection/construction hooks. Existing player worlds
and engine-global API functions are not mutated.