Terrain and the ground
The region is 256 x 256 m with a 1 m terrain vertex grid. In this build the water plane is at 20 m, and the generated heights put H = 0 at 20 m, so the sea and an inland pool (its floor carved below 20 m) both hold water.
Making the land
1. Generate the land, then load it as a RAW32 heightmap
tools/make_paracosm_terrain.js <seed> drives an external terrain-simulation library (uplift, thermal and
hydraulic erosion, a coastal pass; not included in this repo) and writes assets/terrain_data.json;
tools/make_terrain_outputs.py <vscale> turns it into assets/paracosm.r32 (256 x 256 little-endian float32,
metres) plus a relief map to check the features by eye. Load it with the console: wb("console", command="terrain load <path>.r32"). Save the current terrain first (terrain save) so it can be restored with terrain load.
- A closed basin carved BEFORE hydraulic erosion fills with alluvium; carve the pool where the simulated river crosses the valley, after erosion.
2. Edit single vertices with terrain op=set radius=0.5
At an integer (cx, cy), op=set with radius=0.5 sets exactly ONE heightmap vertex. Keep the originals in a JSON
backup and restore from it first, so a re-cut is idempotent.
3. Keep ONE record of the ground as it is now
Every tool that edits terrain records the heights it set; every tool that places things on the ground reads from that record.
- Learned: placing path stones on the generated heights floated them wherever a later build had cut the hill away; a path's "don't pave water" test on the generated terrain passed a stone at 19.97 m, under the water plane.
ground(x, y)interpolates on the viewer's TRIANGLES (each 1 m cell split along its (0,0)-(1,1) diagonal), not bilinearly; draped water edges showed small sawteeth where the two disagreed.- See:
tools/terrain_now.py(record,ground).
4. Record NATURAL heights once for anything you cut
A rebuild that reads terrain_now takes its own earlier cut for the hill. Keep the natural heights in a JSON
beside the builder and read them on every rebuild.
- See:
tools/grow_lookout.py.
Building on slopes
5. A building on a slope: cut, shelve, blend, close
Cut every vertex under the floor below the slab, a shelf just under floor level round the walls, blend back to the slope over about 2 m, and close the exposed underside with a plinth. The viewer triangulates between 1 m vertices, so a keep-out must reach about 1.2 m past the walls.
- Learned: grass came up through the cottage floor until the house was cut into the hillside.
6. A level platform over a slope floats: build a terrace
A retaining wall from wherever the ground is up to the floor, and where the floor cuts INTO the hill, a revetment
facing in from under the floor up to the hill. Cut the terrain vertices inside the outline down to just under the
floor, then terrain_now.record.
- Learned: the first lookout slab hung two metres over the seaward ground.
7. Dry-stone walls batter
A dry-stone wall's foot stands out and its face leans back as it rises (offset outward by the batter times the height below the top). The dark void skin behind the stones must lean with them.
- Learned: the first cut had the batter backwards and buried the lower courses inside the fill.
- Walls along any outline: walk the path by arc length, courses of 15-24 cm, stones of random length, joints broken course over course, placed only where the course is above the ground there. Coping: stones on edge across the wall, tall and short by turns.
- See:
tools/grow_lookout.py,tools/grow_garden_walls.py.
8. Place openings from the thing people move along
Place every window slit in a stair tower from the stair: a helper gives the tread height at a wall angle, and a slit goes at tread + 1.25..2.05 m (eye height).
- Learned: a slit placed "about right" landed at knee height.
- A hatch over a spiral stair cannot work (a head meets the ceiling a dozen steps before the hatch): use a newel stair with an open well and a roof ring round it.
- See:
tools/build_cairn.py(stair_z,OPENINGS),tools/build_tower_inside.py.
Water on the ground
9. Fit the water to the ground, not the ground to the water
A stream about a metre wide cannot have a carved bed and banks inside a 1 m vertex grid.
- Learned: three bank profiles failed in the viewer: a gentle one left the ribbon's edges standing proud (skirts like little walls); a raised one buried the water in puddles.
- What works: the ribbon has 5 points across, and every off-centre point drapes to
min(water, ground + 0.02); the bed is CUT ONLY (a trough about 18 cm under the water across its width, easing back to the slope). Set the water level from the LOWEST ground across the width (the channel floor), or a line riding the bank stands proud. - Relax grid-stepped source lines (a Gaussian moving average) before corner-cutting; Chaikin smoothing alone keeps the 1 m jogs.
- See:
tools/waterribbon.py(ribbon(samples, width, drape=ground)),tools/build_vein.py.
10. Running water is a ribbon mesh, not prim slabs
Straight prim slabs on a bending slope float and meet at plumb joins. A ribbon sampled about every 0.2 m follows the ground (a running minimum so it never climbs) or a fall's parabola, with edge skirts, and a scrolling texture (motion.md).
11. Find a water body from the live terrain
Flood-fill below the water line from its deepest point; if the fill reaches the box edge it is the sea.
- See:
tools/build_ice.py.
Things that lie ON the ground
12. Drape, do not set down
A box set level on a slope floats downhill and pokes in uphill.
- Learned: path slabs 20 cm thick with tops 21 cm proud made every stone a step, and "harder to walk".
- Drape: marching squares over a signed-distance field gives a footprint's triangles and its closed outlines; each vertex takes the ground's height plus a lift; a skirt runs from the outline down into the soil.
- A flagstone is a riven 5-8 sided slab with chipped edges and a worn bevel, about 2.4 cm proud at the edge, and phantom. The viewer draws the terrain a little proud of the true heights here and there, and a stone 4 mm up vanished.
- Moss and other flat things: drape them and give them a tiny buried physics box, with phantom set only after the mesh has settled (physics-and-walkability.md).
- See:
tools/drape.py(fill),tools/flagstone.py,tools/grow_moss.py.
13. Paths go round buildings and end at the door
- Learned: a generated path and the stream both clipped through the cottage.
- Rule: the path's node is the door step, its legs detour round the walls, and it never lays two slabs in one spot.
- See:
tools/build_paths.py.
Ground textures
14. The ground is four textures blended by height
Stock: dirt, grass, mountain, rock over a 10-60 m band. An estate-owner script may swap them
(osSetTerrainTexture(0..3, key)) and move the band (osSetTerrainTextureHeight). The stock textures can be read
out of Asset.db (table assets, columns UUID and Data, J2C) to derive variants that still look like the
region. The four stock UUIDs are listed in tools/build_snowline.py.
15. A PBR viewer is sent the PBR terrain slots
A viewer that declares PBR terrain support (Firestorm 7.x) is sent TerrainPBR1-4 in the region handshake, not
TerrainTexture1-4, and unpatched OpenSim set only the legacy slots from a script: the database changed and the
viewer drew the stock ground.
- Fixed by
opensim-patches/terrain-script-pbr(a script or the console now sets both). A script-driven change reaches the PBR slot only the next time the script calls the function after the restart. - Check the database before blaming the viewer:
OpenSim.db, tableregionsettings, opened read-only WITHOUTimmutableso the live WAL is included.
16. Cover by coverage fraction
Snow cover textures are set by COVERAGE FRACTION (the threshold is a quantile of a drift-and-hollow field), not by a fixed value.
- See:
tools/build_snowline.py.