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