Spaces:
Runtime error
Runtime error
Commit ·
c73d53f
1
Parent(s): 38e746c
feat(cp1): port ascii_view into proteus.grid
Browse files- proteus/grid/ascii_view.py +68 -0
- tests/grid/test_ascii_view.py +15 -0
proteus/grid/ascii_view.py
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""ASCII rendering of a motive_grid frame for text-only model observations.
|
| 2 |
+
|
| 3 |
+
Benchmark models read text, not pixels, so the world state is shown to the
|
| 4 |
+
model as a compact ASCII grid: one character per cell, one line per row. The
|
| 5 |
+
arc_grid PNG/terminal renderers stay reserved for humans and debugging.
|
| 6 |
+
|
| 7 |
+
The native-resolution palette frame comes from
|
| 8 |
+
:meth:`~.game.MotiveGridGame.current_grid` (a ``(height, width)`` array of
|
| 9 |
+
palette indices), and the index -> symbol mapping comes from
|
| 10 |
+
:meth:`~.scenario.Scenario.legend`. This module is pure and deterministic: no
|
| 11 |
+
randomness, no IO.
|
| 12 |
+
|
| 13 |
+
See ``docs/superpowers/specs/2026-06-01-motive-grid-design.md`` §3.
|
| 14 |
+
"""
|
| 15 |
+
|
| 16 |
+
from __future__ import annotations
|
| 17 |
+
|
| 18 |
+
import numpy as np
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
def frame_to_ascii(
|
| 22 |
+
grid: np.ndarray,
|
| 23 |
+
legend: dict[int, str],
|
| 24 |
+
unknown: str = "?",
|
| 25 |
+
) -> str:
|
| 26 |
+
"""Render a native-resolution palette grid as an ASCII string.
|
| 27 |
+
|
| 28 |
+
Each integer cell is mapped to its legend character; any index absent from
|
| 29 |
+
*legend* maps to *unknown*. Rows are joined by newlines and the characters
|
| 30 |
+
within a row are concatenated with no separators, so an ``8x8`` grid yields
|
| 31 |
+
8 lines of 8 characters. The output is deterministic for a given grid and
|
| 32 |
+
legend.
|
| 33 |
+
|
| 34 |
+
Args:
|
| 35 |
+
grid: A 2D native-resolution array of palette indices with shape
|
| 36 |
+
``(height, width)``.
|
| 37 |
+
legend: Mapping from palette index to a single-character symbol.
|
| 38 |
+
unknown: Character used for any palette index not present in *legend*.
|
| 39 |
+
|
| 40 |
+
Returns:
|
| 41 |
+
A newline-joined ASCII rendering of the grid (no trailing newline).
|
| 42 |
+
"""
|
| 43 |
+
return "\n".join(
|
| 44 |
+
"".join(legend.get(int(cell), unknown) for cell in row) for row in grid
|
| 45 |
+
)
|
| 46 |
+
|
| 47 |
+
|
| 48 |
+
def legend_text(legend: dict[int, str]) -> str:
|
| 49 |
+
"""Render a compact, deterministic list of the distinct symbols in use.
|
| 50 |
+
|
| 51 |
+
This is intentionally minimal: it lists the symbols only, not their
|
| 52 |
+
meanings. ``Scenario.legend`` maps palette index -> a single character, so
|
| 53 |
+
no meaning can be recovered from it; the rich symbol-meaning key belongs to
|
| 54 |
+
the scenario's ``rules_text`` (handled later, in CP5).
|
| 55 |
+
|
| 56 |
+
Args:
|
| 57 |
+
legend: Mapping from palette index to a single-character symbol.
|
| 58 |
+
|
| 59 |
+
Returns:
|
| 60 |
+
A line like ``"Symbols: . @ # P"`` listing the distinct symbols in
|
| 61 |
+
ascending palette-index order (deterministic).
|
| 62 |
+
"""
|
| 63 |
+
seen: list[str] = []
|
| 64 |
+
for index in sorted(legend):
|
| 65 |
+
symbol = legend[index]
|
| 66 |
+
if symbol not in seen:
|
| 67 |
+
seen.append(symbol)
|
| 68 |
+
return "Symbols: " + " ".join(seen)
|
tests/grid/test_ascii_view.py
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import numpy as np
|
| 2 |
+
|
| 3 |
+
from proteus.grid.ascii_view import frame_to_ascii, legend_text
|
| 4 |
+
|
| 5 |
+
|
| 6 |
+
def test_frame_to_ascii_maps_indices_and_unknowns():
|
| 7 |
+
grid = np.array([[5, 1], [3, 9]], dtype=np.int8)
|
| 8 |
+
legend = {5: ".", 1: "A", 3: "#"}
|
| 9 |
+
out = frame_to_ascii(grid, legend)
|
| 10 |
+
assert out == ".A\n#?" # 9 is unknown -> "?"
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
def test_legend_text_lists_distinct_symbols_in_index_order():
|
| 14 |
+
legend = {5: ".", 1: "A", 2: "B", 3: "#"}
|
| 15 |
+
assert legend_text(legend) == "Symbols: A B # ."
|