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
+16 -1
View File
@@ -100,6 +100,11 @@ def main(argv: list[str] | None = None) -> int:
default=100,
help="max rounds per battle (default: 100)",
)
parser.add_argument(
"--transcript",
action="store_true",
help="print the turn-by-turn log of the first battle before the report",
)
args = parser.parse_args(argv)
if not args.side:
parser.error("at least one --side is required")
@@ -113,9 +118,19 @@ def main(argv: list[str] | None = None) -> int:
return 2
try:
registry = load_spell_registry(SPELLS_DIR)
report = run_encounter(spec, runs=args.runs, seed=args.seed, spell_registry=registry)
report = run_encounter(
spec,
runs=args.runs,
seed=args.seed,
spell_registry=registry,
capture_first_transcript=args.transcript,
)
except ValueError as exc:
print(f"pf1e-sim: {exc}", file=sys.stderr)
return 2
if args.transcript and report.transcript is not None:
print("=== Déroulé du premier combat ===")
print("\n".join(report.transcript))
print()
print(format_report(report, spec))
return 0
+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,
)