What it does
Each frame your game moves the rider in x and z its own way, then calls step(dt, x, z, yaw) and reads back y,
pitch, roll, grounded, landed and airTime to put on the mesh. The split is deliberate. Horizontal motion
genuinely differs between a board that carves, a ball that rolls and a kart that grips, but sitting on the ground
smoothly is the same fiddly problem for all of them, so the rider owns only the vertical.
The idea that removes the jitter is that contact is a shape, not a point. A point sample drops into every notch in the
terrain. A plank samples a footprint along its length, and across its width if you give one, rests on its highest
contact and tilts to the fitted slope, so a board bridges dips and pivots over crests. A sphere averages the ground
over its contact disc and sits its radius above it, which smooths away the slope kinks between terrain quads, so a ball
rolls across an incline without trembling and bridges notches narrower than its radius. The shape is picked from what you
give: a radius makes a sphere, a length a plank, neither a point.
The rest of the feel is in the details. Suspension stiffens with speed so a fast descent does not float, but it is
capped, because an almost rigid follow passes every change of support straight through. Orientation is smoothed twice,
the slope first and then pitch and roll towards it, so it neither snaps nor lags. Off a lip the rider leaves carrying
the descent speed it already had, so it arcs rather than dropping like a stone, and landed is true on exactly one
frame with airTime frozen at the flight's length, ready to scale an impact.
Features
- Three contact shapes —
point,plankandsphere, picked fromradiusandlengthor set withcontact. - Slope-following orientation — pitch and roll to the fitted slope and cross-slope, scaled by
pitchGainandrollGain, withtilt: falsefor a box that stays level on its highest corner. - Speed-scaled, capped suspension —
followK,followKGainandfollowKMax, 25, 0.5 and 55 by default. - An airborne state machine — detaching off a lip deeper than
detach, gravity in the air, a one-framelandedand the flight'sairTime. - Jumps —
launch(vy)pops the rider into the air. - Glued riders —
detach: Infinitynever leaves the surface, for a ball held to a seabed. - Settling in —
settlesinks the contact into the surface, as a board compacts the snow it rides. - Inputs for an energy model —
groundY,lastGroundYand the smoothedslope, for trading height for speed. - Pure and headless — no objects, no clock and no drawing, so it runs in a test or a worker exactly as in a frame.
Good for
- Snowboarding and skiing games, where a board carves down a slope and catches air off lips.
- Kart and off-road games over rolling 3D terrain, with the kart banking to the cross-slope.
- Rolling-ball games across inclines and bumpy ground, where a point sample would make the ball tremble.
- Boats riding a swell, fed the water's height instead of the land's.
- Characters and creatures walking over terrain, with a point or a short plank for feet.
- Hovering drones holding a clearance above the ground.
Make it yours
groundAt is the one required input; everything else has a default. The contact is shaped by contact, radius,
ringTaps, length, width and tilt; the ride by settle, gravity (30), detach (0.9) and maxFall (200); the
suspension by followK, followKGain and followKMax; and the orientation by slopeK, orientK, pitchGain and
rollGain.
Two different uses: a snowboard as a plank with a length and a width, banking to the cross-slope, settling into the
snow, popping an ollie with launch and scoring the trick from airTime on the landing frame; and a ball as a sphere
with a radius, glued to a seabed with detach: Infinity, its pitch and roll left to the game, which spins it as it
rolls.
A quick look
A board over a terrain, stepped after the game has moved it:
const board = game.rider.add({ groundAt: (x, z) => terrain.heightAt(x, z), length: 1.6, width: 0.5 });
board.reset(start.x, start.z, yaw); // on the ground, at rest
// each frame, after the game has moved x and z its own way:
board.step(dt, x, z, yaw, speed);
mesh.y = board.y; mesh.pitch = board.pitch; mesh.roll = board.roll;
if (board.landed) scoreJump(board.airTime);
Works with
terrain-3d— pass its height query as the ground.water-3d— ride a swell through the water's height query.physics-3d— for walls, overhangs and sideways collisions, which the rider does not know about.
Good to know
- It rides a height function, so it knows nothing of walls, overhangs or anything it should hit sideways; that is your game's job or a physics pack's.
- There is no wheel model, friction or traction: the slope and the change in ground height are given to you to build an energy model on.
- Keep
groundAtsmooth. A rider that carves the surface it rides should be fed the fresh field, or it rides its own trench. - Smooth riding also needs frame order: move the camera after the rider, or every acceleration reads as judder.






