Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 15 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,19 @@ breaking changes; these will always be noted here.

## [Unreleased]

## [0.23.2] — 2026-06-06

### Documentation
- **Two new README/PyPI diagrams.** `assets/compose.svg` shows lego-style components snapping
onto the ruleset — typed `set`/`add`/`union` contributions that keep the target node's id, so
one snap-in recomputes the whole downstream subtree. `assets/theme-scaling.svg` shows one
computation graph re-skinned per setting via `ThemeScalingLayer`: the same `plate` node emerges
as AC 18 (traditional / modern, re-flavored) or AC 19 (sci-fi / steampunk) through
`input_overrides`, `lookup_overrides`, and display-only `flavor_renames`.
- **New README sections** — "Composable — snap mini-graphs onto the ruleset" and "Re-skin for
any setting — theme scaling" (theme scaling was previously undocumented in the README), plus a
`apply_theme_scaling` / `ThemeScalingLayer` row in the feature table. Docs-only; no code change.

## [0.23.1] — 2026-06-06

### Fixed
Expand Down Expand Up @@ -441,7 +454,8 @@ from a working application.
Pure (pydantic + stdlib); no application/framework coupling. Rules content derives
from the SRD 5.2 (CC-BY-4.0); see NOTICE.

[Unreleased]: https://github.com/sligara7/dndwright/compare/v0.23.1...HEAD
[Unreleased]: https://github.com/sligara7/dndwright/compare/v0.23.2...HEAD
[0.23.2]: https://github.com/sligara7/dndwright/compare/v0.23.1...v0.23.2
[0.23.1]: https://github.com/sligara7/dndwright/compare/v0.23.0...v0.23.1
[0.23.0]: https://github.com/sligara7/dndwright/compare/v0.22.0...v0.23.0
[0.22.0]: https://github.com/sligara7/dndwright/compare/v0.21.0...v0.22.0
Expand Down
38 changes: 38 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,43 @@ and serialisable — not buried in imperative code. `DND_5E_2024_RULESET` is a 1
<img alt="The dndwright computation graph: ability scores, level, class and equipment flow through ability modifiers and proficiency bonus to saves, skills, spell DC/attack, spell slots, HP, AC and initiative" width="760" src="https://raw.githubusercontent.com/sligara7/dndwright/main/assets/computation-graph.svg">
</p>

### Composable — snap mini-graphs onto the ruleset

Items, feats and species traits are themselves tiny graphs. `compose()` merges a
`Component`'s nodes and contributions onto a base ruleset and returns a new, larger
`Ruleset` — the base is never mutated. Because each contribution keeps its target node's
**id**, every existing edge downstream re-derives for free: a `set`/`add`/`union` on one
node ripples out to every modifier, save, skill and attack that depends on it.

<p align="center">
<img alt="Components are lego-style mini-graphs that snap onto the dndwright ruleset: a Belt of Giant Strength sets the Strength score, a Ring of Protection adds to Armor Class, and Dwarven Resilience unions in poison resistance — compose() merges them and one snap-in recomputes the whole downstream subtree (strength modifier to athletics, saving throws and melee attack)" width="760" src="https://raw.githubusercontent.com/sligara7/dndwright/main/assets/compose.svg">
</p>

```python
from dndwright import DND_5E_2024_RULESET, compose, modifier
ring = modifier("ring_of_protection", target="armor_class", amount=1)
rs = compose(DND_5E_2024_RULESET, ring) # base untouched; AC now aggregates the +1
```

### Re-skin for any setting — theme scaling

The same engine runs sci-fi, modern-warfare, steampunk or cosmic-horror. A `ThemeScalingLayer`
folds three kinds of override onto a ruleset via `apply_theme_scaling()` (pure, like `compose`):
`input_overrides` re-baseline a node's default value, `lookup_overrides` deep-merge into the
lookup tables (so `plate` armour can read AC 19 instead of 18), and `flavor_renames` relabel
terms for display **without ever changing a computed value**. The graph's shape never changes —
only its numbers and names.

<p align="center">
<img alt="dndwright theme scaling: one computation graph re-skinned per setting. A ThemeScalingLayer applies input_overrides (re-baseline node defaults), lookup_overrides (merge tables like armor AC and weapon ranges) and flavor_renames (display labels only). The same plate armor node emerges as 'plate AC 18' in traditional D&D, 'tactical body armor AC 18' in modern warfare, 'power armor AC 19' in sci-fi, and 'clockwork full-plate AC 19' in steampunk" width="760" src="https://raw.githubusercontent.com/sligara7/dndwright/main/assets/theme-scaling.svg">
</p>

```python
from dndwright import DND_5E_2024_RULESET, apply_theme_scaling, get_theme_scaling
rs = apply_theme_scaling(DND_5E_2024_RULESET, get_theme_scaling("sci_fi"))
rs.lookup_tables["armor_base_ac"]["plate"] # 19 (base is still 18, untouched)
```

## What's inside

| Component | What it does |
Expand All @@ -139,6 +176,7 @@ and serialisable — not buried in imperative code. `DND_5E_2024_RULESET` is a 1
| `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, player-chosen, or *conditional* effects). |
| `apply_theme_scaling` / `ThemeScalingLayer` / `get_theme_scaling` | Re-skin the ruleset for any setting (sci-fi, modern, steampunk, …): override node defaults & lookup tables and re-flavor names, same graph shape. `PREDEFINED_THEME_SCALING` ships ready-made themes. |
| `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. |
Expand Down
121 changes: 121 additions & 0 deletions assets/compose.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
107 changes: 107 additions & 0 deletions assets/theme-scaling.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "hatchling.build"

[project]
name = "dndwright"
version = "0.23.1"
version = "0.23.2"
description = "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)."
readme = "README.md"
requires-python = ">=3.10"
Expand Down
2 changes: 1 addition & 1 deletion src/dndwright/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@
validate_ruleset,
)

__version__ = "0.23.1"
__version__ = "0.23.2"

__all__ = [
# high-level (dict in -> computed sheet out)
Expand Down