A devlog by Neeraj Kotwani, who builds every game on Elipar.
When I built Curling 3D, the first version was unplayable — not badly balanced, genuinely unplayable. Every throw at any realistic power slid straight through the house, past the back line, and out of play. Aim did not matter. Intent did not matter. The game had exactly one outcome.
The cause was a single number, and the way it was chosen is the interesting part.
Choosing a constant because it sounds right
Curling ice is slippery. That is the entire premise of the sport. So friction was set to 0.9935 — a velocity retention of 99.35% per frame, which feels like a sensible number for something that famously glides.
It is not a sensible number, and you can prove that without running the game.
The arithmetic nobody did
Per-frame exponential decay has a closed form for total distance. If each frame retains a fraction f of velocity, a stone launched at v₀ travels:
distance = v₀ / (1 − f)
At f = 0.9935, that divisor is 0.0065 — so the stone travels roughly 154 times its launch velocity. Plug in a full-power throw and it slides about 3,400 pixels.
The sheet is 620 pixels long.
Every throw overshot the entire playing surface by more than five times. There was no skill window because there was no window at all.
Solve backwards from the geometry you actually have
The fix was not to nudge the constant until it felt better. It was to invert the equation and solve for the value the sheet requires.
I wanted a maximum-power throw to land just inside the back boundary at 580px — so around 560px of travel. Rearranged, that gives f = 0.9607.
That single change turned the game into a game. The power range now maps onto real curling decisions: roughly 6–13 puts down a short guard, 17–20 draws into the house, and 20–22 is a takeout that will slide through the back if you overcook it. Those are strategy choices, and they exist only because the numbers were derived from the board instead of from vibes.
Why "realistic" was the wrong target
The instinct that produced 0.9935 was to model real ice. But the sheet is 620 pixels, not the 45-odd metres of a real one, and the stone is a few pixels wide, not 20kg of granite. None of the real-world constants transfer, because none of the real-world dimensions did either.
A physics constant in a game is not a fact about the world. It is a value that has to satisfy the geometry you actually built. If you scale the board and keep the constant, or copy a constant from a reference and keep your own board, you get this bug.
The bugs that only a full match revealed
Three more issues survived the isolated unit tests and were caught only by playing a complete game, which is a pattern worth naming.
The CPU threw thirty stones per turn. The settle-handler scheduled the opponent's throw on a 500ms timer but never changed state while waiting — and the frame loop re-entered that handler every frame while the state was unchanged. One CPU turn queued about thirty pending throws at 60fps, each of which eventually fired. Every isolated test passed. The tell was a final score in the hundreds when the real ceiling is twelve stones.
Restarting mid-delay injected a stray stone. Starting a new end never cancelled a still-pending CPU timer, so it fired later into whatever end was running by then. A real bug for anyone tapping "Play Again" at the wrong moment.
A full-power drag landed on a button. The maximum pull-back distance physically overlapped the in-turn/out-turn toggle, so the release fired on the toggle instead of the canvas and the throw silently never registered. Only a real drag gesture at real coordinates finds that; a synthetic one that dispatches to the canvas directly never will.
What I do differently now
Two rules came out of this game and have caught things since.
First: derive constants from the geometry, and write the arithmetic down. If a value cannot be justified against a measurement of your own board, it is a guess wearing a decimal point.
Second: isolated tests prove a mechanic works; they do not prove the game does. All three of the timing bugs above passed every unit test I had. Playing one complete match found all three.
Curling 3D is free to play in a browser tab — a full rink with granite stones, a painted house under pebbled ice, and friction that now lets you actually reach it. If you want the same drag-and-release physics somewhere warmer, try Mini Golf or Pool Master.