Files
ctan a21e234b41 feat(runner): Monte Carlo runner, closed-form metrics and balance CLI
Adds the S6 layer on top of the deterministic combat engine:
- metrics.py: closed-form binomial statistics (win rate, standard error,
  3-sigma confidence band) with clamped degenerate proportions.
- runner.py: EncounterSpec/Side definitions, per-run SeededRng streams,
  global duplicate-id disambiguation, attrition averages per combatant.
- cli.py: pf1e-sim entry point producing a French balance report with
  win rates, 3-sigma bands, draws, rounds, and attrition.
- Movement fix: greedy straight-line heuristic could oscillate at walls;
  _step_toward now follows the true shortest path via an unbounded
  Dijkstra cost field from the target (Grid.reachable budget=None).
- Regression tests: melee unit routes around a wall and engages; grid
  unbounded-budget coverage.
2026-08-17 22:49:50 +02:00

60 lines
1.6 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Tests for the pf1e-sim command-line interface."""
from __future__ import annotations
from pathlib import Path
from typing import TYPE_CHECKING
if TYPE_CHECKING:
import pytest
from pf1e_simulator.cli import main
ROOT = Path(__file__).resolve().parents[1]
MAP = ROOT / "data" / "maps" / "sample_arena.yaml"
GOBLIN = ROOT / "data" / "monsters" / "goblin.json"
ORC = ROOT / "data" / "monsters" / "orc.json"
def test_cli_prints_balance_report(capsys: pytest.CaptureFixture[str]) -> None:
rc = main(
[
"--map",
str(MAP),
"--side",
"players",
str(GOBLIN),
str(GOBLIN),
str(GOBLIN),
"--side",
"monsters",
str(ORC),
str(ORC),
"--runs",
"50",
"--seed",
"1",
]
)
out = capsys.readouterr().out
assert rc == 0
assert "Rapport d'équilibrage" in out
assert "Victoires players" in out
assert "Victoires monsters" in out
assert "bande 3σ" in out
assert "Nuls" in out
assert "Rounds moyens" in out
assert "Attrition moyenne" in out
def test_cli_rejects_side_not_in_deployment(capsys: pytest.CaptureFixture[str]) -> None:
rc = main(["--map", str(MAP), "--side", "bogus", str(GOBLIN), "--runs", "10"])
assert rc == 2
assert "deployment" in capsys.readouterr().err
def test_cli_rejects_missing_deployment_side(capsys: pytest.CaptureFixture[str]) -> None:
rc = main(["--map", str(MAP), "--side", "players", str(GOBLIN), "--runs", "10"])
assert rc == 2
assert "monsters" in capsys.readouterr().err