namespace DeepDrftPublic.Client.Services; /// /// Holds the Mix visualizer's seven continuous-control positions for the lifetime of the WASM app /// instance. Scoped in DI, so it lives across SPA navigations within one listening session — open a /// second mix and the knobs keep where you left them — but a fresh page load (F5) constructs a new /// instance, resetting to defaults. That matches the spec's "persist within session, reset on fresh /// load" without any cookie/localStorage round-trip (lava reframe §7c). /// /// One state object, seven properties — not seven sibling holders, and (deliberately) NO constructor /// parameters: this is a plain scoped value holder, so widening it from four to seven properties adds /// fields + defaults only and never forces a consumer constructor to grow. Each C#-side default mirrors /// a TS-side tuning anchor in MixVisualizer.ts; keep the two in sync, as the existing /// DefaultVisibleSeconds / DEFAULT_VISIBLE_SECONDS pair does. /// /// /// is the decoupling seam between the controls bar and the visualizer bridge. /// The controls component (a sibling of the backdrop in the page tree) only mutates this shared state /// and raises ; the bridge component (MixWaveformVisualizer) subscribes /// and pushes the affected uniform/dial to the JS module. This keeps the JS module handle single-owned /// by the bridge — no handle sharing, no service-locator, no cross-component interop. /// /// public sealed class MixVisualizerControlState { // ── The seven control defaults (lava reframe §7a). Each mirrors a DEFAULT_* anchor in // MixVisualizer.ts; keep the two in sync, as the existing default-sync discipline requires. // Feel-anchors only — Daniel tunes on screen; the ~20% gravity / ~100% heat pair is his stated // sweet spot (§4c). /// /// Default scroll-speed dial. Mirrors DEFAULT_SCROLL_SPEED in MixVisualizer.ts. Normalized /// [0,1] → mapped to the visible time-span via (0 = slow/wide window, /// 1 = fast/tight window). Opens mid-range. /// public const double DefaultScrollSpeed = 0.5; /// /// Default gradient-rotation-speed dial. Mirrors DEFAULT_GRADIENT_ROTATION_SPEED in /// MixVisualizer.ts. Normalized [0,1] → slow→fast anchor-rotation. INERT until Wave R3 builds the /// OKLab three-colour gradient that consumes it. /// public const double DefaultGradientRotationSpeed = 0.3; /// /// Default lava-gravity dial. Mirrors DEFAULT_LAVA_GRAVITY in MixVisualizer.ts. Normalized /// [0,1]; 0 = near-weightless float, 1 = wax falls + settles fast. Tuned to Daniel's ~20% sweet spot. /// public const double DefaultLavaGravity = 0.2; /// /// Default lava-heat dial. Mirrors DEFAULT_LAVA_HEAT in MixVisualizer.ts. Normalized [0,1]; /// 0 = wax rests at the bottom (collision-only), 1 = many small turbulent rising bubbles. Tuned to /// Daniel's ~100% sweet spot. /// public const double DefaultLavaHeat = 1.0; /// /// Default blob-density dial. Mirrors DEFAULT_BLOB_DENSITY in MixVisualizer.ts. Normalized /// [0,1]; 0 = a few large lazy blobs, 1 = many smaller active blobs. /// public const double DefaultBlobDensity = 0.4; /// /// Default collision-strength dial. Mirrors DEFAULT_COLLISION_STRENGTH in MixVisualizer.ts. /// Normalized [0,1]; 0 = soft mush, 1 = hard elastic up-and-out throw. /// public const double DefaultCollisionStrength = 0.5; /// /// Default waveform-width dial. Mirrors DEFAULT_WAVEFORM_WIDTH in MixVisualizer.ts. /// Normalized [0,1]; 1 = full ribbon width, lower narrows the band so the lava gets more room. /// public const double DefaultWaveformWidth = 0.6; /// Apparent bottom-to-top scroll rate, normalized [0,1]. Bridge maps it to a visible /// time-span via ; the standalone resolution/zoom control is gone. public double ScrollSpeed { get; set; } = DefaultScrollSpeed; /// Gradient anchor-rotation rate, normalized [0,1]. Inert until Wave R3 consumes it. public double GradientRotationSpeed { get; set; } = DefaultGradientRotationSpeed; /// Downward force on the wax, normalized [0,1]. public double LavaGravity { get; set; } = DefaultLavaGravity; /// Energy into the lava system, normalized [0,1]. 0 = rest-at-bottom, 1 = roiling. public double LavaHeat { get; set; } = DefaultLavaHeat; /// Amount of wax (blob count/size), normalized [0,1]. public double BlobDensity { get; set; } = DefaultBlobDensity; /// Collision hardness, normalized [0,1]. 0 = soft mush, 1 = hard up-and-out throw. public double CollisionStrength { get; set; } = DefaultCollisionStrength; /// Waveform-band horizontal extent, normalized [0,1]. Narrowing clears room for the lava. public double WaveformWidth { get; set; } = DefaultWaveformWidth; /// /// Raised whenever any control value changes. The visualizer bridge subscribes to push the /// affected dial(s). Mutators set the property then raise this; subscribers re-read the values. /// public event Action? Changed; /// Raise . Called by the controls component after mutating a value. public void NotifyChanged() => Changed?.Invoke(); }