# Ground material

`createGroundMaterial(options)` returns a portable engine `THREE.MeshStandardMaterial` with generated turf, soil, rock, sand, and snow shading plus optional authored albedo inputs. This is a foundation for graphics approaching photorealism, not a declaration that the visual target is complete. The engine has no dependency on an object package or external asset. Dispose the material when its terrain is removed; its generated textures and any URL-loaded albedo resources are released. Further asset inputs are specified in VEGETATION_ASSET_INPUTS.md.

```js
const landscape = api.generateLandscape();
const ground = landscape.terrain.createMesh({
  material: api.createGroundMaterial({
    waterLevel: landscape.seaLevel,
    size: landscape.terrain.size,
    riverRise: 105,
    beach: landscape.beach
  })
});
scene.add(ground);
```

## Configuration

| Option | Default | Meaning |
| --- | --- | --- |
| `waterLevel` | `4` | Lake/sea level in metres. |
| `size` | `8000` | World width in metres. |
| `snowLine` | `640` | Beginning of the altitude-dependent snow transition. |
| `riverRise` | `0` | Optional river height rise per world width. Zero disables the river gradient. |
| `riverEndZ` | `size * .18` | Z coordinate where the optional river gradient reaches lake level. |
| `beach` | `null` | Explicit designated beach footprint, using the generated landscape's `{x,z,width,depth}` metadata. |
| `textures` | `{}` | Optional `grass`, `soil`, `rock`, and/or `sand` albedo inputs: loaded 2D `THREE.Texture`, URL string, or `URL`. |
| `albedoBlend` | `1` | Authored-color blend from 0 (procedural color) to 1 (authored color). Height detail is unaffected. |
| `albedoScale` | `1` | Positive authored UV multiplier, either one number or a per-surface mapping. Omitted mapping entries use 1. Procedural height scale is unaffected. |

The water-height estimate is `waterLevel + max(0, (riverEndZ - z) / size) * riverRise`. A generic material therefore has no implicit river. The generated landscape uses `riverRise:105`; its river geometry and material then share the same height convention. Other river profiles need caller-supplied material parameters or a different height model.

The beach footprint is centred on `(beach.x, beach.z)`. The current generated-landscape convention uses `depth` along X and `width` along Z. The material feathers that footprint and grades its upper edge by height. The designated beach retains a sandy surface; ordinary lake/river banks use a narrower damp soil, gravel, and grass mixture with small sand patches. Wet ground has slightly lower roughness. This affects material appearance only: water geometry and terrain heights remain independent.

## Optional authored color and readiness

The application supplies asset URLs; the engine never imports an object package. In an async scene factory, register material ownership before awaiting its image load:

```js
import {GROUND_TEXTURES} from './objects/vegetation/index.js';

const material = scope.own(api.createGroundMaterial({
  waterLevel: landscape.seaLevel,
  size: landscape.terrain.size,
  riverRise: 105,
  beach: landscape.beach,
  textures: GROUND_TEXTURES,
  albedoBlend: .55,
  albedoScale: {grass: .35}
}));
const cancelGround = () => material.dispose();
if (signal?.aborted) cancelGround();
signal?.addEventListener('abort', cancelGround, {once: true});
try {
  await material.userData.ready;
} catch (error) {
  if (signal?.aborted) return {};
  throw error;
} finally {
  signal?.removeEventListener('abort', cancelGround);
}
if (signal?.aborted) return {};
scene.add(landscape.terrain.createMesh({material}));
```

`material.userData.ready` resolves to the material after all requested URL images decode. With no URLs, the material is immediately ready. `readyState` reports `loading`, `ready`, `error`, or `disposed`; `albedos` contains per-surface source/ownership/status metadata. Readiness covers asset loading, not subsequent GPU shader compilation.

URL loads use native `fetch` and `createImageBitmap`. Their textures are configured for sRGB color, repeat wrapping, linear mipmap filtering, and anisotropy 8. The material owns both the texture and decoded bitmap. Disposing during loading aborts requests and rejects readiness with `AbortError`; a late decoded bitmap is closed rather than reviving the material. A failed URL batch rejects readiness, cancels remaining requests, releases already decoded URL resources, and resets those URL overrides to procedural fallback. Await or handle the readiness promise to observe failure; `readyError` retains its diagnostic.

Supplied `THREE.Texture` objects are borrowed, used directly, and neither modified nor disposed. They must already contain loaded 2D image data. The caller sets their color space, wrapping, and filtering and remains responsible for their lifetime. Custom ground sampling does not apply `Texture.matrix`; use `albedoScale` for the authored UV scale.

Overrides replace RGB only. Their alpha is not treated as height or transparency. The original generated alpha channels, normal perturbation, broad material variation, riverbank/beach masks, and snow remain in use. Optional sampler code is compiled only for supplied surfaces.

`material.userData.albedoBlend` and `material.userData.albedoScale` can be changed after construction without reloading images or recompiling the material. They support same-camera comparisons and visual tuning. A scale mapping assignment replaces the mapping, with omitted entries reset to 1.

The vegetation package currently exports one project-local generated meadow texture. It was compared against procedural-only color at unchanged ground-level and woodland cameras with time frozen. Blend `.55` and grass scale `.35` are useful visually tuned starting values; the asset is synthetic, not a photograph or scan. See its [provenance, checksum, and measured cost](VEGETATION_GROUND_ASSET.md).

## Material detail

- Turf and soil use locally generated fine grass strokes, grains, and small gravel marks.
- Packed multi-scale variation provides intermediate and large surface variation without broad, isolated brown islands.
- Rock uses irregular fractures, mineral-tone variation, and fine plus coarse triplanar sampling. It contains no repeating sinusoidal vein pattern.
- Alpha channels in the four surface textures carry normalized detail height. The material uses those heights for close normal perturbation, fading that detail between 75 and 300 m.
- The separate packed variation texture uses `NoColorSpace`; surface color maps use sRGB. Terrain itself remains opaque.
- Shader branches skip material layers that do not contribute to a fragment. The additional coarse rock sampling runs only where rock contributes.
- Alpine ground grades toward exposed rock before the snow line. Snow support depends on the surface normal, a fixed aspect bias, and irregular multi-scale deposition; steep faces retain much less snow than shallow shoulders and gullies. The aspect bias is a static artistic proxy for deposition/melt, not a simulation of weather or seasonal snow. Darker mineral variation remains visible at the Mountains inspection distance.

`groundTexture(kind, size=256)` also remains exported. Its supported surface kinds are `grass`, `soil`, `rock`, and `sand`; `field` is the packed variation map. Surface-map alpha carries height rather than transparency, so consumers using those maps outside this material should account for that convention.

## Verification and limitations

The associated vegetation fixture supports actual 8 km terrain and 24,000 trees through `tests/vegetation-object.html?world=1`, including woodland, rock, ground-level, transition, and flight-height inspection views and a current-view profiler. Add `&albedo=1` to start with the optional generated meadow albedo enabled. Its checkbox, blend/scale controls, and frozen time support direct before/after comparison. The full assembled scene is checked through `tests/flight-integration.html`.

The material was directly inspected on the actual terrain and in the assembled flight, woodland, mountain, and beach views. The narrow mixed riverbanks replace the former continuous bright sand belt while the designated beach stays sandy. No shader warnings or errors were observed in the inspected combined scene. Parameter checks verify that the default river gradient is zero, that the generated-landscape gradient matches its river height function, and that all five generated maps are disposed.

The final combined view checks and measured profile are recorded in ground-vegetation-visual4.json, with actual module URLs and limits on interpreting the timing data.

The focused `tests/ground-albedo.test.mjs` suite verifies borrowed texture preservation, independent procedural heights, URL readiness/configuration, HTTP failure, pending-fetch cancellation, late-decode cleanup, failed-batch cleanup, and idempotent disposal. The real local PNG was also decoded and rendered in Chrome without shader errors.

In a frozen ground view with 101 near and 1,216 middle trees, enabling the tuned generated color left 47 draw calls and 7,714,934 submitted triangles unchanged. Median GPU time changed from 43.00 to 45.57 ms over 40 sampled frames per setting; forest update remained zero at timer precision. These timings describe a dense close view at a 2293 × 1262 drawing buffer, not general flight performance. See the asset comparison record for scope and settings. A source review also verified the flight and beach factories' abort-before-await listener registration, listener cleanup, cancellation guard, and propagation of real asset-load failures.

The subsequent alpine material comparison used the actual Mountains POI camera `[1200,1200,-400]` at the same high-quality drawing-buffer size, with 40 frozen frames per version. The terrain/vegetation fixture retained 33 draws and 336,064 triangles; GPU median changed from 3.265 to 4.336 ms. The alpine record separates this focused profile from assembled-shell visual verification. No terrain geometry or forest behavior was changed by the alpine material pass.

This is procedural terrain shading. It does not add displacement geometry or alter the terrain silhouette. Fine texture detail naturally disappears at long viewing distances; the coarse rock layer maintains larger material variation there. Perceived cliff shape, crest aliasing, and geometric shoreline accuracy remain terrain/water concerns. Shadows and postprocessing are owned by their engine modules.
