ttrpg-engine-dnd 0.1.0-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (329) hide show
  1. package/CHANGELOG.md +69 -0
  2. package/CONTRIBUTING.md +98 -0
  3. package/DEVELOPMENT.md +70 -0
  4. package/LICENSE +21 -0
  5. package/README.md +247 -0
  6. package/VERSIONING.md +151 -0
  7. package/dist/content/index.d.ts +3 -0
  8. package/dist/content/index.d.ts.map +1 -0
  9. package/dist/content/pack.d.ts +1657 -0
  10. package/dist/content/pack.d.ts.map +1 -0
  11. package/dist/content/packs/starter.d.ts +4 -0
  12. package/dist/content/packs/starter.d.ts.map +1 -0
  13. package/dist/content/validate.d.ts +8 -0
  14. package/dist/content/validate.d.ts.map +1 -0
  15. package/dist/derive/ability-check.d.ts +26 -0
  16. package/dist/derive/ability-check.d.ts.map +1 -0
  17. package/dist/derive/ability.d.ts +9 -0
  18. package/dist/derive/ability.d.ts.map +1 -0
  19. package/dist/derive/ac.d.ts +19 -0
  20. package/dist/derive/ac.d.ts.map +1 -0
  21. package/dist/derive/action-economy.d.ts +17 -0
  22. package/dist/derive/action-economy.d.ts.map +1 -0
  23. package/dist/derive/attack.d.ts +20 -0
  24. package/dist/derive/attack.d.ts.map +1 -0
  25. package/dist/derive/character-view.d.ts +29 -0
  26. package/dist/derive/character-view.d.ts.map +1 -0
  27. package/dist/derive/damage-mitigation.d.ts +18 -0
  28. package/dist/derive/damage-mitigation.d.ts.map +1 -0
  29. package/dist/derive/effect-stack.d.ts +15 -0
  30. package/dist/derive/effect-stack.d.ts.map +1 -0
  31. package/dist/derive/encumbrance.d.ts +17 -0
  32. package/dist/derive/encumbrance.d.ts.map +1 -0
  33. package/dist/derive/index.d.ts +12 -0
  34. package/dist/derive/index.d.ts.map +1 -0
  35. package/dist/derive/save.d.ts +23 -0
  36. package/dist/derive/save.d.ts.map +1 -0
  37. package/dist/derive/spell-dc.d.ts +21 -0
  38. package/dist/derive/spell-dc.d.ts.map +1 -0
  39. package/dist/derive/spell-slots.d.ts +21 -0
  40. package/dist/derive/spell-slots.d.ts.map +1 -0
  41. package/dist/derive/terrain.d.ts +10 -0
  42. package/dist/derive/terrain.d.ts.map +1 -0
  43. package/dist/effects/builder.d.ts +66 -0
  44. package/dist/effects/builder.d.ts.map +1 -0
  45. package/dist/effects/formula.d.ts +12 -0
  46. package/dist/effects/formula.d.ts.map +1 -0
  47. package/dist/effects/index.d.ts +4 -0
  48. package/dist/effects/index.d.ts.map +1 -0
  49. package/dist/effects/predicate.d.ts +12 -0
  50. package/dist/effects/predicate.d.ts.map +1 -0
  51. package/dist/engine/apply.d.ts +5 -0
  52. package/dist/engine/apply.d.ts.map +1 -0
  53. package/dist/engine/commit.d.ts +12 -0
  54. package/dist/engine/commit.d.ts.map +1 -0
  55. package/dist/engine/conveniences.d.ts +7124 -0
  56. package/dist/engine/conveniences.d.ts.map +1 -0
  57. package/dist/engine/ids-utils.d.ts +2 -0
  58. package/dist/engine/ids-utils.d.ts.map +1 -0
  59. package/dist/engine/index.d.ts +107 -0
  60. package/dist/engine/index.d.ts.map +1 -0
  61. package/dist/engine/plan/action-surge.d.ts +10 -0
  62. package/dist/engine/plan/action-surge.d.ts.map +1 -0
  63. package/dist/engine/plan/attack.d.ts +30 -0
  64. package/dist/engine/plan/attack.d.ts.map +1 -0
  65. package/dist/engine/plan/cast-spell.d.ts +18 -0
  66. package/dist/engine/plan/cast-spell.d.ts.map +1 -0
  67. package/dist/engine/plan/checks.d.ts +26 -0
  68. package/dist/engine/plan/checks.d.ts.map +1 -0
  69. package/dist/engine/plan/concentration.d.ts +12 -0
  70. package/dist/engine/plan/concentration.d.ts.map +1 -0
  71. package/dist/engine/plan/contested.d.ts +28 -0
  72. package/dist/engine/plan/contested.d.ts.map +1 -0
  73. package/dist/engine/plan/encounter.d.ts +47 -0
  74. package/dist/engine/plan/encounter.d.ts.map +1 -0
  75. package/dist/engine/plan/falling.d.ts +11 -0
  76. package/dist/engine/plan/falling.d.ts.map +1 -0
  77. package/dist/engine/plan/index.d.ts +20 -0
  78. package/dist/engine/plan/index.d.ts.map +1 -0
  79. package/dist/engine/plan/level-up.d.ts +22 -0
  80. package/dist/engine/plan/level-up.d.ts.map +1 -0
  81. package/dist/engine/plan/movement.d.ts +25 -0
  82. package/dist/engine/plan/movement.d.ts.map +1 -0
  83. package/dist/engine/plan/multiattack.d.ts +12 -0
  84. package/dist/engine/plan/multiattack.d.ts.map +1 -0
  85. package/dist/engine/plan/npc.d.ts +21 -0
  86. package/dist/engine/plan/npc.d.ts.map +1 -0
  87. package/dist/engine/plan/offhand-attack.d.ts +13 -0
  88. package/dist/engine/plan/offhand-attack.d.ts.map +1 -0
  89. package/dist/engine/plan/opportunity-attack.d.ts +14 -0
  90. package/dist/engine/plan/opportunity-attack.d.ts.map +1 -0
  91. package/dist/engine/plan/reactive-spells.d.ts +34 -0
  92. package/dist/engine/plan/reactive-spells.d.ts.map +1 -0
  93. package/dist/engine/plan/rest.d.ts +16 -0
  94. package/dist/engine/plan/rest.d.ts.map +1 -0
  95. package/dist/engine/plan/travel.d.ts +21 -0
  96. package/dist/engine/plan/travel.d.ts.map +1 -0
  97. package/dist/engine/plan/weapon-mastery.d.ts +15 -0
  98. package/dist/engine/plan/weapon-mastery.d.ts.map +1 -0
  99. package/dist/engine/reducers/action-economy.d.ts +12 -0
  100. package/dist/engine/reducers/action-economy.d.ts.map +1 -0
  101. package/dist/engine/reducers/attack.d.ts +6 -0
  102. package/dist/engine/reducers/attack.d.ts.map +1 -0
  103. package/dist/engine/reducers/bastion.d.ts +10 -0
  104. package/dist/engine/reducers/bastion.d.ts.map +1 -0
  105. package/dist/engine/reducers/charges.d.ts +7 -0
  106. package/dist/engine/reducers/charges.d.ts.map +1 -0
  107. package/dist/engine/reducers/checks.d.ts +6 -0
  108. package/dist/engine/reducers/checks.d.ts.map +1 -0
  109. package/dist/engine/reducers/combat.d.ts +12 -0
  110. package/dist/engine/reducers/combat.d.ts.map +1 -0
  111. package/dist/engine/reducers/concentration.d.ts +6 -0
  112. package/dist/engine/reducers/concentration.d.ts.map +1 -0
  113. package/dist/engine/reducers/downtime.d.ts +5 -0
  114. package/dist/engine/reducers/downtime.d.ts.map +1 -0
  115. package/dist/engine/reducers/encounter.d.ts +11 -0
  116. package/dist/engine/reducers/encounter.d.ts.map +1 -0
  117. package/dist/engine/reducers/inventory.d.ts +9 -0
  118. package/dist/engine/reducers/inventory.d.ts.map +1 -0
  119. package/dist/engine/reducers/level-up.d.ts +7 -0
  120. package/dist/engine/reducers/level-up.d.ts.map +1 -0
  121. package/dist/engine/reducers/locations.d.ts +8 -0
  122. package/dist/engine/reducers/locations.d.ts.map +1 -0
  123. package/dist/engine/reducers/mounts-vehicles.d.ts +11 -0
  124. package/dist/engine/reducers/mounts-vehicles.d.ts.map +1 -0
  125. package/dist/engine/reducers/movement.d.ts +7 -0
  126. package/dist/engine/reducers/movement.d.ts.map +1 -0
  127. package/dist/engine/reducers/npc.d.ts +7 -0
  128. package/dist/engine/reducers/npc.d.ts.map +1 -0
  129. package/dist/engine/reducers/party.d.ts +10 -0
  130. package/dist/engine/reducers/party.d.ts.map +1 -0
  131. package/dist/engine/reducers/progression.d.ts +5 -0
  132. package/dist/engine/reducers/progression.d.ts.map +1 -0
  133. package/dist/engine/reducers/quests.d.ts +14 -0
  134. package/dist/engine/reducers/quests.d.ts.map +1 -0
  135. package/dist/engine/reducers/reactive-spells.d.ts +7 -0
  136. package/dist/engine/reducers/reactive-spells.d.ts.map +1 -0
  137. package/dist/engine/reducers/resources.d.ts +7 -0
  138. package/dist/engine/reducers/resources.d.ts.map +1 -0
  139. package/dist/engine/reducers/rest.d.ts +8 -0
  140. package/dist/engine/reducers/rest.d.ts.map +1 -0
  141. package/dist/engine/reducers/resurrection.d.ts +5 -0
  142. package/dist/engine/reducers/resurrection.d.ts.map +1 -0
  143. package/dist/engine/reducers/session.d.ts +8 -0
  144. package/dist/engine/reducers/session.d.ts.map +1 -0
  145. package/dist/engine/reducers/settings.d.ts +5 -0
  146. package/dist/engine/reducers/settings.d.ts.map +1 -0
  147. package/dist/engine/reducers/spellcasting.d.ts +7 -0
  148. package/dist/engine/reducers/spellcasting.d.ts.map +1 -0
  149. package/dist/engine/reducers/transformations.d.ts +8 -0
  150. package/dist/engine/reducers/transformations.d.ts.map +1 -0
  151. package/dist/engine/reducers/travel.d.ts +7 -0
  152. package/dist/engine/reducers/travel.d.ts.map +1 -0
  153. package/dist/engine/reducers/triggers.d.ts +9 -0
  154. package/dist/engine/reducers/triggers.d.ts.map +1 -0
  155. package/dist/engine/reducers/weapon-mastery.d.ts +5 -0
  156. package/dist/engine/reducers/weapon-mastery.d.ts.map +1 -0
  157. package/dist/engine/replay.d.ts +4 -0
  158. package/dist/engine/replay.d.ts.map +1 -0
  159. package/dist/engine/triggers/dispatch.d.ts +13 -0
  160. package/dist/engine/triggers/dispatch.d.ts.map +1 -0
  161. package/dist/engine/undo-redo.d.ts +4 -0
  162. package/dist/engine/undo-redo.d.ts.map +1 -0
  163. package/dist/handlers/context.d.ts +7 -0
  164. package/dist/handlers/context.d.ts.map +1 -0
  165. package/dist/handlers/index.d.ts +12 -0
  166. package/dist/handlers/index.d.ts.map +1 -0
  167. package/dist/ids.d.ts +64 -0
  168. package/dist/ids.d.ts.map +1 -0
  169. package/dist/index.d.ts +72 -0
  170. package/dist/index.d.ts.map +1 -0
  171. package/dist/internal/clock.d.ts +2 -0
  172. package/dist/internal/clock.d.ts.map +1 -0
  173. package/dist/internal/constants.d.ts +6 -0
  174. package/dist/internal/constants.d.ts.map +1 -0
  175. package/dist/internal/immer.d.ts +4 -0
  176. package/dist/internal/immer.d.ts.map +1 -0
  177. package/dist/internal/invariants.d.ts +5 -0
  178. package/dist/internal/invariants.d.ts.map +1 -0
  179. package/dist/migrations/index.d.ts +5 -0
  180. package/dist/migrations/index.d.ts.map +1 -0
  181. package/dist/rng/default.d.ts +6 -0
  182. package/dist/rng/default.d.ts.map +1 -0
  183. package/dist/rng/dice.d.ts +20 -0
  184. package/dist/rng/dice.d.ts.map +1 -0
  185. package/dist/rng/index.d.ts +10 -0
  186. package/dist/rng/index.d.ts.map +1 -0
  187. package/dist/rng/seeded.d.ts +9 -0
  188. package/dist/rng/seeded.d.ts.map +1 -0
  189. package/dist/rng/throw.d.ts +9 -0
  190. package/dist/rng/throw.d.ts.map +1 -0
  191. package/dist/schemas/content/background.d.ts +46 -0
  192. package/dist/schemas/content/background.d.ts.map +1 -0
  193. package/dist/schemas/content/class.d.ts +264 -0
  194. package/dist/schemas/content/class.d.ts.map +1 -0
  195. package/dist/schemas/content/condition.d.ts +90 -0
  196. package/dist/schemas/content/condition.d.ts.map +1 -0
  197. package/dist/schemas/content/feat.d.ts +25 -0
  198. package/dist/schemas/content/feat.d.ts.map +1 -0
  199. package/dist/schemas/content/index.d.ts +9 -0
  200. package/dist/schemas/content/index.d.ts.map +1 -0
  201. package/dist/schemas/content/item.d.ts +602 -0
  202. package/dist/schemas/content/item.d.ts.map +1 -0
  203. package/dist/schemas/content/monster.d.ts +203 -0
  204. package/dist/schemas/content/monster.d.ts.map +1 -0
  205. package/dist/schemas/content/species.d.ts +63 -0
  206. package/dist/schemas/content/species.d.ts.map +1 -0
  207. package/dist/schemas/content/spell.d.ts +253 -0
  208. package/dist/schemas/content/spell.d.ts.map +1 -0
  209. package/dist/schemas/effects.d.ts +175 -0
  210. package/dist/schemas/effects.d.ts.map +1 -0
  211. package/dist/schemas/events/action-economy.d.ts +38 -0
  212. package/dist/schemas/events/action-economy.d.ts.map +1 -0
  213. package/dist/schemas/events/attack.d.ts +139 -0
  214. package/dist/schemas/events/attack.d.ts.map +1 -0
  215. package/dist/schemas/events/bastion.d.ts +227 -0
  216. package/dist/schemas/events/bastion.d.ts.map +1 -0
  217. package/dist/schemas/events/charges.d.ts +110 -0
  218. package/dist/schemas/events/charges.d.ts.map +1 -0
  219. package/dist/schemas/events/checks.d.ts +103 -0
  220. package/dist/schemas/events/checks.d.ts.map +1 -0
  221. package/dist/schemas/events/combat.d.ts +308 -0
  222. package/dist/schemas/events/combat.d.ts.map +1 -0
  223. package/dist/schemas/events/concentration.d.ts +99 -0
  224. package/dist/schemas/events/concentration.d.ts.map +1 -0
  225. package/dist/schemas/events/downtime.d.ts +53 -0
  226. package/dist/schemas/events/downtime.d.ts.map +1 -0
  227. package/dist/schemas/events/encounter.d.ts +260 -0
  228. package/dist/schemas/events/encounter.d.ts.map +1 -0
  229. package/dist/schemas/events/envelope.d.ts +22 -0
  230. package/dist/schemas/events/envelope.d.ts.map +1 -0
  231. package/dist/schemas/events/index.d.ts +4594 -0
  232. package/dist/schemas/events/index.d.ts.map +1 -0
  233. package/dist/schemas/events/inventory.d.ts +253 -0
  234. package/dist/schemas/events/inventory.d.ts.map +1 -0
  235. package/dist/schemas/events/level-up.d.ts +141 -0
  236. package/dist/schemas/events/level-up.d.ts.map +1 -0
  237. package/dist/schemas/events/locations.d.ts +183 -0
  238. package/dist/schemas/events/locations.d.ts.map +1 -0
  239. package/dist/schemas/events/mounts-vehicles.d.ts +233 -0
  240. package/dist/schemas/events/mounts-vehicles.d.ts.map +1 -0
  241. package/dist/schemas/events/movement.d.ts +131 -0
  242. package/dist/schemas/events/movement.d.ts.map +1 -0
  243. package/dist/schemas/events/npc.d.ts +113 -0
  244. package/dist/schemas/events/npc.d.ts.map +1 -0
  245. package/dist/schemas/events/party.d.ts +260 -0
  246. package/dist/schemas/events/party.d.ts.map +1 -0
  247. package/dist/schemas/events/progression.d.ts +698 -0
  248. package/dist/schemas/events/progression.d.ts.map +1 -0
  249. package/dist/schemas/events/quests.d.ts +426 -0
  250. package/dist/schemas/events/quests.d.ts.map +1 -0
  251. package/dist/schemas/events/reactive-spells.d.ts +98 -0
  252. package/dist/schemas/events/reactive-spells.d.ts.map +1 -0
  253. package/dist/schemas/events/resources.d.ts +107 -0
  254. package/dist/schemas/events/resources.d.ts.map +1 -0
  255. package/dist/schemas/events/rest.d.ts +104 -0
  256. package/dist/schemas/events/rest.d.ts.map +1 -0
  257. package/dist/schemas/events/resurrection.d.ts +44 -0
  258. package/dist/schemas/events/resurrection.d.ts.map +1 -0
  259. package/dist/schemas/events/session.d.ts +144 -0
  260. package/dist/schemas/events/session.d.ts.map +1 -0
  261. package/dist/schemas/events/settings.d.ts +47 -0
  262. package/dist/schemas/events/settings.d.ts.map +1 -0
  263. package/dist/schemas/events/spellcasting.d.ts +103 -0
  264. package/dist/schemas/events/spellcasting.d.ts.map +1 -0
  265. package/dist/schemas/events/transformations.d.ts +279 -0
  266. package/dist/schemas/events/transformations.d.ts.map +1 -0
  267. package/dist/schemas/events/travel.d.ts +143 -0
  268. package/dist/schemas/events/travel.d.ts.map +1 -0
  269. package/dist/schemas/events/triggers.d.ts +60 -0
  270. package/dist/schemas/events/triggers.d.ts.map +1 -0
  271. package/dist/schemas/events/weapon-mastery.d.ts +38 -0
  272. package/dist/schemas/events/weapon-mastery.d.ts.map +1 -0
  273. package/dist/schemas/formula.d.ts +103 -0
  274. package/dist/schemas/formula.d.ts.map +1 -0
  275. package/dist/schemas/index.d.ts +8 -0
  276. package/dist/schemas/index.d.ts.map +1 -0
  277. package/dist/schemas/predicate.d.ts +72 -0
  278. package/dist/schemas/predicate.d.ts.map +1 -0
  279. package/dist/schemas/primitives.d.ts +156 -0
  280. package/dist/schemas/primitives.d.ts.map +1 -0
  281. package/dist/schemas/runtime/bastion.d.ts +130 -0
  282. package/dist/schemas/runtime/bastion.d.ts.map +1 -0
  283. package/dist/schemas/runtime/campaign.d.ts +2122 -0
  284. package/dist/schemas/runtime/campaign.d.ts.map +1 -0
  285. package/dist/schemas/runtime/character.d.ts +580 -0
  286. package/dist/schemas/runtime/character.d.ts.map +1 -0
  287. package/dist/schemas/runtime/currency.d.ts +9 -0
  288. package/dist/schemas/runtime/currency.d.ts.map +1 -0
  289. package/dist/schemas/runtime/downtime.d.ts +31 -0
  290. package/dist/schemas/runtime/downtime.d.ts.map +1 -0
  291. package/dist/schemas/runtime/effect-instance.d.ts +65 -0
  292. package/dist/schemas/runtime/effect-instance.d.ts.map +1 -0
  293. package/dist/schemas/runtime/encounter.d.ts +264 -0
  294. package/dist/schemas/runtime/encounter.d.ts.map +1 -0
  295. package/dist/schemas/runtime/in-game-time.d.ts +18 -0
  296. package/dist/schemas/runtime/in-game-time.d.ts.map +1 -0
  297. package/dist/schemas/runtime/index.d.ts +15 -0
  298. package/dist/schemas/runtime/index.d.ts.map +1 -0
  299. package/dist/schemas/runtime/item-instance.d.ts +66 -0
  300. package/dist/schemas/runtime/item-instance.d.ts.map +1 -0
  301. package/dist/schemas/runtime/location.d.ts +111 -0
  302. package/dist/schemas/runtime/location.d.ts.map +1 -0
  303. package/dist/schemas/runtime/party.d.ts +52 -0
  304. package/dist/schemas/runtime/party.d.ts.map +1 -0
  305. package/dist/schemas/runtime/pending-choice.d.ts +77 -0
  306. package/dist/schemas/runtime/pending-choice.d.ts.map +1 -0
  307. package/dist/schemas/runtime/quest.d.ts +207 -0
  308. package/dist/schemas/runtime/quest.d.ts.map +1 -0
  309. package/dist/schemas/runtime/session.d.ts +102 -0
  310. package/dist/schemas/runtime/session.d.ts.map +1 -0
  311. package/dist/schemas/runtime/settings.d.ts +26 -0
  312. package/dist/schemas/runtime/settings.d.ts.map +1 -0
  313. package/dist/schemas/runtime/travel.d.ts +34 -0
  314. package/dist/schemas/runtime/travel.d.ts.map +1 -0
  315. package/dist/schemas/runtime/vehicle.d.ts +49 -0
  316. package/dist/schemas/runtime/vehicle.d.ts.map +1 -0
  317. package/dist/ttrpg-engine-dnd.cjs +6 -0
  318. package/dist/ttrpg-engine-dnd.cjs.map +1 -0
  319. package/dist/ttrpg-engine-dnd.js +10464 -0
  320. package/dist/ttrpg-engine-dnd.js.map +1 -0
  321. package/dist/types/index.d.ts +8 -0
  322. package/dist/types/index.d.ts.map +1 -0
  323. package/dist/version.d.ts +3 -0
  324. package/dist/version.d.ts.map +1 -0
  325. package/docs/api-overview.md +111 -0
  326. package/docs/concepts.md +154 -0
  327. package/docs/getting-started.md +142 -0
  328. package/docs/recipes.md +302 -0
  329. package/package.json +83 -0
package/CHANGELOG.md ADDED
@@ -0,0 +1,69 @@
1
+ # Changelog
2
+
3
+ Notable changes to this project. The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). The bump policy and pre-release roadmap are documented in [VERSIONING.md](VERSIONING.md).
4
+
5
+ ## 0.1.0-alpha.0 (2026-05-12)
6
+
7
+ First alpha. All six phases of the roadmap are complete except the optional `ttrpg-engine-core` extraction. Forty-six slices shipped across engine mechanics, state schemas, combat fill-in, adoption surface, and 2024 content.
8
+
9
+ ### Engine architecture (Phase A, Slices 1-16)
10
+
11
+ - Event-sourced state machine. `apply(state, event) -> state` is pure; `replay(events)` reconstructs state byte-for-byte.
12
+ - Plan/commit split: all RNG is consumed inside `engine.plan.*` planners; resolution events carry baked rolls. `apply()` never touches RNG. Tested via `ThrowOnCallRNG` on every golden scenario.
13
+ - Branded IDs (`CharacterId`, `EncounterId`, etc.) backed by ULIDs. Normalized state. Immer internally, immutable externally.
14
+ - Twenty-five effect primitives plus a `CustomEffect` code-handler hook.
15
+ - `PendingChoice` protocol for deferred player decisions.
16
+ - Combat resolution chain (`AttackDeclared` -> `AttackRolled` -> `DamageRolled` -> `DamageApplied`), full encounter lifecycle, level-up flow with HP rolls, ability checks and saves, spellcasting with cantrip scaling and ritual casting and area shapes, concentration enforcement, OnEvent triggers (Sneak Attack), action economy (Action Surge, Extra Attack, Bonus Action), reactions (opportunity attacks, off-hand strikes, flat damage reduction), movement and positioning, damage mitigation order of operations, inventory equip/attune with 3-slot cap, creature as combatant with multiattack, environmental hazards (falling, cover), all 15 2024 conditions.
17
+
18
+ ### State schemas (Phase B, Slices 17-20)
19
+
20
+ - Parties + shared inventory + currency + treasure ledger.
21
+ - Sessions + journal entries (player / DM / character visibility) + in-game clock formatted as `Day NN HH:MM`.
22
+ - Locations + maps (normal / difficult / impassable / water cells) + doors (open / closed / locked) + Bresenham line-of-sight and line-of-effect.
23
+ - Quests + objectives + rewards + milestone XP.
24
+
25
+ ### Combat fill-in (Phase C, Slices 21-30)
26
+
27
+ - Grapple, shove, hide (2024 unarmed save-DC model).
28
+ - Counterspell, Dispel Magic, Identify.
29
+ - Weapon Mastery effects: Sap, Vex, Slow, Topple, Push, Graze.
30
+ - Mounts and vehicles (land / water / air, with HP, AC, capacity, occupants).
31
+ - Travel + forage + navigation + forced-march exhaustion.
32
+ - NPCs as first-class with attitude (hostile / unfriendly / indifferent / friendly / helpful) and morale.
33
+ - Downtime + crafting + training + tool proficiency tracking.
34
+ - Magic item charges + recharge cadence + sentient items.
35
+ - Resurrection variants: Revivify, Raise Dead, Reincarnate, Resurrection, True Resurrection.
36
+ - Wild Shape, Polymorph, Simulacrum, Wish.
37
+
38
+ ### Adoption surface (Phase D, Slices 31-37)
39
+
40
+ - Starter content pack bundled with the package. `loadStarterPack()` returns a fully-populated `ContentPack`.
41
+ - `examples/` directory with three runnable apps: character-sheet printer, encounter + replay demo, save/load round-trip. CI executes them to catch public-API regressions.
42
+ - Getting-started doc and API reference under `docs/`.
43
+ - Public API conveniences: `engine.do(campaign, intent)`, `serializeCampaign(c)` / `loadCampaign(json)`, `createPC({...})`.
44
+ - Per-engine derivation memoization keyed on `CampaignState.version`. Repeated `engine.derive.*` calls return the same object reference until the next commit.
45
+ - npm publish prep. Package ships ESM + CJS + `.d.ts`. `prepublishOnly` runs the full CI gate. `publishConfig: public`.
46
+ - Content pack validator with path-pointed Zod failures and Levenshtein "Did you mean X?" suggestions on cross-reference errors.
47
+
48
+ ### 2024 content (Phase E, Slices 38-46)
49
+
50
+ - All 12 PHB classes in the starter pack with 1-20 level tables and signature features at landmark levels.
51
+ - ~31 spells across cantrip + level 1-3 tiers covering the common archetypes.
52
+ - 7 species, 8 backgrounds, 22 feats (origin + general + all six fighting styles), 25+ items including armor variety, 5 tools, 7 adventuring-gear items.
53
+ - 9 magic items across all rarity tiers (common Bag of Holding through legendary Deck of Many Things) including a charged Wand of Magic Missiles.
54
+ - 6 monster statblocks (Goblin, Orc, Wolf, Skeleton, Ogre, Young Red Dragon).
55
+ - 2024 Bastion stronghold system: facilities, hirelings, turn orders, damage and level-up.
56
+ - 9 epic boons under the `epic-boon` feat category.
57
+ - Variant rule toggles on `CampaignState.settings`: gritty rest, hero points, sanity, mass combat, custom houserules.
58
+
59
+ ### Testing
60
+
61
+ - 475 tests across 87 files. Replay-equivalence and RNG-capture invariants asserted on every golden scenario. 80% line coverage gate on `src/engine/`, `src/derive/`, `src/effects/`.
62
+ - Showcase scenario "The Stoneheart Saga" exercises 46 distinct mechanical surfaces in one coherent campaign narrative.
63
+
64
+ ### Not yet done
65
+
66
+ - Phase F: optional `ttrpg-engine-core` extraction for multi-system support.
67
+ - Subclass mechanics for individual classes (the level tables ship; specific subclass features are consumer territory).
68
+ - The full ~370 spell catalog (representative sample ships; bulk content is for consumers to fill out from the 2024 SRD).
69
+ - Specific scenario content (specific NPCs, adventures, named magic items beyond the starter set).
@@ -0,0 +1,98 @@
1
+ # Contributing to ttrpg-engine-dnd
2
+
3
+ Thanks for your interest. This engine is built to be the foundation that other D&D 5.5e tools rely on, so contributions are held to a higher bar than typical app code.
4
+
5
+ ## Before you start
6
+
7
+ - Read [README.md](README.md) for the goal and roadmap.
8
+ - Read [CLAUDE.md](CLAUDE.md) for architecture (locked) and conventions.
9
+ - Read [DEVELOPMENT.md](DEVELOPMENT.md) for the dev workflow and house rules.
10
+
11
+ ## Scope: what this engine does and does not do
12
+
13
+ **Does**: model every printed mechanic in the 2024 PHB, DMG, and Monster Manual. Provide a schema-only library that consumers extend with their own content packs. Run an event-sourced state machine with deterministic replay.
14
+
15
+ **Does not**: ship any D&D content. Adjudicate situations the rules text delegates to the DM (improvised actions, table houserules, ambiguous spell interactions). Replace a human DM.
16
+
17
+ If a contribution would put copyrighted Wizards of the Coast text or stat blocks into this repo, it does not belong here. Content goes in separate, consumer-owned content packs.
18
+
19
+ ## Architecture is locked
20
+
21
+ The decisions in [CLAUDE.md](CLAUDE.md) under "Architecture (locked)" are not up for debate as part of a contribution:
22
+
23
+ - Event-sourced with `apply(state, event) -> state` pure.
24
+ - Plan/commit split: RNG only inside planners, resolution events carry baked rolls, replay is deterministic.
25
+ - Effect-primitive vocabulary plus code-handler escape hatch.
26
+ - Branded IDs with ULIDs. Normalized state. Immer internally, immutable externally. Zod schemas.
27
+ - `PendingChoice` protocol for deferred decisions.
28
+
29
+ If you think one of these needs to change, open an issue first and discuss before coding.
30
+
31
+ ## How to add a slice or feature
32
+
33
+ Most contributions touch the same set of layers:
34
+
35
+ 1. **Event schemas** in [src/schemas/events/](src/schemas/events/). Intent / resolution / notification events as appropriate. Resolution events carry baked RNG.
36
+ 2. **Reducers** in [src/engine/reducers/](src/engine/reducers/), one file per event category. Wire into [src/engine/apply.ts](src/engine/apply.ts) (both the import and the switch case).
37
+ 3. **Planners** in [src/engine/plan/](src/engine/plan/). RNG-consuming logic lives here, never in reducers.
38
+ 4. **Public API** in [src/engine/index.ts](src/engine/index.ts) and re-exports in [src/index.ts](src/index.ts).
39
+ 5. **Tests**: see Testing standard below.
40
+
41
+ ## Testing standard
42
+
43
+ Held to the standard documented in [CLAUDE.md](CLAUDE.md). Summary:
44
+
45
+ **Required**:
46
+
47
+ - Reducer unit tests in [tests/unit/reducers/](tests/unit/reducers/) for every new event type. Happy path, every rulebook edge case, invalid-input rejection.
48
+ - At least one golden scenario in [tests/golden/](tests/golden/) per new gameplay flow. Asserts replay equivalence.
49
+ - Each golden scenario must emit a human-readable transcript via `formatTranscript()` from [tests/transcript.ts](tests/transcript.ts) and assert it against a snapshot in [tests/golden/transcripts/](tests/golden/transcripts/). PRs show the transcript diff. Update intentionally with `npx vitest run -u`.
50
+ - **Naming convention.** Slice-aligned golden tests are named `s<N>-<short-name>.test.ts` (for example `s4-checks.test.ts`, `s8-action-economy.test.ts`) and their transcript snapshots share the same prefix (`s4-checks.transcript.md`). Architectural-invariant tests (`replay-equivalence.test.ts`, `rng-capture.test.ts`) and the multi-slice narrative (`showcase.test.ts`) stay un-prefixed. A directory listing then reveals slice progression at a glance.
51
+ - For RNG-consuming planners: a `ThrowOnCallRNG` test confirming `apply()` never calls the RNG.
52
+ - Derivation tests: table-driven against rulebook tables (proficiency bonus, ability modifier, etc.), branch-tested for derived values that compose.
53
+ - New event type: extend [tests/transcript.ts](tests/transcript.ts) `formatEvent` with a case so the rendering stays readable.
54
+
55
+ **Not required** (and discouraged unless they catch a real bug):
56
+
57
+ - Coverage chasing past 80% on core directories.
58
+ - Public API contract snapshot tests.
59
+ - Schema round-trip tests (Zod already guarantees this).
60
+
61
+ If you cannot name a bug a test would prevent, do not write it.
62
+
63
+ CI gates: typecheck, 80% line coverage on `src/engine/`, `src/derive/`, `src/effects/`, replay equivalence on every golden scenario, RNG capture proof.
64
+
65
+ ## Code style
66
+
67
+ - TypeScript strict mode. Enforced in [tsconfig.json](tsconfig.json).
68
+ - No magic numbers or strings. Extract to named module-scope constants.
69
+ - No defensive error handling for impossible cases. Use `invariant()` for genuine boundary checks.
70
+ - Small functions. Reducers should read as a sequence of named operations.
71
+ - No em dashes or en dashes in code, comments, docs, or error messages. Use commas, parentheses, colons, or separate sentences.
72
+ - File references in markdown as `[label](path)`, not backticks.
73
+
74
+ ## Commit and PR style
75
+
76
+ - One coherent change per PR. Big features land as a series of merged-quickly slices.
77
+ - Commit messages: imperative mood, summary line under 72 chars, optional paragraph for the why.
78
+ - PRs should include: what changed, why, what tests cover it, anything reviewers should look at closely.
79
+
80
+ ## Reporting bugs
81
+
82
+ Open an issue with:
83
+
84
+ - A minimal reproduction: ideally a failing test or a short script that triggers the bug.
85
+ - Expected vs actual behavior.
86
+ - The rulebook citation if the bug is about rules correctness (PHB page, errata).
87
+
88
+ ## Reporting rules-correctness bugs
89
+
90
+ These are different from code bugs. The engine aims for 95%+ correctness against the printed 2024 rules. If you believe a mechanic is implemented incorrectly:
91
+
92
+ 1. Cite the rulebook (PHB chapter / page) or official errata / Sage Advice.
93
+ 2. Show a test case that demonstrates the discrepancy.
94
+ 3. If the rules are genuinely ambiguous, that is a candidate for the `CustomEffect` escape hatch, not for the core engine.
95
+
96
+ ## License
97
+
98
+ By contributing, you agree your contributions are licensed under the MIT License (see [LICENSE](LICENSE)).
package/DEVELOPMENT.md ADDED
@@ -0,0 +1,70 @@
1
+ # Development
2
+
3
+ ## Branches
4
+
5
+ - `main`: releasable, tagged.
6
+ - `dev`: daily work, merges to `main` on release.
7
+
8
+ ## Commands
9
+
10
+ ```
11
+ npm install # install deps
12
+ npm run typecheck # tsc --noEmit
13
+ npm test # vitest run
14
+ npm run test:watch # vitest in watch mode
15
+ npm run test:coverage # vitest with coverage gates
16
+ npm run build # vite build + .d.ts emit
17
+ npm run ci # typecheck + coverage + build (full gate)
18
+ ```
19
+
20
+ ## Adding a new effect primitive
21
+
22
+ 1. Add the discriminated-union variant in [src/schemas/effects.ts](src/schemas/effects.ts) (both the `Effect` type and the `EffectSchema` Zod definition).
23
+ 2. Add the `EFFECT_KINDS` entry.
24
+ 3. Add interpretation in [src/effects/builder.ts](src/effects/builder.ts) `applyEffectToBuilder` if the primitive contributes statically to the effect stack. Triggered primitives (`OnEvent`) are handled by the trigger system instead.
25
+ 4. Add reducer tests for any features that exercise the new primitive end to end (via a golden scenario).
26
+
27
+ ## Adding a new event type
28
+
29
+ 1. Add schema in [src/schemas/events/](src/schemas/events/) and re-export from [src/schemas/events/index.ts](src/schemas/events/index.ts) (including `EVENT_TYPES` and the discriminated union).
30
+ 2. Add reducer in [src/engine/reducers/](src/engine/reducers/).
31
+ 3. Wire into [src/engine/apply.ts](src/engine/apply.ts): both the import at the top and the switch case in `apply()`. Both edits are easy to forget, the resulting bug surfaces only at runtime as `Unhandled event`.
32
+ 4. Reducer unit test in [tests/unit/reducers/](tests/unit/reducers/).
33
+ 5. At least one golden scenario in [tests/golden/](tests/golden/) emits the event so the replay-equivalence gate covers it.
34
+ 6. If the event involves RNG, the resolution event must carry the baked roll. Verify by including the new flow in a `ThrowOnCallRNG` test (apply with a no-RNG must not throw).
35
+
36
+ ## Adding a new planner
37
+
38
+ 1. Add `src/engine/plan/<thing>.ts` with the intent type and the `plan*` function.
39
+ 2. Re-export from [src/engine/plan/index.ts](src/engine/plan/index.ts).
40
+ 3. Add to the `Engine['plan']` type in [src/engine/index.ts](src/engine/index.ts) and to `planNs` in `createEngine`.
41
+ 4. Re-export from the public barrel [src/index.ts](src/index.ts).
42
+ 5. Planner test: deterministic for fixed seed, different seeds produce different rolls, applied events do not call RNG.
43
+
44
+ ## Versioning
45
+
46
+ Bump policy, pre-release tag meanings (alpha / beta / rc), promotion criteria, and the roadmap to 1.0.0 are in [VERSIONING.md](VERSIONING.md). Short version: don't bump on every PR, treat ambiguous changes as breaking, update CHANGELOG with every release.
47
+
48
+ ## Schema migrations
49
+
50
+ Bump `SCHEMA_VERSION` in [src/version.ts](src/version.ts) and add a migration function in [src/migrations/](src/migrations/) in the same PR as the breaking schema change. Migration test accompanies it. `SCHEMA_VERSION` is independent of the package version, see [VERSIONING.md](VERSIONING.md) for the contract.
51
+
52
+ ## Consumer integration
53
+
54
+ For local development from a consumer app (e.g. `dndbnb`):
55
+
56
+ ```jsonc
57
+ // in the consumer's package.json
58
+ "dependencies": {
59
+ "ttrpg-engine-dnd": "file:../dnd-engine"
60
+ }
61
+ ```
62
+
63
+ Or use `npm link` for tighter iteration loops.
64
+
65
+ ## House rules
66
+
67
+ - No em dashes or en dashes anywhere (code, comments, docs, error messages). Use commas, parentheses, colons, or separate sentences.
68
+ - No magic numbers or strings. Extract to named module-scope constants. The 5.5e rules contain many of these (hit die averages, death save thresholds, exhaustion cap, ability score range). Each gets a name.
69
+ - Reducers stay small. If `applyFoo` grows past about 30 lines, extract named helpers. The reducer reads as a sequence of named operations.
70
+ - No defensive error handling for impossible cases. Trust types. Use `invariant()` only for genuine preconditions on incoming events.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Greg Carr
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,247 @@
1
+ # ttrpg-engine-dnd
2
+
3
+ [![CI](https://github.com/greghcarr/ttrpg-engine-dnd/actions/workflows/ci.yml/badge.svg)](https://github.com/greghcarr/ttrpg-engine-dnd/actions/workflows/ci.yml)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
5
+ [![TypeScript](https://img.shields.io/badge/TypeScript-strict-blue)](tsconfig.json)
6
+ [![Status](https://img.shields.io/badge/status-alpha-yellow)](README.md#status)
7
+
8
+ A standalone, event-sourced TypeScript domain engine for Dungeons & Dragons 5.5e (the 2024 rules update). Schema-only. Bring your own content pack (a starter SRD-shaped pack ships in the box).
9
+
10
+ The package is named `ttrpg-engine-dnd` because the long-term plan extracts the system-agnostic core (event sourcing, plan/commit, branded IDs, content packs, sessions, party, predicate / formula DSL) into a future `ttrpg-engine-core` package, with `ttrpg-engine-dnd` becoming the 5.5e adapter. See [VERSIONING.md](VERSIONING.md) and the Phase F slice in the roadmap below.
11
+
12
+ If you are building a D&D character sheet, encounter tracker, virtual tabletop, automation tool, or AI dungeon master and you do not want to reimplement the rules engine from scratch, this is for you.
13
+
14
+ ## Quick start
15
+
16
+ ```sh
17
+ npm install ttrpg-engine-dnd@alpha
18
+ ```
19
+
20
+ ```ts
21
+ import {
22
+ createEngine, loadStarterPack, createPC, commit,
23
+ seededRNG, newEventId,
24
+ } from 'ttrpg-engine-dnd';
25
+
26
+ const engine = createEngine({ contentPacks: [loadStarterPack()], rng: seededRNG(42) });
27
+ const alyx = createPC({
28
+ name: 'Alyx', speciesId: 'human', backgroundId: 'soldier',
29
+ classId: 'fighter', level: 3, hpMax: 26,
30
+ abilityScores: { STR: 16, DEX: 14, CON: 14, INT: 10, WIS: 12, CHA: 8 },
31
+ });
32
+
33
+ let campaign = engine.createCampaign({ name: 'demo' });
34
+ campaign = commit(campaign, [
35
+ { id: newEventId(), at: new Date().toISOString(), type: 'CharacterCreated', snapshot: alyx },
36
+ ]);
37
+
38
+ const sheet = engine.derive.character(campaign.state, alyx.id);
39
+ console.log(`${alyx.name}: AC ${sheet.ac.total}, HP ${sheet.hp.current}/${sheet.hp.max}`);
40
+ ```
41
+
42
+ ## Documentation
43
+
44
+ Pick the doc that matches what you want:
45
+
46
+ | You want to... | Read this |
47
+ |---|---|
48
+ | Try the smallest possible working example | [examples/00-quickstart](examples/00-quickstart/) |
49
+ | Walk through your first character, attack, and save/load | [docs/getting-started.md](docs/getting-started.md) |
50
+ | Understand the mental model (events, plan/commit, content packs) | [docs/concepts.md](docs/concepts.md) |
51
+ | Look up a specific public symbol | [docs/api-overview.md](docs/api-overview.md) |
52
+ | See common patterns (save, undo, houserules, multiplayer sync) | [docs/recipes.md](docs/recipes.md) |
53
+ | Watch a full multi-act campaign run | [the showcase transcript](tests/golden/transcripts/showcase.transcript.md) |
54
+ | Know what each version means (alpha / beta / rc / 1.0) | [VERSIONING.md](VERSIONING.md) |
55
+ | Read the change history | [CHANGELOG.md](CHANGELOG.md) |
56
+
57
+ ## Why this engine
58
+
59
+ - **Built for accuracy first.** Full mechanical coverage of the 2024 Player's Handbook, Dungeon Master's Guide, and Monster Manual is the explicit goal. Every printed class, subclass, species, background, feat, spell, weapon, armor, magic item, condition, and monster statblock can be expressed.
60
+ - **No content, no IP problems.** The library ships schemas and an engine. It does not ship any rulebook text or statblocks. You bring your own content packs (built from the SRD 5.2 or your own homebrew), and the engine validates and runs them.
61
+ - **Event-sourced, fully deterministic replay.** Every state change is an event. A captured event log replays to byte-identical state across machines. Undo and redo are free.
62
+ - **Plan/commit split.** All randomness is consumed inside `engine.plan(intent)` and baked into resolution events. `apply()` is pure and replay never re-rolls dice. This is the architectural foundation that makes multiplayer sync, save files, and audit logs work correctly.
63
+ - **Effect-primitive vocabulary plus escape hatch.** About 25 declarative primitives express the bulk of 5.5e features as pure data; a `CustomEffect` code-handler hook covers genuinely-procedural exotica (Wild Shape, Wish, Simulacrum) and table-specific houserules.
64
+ - **Solid foundations.** TypeScript strict mode. Zod validation at boundaries. Immer-backed reducers, immutable externally. ESM and CJS builds. Zero peer-dependency conflicts.
65
+ - **Living transcripts.** Every golden test emits a human-readable markdown transcript of its event log, checked into [tests/golden/transcripts/](tests/golden/transcripts/). Every PR that changes engine behavior shows the transcript diff alongside the code. See [the showcase transcript](tests/golden/transcripts/showcase.transcript.md) for "The Stoneheart Saga": a multi-act campaign that exercises sessions and journals, party currency and bastion management, locations + doors + terrain, NPC reaction rolls, mounts and a supply wagon, travel and forage, two combat encounters (goblin scouts then a young red dragon) covering attack chains with advantage and counterspell and weapon mastery and concentration breakage, action surge, off-hand strikes, sneak attack, opportunity attacks, falling, polymorph (Alyx into a giant ape), multiattack creatures, fire-mitigation, death save plus revivify, quest objectives plus milestone plus XP plus reward claim, magic-item charges plus dawn recharge, downtime training plus crafting, and replay-equivalence plus RNG-capture invariants over the whole 339-line transcript.
66
+
67
+ ## Architecture
68
+
69
+ - **Event-sourced.** State changes are events. `apply(state, event) -> state` is pure. Replay any campaign from its event log.
70
+ - **Plan/commit split.** RNG is consumed only inside `engine.plan(intent)`. Resolution events carry baked rolls, so `apply()` is deterministic. Replay never re-rolls.
71
+ - **Effect-primitive vocabulary.** Features (class features, feats, magic item powers, conditions) are described via a fixed vocabulary of about 25 effect primitives. Wild Shape, Polymorph, Wish, Simulacrum and a handful of others drop to code handlers.
72
+ - **Schema-only.** The library ships shapes (`Character`, `Spell`, `MagicItem`, `MonsterStatblock`, etc.) and the engine that operates on them. Consumers load rules content from their own JSON content packs. This keeps the IP story clean.
73
+ - **Branded IDs + ULIDs.** `CharacterId`, `SpellId`, `ItemDefinitionId` versus `ItemInstanceId`, etc. Backed by ULIDs (lexicographically sortable by time).
74
+ - **PendingChoice protocol.** Deferred player decisions (ASI vs feat, fighting style selection, spell target selection) are first-class events in the log.
75
+ - **Zod for validation, Immer for clean reducers, Vitest for tests.**
76
+
77
+ ## Status
78
+
79
+ **Alpha-ready.** Forty-six slices complete (Phases A through E done), 475 tests across 87 files. The engine compiles, builds (ESM + CJS + `.d.ts`), the architectural invariants (event-sourcing, plan/commit, RNG capture, replay equivalence, branded IDs, effect primitives) are locked and proven by the test suite, the adoption surface is in place (starter pack, examples, getting-started doc, `engine.do()` convenience, derivation memoization, npm-publish-ready package, content validator with suggestions), and the content layer covers every 2024 PHB shape: all 12 classes with 1-20 level tables, ~31 representative spells across the common archetypes, 7 species, 8 backgrounds, ~31 feats including all fighting styles and 9 epic boons, 25+ items with armor variety and 9 magic items, 6 monster statblocks from CR 1/4 to CR 10, the full 2024 Bastion stronghold system, and toggles for the common DMG variant rules.
80
+
81
+ What remains is the optional Phase F (`ttrpg-engine-core` extraction) if multi-system support becomes a real goal. Otherwise the library is ready for alpha consumers.
82
+
83
+ ## Roadmap
84
+
85
+ Six phases. The slice catalog below is the canonical list; ✓ marks done, blank marks pending. Phases A through E are complete (46 slices). Only the optional Phase F (core extraction) remains.
86
+
87
+ ### Phase A: Engine mechanics (16 slices, all done)
88
+
89
+ Each slice landed a load-bearing combat or rules mechanic. Order was dependency-driven.
90
+
91
+ - ✓ **Slice 1.** Character creation, HP, damage, healing, temp HP, hit dice, short / long rest, exhaustion, conditions, death saves, stabilize.
92
+ - ✓ **Slice 2.** Combat resolution chain (`AttackDeclared` -> `AttackRolled` -> `DamageRolled` -> `DamageApplied`) with RNG-captured d20 + damage dice, advantage / disadvantage, critical hits, full encounter lifecycle (create, roll initiative, start, turn / round, end), item acquisition.
93
+ - ✓ **Slice 3.** Level-up flow with RNG-captured HP rolls (roll or average strategy), `PendingChoice` resolution protocol for deferred decisions (ASI vs feat, fighting style, subclass selection, spell selection). Resolved-choice effects feed into derivations.
94
+ - ✓ **Slice 4.** `plan.save`, `plan.abilityCheck` (with optional skill), record-only `SaveRolled` / `AbilityCheckRolled` resolution events. Honors caller-supplied advantage or derives it from the effect stack. Skill checks apply half / proficient / expertise multipliers. `computeAbilityCheck` + `computePassiveScore` derivations.
95
+ - ✓ **Slice 5.** Spellcasting. `plan.castSpell` handles cantrips and leveled spells; dispatches per-target attack / save / heal mechanics through the existing resolution chains; consumes standard or pact slots (auto-picks pact when both apply); upcasting via `extraDicePerSlotLevel`. `SpellCastDeclared`, `SpellSlotConsumed`, `PactSlotConsumed` events. Long rest restores all slots, short rest restores pact slots only. `computeAvailableSpellSlots` derivation.
96
+ - ✓ **Slice 6.** Concentration enforcement. `EffectInstance` table tracks active spell effects with their applied conditions; concentration spells emit `ConcentrationStarted` and set `Character.concentrationEffectId`. `plan.checkConcentration(characterId, damage)` rolls a CON save with DC `max(10, floor(damage/2))`, emits `ConcentrationBroken` on failure which auto-removes every condition the effect installed. Casting a new concentration spell while already concentrating evicts the prior effect.
97
+ - ✓ **Slice 7.** OnEvent trigger system. The dispatcher walks every character's effect stack after each triggering event, evaluates the `Predicate` filter against event facts, checks cadence (`oncePer: 'turn' | 'round' | 'shortRest' | 'longRest'`), and fires `AddDamage` actions producing rider events. `TriggerFired` event marks usage; `Character.triggerCounters` tracks per-cadence state. Test pack has a Rogue with Sneak Attack as the canonical OnEvent feature.
98
+ - ✓ **Slice 8.** Action economy. `Combatant.turnUsage` tracks per-turn usage; `ActionEconomyConsumed` events enforce "can't double-use the Action" / "Bonus Action" / "Reaction this round". `computeActionEconomyBudget` reads `ModifyActionEconomy` effects (Extra Attack, Action Surge, Bonus Action grants). `planAttack` enforces the attack budget when the attacker is the active combatant in an active encounter.
99
+ - ✓ **Slice 9.** Reactions protocol, scoped to opportunity attacks. `resolveAttack` extracted as a shared helper so `planOpportunityAttack` reuses the d20 / damage / OnEvent-trigger pipeline. Emits `ActionEconomyConsumed { kind: 'reaction' }` and bypasses the action / attack-budget checks. Throws on a second reaction same round; refreshes at `RoundEnded`.
100
+ - ✓ **Slice 9b.** Reaction-window expansion. Action Surge resets the action consumption flag; off-hand attacks consume the bonus action and suppress ability mod on damage; `FlatDamageReduction` effect primitive (Heavy Armor Master, similar) reduces incoming damage before resistance.
101
+ - ✓ **Slice 10.** Movement and positioning. Combatants gain optional `position: { x, y }` in feet and per-turn movement state (`feetMovedThisTurn`, `dashed`, `disengaged`). `planMove` enforces the budget against `Character.speedFeet` (doubled if Dashed) using Chebyshev distance.
102
+ - ✓ **Slice 11.** Damage mitigation order of operations. `mitigateDamage` walks the target's effect stack and applies flat reduction, then immunity (zero), then vulnerability (×2), then resistance (½ rounded down) to each damage component.
103
+ - ✓ **Slice 12.** Inventory mechanics. `ItemEquipped`, `ItemUnequipped`, `ItemAttuned`, `ItemUnattuned` events with reducers enforcing the 3-slot attunement cap. `computeCarryingCapacity` (STR × 15) and `computeEncumbrance` derivations.
104
+ - ✓ **Slice 13.** Creature as a first-class combatant. `Character.kind: 'pc' | 'npc' | 'creature'` discriminator with optional `statblockId` and `multiattack` pattern. `planMultiattack` consumes a single Action and runs `resolveAttack` once per weapon swing in the pattern.
105
+ - ✓ **Slice 14.** Environmental hazards. `planFalling` rolls 1d6 per 10ft (capped at 20d6) routed through `mitigateDamage` as bludgeoning. Cover (`half`, `three-quarters`, `total`) adds +2 / +5 AC respectively; total cover refuses the attack.
106
+ - ✓ **Slice 15.** Full 2024 conditions library. All 15 conditions (blinded, charmed, deafened, exhaustion, frightened, grappled, incapacitated, invisible, paralyzed, petrified, poisoned, prone, restrained, stunned, unconscious) load from content packs and apply their effects to derivations.
107
+ - ✓ **Slice 16.** Spellcasting polish. Cantrip damage scaling at character levels 5 / 11 / 17 via `cantripScalingDice`. Ritual casting via the `asRitual` flag (skips slot consumption; rejects non-ritual spells). Spell area targeting metadata (cone / cube / line / sphere / cylinder).
108
+
109
+ ### Phase B: Full state schemas (4 slices, all done)
110
+
111
+ The campaign-state surface area beyond combatants.
112
+
113
+ - ✓ **Slice 17.** Parties, shared inventory, currency, treasure ledger. `PartyCreated`, `PartyMembersChanged`, `CurrencyAcquired`, `CurrencySpent`, `ItemDepositedToParty`, `ItemWithdrawnFromParty` events. Currency helpers (`totalInCopper`, `addCurrency`, `subtractCurrency`) refuse to let the purse go negative.
114
+ - ✓ **Slice 18.** Sessions, journal entries (player / DM, with party / dm-only / character visibility), in-game clock (minutes from epoch, formatted as `Day NN HH:MM`). Only one session can be active at a time; starting a session syncs the campaign clock and refuses to rewind it.
115
+ - ✓ **Slice 19.** Locations and environmental terrain. `Location` (with optional parent and `LocationMap` of cells: normal / difficult / impassable / water), `Door` (open / closed / locked, blocks LOS and movement when shut). New events `LocationCreated`, `DoorAdded`, `DoorStateChanged`, `CharacterLocationChanged`. Derivations `terrainAt`, `movementCostAt`, `chebyshevDistanceFeet`, `isInRangeFeet`, `hasLineOfSight`, `hasLineOfEffect` (Bresenham ray, blocked by impassable cells and closed/locked doors).
116
+ - ✓ **Slice 20.** Quests, objectives, rewards, milestone XP. `Quest` (active / completed / failed / abandoned) with required and optional `QuestObjective`s tracking progress against thresholds. New events `QuestStarted`, `ObjectiveProgressed` / `Completed` / `Failed`, `QuestCompleted` / `Failed` / `Abandoned`, `QuestRewardClaimed` (distributes XP per beneficiary and currency to the linked party), `XPAwarded` (direct grant), `MilestoneAwarded` (minor / major / campaign tags appended to the campaign state).
117
+
118
+ ### Phase C: Combat fill-in (10 slices, all done)
119
+
120
+ High-impact mechanics consumers will immediately want. Each slice closes a gap that's currently missing or partial.
121
+
122
+ - ✓ **Slice 21.** Grapple, shove, hide actions. `planGrapple` rolls the attacker's unarmed save DC (8 + STR mod + prof) and emits a `SaveRolled` for the target (STR or DEX); failure applies the `grappled` condition. `planShove` does the same with a STR save and applies `prone` or emits a forced `CombatantMoved` (5 ft push). `planHide` rolls a DEX (stealth) check against DC 15 (or caller-provided DC) and applies the `invisible` condition on success. All three consume an Action when the actor is an active combatant; out-of-combat usage is unmetered.
123
+ - ✓ **Slice 22.** Counterspell, Dispel Magic, Identify. `planCounterspell` follows the 2024 model: reaction consumed, 3rd-level slot spent, target makes a CON save against the counter-caster's spell save DC; on failed save a `SpellCountered` event records the outcome (callers omit the original spell's resolution events). `planDispelMagic` auto-succeeds on effects whose level is at or below the dispel slot level, otherwise an `AbilityCheckRolled` against DC 10 + spell level; on success `SpellDispelled` removes the effect plus its conditions and clears any concentration link. `planIdentify` emits `ItemIdentified`, appending the character to `ItemInstance.identifiedByCharacterIds`.
124
+ - ✓ **Slice 23.** Weapon Mastery effects via `planWeaponMastery({mastery, attackerId, targetId, weaponInstanceId})`. Sap, Vex, and Slow apply marker conditions (`sapped`, `vexed-by`, `slowed-10ft`). Topple emits a CON save against the attacker's unarmed save DC; failure applies `prone`. Push emits a forced 10 ft `CombatantMoved` when positions exist. Graze emits a `DamageApplied` for the attacker's STR modifier in the weapon's damage type. Every activation also emits a `WeaponMasteryActivated` record event for replay narrative. Cleave / Nick / Flex are sequencing concerns that belong to the attack planner and are deferred.
125
+ - ✓ **Slice 24.** Mounts, vehicles, mounted combat. Mounts are creatures (kind `creature`) with a rider linked via `Character.mountedOnId`; `Mounted` and `Dismounted` events maintain that back-link. Vehicles are a separate entity (`land` / `water` / `air`) with their own HP, AC, capacity, and occupant roster. New events: `VehicleAcquired`, `VehicleBoarded`, `VehicleDeparted`, `VehicleDamaged`, `VehicleRepaired`; capacity is enforced at boarding time.
126
+ - ✓ **Slice 25.** Travel and overland. `TravelLegCompleted` events append to a `travelLog` on the campaign (pace, hours, miles, optional from/to locations, notes). `planNavigationCheck` rolls Wisdom (Survival) against caller DC and emits `NavigationCheckRolled`. `planForage` rolls Wisdom (Survival) and emits `ForagedFor` with food and water pounds gained on success. Forced-march exhaustion is recorded via the existing `ExhaustionChanged` event so callers can stack incremental exhaustion onto the same character without engine-side bookkeeping.
127
+ - ✓ **Slice 26.** NPCs with reaction and morale mechanics. Character schema gains optional `attitude`, `morale`, and `moraleBroken` fields. `planReactionRoll` rolls the presenter's CHA (Persuasion) against a DC and bumps the NPC's attitude (hostile / unfriendly / indifferent / friendly / helpful) based on margin. `planMoraleCheck` rolls the NPC's Wisdom against a DC; failed checks decrement morale and emit `MoraleBroken` (flee / surrender) when morale hits zero.
128
+ - ✓ **Slice 27.** Downtime, crafting, training. `DowntimeActivityResolved` appends to a `downtimeLog` on the campaign with kind (`crafting` / `training` / `recuperating` / `research` / `work` / `other`), day count, outcome (`success` / `partial` / `failure`), summary, optional produced item definition ID, and optional tool proficiency gained. Tool proficiencies accumulate per character in `toolProficienciesByCharacter`.
129
+ - ✓ **Slice 28.** Magic item charges, recharge, sentient items. ItemInstance gains `maxCharges` and `sentient { ego, alignment, personality }` fields. `ItemChargeConsumed` decrements `chargesRemaining` (refuses to over-spend), `ItemRecharged` adds back up to `maxCharges` on one of five cadences (`dawn`, `dusk`, `shortRest`, `longRest`, `manual`), `SentientItemConflict` records the outcome of an item-vs-wielder showdown.
130
+ - ✓ **Slice 29.** Resurrection variants. `CharacterResurrected` event with `spell` discriminator (`revivify`, `raise-dead`, `reincarnate`, `resurrection`, `true-resurrection`) restores the target to `hpAfter` HP, clears temp HP, resets death saves, and zeroes exhaustion. Reincarnate may set `newSpeciesId` to swap the character's species. Currency cost is left to the caller via the existing `CurrencySpent` event so consumers can apply table-specific economies.
131
+ - ✓ **Slice 30.** Wild Shape, Polymorph, Simulacrum, Wish. `PolymorphApplied` swaps HP, ability scores, speed, and species into a new form and snapshots the originals to `Character.polymorphedSnapshot`; `PolymorphReverted` restores them. `wild-shape`, `polymorph`, and `true-polymorph` share the machinery via a `kind` discriminator. `SimulacrumCreated` clones a character into a creature-kind duplicate at half-HP (transient state reset). `WishGranted` records a freeform wish description; `stressApplied: true` increments the granter's exhaustion. Concrete spell effects beyond the form swap stay in the consumer's hands.
132
+
133
+ ### Phase D: Adoption surface (7 slices, all done)
134
+
135
+ These don't add rules; they make the library usable by people who didn't write it. Higher priority than Phase E for any consumer that isn't this repo's author.
136
+
137
+ - ✓ **Slice 31.** Starter content pack bundled in the package as `src/content/packs/starter-pack.json` and exported via `loadStarterPack()`. Includes Fighter, Wizard, Rogue, Paladin, and Warlock classes (levels 1-5 for combat-relevant features), Human species, Soldier background, all 15 conditions, ~6 spells, ~10 items, plus the canonical Sneak Attack OnEvent feature. Enough to instantiate a character and run combat without writing any content from scratch; consumers extend it from the 2024 SRD CC-BY release as their needs grow.
138
+ - ✓ **Slice 32.** `/examples` directory with three runnable TypeScript apps: a character-sheet printer ([01-character-sheet](examples/01-character-sheet/)), an encounter-and-replay demo ([02-combat-encounter](examples/02-combat-encounter/)), and a save/load round-trip ([03-save-and-load](examples/03-save-and-load/)). Each is a single `npx tsx`-runnable file. An integration test in [tests/integration/examples.test.ts](tests/integration/examples.test.ts) executes them in CI so regressions in the public API surface get caught immediately.
139
+ - ✓ **Slice 33.** Getting-started doc at [docs/getting-started.md](docs/getting-started.md) walking through install, engine setup, character creation, attack resolution, and save/load round-trip. API reference at [docs/api-overview.md](docs/api-overview.md) maps every public symbol by namespace (planners, derivations, events, schemas, content packs, RNG, IDs, migrations).
140
+ - ✓ **Slice 34.** Public API conveniences. `engine.do(campaign, intent)` dispatches on `intent.type` to the right planner and commits the result in one call (covers every Phase A-C planner). `serializeCampaign(c)` writes a JSON string with id, name, schemaVersion, and events only; state is omitted because `loadCampaign(json)` replays the events to reconstruct it. `createPC({name, speciesId, backgroundId, classId, hpMax, ...})` returns a `Character` with sensible defaults; caller emits the `CharacterCreated` event themselves to add to a campaign.
141
+ - ✓ **Slice 35.** Derivation memoization keyed on `CampaignState.version`. Every `engine.derive.*` method now caches its result per-engine; the cache invalidates automatically when `state.version` advances (i.e., on every commit). Repeated calls at the same version return the same object reference, so a UI that asks for derived AC ten times per frame across twelve combatants pays for one computation each.
142
+ - ✓ **Slice 36.** npm publish prep. `package.json` declares `main` (CJS), `module` (ESM), `types` (`.d.ts`), and `exports` for both formats. `files` whitelists `dist/`, `docs/`, license, and READMEs. `prepublishOnly` runs the full CI gate (typecheck + tests + coverage + build) before any publish. `publishConfig: { access: public }` is set. `npm pack --dry-run` reports a ~398 KB tarball with no source or test code. Publishing is `npm publish` away.
143
+ - ✓ **Slice 37.** Content pack validator with diagnostic errors. `loadContentPack` throws a `ContentPackLoadError` whose `.issues` is a list of `{path, message}` entries derived from Zod's `safeParse` (e.g. `classes.0.hitDie: Expected number, received string`). `validateCrossReferences` returns issues with optional Levenshtein-based `suggestion` strings like `Did you mean "savage-attacker"?` so a one-character typo is identifiable from the error alone.
144
+
145
+ ### Phase E: 2024 content fill-out (9 slices, all done)
146
+
147
+ Heavy on data, light on engine code. Each class slice stress-tests Phases A and C.
148
+
149
+ - ✓ **Slice 38.** Classes group 1: Barbarian, Bard, Cleric, Druid added to the starter pack with 1-20 level tables, signature features at landmark levels (Rage, Fast Movement, Bardic Inspiration, Channel Divinity, Wild Shape), and spellcasting blocks for the three full-casters. Subclasses and per-level feature details are consumer extension; the engine schema accepts the full 2024 progression.
150
+ - ✓ **Slice 39.** Classes group 2: Fighter and Paladin were already in the starter pack from earlier slices; Monk and Ranger added with full 1-20 level tables. Monk features: Martial Arts, Unarmored Defense (OverrideACFormula with DEX+WIS), Monk's Focus (Ki resource), Unarmored Movement, Extra Attack at 5, Stunning Strike, Evasion. Ranger features: Favored Enemy with Hunter's Mark resource, Weapon Mastery grant (all 9 masteries, 2 slots), Fighting Style, Extra Attack, half-caster spellcasting on WIS.
151
+ - ✓ **Slice 40.** Classes group 3: Rogue, Warlock, and Wizard were already in the starter pack; Sorcerer added with Innate Sorcery and Font of Magic (sorcery-points resource), Metamagic placeholder, full CHA spellcasting. With this slice all 12 PHB classes ship in the starter pack.
152
+ - ✓ **Slice 41.** Spell catalog expanded to ~31 spells across cantrip + level 1-3 tiers covering the common archetypes: damage cantrips (Sacred Flame, Eldritch Blast, Ray of Frost, Shocking Grasp), utility cantrips (Guidance, Light, Mage Hand, Prestidigitation), save-or-suck level-1 (Bane, Sleep, Faerie Fire, Thunderwave, Burning Hands), buffs (Bless, Mage Armor, Shield), healing (Healing Word), AoE (Spirit Guardians, Web, Spiritual Weapon), utility (Misty Step, Aid, Lesser Restoration, Identify with ritual flag), and the reactive trio (Counterspell, Dispel Magic, Identify) that pair with the Slice 22 planners. Filling out the full 2024 catalog (~370 spells) is consumer territory; the schema and planners support every required shape.
153
+ - ✓ **Slice 42.** Species + backgrounds + feats + fighting styles + equipment + tools. Species expanded from Human only to seven (Human, Elf, Dwarf, Halfling, Tiefling, Dragonborn, Gnome). Backgrounds expanded to eight (Soldier, Sage, Criminal, Acolyte, Folk Hero, Noble, Guild Artisan, Outlander) each pointing at an origin feat. Feat list grew to ~22 covering origin feats (Alert, Magic Initiate variants, Tough, Skilled, Crafter, Lucky), general feats (Great Weapon Master, Sharpshooter, Polearm Master, War Caster, Resilient), and all six 2024 Fighting Styles as the new `fighting-style` category. Equipment doubled: 8 more weapons (Greatsword, Greataxe, Warhammer, Mace, Spear, Quarterstaff, Handaxe, Crossbow), 6 more armors (Studded Leather, Scale Mail, Breastplate, Half Plate, Ring Mail, Splint), 5 tools, and 7 adventuring-gear items.
154
+ - ✓ **Slice 43.** Magic items and monster statblocks. 9 magic items added across all rarity tiers (common Bag of Holding through legendary Deck of Many Things), with a charged wand (Wand of Magic Missiles, 7 charges, dawn recharge `1d6+1`) demonstrating the Slice 28 charge tracking. 6 monster statblocks added (Goblin, Orc, Wolf, Skeleton, Ogre, Young Red Dragon) covering Humanoid / Beast / Undead / Giant / Dragon creature types and CR 0.25 through 10, plus damage immunities/vulnerabilities/resistances and condition immunities where canonical.
155
+ - ✓ **Slice 44.** Bastions (2024 stronghold system). New `Bastion` entity (id, name, owner character, optional location, level 1-9, facilities, hirelings, defenders, treasury, HP). Six new events: `BastionFounded`, `BastionFacilityAdded` (basic / special, cramped / roomy / vast), `BastionHirelingAdded`, `BastionTurnTaken` (turn order: maintain / craft / recruit / research / trade / empower, with treasury delta and optional summary), `BastionDamaged` (clamps HP at zero), `BastionLevelChanged` (rejects mismatched fromLevel). Sufficient state for a consumer to run a full Bastion progression alongside an adventuring campaign.
156
+ - ✓ **Slice 45.** Epic boons (post-20 progression). The Feat schema already supported `category: 'epic-boon'`; this slice adds 9 boons to the starter pack (Combat Prowess, Dimensional Travel, Energy Resistance, Fortitude, Irresistible Offense, Skill, Spell Recall, the Night Spirit, Truesight) so consumers have working post-20 reward content. Granting a boon uses the existing `featsTaken` array on a Character; no new event type required.
157
+ - ✓ **Slice 46.** Variant rules toggles. New `CampaignSettings` shape on `CampaignState.settings` with boolean flags for `grittyRest`, `heroPoints`, `sanity`, `massCombat`, `feaCharacterFlaws`, plus a `customHouserules: string[]` for arbitrary table-specific tags. `CampaignSettingsChanged` event flips any subset of toggles in one go and add/removes custom houserule strings (dedupe on add). Consumers can branch their planner logic on these flags; the engine doesn't enforce them yet, leaving the rule interpretation to the consumer (e.g., a custom rest planner that respects `grittyRest` to scale short/long rest durations).
158
+
159
+ ### Phase F: Core extraction (1 slice, optional, future)
160
+
161
+ - **Slice 47.** Extract `ttrpg-engine-core` as a separate package. The architectural layer (event sourcing, plan/commit, branded IDs, content packs, sessions, journal, party + currency abstraction, predicate + formula DSL, PendingChoice protocol, undo/redo, transcript formatter, RNG-capture proof) is system-agnostic and could be the foundation for other TTRPG engines (Pathfinder, Tales of the Valiant, Gamma World, etc.). `ttrpg-engine-dnd` (this package) becomes the 5.5e adapter on top of the core. Only do this if multi-system support becomes a real goal; premature abstraction would slow the D&D work down for a hypothetical second consumer that doesn't exist yet. Estimated 2-4 weeks once this package is mature.
162
+
163
+ ### What "perfect" cannot mean
164
+
165
+ 5.5e explicitly delegates some rulings to the DM: improvised actions, narrative consequences, table houserules, ambiguous spell interactions that even Sage Advice has issued multiple clarifications on. A rules engine cannot adjudicate these. The `CustomEffect` code-handler escape hatch is the explicit spot for table-specific rulings. After all phases the engine covers ~95% of printed mechanics by surface area; the rest is documented as DM-discretion territory.
166
+
167
+ ## Install
168
+
169
+ ```sh
170
+ npm install ttrpg-engine-dnd@alpha
171
+ ```
172
+
173
+ The package is published under the `alpha` dist-tag and ships ESM, CJS, and `.d.ts`. Peer dependencies (`zod`, `immer`, `ulid`) install transitively. See [VERSIONING.md](VERSIONING.md) for the alpha-to-1.0 roadmap and the alpha->beta promotion gate.
174
+
175
+ You can also pin to a git ref while iterating alongside the engine:
176
+
177
+ ```jsonc
178
+ // in your consumer's package.json
179
+ "dependencies": {
180
+ "ttrpg-engine-dnd": "github:greghcarr/ttrpg-engine-dnd"
181
+ // or, when developing alongside the engine:
182
+ // "ttrpg-engine-dnd": "file:../dnd-engine"
183
+ }
184
+ ```
185
+
186
+ ## Usage (preview)
187
+
188
+ ```ts
189
+ import {
190
+ createEngine,
191
+ loadContentPack,
192
+ seededRNG,
193
+ } from 'ttrpg-engine-dnd';
194
+ import myContent from './my-content-pack.json';
195
+
196
+ const engine = createEngine({
197
+ contentPacks: [loadContentPack(myContent)],
198
+ rng: seededRNG(42),
199
+ });
200
+
201
+ let campaign = engine.createCampaign({ name: 'home game' });
202
+ // commit CharacterCreated + ItemAcquired events, then:
203
+
204
+ // melee attack
205
+ campaign = engine.commit(campaign, engine.plan.attack(campaign.state, {
206
+ attackerId: alyx.id,
207
+ targetId: goblin.id,
208
+ weaponInstanceId: longsword.id,
209
+ }).events);
210
+
211
+ // cast a spell
212
+ campaign = engine.commit(campaign, engine.plan.castSpell(campaign.state, {
213
+ characterId: wizard.id,
214
+ spellId: 'fireball',
215
+ slotLevel: 3,
216
+ targetIds: [goblin1.id, goblin2.id, goblin3.id],
217
+ }).events);
218
+
219
+ // level up with a rolled HP gain
220
+ campaign = engine.commit(campaign, engine.plan.levelUp(campaign.state, {
221
+ characterId: alyx.id,
222
+ classId: 'fighter',
223
+ hpStrategy: 'roll',
224
+ }).events);
225
+
226
+ // derive a character sheet (effective AC, saves, spell slots, etc.)
227
+ const sheet = engine.derive.character(campaign.state, alyx.id);
228
+ // sheet.ac, sheet.savingThrows, sheet.spellSlots, etc.
229
+ ```
230
+
231
+ For a step-by-step walkthrough, see [docs/getting-started.md](docs/getting-started.md). For the full surface, see [docs/api-overview.md](docs/api-overview.md). See [DEVELOPMENT.md](DEVELOPMENT.md) for the dev workflow and [CLAUDE.md](CLAUDE.md) for architecture conventions.
232
+
233
+ ## Intellectual property
234
+
235
+ This library is original work. It contains zero text, statblocks, or content from the Wizards of the Coast D&D 5.5e rulebooks. The schemas describe the *shape* of D&D content (a spell has a level, a school, a list of mechanical effects) but no copyrighted content.
236
+
237
+ D&D content is published by Wizards of the Coast. The 2024 SRD (System Reference Document) is released under Creative Commons CC BY 4.0; portions of older 5e content are available under the OGL 1.0a. If you build a content pack to load into this engine, your pack is subject to those licenses, not this library's license. This library does not ship, distribute, or endorse any specific content pack.
238
+
239
+ Dungeons & Dragons is a trademark of Wizards of the Coast LLC. This project is not affiliated with or endorsed by Wizards of the Coast.
240
+
241
+ ## Contributing
242
+
243
+ See [CONTRIBUTING.md](CONTRIBUTING.md). The architecture is locked (see [CLAUDE.md](CLAUDE.md)); contributions that fit within it are very welcome. Open an issue before a large change.
244
+
245
+ ## License
246
+
247
+ [MIT](LICENSE). Copyright (c) 2026 Greg Carr.