feat(visualizer): Phase 15 control-deck rework

Centered tinted MudOverlay (NowPlayingCard chrome) replaces the anchored popover; eight dials become a deterministic three-row LAVA/WAVE layout; lava + waveform lamp toggles drive a genuine per-subsystem draw-skip; scroll/zoom becomes a slider; playful tooltips; green=interactive/light=static.
This commit is contained in:
daniel-c-harvey
2026-06-17 14:28:15 -04:00
parent fe481d0417
commit dd4f8ddded
8 changed files with 465 additions and 184 deletions
@@ -265,9 +265,10 @@ public partial class WaveformVisualizer : ComponentBase, IAsyncDisposable
return;
}
// Seed the module with the current state now that it exists. All seven control values
// come from the shared (session-persisted) state, so a mix opened mid-session seeds the
// module with the knob positions the listener left them at.
// Seed the module with the current state now that it exists. All control values (the eight
// dials + the two Phase 15 subsystem enables) come from the shared (session-persisted) state,
// so a mix opened mid-session seeds the module with the knob/toggle positions the listener
// left them at.
await PushControlsAsync();
await PushDatumAsync();
await PushPlaybackAsync();
@@ -291,8 +292,9 @@ public partial class WaveformVisualizer : ComponentBase, IAsyncDisposable
// ── Bridge pushes. Each is a no-op until the module handle exists. ───────────────────────────
/// <summary>
/// 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:
/// Push the control values to the module from the shared state — the eight continuous dials plus the
/// two Phase 15 subsystem enables. Used to seed on first render and to re-push when the controls
/// panel signals a change. Each value is its own dedicated dial / enable:
/// <list type="bullet">
/// <item>scroll speed [0,1] is mapped onto the useful zoom band via
/// <see cref="WaveformZoomMapping.ScrollKnobToSeconds"/> and pushed through <c>setScrollSpeed</c>
@@ -302,7 +304,9 @@ public partial class WaveformVisualizer : ComponentBase, IAsyncDisposable
/// <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>
/// <item>waveform width → the ribbon-extent uniform;</item>
/// <item>lava / waveform enabled → <c>setLavaEnabled</c> / <c>setWaveformEnabled</c>, the genuine
/// per-subsystem draw-skip (no physics / no blob upload, ribbon SDF skipped — §10.1).</item>
/// </list>
/// </summary>
private async Task PushControlsAsync()
@@ -319,6 +323,11 @@ public partial class WaveformVisualizer : ComponentBase, IAsyncDisposable
await _handle.InvokeVoidAsync("setFluidViscosity", ControlState.FluidViscosity);
await _handle.InvokeVoidAsync("setCollisionStrength", ControlState.CollisionStrength);
await _handle.InvokeVoidAsync("setWaveformWidth", ControlState.WaveformWidth);
// Phase 15 — the two subsystem enables. "Off" is a genuine draw-skip in the module (no physics,
// no blob upload / ribbon SDF skipped), not a dim. Pushed through the same Changed seam as the
// dials, so a toggle re-reads here exactly as a knob does.
await _handle.InvokeVoidAsync("setLavaEnabled", ControlState.LavaEnabled);
await _handle.InvokeVoidAsync("setWaveformEnabled", ControlState.WaveformEnabled);
}
/// <summary>