Spaces:
Runtime error
Runtime error
Commit ·
a629d19
1
Parent(s): 7e5166c
feat(memory): per-turn cells overlay channel + npc_down/npc_active colours in memory_frames
Browse files
proteus/game/runtime/memory.py
CHANGED
|
@@ -59,6 +59,13 @@ class MemoryTurn(BaseModel):
|
|
| 59 |
"""Per-sprite render states; non-empty ⇒ the multi-agent render path."""
|
| 60 |
resources: list[tuple[int, int]] = Field(default_factory=list)
|
| 61 |
"""Collectible resource cells still present this turn."""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 62 |
events: list[str] = Field(default_factory=list)
|
| 63 |
"""Narration for this turn, e.g. ``"a1 eaten"`` / ``"a0 got resource"``."""
|
| 64 |
|
|
@@ -203,6 +210,10 @@ def _predator_solid_offsets(facing: str) -> list[tuple[int, int]]:
|
|
| 203 |
return [(c, r) for r in range(3) for c in range(3) if (c, r) not in mouth]
|
| 204 |
|
| 205 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 206 |
def memory_frames(
|
| 207 |
checkpoint: MemoryCheckpoint, *, legend: dict[int, str], grid_size: tuple[int, int]
|
| 208 |
) -> list[dict]:
|
|
@@ -239,6 +250,9 @@ def memory_frames(
|
|
| 239 |
for y in range(max(0, ry0), min(h, ry1 + 1)):
|
| 240 |
for x in range(max(0, rx0), min(w, rx1 + 1)):
|
| 241 |
grid[y][x] = wall_idx
|
|
|
|
|
|
|
|
|
|
| 242 |
for (fx, fy) in mt.resources:
|
| 243 |
if 0 <= fx < w and 0 <= fy < h:
|
| 244 |
grid[fy][fx] = food_idx
|
|
@@ -250,6 +264,8 @@ def memory_frames(
|
|
| 250 |
x, y = ag.pos[0] + c, ag.pos[1] + r
|
| 251 |
if 0 <= x < w and 0 <= y < h:
|
| 252 |
grid[y][x] = predator_idx
|
|
|
|
|
|
|
| 253 |
else:
|
| 254 |
color = focal_idx if ag.is_chosen else distractor_idx
|
| 255 |
for r in range(ag.size):
|
|
|
|
| 59 |
"""Per-sprite render states; non-empty ⇒ the multi-agent render path."""
|
| 60 |
resources: list[tuple[int, int]] = Field(default_factory=list)
|
| 61 |
"""Collectible resource cells still present this turn."""
|
| 62 |
+
cells: list[tuple[int, int, int]] = Field(default_factory=list)
|
| 63 |
+
"""Generic coloured overlay cells ``(x, y, palette_idx)`` for this turn.
|
| 64 |
+
|
| 65 |
+
Painted after walls and before agents in :func:`memory_frames`. Lets the
|
| 66 |
+
errand_runner director draw per-tick world objects (the recolouring
|
| 67 |
+
traffic-light bar, roads, construction, wallet, home) without a bespoke
|
| 68 |
+
field per object type. Empty for all legacy single-/multi-agent turns."""
|
| 69 |
events: list[str] = Field(default_factory=list)
|
| 70 |
"""Narration for this turn, e.g. ``"a1 eaten"`` / ``"a0 got resource"``."""
|
| 71 |
|
|
|
|
| 210 |
return [(c, r) for r in range(3) for c in range(3) if (c, r) not in mouth]
|
| 211 |
|
| 212 |
|
| 213 |
+
# NPC agent kinds -> palette colour (errand_runner fallen-pedestrian rescue).
|
| 214 |
+
_NPC_COLOR: dict[str, int] = {"npc_down": 11, "npc_active": 14} # yellow -> green
|
| 215 |
+
|
| 216 |
+
|
| 217 |
def memory_frames(
|
| 218 |
checkpoint: MemoryCheckpoint, *, legend: dict[int, str], grid_size: tuple[int, int]
|
| 219 |
) -> list[dict]:
|
|
|
|
| 250 |
for y in range(max(0, ry0), min(h, ry1 + 1)):
|
| 251 |
for x in range(max(0, rx0), min(w, rx1 + 1)):
|
| 252 |
grid[y][x] = wall_idx
|
| 253 |
+
for (cx, cy, cidx) in mt.cells:
|
| 254 |
+
if 0 <= cx < w and 0 <= cy < h:
|
| 255 |
+
grid[cy][cx] = cidx
|
| 256 |
for (fx, fy) in mt.resources:
|
| 257 |
if 0 <= fx < w and 0 <= fy < h:
|
| 258 |
grid[fy][fx] = food_idx
|
|
|
|
| 264 |
x, y = ag.pos[0] + c, ag.pos[1] + r
|
| 265 |
if 0 <= x < w and 0 <= y < h:
|
| 266 |
grid[y][x] = predator_idx
|
| 267 |
+
elif ag.kind in _NPC_COLOR:
|
| 268 |
+
paint(grid, ag.pos[0], ag.pos[1], ag.size, _NPC_COLOR[ag.kind])
|
| 269 |
else:
|
| 270 |
color = focal_idx if ag.is_chosen else distractor_idx
|
| 271 |
for r in range(ag.size):
|
tests/runtime/test_memory_frames_overlay.py
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# tests/runtime/test_memory_frames_overlay.py
|
| 2 |
+
"""errand_runner memory render: generic `cells` overlay + npc agent colours.
|
| 3 |
+
|
| 4 |
+
Legacy turns (no cells, kind in {agent,predator}) stay byte-identical.
|
| 5 |
+
"""
|
| 6 |
+
from proteus.game.runtime.memory import (
|
| 7 |
+
AgentFrame, MemoryCheckpoint, MemoryTurn, memory_frames,
|
| 8 |
+
)
|
| 9 |
+
|
| 10 |
+
_LEGEND = {5: ".", 1: "A", 2: "B", 3: "#", 14: "F"}
|
| 11 |
+
_GRID = (12, 12)
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
def _ck(turn):
|
| 15 |
+
return MemoryCheckpoint(
|
| 16 |
+
model="m", scenario="errand_runner", difficulty="easy", created_at="x",
|
| 17 |
+
outcome="survived", transparent_prompt="p", memory_turns=[turn],
|
| 18 |
+
)
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
def test_cells_overlay_painted_over_background():
|
| 22 |
+
turn = MemoryTurn(
|
| 23 |
+
turn_idx=1, frame_ascii="", action="up", focal_pos=(0, 0), predator_pos=(0, 0),
|
| 24 |
+
agents=[AgentFrame(id="a0", kind="agent", pos=(0, 0), size=2)],
|
| 25 |
+
cells=[(7, 3, 8), (7, 4, 8), (7, 5, 8)], # red (8) traffic-light bar
|
| 26 |
+
)
|
| 27 |
+
grid = memory_frames(_ck(turn), legend=_LEGEND, grid_size=_GRID)[0]["grid"]
|
| 28 |
+
assert grid[3][7] == 8 and grid[4][7] == 8 and grid[5][7] == 8
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
def test_agents_paint_over_cells():
|
| 32 |
+
# A candidate body on a road cell must render as the body, not the road.
|
| 33 |
+
turn = MemoryTurn(
|
| 34 |
+
turn_idx=1, frame_ascii="", action="up", focal_pos=(0, 0), predator_pos=(0, 0),
|
| 35 |
+
agents=[AgentFrame(id="a0", kind="agent", pos=(2, 2), size=2, is_chosen=True)],
|
| 36 |
+
cells=[(2, 2, 3)], # neutral road under the agent's top-left cell
|
| 37 |
+
)
|
| 38 |
+
grid = memory_frames(_ck(turn), legend=_LEGEND, grid_size=_GRID)[0]["grid"]
|
| 39 |
+
assert grid[2][2] == 1 # focal colour wins over the overlay
|
| 40 |
+
|
| 41 |
+
|
| 42 |
+
def test_npc_down_is_yellow_and_active_is_green():
|
| 43 |
+
turn = MemoryTurn(
|
| 44 |
+
turn_idx=1, frame_ascii="", action="up", focal_pos=(0, 0), predator_pos=(0, 0),
|
| 45 |
+
agents=[
|
| 46 |
+
AgentFrame(id="ped", kind="npc_down", pos=(2, 2), size=2),
|
| 47 |
+
AgentFrame(id="ped2", kind="npc_active", pos=(8, 8), size=2),
|
| 48 |
+
],
|
| 49 |
+
)
|
| 50 |
+
grid = memory_frames(_ck(turn), legend=_LEGEND, grid_size=_GRID)[0]["grid"]
|
| 51 |
+
assert grid[2][2] == 11 # npc_down -> yellow
|
| 52 |
+
assert grid[8][8] == 14 # npc_active -> green
|
| 53 |
+
|
| 54 |
+
|
| 55 |
+
def test_legacy_turn_without_cells_unaffected():
|
| 56 |
+
turn = MemoryTurn(turn_idx=1, frame_ascii="", action="up", focal_pos=(0, 0),
|
| 57 |
+
predator_pos=(0, 0),
|
| 58 |
+
agents=[AgentFrame(id="a0", kind="agent", pos=(0, 0), size=2)])
|
| 59 |
+
out = memory_frames(_ck(turn), legend=_LEGEND, grid_size=_GRID)[0]
|
| 60 |
+
assert "grid" in out and out["events"] == []
|