irregular6612 Claude Opus 4.8 (1M context) commited on
Commit
09f65bb
·
1 Parent(s): 65aa2ad

feat(memory): store + replay food_cells (paint under agents)

Browse files

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

proteus/grid/scenario.py CHANGED
@@ -237,6 +237,13 @@ class Scenario(ABC):
237
  """
238
  return []
239
 
 
 
 
 
 
 
 
240
  def record_focal_move(self, action: str) -> None:
241
  """Record the focal's last committed move (no-op by default).
242
 
 
237
  """
238
  return []
239
 
240
+ def food_cells(self) -> list[tuple[int, int]]:
241
+ """Static 1x1 food cells of the built world, for replay/observation.
242
+
243
+ Default: none. Scenarios with food override this (see ``memory_gen``).
244
+ """
245
+ return []
246
+
247
  def record_focal_move(self, action: str) -> None:
248
  """Record the focal's last committed move (no-op by default).
249
 
proteus/runtime/memory.py CHANGED
@@ -70,6 +70,8 @@ class MemoryCheckpoint(BaseModel):
70
  Populated for scenarios whose observation is prose (no ASCII grid), so the
71
  web replay can paint walls it cannot recover from ``frame_ascii``. Empty for
72
  grid scenarios (their walls are already in each ``frame_ascii``)."""
 
 
73
 
74
 
75
  def _safe(name: str) -> str:
@@ -164,6 +166,7 @@ def memory_frames(
164
  focal_idx = sym2idx.get("A", 1)
165
  predator_idx = sym2idx.get("B", 2)
166
  wall_idx = sym2idx.get("#", 3)
 
167
  w, h = grid_size
168
  out: list[dict] = []
169
 
@@ -182,6 +185,9 @@ def memory_frames(
182
  for y in range(max(0, ry0), min(h, ry1 + 1)):
183
  for x in range(max(0, rx0), min(w, rx1 + 1)):
184
  grid[y][x] = wall_idx
 
 
 
185
  paint(grid, mt.predator_pos[0], mt.predator_pos[1], 5, predator_idx)
186
  paint(grid, mt.focal_pos[0], mt.focal_pos[1], 3, focal_idx)
187
  out.append({"turn_idx": mt.turn_idx, "action": mt.action, "grid": grid})
 
70
  Populated for scenarios whose observation is prose (no ASCII grid), so the
71
  web replay can paint walls it cannot recover from ``frame_ascii``. Empty for
72
  grid scenarios (their walls are already in each ``frame_ascii``)."""
73
+ food_cells: list[tuple[int, int]] = Field(default_factory=list)
74
+ """Static 1x1 food cells of the episode world (same rationale as wall_rects)."""
75
 
76
 
77
  def _safe(name: str) -> str:
 
166
  focal_idx = sym2idx.get("A", 1)
167
  predator_idx = sym2idx.get("B", 2)
168
  wall_idx = sym2idx.get("#", 3)
169
+ food_idx = sym2idx.get("F", 14)
170
  w, h = grid_size
171
  out: list[dict] = []
172
 
 
185
  for y in range(max(0, ry0), min(h, ry1 + 1)):
186
  for x in range(max(0, rx0), min(w, rx1 + 1)):
187
  grid[y][x] = wall_idx
188
+ for (fx, fy) in checkpoint.food_cells:
189
+ if 0 <= fx < w and 0 <= fy < h:
190
+ grid[fy][fx] = food_idx
191
  paint(grid, mt.predator_pos[0], mt.predator_pos[1], 5, predator_idx)
192
  paint(grid, mt.focal_pos[0], mt.focal_pos[1], 3, focal_idx)
193
  out.append({"turn_idx": mt.turn_idx, "action": mt.action, "grid": grid})
proteus/runtime/memory_gen.py CHANGED
@@ -121,4 +121,5 @@ def generate_memory(
121
  transparent_prompt=brief,
122
  persona_weight_id=persona.persona_weight_id if persona else None,
123
  wall_rects=list(scenario.wall_rects()),
 
124
  )
 
121
  transparent_prompt=brief,
122
  persona_weight_id=persona.persona_weight_id if persona else None,
123
  wall_rects=list(scenario.wall_rects()),
124
+ food_cells=list(scenario.food_cells()),
125
  )
tests/runtime/test_memory_frames.py CHANGED
@@ -34,3 +34,27 @@ def test_prose_frame_paints_positions_and_walls():
34
  assert g[2][2] != 3 # focal painted over its cells
35
  assert g[3][3] == 3 # wall cell visible
36
  assert g[0][4] == 5 # background elsewhere
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
34
  assert g[2][2] != 3 # focal painted over its cells
35
  assert g[3][3] == 3 # wall cell visible
36
  assert g[0][4] == 5 # background elsewhere
37
+
38
+
39
+ def test_prose_frame_paints_food_cells():
40
+ ck = _ck(
41
+ [MemoryTurn(turn_idx=1, frame_ascii="Open field 8x8. You are A ...",
42
+ action="up", focal_pos=(0, 0), predator_pos=(6, 6))],
43
+ wall_rects=[(5, 1, 5, 1)], # a clear cell (not under focal/predator/food)
44
+ )
45
+ ck.food_cells = [(4, 1), (1, 4)]
46
+ g = memory_frames(ck, legend=_GRID_LEGEND, grid_size=(8, 8))[0]["grid"]
47
+ assert g[1][4] == 14 and g[4][1] == 14 # food (idx 14) painted
48
+ assert g[1][5] == 3 # wall still there
49
+ assert g[0][0] == 1 # focal still on top
50
+
51
+
52
+ def test_checkpoint_food_cells_roundtrips():
53
+ from proteus.runtime.memory import MemoryCheckpoint
54
+ ck = MemoryCheckpoint(
55
+ model="m", scenario="pack_evade", difficulty="easy", seed=1,
56
+ created_at="t", outcome="survived", transparent_prompt="x",
57
+ food_cells=[(3, 4)],
58
+ )
59
+ ck2 = MemoryCheckpoint.model_validate_json(ck.model_dump_json())
60
+ assert ck2.food_cells == [(3, 4)]