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 aStartCombatResult(.handle, opening.events).submit_player_intent(handle, actor_id, intent)— validate and resolve a PC'sPlayerIntentfor 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 anEndCombatResult(.outcome,.events,.final_active_effects).narration_events(handle)— async iterator streaming theCombatEventunion 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 aCharacterBuildSpecinto a combat-readyPartyMemberSpec.make_build_spec(...)— assemble aCharacterBuildSpec(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 tostart_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 theIntentType/ActionTypeliterals.
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 thednd5e-srd-datapackage'sLICENSEandNOTICEfor the dataset license and attribution chain.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
649763ef321471ccd2cfcf939ae8bbfed6785bf4b949141b09346824ee05c8f3
|
|
| MD5 |
a793e0a1d8e9eb648eebe8e9579f9a7c
|
|
| BLAKE2b-256 |
d87e9bb36628f228a4276012afdf995cc60deac10caaafa02e8339b89133ae90
|
Provenance
The following attestation bundles were made for dnd5e_engine-0.1.1.tar.gz:
Publisher:
release.yml on tapestria/nat20
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
dnd5e_engine-0.1.1.tar.gz -
Subject digest:
649763ef321471ccd2cfcf939ae8bbfed6785bf4b949141b09346824ee05c8f3 - Sigstore transparency entry: 1987032502
- Sigstore integration time:
-
Permalink:
tapestria/nat20@4e78642939ac97b1371effe9dffc8f3677fafd56 -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/tapestria
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@4e78642939ac97b1371effe9dffc8f3677fafd56 -
Trigger Event:
release
-
Statement type:
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
cc486dc517d6b3833d88655a2ee120aa498813b5fe529f11084905c002bdbe3a
|
|
| MD5 |
52924880e3c271a2e83783fb852a7621
|
|
| BLAKE2b-256 |
6f2a2cfd577e6b0197051875d39139f1b6adb0fefa2ad962c6c4027d5b409a51
|
Provenance
The following attestation bundles were made for dnd5e_engine-0.1.1-py3-none-any.whl:
Publisher:
release.yml on tapestria/nat20
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
dnd5e_engine-0.1.1-py3-none-any.whl -
Subject digest:
cc486dc517d6b3833d88655a2ee120aa498813b5fe529f11084905c002bdbe3a - Sigstore transparency entry: 1987032643
- Sigstore integration time:
-
Permalink:
tapestria/nat20@4e78642939ac97b1371effe9dffc8f3677fafd56 -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/tapestria
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@4e78642939ac97b1371effe9dffc8f3677fafd56 -
Trigger Event:
release
-
Statement type: