irregular6612 commited on
Commit
c73d53f
·
1 Parent(s): 38e746c

feat(cp1): port ascii_view into proteus.grid

Browse files
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 # ."