"""Director tests: dominance assessment, bounded interventions, mercy.""" import random import pytest from scrypt.data import load_content from scrypt.engine.cards import CardInstance, make_card from scrypt.engine.combat import CombatState, IllegalMove from scrypt.inference.backend import ScriptedBackend from scrypt.warden.director import CRUSHED, DOMINATING, EVEN, Director, assess TITAN = make_card("titan", power=6, health=6) GRUNT = make_card("grunt", power=2, health=2) def fight(turn: int = 3, scale: int = 0) -> CombatState: state = CombatState(main_deck=[GRUNT] * 4, side_deck=[], script=[[]], seed=1) state.turn = turn state.scale = scale return state def make_director(backend=None, seed: int = 0) -> Director: return Director(content=load_content(), rng=random.Random(seed), backend=backend) # ----------------------------------------------------------------- assess def test_assess_reads_dominance_and_collapse(): state = fight(scale=3) state.player_row[0] = CardInstance(spec=TITAN) assert assess(state) is DOMINATING crushed = fight(scale=-3) crushed.foe_row[0] = CardInstance(spec=TITAN) crushed.hand.clear() assert assess(crushed) is CRUSHED assert assess(fight(scale=0)) is EVEN # ----------------------------------------------------------- intervention async def test_no_intervention_in_opening_turns(): state = fight(turn=0, scale=5) state.player_row[0] = CardInstance(spec=TITAN) assert await make_director().consider(state) is None async def test_dominating_triggers_one_bounded_intervention(): director = make_director() state = fight(scale=3) state.player_row[0] = CardInstance(spec=TITAN) first = await director.consider(state) assert first is not None and first.action in ("throttle", "reinforce") kinds = {e.kind for e in state.events} assert kinds & {"throttled", "reinforced"} # The per-fight budget refuses a second act of cruelty. assert await director.consider(state) is None director.new_fight() assert await director.consider(state) is not None async def test_crushed_gets_secret_mercy(): director = make_director() state = fight(scale=-4) state.foe_row[0] = CardInstance(spec=TITAN) state.foe_queue[1] = CardInstance(spec=TITAN) state.hand.clear() mercy = await director.consider(state) assert mercy is not None and mercy.action == "withdraw" assert state.foe_queue[1] is None assert any(e.kind == "withdrawn" for e in state.events) async def test_backend_picks_from_menu(): backend = ScriptedBackend( playbook={"Choose one intervention": '{"tool": "intervene", "args": {"action": "reinforce"}}'} ) director = make_director(backend=backend) state = fight(scale=3) state.player_row[0] = CardInstance(spec=TITAN) result = await director.consider(state) assert result is not None and result.action == "reinforce" assert any(e.kind == "reinforced" for e in state.events) async def test_nonsense_backend_falls_back_to_rng(): backend = ScriptedBackend(default="I refuse to use tools.") director = make_director(backend=backend, seed=4) state = fight(scale=4) state.player_row[0] = CardInstance(spec=TITAN) result = await director.consider(state) assert result is not None and result.action in ("throttle", "reinforce") # ------------------------------------------------------------ engine hooks def test_throttle_floors_at_zero_power(): state = fight() weak = CardInstance(spec=make_card("weak", power=1, health=1)) state.player_row[0] = weak state.throttle_player_card(0) state.throttle_player_card(0) assert weak.power == 0 # never negative def test_reinforce_requires_empty_lane(): state = fight() state.foe_queue[0] = CardInstance(spec=GRUNT) with pytest.raises(IllegalMove): state.reinforce_queue(0, TITAN) with pytest.raises(IllegalMove): state.withdraw_queue(2)