AgentNewTwo's picture
Make resident persistence engine-authoritative
ff6eb9d
Raw
History Blame Contribute Delete
11.3 kB
import json
import unittest
from terrarium.engine import (
ACTION_FAMILIES, StateError, apply_proposal, cognition_packet, create_random_resident,
create_resident, export_state, import_state, schedule_action, validate_state,
)
class EngineTests(unittest.TestCase):
def setUp(self):
self.state = create_resident("Moss", "Glass Observatory", "Moss Machine", "brass seed")
def proposal(self, action, target):
return {
"action": action, "target": target,
"intention": "Moss follows the scheduled action carefully.",
"narration": "Moss pauses beneath the amber light and acts with deliberate curiosity.",
}
def quiet_proposal(self, action, target):
return {
"action": action, "target": target,
"intention": "The scheduled life action proceeds.",
"narration": f"The resident chooses {action.replace('_', ' ')}.",
}
def test_genesis_is_valid(self):
validate_state(self.state)
self.assertEqual(self.state["cycle"], 0)
def test_scheduler_penalizes_immediate_repetition(self):
first, target = schedule_action(self.state)
state = apply_proposal(self.state, self.proposal(first, target), first, target)
second, _ = schedule_action(state)
self.assertNotEqual(first, second)
def test_model_cannot_override_scheduled_action(self):
action, target = schedule_action(self.state)
bad = self.proposal("rest", "self")
with self.assertRaises(StateError):
apply_proposal(self.state, bad, action, target)
self.assertEqual(self.state["cycle"], 0)
def test_export_import_round_trip(self):
restored = import_state(export_state(self.state))
self.assertEqual(restored, self.state)
def test_tampered_save_is_rejected(self):
envelope = json.loads(export_state(self.state))
envelope["resident"]["cycle"] = 9
with self.assertRaises(StateError):
import_state(json.dumps(envelope))
def test_invalid_brain_proposal_leaves_state_unchanged(self):
action, target = schedule_action(self.state)
bad = self.proposal(action, target)
bad["narration"] = ""
before = json.dumps(self.state, sort_keys=True)
with self.assertRaises(StateError):
apply_proposal(self.state, bad, action, target)
self.assertEqual(json.dumps(self.state, sort_keys=True), before)
def test_rechecksummed_malformed_save_is_rejected(self):
envelope = json.loads(export_state(self.state))
envelope["resident"]["needs"] = {"curiosity": "very"}
canonical = json.dumps(envelope["resident"], sort_keys=True, separators=(",", ":"), ensure_ascii=False)
import hashlib
envelope["sha256"] = hashlib.sha256(canonical.encode()).hexdigest()
with self.assertRaises(StateError):
import_state(json.dumps(envelope))
def test_seeded_genesis_is_reproducible(self):
first = create_random_resident(seed=8675309)
second = create_random_resident(seed=8675309)
first.pop("created_at")
second.pop("created_at")
self.assertEqual(first, second)
def test_genesis_varies_meaningfully_across_seeds(self):
residents = [create_random_resident(seed=seed) for seed in range(24)]
fingerprints = {
(
item["resident"]["name"], item["resident"]["form"], item["habitat"]["name"],
next(iter(item["artifacts"].values()))["name"],
tuple(item["profile"]["temperament"].values()), item["profile"]["dominant_drive"],
)
for item in residents
}
self.assertGreaterEqual(len(fingerprints), 22)
def test_separate_arrivals_do_not_share_mutable_state(self):
first = create_random_resident(seed=101)
second = create_random_resident(seed=202)
first["memories"].append({"cycle": 0, "text": "Only the first resident remembers this.", "salience": 0.5})
self.assertNotEqual(first["genesis"]["seed"], second["genesis"]["seed"])
self.assertEqual(len(second["memories"]), 1)
def test_profile_reaches_cognition_packet(self):
resident = create_random_resident(seed=4242)
action, target = schedule_action(resident)
packet = cognition_packet(resident, action, target)
self.assertEqual(packet["genesis"], resident["genesis"])
self.assertEqual(packet["profile"], resident["profile"])
def test_legacy_v1_save_remains_compatible(self):
envelope = json.loads(export_state(self.state))
envelope["resident"].pop("genesis")
envelope["resident"].pop("profile")
envelope["resident"].pop("mood")
envelope["resident"]["needs"].pop("comfort")
envelope["resident"]["needs"].pop("play")
canonical = json.dumps(envelope["resident"], sort_keys=True, separators=(",", ":"), ensure_ascii=False)
import hashlib
envelope["sha256"] = hashlib.sha256(canonical.encode()).hexdigest()
restored = import_state(json.dumps(envelope))
validate_state(restored)
action, target = schedule_action(restored)
packet = cognition_packet(restored, action, target)
self.assertEqual(packet["genesis"]["version"], 0)
self.assertEqual(packet["profile"]["dominant_drive"], "curiosity")
def test_weighted_ecology_is_reproducible_and_balanced(self):
family_counts = {}
action_sequences = []
ritual_count = 0
for seed in range(12):
resident = create_random_resident(seed=seed)
actions = []
for _ in range(100):
action, target = schedule_action(resident)
actions.append(action)
family = ACTION_FAMILIES[action]
family_counts[family] = family_counts.get(family, 0) + 1
ritual_count += action == "perform_ritual"
resident = apply_proposal(resident, self.quiet_proposal(action, target), action, target)
self.assertGreaterEqual(len(set(actions)), 12)
self.assertFalse(any(left == right for left, right in zip(actions, actions[1:])))
action_sequences.append(actions)
total = sum(family_counts.values())
discovery_share = family_counts["discover"] / total
self.assertGreater(discovery_share, 0.12)
self.assertLess(discovery_share, 0.28)
self.assertGreater(sum(value for key, value in family_counts.items() if key != "discover") / total, 0.70)
self.assertTrue(all(family_counts.get(family, 0) / total > 0.04 for family in {
"discover", "enjoy", "live", "create", "play", "reflect", "restore",
}))
self.assertGreater(ritual_count, 10)
replay = create_random_resident(seed=0)
replay_actions = []
for _ in range(100):
action, target = schedule_action(replay)
replay_actions.append(action)
replay = apply_proposal(replay, self.quiet_proposal(action, target), action, target)
self.assertEqual(replay_actions, action_sequences[0])
def test_moods_persist_then_change_within_bounds(self):
resident = create_random_resident(seed=31337)
observed = []
for _ in range(40):
observed.append(resident["mood"]["name"])
action, target = schedule_action(resident)
resident = apply_proposal(resident, self.quiet_proposal(action, target), action, target)
self.assertGreaterEqual(resident["mood"]["remaining"], 1)
self.assertLessEqual(resident["mood"]["remaining"], 5)
runs = []
for mood in observed:
if not runs or runs[-1][0] != mood:
runs.append([mood, 1])
else:
runs[-1][1] += 1
self.assertGreater(len(runs), 5)
self.assertTrue(any(length >= 2 for _, length in runs))
def test_export_import_preserves_future_ecology_sequence(self):
original = create_random_resident(seed=404)
for _ in range(25):
action, target = schedule_action(original)
original = apply_proposal(original, self.quiet_proposal(action, target), action, target)
restored = import_state(export_state(original))
original_future = []
restored_future = []
for _ in range(30):
action, target = schedule_action(original)
original_future.append((action, target, original["mood"].copy()))
original = apply_proposal(original, self.quiet_proposal(action, target), action, target)
action, target = schedule_action(restored)
restored_future.append((action, target, restored["mood"].copy()))
restored = apply_proposal(restored, self.quiet_proposal(action, target), action, target)
self.assertEqual(original_future, restored_future)
def test_invalid_mood_is_rejected(self):
resident = create_random_resident(seed=78)
resident["mood"] = {"name": "obsessed", "remaining": 99}
with self.assertRaises(StateError):
validate_state(resident)
def test_invalid_profile_is_rejected(self):
resident = create_random_resident(seed=77)
resident["profile"]["dominant_drive"] = "escape"
with self.assertRaises(StateError):
validate_state(resident)
def test_scheduler_never_addresses_observer(self):
resident = create_random_resident(seed=991)
resident["observer_message"] = "Legacy save text that must be ignored."
for _ in range(20):
action, target = schedule_action(resident)
self.assertNotEqual(action, "address_observer")
self.assertNotEqual(target, "observer")
resident = apply_proposal(resident, self.proposal(action, target), action, target)
def test_engine_memories_follow_actions_without_duplicates_or_new_threads(self):
resident = create_random_resident(seed=4444)
initial_threads = json.loads(json.dumps(resident["threads"]))
for _ in range(200):
action, target = schedule_action(resident)
resident = apply_proposal(resident, self.quiet_proposal(action, target), action, target)
memory_texts = [item["text"].casefold() for item in resident["memories"]]
self.assertEqual(len(memory_texts), len(set(memory_texts)))
self.assertEqual(resident["threads"], initial_threads)
self.assertTrue(any("enjoy" in item or "comfort" in item for item in memory_texts))
def test_legacy_observer_action_history_remains_importable(self):
resident = create_random_resident(seed=992)
resident["recent_actions"].append({"cycle": 1, "action": "address_observer", "target": "observer"})
resident["history"].append({
"cycle": 1, "action": "address_observer", "target": "observer",
"intention": "A legacy intention.", "narration": "A legacy narration.", "before_hash": "legacy",
})
validate_state(resident)
restored = import_state(export_state(resident))
self.assertEqual(restored["history"][-1]["action"], "address_observer")
if __name__ == "__main__":
unittest.main()