dnd5e-engine 0.3.0__tar.gz → 0.3.2__tar.gz

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 (157) hide show
  1. {dnd5e_engine-0.3.0 → dnd5e_engine-0.3.2}/CHANGELOG.md +54 -0
  2. {dnd5e_engine-0.3.0 → dnd5e_engine-0.3.2}/Makefile +1 -1
  3. {dnd5e_engine-0.3.0 → dnd5e_engine-0.3.2}/PKG-INFO +2 -2
  4. {dnd5e_engine-0.3.0 → dnd5e_engine-0.3.2}/pyproject.toml +1 -1
  5. {dnd5e_engine-0.3.0 → dnd5e_engine-0.3.2}/src/dnd5e_engine/__init__.py +23 -9
  6. {dnd5e_engine-0.3.0 → dnd5e_engine-0.3.2}/src/dnd5e_engine/activities/__init__.py +1 -1
  7. {dnd5e_engine-0.3.0 → dnd5e_engine-0.3.2}/src/dnd5e_engine/activities/apply.py +2 -2
  8. {dnd5e_engine-0.3.0 → dnd5e_engine-0.3.2}/src/dnd5e_engine/activities/attack.py +6 -6
  9. {dnd5e_engine-0.3.0 → dnd5e_engine-0.3.2}/src/dnd5e_engine/activities/build_context.py +14 -50
  10. {dnd5e_engine-0.3.0 → dnd5e_engine-0.3.2}/src/dnd5e_engine/activities/cast.py +1 -1
  11. {dnd5e_engine-0.3.0 → dnd5e_engine-0.3.2}/src/dnd5e_engine/activities/check.py +2 -2
  12. {dnd5e_engine-0.3.0 → dnd5e_engine-0.3.2}/src/dnd5e_engine/activities/context.py +5 -5
  13. {dnd5e_engine-0.3.0 → dnd5e_engine-0.3.2}/src/dnd5e_engine/activities/damage.py +3 -3
  14. {dnd5e_engine-0.3.0 → dnd5e_engine-0.3.2}/src/dnd5e_engine/activities/dice.py +1 -1
  15. {dnd5e_engine-0.3.0 → dnd5e_engine-0.3.2}/src/dnd5e_engine/activities/effects.py +1 -1
  16. {dnd5e_engine-0.3.0 → dnd5e_engine-0.3.2}/src/dnd5e_engine/activities/formula.py +2 -2
  17. {dnd5e_engine-0.3.0 → dnd5e_engine-0.3.2}/src/dnd5e_engine/activities/heal.py +3 -3
  18. {dnd5e_engine-0.3.0 → dnd5e_engine-0.3.2}/src/dnd5e_engine/activities/monster_actions.py +143 -48
  19. {dnd5e_engine-0.3.0 → dnd5e_engine-0.3.2}/src/dnd5e_engine/activities/passive_stats.py +10 -10
  20. {dnd5e_engine-0.3.0 → dnd5e_engine-0.3.2}/src/dnd5e_engine/activities/resolver.py +2 -2
  21. {dnd5e_engine-0.3.0 → dnd5e_engine-0.3.2}/src/dnd5e_engine/activities/save.py +1 -1
  22. {dnd5e_engine-0.3.0 → dnd5e_engine-0.3.2}/src/dnd5e_engine/activities/save_primitive.py +4 -4
  23. {dnd5e_engine-0.3.0 → dnd5e_engine-0.3.2}/src/dnd5e_engine/activities/scale.py +5 -5
  24. {dnd5e_engine-0.3.0 → dnd5e_engine-0.3.2}/src/dnd5e_engine/build_party.py +1 -1
  25. {dnd5e_engine-0.3.0 → dnd5e_engine-0.3.2}/src/dnd5e_engine/check.py +33 -21
  26. {dnd5e_engine-0.3.0 → dnd5e_engine-0.3.2}/src/dnd5e_engine/dispatch.py +10 -10
  27. {dnd5e_engine-0.3.0 → dnd5e_engine-0.3.2}/src/dnd5e_engine/event_dicts.py +2 -2
  28. {dnd5e_engine-0.3.0 → dnd5e_engine-0.3.2}/src/dnd5e_engine/events.py +4 -4
  29. dnd5e_engine-0.3.2/src/dnd5e_engine/lib_loader.py +45 -0
  30. {dnd5e_engine-0.3.0 → dnd5e_engine-0.3.2}/src/dnd5e_engine/orchestrator.py +139 -119
  31. {dnd5e_engine-0.3.0 → dnd5e_engine-0.3.2}/src/dnd5e_engine/outcome.py +5 -5
  32. {dnd5e_engine-0.3.0 → dnd5e_engine-0.3.2}/src/dnd5e_engine/rest.py +7 -7
  33. {dnd5e_engine-0.3.0 → dnd5e_engine-0.3.2}/src/dnd5e_engine/results.py +2 -2
  34. {dnd5e_engine-0.3.0 → dnd5e_engine-0.3.2}/src/dnd5e_engine/rules/combat.py +18 -12
  35. {dnd5e_engine-0.3.0 → dnd5e_engine-0.3.2}/src/dnd5e_engine/rules/combat_helpers.py +2 -2
  36. {dnd5e_engine-0.3.0 → dnd5e_engine-0.3.2}/src/dnd5e_engine/rules/conditions.py +36 -18
  37. {dnd5e_engine-0.3.0 → dnd5e_engine-0.3.2}/src/dnd5e_engine/rules/dice.py +39 -27
  38. {dnd5e_engine-0.3.0 → dnd5e_engine-0.3.2}/src/dnd5e_engine/rules/effects.py +15 -7
  39. {dnd5e_engine-0.3.0 → dnd5e_engine-0.3.2}/src/dnd5e_engine/rules/resolution.py +1 -1
  40. {dnd5e_engine-0.3.0 → dnd5e_engine-0.3.2}/src/dnd5e_engine/rules/skills.py +16 -9
  41. {dnd5e_engine-0.3.0 → dnd5e_engine-0.3.2}/src/dnd5e_engine/spatial.py +1 -1
  42. {dnd5e_engine-0.3.0 → dnd5e_engine-0.3.2}/src/dnd5e_engine/specs.py +10 -3
  43. {dnd5e_engine-0.3.0 → dnd5e_engine-0.3.2}/src/dnd5e_engine/types/combat.py +10 -10
  44. {dnd5e_engine-0.3.0 → dnd5e_engine-0.3.2}/src/dnd5e_engine/types/conditions.py +1 -1
  45. {dnd5e_engine-0.3.0 → dnd5e_engine-0.3.2}/src/dnd5e_engine/types/effects.py +5 -5
  46. {dnd5e_engine-0.3.0 → dnd5e_engine-0.3.2}/src/dnd5e_engine/types/intent.py +15 -15
  47. {dnd5e_engine-0.3.0 → dnd5e_engine-0.3.2}/src/dnd5e_engine/views.py +4 -1
  48. dnd5e_engine-0.3.2/tests/activities/test_multiattack_fidelity.py +127 -0
  49. dnd5e_engine-0.3.2/tests/activities/test_resolve_check_activity.py +349 -0
  50. dnd5e_engine-0.3.2/tests/activities/test_save_primitive.py +347 -0
  51. dnd5e_engine-0.3.2/tests/activities/test_weapon_mastery.py +335 -0
  52. {dnd5e_engine-0.3.0 → dnd5e_engine-0.3.2}/tests/test_apply_changes_to_check.py +2 -2
  53. dnd5e_engine-0.3.2/tests/test_capability_matrix.py +125 -0
  54. {dnd5e_engine-0.3.0 → dnd5e_engine-0.3.2}/tests/test_combat_bucket_keys.py +1 -1
  55. dnd5e_engine-0.3.2/tests/test_determinism_contract.py +131 -0
  56. dnd5e_engine-0.3.2/tests/test_docstrings_are_host_agnostic.py +60 -0
  57. {dnd5e_engine-0.3.0 → dnd5e_engine-0.3.2}/tests/test_iter11_fixes.py +1 -1
  58. dnd5e_engine-0.3.2/tests/test_lib_loader_configure.py +20 -0
  59. {dnd5e_engine-0.3.0 → dnd5e_engine-0.3.2}/tests/test_orchestrator_pc_resolution_typed.py +1 -1
  60. {dnd5e_engine-0.3.0 → dnd5e_engine-0.3.2}/tests/test_public_api_surface.py +1 -0
  61. {dnd5e_engine-0.3.0 → dnd5e_engine-0.3.2}/tests/test_resolve_check.py +2 -2
  62. dnd5e_engine-0.3.2/tests/test_rules_combat_data_and_skills.py +451 -0
  63. dnd5e_engine-0.3.2/tests/test_rules_combat_helpers.py +654 -0
  64. dnd5e_engine-0.3.2/tests/test_rules_combat_resolution.py +802 -0
  65. dnd5e_engine-0.3.2/tests/test_rules_conditions.py +428 -0
  66. dnd5e_engine-0.3.2/tests/test_rules_dice_equipment_spells.py +563 -0
  67. dnd5e_engine-0.3.2/tests/test_rules_effects_and_gambits.py +580 -0
  68. dnd5e_engine-0.3.2/tests/test_rules_resolution_and_parsing.py +368 -0
  69. dnd5e_engine-0.3.0/src/dnd5e_engine/lib_loader.py +0 -31
  70. {dnd5e_engine-0.3.0 → dnd5e_engine-0.3.2}/.gitignore +0 -0
  71. {dnd5e_engine-0.3.0 → dnd5e_engine-0.3.2}/LICENSE +0 -0
  72. {dnd5e_engine-0.3.0 → dnd5e_engine-0.3.2}/NOTICE +0 -0
  73. {dnd5e_engine-0.3.0 → dnd5e_engine-0.3.2}/README.md +0 -0
  74. {dnd5e_engine-0.3.0 → dnd5e_engine-0.3.2}/scripts/_smoke_grid_combat.py +0 -0
  75. {dnd5e_engine-0.3.0 → dnd5e_engine-0.3.2}/scripts/smoke_clean_install.sh +0 -0
  76. {dnd5e_engine-0.3.0 → dnd5e_engine-0.3.2}/src/dnd5e_engine/activities/mastery.py +0 -0
  77. {dnd5e_engine-0.3.0 → dnd5e_engine-0.3.2}/src/dnd5e_engine/build_spec.py +0 -0
  78. {dnd5e_engine-0.3.0 → dnd5e_engine-0.3.2}/src/dnd5e_engine/death_saves.py +0 -0
  79. {dnd5e_engine-0.3.0 → dnd5e_engine-0.3.2}/src/dnd5e_engine/py.typed +0 -0
  80. {dnd5e_engine-0.3.0 → dnd5e_engine-0.3.2}/src/dnd5e_engine/rules/__init__.py +0 -0
  81. {dnd5e_engine-0.3.0 → dnd5e_engine-0.3.2}/src/dnd5e_engine/rules/_class_meta.py +0 -0
  82. {dnd5e_engine-0.3.0 → dnd5e_engine-0.3.2}/src/dnd5e_engine/rules/_parsing.py +0 -0
  83. {dnd5e_engine-0.3.0 → dnd5e_engine-0.3.2}/src/dnd5e_engine/rules/combat_data.py +0 -0
  84. {dnd5e_engine-0.3.0 → dnd5e_engine-0.3.2}/src/dnd5e_engine/rules/equipment.py +0 -0
  85. {dnd5e_engine-0.3.0 → dnd5e_engine-0.3.2}/src/dnd5e_engine/rules/gambits.py +0 -0
  86. {dnd5e_engine-0.3.0 → dnd5e_engine-0.3.2}/src/dnd5e_engine/rules/spells.py +0 -0
  87. {dnd5e_engine-0.3.0 → dnd5e_engine-0.3.2}/src/dnd5e_engine/testing.py +0 -0
  88. {dnd5e_engine-0.3.0 → dnd5e_engine-0.3.2}/src/dnd5e_engine/types/__init__.py +0 -0
  89. {dnd5e_engine-0.3.0 → dnd5e_engine-0.3.2}/src/dnd5e_engine/types/dice.py +0 -0
  90. {dnd5e_engine-0.3.0 → dnd5e_engine-0.3.2}/tests/__init__.py +0 -0
  91. {dnd5e_engine-0.3.0 → dnd5e_engine-0.3.2}/tests/activities/__init__.py +0 -0
  92. {dnd5e_engine-0.3.0 → dnd5e_engine-0.3.2}/tests/activities/test_build_context.py +0 -0
  93. {dnd5e_engine-0.3.0 → dnd5e_engine-0.3.2}/tests/activities/test_cast_level_override.py +0 -0
  94. {dnd5e_engine-0.3.0 → dnd5e_engine-0.3.2}/tests/activities/test_monster_actions.py +0 -0
  95. {dnd5e_engine-0.3.0 → dnd5e_engine-0.3.2}/tests/e2e/__init__.py +0 -0
  96. {dnd5e_engine-0.3.0 → dnd5e_engine-0.3.2}/tests/e2e/conftest.py +0 -0
  97. {dnd5e_engine-0.3.0 → dnd5e_engine-0.3.2}/tests/e2e/harness.py +0 -0
  98. {dnd5e_engine-0.3.0 → dnd5e_engine-0.3.2}/tests/e2e/test_c00_harness_sanity.py +0 -0
  99. {dnd5e_engine-0.3.0 → dnd5e_engine-0.3.2}/tests/e2e/test_c01_dataset_fixes.py +0 -0
  100. {dnd5e_engine-0.3.0 → dnd5e_engine-0.3.2}/tests/e2e/test_c02_small_mechanics.py +0 -0
  101. {dnd5e_engine-0.3.0 → dnd5e_engine-0.3.2}/tests/e2e/test_c03_effect_modes.py +0 -0
  102. {dnd5e_engine-0.3.0 → dnd5e_engine-0.3.2}/tests/e2e/test_c04_caster_plumbing.py +0 -0
  103. {dnd5e_engine-0.3.0 → dnd5e_engine-0.3.2}/tests/e2e/test_c05_spatial.py +0 -0
  104. {dnd5e_engine-0.3.0 → dnd5e_engine-0.3.2}/tests/e2e/test_c06_reactions.py +0 -0
  105. {dnd5e_engine-0.3.0 → dnd5e_engine-0.3.2}/tests/e2e/test_c07_sneak_and_repertoire.py +0 -0
  106. {dnd5e_engine-0.3.0 → dnd5e_engine-0.3.2}/tests/e2e/test_c08_passive_projection.py +0 -0
  107. {dnd5e_engine-0.3.0 → dnd5e_engine-0.3.2}/tests/e2e/test_c09_rest.py +0 -0
  108. {dnd5e_engine-0.3.0 → dnd5e_engine-0.3.2}/tests/e2e/test_c10_monster_behavior.py +0 -0
  109. {dnd5e_engine-0.3.0 → dnd5e_engine-0.3.2}/tests/e2e/test_c11_item_charges.py +0 -0
  110. {dnd5e_engine-0.3.0 → dnd5e_engine-0.3.2}/tests/test_active_effect_resistance_fold.py +0 -0
  111. {dnd5e_engine-0.3.0 → dnd5e_engine-0.3.2}/tests/test_active_effect_schema.py +0 -0
  112. {dnd5e_engine-0.3.0 → dnd5e_engine-0.3.2}/tests/test_apply_modifiers.py +0 -0
  113. {dnd5e_engine-0.3.0 → dnd5e_engine-0.3.2}/tests/test_build_context_abilities.py +0 -0
  114. {dnd5e_engine-0.3.0 → dnd5e_engine-0.3.2}/tests/test_build_party.py +0 -0
  115. {dnd5e_engine-0.3.0 → dnd5e_engine-0.3.2}/tests/test_build_party_passives.py +0 -0
  116. {dnd5e_engine-0.3.0 → dnd5e_engine-0.3.2}/tests/test_combat_flag_advantage.py +0 -0
  117. {dnd5e_engine-0.3.0 → dnd5e_engine-0.3.2}/tests/test_concentration_drop_effect_name.py +0 -0
  118. {dnd5e_engine-0.3.0 → dnd5e_engine-0.3.2}/tests/test_concentration_repeat_save_identity.py +0 -0
  119. {dnd5e_engine-0.3.0 → dnd5e_engine-0.3.2}/tests/test_cover_bonus_sidecar.py +0 -0
  120. {dnd5e_engine-0.3.0 → dnd5e_engine-0.3.2}/tests/test_coverage_backfill_death_saves.py +0 -0
  121. {dnd5e_engine-0.3.0 → dnd5e_engine-0.3.2}/tests/test_coverage_backfill_seam_errors.py +0 -0
  122. {dnd5e_engine-0.3.0 → dnd5e_engine-0.3.2}/tests/test_duration_tick_at_turn_end.py +0 -0
  123. {dnd5e_engine-0.3.0 → dnd5e_engine-0.3.2}/tests/test_effects_builder_internals.py +0 -0
  124. {dnd5e_engine-0.3.0 → dnd5e_engine-0.3.2}/tests/test_enchantment_sidecar_projection.py +0 -0
  125. {dnd5e_engine-0.3.0 → dnd5e_engine-0.3.2}/tests/test_end_combat_snapshot.py +0 -0
  126. {dnd5e_engine-0.3.0 → dnd5e_engine-0.3.2}/tests/test_expire_clears_active_conditions.py +0 -0
  127. {dnd5e_engine-0.3.0 → dnd5e_engine-0.3.2}/tests/test_feature_save_dc_no_spell_override.py +0 -0
  128. {dnd5e_engine-0.3.0 → dnd5e_engine-0.3.2}/tests/test_get_actor_active_effects.py +0 -0
  129. {dnd5e_engine-0.3.0 → dnd5e_engine-0.3.2}/tests/test_grid_topology.py +0 -0
  130. {dnd5e_engine-0.3.0 → dnd5e_engine-0.3.2}/tests/test_iter13_fixes.py +0 -0
  131. {dnd5e_engine-0.3.0 → dnd5e_engine-0.3.2}/tests/test_iter5_fixes.py +0 -0
  132. {dnd5e_engine-0.3.0 → dnd5e_engine-0.3.2}/tests/test_lib_loader.py +0 -0
  133. {dnd5e_engine-0.3.0 → dnd5e_engine-0.3.2}/tests/test_live_combat_view.py +0 -0
  134. {dnd5e_engine-0.3.0 → dnd5e_engine-0.3.2}/tests/test_monster_dash.py +0 -0
  135. {dnd5e_engine-0.3.0 → dnd5e_engine-0.3.2}/tests/test_monster_flee_retreat.py +0 -0
  136. {dnd5e_engine-0.3.0 → dnd5e_engine-0.3.2}/tests/test_orchestrator_gating_typed.py +0 -0
  137. {dnd5e_engine-0.3.0 → dnd5e_engine-0.3.2}/tests/test_orchestrator_grid_combat.py +0 -0
  138. {dnd5e_engine-0.3.0 → dnd5e_engine-0.3.2}/tests/test_orchestrator_item_charges.py +0 -0
  139. {dnd5e_engine-0.3.0 → dnd5e_engine-0.3.2}/tests/test_orchestrator_monster_typed.py +0 -0
  140. {dnd5e_engine-0.3.0 → dnd5e_engine-0.3.2}/tests/test_orchestrator_move_mark_typed.py +0 -0
  141. {dnd5e_engine-0.3.0 → dnd5e_engine-0.3.2}/tests/test_party_member_reach_ft.py +0 -0
  142. {dnd5e_engine-0.3.0 → dnd5e_engine-0.3.2}/tests/test_passive_stats.py +0 -0
  143. {dnd5e_engine-0.3.0 → dnd5e_engine-0.3.2}/tests/test_phase6_public_surface.py +0 -0
  144. {dnd5e_engine-0.3.0 → dnd5e_engine-0.3.2}/tests/test_rage_second_wind_e2e.py +0 -0
  145. {dnd5e_engine-0.3.0 → dnd5e_engine-0.3.2}/tests/test_reaction_queue.py +0 -0
  146. {dnd5e_engine-0.3.0 → dnd5e_engine-0.3.2}/tests/test_resolve_check_flag_advantage.py +0 -0
  147. {dnd5e_engine-0.3.0 → dnd5e_engine-0.3.2}/tests/test_resolve_check_specific_buckets.py +0 -0
  148. {dnd5e_engine-0.3.0 → dnd5e_engine-0.3.2}/tests/test_rest.py +0 -0
  149. {dnd5e_engine-0.3.0 → dnd5e_engine-0.3.2}/tests/test_rules_smoke.py +0 -0
  150. {dnd5e_engine-0.3.0 → dnd5e_engine-0.3.2}/tests/test_scale_resolver.py +0 -0
  151. {dnd5e_engine-0.3.0 → dnd5e_engine-0.3.2}/tests/test_smoke.py +0 -0
  152. {dnd5e_engine-0.3.0 → dnd5e_engine-0.3.2}/tests/test_sneak_attack_resolver.py +0 -0
  153. {dnd5e_engine-0.3.0 → dnd5e_engine-0.3.2}/tests/test_start_combat_active_effects.py +0 -0
  154. {dnd5e_engine-0.3.0 → dnd5e_engine-0.3.2}/tests/test_start_combat_seeds_lifecycle.py +0 -0
  155. {dnd5e_engine-0.3.0 → dnd5e_engine-0.3.2}/tests/test_types_smoke.py +0 -0
  156. {dnd5e_engine-0.3.0 → dnd5e_engine-0.3.2}/tests/test_use_feature_intent.py +0 -0
  157. {dnd5e_engine-0.3.0 → dnd5e_engine-0.3.2}/tests/test_weapon_damage_bonus_sidecar.py +0 -0
@@ -7,6 +7,60 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.3.2]
11
+
12
+ Lockstep release with `dnd5e-srd-data` 0.3.2 and `nat20-bridge` 0.3.2.
13
+
14
+ Additive maturity pass from an independent review of the packages as a
15
+ standalone engine. **No public name is removed or changed shape.**
16
+
17
+ ### Fixed
18
+
19
+ - **Multiattack fan-out resolved the wrong attack mix for most monsters.** The
20
+ prose parser only matched `[[/item .id]]{label}`, but the corpus mostly ships
21
+ `[[/item Name]]` or bare mnemonic ids, so 174 of 180 multiattacks fell back to
22
+ "repeat one sibling N times" — silently wrong for every heterogeneous
23
+ multiattacker. The parser now reads per-token counts from the multiattack's
24
+ first sentence (excluding "It can replace one attack with …" riders) and
25
+ recovers names from Foundry's mnemonic ids. Precise joins: **6/180 → 119/180**.
26
+ Chuul, Otyugh, Cloaker, Unicorn, Mummy, Pit Fiend, Xorn and Marilith now make
27
+ their correct attack sequences. **Behaviour change for consumers:** affected
28
+ monsters deal their correct (higher) damage per round and consume more of the
29
+ dice stream, so combat transcripts seeded against 0.3.x will diverge.
30
+ - `__version__` now derives from installed package metadata instead of a
31
+ hand-maintained literal, so it cannot drift from `pyproject.toml` (0.3.0 shipped
32
+ reporting `0.2.0`). Pinned by `tests/test_determinism_contract.py`.
33
+
34
+ ### Added
35
+
36
+ - **`CheckSpec.rng`** — `resolve_check` previously drew its d20 from the
37
+ process-global `random` module with no way to inject a generator,
38
+ contradicting the documented determinism guarantee. Pass a seeded
39
+ `random.Random` for a reproducible standalone check; `None` preserves the old
40
+ behaviour. `rules.dice`, `rules.skills` and `roll_dice_str` /
41
+ `apply_changes_to_check` gain matching optional `rng` parameters. Combat
42
+ resolution was already seeded and is unchanged.
43
+ - **`docs/capabilities.md`** — a per-mechanic matrix of what the engine
44
+ resolves, what loads but produces no events, and what is not modelled. Its
45
+ published counts are recomputed from the corpus by
46
+ `tests/test_capability_matrix.py`.
47
+ - Concept pages for reactions (the pre-armed model) and monsters (the built-in
48
+ AI, multiattack join rate, absent legendary actions).
49
+ - `tests/test_docstrings_are_host_agnostic.py` — fails the build on a docstring
50
+ that names a private downstream application, retired tooling, or a doc path
51
+ that does not exist in this repo. 104 such references were rendered onto the
52
+ public API site; all removed.
53
+
54
+ ### Changed
55
+
56
+ - `CONDITION_EFFECTS[EXHAUSTION]` / `[PRONE]` descriptive strings corrected from
57
+ 2014 to SRD 5.2 wording and labelled as not-enforced; the enforced exhaustion
58
+ rule is still the 2014 one (tracked in `BACKLOG.md`).
59
+ - Sphinx cross-reference roles in docstrings, which rendered as literal markup
60
+ on the API site, converted to inline code.
61
+ - Monkeypatching `roll_dice_str` now requires a two-parameter double
62
+ (`lambda expr, rng=None: …`).
63
+
10
64
  ## [0.3.0]
11
65
 
12
66
  Lockstep release with `dnd5e-srd-data` 0.3.0 — the item charge lifecycle: gate
@@ -12,7 +12,7 @@ test:
12
12
  uv run pytest -q
13
13
  # Baseline-and-ratchet coverage gate (75% observed today; floor set 2pts under).
14
14
  coverage:
15
- uv run pytest -q --cov=dnd5e_engine --cov-report=term-missing --cov-fail-under=73
15
+ uv run pytest -q --cov=dnd5e_engine --cov-report=term-missing --cov-fail-under=90
16
16
  security:
17
17
  uv run bandit -r src/ -c pyproject.toml -q
18
18
  smoke:
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.4
1
+ Metadata-Version: 2.5
2
2
  Name: dnd5e-engine
3
- Version: 0.3.0
3
+ Version: 0.3.2
4
4
  Summary: Pure-Python D&D 5e SRD rules engine — combat, checks, effects. Host-agnostic, zero I/O.
5
5
  Author: Tapestria contributors
6
6
  License: MIT License
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "dnd5e-engine"
7
- version = "0.3.0"
7
+ version = "0.3.2"
8
8
  description = "Pure-Python D&D 5e SRD rules engine — combat, checks, effects. Host-agnostic, zero I/O."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.12"
@@ -1,19 +1,31 @@
1
- """dnd5e-engine — host-agnostic D&D 5e SRD rules engine.
1
+ """dnd5e-engine — a host-agnostic, zero-I/O D&D 5e SRD 5.2 rules engine.
2
2
 
3
- Public API per docs/superpowers/specs/2026-05-26-dnd5e-engine-extraction-design.md.
3
+ Everything exported here is listed in ``__all__`` below; that list is the
4
+ package's stable contract and is pinned by ``tests/test_public_api_surface.py``.
4
5
 
5
- Asset loading runs through ``dnd5e_srd_data.BundledAssetLoader`` (the typed
6
- 2024-SRD corpus), wired via :mod:`dnd5e_engine.lib_loader`.
6
+ The four combat-loop coroutines are ``start_combat``, ``submit_player_intent``,
7
+ ``advance_monster_turn`` and ``end_combat``; ``resolve_check`` is the standalone
8
+ ability/skill/saving-throw resolver, and ``build_party_member`` turns a
9
+ ``CharacterBuildSpec`` into a combat-ready ``PartyMemberSpec``.
7
10
 
8
- Deferred for later phases:
9
- - roll (ad-hoc dice) — Phase 7
10
- - get_state_snapshot / list_active_handles (diagnostic introspection)
11
- — not yet implemented in orchestrator; will land alongside Phase 5 or 6.
11
+ Rules content is never bundled with the engine. It is read at runtime from the
12
+ companion ``dnd5e-srd-data`` package; call ``configure_lib_loader`` to
13
+ substitute your own typed corpus.
14
+
15
+ See the ``docs/`` tree (published at https://tapestria.github.io/nat20/) for the
16
+ combat model, the activity corpus, and a per-mechanic capability matrix
17
+ describing exactly which SRD rules this engine resolves today.
12
18
  """
13
19
 
14
20
  from __future__ import annotations
15
21
 
16
- __version__ = "0.2.0"
22
+ from importlib.metadata import PackageNotFoundError
23
+ from importlib.metadata import version as _pkg_version
24
+
25
+ try: # pragma: no cover - trivial packaging fallback
26
+ __version__ = _pkg_version("dnd5e-engine")
27
+ except PackageNotFoundError: # pragma: no cover - source tree without install
28
+ __version__ = "0.0.0+unknown"
17
29
 
18
30
  from dnd5e_engine.build_party import build_party_member
19
31
  from dnd5e_engine.build_spec import (
@@ -24,6 +36,7 @@ from dnd5e_engine.build_spec import (
24
36
  )
25
37
  from dnd5e_engine.check import CheckKind, CheckResult, CheckSpec, resolve_check
26
38
  from dnd5e_engine.events import CombatEvent, IntentType
39
+ from dnd5e_engine.lib_loader import configure_lib_loader
27
40
  from dnd5e_engine.orchestrator import (
28
41
  CombatHandle,
29
42
  LiveCombatView,
@@ -101,6 +114,7 @@ __all__ = [
101
114
  "advance_monster_turn",
102
115
  "build_party_member",
103
116
  "cell_id",
117
+ "configure_lib_loader",
104
118
  "end_combat",
105
119
  "get_actor_active_effects",
106
120
  "get_live",
@@ -1,6 +1,6 @@
1
1
  """Typed Foundry Activity resolver.
2
2
 
3
- Parallel to the Avrae-IR ``effects/`` package: consumes the typed
3
+ Parallel to the legacy ``effects/`` package: consumes the typed
4
4
  ``dnd5e_srd_data`` ``Activity`` discriminated union and emits the engine's
5
5
  existing ``CombatEvent`` union via an ``event_emitter`` callback.
6
6
  """
@@ -4,7 +4,7 @@ target-side vulnerability / resistance / immunity, emit ``DamageApplied``.
4
4
  MIRRORS, does not import from, ``effects/damage.py``:
5
5
 
6
6
  * Apply order is vulnerability ×2 → resistance //2 (integer floor) → immunity ⇒ 0,
7
- matching ``_apply_resistance`` (Avrae's ``do_resistances`` order). An ``"all"``
7
+ matching ``_apply_resistance`` (the legacy ``do_resistances`` order). An ``"all"``
8
8
  wildcard is honored in each list (SRD §Conditions/Petrified emits "all").
9
9
  * ``is_overkill`` mirrors ``effects/damage.py:192`` — ``final_amount >
10
10
  target.hp_current`` (strictly greater).
@@ -88,7 +88,7 @@ def _apply_modifiers(
88
88
  immunities: set[str],
89
89
  vulnerabilities: set[str],
90
90
  ) -> int:
91
- """Apply vuln (×2) → resist (//2 floor) → immune (⇒0) in Avrae order.
91
+ """Apply vuln (×2) → resist (//2 floor) → immune (⇒0) in the legacy evaluator order.
92
92
 
93
93
  Mirrors ``effects/damage.py:_apply_resistance``. The ``"all"`` wildcard is
94
94
  honored in each list (SRD §Conditions/Petrified resistance-to-all).
@@ -106,7 +106,7 @@ def resolve_attack(
106
106
 
107
107
  # SRD §Advantage and Disadvantage — read the attacker's own
108
108
  # ``flags.advantage.attack`` / ``flags.disadvantage.attack`` override
109
- # changes (both present cancel to normal, Avrae ``reconcile_adv``). These
109
+ # changes (both present cancel to normal, the legacy evaluator ``reconcile_adv``). These
110
110
  # gate the SRD §Sneak Attack trigger below; they deliberately do NOT change
111
111
  # the natural-d20 ``mode`` on this live path. Doing so would shift the
112
112
  # seeded dice stream and the crit outcome of the base attack, perturbing
@@ -127,7 +127,7 @@ def resolve_attack(
127
127
  # target's EFFECTIVE AC for this attack only; total cover is filtered
128
128
  # upstream (the target is never reachable as a resolver target at all).
129
129
  # SRD Shield — "+5 bonus to AC, including against the triggering
130
- # attack" (C06-S03): a pre-armed reaction can land an AC-bonus active
130
+ # attack" a pre-armed reaction can land an AC-bonus active
131
131
  # effect on the target between the trigger and this comparison; the
132
132
  # natural roll itself is never touched, only this comparison.
133
133
  effective_ac = (
@@ -193,7 +193,7 @@ def attacker_advantage_flags(ctx: ActivityResolutionContext) -> tuple[bool, bool
193
193
  has_advantage = True
194
194
  elif ch.key == "flags.disadvantage.attack":
195
195
  has_disadvantage = True
196
- # Both present cancel to normal (Avrae ``reconcile_adv``).
196
+ # Both present cancel to normal (the legacy evaluator ``reconcile_adv``).
197
197
  if has_advantage and has_disadvantage:
198
198
  return False, False
199
199
  return has_advantage, has_disadvantage
@@ -290,7 +290,7 @@ def _is_ranged_weapon(weapon: Weapon | None) -> bool:
290
290
  """True iff ``weapon`` is a ranged weapon (Foundry rwak scope).
291
291
 
292
292
  The ranged analog of ``_is_melee_weapon`` — the scope of
293
- ``system.bonuses.rwak.damage`` (C04-S03).
293
+ ``system.bonuses.rwak.damage`` .
294
294
  """
295
295
  return weapon is not None and weapon.weapon_category in _RANGED_CATEGORIES
296
296
 
@@ -311,7 +311,7 @@ def _weapon_default_ability(weapon: Weapon, ctx: ActivityResolutionContext) -> s
311
311
  """SRD default attack/damage ability for a weapon with no explicit ability.
312
312
 
313
313
  * ranged weapon (``weapon_category`` in the ranged set) → DEX.
314
- * finesse weapon (the ``finesse`` :class:`WeaponProperty`) → the better of the
314
+ * finesse weapon (the ``finesse`` ``WeaponProperty``) → the better of the
315
315
  caster's STR/DEX modifier (SRD §Finesse: the wielder chooses).
316
316
  * otherwise (melee non-finesse) → STR.
317
317
 
@@ -482,7 +482,7 @@ def _apply_on_hit_damage(
482
482
  if melee_bonus_expr:
483
483
  by_type[first_type] += roll_expr(melee_bonus_expr, ctx.rng)
484
484
 
485
- # C04-S03 / Foundry ``system.bonuses.rwak.damage`` — a ranged weapon
485
+ # / Foundry ``system.bonuses.rwak.damage`` — a ranged weapon
486
486
  # attack damage bonus, the ranged analog of the melee-only bonus just
487
487
  # above. Add it once to the first damage type, RANGED WEAPON only.
488
488
  if first_type is not None and _is_ranged_weapon(weapon):
@@ -1,49 +1,13 @@
1
- """Build an :class:`ActivityResolutionContext` from live combat state.
1
+ """Build an ``ActivityResolutionContext`` from live combat state.
2
2
 
3
- The integration crux of the Avrae→Foundry resolver cutover (Task 2 of
4
- ``docs/superpowers/plans/2026-06-03-bundled-asset-loader-cutover-plan.md``).
3
+ This is the bridge between the orchestrator's mutable ``_LiveCombat`` and the
4
+ pure activity resolvers. It reads the caster's real six ability scores,
5
+ level-derived proficiency bonus, spellcasting ability and ``@scale`` values,
6
+ folds in the passive attack/damage/save/check modifiers projected from active
7
+ effects and conditions, and hands the resolvers a single immutable context.
5
8
 
6
- The new typed resolver consumes :class:`ActivityResolutionContext`. The OLD
7
- Avrae path faked caster magnitudes off the narrow :class:`Combatant`
8
- (``attack_bonus``, ``dexterity`` only — no per-ability scores, proficiency,
9
- or spellcasting ability). A Character caster's real six-ability scores +
10
- ``character_level``-derived proficiency bonus have since landed (piece 4)
11
- and, per Cluster 4 (C04-S01), its save DC now runs the honest SRD 5.2
12
- formula (``8 + proficiency bonus + spellcasting-ability mod``, ``_save_dc``
13
- below) whenever a spellcasting ability resolves. The OLD flat
14
- approximations below are the byte-for-byte FALLBACK for the paths that still
15
- have no better ground truth:
16
-
17
- * PC fallback (no resolvable spellcasting ability — e.g. a non-caster class,
18
- or a ``use_item`` cast, which never sets one) mirrors
19
- ``intent_resolver._spellcasting_mod`` / ``_proficiency_bonus`` /
20
- ``_spell_save_dc``: ``mod = max(0, attack_bonus-2)``, ``pb = 2``,
21
- ``save_dc = 8 + pb + mod``.
22
- * Monster magnitudes (unaffected by Cluster 4 — no monster stat block threads
23
- a per-ability sheet through here) mirror ``monster_ai._monster_save_dc``:
24
- ``mod = attack_bonus``, ``save_dc = 8 + attack_bonus``.
25
-
26
- ``caster_abilities`` is set uniformly across all six abilities to ``10 +
27
- 2*mod`` FOR A MONSTER caster so the attack/damage handler — which resolves
28
- the governing ability *dynamically* (explicit ``attack.ability`` → weapon
29
- SRD default → finesse better-of-str/dex → spellcasting ability) — yields the
30
- old uniform ``@mod`` regardless of which ability it picks. A Character
31
- caster's ``caster_abilities`` are its real six scores (see below).
32
-
33
- This module is PURE: no I/O, no orchestrator import, no double-compute. The
34
- orchestrator already builds the per-entity passive sidecars (its
35
- ``_build_hydration_payload`` projection) and passes the two dicts this builder
36
- needs — ``passive_damage_modifiers`` and ``save_modifiers`` — in as parameters at
37
- the Task 5/6 cutover. This builder only RESHAPES them into the typed context:
38
-
39
- * ``save_modifiers[id]["saves"]`` → ``passive_save_modifiers[id]`` ({ability:int})
40
- * ``save_modifiers[id]["passive_save_bonus"]`` (signed dice string) → carried verbatim
41
- * ``save_modifiers[id]["passive_save_adv"/"_dis"/"_auto_fail"]`` (UPPER-case
42
- ability-code lists) → carried verbatim
43
-
44
- so the typed save path (``save_primitive.roll_save``) consumes the SAME bless/bane
45
- bonus, advantage/disadvantage, and auto-fail the OLD Avrae ``effects/save.py``
46
- path did. Empty / absent sidecar fields reproduce the prior behavior exactly.
9
+ The resolvers never touch live combat state directly — everything they need
10
+ arrives on the context, which is what keeps them pure and testable.
47
11
  """
48
12
 
49
13
  from __future__ import annotations
@@ -99,7 +63,7 @@ def _save_dc(
99
63
  intent such as ``use_item``, which never sets ``spellcasting_ability``)
100
64
  fall back to the OLD flat approximation byte-for-byte: Monster
101
65
  ``8 + attack_bonus`` (``_monster_save_dc``); PC ``8 + 2 + mod`` (the
102
- Avrae-era ``_spell_save_dc``, ``pb`` hardcoded to ``2``).
66
+ the legacy evaluator-era ``_spell_save_dc``, ``pb`` hardcoded to ``2``).
103
67
  """
104
68
  if caster.entity_type == "Monster":
105
69
  return 8 + caster.attack_bonus
@@ -154,9 +118,9 @@ def build_activity_context(
154
118
  sneak_attack_ally_adjacent: dict[str, bool] | None = None,
155
119
  ) -> ActivityResolutionContext:
156
120
  """Adapt the caster + the pre-computed hydration sidecars into the typed
157
- :class:`ActivityResolutionContext` the new resolver consumes.
121
+ ``ActivityResolutionContext`` the new resolver consumes.
158
122
 
159
- Caster magnitudes reproduce the OLD Avrae approximations (see module
123
+ Caster magnitudes reproduce the OLD the legacy evaluator approximations (see module
160
124
  docstring). ``passive_damage_modifiers`` and ``save_modifiers`` are the two
161
125
  dicts the orchestrator's ``_build_hydration_payload`` already produces —
162
126
  passed IN so this builder stays pure (no orchestrator import, no I/O, no
@@ -188,7 +152,7 @@ def build_activity_context(
188
152
  path keeps the blanket override.
189
153
 
190
154
  ``cast_level_override`` passes straight through to
191
- :class:`ActivityResolutionContext` — a ``use_item`` charges_to_spend
155
+ ``ActivityResolutionContext`` — a ``use_item`` charges_to_spend
192
156
  upcast's forced delegated-cast level (orchestrator-computed off the
193
157
  charge gate's own cost accounting). ``None`` (the default) leaves
194
158
  ``resolve_cast`` (``activities/cast.py``) to fall back to the wrapper
@@ -209,7 +173,7 @@ def build_activity_context(
209
173
  }
210
174
  caster_proficiency_bonus = proficiency_bonus(caster.character_level)
211
175
  else:
212
- # Monsters/NPCs have no per-ability sheet here: keep the Avrae-era
176
+ # Monsters/NPCs have no per-ability sheet here: keep the legacy evaluator-era
213
177
  # uniform fake (10 + 2*mod across all six abilities, PB 2) so the
214
178
  # dynamic governing-ability resolution in attack.py reproduces the old
215
179
  # uniform @mod for any ability it picks.
@@ -253,7 +217,7 @@ def build_activity_context(
253
217
  for entity_id, dmg_entry in passive_damage_modifiers.items():
254
218
  # ``passive_damage_modifiers`` is a WIDE dict: resistance/immunity/
255
219
  # vulnerability lists PLUS the signed-dice ``passive_to_hit_bonus`` STRING
256
- # the orchestrator projection wedges in (mirrors the OLD Avrae sidecar).
220
+ # the orchestrator projection wedges in (mirrors the OLD the legacy evaluator sidecar).
257
221
  to_hit: object = dmg_entry.get("passive_to_hit_bonus")
258
222
  if isinstance(to_hit, str) and to_hit:
259
223
  passive_attack_bonus[entity_id] = to_hit
@@ -8,7 +8,7 @@ level, the referenced spell's base level / concentration flag / own
8
8
  ``resolve_activity`` for each activity on the referenced spell. ``cast`` emits
9
9
  NOTHING itself — the delegated child activities emit their own events.
10
10
 
11
- MIRRORS, does not import from, ``effects/spell.py`` (the Avrae-IR ``type:
11
+ MIRRORS, does not import from, ``effects/spell.py`` (the legacy ``type:
12
12
  "spell"`` analogue). Reproduces its semantics with the typed lib shapes:
13
13
 
14
14
  * Spell-within-itself cycle guard FIRST (cheap, before the lookup): a ``uuid``
@@ -20,7 +20,7 @@ MIRRORS, does not import from, ``effects/check.py``:
20
20
  * The skill→ability mapping mirrors ``effects/check.py:_SKILL_TO_ABILITY`` but is
21
21
  keyed by the Foundry 3-letter skill *codes* the canonical ``check.associated``
22
22
  field actually carries (``"ath"``, ``"slt"``, ``"inv"``, ...) rather than the
23
- Avrae long-form (``"athletics"``). Source: ``CONFIG.DND5E.skills`` in
23
+ the legacy evaluator long-form (``"athletics"``). Source: ``CONFIG.DND5E.skills`` in
24
24
  ``foundry/module/config.mjs``.
25
25
  * The modifier is the RESOLVED integer off a per-actor sidecar
26
26
  (``ctx.check_modifiers``), mirroring ``effects/check.py:_read_check_modifiers``
@@ -67,7 +67,7 @@ _ABILITIES: Final[frozenset[str]] = frozenset(get_args(Ability))
67
67
  # Foundry 3-letter skill code → governing SRD ability. Source: CONFIG.DND5E.skills
68
68
  # in foundry/module/config.mjs (each skill's ``ability`` field). The canonical
69
69
  # ``check.associated`` field carries these codes verbatim. Mirrors
70
- # ``effects/check.py:_SKILL_TO_ABILITY`` (which keys the Avrae long-form names).
70
+ # ``effects/check.py:_SKILL_TO_ABILITY`` (which keys the legacy evaluator long-form names).
71
71
  _SKILL_TO_ABILITY: Final[dict[str, Ability]] = {
72
72
  "acr": "dex",
73
73
  "ani": "wis",
@@ -76,7 +76,7 @@ class ActivityResolutionContext:
76
76
  passive_damage_modifiers: dict[str, dict[str, list[str]]] = field(default_factory=dict)
77
77
  # Per-target saving-throw modifier sidecar, keyed entity_id -> {ability ->
78
78
  # resolved save bonus}. Mirrors how ``effects/save.py`` sources the target
79
- # save modifier (``effect_store._save_modifiers[target_id]["saves"][ability]``)
79
+ # save modifier (``the host effect store._save_modifiers[target_id]["saves"][ability]``)
80
80
  # — the resolved per-ability integer, NOT rebuilt from ability score +
81
81
  # proficiency. ``Combatant`` carries no per-ability save table, so the save
82
82
  # handler reads this sidecar; an absent target / ability contributes +0
@@ -85,8 +85,8 @@ class ActivityResolutionContext:
85
85
  passive_save_modifiers: dict[str, dict[str, int]] = field(default_factory=dict)
86
86
  # Per-target additive save bonus, keyed entity_id -> a signed dice-expression
87
87
  # STRING (e.g. ``"+1d4"`` for Bless, ``"-1d4"`` for Bane; stacked sources
88
- # pre-joined as ``"a + b"``). Mirrors the OLD Avrae path's
89
- # ``effect_store._save_modifiers[id]["passive_save_bonus"]`` (orchestrator
88
+ # pre-joined as ``"a + b"``). Mirrors the OLD the legacy evaluator path's
89
+ # ``the host effect store._save_modifiers[id]["passive_save_bonus"]`` (orchestrator
90
90
  # hydration, ``_build_hydration_payload``); rolled through ``ctx.rng`` so the
91
91
  # bless/bane d4 lands in the same seed stream as the save d20. Absent target →
92
92
  # +0 (no bonus). Empty default keeps the golden corpus identical.
@@ -148,7 +148,7 @@ class ActivityResolutionContext:
148
148
  # ``"STR"``, ``"DEX"``, ...), keyed entity_id -> list[ability]. Mirrors the
149
149
  # OLD path's ``passive_save_adv`` / ``passive_save_dis`` (Faerie Fire,
150
150
  # Restrained, etc.). An ability present in both cancels to normal
151
- # (Avrae ``reconcile_adv``). Empty defaults keep the golden corpus identical.
151
+ # (the legacy evaluator ``reconcile_adv``). Empty defaults keep the golden corpus identical.
152
152
  passive_save_adv: dict[str, list[str]] = field(default_factory=dict)
153
153
  passive_save_dis: dict[str, list[str]] = field(default_factory=dict)
154
154
  # Per-target auto-fail ability-code list (UPPER-case), keyed entity_id ->
@@ -176,7 +176,7 @@ class ActivityResolutionContext:
176
176
  # ``_fold_active_effect_changes`` + extracted in ``build_activity_context``);
177
177
  # consumed in ``attack.py`` alongside the cover-AC fold, before the
178
178
  # ``total >= target_ac`` comparison. Absent target -> +0. Empty default
179
- # keeps the golden corpus identical (C06-S03).
179
+ # keeps the golden corpus identical .
180
180
  passive_ac_bonus: dict[str, int] = field(default_factory=dict)
181
181
  # Per-actor ability/skill-check modifier sidecar, mirroring
182
182
  # ``effects/check.py:_read_check_modifiers``'s shape
@@ -6,7 +6,7 @@ SRD 5.2 corpus: a single Magic Missile dart (``1d4+1`` force), or Divine Smite's
6
6
  radiant burst (``2d8``/``3d8`` radiant) nested under a melee hit.
7
7
 
8
8
  Crit rule (``DamageActivityDamageBlock.critical`` is a
9
- :class:`SaveDamageCriticalBlock` — ``{allow, bonus}``): this activity crits ONLY
9
+ ``SaveDamageCriticalBlock`` — ``{allow, bonus}``): this activity crits ONLY
10
10
  when ``critical.allow`` is True AND ``ctx.variables["in_crit"]`` is truthy (the
11
11
  caller sets ``in_crit`` when the activity is nested under a crit attack). On crit
12
12
  the dice double (``roll_damage_part(crit=True)``) and the parsed ``critical.bonus``
@@ -24,7 +24,7 @@ coercion). A typeless part (empty ``types`` — a DATA defect in a handful of
24
24
  SRD spells; e.g. Call Lightning's 4d10 part) is logged loudly
25
25
  (``damage_part_untyped``) and skipped: ``apply_damage`` keys strictly by SRD
26
26
  type and has no untyped bucket, and the resolver cannot invent the SRD-correct
27
- type. Tracked as a ``dnd5e-srd-data`` data bug in ``docs/BACKLOG.md``.
27
+ type. Tracked as a ``dnd5e-srd-data`` data bug in ``BACKLOG.md``.
28
28
 
29
29
  MIRRORS, does not import from, ``effects/damage.py``: @-token resolution runs
30
30
  through ``formula.resolve_damage_block`` (governing ability = the caster's
@@ -111,7 +111,7 @@ def _part_type(
111
111
  * Empty list: a DATA defect (the SRD part should carry a type). There is no
112
112
  untyped bucket in ``apply_damage``, so the part is logged loudly
113
113
  (``damage_part_untyped``) and skipped. Tracked as a ``dnd5e-srd-data`` bug
114
- (see ``docs/BACKLOG.md``); the resolver must not invent the SRD-correct type.
114
+ (see ``BACKLOG.md``); the resolver must not invent the SRD-correct type.
115
115
  """
116
116
  if not part.types:
117
117
  _LOGGER.warning(
@@ -302,7 +302,7 @@ def _cantrip_dice_count(character_level: int) -> int:
302
302
  def _double_dice(ast: d20.ast.Node) -> d20.ast.Node:
303
303
  """SRD §Critical Hits — double every ``Dice.num``, leave literals alone.
304
304
 
305
- Mirrors ``effects/damage.py:_apply_crit_doubling`` (Avrae's default
305
+ Mirrors ``effects/damage.py:_apply_crit_doubling`` (the legacy default
306
306
  ``crit_mapper``): ``d20.utils.tree_map`` rebuilds each ``Dice(num, size)``
307
307
  as ``Dice(num*2, size)``; numeric modifiers are untouched.
308
308
  """
@@ -228,7 +228,7 @@ def apply_activity_effects(
228
228
  # gated on the mapping.
229
229
  for status in sorted(pe.statuses):
230
230
  if status in _CONDITION_VALUES:
231
- # C08-S02: condition-immunity gate. A target immune to this
231
+ # condition-immunity gate. A target immune to this
232
232
  # condition (Nature's Ward → "poisoned") never has it attach —
233
233
  # the ConditionApplied is SUPPRESSED outright (the EffectApplied
234
234
  # rider above still fired narratively). Suppress, not
@@ -6,11 +6,11 @@ their dice/DC formula fields — e.g. Cure Wounds' healing bonus is verbatim
6
6
  read these tokens, so each one is replaced with its caster-derived integer (as a
7
7
  string) before the dice helper parses the formula.
8
8
 
9
- MIRRORS, does not import from, the old Avrae-IR path's
9
+ MIRRORS, does not import from, the old the legacy path's
10
10
  ``intent_resolver._walk_and_patch`` / ``_parse_with_injection``: that path
11
11
  injected the same caster magnitudes (ability mods, proficiency, spell DC) into
12
12
  formula strings before rolling. The caster's numbers live on
13
- :class:`ActivityResolutionContext`; the typed Activity only declares WHICH
13
+ ``ActivityResolutionContext``; the typed Activity only declares WHICH
14
14
  ability/DC-calc applies.
15
15
 
16
16
  Scope — the roll-data tokens that appear in canonical DICE/DC FORMULA fields
@@ -1,14 +1,14 @@
1
1
  """``heal`` kind handler for the Activity resolver.
2
2
 
3
3
  Foundry ``heal-data.mjs`` stores healing as a single ``DamagePartBlock``
4
- (:class:`HealActivity.healing`), not a list. The ``types`` list carries a
4
+ (``healing``), not a list. The ``types`` list carries a
5
5
  single token: ``"healing"`` for HP restoration, ``"temphp"`` for a temporary
6
6
  HP buffer — both confirmed against canonical SRD 5.2 data (cure-wounds.json
7
7
  emits ``"healing"``; false-life.json emits ``"temphp"``). When ``temphp`` is
8
- present we emit :class:`TempHpApplied`, otherwise :class:`HealingApplied`.
8
+ present we emit ``TempHpApplied``, otherwise ``HealingApplied``.
9
9
 
10
10
  MIRRORS, does not import from, ``effects/temphp.py`` for the temp-hp token
11
- distinction. The keep-higher replacement policy lives only on the Avrae-IR
11
+ distinction. The keep-higher replacement policy lives only on the legacy
12
12
  ``effects/`` path (which carries an existing-temp-HP lookup); the typed
13
13
  Activity resolver emits the rolled grant directly, matching how ``apply.py``
14
14
  emits the rolled magnitude for damage.