diff --git a/docs/fensalir-rendering-rebuild-migration.md b/docs/fensalir-rendering-rebuild-migration.md index 45c7551..fd0168e 100644 --- a/docs/fensalir-rendering-rebuild-migration.md +++ b/docs/fensalir-rendering-rebuild-migration.md @@ -93,8 +93,8 @@ Derived state: - spectrum lines, debug views, screenshots, OBS bridge endpoints, FFmpeg/SRT helpers, and Eve dashboard feeds are views or diagnostics. -- The direct spline-spectrum dashboard is useful because it reveals buffers; it - is not the architectural owner of field rendering. +- The old direct spline-spectrum dashboard is silent. FieldEvidence TubeField + owns the spectrum surface path; screenshots and logs remain diagnostics. Forbidden writers: @@ -294,14 +294,11 @@ flowchart TD ## Migration Plan -### Phase 0: Freeze The Current Debug Surface +### Phase 0: Silence Legacy Debug Surfaces -Keep the direct `AquariumSplineFrame` dashboard because it exposes live audio -buffers and is useful for sanity checks. Label it as deterministic debug -surface, not production field evidence. - -Delete or keep dead any point-splat spectrum path. It already proved the wrong -owner. +The direct `AquariumSplineFrame` spectrum dashboard and point-splat spectrum +path are no longer scene writers. Live spectrum rendering flows through +FieldEvidence TubeField claims and generated mesh expansion. ### Phase 1: Add Bridge DTOs In Mimir.Runtime diff --git a/docs/implementation-plan.md b/docs/implementation-plan.md index 995a08e..14b0dcc 100644 --- a/docs/implementation-plan.md +++ b/docs/implementation-plan.md @@ -133,22 +133,12 @@ a named invariant that the native runtime cannot protect yet. service and can emit live sync telemetry with `MIMIR_SYNC_TELEMETRY_SECONDS`. UI and telemetry read cached reports/states; they do not run synchronization analysis. -- `MimirRuntime` publishes live ASIO spectrum history to Fensalir as direct - `AquariumSplineFrame` tube surfaces. The spline shader owns visible coverage: - it evaluates capsule/tube distance and writes scene color plus temporal - reservoir guide MRTs. Direct spline rendering is deterministic; native - blue-noise jitter belongs in a future stochastic surface-claim producer, not - in the direct debug surface. The old - `AquariumBufferFieldFrame` / `ReservoirSplats` dashboard path is no longer - allowed to decide this surface. Spectrum rows are timestamped history frames: - row identity is stable, source identity is stable, band identity is stable, - and Z is continuous time-age instead of integer queue age. Current budget - knobs are `MIMIR_SPECTRUM_SPLINE_WINDOW_STRIDE`, - `MIMIR_SPECTRUM_SPLINE_BAND_STRIDE`, and - `MIMIR_SPECTRUM_SPLINE_SUBDIVISIONS`; defaults draw every second history row, - every second frequency bin, and one subdivision per segment. Decimation is - allowed only by persistent sequence or frequency-bin identity, never by - render-frame phase. +- `MimirRuntime` no longer submits the legacy direct `AquariumSplineFrame` + spectrum dashboard. Live spectrum visualization authority is the + `AquariumFieldEvidenceFrame`: Mimir declares the rolling resource, emits a + Tube claim and `AquariumFieldTubeSplineLowering`, and Fensalir plans/expands + the TubeField generated mesh. The old `AquariumBufferFieldFrame` / + `ReservoirSplats` dashboard path is also silent. - `MimirFensalirFieldLowering` now emits `AquariumFieldResourceDeclaration` rows for live native/GPU payload views. Observation claims reference `mimir:resource:*` keys, and Fensalir validation/planning can reject or defer diff --git a/notes/current-system-map.md b/notes/current-system-map.md index 6013650..d5d4657 100644 --- a/notes/current-system-map.md +++ b/notes/current-system-map.md @@ -372,28 +372,12 @@ larger claims dishonest. Fensalir's SDF probe/lowering stage should emit surface splats at that detail-adaptive density, not one splat per pixel by default. -The live debug spectrum view is a Fensalir spline-tube SDF surface. Mimir -submits rolling ASIO spectrum history as `AquariumSplineFrame` trails: -frequency lives on X, amplitude on Y, history on Z, and channels stack along -Y. `AquariumBufferFieldFrame` / fractal `ReservoirSplats` no longer own this -dashboard, because point splats were the wrong visible primitive for a surface -whose truth is a tube SDF. - -Fensalir's spline shader owns visible coverage. Geometry is only the -conservative segment envelope; the pixel shader evaluates capsule/tube -distance, writes color/metadata/control, and emits `reservoirGuide` for the -scene temporal path. The direct spline fallback does not use stochastic -blue-noise perturbation; blue-noise sampling belongs in a real reservoir -producer, not in a direct debug surface where the viewer needs coherent lines. -Mimir must not rotate -which rows or bands exist per frame: that made the surface temporally -incoherent and impossible to follow. The live model stores timestamped spectrum -history frames with monotonically increasing sequence ids, computes Z from -continuous sample age, keeps source ordering stable across the whole history, -and keeps band selection stable. `MIMIR_SPECTRUM_SPLINE_BAND_STRIDE` and -`MIMIR_SPECTRUM_SPLINE_WINDOW_STRIDE` and `MIMIR_SPECTRUM_SPLINE_SUBDIVISIONS` -remain budget knobs, but they may only select by persistent frequency-bin or -history-sequence identity. Render-frame phase is forbidden as a visual owner. +The live debug spectrum view is now field-evidence owned. Mimir no longer +submits direct `AquariumSplineFrame` trails for the spectrum dashboard and does +not submit `AquariumBufferFieldFrame` / fractal `ReservoirSplats` for it either. +Mimir declares the rolling resource, publishes a Tube claim plus +`AquariumFieldTubeSplineLowering`, and Fensalir owns planning, generated mesh +expansion, and TubeField material rendering. ## Known Risks diff --git a/src/Mimir.Runtime/MimirRuntime.cs b/src/Mimir.Runtime/MimirRuntime.cs index d0e4ff3..934477b 100644 --- a/src/Mimir.Runtime/MimirRuntime.cs +++ b/src/Mimir.Runtime/MimirRuntime.cs @@ -31,11 +31,6 @@ public sealed class MimirRuntime : IAquariumRuntime private const float SpectrumCameraFitPadding = 1.12f; private const float SpectrumFrustumMinimumNear = 0.01f; private const float SpectrumSplineTubePadding = 0.18f; - private const float SpectrumSplineEmission = 1.85f; - private const float SpectrumSplineHotBloom = 8.0f; - private const int DefaultSpectrumSplineWindowStride = 2; - private const int DefaultSpectrumSplineBandStride = 1; - private const int DefaultSpectrumSplineSubdivisions = 2; private readonly MimirSynchronizationHub synchronization; private readonly MimirFensalirFieldLowering fieldLowering; private readonly MimirAudioSpectrumAnalyzer spectrumAnalyzer; @@ -145,7 +140,6 @@ public sealed class MimirRuntime : IAquariumRuntime { var channelCount = Math.Max(1, lastAudioSpectra.Count); var fieldEvidenceFrame = BuildFieldEvidenceFrame(); - var splineFrame = BuildSpectrumSplineFrame(); var windowCount = Math.Max(1, spectrumHistory.Count); var (splineMin, splineMax) = SpectrumSplineAabb(channelCount, windowCount); var cameraTarget = (splineMin + splineMax) * 0.5f; @@ -172,7 +166,7 @@ public sealed class MimirRuntime : IAquariumRuntime UseStarfieldBackground = false, FieldEvidenceFrame = fieldEvidenceFrame, BufferFieldFrame = AquariumBufferFieldFrame.Empty, - SplineFrame = splineFrame, + SplineFrame = AquariumSplineFrame.Empty, }); } @@ -742,27 +736,6 @@ public sealed class MimirRuntime : IAquariumRuntime : 96; } - private static int ParseSpectrumSplineBandStride() - { - return int.TryParse(Environment.GetEnvironmentVariable("MIMIR_SPECTRUM_SPLINE_BAND_STRIDE"), out var value) - ? Math.Clamp(value, 1, 16) - : DefaultSpectrumSplineBandStride; - } - - private static int ParseSpectrumSplineWindowStride() - { - return int.TryParse(Environment.GetEnvironmentVariable("MIMIR_SPECTRUM_SPLINE_WINDOW_STRIDE"), out var value) - ? Math.Clamp(value, 1, 16) - : DefaultSpectrumSplineWindowStride; - } - - private static int ParseSpectrumSplineSubdivisions() - { - return int.TryParse(Environment.GetEnvironmentVariable("MIMIR_SPECTRUM_SPLINE_SUBDIVISIONS"), out var value) - ? Math.Clamp(value, 1, 8) - : DefaultSpectrumSplineSubdivisions; - } - private static bool IsTruthy(string? value) => string.Equals(value, "1", StringComparison.OrdinalIgnoreCase) || string.Equals(value, "true", StringComparison.OrdinalIgnoreCase) || @@ -914,198 +887,6 @@ public sealed class MimirRuntime : IAquariumRuntime return (min, max); } - private AquariumSplineFrame BuildSpectrumSplineFrame() - { - if (!sceneReady || spectrumHistory.Count == 0) - { - return AquariumSplineFrame.Empty; - } - - var windows = spectrumHistory.ToArray(); - var sourceIds = windows - .SelectMany(window => window.Spectra) - .Select(spectrum => spectrum.SourceId) - .Distinct(StringComparer.Ordinal) - .OrderBy(sourceId => sourceId, StringComparer.Ordinal) - .ToArray(); - if (sourceIds.Length == 0) - { - return AquariumSplineFrame.Empty; - } - - var windowStride = ParseSpectrumSplineWindowStride(); - var bandStride = ParseSpectrumSplineBandStride(); - var subdivisions = ParseSpectrumSplineSubdivisions(); - var approximateWindowCount = Math.Max(1, (windows.Length + windowStride - 1) / windowStride); - var splines = new List(approximateWindowCount * sourceIds.Length + sourceIds.Length * 4); - AddSpectrumGridSplines(splines, sourceIds.Length, windows.Length); - - foreach (var window in windows) - { - var isLatest = window.Sequence == windows[^1].Sequence; - if (!isLatest && windowStride > 1 && window.Sequence % windowStride != 0) - { - continue; - } - - var ageSeconds = Math.Max(0.0f, runtimeSeconds - window.RuntimeSeconds); - var ageT = Math.Clamp(ageSeconds / Math.Max(spectrumUpdateIntervalSeconds * Math.Max(1, SpectrumHistoryWindowCount - 1), 0.0001f), 0.0f, 1.0f); - var alpha = Math.Clamp(0.10f + MathF.Pow(1.0f - ageT, 1.35f) * 0.90f, 0.10f, 1.0f); - var z = ageSeconds * SpectrumWindowDepthSeparation / Math.Max(spectrumUpdateIntervalSeconds, 0.0001f); - var spectraBySource = window.Spectra.ToDictionary(spectrum => spectrum.SourceId, StringComparer.Ordinal); - for (var channelIndex = 0; channelIndex < sourceIds.Length; channelIndex++) - { - if (!spectraBySource.TryGetValue(sourceIds[channelIndex], out var spectrum) || spectrum.BandDecibels.Count < 2) - { - continue; - } - - var points = BuildSpectrumWindowSpline( - spectrum, - channelIndex * SpectrumChannelSeparation, - z, - alpha, - bandStride); - splines.Add(new AquariumSpline3D( - $"spectrum-{spectrum.SourceId}-{window.Sequence}", - points, - new AquariumSplineStyle(0.020f, SpectrumSplineEmission, 1.0f, 0.72f, 0.20f), - CatmullRomSubdivisions: subdivisions)); - } - } - - return new AquariumSplineFrame { Splines = splines }; - } - - private static IReadOnlyList BuildSpectrumWindowSpline( - MimirAudioSpectrumSnapshot spectrum, - float channelBaseY, - float z, - float alpha, - int bandStride) - { - var bands = spectrum.BandDecibels; - var envelope = SmoothedSpectrumEnvelope(spectrum); - var points = new List((bands.Count + bandStride - 1) / bandStride + 2); - for (var index = 0; index < bands.Count; index++) - { - var isEdge = index == 0 || index == bands.Count - 1; - if (!isEdge && bandStride > 1 && index % bandStride != 0) - { - continue; - } - - var x = -SpectrumWidth * 0.5f + SpectrumWidth * index / Math.Max(1, bands.Count - 1); - var normalized = envelope[index]; - var y = channelBaseY + normalized * SpectrumAmplitudeHeight; - points.Add(new AquariumSplineVertex( - new Vector3(x, y, z), - SpectrumColor(normalized, alpha))); - } - - return points; - } - - private static float[] SmoothedSpectrumEnvelope(MimirAudioSpectrumSnapshot spectrum) - { - var bands = spectrum.BandDecibels; - var floor = Math.Min(spectrum.NoiseFloorDb, bands.Min()); - var ceiling = Math.Max(-24.0, Percentile(bands, 0.96)); - var span = Math.Max(18.0, ceiling - floor); - var normalized = new float[bands.Count]; - for (var index = 0; index < bands.Count; index++) - { - normalized[index] = (float)Math.Clamp((bands[index] - floor) / span, 0.0, 1.35); - } - - return GaussianSmooth5(normalized); - } - - private static float[] GaussianSmooth5(IReadOnlyList values) - { - var smoothed = new float[values.Count]; - for (var index = 0; index < values.Count; index++) - { - var value = - values[Math.Max(0, index - 2)] * 0.0625f + - values[Math.Max(0, index - 1)] * 0.25f + - values[index] * 0.375f + - values[Math.Min(values.Count - 1, index + 1)] * 0.25f + - values[Math.Min(values.Count - 1, index + 2)] * 0.0625f; - smoothed[index] = Math.Clamp(value, 0.0f, 1.25f); - } - - return smoothed; - } - - private static double Percentile(IReadOnlyList values, double percentile) - { - if (values.Count == 0) - { - return 0.0; - } - - var sorted = values.OrderBy(value => value).ToArray(); - var index = Math.Clamp((int)Math.Round((sorted.Length - 1) * percentile), 0, sorted.Length - 1); - return sorted[index]; - } - - private static void AddSpectrumGridSplines( - List splines, - int channelCount, - int windowCount) - { - var nearZ = 0.0f; - var farZ = Math.Max(1, windowCount - 1) * SpectrumWindowDepthSeparation; - for (var channelIndex = 0; channelIndex < channelCount; channelIndex++) - { - var y = channelIndex * SpectrumChannelSeparation; - var color = new Vector4(0.24f, 0.90f, 1.05f, 0.28f); - splines.Add(new AquariumSpline3D( - $"spectrum-grid-x-{channelIndex}", - [ - new AquariumSplineVertex(new Vector3(-SpectrumWidth * 0.5f, y, nearZ), color), - new AquariumSplineVertex(new Vector3(SpectrumWidth * 0.5f, y, nearZ), color), - new AquariumSplineVertex(new Vector3(SpectrumWidth * 0.5f, y, farZ), color), - new AquariumSplineVertex(new Vector3(-SpectrumWidth * 0.5f, y, farZ), color), - new AquariumSplineVertex(new Vector3(-SpectrumWidth * 0.5f, y, nearZ), color), - ], - new AquariumSplineStyle(0.006f, 0.8f, 0.7f, 0.72f, 0.28f), - CatmullRomSubdivisions: 1)); - - for (var line = 0; line <= 4; line++) - { - var x = -SpectrumWidth * 0.5f + SpectrumWidth * line / 4.0f; - splines.Add(new AquariumSpline3D( - $"spectrum-grid-z-{channelIndex}-{line}", - [ - new AquariumSplineVertex(new Vector3(x, y, nearZ), color), - new AquariumSplineVertex(new Vector3(x, y, farZ), color), - ], - new AquariumSplineStyle(0.005f, 0.8f, 0.7f, 0.72f, 0.28f), - CatmullRomSubdivisions: 1)); - } - } - } - - private static Vector4 SpectrumColor(float normalized, float alpha) - { - var hot = Math.Clamp(normalized, 0.0f, 1.0f); - var hardDrive = SmoothStep(0.58f, 0.96f, hot); - var bloom = 1.0f + SpectrumSplineHotBloom * hardDrive * hardDrive; - var red = (0.72f + 2.35f * hot) * bloom; - var green = (0.10f + 1.90f * hot * hot) * bloom; - var blue = (0.40f * (1.0f - hot) + 0.16f * hot + 0.22f * hardDrive) * (1.0f + 2.8f * hardDrive); - var drivenAlpha = alpha * Math.Clamp(0.34f + 0.88f * hot + 0.35f * hardDrive, 0.24f, 1.0f); - return new Vector4(red, green, blue, drivenAlpha); - } - - private static float SmoothStep(float edge0, float edge1, float value) - { - var t = Math.Clamp((value - edge0) / Math.Max(0.0001f, edge1 - edge0), 0.0f, 1.0f); - return t * t * (3.0f - 2.0f * t); - } - private static string SpectrumBars(MimirAudioSpectrumSnapshot spectrum) { const string ramp = " .:-=+*#%@"; diff --git a/state/evidence.jsonl b/state/evidence.jsonl index 7d3efae..f954704 100644 --- a/state/evidence.jsonl +++ b/state/evidence.jsonl @@ -156,3 +156,4 @@ {"ts": "2026-05-26T22:55:00+01:00", "type": "implementation", "status": "ok", "note": "Fensalir TubeField expansion now follows selected backend packet authority. D3D12Renderer expands only TubeSplineLowerings whose claim has a planned TubeField packet; unplanned lowerings are counted in debug status instead of rendered. This prevents explicit lowering metadata from bypassing validation/planning."} {"ts": "2026-05-26T22:58:00+01:00", "type": "implementation", "status": "ok", "note": "Fensalir validation now rejects TubeSpline lowering metadata if the referenced claim is not Tube-encoded or if the lowering resource differs from the claim payload resource. TubeField split authority is caught at validation, before renderer packet gating. Fensalir build/tests/headless reload passed."} {"ts": "2026-05-26T23:03:00+01:00", "type": "implementation", "status": "ok", "note": "Mimir's typed Fensalir surface-intent lowering now emits AquariumFieldTubeSplineLowering metadata for audio spectrum/waveform Tube claims. The hardware-free field evidence smoke now proves one resource, three claims, one TubeSpline lowering, one planned TubeField packet, and two deferred non-backend claims; the DSL smoke still proves resource-bound TubeSpline lowering with ramp binding."} +{"ts": "2026-05-26T23:18:00+01:00", "type": "correction", "status": "ok", "note": "Silenced the legacy direct spectrum spline path. MimirRuntime now submits AquariumSplineFrame.Empty and the old BuildSpectrumSplineFrame/direct spline styling helpers were deleted, so live spectrum visualization authority is FieldEvidence TubeField only. Mimir build and both Fensalir field smokes passed."} diff --git a/state/map.yaml b/state/map.yaml index 5123fd0..e5cddaf 100644 --- a/state/map.yaml +++ b/state/map.yaml @@ -350,7 +350,9 @@ accepted_design: not match the claim payload. Mimir's typed surface-intent lowering now emits TubeSpline lowering metadata for audio spectrum/waveform Tube claims, so the generated mesh path is fed by real Mimir evidence rather than only the DSL - smoke. + smoke. The legacy direct `AquariumSplineFrame` spectrum dashboard is silent: + `MimirRuntime` submits an empty spline frame and the old direct spectrum + spline builder has been deleted. - `config/perfect-machine.example.json` declares live owners, sensors, reservoir views, and runtime policy. - `src/Mimir.Runtime/Synchronization` now includes the Perfect Machine module