| """Needs system — Maslow-inspired needs that drive agent behavior.""" |
|
|
| from __future__ import annotations |
|
|
| from dataclasses import dataclass |
|
|
|
|
| @dataclass |
| class NeedsState: |
| """Tracks an agent's current needs. Each need ranges 0.0 (desperate) to 1.0 (fully satisfied).""" |
|
|
| hunger: float = 0.8 |
| energy: float = 1.0 |
| social: float = 0.6 |
| purpose: float = 0.7 |
| comfort: float = 0.8 |
| fun: float = 0.5 |
|
|
| |
| _decay_rates: dict = None |
|
|
| def __post_init__(self): |
| self._decay_rates = { |
| "hunger": 0.02, |
| "energy": 0.015, |
| "social": 0.01, |
| "purpose": 0.008, |
| "comfort": 0.005, |
| "fun": 0.012, |
| } |
|
|
| def tick(self, is_sleeping: bool = False) -> None: |
| """Decay all needs by one tick.""" |
| if is_sleeping: |
| |
| self.energy = min(1.0, self.energy + 0.05) |
| self.hunger = max(0.0, self.hunger - self._decay_rates["hunger"]) |
| else: |
| for need_name, rate in self._decay_rates.items(): |
| current = getattr(self, need_name) |
| setattr(self, need_name, max(0.0, current - rate)) |
|
|
| def satisfy(self, need: str, amount: float) -> None: |
| """Satisfy a need by a given amount.""" |
| if hasattr(self, need): |
| current = getattr(self, need) |
| setattr(self, need, min(1.0, current + amount)) |
|
|
| @property |
| def most_urgent(self) -> str: |
| """Return the name of the most urgent (lowest) need.""" |
| needs = { |
| "hunger": self.hunger, |
| "energy": self.energy, |
| "social": self.social, |
| "purpose": self.purpose, |
| "comfort": self.comfort, |
| "fun": self.fun, |
| } |
| return min(needs, key=needs.get) |
|
|
| @property |
| def urgent_needs(self) -> list[str]: |
| """Return needs below 0.3 threshold, sorted by urgency.""" |
| needs = { |
| "hunger": self.hunger, |
| "energy": self.energy, |
| "social": self.social, |
| "purpose": self.purpose, |
| "comfort": self.comfort, |
| "fun": self.fun, |
| } |
| return sorted( |
| [n for n, v in needs.items() if v < 0.3], |
| key=lambda n: needs[n], |
| ) |
|
|
| @property |
| def is_critical(self) -> bool: |
| """True if any need is critically low (below 0.15).""" |
| return any(v < 0.15 for v in [ |
| self.hunger, self.energy, self.social, |
| self.purpose, self.comfort, self.fun, |
| ]) |
|
|
| def describe(self) -> str: |
| """Natural language description of current need state.""" |
| parts = [] |
| if self.hunger < 0.3: |
| parts.append("very hungry" if self.hunger < 0.15 else "getting hungry") |
| if self.energy < 0.3: |
| parts.append("exhausted" if self.energy < 0.15 else "tired") |
| if self.social < 0.3: |
| parts.append("lonely" if self.social < 0.15 else "wanting company") |
| if self.purpose < 0.3: |
| parts.append("feeling aimless" if self.purpose < 0.15 else "wanting to do something meaningful") |
| if self.comfort < 0.3: |
| parts.append("uncomfortable" if self.comfort < 0.15 else "a bit uneasy") |
| if self.fun < 0.3: |
| parts.append("bored" if self.fun < 0.15 else "wanting some fun") |
| if not parts: |
| return "feeling good overall" |
| return ", ".join(parts) |
|
|
| def to_dict(self) -> dict: |
| return { |
| "hunger": round(self.hunger, 3), |
| "energy": round(self.energy, 3), |
| "social": round(self.social, 3), |
| "purpose": round(self.purpose, 3), |
| "comfort": round(self.comfort, 3), |
| "fun": round(self.fun, 3), |
| } |
|
|
| @classmethod |
| def from_dict(cls, data: dict) -> NeedsState: |
| state = cls() |
| for key, val in data.items(): |
| if hasattr(state, key) and not key.startswith("_"): |
| setattr(state, key, val) |
| return state |
|
|