Spaces:
Runtime error
Runtime error
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 =
|
| 450 |
"""Author a single-agent errand memory on MEMORY_LAYOUT for one persona.
|
| 451 |
|
| 452 |
-
The lone courier BFS-routes from start to the
|
| 453 |
-
crosswalk / construction / wallet / pedestrian per
|
| 454 |
-
seed-chosen persona, matching the live scenario).
|
| 455 |
-
|
| 456 |
-
|
| 457 |
-
|
| 458 |
-
|
| 459 |
-
|
| 460 |
-
|
|
|
|
|
|
|
| 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 |
-
|
| 471 |
-
|
| 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.
|
| 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
|
| 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 |
-
|
|
|
|
| 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
|