| """ |
| SceneGraph: the deterministic semantic layer of a TurboSkillSlug session. |
| |
| This is the CONTRACT that every renderer reads. It is built once, deterministically, |
| from a session extraction, and it is renderer-agnostic. The four lenses (character, |
| 3D shell, generative atmosphere, full diorama) all consume the SAME SceneGraph, so: |
| |
| - every visible element still traces to real session data (the core promise), |
| - renderers are independent and individually degradable, |
| - a new renderer can be added without touching extraction or the others. |
| |
| Design rules: |
| - NOTHING here renders. No SVG, no canvas, no shader. Only structured meaning. |
| - Every field is DERIVED from the extraction (or a stable default), so the graph |
| is reproducible: same session -> same SceneGraph. |
| - Values are normalized and renderer-friendly (0..1 scalars, named enums, hex |
| colors) so each renderer can map them without re-interpreting raw extraction. |
| - The graph is versioned. Renderers declare which SCHEMA_VERSION they support. |
| """ |
|
|
| from __future__ import annotations |
|
|
| import colorsys |
| import hashlib |
| from dataclasses import dataclass, field, asdict |
| from typing import Any |
|
|
| SCHEMA_VERSION = "1.0" |
|
|
| |
| |
| _SENTIMENT_VA = { |
| "frustrated": (-0.7, 0.7), "stuck": (-0.6, 0.3), "exhausted": (-0.4, 0.1), |
| "anxious": (-0.5, 0.6), "confused": (-0.4, 0.4), "focused": (0.1, 0.6), |
| "curious": (0.4, 0.6), "determined": (0.3, 0.7), "calm": (0.3, 0.3), |
| "relieved": (0.6, 0.3), "satisfied": (0.7, 0.4), "resolved": (0.7, 0.5), |
| "joyful": (0.9, 0.8), "delighted": (0.9, 0.7), "triumphant": (1.0, 0.9), |
| "neutral": (0.0, 0.4), |
| } |
|
|
|
|
| def _norm_sentiment(name: str | None) -> str: |
| if not name: |
| return "neutral" |
| n = str(name).strip().lower() |
| if n in _SENTIMENT_VA: |
| return n |
| |
| for k in _SENTIMENT_VA: |
| if k in n or n in k: |
| return k |
| return "neutral" |
|
|
|
|
| def _va(name: str) -> tuple[float, float]: |
| return _SENTIMENT_VA[_norm_sentiment(name)] |
|
|
|
|
| def _clamp01(x: float) -> float: |
| return max(0.0, min(1.0, float(x))) |
|
|
|
|
| |
| |
| |
|
|
| @dataclass |
| class SlugState: |
| """How the slug itself looks and feels β for the CHARACTER renderer.""" |
| mood: str |
| valence: float |
| energy: float |
| scars: int |
| expression: str |
| pose: str |
| eye_state: str |
|
|
|
|
| @dataclass |
| class ShellState: |
| """The nautilus geometry parameters β used by ALL renderers (SVG/3D/guide).""" |
| turns: float |
| growth_curve: str |
| knots: list[dict] |
| jewels: list[dict] |
| aperture: dict |
| palette: dict |
| iridescence: float |
|
|
|
|
| @dataclass |
| class ArcState: |
| """The emotional timeline β for the SCORE and ATMOSPHERE renderers.""" |
| start: str |
| end: str |
| beats: list[dict] |
| tension_curve: list[float] |
|
|
|
|
| @dataclass |
| class BattleState: |
| """The byobu battle cast β for the painted/animated battle layers.""" |
| general: dict |
| adversary: dict |
| fallen: list[dict] |
| archers: list[dict] |
| dragon: dict |
|
|
|
|
| @dataclass |
| class SceneEnv: |
| """The surrounding scene mood β for the BACKDROP/diffusion renderer.""" |
| time_of_day: str |
| weather: str |
| mood_tags: list[str] |
| palette: dict |
|
|
|
|
| @dataclass |
| class SceneGraph: |
| schema_version: str |
| session_id: str |
| duration_minutes: float |
| themes: list[str] |
| slug: SlugState |
| shell: ShellState |
| arc: ArcState |
| battle: BattleState |
| env: SceneEnv |
| |
| extraction: dict = field(default_factory=dict) |
|
|
| def to_dict(self) -> dict[str, Any]: |
| return asdict(self) |
|
|