Spaces:
Sleeping
Sleeping
| """ | |
| echo/core/tree.py | |
| ----------------- | |
| The branching tree of alternate lives. Holds every WorldState node and the | |
| parent/child links the UI renders (gold for flourishing, dark for struggling). | |
| Pure data structure + traversal helpers. The orchestrator mutates it; the | |
| Gradio front-end reads it to draw the D3/vis-network graph. | |
| """ | |
| from __future__ import annotations | |
| from dataclasses import dataclass, field | |
| from typing import Optional | |
| from .world_state import WorldState | |
| class LifeTree: | |
| seed_choice: str | |
| nodes: dict[str, WorldState] = field(default_factory=dict) | |
| root_id: Optional[str] = None | |
| # ---------------------------------------------------------------- build | |
| def add(self, state: WorldState) -> None: | |
| self.nodes[state.node_id] = state | |
| if state.parent_id is None: | |
| self.root_id = state.node_id | |
| def children(self, node_id: str) -> list[WorldState]: | |
| return [n for n in self.nodes.values() if n.parent_id == node_id] | |
| def path_to_root(self, node_id: str) -> list[WorldState]: | |
| """Ancestor chain from root -> node (inclusive). The branch's history.""" | |
| chain: list[WorldState] = [] | |
| cur = self.nodes.get(node_id) | |
| while cur is not None: | |
| chain.append(cur) | |
| cur = self.nodes.get(cur.parent_id) if cur.parent_id else None | |
| return list(reversed(chain)) | |
| def branch_narrative(self, node_id: str) -> str: | |
| """Human-readable history of a branch, fed to the Curator as context.""" | |
| chain = self.path_to_root(node_id) | |
| lines = [] | |
| for n in chain: | |
| if n.depth == 0: | |
| lines.append(f"Origin: {n.divergence}") | |
| else: | |
| lines.append( | |
| f"+{n.years_elapsed}y -> {n.divergence} " | |
| f"(now: {n.facts.constraints_text()})" | |
| ) | |
| return "\n".join(lines) | |
| # --------------------------------------------------------------- export | |
| def to_graph(self) -> dict: | |
| """Serialize to nodes/edges for the front-end visualization.""" | |
| graph_nodes = [] | |
| graph_edges = [] | |
| for n in self.nodes.values(): | |
| graph_nodes.append({ | |
| "id": n.node_id, | |
| "label": n.facts.occupation or "…", | |
| "summary": n.summary, | |
| "valence": n.tone.valence, | |
| "flourishing": n.tone.is_flourishing, | |
| "depth": n.depth, | |
| "has_voice": n.voice_audio_path is not None, | |
| }) | |
| if n.parent_id: | |
| graph_edges.append({ | |
| "from": n.parent_id, "to": n.node_id, | |
| "label": n.divergence[:40], | |
| }) | |
| return {"nodes": graph_nodes, "edges": graph_edges, | |
| "root": self.root_id, "seed": self.seed_choice} | |
| def size(self) -> int: | |
| return len(self.nodes) | |
| def max_depth(self) -> int: | |
| return max((n.depth for n in self.nodes.values()), default=0) |