Making of King Tide

Chapter 02 — Hover & Buoyancy

Settling onto a moving sea.

In Chapter 01 we built water you can ask for a height at any point. Now the bike has to sit on it — bobbing with the swell, pitching up a wave face, leaning through a roll — without feeling like a cork in a washing machine. The trick isn't fancier physics. It's where you sample.

The obvious approach, and why it jitters

The lazy version: find the water's height and tilt directly under the bike's center, and glue the bike to it. The wave field hands you an exact surface normal for free, so this is almost no code. It also feels awful.

A real sea has chop — little ripples riding the big swells. A single center sample sees every one of those ripples and the bike snaps to match each, twitching constantly. Set the demo below to Center only and turn the sea up: the bike chatters, tilting to ripples far smaller than itself. A bike is not a point; it shouldn't pretend to be one.

Live demo — drag to orbit. Yellow probes read pitch, cyan read roll. The water and every probe height come from the game's real wave-field sampler.

Four probes, not one

So we give the bike a footprint. Four probes sample the surface around the hull — bow, stern, port, starboard — and the bike's pose comes from how they differ:

Because the probes straddle the hull, ripples smaller than the footprint average out between them, while long swells — wider than the bike — still tilt it the way you'd expect. Switch the demo to 4 probes and widen the footprint: the chatter smooths into a confident ride, and the pitch/roll readouts settle. The game's defaults are a 1.6 m × 0.8 m footprint, matching the bike's visual size.

One sea, water and land. In the real game each probe takes the higher of the wave height and a ground raycast, so a bike straddling a shoreline reads terrain on one side and wave on the other and transitions smoothly. The demo shows the water half of that.

Looking ahead at speed

There's one more trick. If the probes sat at a fixed spread, a fast bike would only start pitching up after its nose was already climbing a wave — too late, and it feels mushy. So the fore/aft spread grows with speed: the faster you go, the further ahead the bow probe reaches, and the bike begins tipping to meet a slope before it arrives. Drag Approach speed up and watch the probes stretch (the reach readout climbs); the bike anticipates the next face instead of reacting to it. The real system extends the reach by speed × 0.05, capped at 1.4 m so a very fast bike doesn't over-anticipate.

Keeping it from buzzing

Even with a footprint, raw slope readings flicker frame to frame over choppy water — and on bumpy terrain in the full game, over every little trimesh edge. So the slope is run through a gentle low-pass filter (about a 50 ms time constant) before it becomes pitch and roll. Fast enough to feel responsive on a real wave face, slow enough that single-frame bumps don't kick the bike. That smoothing is on in the demo's 4-probe mode — it's part of why the readouts glide instead of snapping.

Read the code

The footprint sampler is sampleSurfaceFootprint in src/game/systems/hover.ts. In the game its four heights feed spring forces through the Rapier rigid body rather than setting the pose directly — but the sampling, the differential pitch/roll, the speed-scaled reach, and the 50 ms slope filter are all exactly what you're playing with above.