Nat20 is an open-source, host-agnostic, zero-I/O D&D 5e SRD 5.2 (CC-BY-4.0) rules engine for Python. Deterministic combat, skill checks, saving throws, effects, and grid or zone-graph movement — driven by a typed, bundled SRD dataset. No network, no database, no game host required.
Nat20 is a uv workspace of two complementary packages:
| Package | What it is | License |
|---|---|---|
dnd5e-engine |
Pure-Python 5e SRD rules engine — combat, checks, effects, grid/zone movement. Ships no rules data; reads the dataset at runtime. | MIT (code) |
dnd5e-srd-data |
The typed, canonical SRD 5.2 dataset the engine consumes via BundledAssetLoader. |
CC-BY-4.0 (data) |
nat20-bridge |
Localhost FastAPI sidecar that exposes the engine over HTTP — rolls, checks, rests, party validation, full combat lifecycle, SRD browsing, and homebrew content. | MIT (code) |
The engine is edition-agnostic: it resolves whatever typed content it is handed —
install your own loader with set_asset_loader to drive it from a different corpus.
The shipped dataset is the 2024 SRD (5.2) corpus.
Nat20 implements much, but not all, of SRD 5.2. The capability matrix is the honest per-mechanic inventory of what resolves and what does not; read it before depending on a specific rule.
nat20-bridge is a small localhost HTTP host that puts the engine behind a REST API,
so a non-Python client can drive combat, checks, and rests without embedding Python.
It's the server half of the SillyTavern-nat20
extension, which lets a SillyTavern chat run 5e SRD combat and skill resolution through
the bridge instead of freeform narration.
uvx nat20-bridge # or: uv run nat20-bridge (in-repo)nat20-bridge isn't published on PyPI yet — until then, uvx nat20-bridge won't
resolve; run uv run nat20-bridge from a checkout of this repo instead.
See docs/bridge.md for the full endpoint table, quickstart, and
homebrew-content notes.
Install the engine (the dataset comes along as a dependency):
uv add dnd5e-engineRun a tiny grid combat end-to-end. Every name below comes from the engine's public
surface (dnd5e_engine.__all__):
import asyncio
from dnd5e_engine import (
EncounterMemberSpec,
GridScene,
PartyMemberSpec,
PlayerIntent,
cell_id,
end_combat,
start_combat,
submit_player_intent,
)
async def main() -> None:
# One Hero at cell (0,0), one Foe at (5,0), on a 10x10 grid. rng_seed makes
# the dice deterministic; cell_id(col, row) encodes the "col,row" position.
start = await start_combat(
session_id="example",
party=[
PartyMemberSpec(
entity_id="char:hero",
name="Hero",
initiative=20, # high initiative => the Hero acts first
hp_current=12,
hp_max=12,
ac=12,
zone_id=cell_id(0, 0),
)
],
encounter=[
EncounterMemberSpec(
entity_id="mon:foe",
entity_type="Monster",
name="Foe",
initiative=1,
hp_current=7,
hp_max=7,
zone_id=cell_id(5, 0),
)
],
grid_scene=GridScene(width=10, height=10),
rng_seed=1,
)
# The Hero takes one diagonal step toward the Foe.
await submit_player_intent(
start.handle,
actor_id="char:hero",
intent=PlayerIntent(intent_type="move", target_zone_id=cell_id(1, 1)),
)
# Close the encounter; the result carries the projected outcome.
result = await end_combat(start.handle)
print(f"Combat ended ({result.outcome.ended_reason}).")
print(f"Residual HP: {result.outcome.residual_hp}")
asyncio.run(main())The full, verified-runnable version lives in examples/grid_combat.py,
which also resolves an attack and prints the resulting CombatEvent stream.
apps/demo is a small playable HTMX web app built on top of the public
engine API — pick a scenario, take a turn, watch the engine's event tape fill in. The
server is stateless: every fight is replayed from a seed, so any combat is a shareable
permalink that reproduces the same dice.
Run it locally (from the repo root, after uv sync --all-packages --all-extras):
uv run nat20-demothen open http://127.0.0.1:8000. The demo README covers the scenarios, the replay architecture, and development. The repo ships no deployment logic — the demo is a plain ASGI app any host can run.
📖 tapestria.github.io/nat20 — concepts, the capability matrix, the public API reference, and a feature comparison.
The site is built with MkDocs from docs/. Build and browse it locally:
uv run --group docs mkdocs serveThe published site is generated from the same sources (mkdocs build --strict).
- Engine code (
dnd5e-engine): MIT — seepackages/dnd5e-engine/LICENSE. The engine ships no SRD data. - Dataset (
dnd5e-srd-data): CC-BY-4.0 — seepackages/dnd5e-srd-data/LICENSE. Portions derived from the Foundry VTT dnd5e system (CC-BY-4.0), cross-checked against open5e (CC-BY-4.0) and the 5e-bits/5e-database project (MIT). - Original source: System Reference Document 5.1 and 5.2 by Wizards of the Coast LLC, distributed under CC-BY-4.0.
See NOTICE for the consolidated attribution.
Nat20 implements rules from the D&D 5e System Reference Document (SRD 5.2), which is published by Wizards of the Coast LLC under the Creative Commons Attribution 4.0 International License (CC-BY-4.0). Nat20 is an independent, unofficial project. It is not affiliated with or endorsed by Wizards of the Coast. "Dungeons & Dragons" and "D&D" are trademarks of Wizards of the Coast LLC; their use here is nominative, to identify the SRD ruleset this project implements.
Built by the Tapestria team. Nat20 is the deterministic rules oracle behind tapestria.quest, an AI-driven MUD TTRPG.