""" echo/core/world_state.py ------------------------ The persistent, structured memory of a single alternate life (one node in the tree). This is what makes "the same you" recognizable across branches: every new branch is generated as a *causal consequence* of its parent's WorldState, not invented from scratch. Pure data. No LLM, no I/O. The Curator agent reads a parent WorldState and writes a child; the Verifier checks a child against its parent using these fields. """ from __future__ import annotations from dataclasses import dataclass, field, asdict from typing import Optional import uuid @dataclass class LifeFacts: """The concrete, checkable facts about this version of the person.""" age: int location: str # city / country right now occupation: str relationships: list[str] = field(default_factory=list) # e.g. "married to Sofia" dependents: list[str] = field(default_factory=list) # e.g. "daughter, 4" scars: list[str] = field(default_factory=list) # losses / regrets carried triumphs: list[str] = field(default_factory=list) # what went right possessions: list[str] = field(default_factory=list) # grounding mundane detail def constraints_text(self) -> str: """A compact statement of facts the next branch must NOT contradict.""" parts = [f"age {self.age}", f"in {self.location}", f"works as {self.occupation}"] if self.relationships: parts.append("relationships: " + "; ".join(self.relationships)) if self.dependents: parts.append("dependents: " + "; ".join(self.dependents)) if self.scars: parts.append("carries: " + "; ".join(self.scars)) return " | ".join(parts) @dataclass class EmotionalTone: """Where this life sits emotionally — drives the gold/dark visual + voice.""" valence: float # -1.0 (devastated) .. +1.0 (thriving) dominant_feeling: str # e.g. "restless pride", "quiet grief" voice_hint: str = "" # guidance for TTS (pace, warmth, weariness) @property def is_flourishing(self) -> bool: return self.valence >= 0.25 @property def is_struggling(self) -> bool: return self.valence <= -0.25 @dataclass class WorldState: """One node: a complete snapshot of an alternate life.""" node_id: str parent_id: Optional[str] depth: int # the choice that CREATED this branch (what diverged from the parent) divergence: str # how many years passed since the parent node years_elapsed: int facts: LifeFacts tone: EmotionalTone # a short second-person summary the UI shows ("You are 34, in Lisbon...") summary: str = "" # the spoken message this echo leaves (filled by the voice tool) voice_line: str = "" voice_audio_path: Optional[str] = None # the two planned next forks (filled by the Screenwriter) pending_forks: list[str] = field(default_factory=list) def to_dict(self) -> dict: return asdict(self) @staticmethod def new_id() -> str: return uuid.uuid4().hex[:8] def root_state(seed_choice: str, base_age: int = 30) -> WorldState: """ Create the minimal root before the Curator fills it. The Curator will overwrite facts/tone; this just establishes the tree's origin. """ return WorldState( node_id=WorldState.new_id(), parent_id=None, depth=0, divergence=seed_choice, years_elapsed=0, facts=LifeFacts(age=base_age, location="unknown", occupation="unknown"), tone=EmotionalTone(valence=0.0, dominant_feeling="uncertain"), )