File size: 6,146 Bytes
59edb07 | 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 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 | """Entropy management β keeps the simulation diverse and interesting."""
from __future__ import annotations
import random
import logging
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from soci.agents.agent import Agent
from soci.world.clock import SimClock
from soci.world.events import EventSystem
logger = logging.getLogger(__name__)
class EntropyManager:
"""Manages simulation entropy to prevent bland, repetitive behavior."""
def __init__(self) -> None:
# How often to inject events (every N ticks)
self.event_injection_interval: int = 10
# Ticks since last injection
self._ticks_since_event: int = 0
# Track agent behavior patterns for drift detection
self._action_history: dict[str, list[str]] = {} # agent_id -> last N actions
self._history_window: int = 20
def tick(
self,
agents: list[Agent],
event_system: EventSystem,
clock: SimClock,
city_location_ids: list[str],
) -> list[str]:
"""Process one tick of entropy management. Returns list of notable events/messages."""
messages: list[str] = []
self._ticks_since_event += 1
# Track action patterns
for agent in agents:
if agent.current_action:
history = self._action_history.setdefault(agent.id, [])
history.append(agent.current_action.type)
if len(history) > self._history_window:
self._action_history[agent.id] = history[-self._history_window:]
# Detect repetitive behavior
for agent in agents:
if self._is_stuck_in_loop(agent.id):
messages.append(
f"[ENTROPY] {agent.name} seems stuck in a behavioral loop β "
f"injecting stimulus."
)
self._inject_personal_stimulus(agent, clock)
# Periodic event injection
if self._ticks_since_event >= self.event_injection_interval:
new_events = event_system.tick(city_location_ids)
self._ticks_since_event = 0
for evt in new_events:
messages.append(f"[EVENT] {evt.name}: {evt.description}")
else:
# Still tick the event system for weather/expiry
event_system.tick(city_location_ids)
# Time-based entropy: inject daily rhythm changes
if clock.hour == 12 and clock.minute == 0:
messages.append("[RHYTHM] Noon β the city bustles with lunch crowds.")
elif clock.hour == 18 and clock.minute == 0:
messages.append("[RHYTHM] Evening β people head home or to the bar.")
elif clock.hour == 22 and clock.minute == 0:
messages.append("[RHYTHM] Late night β the city quiets down.")
return messages
def _is_stuck_in_loop(self, agent_id: str) -> bool:
"""Detect if an agent is repeating the same actions."""
history = self._action_history.get(agent_id, [])
if len(history) < 10:
return False
# Check if last 10 actions are all the same
recent = history[-10:]
unique = set(recent)
return len(unique) <= 2 and "sleep" not in unique
def _inject_personal_stimulus(self, agent: Agent, clock: SimClock) -> None:
"""Inject a personal event to break an agent out of a loop."""
stimuli = [
f"{agent.name} suddenly remembers something important they forgot to do.",
f"{agent.name} gets an unexpected phone call from an old friend.",
f"{agent.name} notices something unusual in their surroundings.",
f"{agent.name} overhears an interesting conversation nearby.",
f"{agent.name} finds a forgotten note in their pocket.",
f"{agent.name} suddenly craves something completely different.",
]
stimulus = random.choice(stimuli)
agent.add_observation(
tick=clock.total_ticks,
day=clock.day,
time_str=clock.time_str,
content=stimulus,
importance=7,
)
def get_conflict_catalysts(self, agents: list[Agent]) -> list[tuple[str, str, str]]:
"""Identify potential conflicts between agents based on their personas.
Returns list of (agent1_id, agent2_id, tension_description) tuples.
"""
catalysts = []
# Find agents with opposing values or competing interests
for i, a in enumerate(agents):
for b in agents[i + 1:]:
tension = self._find_tension(a, b)
if tension:
catalysts.append((a.id, b.id, tension))
return catalysts
def _find_tension(self, a: Agent, b: Agent) -> str | None:
"""Find natural tension between two agents."""
# Big personality differences can create friction
extraversion_gap = abs(a.persona.extraversion - b.persona.extraversion)
agreeableness_gap = abs(a.persona.agreeableness - b.persona.agreeableness)
if extraversion_gap >= 6 and agreeableness_gap >= 4:
return "personality clash β one is outgoing and blunt, the other is reserved and sensitive"
# Competing values
a_values = set(a.persona.values)
b_values = set(b.persona.values)
if a_values and b_values and not a_values.intersection(b_values):
return f"different values β {a.name} values {', '.join(a.persona.values)}, while {b.name} values {', '.join(b.persona.values)}"
return None
def to_dict(self) -> dict:
return {
"event_injection_interval": self.event_injection_interval,
"ticks_since_event": self._ticks_since_event,
"action_history": dict(self._action_history),
}
@classmethod
def from_dict(cls, data: dict) -> EntropyManager:
mgr = cls()
mgr.event_injection_interval = data.get("event_injection_interval", 10)
mgr._ticks_since_event = data.get("ticks_since_event", 0)
mgr._action_history = data.get("action_history", {})
return mgr
|