- Core engine: simulator, game mechanics, triggers (138 tests) - Fog-of-war per-player state tracking - Meeting flow: interrupt, discussion, voting, consolidation - Prompt assembler with strategy injection tiers - LLM client with fallbacks for models without JSON/system support - Prompt templates: action, discussion, voting, reflection - Full integration in main.py orchestrator - Verified working with free OpenRouter models (Gemma)
193 lines
6.4 KiB
Python
193 lines
6.4 KiB
Python
"""
|
|
Tests for the trigger system.
|
|
"""
|
|
|
|
import unittest
|
|
import sys
|
|
import os
|
|
|
|
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|
|
|
from src.engine.triggers import (
|
|
TriggerRegistry, TriggerType, TriggerCondition, Trigger,
|
|
MANDATORY_TRIGGERS, STANDARD_TRIGGERS
|
|
)
|
|
|
|
|
|
class TestTriggerType(unittest.TestCase):
|
|
"""Tests for TriggerType enum."""
|
|
|
|
def test_mandatory_triggers_defined(self):
|
|
self.assertIn(TriggerType.DISCUSSION_START, MANDATORY_TRIGGERS)
|
|
self.assertIn(TriggerType.VOTE_START, MANDATORY_TRIGGERS)
|
|
self.assertIn(TriggerType.GAME_START, MANDATORY_TRIGGERS)
|
|
|
|
def test_standard_triggers_defined(self):
|
|
self.assertIn(TriggerType.BODY_IN_FOV, STANDARD_TRIGGERS)
|
|
self.assertIn(TriggerType.PLAYER_ENTERS_FOV, STANDARD_TRIGGERS)
|
|
|
|
|
|
class TestTriggerRegistry(unittest.TestCase):
|
|
"""Tests for TriggerRegistry class."""
|
|
|
|
def setUp(self):
|
|
self.registry = TriggerRegistry()
|
|
self.registry.register_agent("agent1")
|
|
self.registry.register_agent("agent2")
|
|
|
|
def test_register_agent(self):
|
|
# Agents should have standard triggers subscribed
|
|
self.assertIn(TriggerType.BODY_IN_FOV, self.registry._subscriptions["agent1"])
|
|
self.assertIn(TriggerType.PLAYER_ENTERS_FOV, self.registry._subscriptions["agent1"])
|
|
|
|
def test_subscribe(self):
|
|
self.registry.subscribe("agent1", TriggerType.INTERSECTION)
|
|
self.assertIn(TriggerType.INTERSECTION, self.registry._subscriptions["agent1"])
|
|
|
|
def test_unsubscribe(self):
|
|
self.registry.unsubscribe("agent1", TriggerType.BODY_IN_FOV)
|
|
self.assertNotIn(TriggerType.BODY_IN_FOV, self.registry._subscriptions["agent1"])
|
|
|
|
def test_cannot_unsubscribe_mandatory(self):
|
|
self.registry.unsubscribe("agent1", TriggerType.DISCUSSION_START)
|
|
# Should still fire because mandatory
|
|
self.assertTrue(
|
|
self.registry.should_fire("agent1", TriggerType.DISCUSSION_START, 0.0)
|
|
)
|
|
|
|
def test_should_fire_standard(self):
|
|
# Standard triggers should fire by default
|
|
self.assertTrue(
|
|
self.registry.should_fire("agent1", TriggerType.BODY_IN_FOV, 0.0)
|
|
)
|
|
|
|
def test_should_fire_optional_not_subscribed(self):
|
|
# Optional triggers don't fire unless subscribed
|
|
self.assertFalse(
|
|
self.registry.should_fire("agent1", TriggerType.INTERSECTION, 0.0)
|
|
)
|
|
|
|
def test_should_fire_mandatory_always(self):
|
|
# Mandatory triggers always fire
|
|
self.assertTrue(
|
|
self.registry.should_fire("agent1", TriggerType.GAME_START, 0.0)
|
|
)
|
|
|
|
def test_mute_trigger(self):
|
|
condition = TriggerCondition(
|
|
trigger_type=TriggerType.PLAYER_ENTERS_FOV,
|
|
until_time=10.0
|
|
)
|
|
self.registry.mute("agent1", condition)
|
|
|
|
# Should be muted before time expires
|
|
self.assertTrue(
|
|
self.registry.is_muted("agent1", TriggerType.PLAYER_ENTERS_FOV, 5.0)
|
|
)
|
|
|
|
# Should not fire while muted
|
|
self.assertFalse(
|
|
self.registry.should_fire("agent1", TriggerType.PLAYER_ENTERS_FOV, 5.0)
|
|
)
|
|
|
|
def test_mute_expires(self):
|
|
condition = TriggerCondition(
|
|
trigger_type=TriggerType.PLAYER_ENTERS_FOV,
|
|
until_time=10.0
|
|
)
|
|
self.registry.mute("agent1", condition)
|
|
|
|
# Should not be muted after time expires
|
|
self.assertFalse(
|
|
self.registry.is_muted("agent1", TriggerType.PLAYER_ENTERS_FOV, 15.0)
|
|
)
|
|
|
|
def test_cannot_mute_mandatory(self):
|
|
condition = TriggerCondition(
|
|
trigger_type=TriggerType.DISCUSSION_START,
|
|
until_time=100.0
|
|
)
|
|
self.registry.mute("agent1", condition)
|
|
|
|
# Mandatory should still fire
|
|
self.assertTrue(
|
|
self.registry.should_fire("agent1", TriggerType.DISCUSSION_START, 50.0)
|
|
)
|
|
|
|
def test_target_specific_mute(self):
|
|
condition = TriggerCondition(
|
|
trigger_type=TriggerType.PLAYER_ENTERS_FOV,
|
|
until_time=10.0,
|
|
target_id="player_blue"
|
|
)
|
|
self.registry.mute("agent1", condition)
|
|
|
|
# Should be muted for specific target
|
|
self.assertTrue(
|
|
self.registry.is_muted("agent1", TriggerType.PLAYER_ENTERS_FOV, 5.0, "player_blue")
|
|
)
|
|
|
|
# Should NOT be muted for different target
|
|
self.assertFalse(
|
|
self.registry.is_muted("agent1", TriggerType.PLAYER_ENTERS_FOV, 5.0, "player_red")
|
|
)
|
|
|
|
def test_clear_expired_mutes(self):
|
|
cond1 = TriggerCondition(trigger_type=TriggerType.PLAYER_ENTERS_FOV, until_time=5.0)
|
|
cond2 = TriggerCondition(trigger_type=TriggerType.PLAYER_EXITS_FOV, until_time=15.0)
|
|
|
|
self.registry.mute("agent1", cond1)
|
|
self.registry.mute("agent1", cond2)
|
|
|
|
self.assertEqual(len(self.registry._mutes["agent1"]), 2)
|
|
|
|
self.registry.clear_expired_mutes("agent1", 10.0)
|
|
|
|
self.assertEqual(len(self.registry._mutes["agent1"]), 1)
|
|
|
|
def test_get_agents_for_trigger(self):
|
|
# Add a third agent
|
|
self.registry.register_agent("agent3")
|
|
|
|
# Mute agent2
|
|
self.registry.mute("agent2", TriggerCondition(
|
|
trigger_type=TriggerType.BODY_IN_FOV,
|
|
until_time=10.0
|
|
))
|
|
|
|
agents = self.registry.get_agents_for_trigger(TriggerType.BODY_IN_FOV, 5.0)
|
|
|
|
self.assertIn("agent1", agents)
|
|
self.assertNotIn("agent2", agents) # muted
|
|
self.assertIn("agent3", agents)
|
|
|
|
def test_get_agents_with_exclude(self):
|
|
agents = self.registry.get_agents_for_trigger(
|
|
TriggerType.BODY_IN_FOV,
|
|
0.0,
|
|
exclude={"agent1"}
|
|
)
|
|
|
|
self.assertNotIn("agent1", agents)
|
|
self.assertIn("agent2", agents)
|
|
|
|
|
|
class TestTrigger(unittest.TestCase):
|
|
"""Tests for Trigger dataclass."""
|
|
|
|
def test_trigger_creation(self):
|
|
trigger = Trigger(
|
|
trigger_type=TriggerType.BODY_IN_FOV,
|
|
target_agent_id="agent1",
|
|
time=10.5,
|
|
data={"victim": "Blue"}
|
|
)
|
|
|
|
self.assertEqual(trigger.trigger_type, TriggerType.BODY_IN_FOV)
|
|
self.assertEqual(trigger.target_agent_id, "agent1")
|
|
self.assertEqual(trigger.time, 10.5)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|