"""The alteration engine: a knob vector -> a layered RenderPlan (design §4, §5). Right axis (reframed session 0013): a DETERMINISTIC, real-time dream applied live to the base footage — a STILL altered state, not the old pre-baked SD variant that churned across the loop (Right-axis dream design, docs/superpowers/specs/2026-06-22-right-axis-deterministic-dream-design.md). The Left axis carries its knob LEVEL so a runtime annotation track can pick which labels show, and a frozen `Calibration` parameterizes the knob->strength gains so they can be tuned by eye in the simulator and baked into DEFAULT_CALIBRATION. Layers compose per §4.2: - Substrate: ColorGrade (Dark/Light mood, center = identity §5) + the Right Dream (deterministic luminous haze, applied client-side from `dream.intensity`). - Overlay: AnalyticalOverlay (Left) + the affect channel, composited on top. Left and Right stack (different layers); Dark/Light are the two poles of one mood grade. """ from __future__ import annotations from dataclasses import dataclass from hef.selection import Coordinate KNOB_MAX = 4 # knob full-scale (0..4) def _clamp(x: float, lo: float, hi: float) -> float: return max(lo, min(hi, x)) @dataclass(frozen=True) class Calibration: """Tunable knob->strength curves (settled by eye in the sim, then baked). - mood_gain: scales the signed Dark/Light tone (result clamped to [-1, 1]). - overlay_gain: scales the Left overlay (and affect) intensity (clamped [0, 1]). - dream_gain: scales the Right deterministic-dream intensity (clamped [0, 1]). """ mood_gain: float = 1.0 overlay_gain: float = 1.0 dream_gain: float = 1.0 # The LOCKED calibration (session 0010, 2026-06-07) — settled by eye in the # simulator, closing the open session-0006 knob->strength decision. Convention: # every experience knob runs 0 = off .. 4 = max, and equal Dark/Light = identity # (raw footage); there is no "centered at 2 = no push" coordinate. # - mood_gain = 1.0: full Dark/Light knob reaches the full mood grade. By-eye # evidence (POC renders + sim, after the dark-grade fix this session) shows # full tilt is peaceful on every axis, so no softening is warranted. # - overlay_gain = 1.0: full Left = opacity 1.0; the HUD is legible, not # overwhelming, sitting above the grade. # - dream_gain = 1.0: full Right = dream intensity 1.0; the deterministic # luminous haze (Right-axis dream design, session 0013) reaches full strength. # These are unity by deliberate choice, not as placeholders. The gains stay # parameterized so a different feel is one edit away; test_default_calibration_is_locked # guards the values from drifting silently. DEFAULT_CALIBRATION = Calibration( mood_gain=1.0, overlay_gain=1.0, dream_gain=1.0, ) @dataclass(frozen=True) class ColorGrade: """Mood-axis grade (§5). `tone` is signed: >0 warm yellow->white (light), <0 cool blue->black (dark), 0 = identity (raw, ungraded).""" tone: float @property def is_identity(self) -> bool: return self.tone == 0.0 @dataclass(frozen=True) class AnalyticalOverlay: """Left axis (§4.1): analytical HUD/annotation, composited on top at runtime. `level` is the Left knob (0..4); a runtime annotation track uses it to pick which labels appear. `intensity` 0..1 is the overlay opacity/strength.""" level: int intensity: float @dataclass(frozen=True) class AffectOverlay: """Affect channel (Left x Right): emotion-words surfaced only when BOTH the analytical (Left) and dreamlike (Right) knobs are up. `strength` is `min(left, right)` (0..4) — a runtime affect track picks which feeling words appear by it; `intensity` 0..1 is their opacity. The dream leaking into the machine's reading (affect-channel design, session 0013).""" strength: int intensity: float @dataclass(frozen=True) class Dream: """Right axis: a deterministic, real-time dream applied live to the base footage — a STILL altered state (Right-axis dream design, session 0013), NOT the old pre-baked SD variant that churned across the loop. `strength` is the Right knob (0..4); `intensity` 0..1 is the dream's strength (the client maps it to the luminous-haze look, the way it maps `grade.tone` to a filter).""" strength: int intensity: float @dataclass(frozen=True) class RenderPlan: """The full layered alteration for one knob vector (§4.2).""" grade: ColorGrade overlay: AnalyticalOverlay affect: AffectOverlay dream: Dream @property def is_identity(self) -> bool: """True when the plan leaves the neutral base un-altered. Affect needs no clause: `min(left,right) > 0` implies `left > 0`, so `overlay.level > 0` already makes a plan with active affect non-identity.""" return ( self.grade.is_identity and self.overlay.level == 0 and self.dream.strength == 0 ) def _overlay_intensity(left: int, cal: Calibration) -> float: return _clamp(cal.overlay_gain * left / KNOB_MAX, 0.0, 1.0) def _affect_strength(left: int, right: int) -> int: """Affect surfaces only when BOTH knobs are up — gated by the smaller one.""" return min(left, right) def _affect_intensity(left: int, right: int, cal: Calibration) -> float: return _clamp(cal.overlay_gain * _affect_strength(left, right) / KNOB_MAX, 0.0, 1.0) def _dream_intensity(right: int, cal: Calibration) -> float: return _clamp(cal.dream_gain * right / KNOB_MAX, 0.0, 1.0) def _mood_tone(dark: int, light: int, cal: Calibration) -> float: return _clamp(cal.mood_gain * (light - dark) / KNOB_MAX, -1.0, 1.0) def plan_alteration( coord: Coordinate, calibration: Calibration = DEFAULT_CALIBRATION ) -> RenderPlan: """Map a knob vector to its layered RenderPlan (design §4).""" return RenderPlan( grade=ColorGrade(tone=_mood_tone(coord.dark, coord.light, calibration)), overlay=AnalyticalOverlay( level=coord.left, intensity=_overlay_intensity(coord.left, calibration), ), affect=AffectOverlay( strength=_affect_strength(coord.left, coord.right), intensity=_affect_intensity(coord.left, coord.right, calibration), ), dream=Dream( strength=coord.right, intensity=_dream_intensity(coord.right, calibration), ), ) def render_plan_to_dict(plan: RenderPlan) -> dict: """JSON-serializable form for the simulator API.""" return { "grade": {"tone": plan.grade.tone}, "overlay": {"level": plan.overlay.level, "intensity": plan.overlay.intensity}, "affect": {"strength": plan.affect.strength, "intensity": plan.affect.intensity}, "dream": {"strength": plan.dream.strength, "intensity": plan.dream.intensity}, "is_identity": plan.is_identity, }