dnd5e-engine 0.1.0__tar.gz → 0.1.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 (116) hide show
  1. {dnd5e_engine-0.1.0 → dnd5e_engine-0.1.2}/CHANGELOG.md +14 -0
  2. dnd5e_engine-0.1.2/Makefile +19 -0
  3. {dnd5e_engine-0.1.0 → dnd5e_engine-0.1.2}/PKG-INFO +3 -1
  4. {dnd5e_engine-0.1.0 → dnd5e_engine-0.1.2}/pyproject.toml +30 -3
  5. {dnd5e_engine-0.1.0 → dnd5e_engine-0.1.2}/scripts/_smoke_grid_combat.py +4 -6
  6. {dnd5e_engine-0.1.0 → dnd5e_engine-0.1.2}/src/dnd5e_engine/__init__.py +7 -1
  7. {dnd5e_engine-0.1.0 → dnd5e_engine-0.1.2}/src/dnd5e_engine/activities/formula.py +1 -3
  8. {dnd5e_engine-0.1.0 → dnd5e_engine-0.1.2}/src/dnd5e_engine/orchestrator.py +1068 -774
  9. {dnd5e_engine-0.1.0 → dnd5e_engine-0.1.2}/src/dnd5e_engine/rules/combat.py +2 -8
  10. {dnd5e_engine-0.1.0 → dnd5e_engine-0.1.2}/src/dnd5e_engine/rules/effects.py +5 -5
  11. {dnd5e_engine-0.1.0 → dnd5e_engine-0.1.2}/src/dnd5e_engine/types/intent.py +5 -0
  12. dnd5e_engine-0.1.2/src/dnd5e_engine/views.py +60 -0
  13. {dnd5e_engine-0.1.0 → dnd5e_engine-0.1.2}/tests/test_apply_changes_to_check.py +3 -5
  14. {dnd5e_engine-0.1.0 → dnd5e_engine-0.1.2}/tests/test_build_party_passives.py +1 -3
  15. {dnd5e_engine-0.1.0 → dnd5e_engine-0.1.2}/tests/test_combat_bucket_keys.py +2 -3
  16. {dnd5e_engine-0.1.0 → dnd5e_engine-0.1.2}/tests/test_concentration_drop_effect_name.py +1 -3
  17. {dnd5e_engine-0.1.0 → dnd5e_engine-0.1.2}/tests/test_concentration_repeat_save_identity.py +3 -9
  18. {dnd5e_engine-0.1.0 → dnd5e_engine-0.1.2}/tests/test_enchantment_sidecar_projection.py +2 -6
  19. {dnd5e_engine-0.1.0 → dnd5e_engine-0.1.2}/tests/test_expire_clears_active_conditions.py +2 -4
  20. {dnd5e_engine-0.1.0 → dnd5e_engine-0.1.2}/tests/test_iter11_fixes.py +9 -29
  21. {dnd5e_engine-0.1.0 → dnd5e_engine-0.1.2}/tests/test_iter13_fixes.py +1 -2
  22. dnd5e_engine-0.1.2/tests/test_live_combat_view.py +91 -0
  23. {dnd5e_engine-0.1.0 → dnd5e_engine-0.1.2}/tests/test_orchestrator_pc_resolution_typed.py +44 -2
  24. {dnd5e_engine-0.1.0 → dnd5e_engine-0.1.2}/tests/test_passive_stats.py +6 -2
  25. {dnd5e_engine-0.1.0 → dnd5e_engine-0.1.2}/tests/test_phase6_public_surface.py +8 -0
  26. dnd5e_engine-0.1.2/tests/test_public_api_surface.py +93 -0
  27. {dnd5e_engine-0.1.0 → dnd5e_engine-0.1.2}/tests/test_rage_second_wind_e2e.py +1 -3
  28. {dnd5e_engine-0.1.0 → dnd5e_engine-0.1.2}/tests/test_resolve_check.py +6 -14
  29. {dnd5e_engine-0.1.0 → dnd5e_engine-0.1.2}/tests/test_resolve_check_flag_advantage.py +3 -15
  30. {dnd5e_engine-0.1.0 → dnd5e_engine-0.1.2}/tests/test_start_combat_active_effects.py +39 -35
  31. {dnd5e_engine-0.1.0 → dnd5e_engine-0.1.2}/tests/test_start_combat_seeds_lifecycle.py +1 -2
  32. {dnd5e_engine-0.1.0 → dnd5e_engine-0.1.2}/tests/test_types_smoke.py +5 -9
  33. dnd5e_engine-0.1.0/Makefile +0 -10
  34. dnd5e_engine-0.1.0/tests/test_public_api_surface.py +0 -47
  35. {dnd5e_engine-0.1.0 → dnd5e_engine-0.1.2}/.gitignore +0 -0
  36. {dnd5e_engine-0.1.0 → dnd5e_engine-0.1.2}/LICENSE +0 -0
  37. {dnd5e_engine-0.1.0 → dnd5e_engine-0.1.2}/NOTICE +0 -0
  38. {dnd5e_engine-0.1.0 → dnd5e_engine-0.1.2}/README.md +0 -0
  39. {dnd5e_engine-0.1.0 → dnd5e_engine-0.1.2}/scripts/smoke_clean_install.sh +0 -0
  40. {dnd5e_engine-0.1.0 → dnd5e_engine-0.1.2}/src/dnd5e_engine/activities/__init__.py +0 -0
  41. {dnd5e_engine-0.1.0 → dnd5e_engine-0.1.2}/src/dnd5e_engine/activities/apply.py +0 -0
  42. {dnd5e_engine-0.1.0 → dnd5e_engine-0.1.2}/src/dnd5e_engine/activities/attack.py +0 -0
  43. {dnd5e_engine-0.1.0 → dnd5e_engine-0.1.2}/src/dnd5e_engine/activities/build_context.py +0 -0
  44. {dnd5e_engine-0.1.0 → dnd5e_engine-0.1.2}/src/dnd5e_engine/activities/cast.py +0 -0
  45. {dnd5e_engine-0.1.0 → dnd5e_engine-0.1.2}/src/dnd5e_engine/activities/check.py +0 -0
  46. {dnd5e_engine-0.1.0 → dnd5e_engine-0.1.2}/src/dnd5e_engine/activities/context.py +0 -0
  47. {dnd5e_engine-0.1.0 → dnd5e_engine-0.1.2}/src/dnd5e_engine/activities/damage.py +0 -0
  48. {dnd5e_engine-0.1.0 → dnd5e_engine-0.1.2}/src/dnd5e_engine/activities/dice.py +0 -0
  49. {dnd5e_engine-0.1.0 → dnd5e_engine-0.1.2}/src/dnd5e_engine/activities/effects.py +0 -0
  50. {dnd5e_engine-0.1.0 → dnd5e_engine-0.1.2}/src/dnd5e_engine/activities/heal.py +0 -0
  51. {dnd5e_engine-0.1.0 → dnd5e_engine-0.1.2}/src/dnd5e_engine/activities/mastery.py +0 -0
  52. {dnd5e_engine-0.1.0 → dnd5e_engine-0.1.2}/src/dnd5e_engine/activities/monster_actions.py +0 -0
  53. {dnd5e_engine-0.1.0 → dnd5e_engine-0.1.2}/src/dnd5e_engine/activities/passive_stats.py +0 -0
  54. {dnd5e_engine-0.1.0 → dnd5e_engine-0.1.2}/src/dnd5e_engine/activities/resolver.py +0 -0
  55. {dnd5e_engine-0.1.0 → dnd5e_engine-0.1.2}/src/dnd5e_engine/activities/save.py +0 -0
  56. {dnd5e_engine-0.1.0 → dnd5e_engine-0.1.2}/src/dnd5e_engine/activities/save_primitive.py +0 -0
  57. {dnd5e_engine-0.1.0 → dnd5e_engine-0.1.2}/src/dnd5e_engine/activities/scale.py +0 -0
  58. {dnd5e_engine-0.1.0 → dnd5e_engine-0.1.2}/src/dnd5e_engine/build_party.py +0 -0
  59. {dnd5e_engine-0.1.0 → dnd5e_engine-0.1.2}/src/dnd5e_engine/build_spec.py +0 -0
  60. {dnd5e_engine-0.1.0 → dnd5e_engine-0.1.2}/src/dnd5e_engine/check.py +0 -0
  61. {dnd5e_engine-0.1.0 → dnd5e_engine-0.1.2}/src/dnd5e_engine/death_saves.py +0 -0
  62. {dnd5e_engine-0.1.0 → dnd5e_engine-0.1.2}/src/dnd5e_engine/dispatch.py +0 -0
  63. {dnd5e_engine-0.1.0 → dnd5e_engine-0.1.2}/src/dnd5e_engine/event_dicts.py +0 -0
  64. {dnd5e_engine-0.1.0 → dnd5e_engine-0.1.2}/src/dnd5e_engine/events.py +0 -0
  65. {dnd5e_engine-0.1.0 → dnd5e_engine-0.1.2}/src/dnd5e_engine/lib_loader.py +0 -0
  66. {dnd5e_engine-0.1.0 → dnd5e_engine-0.1.2}/src/dnd5e_engine/outcome.py +0 -0
  67. {dnd5e_engine-0.1.0 → dnd5e_engine-0.1.2}/src/dnd5e_engine/py.typed +0 -0
  68. {dnd5e_engine-0.1.0 → dnd5e_engine-0.1.2}/src/dnd5e_engine/results.py +0 -0
  69. {dnd5e_engine-0.1.0 → dnd5e_engine-0.1.2}/src/dnd5e_engine/rules/__init__.py +0 -0
  70. {dnd5e_engine-0.1.0 → dnd5e_engine-0.1.2}/src/dnd5e_engine/rules/_class_meta.py +0 -0
  71. {dnd5e_engine-0.1.0 → dnd5e_engine-0.1.2}/src/dnd5e_engine/rules/_parsing.py +0 -0
  72. {dnd5e_engine-0.1.0 → dnd5e_engine-0.1.2}/src/dnd5e_engine/rules/combat_data.py +0 -0
  73. {dnd5e_engine-0.1.0 → dnd5e_engine-0.1.2}/src/dnd5e_engine/rules/combat_helpers.py +0 -0
  74. {dnd5e_engine-0.1.0 → dnd5e_engine-0.1.2}/src/dnd5e_engine/rules/conditions.py +0 -0
  75. {dnd5e_engine-0.1.0 → dnd5e_engine-0.1.2}/src/dnd5e_engine/rules/dice.py +0 -0
  76. {dnd5e_engine-0.1.0 → dnd5e_engine-0.1.2}/src/dnd5e_engine/rules/equipment.py +0 -0
  77. {dnd5e_engine-0.1.0 → dnd5e_engine-0.1.2}/src/dnd5e_engine/rules/gambits.py +0 -0
  78. {dnd5e_engine-0.1.0 → dnd5e_engine-0.1.2}/src/dnd5e_engine/rules/resolution.py +0 -0
  79. {dnd5e_engine-0.1.0 → dnd5e_engine-0.1.2}/src/dnd5e_engine/rules/skills.py +0 -0
  80. {dnd5e_engine-0.1.0 → dnd5e_engine-0.1.2}/src/dnd5e_engine/rules/spells.py +0 -0
  81. {dnd5e_engine-0.1.0 → dnd5e_engine-0.1.2}/src/dnd5e_engine/spatial.py +0 -0
  82. {dnd5e_engine-0.1.0 → dnd5e_engine-0.1.2}/src/dnd5e_engine/specs.py +0 -0
  83. {dnd5e_engine-0.1.0 → dnd5e_engine-0.1.2}/src/dnd5e_engine/testing.py +0 -0
  84. {dnd5e_engine-0.1.0 → dnd5e_engine-0.1.2}/src/dnd5e_engine/types/__init__.py +0 -0
  85. {dnd5e_engine-0.1.0 → dnd5e_engine-0.1.2}/src/dnd5e_engine/types/combat.py +0 -0
  86. {dnd5e_engine-0.1.0 → dnd5e_engine-0.1.2}/src/dnd5e_engine/types/conditions.py +0 -0
  87. {dnd5e_engine-0.1.0 → dnd5e_engine-0.1.2}/src/dnd5e_engine/types/dice.py +0 -0
  88. {dnd5e_engine-0.1.0 → dnd5e_engine-0.1.2}/src/dnd5e_engine/types/effects.py +0 -0
  89. {dnd5e_engine-0.1.0 → dnd5e_engine-0.1.2}/tests/__init__.py +0 -0
  90. {dnd5e_engine-0.1.0 → dnd5e_engine-0.1.2}/tests/activities/__init__.py +0 -0
  91. {dnd5e_engine-0.1.0 → dnd5e_engine-0.1.2}/tests/activities/test_build_context.py +0 -0
  92. {dnd5e_engine-0.1.0 → dnd5e_engine-0.1.2}/tests/activities/test_monster_actions.py +0 -0
  93. {dnd5e_engine-0.1.0 → dnd5e_engine-0.1.2}/tests/test_active_effect_schema.py +0 -0
  94. {dnd5e_engine-0.1.0 → dnd5e_engine-0.1.2}/tests/test_apply_modifiers.py +0 -0
  95. {dnd5e_engine-0.1.0 → dnd5e_engine-0.1.2}/tests/test_build_context_abilities.py +0 -0
  96. {dnd5e_engine-0.1.0 → dnd5e_engine-0.1.2}/tests/test_build_party.py +0 -0
  97. {dnd5e_engine-0.1.0 → dnd5e_engine-0.1.2}/tests/test_combat_flag_advantage.py +0 -0
  98. {dnd5e_engine-0.1.0 → dnd5e_engine-0.1.2}/tests/test_coverage_backfill_death_saves.py +0 -0
  99. {dnd5e_engine-0.1.0 → dnd5e_engine-0.1.2}/tests/test_coverage_backfill_seam_errors.py +0 -0
  100. {dnd5e_engine-0.1.0 → dnd5e_engine-0.1.2}/tests/test_duration_tick_at_turn_end.py +0 -0
  101. {dnd5e_engine-0.1.0 → dnd5e_engine-0.1.2}/tests/test_effects_builder_internals.py +0 -0
  102. {dnd5e_engine-0.1.0 → dnd5e_engine-0.1.2}/tests/test_end_combat_snapshot.py +0 -0
  103. {dnd5e_engine-0.1.0 → dnd5e_engine-0.1.2}/tests/test_feature_save_dc_no_spell_override.py +0 -0
  104. {dnd5e_engine-0.1.0 → dnd5e_engine-0.1.2}/tests/test_get_actor_active_effects.py +0 -0
  105. {dnd5e_engine-0.1.0 → dnd5e_engine-0.1.2}/tests/test_grid_topology.py +0 -0
  106. {dnd5e_engine-0.1.0 → dnd5e_engine-0.1.2}/tests/test_iter5_fixes.py +0 -0
  107. {dnd5e_engine-0.1.0 → dnd5e_engine-0.1.2}/tests/test_lib_loader.py +0 -0
  108. {dnd5e_engine-0.1.0 → dnd5e_engine-0.1.2}/tests/test_orchestrator_gating_typed.py +0 -0
  109. {dnd5e_engine-0.1.0 → dnd5e_engine-0.1.2}/tests/test_orchestrator_grid_combat.py +0 -0
  110. {dnd5e_engine-0.1.0 → dnd5e_engine-0.1.2}/tests/test_orchestrator_monster_typed.py +0 -0
  111. {dnd5e_engine-0.1.0 → dnd5e_engine-0.1.2}/tests/test_orchestrator_move_mark_typed.py +0 -0
  112. {dnd5e_engine-0.1.0 → dnd5e_engine-0.1.2}/tests/test_resolve_check_specific_buckets.py +0 -0
  113. {dnd5e_engine-0.1.0 → dnd5e_engine-0.1.2}/tests/test_rules_smoke.py +0 -0
  114. {dnd5e_engine-0.1.0 → dnd5e_engine-0.1.2}/tests/test_scale_resolver.py +0 -0
  115. {dnd5e_engine-0.1.0 → dnd5e_engine-0.1.2}/tests/test_smoke.py +0 -0
  116. {dnd5e_engine-0.1.0 → dnd5e_engine-0.1.2}/tests/test_use_feature_intent.py +0 -0
@@ -7,6 +7,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.1.2]
11
+
12
+ ### Added
13
+ - `ActionType.USE_ITEM` parser-facing action member.
14
+
15
+ ## [0.1.1]
16
+
17
+ ### Added
18
+ - `LiveCombatView` — public read-model projection of live combat state.
19
+ - `get_live(handle) -> LiveCombatView` is now part of the public API (`__all__`);
20
+ it returns a point-in-time snapshot view instead of the private `_LiveCombat`.
21
+ - `roll_dice_str` — the dice-expression evaluator is now a public test seam
22
+ (renamed from the private `_roll_dice_str`).
23
+
10
24
  ## [0.1.0]
11
25
 
12
26
  First public release.
@@ -0,0 +1,19 @@
1
+ .PHONY: check lint format format-check type test coverage security smoke
2
+ check: lint format-check type coverage security
3
+ lint:
4
+ uv run ruff check .
5
+ format:
6
+ uv run ruff format .
7
+ format-check:
8
+ uv run ruff format --check .
9
+ type:
10
+ uv run mypy src/
11
+ test:
12
+ uv run pytest -q
13
+ # Baseline-and-ratchet coverage gate (75% observed today; floor set 2pts under).
14
+ coverage:
15
+ uv run pytest -q --cov=dnd5e_engine --cov-report=term-missing --cov-fail-under=73
16
+ security:
17
+ uv run bandit -r src/ -c pyproject.toml -q
18
+ smoke:
19
+ bash scripts/smoke_clean_install.sh
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dnd5e-engine
3
- Version: 0.1.0
3
+ Version: 0.1.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
@@ -37,7 +37,9 @@ Requires-Dist: d20>=1.1.2
37
37
  Requires-Dist: dnd5e-srd-data>=0.1.0
38
38
  Requires-Dist: pydantic>=2.10.0
39
39
  Provides-Extra: dev
40
+ Requires-Dist: bandit>=1.8.0; extra == 'dev'
40
41
  Requires-Dist: mypy>=1.13.0; extra == 'dev'
42
+ Requires-Dist: pytest-cov>=6.0.0; extra == 'dev'
41
43
  Requires-Dist: pytest>=8.3.0; extra == 'dev'
42
44
  Requires-Dist: ruff>=0.9.0; extra == 'dev'
43
45
  Description-Content-Type: text/markdown
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "dnd5e-engine"
7
- version = "0.1.0"
7
+ version = "0.1.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"
@@ -30,8 +30,10 @@ dependencies = [
30
30
  [project.optional-dependencies]
31
31
  dev = [
32
32
  "pytest>=8.3.0",
33
+ "pytest-cov>=6.0.0",
33
34
  "ruff>=0.9.0",
34
35
  "mypy>=1.13.0",
36
+ "bandit>=1.8.0",
35
37
  ]
36
38
 
37
39
  [tool.uv.sources]
@@ -60,17 +62,42 @@ target-version = "py312"
60
62
  [tool.ruff.lint]
61
63
  select = [
62
64
  "E", "F", "I", "N", "UP", "B", "SIM", "T20", "BLE",
63
- "ASYNC", "PT", "RUF", "TRY",
65
+ "C90", "ASYNC", "PT", "RUF", "TRY",
64
66
  ]
65
67
  ignore = [
66
68
  "TRY003",
67
69
  "RUF001", "RUF002", "RUF003",
68
70
  ]
69
71
 
72
+ # Baseline-and-ratchet: set at the worst function observed today
73
+ # (submit_player_intent=23, in orchestrator.py). Ratcheted down from 56
74
+ # after extracting the orchestrator hotspots (_emit 49→12, start_combat
75
+ # 34→3, _build_hydration_payload 36→6). A PR past this must justify it;
76
+ # keep ratcheting DOWN.
77
+ [tool.ruff.lint.mccabe]
78
+ max-complexity = 23
79
+
70
80
  [tool.ruff.lint.per-file-ignores]
71
- "tests/**/*.py" = ["E501", "F841", "PT006", "PT011"]
81
+ # Test bodies are allowed to be branchy/repetitive (C901) and print (T201).
82
+ "tests/**/*.py" = ["E501", "F841", "PT006", "PT011", "C901"]
72
83
  # Smoke scripts print human-readable progress/result lines (T201) by design.
73
84
  "scripts/**/*.py" = ["T201"]
74
85
 
75
86
  [tool.pytest.ini_options]
76
87
  testpaths = ["tests"]
88
+
89
+ [tool.coverage.run]
90
+ source = ["dnd5e_engine"]
91
+
92
+ [tool.coverage.report]
93
+ exclude_also = [
94
+ "if TYPE_CHECKING:",
95
+ "@overload",
96
+ "raise NotImplementedError",
97
+ ]
98
+
99
+ # Recursive scan of src/ only; tests carry asserts (B101) and the engine
100
+ # rolls dice with the stdlib RNG (B311) — neither is a security concern.
101
+ [tool.bandit]
102
+ exclude_dirs = ["tests"]
103
+ skips = ["B101", "B311"]
@@ -30,9 +30,7 @@ _BUNDLED_MONSTER_SLUG = "goblin-warrior"
30
30
  def _check_corpus() -> None:
31
31
  loader = get_lib_loader() # BundledAssetLoader → reads bundled canonical/
32
32
  monster = loader.get_monster(_BUNDLED_MONSTER_SLUG)
33
- assert monster is not None, (
34
- f"bundled corpus did not resolve monster {_BUNDLED_MONSTER_SLUG!r}"
35
- )
33
+ assert monster is not None, f"bundled corpus did not resolve monster {_BUNDLED_MONSTER_SLUG!r}"
36
34
 
37
35
 
38
36
  async def _run_grid_combat() -> None:
@@ -63,15 +61,15 @@ async def _run_grid_combat() -> None:
63
61
  grid_scene=GridScene(width=10, height=10),
64
62
  rng_seed=1,
65
63
  )
66
- live = get_live(start.handle)
67
- assert live.actor_zone["char:hero"] == "0,0"
64
+ assert get_live(start.handle).actor_zone["char:hero"] == "0,0"
68
65
  # One legal single-cell move proves the grid MOVE path resolves end-to-end.
69
66
  await submit_player_intent(
70
67
  start.handle,
71
68
  actor_id="char:hero",
72
69
  intent=PlayerIntent(intent_type="move", target_zone_id=cell_id(1, 1)),
73
70
  )
74
- assert live.actor_zone["char:hero"] == "1,1", "grid move did not apply"
71
+ # get_live returns a point-in-time snapshot, so re-fetch after the move.
72
+ assert get_live(start.handle).actor_zone["char:hero"] == "1,1", "grid move did not apply"
75
73
  result = await end_combat(start.handle)
76
74
  assert result is not None
77
75
  print("SMOKE OK: corpus loaded + grid combat ran (hero moved 0,0 -> 1,1)")
@@ -13,7 +13,7 @@ Deferred for later phases:
13
13
 
14
14
  from __future__ import annotations
15
15
 
16
- __version__ = "0.1.0"
16
+ __version__ = "0.1.1"
17
17
 
18
18
  from dnd5e_engine.build_party import build_party_member
19
19
  from dnd5e_engine.build_spec import (
@@ -26,10 +26,12 @@ from dnd5e_engine.check import CheckKind, CheckResult, CheckSpec, resolve_check
26
26
  from dnd5e_engine.events import CombatEvent, IntentType
27
27
  from dnd5e_engine.orchestrator import (
28
28
  CombatHandle,
29
+ LiveCombatView,
29
30
  PlayerIntent,
30
31
  advance_monster_turn,
31
32
  end_combat,
32
33
  get_actor_active_effects,
34
+ get_live,
33
35
  narration_events,
34
36
  start_combat,
35
37
  submit_player_intent,
@@ -40,6 +42,7 @@ from dnd5e_engine.outcome import (
40
42
  LootDrop,
41
43
  )
42
44
  from dnd5e_engine.results import EndCombatResult, StartCombatResult
45
+ from dnd5e_engine.rules.effects import roll_dice_str
43
46
  from dnd5e_engine.spatial import cell_id, parse_cell
44
47
  from dnd5e_engine.specs import (
45
48
  EncounterMemberSpec,
@@ -74,6 +77,7 @@ __all__ = [
74
77
  "EndCombatResult",
75
78
  "GridScene",
76
79
  "IntentType",
80
+ "LiveCombatView",
77
81
  "LootDrop",
78
82
  "PartyMemberSpec",
79
83
  "PlayerIntent",
@@ -85,10 +89,12 @@ __all__ = [
85
89
  "cell_id",
86
90
  "end_combat",
87
91
  "get_actor_active_effects",
92
+ "get_live",
88
93
  "make_build_spec",
89
94
  "narration_events",
90
95
  "parse_cell",
91
96
  "resolve_check",
97
+ "roll_dice_str",
92
98
  "start_combat",
93
99
  "submit_player_intent",
94
100
  ]
@@ -123,9 +123,7 @@ def resolve_damage_block(
123
123
  return block.model_copy(update=updates) if updates else block
124
124
 
125
125
 
126
- def _resolve_token(
127
- token: str, ctx: ActivityResolutionContext, ability: str | None
128
- ) -> int | str:
126
+ def _resolve_token(token: str, ctx: ActivityResolutionContext, ability: str | None) -> int | str:
129
127
  if token == _TOKEN_PROF:
130
128
  return ctx.caster_proficiency_bonus
131
129
  if token == _TOKEN_MOD: