One dice API for web apps and game engines.
Roll dice deterministically, then — optionally — show them in 3D. The engine resolves every outcome headlessly, with no renderer, network, or DOM in sight; the presenter animates that already-decided result. Because the two are separate, the same roll behaves identically in a test, a server, and a browser.
import { createDiceEngine, createSeededRandomSource } from "@diceforge-sdk/core";
const engine = createDiceEngine({ random: createSeededRandomSource("table-42") });
engine.roll("2d20kh1+3"); // advantage, +3 — frozen, serializable, replayable
engine.flipCoin(); // { outcome: "heads" | "tails", ... }- The result is decided before anything is drawn. Presentation consumes a resolved record and cannot change it — a die's animation ends on the face the engine already chose, by construction rather than by correction.
- Deterministic where it matters. The same seed produces the same rolls on every platform and release. Golden tests lock the generator; changing it is a documented breaking change.
- Offline and dependency-light. The core has zero runtime dependencies and never touches the network. Multiplayer is an opt-in plugin concern, not a baseline assumption.
- Trustworthy records. Results are deeply frozen and schema-versioned; deserialization re-checks totals, so a record you load is a record you can rely on.
- Extensible by design. Renderers, themes, physics, audio and transports sit behind small contracts instead of forks.
npm install @diceforge-sdk/core # headless engine, zero dependencies
npm install @diceforge-sdk/renderer-web # + browser presentation (pulls in three.js)
npm install @diceforge-sdk/assets-forge # + the 3D dice themselves (optional)Both are ESM-only. The core runs anywhere with ES2022 — Node 20+, browsers, workers; the renderer needs a DOM, and uses WebGL when it can.
| Area | Status |
|---|---|
| Dice d4–d20, d100/percentile, any face count, custom dice, coin flips | Resolved headlessly, fully tested |
Notation 2d20kh1+3, 4d6dl1, 4d6r1!, d% |
Grammar v1.2: keep/drop, exploding, rerolls, positioned parse errors |
| Seeded + system randomness | Reproducible across platforms, provenance recorded per result |
| Serialization and replay | schemaVersion: 2, validated on read; sessions replay without re-rolling |
| Browser presentation | Three.js dice, DOM fallback, reduced motion, aria-live announcements |
| Themes | First-party textured set: every shape plus a two-faced coin, in five colours, installable from npm |
| Plugin conformance suite | @diceforge-sdk/testing — checks a presenter against the contract it declares |
| Unity / Godot adapters | Not started — see ROADMAP.md |
import { forgeAssets } from "@diceforge-sdk/assets-forge";
import { createDicePresenter, forgeTheme } from "@diceforge-sdk/renderer-web";
const presenter = createDicePresenter({
container: document.querySelector("#stage")!,
theme: forgeTheme(forgeAssets({ color: "red" })),
});
await presenter.present(engine.roll("4d6dl1"));3D needs art, and the code packages ship none. The dice are a separate optional install: @diceforge-sdk/assets-forge carries the models and textures and hands back URLs your bundler emits, so nothing has to be copied or hosted (ADR-0013). Prefer to serve them yourself? Pass forgeTheme({ baseUrl: "/dice-assets", color: "red" }) instead. Without a theme the presenter still works — it falls back to accessible labelled tiles and reports mode: "dom" — but there is nothing to draw in 3D (ADR-0012).
Full options, fallback behaviour and theming are in the renderer README.
npm ci
npm run example # headless: seeded rolls and serialization, in the terminal
npm run demo:web # browser demo: 3D dice, themes, fallbacks
npm run demo:react # the same, inside React — swap presenters while it runs0.7.0 is on npm — the engine @diceforge-sdk/core, the browser presenter @diceforge-sdk/renderer-web, and three optional packages: the dice themselves in @diceforge-sdk/assets-forge, the plugin conformance suite in @diceforge-sdk/testing, and physics motion in @diceforge-sdk/presenter-physics. DiceForge also runs natively in Godot as a GDScript addon — see adapters/godot. 0.2.0 completed browser presentation, 0.3.0 made the dice installable, 0.4.0 was the extensibility milestone — custom dice, exploding and reroll notation, replayable sessions, a presenter contract a third party can be held to — 0.5.0 added dice that tumble under real physics and still land on the resolved face, 0.6.0 flips coins the same way, and 0.7.0 brings sound, the Godot engine port, and rerolls and explosions played as stories. See the changelog, which lists what to check when upgrading.
The headless core's contract — notation grammar, event schema, seeded RNG, the presenter interface — is declared stable as of ADR-0022: changes from here are additive only, each carried by its own dated decision record. Presentation packages still iterate freely between minor versions. Serialized records carry a schemaVersion so stored results survive additions.
| API.md | Public contracts, notation grammar, determinism guarantees |
| ARCHITECTURE.md | Package boundaries and the rules that keep the core portable |
| DECISIONS.md | Architecture decision records — why things are the way they are |
| ROADMAP.md · TASKS.md | Where this is going, and what is in flight |
| CONTRIBUTING.md · CODE_OF_CONDUCT.md | How to build, test, and take part |
| packages/testing | Writing a presenter, and the conformance suite that checks one |
| CHANGELOG.md | What changed, and when |
Two pieces of tooling have their own guides: the Blender dice generator, which produces the first-party models and textures, and the visual regression suite, which catches renderer changes that unit tests cannot see.
The dice are first-party and MIT licensed, generated by the Blender pipeline, and published as @diceforge-sdk/assets-forge — an optional package, so an install of the engine or the renderer still carries no art. Provenance and the rules for any third-party pack are recorded in assets/LICENSES.md.
MIT. See LICENSE.