"""Wiring tests: the Monte Carlo runner loads the real spell registry and spellcasting PCs cast against ``data/spells/``. The engine's ``spell_registry`` defaults to ``{}`` for retro-compat with pure unit tests; the runner must therefore load the registry from ``SPELLS_DIR`` and pass it into every ``CombatEngine`` so a caster PC actually produces ``cast_spell`` actions in Monte Carlo. """ from __future__ import annotations from pathlib import Path from pf1e_simulator.combat import CombatEngine from pf1e_simulator.grid import Grid from pf1e_simulator.loaders import load_combatant from pf1e_simulator.map import load_map from pf1e_simulator.rng import SeededRng from pf1e_simulator.runner import EncounterSpec, Side, build_states from pf1e_simulator.spells import SPELLS_DIR, load_spell_registry ROOT = Path(__file__).resolve().parents[1] MAP = ROOT / "data" / "maps" / "sample_arena.yaml" ONI = ROOT / "data" / "pcs" / "oni.json" GOBLIN = ROOT / "data" / "monsters" / "goblin.json" _NUM_SPELLS = 107 def test_runner_loads_realtime_spell_registry() -> None: """The runner's registry source holds all 107 real spells from data/spells/.""" registry = load_spell_registry(SPELLS_DIR) assert len(registry) == _NUM_SPELLS def test_monte_carlo_caster_pc_casts_spell() -> None: """oni (cleric) casts against data/spells/ once it drops below 50% HP. oni's known spells overlap the registry's heal spells (Cure Light/Moderate Wounds); with enough damage taken it heals via cast_spell. Channel energy is stripped from the resource pool: it now heals living creatures in the burst, so below 50% HP oni self-heals by channeling and never reaches the spell branch — this test stays a pure spell-wiring check. The Monte Carlo loop mirrors run_encounter: build fresh states and one CombatEngine per run with the registry from SPELLS_DIR. """ oni = load_combatant(ONI).model_copy(update={"resources": {}}) spec = EncounterSpec( map=load_map(MAP), round_cap=100, sides=( Side(name="players", combatants=(oni,), zone="A"), Side(name="monsters", combatants=(load_combatant(GOBLIN),) * 4, zone="B"), ), ) registry = load_spell_registry(SPELLS_DIR) grid = Grid.from_spec(spec.map) casts = 0 for i in range(100): states = build_states(spec) rng = SeededRng(7 + i) result = CombatEngine( rng, grid, states, round_cap=spec.round_cap, spell_registry=registry ).run() casts += sum( 1 for line in result.transcript if " vs " in line and "cast " in line ) assert casts > 0