Taps can be unreliable inside this app's built-in browser. For the full experience, use its menu to open this page in your browser.
Skip to content

The Dart You Could Not See

A devlog by Neeraj Kotwani, who builds every game on Elipar.

A darts game is a stationary camera pointed at a board, and a small object travelling away from you. That second half turns out to be the whole problem.

Two compounding reasons it was invisible

First screenshot of a throw in flight: no dart. Not a faint dart, no dart.

The first cause was dull. The dart's geometry was built from small absolute numbers that added up to about 0.2 units — the board is several units across, so the dart was a speck. Worse than wrong, those numbers were never tied to the scene's scale at all; they were just numbers that had seemed reasonable in isolation. A real dart is genuinely comparable in length to a real board's radius, so the geometry is now sized from a constant derived directly from the board radius. Change the board and the dart follows.

Then I took the screenshot again and the dart was still barely visible.

The second cause is the interesting one. The camera sits behind the throw and looks at the board. The dart travels from the camera toward the board — which means for almost the entire flight it is moving along the camera's own line of sight, so you are seeing it nearly end-on. A long thin object viewed down its own axis projects to a dot. Every bit of length I had just added was going into the one dimension the camera cannot see.

You cannot fix that by making it bigger. What reads from this angle is whatever stays roughly broadside throughout, which on a dart is the flights — the fins at the back. So those were enlarged and made emissive, and the dart got a short fading trail of spheres behind it, reusing the pooled-ghost-mesh pattern from another game's ball trail. The throw now reads clearly regardless of the dart's own foreshortened silhouette.

The general version: before scaling an object up, check whether the camera can see the axis you are scaling.

The screenshot was slower than the flight

My other problem was not a game bug at all, and it is the kind of thing that quietly invalidates a whole afternoon.

A screenshot of the WebGL canvas takes about 440ms in my test sandbox. The dart's flight lasts 460ms. So a "mid-flight" screenshot taken after a 200ms wait was, nearly every time, capturing an already-landed dart — the capture itself consumed the rest of the flight.

I only caught it by cross-checking against a cheap state-only evaluation — no GPU readback, so the timing is accurate — which proved the flight genuinely was still in progress at that moment. The screenshot was telling the truth about a later moment than the one I asked for.

Worked around by temporarily stretching the flight duration in a throwaway debug copy, to buy real margin for the capture. Never shipped, obviously. But it belongs in the same family as every other case where measuring something changed what got measured, and the only defence is a second instrument that does not share the first one's cost.

Timing, not aim, is the skill

Aiming is a raycast: fire a ray from the camera through the pointer's normalised coordinates onto an invisible plane at the board's depth. That gives the exact board-local point regardless of viewport aspect, which is the same technique the dots-and-boxes game on this site uses for picking edges. A hand-tuned scale factor from screen pixels to board units would have needed re-tuning for every frame size.

Power is deliberately not a charge-and-release meter. Holding starts a sine wave sweeping 0 to 100 on a 0.85 second period; releasing inside the top fifth throws exactly where you aimed, and releasing outside it adds wobble scaled to how far short you were.

That is a different feel from the slingshot pull-back the pool, curling and mini-golf games share, and it is chosen rather than copied: a real dart's skill is in release timing, not draw distance. It also means aiming alone is not sufficient, which is what makes a stationary target interesting at all.

Scoring checked against something that is not itself

The board is standard proportions — bull, outer bull, treble ring, double ring, all as fractions of the radius — plus the real segment order starting at 20 and running 1, 18, 4, 13 and so on. Crucially the same order and the same ring fractions feed both the scoring function and the canvas texture that draws the numbers, so the printed board and the maths cannot drift apart.

For the test I did not simply call the scoring function and check it agreed with itself. I constructed test points from an independently derived clockwise-from-top parametrisation and checked those landed in the segments a dartboard says they should. Verifying a function with its own internal angle formula proves only that it is self-consistent, which it would be if the whole convention were rotated by one segment.

One more small thing: starting a new game now defensively clears any in-progress flight. That is unreachable in normal play — the game-over screen is the only route to Play Again and it cannot appear until the last dart has landed — but debug hooks calling it mid-flight left a stale flight object bleeding its landing into the next game's dart count. Cheap insurance, found while writing the nine-dart test.

Dart Master 3D is free in a browser, nine darts, no account, no download. You can see the dart.

We use cookies to keep games running and, once ads are live, to show relevant content. See our Privacy Policy for details.