# tests/runtime/test_director_errand_runner.py """author_errand_runner: single-agent persona route on MEMORY_LAYOUT + 3 variants.""" from proteus.game.runtime.multiagent_director import ( author_errand_runner, author_errand_runner_variants) from proteus.game.runtime.memory import memory_frames from proteus.game.scenarios.base import get_scenario from proteus.game.scenarios import errand_world as w def test_single_agent_deterministic(): a = author_errand_runner(seed=7, persona="civic") b = author_errand_runner(seed=7, persona="civic") assert a.model_dump_json() == b.model_dump_json() t0 = a.memory_turns[0] couriers = [ag for ag in t0.agents if ag.kind == "agent"] assert len(couriers) == 1 and couriers[0].is_chosen # exactly ONE moving agent assert "npc_down" in {ag.kind for ag in t0.agents} # pedestrian present idxs = {i for (_x, _y, i) in t0.cells} assert {w.C_ROAD, w.C_GRASS, w.C_HOUSE} <= idxs # overlay carried (road+grass+house) assert a.wall_rects # walls carried for replay def test_persona_reaches_home(): for pid in ("civic", "warm_outlaw", "opportunist"): ck = author_errand_runner(seed=7, persona=pid) assert ck.memory_turns[-1].events == ["arrived home"], pid assert ck.persona_weight_id == pid def test_opportunist_grabs_wallet_civic_does_not(): civic = author_errand_runner(seed=7, persona="civic") opp = author_errand_runner(seed=7, persona="opportunist") def wallet_ever_gone(ck): return any(all(i != w.C_WALLET for (_x, _y, i) in t.cells) for t in ck.memory_turns) assert wallet_ever_gone(opp) and not wallet_ever_gone(civic) def test_variants_returns_three_labelled(): v = author_errand_runner_variants(seed=7) assert set(v) == {"civic", "warm_outlaw", "opportunist"} assert all(ck.scenario == "errand_runner" for ck in v.values()) def test_frames_render_and_have_single_courier(): ck = author_errand_runner(seed=7, persona="civic") scen = get_scenario("errand_runner")() frames = memory_frames(ck, legend=scen.legend(), grid_size=scen.grid_size) assert frames and "grid" in frames[0] def test_default_memory_attached(): scen = get_scenario("errand_runner")() ck = scen.default_memory(7, None) assert ck is not None and ck.scenario == "errand_runner" def test_memory_paints_walls_grass_house_and_one_courier(): ck = author_errand_runner(seed=7, persona="civic") scen = get_scenario("errand_runner")() g = memory_frames(ck, legend=scen.legend(), grid_size=scen.grid_size)[0]["grid"] flat = [v for row in g for v in row] # walls/grass/house/road painted assert w.C_WALL in flat and w.C_HOUSE in flat and w.C_GRASS in flat and w.C_ROAD in flat assert flat.count(w.C_COURIER) == w.AGENT * w.AGENT # exactly ONE 2x2 courier def test_memory_text_path_carries_position_and_decision_context(): """The LLM-visible memory (render_memory_block) must convey WHERE the persona was each turn and the salient decision context, not a bare 'errand tN' placeholder — otherwise the text handover memory has no spatial grounding and the promised "watch how you handled the crosswalk/wallet/pedestrian" is empty. """ from proteus.game.runtime.memory import render_memory_block opp = author_errand_runner(seed=7, persona="opportunist") block = render_memory_block(opp) # placeholder gone; every turn carries the courier's coordinates. assert "errand t" not in block assert block.count("you@(") == len(opp.memory_turns) # the opportunist grabs the wallet -> the wallet context surfaces in memory; # the civic citizen never stands on it, so its memory shows no wallet pickup. assert "wallet" in block.lower() civic = author_errand_runner(seed=7, persona="civic") assert "wallet" not in render_memory_block(civic).lower() def test_civic_avoids_grass_cut_personas_step_on_it(): from proteus.game.scenarios import errand_world as w lay = w.MEMORY_LAYOUT grass = set(w.grass_cells(lay)) def grass_cells_walked(ck): # any tick where the chosen agent's 2x2 footprint overlaps grass hits = 0 for t in ck.memory_turns: ag = next((a for a in t.agents if a.kind == "agent"), None) if ag is None: continue fp = {(ag.pos[0]+i, ag.pos[1]+j) for i in range(ag.size) for j in range(ag.size)} if fp & grass: hits += 1 return hits civic = author_errand_runner(seed=7, persona="civic") outlaw = author_errand_runner(seed=7, persona="warm_outlaw") assert grass_cells_walked(civic) == 0 # civic keeps off the lawn assert grass_cells_walked(outlaw) > 0 # warm_outlaw cuts across the grass