9c347d580f
Tuning the look by eye in the simulator (design §8) surfaced that the dark mood pole rendered a full-frame hue-rotate(-200deg) — rock orange, trees purple: the disorienting look rejected in session 0008, not the peaceful POC dark_frame. Replace it with darken + slight desaturate on the video filter plus a multiply-blended deep-blue wash (#tint, below the SVG HUD so the overlay stays legible). Dark now reads cool/somber with natural greens, like the approved POC look; light/left/right unchanged and confirmed peaceful. With full tilt now tasteful on every axis, LOCK DEFAULT_CALIBRATION to unity gains + linear variant map as a deliberate by-eye choice (not placeholders), closing the open session-0006 knob->strength decision: knobs run 0=off..4=max, equal Dark/Light = identity, the 5 notches map 1:1 to the 5 discrete Right bakes. Guard the locked constants with test_default_calibration_is_locked. Resolves design §8 open questions (calibration curve shape; grade-vs-overlay ordering) and records the dark-grade fix. Full suite 193 passed / 2 skipped. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
148 lines
5.1 KiB
Python
148 lines
5.1 KiB
Python
"""The alteration engine: a knob vector -> a layered RenderPlan (design §4, §5).
|
|
|
|
Reconciled slice (2026-06-07): the Right axis is a DISCRETE selection of a
|
|
pre-baked, flow-stabilized restyle variant (not a continuous blend), 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 curves 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) + a pre-baked
|
|
Right restyle variant.
|
|
- Overlay: AnalyticalOverlay (Left), composited on top at runtime.
|
|
Left and Right stack (different layers); Dark/Light are the two poles of one
|
|
mood grade. See docs/superpowers/specs/2026-06-07-reconciled-simulator-
|
|
alteration-slice-design.md.
|
|
"""
|
|
|
|
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 intensity (clamped to [0, 1]).
|
|
- right_variant_map: knob value (0..4) -> pre-baked Right variant index.
|
|
"""
|
|
|
|
mood_gain: float = 1.0
|
|
overlay_gain: float = 1.0
|
|
right_variant_map: tuple = (0, 1, 2, 3, 4)
|
|
|
|
|
|
# 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.
|
|
# - right_variant_map linear: the 5 knob notches map 1:1 onto the 5 discrete
|
|
# pre-baked Right strengths (0 = raw base).
|
|
# These are unity/linear by deliberate choice, not as placeholders. The curve
|
|
# stays parameterized so a future re-bake or 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,
|
|
right_variant_map=(0, 1, 2, 3, 4),
|
|
)
|
|
|
|
|
|
@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 Restyle:
|
|
"""Right axis (§4.1): selects a pre-baked, flow-stabilized restyle variant.
|
|
`variant` is a discrete index (0 = raw base, no restyle)."""
|
|
|
|
variant: int
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class RenderPlan:
|
|
"""The full layered alteration for one knob vector (§4.2)."""
|
|
|
|
grade: ColorGrade
|
|
overlay: AnalyticalOverlay
|
|
restyle: Restyle
|
|
|
|
@property
|
|
def is_identity(self) -> bool:
|
|
"""True when the plan leaves the neutral base un-altered."""
|
|
return (
|
|
self.grade.is_identity
|
|
and self.overlay.level == 0
|
|
and self.restyle.variant == 0
|
|
)
|
|
|
|
|
|
def _overlay_intensity(left: int, cal: Calibration) -> float:
|
|
return _clamp(cal.overlay_gain * left / KNOB_MAX, 0.0, 1.0)
|
|
|
|
|
|
def _right_variant(right: int, cal: Calibration) -> int:
|
|
return cal.right_variant_map[right]
|
|
|
|
|
|
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),
|
|
),
|
|
restyle=Restyle(variant=_right_variant(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},
|
|
"restyle": {"variant": plan.restyle.variant},
|
|
"is_identity": plan.is_identity,
|
|
}
|