Skip to content
 
 

Repository files navigation

Nat20 — Pure-Python D&D 5e SRD 5.2 rules engine

Engine: MIT Data: CC-BY-4.0 Python: 3.12+

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.

What is Nat20

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.

SillyTavern bridge

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.

Quickstart

Install the engine (the dataset comes along as a dependency):

uv add dnd5e-engine

Run 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.

Live demo

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-demo

then 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.

Documentation

📖 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 serve

The published site is generated from the same sources (mkdocs build --strict).

Licensing & provenance

See NOTICE for the consolidated attribution.

Disclaimer

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.

About

D&D 5 Engine

Resources

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages