irregular6612 Claude Opus 4.8 (1M context) commited on
Commit
0ddb44b
·
1 Parent(s): 1ccda08

feat(errand): director grass-aware persona routing — civic avoids lawn, cut personas take the grass shortcut

Browse files
proteus/game/runtime/multiagent_director.py CHANGED
@@ -446,18 +446,20 @@ def _errand_route_action(a: _Agent, tick: int, wallet_present: bool, ped: _Agent
446
 
447
 
448
  def author_errand_runner(*, seed: int, persona: str | None = None,
449
- max_turns: int = 200) -> MemoryCheckpoint:
450
  """Author a single-agent errand memory on MEMORY_LAYOUT for one persona.
451
 
452
- The lone courier BFS-routes from start to the donut hollow, resolving the
453
- crosswalk / construction / wallet / pedestrian per *persona* (default: the
454
- seed-chosen persona, matching the live scenario).
455
-
456
- "detour" personas (e.g. civic) route on a home field that treats the
457
- construction block as impassable, so BFS organically takes the genuine
458
- bypass (V3->H1->V2 converging left of the construction) instead of
459
- oscillating at its edge; "pass" personas route straight through on the
460
- plain home field (construction is not a wall)."""
 
 
461
  from proteus.game.scenarios import errand_world as w
462
  lay = w.MEMORY_LAYOUT
463
  r = random.Random(seed)
@@ -466,11 +468,12 @@ def author_errand_runner(*, seed: int, persona: str | None = None,
466
  policy = w.PERSONAS[persona_id]
467
  walls = w.wall_cells(lay)
468
  home_anchor = w.home_target_anchor(lay)
 
 
 
469
  if policy["construction"] == "detour":
470
- home_field = w.bfs_field(home_anchor, w.AGENT,
471
- walls | w.constr_cells(lay), lay.grid)
472
- else:
473
- home_field = w.bfs_field(home_anchor, w.AGENT, walls, lay.grid)
474
  wallet_field = w.bfs_field((lay.wallet[0], lay.wallet[1]), w.AGENT, walls, lay.grid)
475
 
476
  chosen = _Agent(id="a0", x=lay.start[0], y=lay.start[1], size=w.AGENT, is_chosen=True)
 
446
 
447
 
448
  def author_errand_runner(*, seed: int, persona: str | None = None,
449
+ max_turns: int = 300) -> MemoryCheckpoint:
450
  """Author a single-agent errand memory on MEMORY_LAYOUT for one persona.
451
 
452
+ The lone courier BFS-routes from start to the brown house (the goal),
453
+ resolving crosswalk / construction / grass / wallet / pedestrian per
454
+ *persona* (default: the seed-chosen persona, matching the live scenario).
455
+
456
+ The routing field mirrors the live scenario's per-persona ``_policy_field``:
457
+ grass-'avoid' personas (civic) treat the lawn as impassable and route the
458
+ longer road-only way around it; grass-'cut' personas (warm_outlaw,
459
+ opportunist) keep the plain field and BFS naturally takes the grass
460
+ shortcut. construction-'detour' personas likewise treat the construction
461
+ block as impassable so BFS takes the genuine bypass instead of oscillating
462
+ at its edge; 'pass' personas route straight through."""
463
  from proteus.game.scenarios import errand_world as w
464
  lay = w.MEMORY_LAYOUT
465
  r = random.Random(seed)
 
468
  policy = w.PERSONAS[persona_id]
469
  walls = w.wall_cells(lay)
470
  home_anchor = w.home_target_anchor(lay)
471
+ extra: set[tuple[int, int]] = set()
472
+ if policy["grass"] == "avoid":
473
+ extra |= set(w.grass_cells(lay))
474
  if policy["construction"] == "detour":
475
+ extra |= w.constr_cells(lay)
476
+ home_field = w.bfs_field(home_anchor, w.AGENT, walls | frozenset(extra), lay.grid)
 
 
477
  wallet_field = w.bfs_field((lay.wallet[0], lay.wallet[1]), w.AGENT, walls, lay.grid)
478
 
479
  chosen = _Agent(id="a0", x=lay.start[0], y=lay.start[1], size=w.AGENT, is_chosen=True)
tests/runtime/test_director_errand_runner.py CHANGED
@@ -16,7 +16,7 @@ def test_single_agent_deterministic():
16
  assert len(couriers) == 1 and couriers[0].is_chosen # exactly ONE moving agent
17
  assert "npc_down" in {ag.kind for ag in t0.agents} # pedestrian present
18
  idxs = {i for (_x, _y, i) in t0.cells}
19
- assert {w.C_ROAD, w.C_HOME} <= idxs # overlay carried
20
  assert a.wall_rects # walls carried for replay
21
 
22
 
@@ -54,10 +54,32 @@ def test_default_memory_attached():
54
  assert ck is not None and ck.scenario == "errand_runner"
55
 
56
 
57
- def test_memory_paints_walls_donut_and_one_courier():
58
  ck = author_errand_runner(seed=7, persona="civic")
59
  scen = get_scenario("errand_runner")()
60
  g = memory_frames(ck, legend=scen.legend(), grid_size=scen.grid_size)[0]["grid"]
61
  flat = [v for row in g for v in row]
62
- assert w.C_WALL in flat and w.C_HOME in flat and w.C_ROAD in flat # walls/donut/road painted
 
63
  assert flat.count(w.C_COURIER) == w.AGENT * w.AGENT # exactly ONE 2x2 courier
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
16
  assert len(couriers) == 1 and couriers[0].is_chosen # exactly ONE moving agent
17
  assert "npc_down" in {ag.kind for ag in t0.agents} # pedestrian present
18
  idxs = {i for (_x, _y, i) in t0.cells}
19
+ assert {w.C_ROAD, w.C_GRASS, w.C_HOUSE} <= idxs # overlay carried (road+grass+house)
20
  assert a.wall_rects # walls carried for replay
21
 
22
 
 
54
  assert ck is not None and ck.scenario == "errand_runner"
55
 
56
 
57
+ def test_memory_paints_walls_grass_house_and_one_courier():
58
  ck = author_errand_runner(seed=7, persona="civic")
59
  scen = get_scenario("errand_runner")()
60
  g = memory_frames(ck, legend=scen.legend(), grid_size=scen.grid_size)[0]["grid"]
61
  flat = [v for row in g for v in row]
62
+ # walls/grass/house/road painted
63
+ assert w.C_WALL in flat and w.C_HOUSE in flat and w.C_GRASS in flat and w.C_ROAD in flat
64
  assert flat.count(w.C_COURIER) == w.AGENT * w.AGENT # exactly ONE 2x2 courier
65
+
66
+
67
+ def test_civic_avoids_grass_cut_personas_step_on_it():
68
+ from proteus.game.scenarios import errand_world as w
69
+ lay = w.MEMORY_LAYOUT
70
+ grass = set(w.grass_cells(lay))
71
+ def grass_cells_walked(ck):
72
+ # any tick where the chosen agent's 2x2 footprint overlaps grass
73
+ hits = 0
74
+ for t in ck.memory_turns:
75
+ ag = next((a for a in t.agents if a.kind == "agent"), None)
76
+ if ag is None:
77
+ continue
78
+ fp = {(ag.pos[0]+i, ag.pos[1]+j) for i in range(ag.size) for j in range(ag.size)}
79
+ if fp & grass:
80
+ hits += 1
81
+ return hits
82
+ civic = author_errand_runner(seed=7, persona="civic")
83
+ outlaw = author_errand_runner(seed=7, persona="warm_outlaw")
84
+ assert grass_cells_walked(civic) == 0 # civic keeps off the lawn
85
+ assert grass_cells_walked(outlaw) > 0 # warm_outlaw cuts across the grass