English · Deutsch
Reads real dice with a camera. A Raspberry Pi looks down on the landing area of a dice tower and turns what lands there into data: how many dice, of which kind, showing which value, and what they add up to — as a number on a screen, as a JSON API, and as a live event stream a bot or a game can subscribe to.
Status: it plays, but nothing has run on a real tower yet. Everything from the camera to the scorecard works end to end on simulated frames: capture, settling, pip counting, fifteen game modes, three boards, turns and chips, the fair-play watch, the label loop, the panel, the API and both web front ends — 401 tests, none of which need hardware.
What is missing is the half only a real tower can supply: a trained model (which needs real dice in front of a real camera, and is what makes a d20 readable at all) and every hardware path — the camera modules, the little screen, the lamps and buttons are written and unverified until they are on a Pi.
docs/CONCEPT.md is the reference for where this is going; docs/HARDWARE.md is what to buy and where to put it.
The game screen — the page you put on the television. Pick a game from the lobby, tap through a short wizard, throw.
- Reading pipped dice with no training at all. Segment the dice against the tray, count the pips, report the total. On synthetic scenes it is exact; on real ones it will need the tray and contrast settings adjusted, which is what the Detection tab is for.
- Honest failure. Polyhedral dice are located and reported as unread rather than guessed at, because a confident wrong number is worse than "I need a model for this one".
- Fair play. The tray keeps being watched after the number is read. A hand reaching in is recorded; dice that are not what was read void the roll. It catches a die turned over after the throw, a die added or palmed, the same lucky roll reported twice, a frozen feed and a covered lens — and it says plainly that it is tamper evidence, not tamper proof.
- A label loop instead of a labelling tool. Roll, glance, correct, confirm — every confirmed roll is a training sample, in the browser, with no command line.
- Training from the browser, with live loss and accuracy, exporting an ONNX model the engine picks up. Training needs PyTorch and so runs on a PC; the UI says so plainly rather than failing halfway through.
- A game screen and a setup page.
/is the board you put on the television: pick a game, tap through a setup wizard, play./setupis everything else. One service, one repo, two front doors. - Nothing is read until a game is started. Pick from the lobby, choose how many are playing — the defaults are already right — and go. No keyboard needed: player count, names, colours and the game's own settings are all a tap.
- Turns, holds and chips. Kniffel is three throws with dice kept in between, so DiceCore counts them down, notices which dice you left on the tray, and lets a chip buy a fourth. Two optional GPIO buttons do the same without a browser.
- Game modes. The same dice, read the way the game at your table reads them: a sum, a count of successes, a Kniffel combination, a percentile, a roll under a target. Fifteen of them, from plain six-siders to a chi-square test for whether a die is loaded — and a build your own for the game that is not in the list.
- A screen and two lamps. ST7789, ILI9341 or SSD1306 over the tower shows the number the instant the dice stop, with a small animation for a natural 20; a green and a red LED plus a buzzer say whose turn it is without anyone reading anything. Both are optional, both run at once, and both are previewed in the browser before a single wire is soldered.
- A way in when there is no network. No WiFi for a minute and the box opens its own, with a captive portal that pops the setup page open on a phone — because a tower on a shelf has no keyboard and no screen, and every other recovery needs one.
- Sends rolls out. A finished roll can go straight into a Discord channel, into an
Avrae variable a
!physalias reads back, or as JSON to any URL of yours — so the number off your table lands where the game already is. - Playing against other DiceCores. One of you opens a table, the others join with the address it shows, and one game runs across all of them turn by turn — every roll on every screen as it lands. Each player throws on their own tray, so nobody shares a tower. Works on a LAN and over Tailscale. See docs/ONLINE.md.
- Dice without dice, and one switch to say so. Real or Simulated sits at the top of
the lobby — not in a settings page, because that is not where somebody looks to find out
whether they can play without a tower. Simulated is the default. It draws the dice and
reads the picture back through the real engine, so a
Throwbutton replaces the tower. Everything downstream — modes, boards, panel, scorecards — behaves exactly as it does with a camera. A game night with no hardware at all plays, and so does a table where two people have towers and two do not. - A versioned API and a websocket stream, meant to be embedded by other projects.
- CSI camera modules as configuration — including Arducam IMX519 / 64MP / Owlsight /
Pivariety, which a Pi does not auto-detect. Picking one in the UI writes the
dtoverlayintoconfig.txtand tells you to reboot. - The whole thing without hardware. Simulated dice are the default source,
dicecore synthrenders scenes for thefoldersource to replay, and everything above works on a laptop with nothing plugged in.
The same renderer drives every panel and the browser preview, so the layout can be worked out before anything is soldered.
The lobby. Real dice or simulated ones is one tap, at the top of the page you arrive at — not a capture backend buried in Setup. Nothing is read from the tray until a game is running.
Opening a table. Read out one of those addresses; the seat list fills up as people join, and the seats become the players.
Playing at somebody else's table. The scorecard is theirs, the dice are yours — thrown on your own DiceCore, on your own tray or with the simulator, and on everyone's screen the moment they land.
Teaching it your dice. A set is one lot of dice under one light; a model is trained from one or more sets and knows exactly the faces that were in them.
The workshop side: what the engine actually saw, with its confidence per die.
git clone https://github.com/TechnikWeber/DiceCore && cd DiceCore
python3 -m venv .venv && .venv/bin/pip install -e '.[vision,server,dev]'
.venv/bin/dicecore serve # → http://localhost:8099/That is all of it: simulated dice are the default, so the lobby is already playable — pick a
game and press Throw. / is the game screen, /setup is the workshop. The switch at the
top of the lobby moves between simulated dice and a camera.
Then curl localhost:8099/api/v1/roll:
{"dice": [{"kind": "d6", "value": 3, "confidence": 0.99, "box": {"x": 88, "y": 154, "w": 98, "h": 98}}],
"total": 3, "count": 1, "notation": "1d6 → 3", "engine": "classic", "warnings": [],
"verdict": "clean", "usable": true, "stale": false}The number arrives about a fifth of a second after the dice stop — the fair-play watch runs
behind it and the verdict lands on /api/v1/state and the websocket a couple of seconds
later. ?verify=1 waits for it instead.
curl -fsSL https://raw.githubusercontent.com/TechnikWeber/DiceCore/main/provisioning/bootstrap.sh | bashIt works out which machine it is on and installs what that machine can actually use: the
camera stack and a systemd service on a Pi, PyTorch and no service on a desktop, and on an
ARMv6 Pi Zero the bare package plus a note to point it at another machine. Force it with
| bash -s -- --role desk or --role pi.
By hand instead:
sudo apt install rpicam-apps python3-picamera2
git clone https://github.com/TechnikWeber/DiceCore && cd DiceCore
python3 -m venv --system-site-packages .venv
.venv/bin/pip install -e '.[vision,server]'
.venv/bin/dicecore doctor # what this Pi can and cannot do
.venv/bin/dicecore servedoctor is worth reading before anything else — on a Pi Zero v1 or Pi 3 (ARMv6) there
is no OpenCV and no onnxruntime, and it will say so. That is not a dead end: install with no
extras, set engine.mode=remote, and point it at a PC or a Pi 5 also running DiceCore. The
Pi captures, the other machine reads, and the API answers identically either way.
Capture ────────────► Engine ──────────► Outputs
picamera2 / rpicam classic (pips) HTTP JSON /api/v1/roll
v4l2 / folder / push model (ONNX) WebSocket /api/v1/events
remote (another Web UI
│ DiceCore node) your bot, your game
└──► Dataset ──► Training ──► model.onnx ──┘
Every box is swappable through configuration, and every one of them has an implementation that works without hardware. See docs/CONCEPT.md.
That is the entire point — see docs/API.md.
import requests
roll = requests.get("http://dicecore.local:8099/api/v1/roll", timeout=15).json()
if roll["usable"]: # false only when the tray was interfered with
print(roll["notation"], "=", roll["total"]) # 1d6+1d20 → 4, 14 = 18
# …or let a game mode read it, without changing anybody else's
pool = requests.get(".../api/v1/roll?mode=pool").json()
print(pool["reading"]["headline"]) # 3 successesA die that could not be read has "value": 0 and prints as ?. Never add it up.
Sending rolls out. Avrae rolls its own dice and cannot be made to use yours — what this does instead is put your number where an alias can reach it.
| docs/CONCEPT.md | What this is meant to become, and why it is built this way |
| docs/NETWORK.md | Getting the box on the WiFi, and the network it opens when there is none |
| docs/HARDWARE.md | Which Pi, which camera, where to mount it, how to light it |
| docs/API.md | The contract other projects depend on |
| docs/TRAINING.md | Teaching it your own dice |
| docs/ANTI-CHEAT.md | What the fair-play watch catches, and what it cannot |
| docs/PLAYING.md | The game screen, turns, chips and the two buttons |
| docs/ONLINE.md | Playing against other DiceCores, and dice without dice |
| docs/AVRAE.md | Sending rolls to Avrae, to Discord, or anywhere else |
| docs/GAME-MODES.md | The modes, what each one scores, and how to add another |
| docs/DISPLAYS.md | The screen, the lamps and the buzzer — panels, pins, wiring |
dicecore serve # API + setup page
dicecore roll # read once, print the result
dicecore doctor # what this machine can do, and what the camera says
dicecore synth [folder] # synthetic rolls for the simulator
dicecore sets # dataset sets and whether they are trainable
dicecore train <set> # train a model (needs PyTorch)
dicecore camera-module list # CSI modules; `camera-module imx519` writes config.txt.venv/bin/pytest # the whole suite, no hardware needed
.venv/bin/ruff check src testsThe suite renders its own dice, so recognition is actually tested rather than merely imported. See CLAUDE.md for the conventions before changing anything.
- Run it on a real tower with a real camera; correct docs/HARDWARE.md
- Collect the first real dataset and train the first model
- Top-face selection and 6/9 handling verified on real d20s
- Overlapping and cocked dice: detect and report rather than mis-read
- Fair-play thresholds (
hand_area_frac,motion_threshold) checked against real hands - A real ST7789 and a real SSD1306 on a real Pi; the drivers are written but untested
- A reference Discord bot, in its own repo, consuming this API
- Provisioning: installer, systemd unit, and the IMX519 tuning file shipped
CC BY-NC-ND 4.0 with an additional no-military restriction — see LICENSE.







