Domain-neutral D&D 5e (2024) rules & character-sheet computation engine: a data-driven DAG of formulas (ability mods, proficiency, spell DC/slots, HP, AC).
Project description
dndwright
A domain-neutral D&D 5e (2024) rules & character-sheet computation engine — formulas as data, not code.
A character sheet is modelled as a directed acyclic computation graph — nodes are values,
edges are dependencies, and formulas are data (a JSON-serialisable DSL), not code. Pure
Python (pydantic + stdlib), no application or framework coupling: map your own character
data in, read computed stats out.
⚠️ Early development (alpha). The API is still moving and may change between minor versions while at
0.x. Usable today — pin a version if you depend on it.
Install
pip install git+https://github.com/sligara7/dndwright.git
# or, for local development:
pip install -e ".[dev]"
Quickstart
from dndwright import evaluate_character
sheet = evaluate_character({
"ability_scores": {"strength": 8, "dexterity": 14, "constitution": 14,
"intelligence": 18, "wisdom": 12, "charisma": 10},
"class_data": {"class_name": "wizard"},
"species_data": {"name": "Human", "speed": 30},
"level": 5,
})
sheet["proficiency_bonus"] # 3
sheet["ability_modifiers"] # {"intelligence": 4, "dexterity": 2, ...}
sheet["spellcasting_type"] # "full_caster"
# ...plus armor_class, hit_points, hit_dice, initiative, saves, features, ...
Lower level — assemble typed inputs and evaluate against the ruleset:
from dndwright import DND_5E_2024_RULESET, assemble_character_inputs, evaluate, apply_modifiers
from dndwright.rules.components import ClassMechanics
inputs = assemble_character_inputs(class_mechanics=..., ability_scores={...}, level=5)
computed = apply_modifiers(evaluate(DND_5E_2024_RULESET, inputs), inputs)
Command line
Installing the package also installs a dndwright command (no Python required):
dndwright eval character.json # character JSON → computed sheet (or '-' for stdin)
dndwright graph --format mermaid # export the computation DAG (mermaid|dot)
dndwright content magic_items # dump bundled content (omit category to list)
dndwright validate ruleset.json # check a ruleset (built-in if omitted)
Rolling dice
A self-contained, typed dice engine (dndwright.dice) — deterministic by default:
from dndwright.dice import DiceEngine
eng = DiceEngine(seed=42) # reproducible (stdlib RNG)
eng.roll("4d6kh3").total # keep highest 3 of 4
eng.roll("1d20", advantage=True) # -> ExpressionResult
eng.roll_attack(modifier=5, target_ac=15).is_hit
eng.roll_damage("2d8", is_critical=True) # crit doubles the dice
# unpredictable production rolls (no NumPy dependency):
import secrets
DiceEngine(rng=secrets.SystemRandom())
Combat rules
Pure, persistence-free 5e combat (dndwright.combat) — state is a frozen value object,
every op is (state, input) → (new_state, explanation):
from dndwright.combat import CombatantState, apply_damage, roll_death_save
from dndwright.dice import DiceEngine
s = CombatantState(current_hp=8, max_hp=20, temp_hp=3)
s, applied = apply_damage(s, 10) # temp HP absorbs first, overkill tracked
s, save = roll_death_save(s, DiceEngine(seed=1)) # nat 20 → 1 HP; 3 fails → dead
s.is_stable, s.is_dead, s.hp_percentage
Your app owns persistence: load a row → call these → write the new state back. The rules never see a database.
Why a computation graph?
Derived character values form a dependency DAG: ability scores → modifiers → proficiency →
save DCs / spell slots / AC / HP. dndwright represents that DAG explicitly and stores the
formulas as data (FormulaSpec: an op + args), so the rules are inspectable, testable,
and serialisable — not buried in imperative code. DND_5E_2024_RULESET is a 135-node graph.
What's inside
| Component | What it does |
|---|---|
evaluate_character |
One call: character data dict → fully computed sheet. |
DND_5E_2024_RULESET |
The 135-node 5e-2024 computation DAG (formulas as data). |
evaluate / assemble_character_inputs / apply_modifiers |
The lower-level engine. |
Ruleset / ComputationNode / FormulaSpec / NodeType |
The DAG schema. |
validate_ruleset / assert_valid_ruleset |
Static integrity check for a ruleset (unknown ops, cycles, dangling refs) — catch authoring errors before evaluation. |
compose / modifier / Component |
Snap mini-graphs (items/feats/traits) onto a ruleset; downstream values cascade. |
component_from_content |
Build a Component from a bundled item/feat's component field — magic items & feats as data that snap onto a character (constant, dynamic, or player-chosen effects). |
to_mermaid / to_dot |
Render the computation DAG as Mermaid or Graphviz DOT — see the dependency graph. |
dndwright.dice |
Typed dice engine: parse/roll 5e expressions, attacks, saves, damage, stat arrays. |
dndwright.combat |
Pure combat rules over a frozen CombatantState: damage, temp HP, healing, death saves. |
dndwright.combat.initiative |
Pure initiative: roll, order (DEX tie-break), advance/rewind turns. |
dndwright.combat.conditions |
Pure conditions over the bundled SRD catalog: effects, ticking, saves. |
dndwright.rules.components |
Typed inputs (ClassMechanics, SpeciesMechanics, …). |
dndwright.rules.lookup_tables |
SRD-derived rules tables (hit dice, spell slots, AC, saves). |
load_content("feats") / load_content("magic_items") |
Bundled SRD feats & magic items as data — many carry a composable component. |
API stability
The public API is exactly dndwright.__all__, pinned by tests/test_api_contract.py.
Versioning follows SemVer; at 0.x minor versions may break, with
every change recorded in CHANGELOG.md. Maintainers: the release process is documented in
RELEASING.md.
Credits & license
MIT licensed (see LICENSE). The rules tables encode game mechanics derived from the
D&D System Reference Document 5.2 (© Wizards of the Coast, CC-BY-4.0); see NOTICE.
Not affiliated with or endorsed by Wizards of the Coast. Contains no PHB/DMG/MM content.
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 dndwright-0.11.0.tar.gz.
File metadata
- Download URL: dndwright-0.11.0.tar.gz
- Upload date:
- Size: 166.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
fc04e64b4e61e3dc498c041bffda8be34ee21b5598a70dac1b114be32b65e2e5
|
|
| MD5 |
953b285f85f4440d1ca98c913dca87d4
|
|
| BLAKE2b-256 |
a185cc151c31bcef82211f9bad78d308ad2a5663bed4f3bd24fbb98c127ffb31
|
Provenance
The following attestation bundles were made for dndwright-0.11.0.tar.gz:
Publisher:
publish.yml on sligara7/dndwright
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
dndwright-0.11.0.tar.gz -
Subject digest:
fc04e64b4e61e3dc498c041bffda8be34ee21b5598a70dac1b114be32b65e2e5 - Sigstore transparency entry: 1706941451
- Sigstore integration time:
-
Permalink:
sligara7/dndwright@970f1271ecfa364d6accd08a31d7d51267461b8d -
Branch / Tag:
refs/tags/v0.11.0 - Owner: https://github.com/sligara7
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@970f1271ecfa364d6accd08a31d7d51267461b8d -
Trigger Event:
release
-
Statement type:
File details
Details for the file dndwright-0.11.0-py3-none-any.whl.
File metadata
- Download URL: dndwright-0.11.0-py3-none-any.whl
- Upload date:
- Size: 139.3 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 |
b82801e9ddb221d0997d658e905cb1b27c600aa87bc0bf2cc6d63e2e37a806d0
|
|
| MD5 |
5ab6b90a9353f2299214e4404d43f21f
|
|
| BLAKE2b-256 |
384cdb50229035ab4fe8df9a4d2cbcc0dcf67a70186b6aefd780d1a91a0864bd
|
Provenance
The following attestation bundles were made for dndwright-0.11.0-py3-none-any.whl:
Publisher:
publish.yml on sligara7/dndwright
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
dndwright-0.11.0-py3-none-any.whl -
Subject digest:
b82801e9ddb221d0997d658e905cb1b27c600aa87bc0bf2cc6d63e2e37a806d0 - Sigstore transparency entry: 1706941500
- Sigstore integration time:
-
Permalink:
sligara7/dndwright@970f1271ecfa364d6accd08a31d7d51267461b8d -
Branch / Tag:
refs/tags/v0.11.0 - Owner: https://github.com/sligara7
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@970f1271ecfa364d6accd08a31d7d51267461b8d -
Trigger Event:
release
-
Statement type: