74 lines
2.8 KiB
Python
74 lines
2.8 KiB
Python
"""Unit tests for `tooling.rl_self_play.harness_client.HarnessConfig`.
|
|
|
|
We don't spawn the Godot subprocess here (smoke.py already exercises
|
|
that). We test the env-var serialisation contract added in commit
|
|
e103928d2: `player_slots`, `player_controllers`, and the
|
|
`effective_player_slots` back-compat fallback. The `mc-player-api`
|
|
dispatcher reads CP_PLAYER_SLOTS / CP_PLAYER_CONTROLLERS verbatim, so
|
|
a regression in this serialisation silently corrupts multi-slot RL
|
|
training.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
from tooling.rl_self_play.harness_client import HarnessConfig
|
|
|
|
|
|
def test_defaults_serialise_single_slot_layout() -> None:
|
|
cfg = HarnessConfig()
|
|
env = cfg.to_env()
|
|
assert env["CP_SEED"] == "42"
|
|
assert env["CP_PLAYERS"] == "2"
|
|
assert env["CP_PLAYER_SLOT"] == "0"
|
|
# Back-compat: when player_slots is empty, the wire still receives
|
|
# a single-slot list derived from player_slot.
|
|
assert env["CP_PLAYER_SLOTS"] == "0"
|
|
assert env["CP_MAP_SIZE"] == "duel"
|
|
assert env["CP_MAP_TYPE"] == "continents"
|
|
assert env["CP_OMNISCIENT"] == "0"
|
|
assert env["CP_VICTORY_MODE"] == "domination"
|
|
# Controllers are emitted ONLY when explicitly configured — empty
|
|
# means "every AI slot uses scripted:default".
|
|
assert "CP_PLAYER_CONTROLLERS" not in env
|
|
|
|
|
|
def test_effective_player_slots_back_compat_fallback() -> None:
|
|
cfg = HarnessConfig(player_slot=2)
|
|
assert cfg.effective_player_slots == (2,)
|
|
cfg = HarnessConfig(player_slot=0, player_slots=(0, 1, 3))
|
|
assert cfg.effective_player_slots == (0, 1, 3)
|
|
|
|
|
|
def test_multi_slot_serialisation_uses_first_slot_for_legacy_var() -> None:
|
|
"""When driving multiple slots, CP_PLAYER_SLOT keeps the legacy
|
|
single-slot consumer happy by naming the first slot, while
|
|
CP_PLAYER_SLOTS carries the full tuple."""
|
|
cfg = HarnessConfig(player_slots=(0, 2, 4))
|
|
env = cfg.to_env()
|
|
assert env["CP_PLAYER_SLOT"] == "0"
|
|
assert env["CP_PLAYER_SLOTS"] == "0,2,4"
|
|
|
|
|
|
def test_player_controllers_serialised_when_set() -> None:
|
|
cfg = HarnessConfig(
|
|
player_controllers=("learned:duel-v1b", "", "scripted:aggressive"),
|
|
)
|
|
env = cfg.to_env()
|
|
assert env["CP_PLAYER_CONTROLLERS"] == "learned:duel-v1b,,scripted:aggressive"
|
|
|
|
|
|
def test_omniscient_and_victory_mode_round_trip() -> None:
|
|
cfg = HarnessConfig(omniscient=True, victory_mode="science")
|
|
env = cfg.to_env()
|
|
assert env["CP_OMNISCIENT"] == "1"
|
|
assert env["CP_VICTORY_MODE"] == "science"
|
|
|
|
|
|
def test_config_is_frozen_dataclass() -> None:
|
|
cfg = HarnessConfig()
|
|
import dataclasses
|
|
assert dataclasses.is_dataclass(cfg)
|
|
try:
|
|
cfg.seed = 99 # type: ignore[misc]
|
|
except dataclasses.FrozenInstanceError:
|
|
return
|
|
raise AssertionError("HarnessConfig must remain a frozen dataclass")
|