Spaces:
Runtime error
Runtime error
Commit ·
6a4d305
1
Parent(s): ae092aa
feat(memory): memory_frames() reconstructs per-turn color grids
Browse filesCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- proteus/runtime/memory.py +59 -0
- tests/runtime/test_memory_frames.py +36 -0
proteus/runtime/memory.py
CHANGED
|
@@ -127,3 +127,62 @@ def render_memory_block(checkpoint: MemoryCheckpoint) -> str:
|
|
| 127 |
parts.append(mt.frame_ascii)
|
| 128 |
parts.append(f" you chose: {mt.action}")
|
| 129 |
return "\n".join(parts)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 127 |
parts.append(mt.frame_ascii)
|
| 128 |
parts.append(f" you chose: {mt.action}")
|
| 129 |
return "\n".join(parts)
|
| 130 |
+
|
| 131 |
+
|
| 132 |
+
def _invert_legend(legend: dict[int, str]) -> dict[str, int]:
|
| 133 |
+
return {sym: idx for idx, sym in legend.items()}
|
| 134 |
+
|
| 135 |
+
|
| 136 |
+
def _ascii_to_grid(frame: str, sym2idx: dict[str, int]) -> list[list[int]] | None:
|
| 137 |
+
"""Parse *frame* as a rectangular grid of legend symbols, else None."""
|
| 138 |
+
lines = frame.split("\n")
|
| 139 |
+
if len(lines) < 2 or len({len(ln) for ln in lines}) != 1 or not lines[0]:
|
| 140 |
+
return None
|
| 141 |
+
grid: list[list[int]] = []
|
| 142 |
+
for ln in lines:
|
| 143 |
+
row: list[int] = []
|
| 144 |
+
for ch in ln:
|
| 145 |
+
if ch not in sym2idx:
|
| 146 |
+
return None
|
| 147 |
+
row.append(sym2idx[ch])
|
| 148 |
+
grid.append(row)
|
| 149 |
+
return grid
|
| 150 |
+
|
| 151 |
+
|
| 152 |
+
def memory_frames(
|
| 153 |
+
checkpoint: MemoryCheckpoint, *, legend: dict[int, str], grid_size: tuple[int, int]
|
| 154 |
+
) -> list[dict]:
|
| 155 |
+
"""Reconstruct a renderable color grid per memory turn.
|
| 156 |
+
|
| 157 |
+
Returns ``[{turn_idx, action, grid}]`` where ``grid`` is a ``height x width``
|
| 158 |
+
list of palette indices. Grid-style ``frame_ascii`` is inverted via *legend*;
|
| 159 |
+
prose frames are rebuilt from walls + recorded focal/predator positions.
|
| 160 |
+
Pure: no engine, no IO.
|
| 161 |
+
"""
|
| 162 |
+
sym2idx = _invert_legend(legend)
|
| 163 |
+
bg = sym2idx.get(".", 5)
|
| 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 |
+
|
| 170 |
+
def paint(grid, x0, y0, n, idx):
|
| 171 |
+
for j in range(n):
|
| 172 |
+
for i in range(n):
|
| 173 |
+
x, y = x0 + i, y0 + j
|
| 174 |
+
if 0 <= x < w and 0 <= y < h:
|
| 175 |
+
grid[y][x] = idx
|
| 176 |
+
|
| 177 |
+
for mt in checkpoint.memory_turns:
|
| 178 |
+
grid = _ascii_to_grid(mt.frame_ascii, sym2idx)
|
| 179 |
+
if grid is None:
|
| 180 |
+
grid = [[bg] * w for _ in range(h)]
|
| 181 |
+
for (rx0, ry0, rx1, ry1) in checkpoint.wall_rects:
|
| 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})
|
| 188 |
+
return out
|
tests/runtime/test_memory_frames.py
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Pure reconstruction of per-turn color grids from a MemoryCheckpoint."""
|
| 2 |
+
from __future__ import annotations
|
| 3 |
+
|
| 4 |
+
from proteus.runtime.memory import MemoryCheckpoint, MemoryTurn, memory_frames
|
| 5 |
+
|
| 6 |
+
_GRID_LEGEND = {5: ".", 1: "A", 2: "B", 3: "#"}
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
def _ck(turns, scenario="x", wall_rects=None):
|
| 10 |
+
return MemoryCheckpoint(
|
| 11 |
+
model="m", scenario=scenario, difficulty="easy", seed=1,
|
| 12 |
+
created_at="t", outcome="survived", transparent_prompt="p",
|
| 13 |
+
memory_turns=turns, wall_rects=wall_rects or [],
|
| 14 |
+
)
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
def test_grid_frame_inverts_ascii_via_legend():
|
| 18 |
+
ck = _ck([MemoryTurn(turn_idx=1, frame_ascii="A.\n.#", action="right",
|
| 19 |
+
focal_pos=(0, 0), predator_pos=(1, 1))])
|
| 20 |
+
frames = memory_frames(ck, legend=_GRID_LEGEND, grid_size=(2, 2))
|
| 21 |
+
assert frames[0]["action"] == "right"
|
| 22 |
+
assert frames[0]["grid"] == [[1, 5], [5, 3]] # A . / . #
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
def test_prose_frame_paints_positions_and_walls():
|
| 26 |
+
ck = _ck(
|
| 27 |
+
[MemoryTurn(turn_idx=1, frame_ascii="Open field 8x8. You are A ...",
|
| 28 |
+
action="up", focal_pos=(0, 0), predator_pos=(5, 5))],
|
| 29 |
+
wall_rects=[(2, 2, 3, 3)],
|
| 30 |
+
)
|
| 31 |
+
g = memory_frames(ck, legend=_GRID_LEGEND, grid_size=(8, 8))[0]["grid"]
|
| 32 |
+
assert g[0][0] == 1 and g[2][2] == 1 # focal 3x3 at (0,0)
|
| 33 |
+
assert g[5][5] == 2 and g[7][7] == 2 # predator 5x5 at (5,5) clipped in-bounds
|
| 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
|