thousand-token-terrarium / tests /test_pattern_world.py
openaibot's picture
Submission deploy: app + terrarium + tracked artifacts + requirements
4755af4 verified
Raw
History Blame Contribute Delete
7.54 kB
"""Track C — tests for the minimal hidden-pattern world and the probe harness.
Fast, deterministic, no network. The LLM path is exercised only through a stub
so the survival-uplift plumbing is covered without spending tokens.
"""
from __future__ import annotations
import random
import pytest
from terrarium.pattern_world import World, WorldConfig
from terrarium import pattern_probe as P
# --------------------------------------------------------------------------- #
# World
# --------------------------------------------------------------------------- #
def test_determinism_same_seed():
cfg = WorldConfig.for_pattern("phenology")
a = World.create(cfg, seed=3)
b = World.create(cfg, seed=3)
assert a.agent == b.agent
assert a.water == b.water
assert a.marsh == b.marsh
for _ in range(40):
a.step((1, 0))
b.step((1, 0))
assert (a.tick, a.energy, a.agent, a.food_eaten) == (b.tick, b.energy, b.agent, b.food_eaten)
def test_water_is_impassable():
cfg = WorldConfig.for_pattern("phenology")
w = World.create(cfg, seed=1)
for cell in w.water:
assert not w.passable(cell)
def test_phenology_cue_precedes_reward_in_time_and_space():
"""The whole point: rain (cue) appears, then food (reward) ripens in the
marsh ~ripen_delay ticks later. Cue and reward are separated in time and
the reward is confined to the marsh near water."""
cfg = WorldConfig.for_pattern("phenology")
w = World.create(cfg, seed=2)
w.energy = 10_000 # immortal observer
rains, spawns = [], []
marsh = set(w.marsh)
for _ in range(120):
before = len(w.food)
w.step((0, 0))
if w.rained_this_tick:
rains.append(w.tick)
if len(w.food) > before:
spawns.append(w.tick)
# every freshly ripened food sits in the marsh
for f in w.food:
assert f.pos in marsh
assert rains, "rain must fire"
assert spawns, "food must ripen"
# each spawn is preceded by a rain exactly ripen_delay earlier
for s in spawns:
assert (s - cfg.ripen_delay) in rains
def test_depletion_patch_relocates_far():
cfg = WorldConfig.for_pattern("depletion")
w = World.create(cfg, seed=4)
w.energy = 10_000
origins = [w.patch_origin]
for _ in range(120):
w.step((0, 0))
if w.patch_origin != origins[-1]:
origins.append(w.patch_origin)
assert len(origins) >= 3, "patch should relocate several times"
# consecutive relocations land in a different region
for prev, nxt in zip(origins, origins[1:]):
dist = abs(prev[0] - nxt[0]) + abs(prev[1] - nxt[1])
assert dist >= 2
def test_depletion_eating_out_a_patch_respawns_elsewhere():
cfg = WorldConfig.for_pattern("depletion")
w = World.create(cfg, seed=4)
w.energy = 10_000
first_origin = w.patch_origin
# teleport-eat every food cell of the current patch
cells = [f.pos for f in w.food]
for c in cells:
w.agent = c
w.step((0, 0))
assert w.patch_origin != first_origin
assert w.food, "a new patch must exist after exhaustion"
def test_starvation_terminates():
cfg = WorldConfig.for_pattern("phenology")
w = World.create(cfg, seed=0)
for _ in range(cfg.max_ticks):
if not w.alive:
break
w.step((1, 0))
# with no eating an agent cannot reach max_ticks
assert not w.alive or w.energy <= cfg.start_energy
# --------------------------------------------------------------------------- #
# Probe — local policies establish the oracle>>reflex precondition
# --------------------------------------------------------------------------- #
@pytest.mark.parametrize("pattern", ["phenology", "depletion"])
def test_oracle_beats_reflex(pattern):
seeds = list(range(8))
reflex = P.run_condition(pattern, "reflex", seeds)
oracle = P.run_condition(pattern, "oracle", seeds)
# the world must be winnable-with-reasoning, else the LLM test is moot
assert oracle["mean_ticks"] > reflex["mean_ticks"]
assert oracle["mean_food"] > reflex["mean_food"]
def test_phenology_gap_is_large():
seeds = list(range(8))
reflex = P.run_condition("phenology", "reflex", seeds)
oracle = P.run_condition("phenology", "oracle", seeds)
# featured pattern: the oracle should roughly double survival ticks
assert oracle["mean_ticks"] > reflex["mean_ticks"] * 1.6
def test_memory_is_partial_not_godmode():
"""Memory must only contain things within vision; the full grid leaks
nothing it didn't perceive."""
cfg = WorldConfig.for_pattern("phenology")
w = World.create(cfg, seed=5)
mem = P.Memory()
for _ in range(30):
mem.observe(w)
w.step((1, 0))
# every remembered water tile was actually water
wset = set(w.water)
assert mem.water_seen.issubset(wset)
# and the agent cannot have seen the entire river through a radius-2 window
assert len(mem.water_seen) <= len(wset)
# --------------------------------------------------------------------------- #
# Probe — LLM plumbing via a stub (no network)
# --------------------------------------------------------------------------- #
def test_llm_episode_with_stub(monkeypatch):
"""An oracle-quality stub 'LLM' that names the marsh should survive as long
as the real oracle, proving the goal/eat/step plumbing is correct."""
cfg = WorldConfig.for_pattern("phenology")
def fake_call(settings, narration, timeout=45):
return {"goal": [0, 0], "reason": "stub", "rule": "after rain food ripens in the marsh near water later"}
monkeypatch.setattr(P, "call_openrouter", fake_call)
# Build a stub world reference to expose the marsh through the goal: we
# cannot see cfg internals from the stub, so instead patch step_toward to
# always chase the nearest marsh tile -> emulates a perfect goal-setter.
real_step_toward = P.step_toward
def marsh_seeking(world, target, rng):
if not [f for f in world.food if f.pos in set(world.visible_cells())] and world.marsh:
target = min(world.marsh, key=lambda p: abs(p[0] - world.agent[0]) + abs(p[1] - world.agent[1]))
return real_step_toward(world, target, rng)
monkeypatch.setattr(P, "step_toward", marsh_seeking)
settings = object()
res = P.run_llm_episode(cfg, seed=2, settings=settings, decide_every=5)
assert res.ticks_alive > 100 # near-oracle survival
assert res.rule_articulated == 1.0 # the stub rule matches ground truth
def test_rule_matcher_phenology():
assert P.rule_matches("phenology", "After it rains, food ripens near the water a few ticks later")
assert not P.rule_matches("phenology", "Food appears where x+y is even")
assert not P.rule_matches("phenology", "")
def test_rule_matcher_depletion():
assert P.rule_matches("depletion", "When a patch is eaten out, food regenerates elsewhere")
assert not P.rule_matches("depletion", "Food is always near water")
def test_go_no_go_structure():
reflex = {"mean_ticks": 40, "mean_food": 5, "survival_rate": 0.1}
oracle = {"mean_ticks": 120, "mean_food": 20, "survival_rate": 0.9}
llm = {"mean_ticks": 90, "mean_food": 15, "survival_rate": 0.5, "mean_rule_articulation": 0.4}
v = P.go_no_go("phenology", reflex, oracle, llm)
assert v["world_winnable_with_reasoning"] is True
assert v["cognition_beats_reflex"] is True
assert v["GO"] is True
assert v["llm_vs_reflex_ticks_uplift"] > 0