Add Kiai-decided metrics; keep Kiai at +2 (6.6, D47)

Report how often a Tecnica is played and how often Kiai decides a
Kumite or an Exam fight. The tie rule moves to RulesConfig.attacker_wins,
shared by the engine and the metric; games are unchanged. RULES.md drops
the Kiai +1 and catch-up variants from the rules under test.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-27 17:27:37 +02:00
parent f0e112590a
commit 23ae7a2572
7 changed files with 106 additions and 16 deletions
+31 -1
View File
@@ -17,7 +17,7 @@ from dojo.events import (
TurnStarted,
)
from dojo.metrics import KUMITE_SECONDS, TURN_SECONDS, GameMetrics, measure, summarize
from dojo.rules import DEFAULT_RULES
from dojo.rules import DEFAULT_RULES, Card
HOME_0 = Pos(0, 1) # quadrant 0
@@ -76,6 +76,36 @@ def test_measure_counts_fights_income_and_exams() -> None:
assert metrics.minutes == pytest.approx(round((3 * TURN_SECONDS + 5 * KUMITE_SECONDS) / 60, 1))
@pytest.mark.parametrize(
("totals", "attacker_won", "cards", "decided"),
[
((5, 4), True, ((0, Card.KIAI),), True),
((5, 3), True, ((0, Card.KIAI),), False), # 3 vs 3 without it: Senshu, still won.
((8, 4), True, ((0, Card.KIAI),), False),
((4, 5), False, ((1, Card.KIAI),), True),
((6, 5), True, ((0, Card.KIAI), (1, Card.KIAI)), False),
((5, 4), True, ((0, Card.KAESHI),), False),
],
)
def test_kiai_decides_a_fight_only_when_the_result_flips_without_it(
totals: tuple[int, int],
attacker_won: bool,
cards: tuple[tuple[int, Card], ...],
decided: bool,
) -> None:
fight = KumiteFought(0, 1, 0, 0, *totals, attacker_won, FightKind.EQUAL, False, cards)
exam = replace(fight, exam=True)
metrics = run([TurnStarted(2, 0, 7), fight, fought(1, 0, FightKind.EQUAL), exam])
summary = summarize("test", [metrics])
assert (metrics.tecnica_kumite, metrics.kiai_decided) == (1, decided)
assert (metrics.exam_fights, metrics.exam_kiai_decided) == (1, decided)
assert summary.tecnica_rate == 0.5
assert summary.kiai_decided_rate == decided / 2
assert summary.exam_kiai_decided_rate == decided
def test_standings_are_sampled_at_turn_ends() -> None:
metrics = run(
[