| """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) |
|
|
|
|
| |
|
|
| 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 |
|
|
|
|
| |
|
|
| 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"} |
|
|
| |
| 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") |
|
|
|
|
| |
|
|
| 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 |
|
|
|
|
| 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) |
|
|