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
@@ -0,0 +1,8 @@
1
+ export type { AbilityScore, AbilityScores, Size, CreatureType, DamageType, Skill, ProficiencyLevel, HitDie, MovementMode, Speed, Sense, Senses, SpellSchool, SpellLevel, CharacterLevel, ExhaustionLevel, Alignment, Currency, CurrencyKey, Recharge, WeaponProperty, WeaponMastery, DiceExpression, } from '../schemas/primitives.js';
2
+ export type { Formula } from '../schemas/formula.js';
3
+ export type { Predicate } from '../schemas/predicate.js';
4
+ export type { Effect, EffectKind, ModifierTarget, RollTarget, } from '../schemas/effects.js';
5
+ export type { Condition, Species, Background, Feat, Class, ClassFeature, Subclass, SpellcastingProgression, LevelEntry, Spell, ItemDefinition, Weapon, Armor, Tool, MagicItem, Consumable, Gear, MonsterStatblock, } from '../schemas/content/index.js';
6
+ export type { Character, HP, DeathSaves, ClassEnrollment, AppliedCondition, ResourceState, ItemInstance, PendingChoice, ChoiceOption, CampaignState, } from '../schemas/runtime/index.js';
7
+ export type { Event, EventType, EventEnvelope, DamageAppliedEvent, DamageComponent, HealedEvent, TempHPGrantedEvent, ConditionAppliedEvent, ConditionRemovedEvent, ExhaustionChangedEvent, DeathSaveRolledEvent, StabilizedEvent, ResourceSpentEvent, ResourceRestoredEvent, HitDieSpentEvent, ShortRestStartedEvent, ShortRestEndedEvent, LongRestStartedEvent, LongRestEndedEvent, CharacterCreatedEvent, } from '../schemas/events/index.js';
8
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"AAAA,YAAY,EACV,YAAY,EACZ,aAAa,EACb,IAAI,EACJ,YAAY,EACZ,UAAU,EACV,KAAK,EACL,gBAAgB,EAChB,MAAM,EACN,YAAY,EACZ,KAAK,EACL,KAAK,EACL,MAAM,EACN,WAAW,EACX,UAAU,EACV,cAAc,EACd,eAAe,EACf,SAAS,EACT,QAAQ,EACR,WAAW,EACX,QAAQ,EACR,cAAc,EACd,aAAa,EACb,cAAc,GACf,MAAM,0BAA0B,CAAC;AAClC,YAAY,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAC;AACrD,YAAY,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAC;AACzD,YAAY,EACV,MAAM,EACN,UAAU,EACV,cAAc,EACd,UAAU,GACX,MAAM,uBAAuB,CAAC;AAC/B,YAAY,EACV,SAAS,EACT,OAAO,EACP,UAAU,EACV,IAAI,EACJ,KAAK,EACL,YAAY,EACZ,QAAQ,EACR,uBAAuB,EACvB,UAAU,EACV,KAAK,EACL,cAAc,EACd,MAAM,EACN,KAAK,EACL,IAAI,EACJ,SAAS,EACT,UAAU,EACV,IAAI,EACJ,gBAAgB,GACjB,MAAM,6BAA6B,CAAC;AACrC,YAAY,EACV,SAAS,EACT,EAAE,EACF,UAAU,EACV,eAAe,EACf,gBAAgB,EAChB,aAAa,EACb,YAAY,EACZ,aAAa,EACb,YAAY,EACZ,aAAa,GACd,MAAM,6BAA6B,CAAC;AACrC,YAAY,EACV,KAAK,EACL,SAAS,EACT,aAAa,EACb,kBAAkB,EAClB,eAAe,EACf,WAAW,EACX,kBAAkB,EAClB,qBAAqB,EACrB,qBAAqB,EACrB,sBAAsB,EACtB,oBAAoB,EACpB,eAAe,EACf,kBAAkB,EAClB,qBAAqB,EACrB,gBAAgB,EAChB,qBAAqB,EACrB,mBAAmB,EACnB,oBAAoB,EACpB,kBAAkB,EAClB,qBAAqB,GACtB,MAAM,4BAA4B,CAAC"}
@@ -0,0 +1,3 @@
1
+ export declare const SCHEMA_VERSION: 1;
2
+ export type SchemaVersion = typeof SCHEMA_VERSION;
3
+ //# sourceMappingURL=version.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"version.d.ts","sourceRoot":"","sources":["../src/version.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,cAAc,EAAG,CAAU,CAAC;AAEzC,MAAM,MAAM,aAAa,GAAG,OAAO,cAAc,CAAC"}
@@ -0,0 +1,111 @@
1
+ # API overview
2
+
3
+ The public surface is everything re-exported from [src/index.ts](../src/index.ts). Anything not exported is internal and may change without notice.
4
+
5
+ ## Engine
6
+
7
+ ```ts
8
+ const engine = createEngine({ contentPacks, rng, handlers? });
9
+ ```
10
+
11
+ Returns an `Engine` with five namespaces:
12
+
13
+ - `engine.createCampaign({ name })` builds a fresh `Campaign` with an empty state.
14
+ - `engine.apply(state, event)`, `engine.applyAll(state, events)`, `engine.replay(events)`: pure state transitions.
15
+ - `engine.commit(campaign, events)`: append events to a campaign, returning the new campaign.
16
+ - `engine.undo(campaign)`, `engine.redo(campaign)`: move the cursor along the log.
17
+ - `engine.plan.*`: planners that consume RNG and return events to commit. See [planners](#planners).
18
+ - `engine.derive.*`: pure derivations that read state and return typed results. See [derivations](#derivations).
19
+
20
+ ## Planners
21
+
22
+ Every planner returns `{ events: Event[] }` with the resolution chain baked in (dice rolls included). `apply()` is RNG-free.
23
+
24
+ **Resting & resources**: `shortRest`, `longRest`, `rest` (generic).
25
+
26
+ **Combat**: `attack`, `opportunityAttack`, `actionSurge`, `offHandAttack`, `multiattack`, `falling`.
27
+
28
+ **Encounter lifecycle**: `createEncounter`, `rollInitiative`, `startEncounter`, `beginFirstTurn`, `advanceTurn`, `endEncounter`.
29
+
30
+ **Movement**: `move`, `dash`, `disengage`.
31
+
32
+ **Spellcasting**: `castSpell`, `checkConcentration`, `counterspell`, `dispelMagic`, `identify`.
33
+
34
+ **Checks & saves**: `save`, `abilityCheck`.
35
+
36
+ **Progression**: `levelUp`, `resolveChoice`.
37
+
38
+ **Contested actions (Slice 21)**: `grapple`, `shove`, `hide`.
39
+
40
+ **Weapon mastery (Slice 23)**: `weaponMastery({mastery, attackerId, targetId, weaponInstanceId})`.
41
+
42
+ **Travel & exploration (Slice 25)**: `forage`, `navigationCheck`.
43
+
44
+ **NPC mechanics (Slice 26)**: `moraleCheck`, `reactionRoll`.
45
+
46
+ ## Derivations
47
+
48
+ All read-only.
49
+
50
+ - `character(state, id)` → `DerivedCharacter` (totalLevel, proficiency bonus, ability modifiers, HP, AC, saves, spell slots, pending choices).
51
+ - `ac(state, id)`, `savingThrow(state, id, ability)`, `attackBonus(state, id, weaponInstanceId)`.
52
+ - `spellSaveDC(state, id, classId)`, `spellAttackBonus(state, id, classId)`, `spellSlots(state, id)`.
53
+ - `abilityModifier(score)`, `proficiencyBonus(level)`: pure helpers.
54
+
55
+ Stand-alone derivations also exported: `computeAbilityCheck`, `computePassiveScore`, `computeAC`, `buildEffectStack`, plus terrain helpers `terrainAt`, `movementCostFor`, `movementCostAt`, `chebyshevDistanceFeet`, `isInRangeFeet`, `hasLineOfSight`, `hasLineOfEffect`.
56
+
57
+ ## Events
58
+
59
+ Every state transition is an event. The discriminated union `Event` lives at `EventSchema` (Zod) and `Event` (TypeScript). The full list is at [src/schemas/events/index.ts](../src/schemas/events/index.ts) in the `EVENT_TYPES` constant.
60
+
61
+ Categories:
62
+
63
+ - **Combat**: `DamageApplied`, `Healed`, `TempHPGranted`, `ConditionApplied/Removed`, `DeathSaveRolled`, `Stabilized`, `ExhaustionChanged`, `AttackRolled`, `DamageRolled`, `SpellCastDeclared`, `SpellSlotConsumed`, `PactSlotConsumed`, `ConcentrationStarted/Broken`, `TriggerFired`, `ActionEconomyConsumed`, `CombatantMoved`, `Dashed`, `Disengaged`, `SaveRolled`, `AbilityCheckRolled`.
64
+ - **Spellcasting (reactive)**: `SpellCountered`, `SpellDispelled`, `ItemIdentified`.
65
+ - **Weapon mastery**: `WeaponMasteryActivated`.
66
+ - **Encounter**: `EncounterCreated/Started/Ended`, `InitiativeRolled`, `TurnStarted/Ended`, `RoundEnded`.
67
+ - **Resting**: `ShortRestStarted/Ended`, `LongRestStarted/Ended`, `HitDieSpent`, `ResourceSpent/Restored`.
68
+ - **Progression**: `CharacterCreated`, `LevelUpResolved`, `ChoiceRequired/Resolved`, `XPAwarded`, `MilestoneAwarded`.
69
+ - **Inventory**: `ItemAcquired/Equipped/Unequipped/Attuned/Unattuned`, `ItemChargeConsumed`, `ItemRecharged`, `SentientItemConflict`.
70
+ - **Party & treasure**: `PartyCreated`, `PartyMembersChanged`, `CurrencyAcquired/Spent`, `ItemDepositedToParty/WithdrawnFromParty`.
71
+ - **Sessions & journal**: `SessionStarted/Ended`, `JournalEntryAdded`, `InGameTimeAdvanced`.
72
+ - **Locations & terrain**: `LocationCreated`, `DoorAdded`, `DoorStateChanged`, `CharacterLocationChanged`.
73
+ - **Quests**: `QuestStarted`, `ObjectiveProgressed/Completed/Failed`, `QuestCompleted/Failed/Abandoned`, `QuestRewardClaimed`.
74
+ - **Travel**: `TravelLegCompleted`, `NavigationCheckRolled`, `ForagedFor`.
75
+ - **NPC mechanics**: `AttitudeChanged`, `MoraleCheckRolled`, `MoraleBroken`.
76
+ - **Downtime**: `DowntimeActivityResolved`.
77
+ - **Mounts & vehicles**: `Mounted`, `Dismounted`, `VehicleAcquired`, `VehicleBoarded`, `VehicleDeparted`, `VehicleDamaged`, `VehicleRepaired`.
78
+ - **Resurrection & transformation**: `CharacterResurrected`, `PolymorphApplied/Reverted`, `SimulacrumCreated`, `WishGranted`.
79
+
80
+ ## Schemas
81
+
82
+ Every shape is a Zod schema (parse at boundaries, types via `z.infer`):
83
+
84
+ - Content: `ContentPackSchema`, `SpeciesSchema`, `BackgroundSchema`, `FeatSchema`, `ClassSchema`, `SubclassSchema`, `ClassFeatureSchema`, `SpellSchema`, `ConditionSchema`, `ItemDefinitionSchema` (and its variants `WeaponSchema`, `ArmorSchema`, `ToolSchema`, `MagicItemSchema`, `ConsumableSchema`, `GearSchema`), `MonsterStatblockSchema`.
85
+ - Runtime: `CharacterSchema`, `ItemInstanceSchema`, `EncounterSchema`, `EffectInstanceSchema`, `PartySchema`, `SessionSchema`, `JournalEntrySchema`, `LocationSchema`, `DoorSchema`, `LocationMapSchema`, `QuestSchema`, `QuestObjectiveSchema`, `VehicleSchema`, `CampaignStateSchema`.
86
+
87
+ ## Content packs
88
+
89
+ ```ts
90
+ const pack = loadContentPack(json);
91
+ const resolved = resolveContent([pack1, pack2]);
92
+ const issues = validateCrossReferences(resolved);
93
+ ```
94
+
95
+ `loadStarterPack()` returns the bundled starter pack. `STARTER_PACK_RAW` exposes the underlying object if you need to inspect or extend it.
96
+
97
+ ## RNG
98
+
99
+ ```ts
100
+ import { defaultRNG, seededRNG, throwOnCallRNG } from 'ttrpg-engine-dnd';
101
+ ```
102
+
103
+ `seededRNG(seed)` for deterministic tests. `throwOnCallRNG()` is the architectural canary: pass it into a replay to prove `apply()` never reaches for randomness.
104
+
105
+ ## IDs
106
+
107
+ Branded string types per kind. Factories: `newCharacterId`, `newCreatureId`, `newPartyId`, `newEncounterId`, `newCampaignId`, `newSessionId`, `newLocationId`, `newQuestId`, `newJournalEntryId`, `newEventId`, `newChoiceId`, `newEffectInstanceId`, `newAppliedConditionId`, `newItemInstanceId`. Brand casts: `asCharacterId`, `asSpeciesId`, etc.
108
+
109
+ ## Migrations
110
+
111
+ `migrate(json) → CampaignState` walks the on-disk version forward.
@@ -0,0 +1,154 @@
1
+ # Concepts
2
+
3
+ The mental model behind the engine. About fifteen minutes to read. After this, the planners and reducers will make a lot more sense.
4
+
5
+ ## Events are the truth, state is the projection
6
+
7
+ Most game engines mutate state in place: an actor's HP field gets decremented, an effect gets appended to a list. The state IS the truth; the history of how it got there is a chat log at best.
8
+
9
+ This engine inverts that. The truth is an append-only **event log**. The current state is **computed** by folding `apply(state, event) -> state` over the log.
10
+
11
+ ```ts
12
+ campaign.events // ← the truth: every state change ever recorded
13
+ campaign.state // ← derived: state == replay(events)
14
+ ```
15
+
16
+ `apply()` is **pure**: same state + same event always produces the same next state. `replay(events)` walks the entire log and reconstructs the state from empty. On every commit the engine asserts `replay(state.events).state` deep-equals `campaign.state` (this invariant is tested in CI on every golden scenario).
17
+
18
+ What this gives you:
19
+
20
+ - **Save files are just JSON arrays of events.** `serializeCampaign(c)` writes id + name + schemaVersion + events. State is not stored; it's reconstructed by `loadCampaign(json)`.
21
+ - **Undo / redo are free.** The campaign carries a cursor into the event log. `undo()` decrements it; the state is recomputed from the prefix.
22
+ - **Multiplayer sync is straightforward.** Stream events between clients. As long as the planners on both sides consume the same RNG, they arrive at byte-equivalent state.
23
+ - **Debugging.** "Why is my HP 12?" Filter the event log: every change has a cause. The chat log isn't the only record; the events are.
24
+ - **Branching.** Fork the event list, append hypothetical events, observe the resulting state. Discard the fork. This is how an AI DM could evaluate "what if I cast Fireball here?"
25
+
26
+ The cost: every state change has to go through an event. Direct mutation of `campaign.state` is forbidden (and TypeScript enforces it: the state shape is frozen by Immer).
27
+
28
+ ## Plan / commit split: RNG capture
29
+
30
+ The hardest part of event-sourced game engines is randomness. If `apply()` rolls dice during the attack reducer, the same events replay to different states on different machines.
31
+
32
+ This engine splits the work:
33
+
34
+ - **`engine.plan.*(intent)`**: consumes RNG. Rolls dice. Returns events with the dice results **baked in**.
35
+ - **`apply()`**: pure. Reads the baked rolls. Never touches RNG.
36
+
37
+ ```ts
38
+ // plan.attack consumes RNG, returns events with d20, damage dice, etc. captured.
39
+ const { events } = engine.plan.attack(state, { attackerId, targetId, weaponInstanceId });
40
+
41
+ // commit appends events and applies them. apply() never re-rolls.
42
+ campaign = commit(campaign, events);
43
+ ```
44
+
45
+ The architectural invariant: passing `ThrowOnCallRNG()` to `replay()` must not throw on any golden scenario. This is asserted in CI. If a reducer ever reaches for the RNG, the test suite fails.
46
+
47
+ What this gives you:
48
+
49
+ - **Deterministic replay across machines.** Two clients playing the same campaign arrive at the same state by streaming events alone.
50
+ - **The events ARE the dice record.** Looking at `AttackRolled` you see `d20: [14, 17], used: 17` and you know the natural-17 came from rolling with advantage.
51
+ - **Easy to test.** A seeded RNG (`seededRNG(42)`) makes a scenario reproducible from end to end.
52
+
53
+ The cost: dice rolls happen at plan time, not at apply time. You can't roll dice in a reducer.
54
+
55
+ ## Schema-only library: bring your own content
56
+
57
+ A rules engine that ships content has IP problems and is hard to extend. This engine ships **only schemas + machinery** and a small **starter content pack** (under MIT, drawn from the 2024 SRD shape). Consumers load their own content packs at runtime.
58
+
59
+ A content pack is a JSON file with this shape:
60
+
61
+ ```ts
62
+ {
63
+ id: 'my-pack',
64
+ name: 'My Pack',
65
+ version: '0.1.0',
66
+ species: [...],
67
+ backgrounds: [...],
68
+ classes: [...],
69
+ subclasses: [...],
70
+ feats: [...],
71
+ spells: [...],
72
+ items: [...],
73
+ monsters: [...],
74
+ conditions: [...],
75
+ }
76
+ ```
77
+
78
+ Each list is validated against its Zod schema (`SpeciesSchema`, `SpellSchema`, etc.). Cross-references are checked (a `background.originFeatId` must point at a real feat).
79
+
80
+ ```ts
81
+ import { loadContentPack, resolveContent, validateCrossReferences } from 'ttrpg-engine-dnd';
82
+
83
+ const pack = loadContentPack(myJson); // Zod parse, throws on shape error
84
+ const content = resolveContent([pack, otherPack]); // merge multiple packs
85
+ const issues = validateCrossReferences(content); // returns Levenshtein-suggested issues
86
+ ```
87
+
88
+ Multiple packs merge with later packs winning on ID conflicts. This is how you layer "core + homebrew" or "core + setting + table-specific."
89
+
90
+ ## Effect primitives plus an escape hatch
91
+
92
+ 5.5e content has a wildly heterogeneous mechanical surface. The Barbarian's Rage is one shape; the Wizard's Mage Armor is another; Wish is its own beast.
93
+
94
+ The engine expresses **most** features via a fixed vocabulary of about 25 **effect primitives**:
95
+
96
+ - `AddModifier { target, value, condition? }`: a +1 to AC, a +2 to attack bonus
97
+ - `GrantResource { resourceId, max, recharge }`: Bardic Inspiration, Action Surge, Channel Divinity
98
+ - `OverrideACFormula { base, abilityModifiers, ... }`: Unarmored Defense
99
+ - `OnEvent { trigger, actions, oncePer? }`: Sneak Attack (rider damage on a hit)
100
+ - `GrantResistance / GrantImmunity / GrantVulnerability`: Half-Orc Relentless Endurance, dragon fire immunity
101
+ - `ModifyActionEconomy { op: 'extraAttack' | ... }`: Extra Attack
102
+ - `ModifySpeed { mode, op, value }`: Barbarian Fast Movement
103
+ - `GrantWeaponMastery { masteries, slots }`: Ranger's mastery grant
104
+ - ...and about a dozen more.
105
+
106
+ Effects live on **classes**, **feats**, **species**, **conditions**, and **items**. The engine's `buildEffectStack(character)` walks every source and assembles a single ordered list. Derivations then read the stack.
107
+
108
+ For features that genuinely don't fit a primitive (Wild Shape, Polymorph, Simulacrum, Wish), there's an **escape hatch**: dedicated event types (`PolymorphApplied`, `SimulacrumCreated`, `WishGranted`) with their own reducers. Same architectural contract: pure apply, baked rolls, replay-safe.
109
+
110
+ This split lets data express ~95% of features without code, and code handle the ~5% that data can't.
111
+
112
+ ## PendingChoice: deferred decisions are events too
113
+
114
+ A character levels up: do they want a feat or ASI? They learn a new spell: which spells does the pack offer at that level?
115
+
116
+ In an imperative engine these are interactive prompts mid-mutation. Here they're **first-class events**:
117
+
118
+ ```ts
119
+ ChoiceRequired { choiceId, characterId, kind, options } // engine emits this
120
+ ChoiceResolved { choiceId, selectedOptionIds } // consumer emits this
121
+ ```
122
+
123
+ The level-up planner emits a `LevelUpResolved` event plus any `ChoiceRequired` for decisions the player owes. The choice sits on `campaign.state.pendingChoices` indefinitely. When the player picks, the consumer emits `ChoiceResolved` and the engine applies the chosen option's effects.
124
+
125
+ This is how the engine supports "build a UI" without owning the UI: ask the engine what decisions are open, render them, send back the selections.
126
+
127
+ ## Branded IDs
128
+
129
+ Every entity kind has its own branded string type: `CharacterId`, `EncounterId`, `ItemInstanceId`, `ItemDefinitionId`, etc. They're all `string` at runtime (ULIDs), but the type system stops you from passing a spell ID where a character ID was expected.
130
+
131
+ ```ts
132
+ const c: CharacterId = newCharacterId();
133
+ const i: ItemInstanceId = newItemInstanceId();
134
+ engine.derive.attackBonus(state, c, i); // typechecks
135
+ engine.derive.attackBonus(state, i, c); // ❌ type error
136
+ ```
137
+
138
+ When you load IDs from JSON, use the cast helpers: `asCharacterId(s)`, `asSpeciesId(s)`, etc. At parse time the Zod schemas validate that the strings are ULIDs.
139
+
140
+ ## Derivations are pure, memoized per state version
141
+
142
+ `engine.derive.character(state, id)`, `engine.derive.ac(state, id)`, etc. are **pure functions** of `(state, args)`. No caching, no side effects, no mutation.
143
+
144
+ Internally the engine memoizes them per `CampaignState.version`. Every commit bumps the version; the next derive call clears the cache. So if you ask for derived AC ten times per frame across twelve combatants, you pay for one computation each, not 120.
145
+
146
+ This is invisible to callers: the API is just "give me the derived sheet"; performance is handled.
147
+
148
+ ## Where to next
149
+
150
+ - **Try it**: [examples/00-quickstart](../examples/00-quickstart/) is the smallest working consumer.
151
+ - **Walk through the tutorial**: [docs/getting-started.md](getting-started.md).
152
+ - **Look up an API**: [docs/api-overview.md](api-overview.md).
153
+ - **Common how-to recipes**: [docs/recipes.md](recipes.md).
154
+ - **See it all at once**: the showcase transcript at [tests/golden/transcripts/showcase.transcript.md](../tests/golden/transcripts/showcase.transcript.md).
@@ -0,0 +1,142 @@
1
+ # Getting started
2
+
3
+ This walkthrough builds your first character, attacks a goblin, saves the campaign, and reloads it. About fifteen minutes from a blank project.
4
+
5
+ ## 1. Install
6
+
7
+ ```sh
8
+ npm install ttrpg-engine-dnd@alpha
9
+ ```
10
+
11
+ Peer dependencies (`zod`, `immer`, `ulid`) install transitively.
12
+
13
+ ## 2. Create an engine with the starter pack
14
+
15
+ ```ts
16
+ import { createEngine, loadStarterPack, seededRNG } from 'ttrpg-engine-dnd';
17
+
18
+ const engine = createEngine({
19
+ contentPacks: [loadStarterPack()],
20
+ rng: seededRNG(42),
21
+ });
22
+ ```
23
+
24
+ The starter pack ships in the package and includes Fighter, Wizard, Rogue, Paladin, and Warlock classes through level 5, Human species, Soldier background, all 15 conditions, common weapons and armor, and a handful of spells. Bring your own content pack when you outgrow it.
25
+
26
+ ## 3. Build a character
27
+
28
+ ```ts
29
+ import { CharacterSchema, newCharacterId, newItemInstanceId, newEventId } from 'ttrpg-engine-dnd';
30
+ import { commit } from 'ttrpg-engine-dnd';
31
+
32
+ const alyx = CharacterSchema.parse({
33
+ id: newCharacterId(),
34
+ name: 'Alyx',
35
+ speciesId: 'human',
36
+ backgroundId: 'soldier',
37
+ classes: [{ classId: 'fighter', level: 3, hitDiceRemaining: 3 }],
38
+ abilityScores: { STR: 16, DEX: 14, CON: 14, INT: 10, WIS: 12, CHA: 8 },
39
+ hp: { current: 26, max: 26, temp: 0 },
40
+ featsTaken: ['savage-attacker'],
41
+ });
42
+
43
+ const sword = {
44
+ id: newItemInstanceId(),
45
+ definitionId: 'longsword',
46
+ quantity: 1,
47
+ attuned: false,
48
+ identifiedByCharacterIds: [],
49
+ };
50
+
51
+ let campaign = engine.createCampaign({ name: 'demo' });
52
+ campaign = commit(campaign, [
53
+ { id: newEventId(), at: new Date().toISOString(), type: 'ItemAcquired', instance: sword },
54
+ { id: newEventId(), at: new Date().toISOString(), type: 'CharacterCreated', snapshot: alyx },
55
+ ]);
56
+ ```
57
+
58
+ The engine state is now populated. `commit` is pure: it returns a new `Campaign` with the events appended and the state advanced.
59
+
60
+ ## 4. Derive their sheet
61
+
62
+ ```ts
63
+ const sheet = engine.derive.character(campaign.state, alyx.id);
64
+ const ac = engine.derive.ac(campaign.state, alyx.id);
65
+ const attack = engine.derive.attackBonus(campaign.state, alyx.id, sword.id);
66
+
67
+ console.log(`AC ${ac.total}, Longsword +${attack.total} to hit`);
68
+ ```
69
+
70
+ Every derivation returns a typed result with a breakdown (each contributing modifier and its source), not just a total.
71
+
72
+ ## 5. Take an attack
73
+
74
+ Add a goblin, create an encounter, and attack:
75
+
76
+ ```ts
77
+ const goblin = CharacterSchema.parse({
78
+ id: newCharacterId(),
79
+ name: 'Goblin',
80
+ speciesId: 'human',
81
+ backgroundId: 'soldier',
82
+ classes: [{ classId: 'fighter', level: 1, hitDiceRemaining: 1 }],
83
+ abilityScores: { STR: 8, DEX: 14, CON: 10, INT: 10, WIS: 8, CHA: 8 },
84
+ hp: { current: 7, max: 7, temp: 0 },
85
+ featsTaken: ['savage-attacker'],
86
+ });
87
+
88
+ campaign = commit(campaign, [
89
+ { id: newEventId(), at: new Date().toISOString(), type: 'CharacterCreated', snapshot: goblin },
90
+ ]);
91
+
92
+ const enc = engine.plan.createEncounter(campaign.state, {
93
+ combatantIds: [alyx.id, goblin.id],
94
+ name: 'Goblin at the bridge',
95
+ });
96
+ campaign = commit(campaign, enc.events);
97
+ campaign = commit(campaign, engine.plan.rollInitiative(campaign.state, { encounterId: enc.encounterId }).events);
98
+ campaign = commit(campaign, engine.plan.beginFirstTurn(campaign.state, { encounterId: enc.encounterId }).events);
99
+
100
+ campaign = commit(
101
+ campaign,
102
+ engine.plan.attack(campaign.state, {
103
+ attackerId: alyx.id,
104
+ targetId: goblin.id,
105
+ weaponInstanceId: sword.id,
106
+ }).events,
107
+ );
108
+
109
+ console.log(`Goblin HP: ${campaign.state.characters[goblin.id]?.hp.current}/7`);
110
+ ```
111
+
112
+ All randomness was consumed inside `engine.plan.attack`. The events it returned have the d20 and damage dice baked in. `apply()` never touches RNG, so the campaign event log replays to byte-equivalent state on any machine.
113
+
114
+ ## 6. Save and load
115
+
116
+ ```ts
117
+ import { replay, EventSchema } from 'ttrpg-engine-dnd';
118
+
119
+ // Save: events are the durable artifact. State is computed.
120
+ const saved = JSON.stringify({
121
+ id: campaign.id,
122
+ name: campaign.name,
123
+ schemaVersion: campaign.schemaVersion,
124
+ events: campaign.events,
125
+ });
126
+
127
+ // Load: parse events, replay, you have the same state.
128
+ const parsed = JSON.parse(saved) as { events: unknown[] };
129
+ const events = parsed.events.map((e) => EventSchema.parse(e));
130
+ const restoredState = replay(events);
131
+ // restoredState deep-equals campaign.state.
132
+ ```
133
+
134
+ This is the practical payoff of event sourcing. Your save file is the truth; the state is derived.
135
+
136
+ ## What's next
137
+
138
+ - **Understand the mental model**: [docs/concepts.md](concepts.md) explains why the API has the shape it does (events, plan/commit, content packs, effect primitives, PendingChoice).
139
+ - **Common how-tos**: [docs/recipes.md](recipes.md) covers save/undo/redo, branching timelines, adding content and feats, houserules, multiplayer sync, custom planners, and migrations.
140
+ - **Browse the public surface**: [docs/api-overview.md](api-overview.md) lists every public symbol by namespace.
141
+ - **Larger scenarios**: [examples/](../examples/) has three runnable scripts. The showcase transcript at [tests/golden/transcripts/showcase.transcript.md](../tests/golden/transcripts/showcase.transcript.md) walks through a multi-act campaign exercising most of the engine.
142
+ - **Bring your own content**: see [src/schemas/content/](../src/schemas/content/) for the Zod schemas of `Species`, `Background`, `Class`, `Spell`, `Feat`, `ItemDefinition`, `MonsterStatblock`, `Condition`. Load with `loadContentPack(json)` and merge with the starter via `resolveContent([starter, mine])`.