Play it · Roadmap · Docs · Contributing
A web app for playing darts: X01 scoring, a spoken caller, checkout help, deep statistics, and a camera autoscorer that reads the board and scores your darts for you, all running in the browser on a phone or a laptop.
| 🎯 X01 | 301 → 1001 · straight, double, triple or master in and out · legs and sets · 1–8 players · a checkout route on every finishable score, following the out rule · every dart can be undone. |
| 👆 Board input | Tap where the dart landed (a magnifying lens rises offset from your finger, so a treble is hittable on a phone screen) or tap its number. A tapped dart keeps its position in board millimetres, not just T20. |
| 🔊 Caller | Every visit called out loud from ~440 recorded clips, the delivery rising with the score up to the 180. One MP3 pack with a byte-range index, fetched on the first tap; player names go through speechSynthesis. |
| 📈 Statistics | Classical: 3-dart and first-9 average, checkout %, darts at double, a per-double table, scoring bands, the session trend. Positional: a heatmap, grouping along and across the sector, what happens when you go at your number, and an aim map built from your own spread. |
| 📷 Autoscorer | The camera reads each dart as it lands and proposes the score; you correct the odd one, and each correction is a labelled training sample that makes the next model sharper. |
| 📱 Phone + laptop | The phone is only a camera, the laptop runs the game. WebRTC, with the handshake carried by QR codes both ways: no signalling server, and it works on a hotspot with no internet. No webcam? A 103-character code instead. |
| 🔒 Local‑first | Everything lives in IndexedDB on your device. No account, and the photos you label stay there until you export them. |
npm install
npm test # core rules + geometry
npm run dev # app on http://localhost:5173A match is an append-only log of darts and their corrections, and every dart keeps where it landed, in millimetres on the board. Legs, sets, busts, whose throw it is and who won are folded from the log, never stored, so they cannot disagree with the darts, and a fixed statistics bug fixes your history too. Positions are what make the positional statistics possible at all.
camera ──▶ change gate ──▶ rectify ──────▶ detect tips ──▶ reconcile ──▶ score ──────▶ propose
64×64 grey homography, heatmap model, which tip mm → polar call it,
board crop: board to a 128² grid at is new since → sector or ask
still + changed 512² top-down stride 4 the last one and ring when unsure
- One dart at a time. Each dart is read as it lands, so dart 3 cannot hide dart 2's tip: dart 2 was already locked in.
- Rectify first. The model only ever sees one geometry (board filling the frame, 20 at the top), so camera angle and distance stop being things it has to learn.
- In the browser. ONNX Runtime Web on WebAssembly, loaded only when you switch proposals on. The model is checked by SHA-256 against its release manifest, so the app runs exactly the file that was evaluated, or nothing.
- Calibration is four board points you place once;
packages/corefits the homography. Finding the board automatically is on the roadmap.
packages/core |
Pure TypeScript, no DOM, no I/O: board geometry, X01 rules, checkout routes, statistics, homography. Tested with Vitest. |
apps/web |
Vue 3 (<script setup>), Vite 7, strict TypeScript checked by vue-tsc, Pinia, Vue Router. No component library. |
ml/ |
Python: dataset loading, PyTorch training, evaluation against the accuracy gate, ONNX export. Weights are release artefacts, never committed. |
| Deploy | Static, on GitHub Pages; the tests gate every deploy. |
| 🟢 | Now | Scoring, the caller, statistics and the camera. All working today. The camera scores a match by itself: it reads each dart, calls it, and you correct the odd one it gets wrong. |
| 🟡 | Next | A better interface. Screens redesigned for the phone, the laptop and a scoreboard you can read from the oche. |
| ⚪ | Then | Automatic board calibration. The camera finds the board by itself, instead of you tapping four points on it. |
| ⚪ | Then | Offline and installable. A service worker, so the app installs to your home screen and plays a match with no connection at all. |
| ⚪ | Later | Scoring by voice. Say "treble twenty, five, one" and the caller says it back, so you never touch the screen. |
| ⚪ | Ongoing | A sharper autoscorer. More labelled photos of real boards, in more light and from more angles, to train the next model on. Every dart you correct in a game is one. |
Plans, not promises: the phases and the gates between them are in
docs/07-roadmap.md.
| Doc | What's in it |
|---|---|
| 01 – Product | Scope, the two device modes, what is deliberately not in v1. |
| 02 – Architecture | Packages, the event log, why every dart stores coordinates. |
| 03 – Autoscorer | The runtime choice (ONNX Runtime Web), the pipeline, the training plan, the accuracy gate. |
| 04 – Statistics | Every stat, with its formula. |
| 05 – Dual device | Pairing, WebRTC, signalling. |
| 06 – Voice | Caller out loud, voice-controlled scoring, platform limits. |
| 07 – Roadmap | Phases and the gates between them. |
| 08 – Licensing & data | What may and may not be used here. Read before importing anything. |
AGPL-3.0. The scorer and the autoscorer stay free and open source, including for anyone who runs them as a service.
Caller voice: Chatterbox by Resemble AI. The wordmark is drawn from Barlow (SIL OFL 1.1).
