Skip to main content

Pure-Python D&D 5e SRD rules engine — combat, checks, effects. Host-agnostic, zero I/O.

Project description

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 (CheckSpecCheckResult).
  • 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.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

dnd5e_engine-0.1.1.tar.gz (209.9 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

dnd5e_engine-0.1.1-py3-none-any.whl (183.7 kB view details)

Uploaded Python 3

File details

Details for the file dnd5e_engine-0.1.1.tar.gz.

File metadata

  • Download URL: dnd5e_engine-0.1.1.tar.gz
  • Upload date:
  • Size: 209.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for dnd5e_engine-0.1.1.tar.gz
Algorithm Hash digest
SHA256 649763ef321471ccd2cfcf939ae8bbfed6785bf4b949141b09346824ee05c8f3
MD5 a793e0a1d8e9eb648eebe8e9579f9a7c
BLAKE2b-256 d87e9bb36628f228a4276012afdf995cc60deac10caaafa02e8339b89133ae90

See more details on using hashes here.

Provenance

The following attestation bundles were made for dnd5e_engine-0.1.1.tar.gz:

Publisher: release.yml on tapestria/nat20

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file dnd5e_engine-0.1.1-py3-none-any.whl.

File metadata

  • Download URL: dnd5e_engine-0.1.1-py3-none-any.whl
  • Upload date:
  • Size: 183.7 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for dnd5e_engine-0.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 cc486dc517d6b3833d88655a2ee120aa498813b5fe529f11084905c002bdbe3a
MD5 52924880e3c271a2e83783fb852a7621
BLAKE2b-256 6f2a2cfd577e6b0197051875d39139f1b6adb0fefa2ad962c6c4027d5b409a51

See more details on using hashes here.

Provenance

The following attestation bundles were made for dnd5e_engine-0.1.1-py3-none-any.whl:

Publisher: release.yml on tapestria/nat20

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page