Fix the bots' corner bias; settle seat order and RULES v0.9 (6.2, 6.8)

Bots now pick their start square and break path ties in their own
corner's frame; the absolute tie-break made corners 1 and 2 win 25-28%.
With that fixed, seat order is within target at 4 players, and a +1
stripe first-player bonus (new switch) gains only ~1 point, so no
compensation. RULES.md v0.9 lists the tested alternatives; the plan has
the 6.8 consolidation run and the seat-order study.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-27 18:27:06 +02:00
parent 52d8af620b
commit 79cbbccbb2
9 changed files with 178 additions and 24 deletions
+26 -4
View File
@@ -3,9 +3,9 @@
import random
import pytest
from scripted import make_game, put
from scripted import fixture_board, make_game, put
from dojo.board import Board, Pos
from dojo.board import Board, Pos, rotate
from dojo.bots import (
AGGRESSIVE,
BALANCED,
@@ -15,7 +15,9 @@ from dojo.bots import (
HeuristicAgent,
Profile,
RandomAgent,
own_frame,
series_probability,
start_square,
win_probability,
)
from dojo.engine import Agent, FightView, Game, RandomSource
@@ -73,9 +75,29 @@ def test_heuristic_sees_no_opponent_to_fight_in_round_one() -> None:
put(game, 1, TATAMI, track=3)
# In round 2 the aggressive bot picks the fight (test_profiles_differ_on_an_even_fight).
choice = HeuristicAgent(AGGRESSIVE).choose_path(game.view(0), [(Pos(5, 3),), (TATAMI,)])
# Two plain Tatami tie; the tie goes to the lower square in the bot's frame, (4, 2).
choice = HeuristicAgent(AGGRESSIVE).choose_path(game.view(0), [(TATAMI,), (Pos(4, 2),)])
assert choice == (Pos(5, 3),)
assert choice == (Pos(4, 2),)
def test_bots_start_from_the_same_square_of_every_safe_room() -> None:
board = fixture_board()
assert len({own_frame(start_square(board, corner), corner) for corner in range(4)}) == 1
def test_tied_paths_are_chosen_alike_from_every_corner() -> None:
game, _, _ = make_game(players=4)
agent = HeuristicAgent(BALANCED)
chosen = set()
for seat, player in enumerate(game.players):
player.position = rotate(Pos(4, 3), player.corner)
tatami = [(rotate(square, player.corner),) for square in (Pos(5, 3), Pos(3, 3), Pos(4, 2))]
choice = agent.choose_path(game.view(seat), tatami)
chosen.add(own_frame(choice[0], player.corner))
assert chosen == {Pos(3, 3)}
def test_brown_heuristic_takes_the_exam_when_it_can() -> None:
+11
View File
@@ -246,6 +246,17 @@ def test_no_kumite_in_round_one_an_occupied_tunnel_exit_cannot_be_used() -> None
assert game.players[0].position == Pos(1, 6)
def test_first_player_bonus_raises_only_the_first_seat() -> None:
game, _, _ = make_game(players=3, config=RulesConfig(start_position=3, first_player_bonus=1))
assert [p.track for p in game.players] == [4, 3, 3]
def test_first_player_bonus_cannot_be_negative() -> None:
with pytest.raises(ValueError, match="first_player_bonus"):
RulesConfig(first_player_bonus=-1)
def test_first_kumite_round_must_be_positive() -> None:
with pytest.raises(ValueError, match="first_kumite_round"):
RulesConfig(first_kumite_round=0)
+11 -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, Card
from dojo.rules import DEFAULT_RULES, Card, RulesConfig
HOME_0 = Pos(0, 1) # quadrant 0
@@ -106,6 +106,16 @@ def test_kiai_decides_a_fight_only_when_the_result_flips_without_it(
assert summary.exam_kiai_decided_rate == decided
def test_first_player_bonus_is_counted_from_the_start() -> None:
config = RulesConfig(first_player_bonus=1)
metrics = measure(
[GameOver(None, 1)], config, (0, 1), ("balanced", "farmer"), scenario="t", seed=0
)
assert metrics.final_track == (1, 0)
def test_standings_are_sampled_at_turn_ends() -> None:
metrics = run(
[