case0 / tests /test_runtime_pool.py
HusseinEid's picture
Per-case visuals, incident vignettes, guided play, no-repeat dealing, four new crime kinds (#4)
16ff49b
Raw
History Blame
2.85 kB
"""Never-repeat pool logic: excluded case ids are never dealt, the shared rotation is
untouched by per-player filtering, and the fallback ladder degrades gracefully."""
from __future__ import annotations
from test_case_adapter import _casefile
from case_zero.api.runtime import GameRuntime
def _pool(ids: list[str]):
base = _casefile()
return [base.model_copy(update={"case_id": cid}) for cid in ids]
def _runtime_with(ids: list[str]) -> GameRuntime:
rt = GameRuntime()
rt._prebaked_loaded = True
rt._prebaked = _pool(ids)
return rt
def test_rotation_unchanged_without_exclusions():
rt = _runtime_with(["A", "B", "C"])
dealt = [rt._take_prebaked().case_id for _ in range(3)]
assert dealt == ["A", "B", "C"]
assert rt._prebaked_idx == 3
def test_excluded_ids_never_dealt_and_rotation_untouched():
rt = _runtime_with(["A", "B", "C"])
for _ in range(20):
case = rt._take_prebaked({"A", "C"})
assert case.case_id == "B"
assert rt._prebaked_idx == 0 # per-player picks never advance the shared rotation
def test_all_excluded_returns_none():
rt = _runtime_with(["A", "B"])
assert rt._take_prebaked({"A", "B"}) is None
def test_excluded_buffered_case_stays_buffered():
rt = _runtime_with(["A"])
buffered = _pool(["FRESH"])[0]
rt._buffer = buffered
assert rt._take_buffered({"FRESH"}) is None
assert rt._buffer is buffered # still there for another player
assert rt._take_buffered(set()).case_id == "FRESH"
assert rt._buffer is None
def test_pressure_heuristic_spawns_generation(monkeypatch):
rt = _runtime_with(["A", "B", "C"])
spawned = []
monkeypatch.setattr(rt, "available", lambda: True)
monkeypatch.setattr(rt, "_spawn_gen", lambda: spawned.append(1))
monkeypatch.setattr(rt, "_register", lambda case: (case.case_id, "run"))
monkeypatch.setattr(rt, "_maybe_refill", lambda: None)
# two of three already played -> fewer than 3 unplayed -> pressure spawn
out = rt.new_generated_run(exclude=["A", "B"])
assert out == ("C", "run")
assert spawned
def test_lru_fallback_when_generation_fails(monkeypatch):
rt = _runtime_with(["A", "B"])
monkeypatch.setattr(rt, "available", lambda: True)
monkeypatch.setattr(rt, "_spawn_gen", lambda: None)
monkeypatch.setattr(rt, "_maybe_refill", lambda: None)
def boom(seed, **kw):
raise RuntimeError("no model")
monkeypatch.setattr(rt, "_generate", boom)
registered = []
def fake_load(case_id):
registered.append(case_id)
return (case_id, "run")
monkeypatch.setattr(rt, "load_generated_run", fake_load)
# everything played: generation fails -> oldest played id is re-dealt
out = rt.new_generated_run(exclude=["A", "B"])
assert out == ("A", "run")
assert registered == ["A"]