# Terrain

# Terrain

Package `@ferret/physics-terrain`, capability `terrain`.

## What it is for

Ground that behaves. Dig a trench and its walls slump if the soil cannot stand. Pour sand and it settles into a cone. A pit below the water table floods. A heavy crate sinks into mud and stays on top of sand. A tunnel stands in clay and caves in through dry sand.

Use it when the player or an excavator changes the ground (digging, building, quarries, trenches), and for soft ground that grips and swallows. Do not use it for a static level surface: the ground of an ordinary level is a mesh.

## Soil and strata

A `Terrain` is a grid of columns. Each column is a stack of **strata**, bottom to top, each of some soil and some thickness. The surface is their sum.

A **soil** is a record of physical numbers:

| Field | Unit | What it means |
|---|---|---|
| `density` | kg/m³ | dry, in place |
| `friction` | degrees | internal friction angle, which is also the **angle of repose** of the loose material |
| `cohesion` | Pa | what makes a cut stand (0 for dry sand and gravel) |
| `hardness` | J/m³ | the energy needed to remove one cubic metre |
| `bulking` | none | loose volume per volume in place; 1.3 means a 1 m³ hole in clay makes 1.3 m³ of heap |
| `bearing` | Pa | pressure the ground carries without sinking |
| `sinkage` | `{ k, n }` | beyond the bearing, how fast resistance grows as a body sinks |
| `damping` | Pa·s/m | how slowly a body sinks |
| `wet` | `{ cohesion, density }` | the values when saturated; moisture 0..1 blends dry and wet |

`EXAMPLE_SOILS` has `sand`, `gravel`, `clay`, `silt`, `mud` and `rock` to start from; a game defines its own. **Heap** is loose spoil, a stratum marked `loose`: it is not ground in place, and it settles at the angle of repose until it is compacted.

Everything conserves volume. What is dug ends up in the spoil, what is dumped goes into the heap, and compacting heap shrinks it by the bulking and reports how much volume vanished.

```ts
import { EXAMPLE_SOILS, SoilLibrary, Terrain } from '@ferret/physics-terrain';

const t = new Terrain({ nx: 81, nz: 81, cell: 0.1, origin: [0, 0],
                        soils: new SoilLibrary(EXAMPLE_SOILS), strata: [{ soil: 'clay', thickness: 3 }] });

const dug = t.dig({ footprint: { box: [3.95, 3.95, 4.95, 4.95] }, depth: 1, tool: { power: 1e9, reach: 1e9 }, dt: 1 });
t.dump([6, 6], dug.spoil);              // pour the heap somewhere else
t.compact({ box: [0, 0, 8, 8] });       // pack it into the ground
```

## Digging

A **tool** has a `power` in watts and a `reach` in J/m³. It removes `power / hardness` cubic metres per second. If the soil is harder than the tool's reach, nothing is removed and the result says `blocked: true`. A small shovel takes sand quickly and rock never; a pick with a low reach cannot break clay.

A `DigResult` has `removed` (m³), `spoil` (the heap you can dump), `energy` spent, `blocked`, `flooded` (how much water a pit needs to reach the water table) and `slump` (what the walls did afterwards).

## Slopes and slumping

After every edit the terrain relaxes: wherever a step between neighbouring columns cannot stand, soil moves from the higher to the lower, until everything stands. So:

- poured **sand** ends as a cone whose flank is at its friction angle;
- poured **clay** stands in a taller pile than sand of the same volume, as far as its cohesion allows;
- a **vertical cut** in clay stands up to `criticalHeight(soil, moisture)` and slumps above it; dry sand never holds one; **wet** sand holds a cut as high as its wet cohesion allows (`terrain.wet` wets an area).

`culmannHeight(soil, slopeDeg)` gives the height at which a sloped face stands. Use these functions to give the player advice such as "this trench will fail at 1.6 m".

## Water table and floods

`waterTable` (a number, or a function of x and z) is the height of the ground water. A pit that goes below it reports `flooded`, the volume of water needed to fill it to the table, and the capability raises `terrain:flooded { volume }`. A column under the table counts as saturated, so it is heavier and less cohesive.

Terrain does not add water as a medium. Put a `water` medium (media (skill `ferret`)) with its surface at the table's height over the pit to make things float in it.

## Soft ground

With `sinkage: true` (the default) a body sinks into soil it is too heavy for and is held by the soil's stiffness. The pressure under its `footprint` (m²; a sphere's cross-section if omitted) is compared with the soil's `bearing`: below it the ground carries the body, above it the body creeps down until the soil's resistance catches up. The creep is slow, because soil damping is large.

A 400 kg crate on a 0.25 m² face presses with about 16 kPa. That is above mud's 4 kPa bearing and well below sand's 100 kPa, so the crate sinks into mud and stays on sand.

```ts
import { terrain } from '@ferret/physics-terrain';

const cap = terrain(t);
host.register(cap);
host.enable('terrain');
cap.depthOf(crateBody);          // how far it has sunk
```

## Tunnels

`t.tunnel({ from, to, radius, lined? })` bores a round tunnel along a line in world coordinates (y up) and checks that it can stand. A **lined** tunnel stands, and so does one in **cohesive** ground deep enough. In dry sand it caves in, and the surface above settles by the volume that fell (`TunnelResult.subsided`, `stable: false`). Cutting into bedrock throws. `t.solidAt(x, y, z)` answers "is there ground here", knowing the bores.

## Letting bodies stand on it

By default the terrain is read only by the sinkage law. Two other modes exist:

- `cap.collide('tunnels')` turns the insides of the bores into static meshes for the kernel, so a ball dropped in a tunnel rests on its floor.
- `cap.collide('all')` also builds the surface in tiles, so bodies rest on the ground, and the sinkage law then has nothing to act on. Dug tiles are rebuilt on `terrain:changed`.

Choose one world: soft ground that swallows things, or hard ground that holds them.

## From an Unreal landscape

`LandscapeGrid` (the height grid of a landscape in centimetres, with the dominant layer of each sample) becomes a terrain with a soil per layer. This is the path for levels made in Unreal; see porting from Unreal (skill `ferret-unreal-porting`).

## Parameters

| Capability value | Default | Effect |
|---|---|---|
| `sinkage` | true | bodies sink into soft ground and are held by it |
| `footprint` | a sphere's cross-section | contact area of a body, m² |

| Terrain option | Meaning |
|---|---|
| `nx`, `nz`, `cell` | columns and their spacing; 0.25–1 m for a play area; cost grows with the square of the number of columns |
| `origin` | world x, z of column (0, 0) |
| `base` | elevation of the bottom of every column |
| `strata` | the starting stack, bottom to top |
| `tile` | columns per tile edge, which is what the mesh and the navigation mesh rebuild by |
| `waterTable` | metres, or a function of x and z |
| `reach` | how many columns away to look for the foot of a face |

## Commands and events

Commands: `terrain.dig { footprint, depth, tool, dt? }`, `terrain.dump { spoil, at }`, `terrain.fill { footprint, soil, height, loose? }`, `terrain.level { footprint, height, soil? }`, `terrain.compact { footprint }`, `terrain.wet { footprint, moisture }`, `terrain.at { x, z }`. A `footprint` is `{ box: [x0, z0, x1, z1] }` or `{ centre, radius }`. An agent can dig a trench or level a building site with these alone.

Events: `terrain:slump { volume, passes, columns, at }`, `terrain:changed { tiles }` (what the navigation and the mesh must rebuild), `terrain:flooded { volume }`.

## Works with

media (skill `ferret`): a flooded pit needs a water medium over it to float things; mud as soil sinks things, mud as a medium only drags. explosions (skill `ferret-physics-combustion-explosions`): craters are dug in the terrain you hand them, and `terrain:changed` is raised for the tiles touched, so a blast and a shovel leave the ground the same way. Navigation rebuilds the tiles named in `terrain:changed`.

## Cost

Every edit relaxes only the affected columns, so cost grows with the size of the disturbed area, not with the terrain. A trench is cheap; a landslide over 200 columns takes milliseconds. Keep `cell` coarse where nobody digs.

## When it does not behave

| Symptom | Cause |
|---|---|
| Nothing is removed by a dig | the tool's `reach` is below the soil's `hardness` (`blocked: true`), or `power` is tiny for the `dt` |
| A pit never slumps | the soil is cohesive and the cut is below `criticalHeight`; that is correct. Make it deeper, or wet the ground |
| A body does not sink | the pressure under it is below the soil's `bearing`: weight over footprint area |
| A body sinks forever | the soil is mud with no bottom beneath it: give it a stratum of something firm |
| `a tunnel needs bedrock` | the bore runs below the strata you gave; add a rock stratum |
| A body falls through a dug pit | you are on `collide('all')` and have not rebuilt, or in sinkage-only mode on mud; pick a mode |
| Flood volume is 0 | no `waterTable`, or the pit stayed above it |

Worked numbers (volume balance of digging and compacting, sinkage depth in mud against sand) are in `tests/unit/examples/physics-terrain.test.ts`.

## Reference

`Terrain`, `terrain`, `TerrainCapability`, `Soil`, `SoilLibrary`, `EXAMPLE_SOILS`, `Tool`, `DigResult`, `criticalHeight`, `culmannHeight`, `sinkageDepth`.
