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

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.1.2
File Size Uploaded
dnd5e_engine-0.1.2.tar.gz 213.3 kB Details

Built distribution (wheel)

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

Total release size: 399.5 kB

Release files / dnd5e_engine-0.1.2.tar.gz

Download URL dnd5e_engine-0.1.2.tar.gz
Size 213.3 kB
Tags Source
SHA-256 checksum
How to use checksums
dd02a5db1ad0f56553169ed081af9d5cdd9efbccd92e1831f4d22fc5f388d2e7
BLAKE2b-256 checksum
How to use checksums
a1b7359cdedfa895b3891bbd351906770ec8acd0bd0cc6da1024db36f5338a1c
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.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 Jul 26, 2026.

Transparency log

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

Download URL dnd5e_engine-0.1.2-py3-none-any.whl
Size 186.2 kB
Tags Python 3
SHA-256 checksum
How to use checksums
e2914af06be2f12ce8c90a242be09d7c437d9c685867fcd9d0dd3a8e526ed084
BLAKE2b-256 checksum
How to use checksums
7a5a5b4ab232729c7e7f7d5ccb82d5bbe2f3eb123c295c28a5d8a9f362976609
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.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 Jul 26, 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

0.3.0

2 release files

0.2.0

2 release files

This release

0.1.2 This release

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