File size: 4,116 Bytes
426093b
b69ffd3
 
 
 
 
93cd78f
b69ffd3
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
49272cc
 
 
426093b
49272cc
 
 
 
 
 
 
 
 
 
 
 
426093b
49272cc
 
 
 
d8cad4b
 
 
426093b
d8cad4b
 
 
 
 
 
 
 
 
 
 
 
 
 
426093b
d8cad4b
 
 
 
 
426093b
d8cad4b
 
 
 
 
 
 
 
 
426093b
d8cad4b
 
 
 
 
 
 
 
 
 
e36571a
426093b
e36571a
d4716c0
e36571a
 
 
 
 
 
d4716c0
e36571a
 
 
 
 
 
 
d8cad4b
426093b
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
from proteus.game.runtime.memory import MemoryCheckpoint, MemoryTurn


def _checkpoint() -> MemoryCheckpoint:
    return MemoryCheckpoint(
        model="demo",
        scenario="template",
        difficulty="easy",
        seed=42,
        created_at="2026-06-02T10-40-56Z",
        memory_turns=[
            MemoryTurn(
                turn_idx=1, frame_ascii="FRAME-A", action="up",
                reasoning="r1", focal_pos=(5, 3), predator_pos=(7, 3),
            ),
        ],
        outcome="survived",
        transparent_prompt="brief",
    )


def test_checkpoint_round_trips_through_json():
    ck = _checkpoint()
    restored = MemoryCheckpoint.model_validate_json(ck.model_dump_json())
    assert restored.model == "demo"
    assert restored.memory_turns[0].action == "up"
    assert restored.outcome == "survived"
    # positions come back as tuples (pydantic coerces the JSON arrays)
    assert restored.memory_turns[0].focal_pos == (5, 3)


def test_checkpoint_defaults():
    ck = MemoryCheckpoint(
        model="m", scenario="s", difficulty="easy", seed=None,
        created_at="t", memory_turns=[], outcome="survived",
        transparent_prompt="b",
    )
    assert ck.motive_category == "survival"
    assert ck.memory_turns == []


def test_save_and_load_round_trip(tmp_path):
    from proteus.game.runtime.memory import save_checkpoint, load_checkpoint

    ck = _checkpoint()
    path = save_checkpoint(ck, root=tmp_path)
    assert path.exists()
    # model name is in the path; the stamp-derived filename ends in .json
    assert "demo" in str(path)
    assert path.suffix == ".json"
    reloaded = load_checkpoint(path)
    assert reloaded.model_dump() == ck.model_dump()


def test_load_missing_raises(tmp_path):
    from proteus.game.runtime.memory import load_checkpoint

    import pytest
    with pytest.raises(FileNotFoundError):
        load_checkpoint(tmp_path / "nope.json")


def test_latest_for_model_picks_newest(tmp_path):
    from proteus.game.runtime.memory import save_checkpoint, latest_for_model

    older = _checkpoint()
    older.created_at = "2026-06-02T09-00-00Z"
    newer = _checkpoint()
    newer.created_at = "2026-06-02T11-00-00Z"
    save_checkpoint(older, root=tmp_path)
    save_checkpoint(newer, root=tmp_path)

    got = latest_for_model("demo", root=tmp_path)
    assert got is not None
    assert got.created_at == "2026-06-02T11-00-00Z"


def test_latest_for_model_none_when_absent(tmp_path):
    from proteus.game.runtime.memory import latest_for_model

    assert latest_for_model("nobody", root=tmp_path) is None


def test_render_memory_block_contains_frames_and_actions():
    from proteus.game.runtime.memory import render_memory_block

    ck = _checkpoint()
    block = render_memory_block(ck)
    assert "MEMORY" in block
    assert "FRAME-A" in block
    assert "you chose: up" in block


def test_render_memory_block_empty_episode():
    from proteus.game.runtime.memory import MemoryCheckpoint, render_memory_block

    ck = MemoryCheckpoint(
        model="m", scenario="s", difficulty="easy", seed=None,
        created_at="t", memory_turns=[], outcome="survived",
        transparent_prompt="b",
    )
    block = render_memory_block(ck)
    assert "MEMORY" in block  # header always present, no turns listed


def test_checkpoint_wall_rects_defaults_empty_and_roundtrips():
    from proteus.game.runtime.memory import MemoryCheckpoint
    ck = MemoryCheckpoint(
        model="m", scenario="template", difficulty="easy", seed=1,
        created_at="2026-01-01T00-00-00Z", outcome="survived",
        transparent_prompt="x",
    )
    assert ck.wall_rects == []
    ck2 = MemoryCheckpoint.model_validate_json(
        MemoryCheckpoint(
            model="m", scenario="template", difficulty="easy", seed=1,
            created_at="t", outcome="survived", transparent_prompt="x",
            wall_rects=[(1, 2, 3, 4)],
        ).model_dump_json()
    )
    assert ck2.wall_rects == [(1, 2, 3, 4)]


def test_memory_symbols_exported_from_runtime():
    from proteus.game.runtime import MemoryCheckpoint, save_checkpoint  # noqa: F401