Engineering
How I built Dolphin Beyond's deterministic physics engine in pure TypeScript
The making of Dolphin Beyond's engine — why determinism was the first decision, how it powers replays and cheat-proof leaderboards, and how one core ships two different games.
Indie game developer · Aarhus, Denmark
Dolphin Beyond looks like a simple arcade game: build speed underwater, breach the surface, chain tricks, fly higher. Under the water, though, sits the most important engineering decision I’ve made in any of my games — the entire simulation is deterministic, and written in pure TypeScript.
This post explains what that means, why I did it, and everything it bought me for free.
What “deterministic” actually means here
The engine has no hidden state and no reliance on wall-clock time. Every tick is a pure function:
nextState = step(previousState, playerInput)
Same starting state, same inputs, same result — every time, on every device. No Math.random() inside the simulation without a seeded generator, no physics library with its own internal solver state, no frame-time dependence (the simulation runs on a fixed timestep; rendering interpolates).
That discipline is annoying for exactly one week, and then it pays rent forever.
What determinism bought me
Replays are just input recordings. A complete run in Dolphin Beyond is stored as the seed plus the input stream — a few kilobytes. Feed them back through step() and you get a pixel-perfect reconstruction of the run. I didn’t build a replay system; the architecture is one.
Leaderboards that can’t be lied to. This is the big one. When you submit a score, the client sends the input recording, not the number. The server runs the same TypeScript engine (same code, literally shared package) over the recording and computes the score itself. Claimed 2,000,000 points but your inputs only produce 40,000? The board never sees you. Score injection, memory editing, request tampering — all dead on arrival, because the score isn’t data, it’s a proof.
Tests that catch feel-regressions. Golden-master tests replay canonical runs and assert the final state hash. If I tweak buoyancy and an old replay diverges, CI tells me before a player does.
Two games, one core
Dolphin Beyond ships in two editions, and the deterministic core made this nearly free:
- iPhone (App Store): portrait, one-thumb touch controlling the dive/launch axis, haptics, Sign in with Apple.
- Web (dolphinbeyond.com): landscape, keyboard-controlled, playable instantly in the browser.
Same engine package, two shells. And deliberately two separate leaderboards — a touch score and a keyboard score are different sports, and ranking them against each other would be unfair to both. Fairness is a feature you have to design, not a default you inherit.
The stack around the engine
The shell is React + Vite. Capacitor wraps the iOS build; Firebase handles identity and the leaderboards; and Capgo gives me over-the-air updates, so a physics tuning fix reaches live players in minutes instead of waiting on App Store review (more on that in its own post).
What I’d tell you to steal
If you’re building any score-based game: make the simulation deterministic on day one. It’s nearly impossible to retrofit, and it converts your hardest problems — cheating, replays, regression testing — into properties you get by construction.
Dolphin Beyond is free in the browser and on the App Store. Beat my Two Minutes score; the server will check.