feat(cli): add --transcript option

This commit is contained in:
2026-09-04 23:34:12 +02:00
parent cedff5b932
commit 7591614460
4 changed files with 61 additions and 2 deletions
+15 -1
View File
@@ -57,7 +57,12 @@ class CombatantAttrition:
@dataclass(frozen=True)
class EncounterReport:
"""Aggregated results of a Monte Carlo encounter."""
"""Aggregated results of a Monte Carlo encounter.
`transcript` holds the turn-by-turn log of the first battle when
`run_encounter` is called with ``capture_first_transcript=True``; it is
None otherwise (the default keeps the report cheap for pure Monte Carlo).
"""
runs: int
seed: int
@@ -65,6 +70,7 @@ class EncounterReport:
draws: int
avg_rounds: float
attrition: dict[str, CombatantAttrition]
transcript: tuple[str, ...] | None = None
def win_rate(self, side: str) -> float:
"""Fraction of runs won by `side` (closed form, see metrics)."""
@@ -126,11 +132,15 @@ def run_encounter(
*,
policy: Policy | None = None,
spell_registry: dict[str, SpellSpec] | None = None,
capture_first_transcript: bool = False,
) -> EncounterReport:
"""Run `runs` battles (one SeededRng per run) and aggregate balance metrics.
The spell registry is loaded once (from ``data/spells/`` unless overridden)
and passed into every ``CombatEngine`` so caster PCs actually cast.
When ``capture_first_transcript`` is True, the turn-by-turn log of the
first battle (``seed + 0``) is stored in ``report.transcript``.
"""
if runs <= 0:
msg = "runs must be positive"
@@ -144,6 +154,7 @@ def run_encounter(
draws = 0
total_rounds = 0
totals = {cid: CombatantStats() for cid in ids}
transcript: tuple[str, ...] | None = None
for index in range(runs):
if index > 0:
states = build_states(spec)
@@ -156,6 +167,8 @@ def run_encounter(
policy=policy,
spell_registry=spell_registry,
).run()
if capture_first_transcript and index == 0:
transcript = result.transcript
if result.winner is None:
draws += 1
else:
@@ -185,4 +198,5 @@ def run_encounter(
draws=draws,
avg_rounds=total_rounds / runs,
attrition=attrition,
transcript=transcript,
)