The pack
deformTerrain3d
function deformTerrain3d(opts: Partial<Deform3dSurface> = {}): Pack<{ deform: Deform3dApi }>
The deform-terrain-3d pack: a carve window that follows the action, a high-poly patch that renders the
near trench as real geometry, and the far trail as a tint and rim normals on the base surface itself.
Requires a 3D world and render-3d.
Functions
deformPatchVerts
function deformPatchVerts(cells: number): Float32Array
The high-poly snow-layer PATCH: a unit grid in XZ ([-0.5, 0.5], y = 0),
cells² quads as a plain pos+normal+uv soup (the VS displaces it by the
baked base height + thickness − carve, so the trench is REAL geometry).
uv is 0..1 across the patch. Pure — world3d uploads it once.
Classes
DeformMap
class DeformMap
The square window of carve depths that follows the action. Made by
game.deform.surface(). The game stamps dents; depthAt reads them back.
readonly res: number— Texels per side of the window.readonly window: number— World units the window covers.readonly maxDepth: number— Maximum carve depth, in world units — stamps clamp here.readonly sink: number— Geometric sink cap on the base terrain, in world units.readonly thickness: number— Depth of the deformable snow/sand layer above the base (see DeformOptions.thickness). heightAt lifts the surface by this.readonly patchWin: number— High-poly patch size in world units (0 = no layer) — the feather radius the RENDERED surface uses; snowSurfaceAt matches it.readonly patchCells: number— Patch grid cells per side (the follow-grid resolution).heal: number— Refill rate, depth-units each second. 0 = trails persist.focusX: number— The point the window and the patch follow, set bytrackfrom the pack's carvers: the lead carver's position, or the middle of the whole group when they all fit in the window (the deform WINDOW rides ~30% behind this; the patch sits under the action).focusZ: number— The focus's z position — seefocusX.patchCx: number— The patch centre SNAPPED to the cell grid. The follow-grid must land on a fixed world lattice every frame — if it slid continuously with the rider, each vertex would sample a moving world point and the triangulation would SWIM (Rich: the trenches "ripple like water"). Snapping to whole cells makes the mesh world-stable (it re-indexes as it shifts, but the surface is identical).patchCz: number— The patch centre z, snapped to the cell grid — seepatchCx.readonly data: Float32Array— res² carve depths (≥ 0), row-major, z-major rows, texel centres.cx: number— Window centre in world units — moves via follow(), quantised to texels.cz: number— Window centre z, in world units — seecx.dirtyX0: number— The first dirty texel column (x), inclusive — the dirty rect [x0, z0) .. [x1, z1) for partial uploads.dirtyZ0: number— The first dirty texel row (z), inclusive.dirtyX1: number— One past the last dirty texel column (x).dirtyZ1: number— One past the last dirty texel row (z).fullDirty: boolean— The whole window changed (shift/heal) — upload everything.constructor(opts: DeformOptions = {})get texel(): number— World units per texel.stamp(x: number, z: number, r: number, d: number): void— Stamp one bowl-profiled dent: depth d at the centre easing to 0 at radius r. MAX-blends with what's there (carving is compaction, not excavation — the same pass twice is one trench, not two deep).line(x0: number, z0: number, x1: number, z1: number, r: number, d: number): void— Stamp a capsule: discs along the segment every half-texel — the board's frame-to-frame trail with no dashed gaps at speed.depthAt(x: number, z: number): number— Carve depth at a world position — the SAME manual bilinear over texel centres the shader runs, including its window-edge fade, so a board at heightAt() sits exactly on the rendered snow. 0 outside the window.follow(x: number, z: number, minShift: number = this.res / 8): boolean— Re-centre the window on (x, z) when it has drifted more than an eighth of the window (orminShifttexels, when given) — COPY-ON-SHIFT: content moves by whole texels so every carve keeps its world position; texels scrolling in are fresh snow. Returns true when a shift happened (the texture needs a full upload).holds(x: number, z: number): boolean— Is a world point inside the part of the window that is drawn in full — clear of the fade at its edge? A stamp outside the window is dropped, and one in the fade is cut but only faintly shown, so this is whether a carver at (x, z) leaves a trail.track(points: ReadonlyArray<{ x: number; z: number }>): boolean— Keep the window and the patch on a GROUP of positions — every carver — rather than on one.points[0]is the lead. There is one window of texels, so it can hold the group only while the group fits in it: when the points' spread is withinGROUP_FITof the window's width the window is centred on the middle of the group and every point stays inside it (holdssays so); when it is not, the window stays with the lead, and the others are held only if they happen to be inside it. The window rides ~30% behind the focus, as before, and only shifts once it has drifted an eighth of its width; a point still outside it after that (a carver that has only just appeared, or jumped) re-centres it at once. The patch (real geometry, a disc ofpatchWin / 2) takes the middle of the group when the group fits in it, else the lead. Non-finite points are ignored. Returns true when the whole group fits in the window, so every point is held.healStep(dt: number): void— Uniform refill (sand): every carve shallows by heal·dt. Applied in 0.1 s quanta — every heal tick re-uploads the whole map, and 10 Hz is invisible on a slow drift-shut.clearDirty(): void— Clear the dirty rect and the full-dirty flag after an upload.
Constants
DEFORM_UNDER
const DEFORM_UNDER: Record<DeformSurfaceName, string>
The exposed under-layer, per surface — v2's table, and worth keeping its note: compressed snow is
BLUE-white, not grey. One grey default for every surface turns a bright snowfield into a smudge, which
is what a single #8d9aa8 did here until it was measured against v2.
PATCH_WGSL
const PATCH_WGSL: "\nstruct Cam {\n viewProj : mat4x4<f32>,\n eye : vec4<f32>,\n sunDir : vec4<f32>, // xyz = direction the light travels, w = intensity\n sunCol : vec4<f32>,\n skyCol : vec4<f32>, // w = ambient strength\n groundCol: vec4<f32>,\n fogCol : vec4<f32>,\n params : vec4<f32>, // shininess, spe... …
WGSL source of the snow-layer patch shader: a dense follow-grid that replaces the base surface around the action and digs the carve into the same heightfield, triangle-exact.
sparkleOf
const sparkleOf = (cfg: { sparkle?: number; surface?: DeformSurfaceName }): number=> …
The glint strength for a surface: 0.6 on snow, nothing on anything else.
It is a function rather than a number read at two call sites because TWO passes draw glints — the base terrain across the whole carve window, and the high-poly patch under the rider — and they have to agree exactly or the patch twinkles against a still field, which reads as the disc sliding over the snow. The base's copy was simply missing for the whole of the v2 port; nothing declared the two had to match.
Types
Carver
interface Carver
A live carver.
kill(): void— Stop it cutting and forget it.radius: number— Cut radius, live.depth: number— Cut depth, live.readonly alive: boolean— True until killed.readonly covered: boolean— True while the carve window holds this carver, so its trail is cut and drawn. There is one window, and it follows the whole group of carvers while they fit in it (80% ofwindow); a carver left outside it leaves no trail,coveredis false for it, and the pack warns once. Updated each step; true before the first.
CarverOptions
interface CarverOptions
A carver: something that cuts as it moves. Follow an object and it stamps its own trail.
radius?: number— Cut radius, world units. Default 0.6.depth?: number— Cut depth, world units — clamped to the map's maximum. Default the map's maximum.when?: () => boolean— Only cut while this returns true — a board carves, a board in the air does not.
Deform3dApi
interface Deform3dApi
game.deform.
surface(opts: Deform3dSurface): DeformMap— Make a deformable surface. One at a time; a second replaces the first.carver(target: { x: number; y: number; z: number }, opts?: CarverOptions): Carver— Something that cuts as it moves — follow an object and it stamps its own trail. Any number may be live: the carve window follows all of them while they fit in it, and the carver'scoveredsays whether it does.carve(x: number, z: number, radius: number, depth: number): void— Stamp one dent by hand.depthAt(x: number, z: number): number— The carve depth at a world point — what a rider should subtract from the ground.surfaceAt(x: number, z: number): number— The surface height with the carve taken out — hand this to a rider as itsgroundAt.readonly map: DeformMap | null— The live map, or null before one is made.patch: boolean— Draw the high-poly patch. Default true.
Deform3dSurface
interface Deform3dSurface extends DeformOptions
What the pack needs to know about the surface being carved.
groundAt: (x: number, z: number) => number— The undisturbed surface height at a world point — the terrain'sheightAt.size?: number— The terrain's extent, world units. Default 400.baseRes?: number— The resolution the base height is baked at. Default 256.frame?: number— The atlas frame the surface is textured with, so the patch matches it exactly.berm?: number— Berm lift: how far displaced material piles into lips along the cut's skirt. Default 0.09.surface?: DeformSurfaceName— Which surface is being carved. It picks the per-surface defaults forunderandsparkle— v2 takes this from the terrain's own surface name, and without it a snowfield carves grey.
DeformOptions
interface DeformOptions
The options that configure a deformable surface. Passed to game.deform.surface().
res?: number— Texels per side of the window (default 1024 — 4 MB of r32float).window?: number— World units the window covers (default 480). Texel = window/res — 0.47 u at the defaults. SIZE THIS FOR SPEED: the window rides ~30% behind the carver, so trail persistence ≈ 0.8 × window — a 28 u/s snowboarder keeps ~14 s of line at the default. Texels should stay ≲ the carver radius or stamps fall between them.depth?: number— Maximum carve depth in world units (default 0.6). Stamps clamp here — re-riding a trail compacts it, never digs to bedrock.heal?: number— Refill rate, depth-units/second (default 0 — snow: trails persist). Sand wants ~0.1–0.3 so tracks slowly drift shut.under?: string— Exposed under-layer colour (default per surface: compressed blue-white for snow, dark wet sand, dirt for grass).rim?: number— Trench-edge normal strength (default 1 = physical gradient).sparkle?: number— Snow glint strength (default 0.6 onsnowsurfaces, else 0): pin-point sub-texel sparks on the sun half-vector, over the whole carve window and dimmed inside a trench, where the crystals have been crushed flat. BOTH passes draw them — the base terrain and the high-poly patch — from the same hash, so the disc of real geometry around the rider twinkles in step with the field it sits in. Take the default throughsparkleOfrather than repeating it.sink?: number— GEOMETRIC sink cap on the BASE terrain, world units (default 0.18). The base mesh's verts are metres apart, so it can't render a board-width trench — the far/persistent line is fragment work on it (tint + rim normals) with at most this much real dip. The NEAR trench is real geometry on the high-poly snow layer (seelayer).layer?: boolean— SNOW LAYER (E5, the real deformable geometry): a dense follow-grid mesh — the WebGPU stand-in for tessellation (Batman: Arkham Origins / UE RVT do this with hardware tessellation) — that REPLACES the base terrain around the rider: the base discards a disc under it, the patch renders the SAME surface (triangle-exact) minus the carve. One surface, never two → no z-fighting, and the trench is real geometry you can fly a probe line into.falsedisables it (base-only, the flat fragment-trail look). Default on.thickness?: number— Snow/sand depth available to carve into (default 0.6): conceptually the terrain surface IS the snow top and the ground sitsthicknessbelow it — carves clamp here so a trench never reaches bedrock.patch?: number— Layer patch size in world units (default 240 — the real-geometry radius around the rider; beyond it the base-terrain fragment trail carries the line).cells?: number— Grid cells per side of the patch (default 288 → ~0.83 u at the default patch; keep the cell ≲ the carver radius).
DeformSurfaceName
type DeformSurfaceName = 'snow' | 'sand' | 'grass' | 'rock'
The surface being carved, which picks the per-surface defaults below. v2 derives it from the terrain's own surface name; here it is passed, because the deform pack does not otherwise know what it is sitting on.


