irregular6612 commited on
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", w.C_WALLET: "$"}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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"