feat(mix-visualizer): Phase 10 tuning — smooth waveform, bouncy collision, 8 knobs
Smooth the loudness contour (~50 ms envelope at preprocessing + decode-time, plus smootherstep render reconstruction); retune wax↔waveform collision to bouncy/sub-unity (no explosion/stuck/jitter); split the bubbles knob into fluid-amount + fluid-viscosity (cohesion via uniform-only smin/wobble); retune scroll/gravity/heat/width ranges; make the colour rotation visible and boost OKLab chroma; the controls bar now holds its layout and hides only its knobs via a Visible parameter.
This commit is contained in:
@@ -2,12 +2,17 @@
|
||||
@using DeepDrftPublic.Client.Services
|
||||
@inject MixVisualizerControlState ControlState
|
||||
|
||||
@* The Mix visualizer controls. SEVEN continuous RadialKnobs — scroll speed, gradient rotation speed,
|
||||
lava gravity, lava heat, blob density, collision strength, waveform width — each its own dedicated
|
||||
control with a Material-icon caption. Visibility is controlled by Blazor, not CSS: the host page
|
||||
renders this component only while the lava-lamp toggle is on (@if-guarded), so when off it is not in
|
||||
the DOM at all. There is no collapse/expand animation and no glass surface — the knobs simply appear
|
||||
in their own transparent band and disappear when un-rendered.
|
||||
@* The Mix visualizer controls. EIGHT continuous RadialKnobs — scroll speed, gradient rotation speed,
|
||||
lava gravity, lava heat, fluid amount, fluid viscosity, collision strength, waveform width — each its
|
||||
own dedicated control with a Material-icon caption. The single "bubbles" knob is split into
|
||||
fluid-amount + fluid-viscosity (Phase 10 §5).
|
||||
|
||||
Visibility (Phase 10 §4): the host ALWAYS renders this component now and feeds the lava-lamp toggle
|
||||
into the @Visible parameter. THIS component decides knob visibility — it @if-gates the knobs but keeps
|
||||
the container's reserved size, so the content below the controls bar never pops when the lamp toggles.
|
||||
The gating is Blazor @if (matching the established "@if-gated knob band, no CSS hide/glass/animation"
|
||||
convention) — the knobs are simply not rendered when hidden, while a min-height container holds the
|
||||
layout. No collapse animation, no glass surface, no CSS visibility-hiding of populated knobs.
|
||||
|
||||
It owns NO JS interop: it mutates the shared, session-scoped MixVisualizerControlState and raises its
|
||||
Changed event. The backdrop bridge (MixWaveformVisualizer) subscribes to that event and pushes the
|
||||
@@ -21,72 +26,92 @@
|
||||
|
||||
<div class="mix-visualizer-controls-bar">
|
||||
|
||||
<div class="mix-visualizer-control" role="group" aria-label="Waveform scroll speed">
|
||||
<RadialKnob Value="@ControlState.ScrollSpeed"
|
||||
ValueChanged="@OnScrollSpeedChanged"
|
||||
Min="0" Max="1" Step="0.001"
|
||||
Size="64"
|
||||
Color="Color.Primary" />
|
||||
<MudIcon Icon="@Icons.Material.Filled.Speed" Size="Size.Small" Class="mix-visualizer-control-icon" />
|
||||
</div>
|
||||
@if (Visible)
|
||||
{
|
||||
<div class="mix-visualizer-control" role="group" aria-label="Waveform scroll speed">
|
||||
<RadialKnob Value="@ControlState.ScrollSpeed"
|
||||
ValueChanged="@OnScrollSpeedChanged"
|
||||
Min="0" Max="1" Step="0.001"
|
||||
Size="64"
|
||||
Color="Color.Primary" />
|
||||
<MudIcon Icon="@Icons.Material.Filled.Speed" Size="Size.Small" Class="mix-visualizer-control-icon" />
|
||||
</div>
|
||||
|
||||
<div class="mix-visualizer-control" role="group" aria-label="Color gradient rotation speed">
|
||||
<RadialKnob Value="@ControlState.GradientRotationSpeed"
|
||||
ValueChanged="@OnGradientRotationSpeedChanged"
|
||||
Min="0" Max="1" Step="0.001"
|
||||
Size="64"
|
||||
Color="Color.Primary" />
|
||||
<MudIcon Icon="@Icons.Material.Filled.Palette" Size="Size.Small" Class="mix-visualizer-control-icon" />
|
||||
</div>
|
||||
<div class="mix-visualizer-control" role="group" aria-label="Color gradient rotation speed">
|
||||
<RadialKnob Value="@ControlState.GradientRotationSpeed"
|
||||
ValueChanged="@OnGradientRotationSpeedChanged"
|
||||
Min="0" Max="1" Step="0.001"
|
||||
Size="64"
|
||||
Color="Color.Primary" />
|
||||
<MudIcon Icon="@Icons.Material.Filled.Palette" Size="Size.Small" Class="mix-visualizer-control-icon" />
|
||||
</div>
|
||||
|
||||
<div class="mix-visualizer-control" role="group" aria-label="Lava gravity">
|
||||
<RadialKnob Value="@ControlState.LavaGravity"
|
||||
ValueChanged="@OnLavaGravityChanged"
|
||||
Min="0" Max="1" Step="0.001"
|
||||
Size="64"
|
||||
Color="Color.Primary" />
|
||||
<MudIcon Icon="@Icons.Material.Filled.ArrowDownward" Size="Size.Small" Class="mix-visualizer-control-icon" />
|
||||
</div>
|
||||
<div class="mix-visualizer-control" role="group" aria-label="Lava gravity">
|
||||
<RadialKnob Value="@ControlState.LavaGravity"
|
||||
ValueChanged="@OnLavaGravityChanged"
|
||||
Min="0" Max="1" Step="0.001"
|
||||
Size="64"
|
||||
Color="Color.Primary" />
|
||||
<MudIcon Icon="@Icons.Material.Filled.ArrowDownward" Size="Size.Small" Class="mix-visualizer-control-icon" />
|
||||
</div>
|
||||
|
||||
<div class="mix-visualizer-control" role="group" aria-label="Lava heat">
|
||||
<RadialKnob Value="@ControlState.LavaHeat"
|
||||
ValueChanged="@OnLavaHeatChanged"
|
||||
Min="0" Max="1" Step="0.001"
|
||||
Size="64"
|
||||
Color="Color.Primary" />
|
||||
<MudIcon Icon="@Icons.Material.Filled.LocalFireDepartment" Size="Size.Small" Class="mix-visualizer-control-icon" />
|
||||
</div>
|
||||
<div class="mix-visualizer-control" role="group" aria-label="Lava heat">
|
||||
<RadialKnob Value="@ControlState.LavaHeat"
|
||||
ValueChanged="@OnLavaHeatChanged"
|
||||
Min="0" Max="1" Step="0.001"
|
||||
Size="64"
|
||||
Color="Color.Primary" />
|
||||
<MudIcon Icon="@Icons.Material.Filled.LocalFireDepartment" Size="Size.Small" Class="mix-visualizer-control-icon" />
|
||||
</div>
|
||||
|
||||
<div class="mix-visualizer-control" role="group" aria-label="Blob density and size">
|
||||
<RadialKnob Value="@ControlState.BlobDensity"
|
||||
ValueChanged="@OnBlobDensityChanged"
|
||||
Min="0" Max="1" Step="0.001"
|
||||
Size="64"
|
||||
Color="Color.Primary" />
|
||||
<MudIcon Icon="@Icons.Material.Filled.BubbleChart" Size="Size.Small" Class="mix-visualizer-control-icon" />
|
||||
</div>
|
||||
<div class="mix-visualizer-control" role="group" aria-label="Fluid amount">
|
||||
<RadialKnob Value="@ControlState.FluidAmount"
|
||||
ValueChanged="@OnFluidAmountChanged"
|
||||
Min="0" Max="1" Step="0.001"
|
||||
Size="64"
|
||||
Color="Color.Primary" />
|
||||
<MudIcon Icon="@Icons.Material.Filled.BubbleChart" Size="Size.Small" Class="mix-visualizer-control-icon" />
|
||||
</div>
|
||||
|
||||
<div class="mix-visualizer-control" role="group" aria-label="Collision strength">
|
||||
<RadialKnob Value="@ControlState.CollisionStrength"
|
||||
ValueChanged="@OnCollisionStrengthChanged"
|
||||
Min="0" Max="1" Step="0.001"
|
||||
Size="64"
|
||||
Color="Color.Primary" />
|
||||
<MudIcon Icon="@Icons.Material.Filled.Compress" Size="Size.Small" Class="mix-visualizer-control-icon" />
|
||||
</div>
|
||||
<div class="mix-visualizer-control" role="group" aria-label="Fluid viscosity">
|
||||
<RadialKnob Value="@ControlState.FluidViscosity"
|
||||
ValueChanged="@OnFluidViscosityChanged"
|
||||
Min="0" Max="1" Step="0.001"
|
||||
Size="64"
|
||||
Color="Color.Primary" />
|
||||
<MudIcon Icon="@Icons.Material.Filled.Opacity" Size="Size.Small" Class="mix-visualizer-control-icon" />
|
||||
</div>
|
||||
|
||||
<div class="mix-visualizer-control" role="group" aria-label="Waveform width">
|
||||
<RadialKnob Value="@ControlState.WaveformWidth"
|
||||
ValueChanged="@OnWaveformWidthChanged"
|
||||
Min="0" Max="1" Step="0.001"
|
||||
Size="64"
|
||||
Color="Color.Primary" />
|
||||
<MudIcon Icon="@Icons.Material.Filled.SettingsEthernet" Size="Size.Small" Class="mix-visualizer-control-icon" />
|
||||
</div>
|
||||
<div class="mix-visualizer-control" role="group" aria-label="Collision strength">
|
||||
<RadialKnob Value="@ControlState.CollisionStrength"
|
||||
ValueChanged="@OnCollisionStrengthChanged"
|
||||
Min="0" Max="1" Step="0.001"
|
||||
Size="64"
|
||||
Color="Color.Primary" />
|
||||
<MudIcon Icon="@Icons.Material.Filled.Compress" Size="Size.Small" Class="mix-visualizer-control-icon" />
|
||||
</div>
|
||||
|
||||
<div class="mix-visualizer-control" role="group" aria-label="Waveform width">
|
||||
<RadialKnob Value="@ControlState.WaveformWidth"
|
||||
ValueChanged="@OnWaveformWidthChanged"
|
||||
Min="0" Max="1" Step="0.001"
|
||||
Size="64"
|
||||
Color="Color.Primary" />
|
||||
<MudIcon Icon="@Icons.Material.Filled.SettingsEthernet" Size="Size.Small" Class="mix-visualizer-control-icon" />
|
||||
</div>
|
||||
}
|
||||
|
||||
</div>
|
||||
|
||||
@code {
|
||||
/// <summary>
|
||||
/// Whether the knob band is shown. The host wires its lava-lamp toggle straight into this — the host
|
||||
/// always renders this component, and THIS component decides knob visibility (Phase 10 §4). When
|
||||
/// false the knobs are @if-gated out but the container holds its reserved height (CSS min-height), so
|
||||
/// content below the bar never pops as the lamp toggles.
|
||||
/// </summary>
|
||||
[Parameter] public bool Visible { get; set; }
|
||||
|
||||
// Each handler mutates its own dedicated property then raises Changed — the bridge re-reads and
|
||||
// pushes the affected dial. All values are already normalized [0,1]; the bridge maps scroll speed
|
||||
// to a visible time-span and routes the rest straight to the lava/colour dials.
|
||||
@@ -115,9 +140,15 @@
|
||||
ControlState.NotifyChanged();
|
||||
}
|
||||
|
||||
private void OnBlobDensityChanged(double value)
|
||||
private void OnFluidAmountChanged(double value)
|
||||
{
|
||||
ControlState.BlobDensity = value;
|
||||
ControlState.FluidAmount = value;
|
||||
ControlState.NotifyChanged();
|
||||
}
|
||||
|
||||
private void OnFluidViscosityChanged(double value)
|
||||
{
|
||||
ControlState.FluidViscosity = value;
|
||||
ControlState.NotifyChanged();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,13 @@
|
||||
/* The seven-knob band. Blazor gates its presence (the host renders this component only while the
|
||||
lava-lamp is on), so this is purely a layout rule for the visible state — no collapse machinery, no
|
||||
transitions, no glass surface. A plain transparent horizontal flex row of the seven knobs that wraps
|
||||
to a second line only if the band is genuinely too narrow. */
|
||||
/* The eight-knob band. Phase 10 §4: the host ALWAYS renders this component and the component @if-gates
|
||||
the knobs on its Visible parameter. So the container is permanent and reserves its height whether or
|
||||
not the knobs are present — content below the bar never pops on toggle. No collapse machinery, no
|
||||
transitions, no glass surface. A plain transparent horizontal flex row of the eight knobs that wraps
|
||||
to a second line only if the band is genuinely too narrow.
|
||||
|
||||
min-height reserves one knob-row's worth of space (knob Size=64 + icon caption + gaps + margins) so
|
||||
the empty (hidden) state occupies the same vertical box the populated single-row state does. On very
|
||||
narrow viewports a populated band may wrap to a second row and exceed this floor — the no-pop
|
||||
guarantee is exact for the common single-row (desktop) layout. */
|
||||
.mix-visualizer-controls-bar {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
@@ -9,6 +15,7 @@
|
||||
justify-content: center;
|
||||
gap: 0.85rem 1rem;
|
||||
margin: 0.5rem 0;
|
||||
min-height: 6rem;
|
||||
}
|
||||
|
||||
/* One control: a RadialKnob with its Material icon caption underneath. RadialKnob has no icon slot, so
|
||||
|
||||
@@ -202,28 +202,32 @@ public partial class MixWaveformVisualizer : ComponentBase, IAsyncDisposable
|
||||
// ── Bridge pushes. Each is a no-op until the module handle exists. ───────────────────────────
|
||||
|
||||
/// <summary>
|
||||
/// Push the seven control values to the module from the shared state. Used to seed on first render
|
||||
/// and to re-push when the controls bar signals a change (lava reframe Wave R4). Each value is its
|
||||
/// own dedicated dial now — no more R2 temp-remapping:
|
||||
/// Push the eight control values to the module from the shared state. Used to seed on first render
|
||||
/// and to re-push when the controls bar signals a change. Each value is its own dedicated dial:
|
||||
/// <list type="bullet">
|
||||
/// <item>scroll speed [0,1] is mapped to a visible time-span via <see cref="MixZoomMapping"/> and
|
||||
/// pushed through <c>setScrollSpeed</c> (higher speed → tighter window → faster scroll);</item>
|
||||
/// <item>gradient rotation speed → <c>setGradientRotationSpeed</c> (inert until Wave R3);</item>
|
||||
/// <item>gravity / heat / blob density / collision strength → their dedicated lava-physics dials;</item>
|
||||
/// <item>scroll speed [0,1] is mapped onto the useful zoom band via
|
||||
/// <see cref="MixZoomMapping.ScrollKnobToSeconds"/> and pushed through <c>setScrollSpeed</c>
|
||||
/// (higher speed → tighter window → faster scroll);</item>
|
||||
/// <item>gradient rotation speed → <c>setGradientRotationSpeed</c> (live OKLab anchor rotation);</item>
|
||||
/// <item>gravity / heat / collision strength → their dedicated lava-physics dials;</item>
|
||||
/// <item>fluid amount → <c>setFluidAmount</c> (blob count + volume); fluid viscosity →
|
||||
/// <c>setFluidViscosity</c> (cohesion / sphere-restoration) — the Phase 10 split of the
|
||||
/// former single density knob;</item>
|
||||
/// <item>waveform width → the ribbon-extent uniform.</item>
|
||||
/// </list>
|
||||
/// </summary>
|
||||
private async Task PushControlsAsync()
|
||||
{
|
||||
if (_handle is null) return;
|
||||
// Scroll speed is a normalized [0,1] axis; map it to the visible time-span the renderer scrolls
|
||||
// through. The log map keeps the even-to-the-hand feel the old zoom slider had.
|
||||
var visibleSeconds = MixZoomMapping.FractionToSeconds(ControlState.ScrollSpeed);
|
||||
// Scroll speed is a normalized [0,1] axis; map it onto the useful zoom band (Phase 10 retune —
|
||||
// the knob's full travel now covers the 60%–100% zoom range, dropping the dead slow/wide end).
|
||||
var visibleSeconds = MixZoomMapping.ScrollKnobToSeconds(ControlState.ScrollSpeed);
|
||||
await _handle.InvokeVoidAsync("setScrollSpeed", visibleSeconds);
|
||||
await _handle.InvokeVoidAsync("setGradientRotationSpeed", ControlState.GradientRotationSpeed);
|
||||
await _handle.InvokeVoidAsync("setLavaGravity", ControlState.LavaGravity);
|
||||
await _handle.InvokeVoidAsync("setLavaHeat", ControlState.LavaHeat);
|
||||
await _handle.InvokeVoidAsync("setBlobDensity", ControlState.BlobDensity);
|
||||
await _handle.InvokeVoidAsync("setFluidAmount", ControlState.FluidAmount);
|
||||
await _handle.InvokeVoidAsync("setFluidViscosity", ControlState.FluidViscosity);
|
||||
await _handle.InvokeVoidAsync("setCollisionStrength", ControlState.CollisionStrength);
|
||||
await _handle.InvokeVoidAsync("setWaveformWidth", ControlState.WaveformWidth);
|
||||
}
|
||||
|
||||
@@ -15,6 +15,27 @@ public static class MixZoomMapping
|
||||
/// <summary>Longest span (min zoom). Tunable.</summary>
|
||||
public const double MaxVisibleSeconds = 30.0;
|
||||
|
||||
/// <summary>
|
||||
/// Lower edge of the useful zoom band on the underlying fraction axis. Phase 10 retune: the bottom
|
||||
/// 60% of the old knob travel (fraction 0…0.6) was a useless slow/wide window, so the scroll knob's
|
||||
/// full [0,1] travel now maps onto the upper 0.6…1.0 band where every position reads as a useful
|
||||
/// zoom (Daniel: "range below 60% is useless; optimize for the current 60%–110% zoom values" — 110%
|
||||
/// caps at the hard 0.333 s max-zoom anchor, fraction 1.0).
|
||||
/// </summary>
|
||||
public const double ScrollKnobZoomFloor = 0.60;
|
||||
|
||||
/// <summary>
|
||||
/// Maps the scroll-speed knob [0,1] onto the useful zoom band [<see cref="ScrollKnobZoomFloor"/>, 1.0]
|
||||
/// of the underlying fraction axis, then to visible seconds. So knob 0 sits at the slow edge of the
|
||||
/// *useful* range (not the dead slow end), and knob 1 reaches max zoom. Phase 10 scroll retune.
|
||||
/// </summary>
|
||||
public static double ScrollKnobToSeconds(double knob)
|
||||
{
|
||||
knob = Math.Clamp(knob, 0, 1);
|
||||
var fraction = ScrollKnobZoomFloor + (1.0 - ScrollKnobZoomFloor) * knob;
|
||||
return FractionToSeconds(fraction);
|
||||
}
|
||||
|
||||
/// <summary>Slider position [0, 1] -> visible seconds. 0 = zoomed out, 1 = zoomed in.</summary>
|
||||
public static double FractionToSeconds(double fraction)
|
||||
{
|
||||
|
||||
@@ -53,15 +53,13 @@ else
|
||||
ShowMeta="false"
|
||||
ShowShareRow="false">
|
||||
<TopContent>
|
||||
@* The seven-knob band lives in its own full-width area below the back/lamp top row.
|
||||
Blazor — not CSS — controls its visibility: it is rendered only while the lava-lamp is
|
||||
on, so when off it is not in the DOM at all. No background, no animation, no reflow of
|
||||
the row above. The band mutates the shared MixVisualizerControlState; the backdrop
|
||||
bridge pushes the dials. A knob drag does not toggle it — the lamp's click does. *@
|
||||
@if (_controlsExpanded)
|
||||
{
|
||||
<MixVisualizerControls />
|
||||
}
|
||||
@* The eight-knob band lives in its own full-width area below the back/lamp top row.
|
||||
Phase 10 §4: the control is ALWAYS rendered; the lava-lamp toggle feeds its Visible
|
||||
parameter, and the control itself @if-gates the knobs while holding the container's
|
||||
reserved height — so content below never pops on toggle. The band mutates the shared
|
||||
MixVisualizerControlState; the backdrop bridge pushes the dials. A knob drag does not
|
||||
toggle it — the lamp's click does. *@
|
||||
<MixVisualizerControls Visible="@_controlsExpanded" />
|
||||
</TopContent>
|
||||
<TopRightAction>
|
||||
@* Lava-lamp button top-right, across from the back link. Toggles the knob band below the
|
||||
|
||||
@@ -1,17 +1,18 @@
|
||||
namespace DeepDrftPublic.Client.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Holds the Mix visualizer's seven continuous-control positions for the lifetime of the WASM app
|
||||
/// Holds the Mix visualizer's eight 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
|
||||
/// <c>DefaultVisibleSeconds</c> / <c>DEFAULT_VISIBLE_SECONDS</c> pair does.
|
||||
/// One state object, eight properties — not eight sibling holders, and (deliberately) NO constructor
|
||||
/// parameters: this is a plain scoped value holder, so widening it (the Phase 10 split of the single
|
||||
/// density knob into fluid-amount + fluid-viscosity) 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 <c>DefaultVisibleSeconds</c> /
|
||||
/// <c>DEFAULT_VISIBLE_SECONDS</c> pair does.
|
||||
///
|
||||
/// <para>
|
||||
/// <see cref="Changed"/> is the decoupling seam between the controls bar and the visualizer bridge.
|
||||
@@ -23,8 +24,8 @@ namespace DeepDrftPublic.Client.Services;
|
||||
/// </summary>
|
||||
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.
|
||||
// ── The eight control defaults (Phase 10). Each mirrors a DEFAULT_* anchor in
|
||||
// MixVisualizer.ts; keep the two in sync, as the default-sync discipline requires.
|
||||
// Feel-anchors only — Daniel tunes on screen; the ~20% gravity / ~100% heat pair is his stated
|
||||
// sweet spot (§4c).
|
||||
|
||||
@@ -37,10 +38,10 @@ public sealed class MixVisualizerControlState
|
||||
|
||||
/// <summary>
|
||||
/// Default gradient-rotation-speed dial. Mirrors <c>DEFAULT_GRADIENT_ROTATION_SPEED</c> in
|
||||
/// MixVisualizer.ts. Normalized [0,1] → slow→fast anchor-rotation. INERT until Wave R3 builds the
|
||||
/// OKLab three-colour gradient that consumes it.
|
||||
/// MixVisualizer.ts. Normalized [0,1] → slow→fast anchor-rotation; drives the live OKLab
|
||||
/// three-colour gradient. 0.45 opens with a clearly-visible ~7 s colour cycle (Phase 10 §3.2).
|
||||
/// </summary>
|
||||
public const double DefaultGradientRotationSpeed = 0.3;
|
||||
public const double DefaultGradientRotationSpeed = 0.45;
|
||||
|
||||
/// <summary>
|
||||
/// Default lava-gravity dial. Mirrors <c>DEFAULT_LAVA_GRAVITY</c> in MixVisualizer.ts. Normalized
|
||||
@@ -56,10 +57,18 @@ public sealed class MixVisualizerControlState
|
||||
public const double DefaultLavaHeat = 1.0;
|
||||
|
||||
/// <summary>
|
||||
/// Default blob-density dial. Mirrors <c>DEFAULT_BLOB_DENSITY</c> in MixVisualizer.ts. Normalized
|
||||
/// [0,1]; 0 = a few large lazy blobs, 1 = many smaller active blobs.
|
||||
/// Default fluid-amount dial. Mirrors <c>DEFAULT_FLUID_AMOUNT</c> in MixVisualizer.ts. The first
|
||||
/// half of the Phase 10 "bubbles" split. Normalized [0,1]; 0 = few small blobs, 1 = many larger
|
||||
/// blobs (more wax in the container — blob count + per-blob volume).
|
||||
/// </summary>
|
||||
public const double DefaultBlobDensity = 0.4;
|
||||
public const double DefaultFluidAmount = 0.4;
|
||||
|
||||
/// <summary>
|
||||
/// Default fluid-viscosity / cohesion dial. Mirrors <c>DEFAULT_FLUID_VISCOSITY</c> in
|
||||
/// MixVisualizer.ts. The second half of the Phase 10 "bubbles" split. Normalized [0,1]; 1 = high
|
||||
/// cohesion (crisp spheres that snap back), 0 = low cohesion (deforms freely, stays gooey/merged).
|
||||
/// </summary>
|
||||
public const double DefaultFluidViscosity = 0.6;
|
||||
|
||||
/// <summary>
|
||||
/// Default collision-strength dial. Mirrors <c>DEFAULT_COLLISION_STRENGTH</c> in MixVisualizer.ts.
|
||||
@@ -69,15 +78,16 @@ public sealed class MixVisualizerControlState
|
||||
|
||||
/// <summary>
|
||||
/// Default waveform-width dial. Mirrors <c>DEFAULT_WAVEFORM_WIDTH</c> in MixVisualizer.ts.
|
||||
/// Normalized [0,1]; 1 = full ribbon width, lower narrows the band so the lava gets more room.
|
||||
/// Normalized [0,1], mapped onto the useful 10%–95% ribbon-extent band (Phase 10 §3.7); 0.5 opens
|
||||
/// mid-band. Narrowing clears room for the lava.
|
||||
/// </summary>
|
||||
public const double DefaultWaveformWidth = 0.6;
|
||||
public const double DefaultWaveformWidth = 0.5;
|
||||
|
||||
/// <summary>Apparent bottom-to-top scroll rate, normalized [0,1]. Bridge maps it to a visible
|
||||
/// time-span via <see cref="MixZoomMapping"/>; the standalone resolution/zoom control is gone.</summary>
|
||||
public double ScrollSpeed { get; set; } = DefaultScrollSpeed;
|
||||
|
||||
/// <summary>Gradient anchor-rotation rate, normalized [0,1]. Inert until Wave R3 consumes it.</summary>
|
||||
/// <summary>Gradient anchor-rotation rate, normalized [0,1]. Drives the live OKLab gradient.</summary>
|
||||
public double GradientRotationSpeed { get; set; } = DefaultGradientRotationSpeed;
|
||||
|
||||
/// <summary>Downward force on the wax, normalized [0,1].</summary>
|
||||
@@ -86,8 +96,12 @@ public sealed class MixVisualizerControlState
|
||||
/// <summary>Energy into the lava system, normalized [0,1]. 0 = rest-at-bottom, 1 = roiling.</summary>
|
||||
public double LavaHeat { get; set; } = DefaultLavaHeat;
|
||||
|
||||
/// <summary>Amount of wax (blob count/size), normalized [0,1].</summary>
|
||||
public double BlobDensity { get; set; } = DefaultBlobDensity;
|
||||
/// <summary>Amount of wax (blob count + per-blob volume), normalized [0,1]. Phase 10 split, part 1.</summary>
|
||||
public double FluidAmount { get; set; } = DefaultFluidAmount;
|
||||
|
||||
/// <summary>Fluid viscosity / cohesion, normalized [0,1]. 1 = crisp spheres, 0 = gooey/deformed.
|
||||
/// Phase 10 split, part 2.</summary>
|
||||
public double FluidViscosity { get; set; } = DefaultFluidViscosity;
|
||||
|
||||
/// <summary>Collision hardness, normalized [0,1]. 0 = soft mush, 1 = hard up-and-out throw.</summary>
|
||||
public double CollisionStrength { get; set; } = DefaultCollisionStrength;
|
||||
|
||||
Reference in New Issue
Block a user