Skip to main content

dnd5e-engine

Pure-Python D&D 5e SRD rules engine — host-agnostic, zero I/O. Combat, checks, effects, on a zone graph or a 2-D grid.

Status

Working engine. It resolves combat against the typed 2024-SRD corpus shipped by dnd5e-srd-data (the BundledAssetLoader reads the bundled canonical/ data — no network, no DB). The engine is edition-agnostic: it consumes whatever typed content it is handed.

Two spatial backends are supported, selected at start_combat:

  • Zone graph — pass scene_zones=SceneTopology(zones=..., edges=...); range/reach are resolved as shortest-path distance over a weighted, undirected zone graph.
  • 2-D grid — pass grid_scene=GridScene(width, height); positions are "col,row" cell ids (cell_id(col, row)), distance is Chebyshev (8-direction, one cell = cell_size_ft).

Install

Dev (editable, from this directory):

cd packages/dnd5e-engine
uv venv && uv pip install -e '.[dev]'
uv run --extra dev pytest -q
uv run --extra dev ruff check src/ tests/ scripts/
uv run --extra dev mypy src/

Standalone wheel:

uv build          # builds dist/dnd5e_engine-*.whl (+ sdist)

A clean-room install smoke builds both wheels, installs them into a throwaway venv with no editable path deps, and runs a real grid combat through the public API:

bash scripts/smoke_clean_install.sh   # ends with "==> SMOKE PASSED"

Quickstart

A minimal grid combat: open it, move a PC one cell, then close it.

import asyncio

from dnd5e_engine import (
    EncounterMemberSpec,
    GridScene,
    PartyMemberSpec,
    PlayerIntent,
    cell_id,
    end_combat,
    start_combat,
    submit_player_intent,
)


async def main() -> None:
    start = await start_combat(
        session_id="demo",
        party=[
            PartyMemberSpec(
                entity_id="char:hero",
                name="Hero",
                initiative=20,
                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 won initiative; move one cell diagonally.
    await submit_player_intent(
        start.handle,
        actor_id="char:hero",
        intent=PlayerIntent(intent_type="move", target_zone_id=cell_id(1, 1)),
    )

    result = await end_combat(start.handle)
    print("ended reason:", result.outcome.ended_reason)


asyncio.run(main())

An attack instead of a move: submit PlayerIntent(intent_type="attack", target_id="mon:foe", weapon_id="longsword") — the engine fetches the typed weapon from the bundled corpus and walks its activities.

Public API

All exported names live in __all__ in src/dnd5e_engine/__init__.py. The key entry points:

  • start_combat(*, session_id, party, encounter, scene_zones=|grid_scene=, rng_seed, ...) — open a combat, materialize runtime state, return a StartCombatResult (.handle, opening .events).
  • submit_player_intent(handle, actor_id, intent) — validate and resolve a PC's PlayerIntent for the current turn.
  • advance_monster_turn(handle) — resolve the active monster's turn via its typed action repertoire + behavior gambits.
  • end_combat(handle) — close a combat and return an EndCombatResult (.outcome, .events, .final_active_effects).
  • narration_events(handle) — async iterator streaming the CombatEvent union for the narrator.
  • get_actor_active_effects(handle, entity_id) — read-only snapshot of one combatant's active effects.
  • resolve_check(spec) — resolve an out-of-combat ability check / saving throw (CheckSpec → CheckResult).
  • build_party_member(spec, ...) — project a CharacterBuildSpec into a combat-ready PartyMemberSpec.
  • make_build_spec(...) — assemble a CharacterBuildSpec (ability scores, class, species).

Spatial helpers and spec types:

  • GridScene / cell_id(col, row) / parse_cell(cell_id) — 2-D grid scene + cell-id codec.
  • SceneTopology / ZoneEdge — zone-graph scene description.
  • PartyMemberSpec / EncounterMemberSpec — combatant inputs to start_combat.
  • PlayerIntent — a PC's submitted intent (move / attack / cast_spell / use_item / ...).
  • CombatHandle, StartCombatResult, EndCombatResult, CombatOutcome, CombatEvent, ActiveEffect, CheckSpec/CheckResult/CheckKind, CharacterBuildSpec, AbilityScores, and the IntentType / ActionType literals.

Layout

packages/dnd5e-engine/
├── pyproject.toml          hatchling build; pydantic + d20 + dnd5e-srd-data deps
├── LICENSE                 MIT (engine code)
├── scripts/
│   ├── _smoke_grid_combat.py     clean-room smoke program (runs in a fresh venv)
│   └── smoke_clean_install.sh    builds wheels + installs them + runs the smoke
├── src/dnd5e_engine/
│   ├── __init__.py         public API (__all__)
│   ├── orchestrator.py     start/submit/advance/end combat seam + live state
│   ├── spatial.py          grid + zone topologies, cell_id / parse_cell
│   ├── specs.py            GridScene, SceneTopology, party/encounter specs
│   ├── check.py            out-of-combat ability check / saving throw resolver
│   ├── build_party.py / build_spec.py   character build → combat spec projection
│   ├── lib_loader.py       BundledAssetLoader singleton (typed SRD corpus)
│   ├── events.py / outcome.py / results.py   event union, outcome, result envelopes
│   ├── activities/         typed-Activity resolvers (attack / save / monster actions)
│   ├── rules/              dice, conditions, gambits, ...
│   └── types/              combat / effects / conditions / intent value types
└── tests/                  pytest suite

License

  • Engine code: MIT — see LICENSE. The engine ships no SRD data.
  • SRD content (the typed corpus consumed via dnd5e-srd-data): CC-BY-4.0 — see the dnd5e-srd-data package's LICENSE and NOTICE for the dataset license and attribution chain.

Metadata

Release files for dnd5e-engine 0.3.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for dnd5e-engine 0.3.0
File Size Uploaded
dnd5e_engine-0.3.0.tar.gz 311.8 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for dnd5e-engine 0.3.0
File Interpreter ABI Platform
dnd5e_engine-0.3.0-py3-none-any.whl Python 3 none any Details

Total release size: 540.7 kB

Release files / dnd5e_engine-0.3.0.tar.gz

Download URL dnd5e_engine-0.3.0.tar.gz
Size 311.8 kB
Tags Source
SHA-256 checksum
How to use checksums
87fa77a06d32c336bc3b15a077ae389a31c33d6d1b5b89df659a1b3dec4ccfe3
BLAKE2b-256 checksum
How to use checksums
606e454941fdcda082ee86e2ca3c89b8deef2d115d5e1f020cb0efd93e6923ac
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 10, 2026.

Transparency log

Release files / dnd5e_engine-0.3.0-py3-none-any.whl

Download URL dnd5e_engine-0.3.0-py3-none-any.whl
Size 228.9 kB
Tags Python 3
SHA-256 checksum
How to use checksums
7d817eac818108ee46f0cbb560d8f916d48f0eaf35831a104c337bf05efc14ff
BLAKE2b-256 checksum
How to use checksums
f52101c236b872cd9c98c01b217c70d11e0c6be3a566e3f4de475e49b6f02348
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 10, 2026.

Transparency log

Release history Release notifications | RSS feed

0.7.0

2 release files

0.6.0

2 release files

0.5.0

2 release files

0.4.0

2 release files

0.3.2

2 release files

0.3.1

2 release files

This release

0.3.0 This release

2 release files

0.2.0

2 release files

0.1.2

2 release files

0.1.1

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page