Spaces:
Running on Zero
Running on Zero
| """The encounter author: bounded composition, validated choice, sim gate.""" | |
| from __future__ import annotations | |
| import random | |
| import pytest | |
| from scrypt.data import load_content | |
| from scrypt.engine.combat import ScriptedPlay | |
| from scrypt.warden import author | |
| def content(): | |
| return load_content() | |
| REAL_ENCOUNTERS = ["first_blood", "audit_sweep", "swap_storm", "scheduled_doom", "init_zero"] | |
| def test_every_offered_variant_is_inside_the_bands(content): | |
| for enc_id in REAL_ENCOUNTERS: | |
| base = content.encounters[enc_id]["script"] | |
| options = author.variants(content, base, random.Random(7)) | |
| assert options[0].label == author.BASE_LABEL | |
| for v in options[1:]: | |
| assert author.within_bands(v.script, base), f"{enc_id}/{v.label}" | |
| assert v.script != base | |
| def test_real_encounters_offer_real_choices(content): | |
| """If mutations silently stop building, the author dies quietly — | |
| make that loud: every non-tutorial encounter must offer ≥2 variants.""" | |
| for enc_id in ["audit_sweep", "swap_storm", "scheduled_doom", "init_zero"]: | |
| base = content.encounters[enc_id]["script"] | |
| assert len(author.variants(content, base, random.Random(7))) >= 3, enc_id | |
| def test_bands_reject_inflation_and_frontloading(content): | |
| base = content.encounters["audit_sweep"]["script"] | |
| init = content.card("init") | |
| inflated = [list(t) for t in base] | |
| inflated[0] = [ScriptedPlay(lane=0, card=init), ScriptedPlay(lane=1, card=init)] | |
| assert not author.within_bands(inflated, base) | |
| frontloaded = [list(t) for t in base] | |
| # Move every late play to turn 0 (worth >2 plays -> also rejected). | |
| frontloaded[0] = [p for t in base for p in t][:3] | |
| assert not author.within_bands(frontloaded, base) | |
| wrong_length = [list(t) for t in base][:-1] | |
| assert not author.within_bands(wrong_length, base) | |
| def test_smoke_gate_passes_base_against_itself(content): | |
| base = content.encounters["first_blood"]["script"] | |
| deck = list(content.starter_decks["vanilla"]["cards"]) | |
| assert author.smoke_gate(content, base, base, deck, seeds=8) | |
| class ComposeBackend: | |
| """Answers the harness with a fixed tool call.""" | |
| def __init__(self, variant: str): | |
| self.reply = '{"tool": "compose", "args": {"variant": "%s"}}' % variant | |
| async def stream(self, messages, **kw): | |
| yield self.reply | |
| async def test_compose_honors_a_valid_model_pick(content): | |
| base = content.encounters["audit_sweep"]["script"] | |
| labels = [v.label for v in author.variants(content, base, random.Random(7))] | |
| assert "the swarm" in labels | |
| deck = list(content.starter_decks["vanilla"]["cards"]) | |
| script, label = await author.compose( | |
| ComposeBackend("the swarm"), content, "audit_sweep", | |
| deck, "- leans on firewall", random.Random(7), | |
| ) | |
| assert label == "the swarm" | |
| assert script != base | |
| assert author.within_bands(script, base) | |
| async def test_compose_survives_a_rambling_model(content): | |
| class Rambler: | |
| async def stream(self, messages, **kw): | |
| yield "I think, given the circumstances, we should talk about feelings." | |
| deck = list(content.starter_decks["vanilla"]["cards"]) | |
| script, label = await author.compose( | |
| Rambler(), content, "audit_sweep", deck, "", random.Random(3), | |
| ) | |
| # Harness fallback -> seeded rng pick; either way the result is legal. | |
| base = content.encounters["audit_sweep"]["script"] | |
| assert label is None or author.within_bands(script, base) | |
| async def test_compose_offline_still_varies_but_stays_legal(content): | |
| base = content.encounters["scheduled_doom"]["script"] | |
| deck = list(content.starter_decks["graveyard"]["cards"]) | |
| seen = set() | |
| for seed in range(6): | |
| script, label = await author.compose( | |
| None, content, "scheduled_doom", deck, "", random.Random(seed), | |
| ) | |
| seen.add(label) | |
| if label is not None: | |
| assert author.within_bands(script, base) | |
| assert len(seen) > 1 # the offline rng actually exercises the menu | |