Files
dojo_cate/tests/test_sim.py
T
eisterman 4998931cb0 Drop variant B, adopt Black-belt Exam defender, add one-sidedness metrics
- Remove variant B (Brown cap, +1 for higher beating lower) from rules,
  engine, bots and simulator; a higher belt beating a lower one gains 0.
- Final Exam: the defender fights as a Black belt (RULES.md v0.7). Ties to
  the defender and best-of-3 stay as simulator switches (`--exam`).
- Report stripe income per source as gains and losses.
- Add ten one-sidedness measures sampled at turn ends (lead gap, winner
  margin and lead share, halfway leader, last lead change, contention,
  rank swaps, leader Kumite losses, Exam challengers, winner pace).
- Plan: decisions D28-D33 and the detailed Phase 6.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-26 17:09:42 +02:00

81 lines
2.6 KiB
Python

from pathlib import Path
import pytest
from dojo.rules import RulesConfig
from dojo.sim import BoardFiles, Scenario, matrix, run
BOARD = Path(__file__).parent.parent / "board"
FILES = BoardFiles(BOARD / "quadrant.txt", BOARD / "legend.toml")
LINEUP = ("balanced", "random")
def test_matrix_crosses_every_axis() -> None:
scenarios = matrix(["none", "acd"], [3, 4], [2, 1], [0], [LINEUP])
assert len(scenarios) == 8
assert scenarios[0].name == "exam:none p3 kiai+2 start0 balanced,random"
assert scenarios[0].config == RulesConfig(exam_defender_black=False)
assert scenarios[-1].config == RulesConfig(
exam_ties_to_defender=True, exam_defender_black=True, exam_rounds=3, kiai_bonus=1
)
@pytest.mark.parametrize(
("exam", "ties", "black", "rounds"),
[
("none", False, False, 1),
("a", True, False, 1),
("c", False, True, 1),
("d", False, False, 3),
("cd", False, True, 3),
],
)
def test_exam_options_map_to_rules(exam: str, ties: bool, black: bool, rounds: int) -> None:
config = Scenario(exam, 4, 2, 0, LINEUP).config
assert (config.exam_ties_to_defender, config.exam_defender_black) == (ties, black)
assert config.exam_rounds == rounds
def test_lineup_fills_the_seats_and_rotates_each_game() -> None:
scenario = Scenario("none", 3, 2, 0, LINEUP)
assert scenario.seating(0) == ("balanced", "random", "balanced")
assert scenario.seating(1) == ("random", "balanced", "balanced")
assert scenario.seating(3) == scenario.seating(0)
@pytest.mark.parametrize(
("exam", "players", "lineup", "message"),
[
("b", 4, LINEUP, "unknown Exam options"),
("aa", 4, LINEUP, "unknown Exam options"),
("", 4, LINEUP, "unknown Exam options"),
("none", 4, ("balanced", "cheater"), "unknown bots"),
("none", 4, (), "unknown bots"),
("none", 9, LINEUP, "supported range"),
],
)
def test_invalid_scenarios_are_rejected(
exam: str, players: int, lineup: tuple[str, ...], message: str
) -> None:
with pytest.raises(ValueError, match=message):
Scenario(exam, players, 2, 0, lineup)
def test_runs_are_reproducible_and_independent_of_worker_count() -> None:
scenarios = matrix(["none", "acd"], [3], [2], [0], [LINEUP])
serial = run(FILES, scenarios, games=2, seed=5, jobs=1)
parallel = run(FILES, scenarios, games=2, seed=5, jobs=2)
assert serial == parallel
assert [(g.scenario, g.seed) for g in serial] == [
(scenarios[0].name, 5),
(scenarios[0].name, 6),
(scenarios[1].name, 5),
(scenarios[1].name, 6),
]
assert serial[1].lineup == "random,balanced,balanced"