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.
This commit is contained in:
@@ -0,0 +1,59 @@
|
||||
"""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
|
||||
Reference in New Issue
Block a user