Making of King Tide

Chapter 01 — Wave Mastery

An ocean you can read.

The whole game hangs off the water. If the sea feels random, riding it is a chore; if you can read it — see the next crest coming, time your launch — it becomes a skill. Here's how we built water that's cheap enough for a browser yet predictable enough to master.

A wave is just a moving sine

Drop one ripple on a flat sea and it's a sine wave: a smooth up-and-down with a height (amplitude), a crest-to-crest distance (wavelength), and a speed it travels at. On its own it looks like a bedsheet flapping — too regular to pass for the sea.

The trick is that real water is many of these added together: long, slow swells carry the big silhouette, and shorter, faster chop rides on top for texture. Stack a few and the regularity disappears. That's the entire idea behind the field — a sum of sines.

Pull the Wave layers slider below from 1 up to 6. One layer is that flapping bedsheet. By six, you've got a believable, choppy ocean — and the orange bike is floating on it the whole time.

Live demo — drag to orbit, scroll to zoom. The surface and the bike are both sampled from the game's real wave-field.ts.

Our default sea is six waves: two long swells (50 m and 85 m) whose periods beat together into an "occasional big set" every ~24 seconds, plus four bands of chop fanned within ±25° of the swell direction. Keeping every wave within a narrow fan is deliberate — an earlier tune spread them across 190° (literally the physics definition of "confused seas") and the bike just got jostled. Coherent swell marching one way is what you can learn to ride.

Gerstner, minus the expensive half

Film and AAA water usually reaches for Gerstner waves, which don't just move points up and down — they also pull them sideways toward each crest, giving that sharp, pinched peak. Looks fantastic. The catch: once points slide horizontally, you can no longer ask "what's the height at this exact spot?" without solving the motion backwards. For physics that's death by a thousand iterations.

So we keep the vertical motion and drop the sideways shove. The height at any point is a plain closed-form sum:

y(x, z, t) = baseY + Σ Aᵢ · sin(kᵢ · (Dᵢ · xz) − ωᵢ · t + φᵢ)

No inverse solve, no iteration — one pass and you have the height and the exact slope (the surface normal) for free, because the derivative of a sine is just a cosine. The visual loss is small at arcade amplitudes, and the part players actually feel — the launch off a crest — comes from the slope, which we keep. The fine sub-meter chop that the dropped term would have added is faked back in on the GPU with scrolling detail-normal textures, purely cosmetic.

That cosmetic layer has had its own long tuning arc. An early version piled on extra readability overlays — contour-line foam, posterized value bands, rising-face strokes — and at full strength they read as allover noise rather than a legible sea. The fix was to dial almost all of them back to a whisper (and delete three outright once we measured what each cost per fragment), letting a curvature-driven whitecap and a roughness-coupled reflection carry the swell read instead. How the water spends its visual budget so the gameplay stays the loudest thing on screen is its own story — Chapter 08.

Why it matters for feel: because height is closed-form, the game can ask the sea "how high are you, right here, right now?" thousands of times per frame for almost nothing. That budget is what lets every bike float on the same waves you see, instead of on a cheaper stand-in.

One formula, two consumers

Here's the rule that keeps the water honest: the height function lives in exactly one place, and two very different systems read from it.

Because both sides compute identical math, the crest you watch roll under the bike is the crest the physics launches it off — to within floating-point precision. There's no "visual ocean" and "physics ocean" drifting apart. The readout panel in the demo shows the height the physics would feel under the bike at this instant; watch it rise and fall as crests pass, and watch the bike's tilt track the slope.

The wake you can jump

A moving bike drags a wake — the V-shaped wash trailing a boat. We add it as another term in the very same field: a Kelvin-style V that widens behind the rider, with its ridge riding the edge of the V and gentle "scallops" scrolling backward down its length.

Crucially, the wake isn't just a foam decal — it's real displacement in the height function, which both the visuals and the physics read. So a trailing rider who clips your wake genuinely gets bumped by it, and a well-timed line can jump off it. Switch the bike to Ride a wake in the demo and orbit around behind it to see the V carve into the surface.

Per-track wave zones

One global sea would make eleven tracks feel the same. So tracks can drop wave zones — boxes that locally crank the amplitude, tighten the chop, re-aim the swell, or add a periodic surge. A calm lagoon and a track with a tsunami rolling through an arch can share the exact same wave engine; the zone just blends in its overrides where the author painted them. (We'll give zones their own demo in a later chapter.)

Read the code

Everything above lives in one focused, dependency-free module: src/engine/sim/water/wave-field.ts. It's pure math with no rendering imports, which is exactly why this page could pull it straight into a Three.js demo. The design notes and references (Sea of Thieves, the Atlas water talk, Tessendorf) are in docs/water-deep-dive.md.