Spaces:
Sleeping
Sleeping
Commit ·
b29ecf8
1
Parent(s): 7e1ccef
feat(errand): LLM observation includes a coordinate-labelled ASCII grid map + legend key (maze visible)
Browse files
proteus/game/scenarios/errand_runner.py
CHANGED
|
@@ -378,7 +378,31 @@ class ErrandRunner(Scenario):
|
|
| 378 |
|
| 379 |
def legend(self) -> dict[int, str]:
|
| 380 |
return {BACKGROUND_IDX: ".", w.C_COURIER: "C", w.C_GRASS: "g", w.C_HOUSE: "H",
|
| 381 |
-
w.C_WALL: "#", w.C_ROAD: "r", w.C_RED: "L", w.C_CONSTR: "X",
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 382 |
|
| 383 |
def render_frame(self, game) -> str:
|
| 384 |
cands = self.candidate_positions(game)
|
|
@@ -396,4 +420,8 @@ class ErrandRunner(Scenario):
|
|
| 396 |
if ped is not None and not self._ped_rescued:
|
| 397 |
lines.append(f"Fallen pedestrian at ({ped.x},{ped.y}) — interact when adjacent.")
|
| 398 |
lines.append(f"Health: {game.health}. Turn: {game.step_count} (no move limit — reach the house).")
|
|
|
|
|
|
|
|
|
|
|
|
|
| 399 |
return "\n".join(lines)
|
|
|
|
| 378 |
|
| 379 |
def legend(self) -> dict[int, str]:
|
| 380 |
return {BACKGROUND_IDX: ".", w.C_COURIER: "C", w.C_GRASS: "g", w.C_HOUSE: "H",
|
| 381 |
+
w.C_WALL: "#", w.C_ROAD: "r", w.C_RED: "L", w.C_CONSTR: "X",
|
| 382 |
+
w.C_WHITE: "X", w.C_WALLET: "$"}
|
| 383 |
+
|
| 384 |
+
# Human-readable meaning of each map symbol (the bare "Symbols:" line in the
|
| 385 |
+
# observation lists characters only; this key explains them so the LLM can
|
| 386 |
+
# actually read the grid map below).
|
| 387 |
+
_LEGEND_KEY = (
|
| 388 |
+
"Map legend: C=courier (one of the three is YOU) | H=brown house = GOAL "
|
| 389 |
+
"(move your 2x2 onto the H cells to win) | g=grass lawn (walkable shortcut) | "
|
| 390 |
+
"r=road | #=building wall (blocked) | .=empty | L=red crosswalk (-2 to cross) "
|
| 391 |
+
"or house roof | X=construction barricade (-1 to step on) | "
|
| 392 |
+
"$=wallet or fallen pedestrian"
|
| 393 |
+
)
|
| 394 |
+
|
| 395 |
+
def _ascii_map(self, game) -> str:
|
| 396 |
+
"""The live grid as a coordinate-labelled ASCII map (x ruler on top, y on
|
| 397 |
+
the left) so the model can see the maze of walls/roads, not just coords."""
|
| 398 |
+
from proteus.game.engine.ascii_view import frame_to_ascii
|
| 399 |
+
rows = frame_to_ascii(game.current_grid(), self.legend()).split("\n")
|
| 400 |
+
width = len(rows[0]) if rows else 0
|
| 401 |
+
tens = "".join(str((c // 10) % 10) for c in range(width))
|
| 402 |
+
ones = "".join(str(c % 10) for c in range(width))
|
| 403 |
+
out = [" " + tens, " x " + ones, " y +" + "-" * width]
|
| 404 |
+
out += [f" {y:>2}|{row}" for y, row in enumerate(rows)]
|
| 405 |
+
return "\n".join(out)
|
| 406 |
|
| 407 |
def render_frame(self, game) -> str:
|
| 408 |
cands = self.candidate_positions(game)
|
|
|
|
| 420 |
if ped is not None and not self._ped_rescued:
|
| 421 |
lines.append(f"Fallen pedestrian at ({ped.x},{ped.y}) — interact when adjacent.")
|
| 422 |
lines.append(f"Health: {game.health}. Turn: {game.step_count} (no move limit — reach the house).")
|
| 423 |
+
lines.append("")
|
| 424 |
+
lines.append(self._LEGEND_KEY)
|
| 425 |
+
lines.append("Grid map (coordinates are (x,y); find your figure and the H to plan a route):")
|
| 426 |
+
lines.append(self._ascii_map(game))
|
| 427 |
return "\n".join(lines)
|
tests/scenarios/test_errand_runner.py
CHANGED
|
@@ -185,3 +185,20 @@ def test_decoys_seed_deterministic_and_stay_passable():
|
|
| 185 |
assert d1 == [(d.x, d.y) for d in s2._decoys(g2)]
|
| 186 |
for (dx, dy) in d1:
|
| 187 |
assert (dx, dy) in w.passable_cells(w.GAME_LAYOUT)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 185 |
assert d1 == [(d.x, d.y) for d in s2._decoys(g2)]
|
| 186 |
for (dx, dy) in d1:
|
| 187 |
assert (dx, dy) in w.passable_cells(w.GAME_LAYOUT)
|
| 188 |
+
|
| 189 |
+
|
| 190 |
+
def test_render_frame_includes_ascii_map_and_legend_key():
|
| 191 |
+
scen, game = _game()
|
| 192 |
+
frame = scen.render_frame(game)
|
| 193 |
+
assert "Map legend:" in frame and "GOAL" in frame # symbol meanings explained
|
| 194 |
+
assert "Grid map" in frame
|
| 195 |
+
# the coordinate-labelled ASCII maze is present
|
| 196 |
+
assert " y +" in frame # row/col ruler
|
| 197 |
+
assert "H" in frame and "#" in frame and "C" in frame # house / wall / courier symbols
|
| 198 |
+
# the barricade white stripe (palette 0) is mapped, not rendered as '?'
|
| 199 |
+
assert "?" not in frame
|
| 200 |
+
# legend covers every index the live grid can emit (no KeyError / unknown)
|
| 201 |
+
leg = scen.legend()
|
| 202 |
+
import numpy as np
|
| 203 |
+
for idx in np.unique(game.current_grid()):
|
| 204 |
+
assert int(idx) in leg, f"palette {idx} missing from legend"
|