| """ |
| Tests for NPC episodic memory system. |
| """ |
|
|
| from src.world.state import WorldState, MemoryEntry |
| import json |
|
|
|
|
| def load_test_state() -> WorldState: |
| with open("data/worlds/default.json") as f: |
| return WorldState.from_json(f.read()) |
|
|
|
|
| def test_memory_written_on_interaction(): |
| state = load_test_state() |
| changes = state.apply_update({ |
| "npc_memory": { |
| "barkeep": { |
| "description": "Player asked about the locked chest upstairs", |
| "emotional_tone": "suspicious", |
| "significance": 2, |
| } |
| } |
| }) |
| barkeep = state.npcs["barkeep"] |
| assert len(barkeep.memories) == 1 |
| assert barkeep.memories[0].emotional_tone == "suspicious" |
| assert barkeep.memories[0].significance == 2 |
| assert any("Marta" in c for c in changes) |
|
|
|
|
| def test_multiple_memories_accumulate(): |
| state = load_test_state() |
|
|
| state.apply_update({"npc_memory": { |
| "barkeep": {"description": "Player ordered ale", "emotional_tone": "neutral", "significance": 1} |
| }}) |
| state.apply_update({"npc_memory": { |
| "barkeep": {"description": "Player stole the dagger", "emotional_tone": "angry", "significance": 3} |
| }}) |
| state.apply_update({"npc_memory": { |
| "barkeep": {"description": "Player apologised and returned the dagger", "emotional_tone": "wary", "significance": 2} |
| }}) |
|
|
| barkeep = state.npcs["barkeep"] |
| assert len(barkeep.memories) == 3 |
|
|
|
|
| def test_relevant_memories_prioritises_significance(): |
| state = load_test_state() |
| barkeep = state.npcs["barkeep"] |
|
|
| |
| barkeep.memories = [ |
| MemoryEntry(turn=1, description="minor thing", emotional_tone="neutral", significance=1), |
| MemoryEntry(turn=2, description="very significant thing", emotional_tone="angry", significance=3), |
| MemoryEntry(turn=3, description="notable thing", emotional_tone="wary", significance=2), |
| ] |
|
|
| relevant = barkeep.relevant_memories(max_memories=2) |
| |
| assert relevant[0].significance == 3 |
| assert relevant[1].significance == 2 |
|
|
|
|
| def test_memory_appears_in_context_summary(): |
| state = load_test_state() |
|
|
| state.apply_update({"npc_memory": { |
| "barkeep": { |
| "description": "Player threatened her with the dagger", |
| "emotional_tone": "fearful", |
| "significance": 3, |
| } |
| }}) |
|
|
| summary = state.to_context_summary() |
| |
| assert "threatened" in summary |
| assert "fearful" in summary |
|
|
|
|
| def test_memory_survives_serialisation_roundtrip(): |
| state = load_test_state() |
|
|
| state.apply_update({"npc_memory": { |
| "barkeep": { |
| "description": "Player left a large tip", |
| "emotional_tone": "grateful", |
| "significance": 2, |
| } |
| }}) |
|
|
| |
| restored = WorldState.from_json(state.to_json()) |
|
|
| barkeep = restored.npcs["barkeep"] |
| assert len(barkeep.memories) == 1 |
| assert barkeep.memories[0].emotional_tone == "grateful" |
| assert barkeep.memories[0].description == "Player left a large tip" |
|
|
|
|
| def test_dead_npc_memory_not_injected_in_summary(): |
| state = load_test_state() |
|
|
| |
| state.apply_update({"npc_memory": { |
| "barkeep": {"description": "Player was rude", "emotional_tone": "angry", "significance": 1} |
| }}) |
| state.apply_update({"npc_state": {"barkeep": {"alive": False}}}) |
|
|
| summary = state.to_context_summary() |
| |
| assert "Player was rude" not in summary |
|
|
|
|
| def test_memory_summary_format(): |
| state = load_test_state() |
| barkeep = state.npcs["barkeep"] |
|
|
| barkeep.memories = [ |
| MemoryEntry(turn=5, description="Gave player information about the chest", |
| emotional_tone="neutral", significance=1), |
| ] |
|
|
| summary = barkeep.memory_summary() |
| assert "Marta" in summary |
| assert "Gave player information" in summary |
| assert "neutral" in summary |