From 5f9e5b31da7c7d81feca80ccc16240986fe93dbd Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Tue, 26 May 2026 11:14:02 +0100 Subject: [PATCH] Rebuild spectrum splines around temporal identity --- docs/implementation-plan.md | 13 +++--- notes/current-system-map.md | 15 ++++--- src/Mimir.Runtime/MimirRuntime.cs | 70 ++++++++++++++++--------------- state/evidence.jsonl | 1 + 4 files changed, 55 insertions(+), 44 deletions(-) diff --git a/docs/implementation-plan.md b/docs/implementation-plan.md index 987de9d..37d748b 100644 --- a/docs/implementation-plan.md +++ b/docs/implementation-plan.md @@ -111,12 +111,15 @@ a named invariant that the native runtime cannot protect yet. it evaluates capsule/tube distance, applies native blue-noise jitter, and writes scene color plus temporal reservoir guide MRTs. The old `AquariumBufferFieldFrame` / `ReservoirSplats` dashboard path is no longer - allowed to decide this surface. Current budget knobs are - `MIMIR_SPECTRUM_SPLINE_WINDOW_STRIDE`, + 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 intentionally undersample the - current frame so the temporal path amortizes the surface instead of drawing - every equivalent segment every frame. + `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` queues bioacoustic timeline PCM through Fensalir audio when the active timing witness is allowed. `MimirAudioSynchronizationSettings.Mode` selects `chirp-only`, `passive`, or `hybrid`; passive disables active diff --git a/notes/current-system-map.md b/notes/current-system-map.md index aec6b0b..0591016 100644 --- a/notes/current-system-map.md +++ b/notes/current-system-map.md @@ -319,12 +319,15 @@ 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, applies native blue-noise sample jitter, writes color/metadata/control, -and emits `reservoirGuide` for the scene temporal path. Mimir intentionally -submits a sparse rotating surface budget with -`MIMIR_SPECTRUM_SPLINE_WINDOW_STRIDE`, `MIMIR_SPECTRUM_SPLINE_BAND_STRIDE`, and -`MIMIR_SPECTRUM_SPLINE_SUBDIVISIONS` so the temporal reservoir/TAA path -amortizes the surface instead of paying the full polygon-equivalent cost every -frame. +and emits `reservoirGuide` for the scene temporal path. 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. ## Known Risks diff --git a/src/Mimir.Runtime/MimirRuntime.cs b/src/Mimir.Runtime/MimirRuntime.cs index d35cfe4..81458b8 100644 --- a/src/Mimir.Runtime/MimirRuntime.cs +++ b/src/Mimir.Runtime/MimirRuntime.cs @@ -33,7 +33,7 @@ public sealed class MimirRuntime : IAquariumRuntime private const float SpectrumSplineTubePadding = 0.18f; private const float SpectrumSplineEmission = 1.85f; private const float SpectrumSplineHotBloom = 8.0f; - private const int DefaultSpectrumSplineWindowStride = 4; + private const int DefaultSpectrumSplineWindowStride = 2; private const int DefaultSpectrumSplineBandStride = 2; private const int DefaultSpectrumSplineSubdivisions = 1; private readonly MimirSynchronizationHub synchronization; @@ -62,14 +62,14 @@ public sealed class MimirRuntime : IAquariumRuntime private double lastPassiveSynchronizationConfidence; private float spectrumCameraDistanceMultiplier = SpectrumCameraDefaultDistanceMultiplier; private float spectrumCameraAngleDegrees = SpectrumCameraDefaultAngleDegrees; - private ulong spectrumSurfaceFrameIndex; private AquariumCameraFrustum lastSpectrumFrustum = AquariumCameraFrustum.Default; private Vector3 lastSpectrumCameraPosition; private Vector3 lastSpectrumCameraTarget; private (Vector3 Min, Vector3 Max) lastSpectrumAabb = (Vector3.Zero, Vector3.One); private IReadOnlyList lastAudioSynchronizationReports = []; private IReadOnlyList lastAudioSpectra = []; - private readonly Queue> spectrumHistory = new(); + private long spectrumHistorySequence; + private readonly Queue spectrumHistory = new(); public MimirRuntime(AquariumRuntimeOptions options) : this(options, MimirRuntimeConfiguration.Load()) @@ -140,7 +140,7 @@ public sealed class MimirRuntime : IAquariumRuntime private AquariumFrame CreateFrame() { var channelCount = Math.Max(1, lastAudioSpectra.Count); - var splineFrame = BuildSpectrumSplineFrame(spectrumSurfaceFrameIndex++); + var splineFrame = BuildSpectrumSplineFrame(); var windowCount = Math.Max(1, spectrumHistory.Count); var (splineMin, splineMax) = SpectrumSplineAabb(channelCount, windowCount); var cameraTarget = (splineMin + splineMax) * 0.5f; @@ -582,9 +582,12 @@ public sealed class MimirRuntime : IAquariumRuntime if (lastAudioSpectra.Count > 0) { - spectrumHistory.Enqueue(lastAudioSpectra - .OrderBy(spectrum => spectrum.SourceId, StringComparer.Ordinal) - .ToArray()); + spectrumHistory.Enqueue(new MimirSpectrumHistoryFrame( + spectrumHistorySequence++, + runtimeSeconds, + lastAudioSpectra + .OrderBy(spectrum => spectrum.SourceId, StringComparer.Ordinal) + .ToArray())); while (spectrumHistory.Count > SpectrumHistoryWindowCount) { spectrumHistory.Dequeue(); @@ -689,13 +692,6 @@ public sealed class MimirRuntime : IAquariumRuntime : 96; } - 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 ParseSpectrumSplineBandStride() { return int.TryParse(Environment.GetEnvironmentVariable("MIMIR_SPECTRUM_SPLINE_BAND_STRIDE"), out var value) @@ -703,6 +699,13 @@ public sealed class MimirRuntime : IAquariumRuntime : 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) @@ -861,7 +864,7 @@ public sealed class MimirRuntime : IAquariumRuntime return (min, max); } - private AquariumSplineFrame BuildSpectrumSplineFrame(ulong frameIndex) + private AquariumSplineFrame BuildSpectrumSplineFrame() { if (!sceneReady || spectrumHistory.Count == 0) { @@ -869,9 +872,10 @@ public sealed class MimirRuntime : IAquariumRuntime } var windows = spectrumHistory.ToArray(); - var latest = windows[^1]; - var sourceIds = latest + var sourceIds = windows + .SelectMany(window => window.Spectra) .Select(spectrum => spectrum.SourceId) + .Distinct(StringComparer.Ordinal) .OrderBy(sourceId => sourceId, StringComparer.Ordinal) .ToArray(); if (sourceIds.Length == 0) @@ -882,23 +886,23 @@ public sealed class MimirRuntime : IAquariumRuntime var windowStride = ParseSpectrumSplineWindowStride(); var bandStride = ParseSpectrumSplineBandStride(); var subdivisions = ParseSpectrumSplineSubdivisions(); - var windowPhase = (int)(frameIndex % (ulong)windowStride); 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); - for (var windowIndex = 0; windowIndex < windows.Length; windowIndex++) + foreach (var window in windows) { - var ageFromNewest = windows.Length - 1 - windowIndex; - if (windowStride > 1 && ageFromNewest != 0 && ageFromNewest % windowStride != windowPhase) + var isLatest = window.Sequence == windows[^1].Sequence; + if (!isLatest && windowStride > 1 && window.Sequence % windowStride != 0) { continue; } - var ageT = windows.Length <= 1 ? 0.0f : ageFromNewest / (float)(windows.Length - 1); + 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 = ageFromNewest * SpectrumWindowDepthSeparation; - var spectraBySource = windows[windowIndex].ToDictionary(spectrum => spectrum.SourceId, StringComparer.Ordinal); + 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) @@ -906,18 +910,14 @@ public sealed class MimirRuntime : IAquariumRuntime continue; } - var bandPhase = bandStride <= 1 - ? 0 - : (int)((frameIndex + (ulong)channelIndex + (ulong)ageFromNewest) % (ulong)bandStride); var points = BuildSpectrumWindowSpline( spectrum, channelIndex * SpectrumChannelSeparation, z, alpha, - bandStride, - bandPhase); + bandStride); splines.Add(new AquariumSpline3D( - $"spectrum-{spectrum.SourceId}-{windowIndex}", + $"spectrum-{spectrum.SourceId}-{window.Sequence}", points, new AquariumSplineStyle(0.020f, SpectrumSplineEmission, 1.0f, 0.72f, 0.20f), CatmullRomSubdivisions: subdivisions)); @@ -932,8 +932,7 @@ public sealed class MimirRuntime : IAquariumRuntime float channelBaseY, float z, float alpha, - int bandStride, - int bandPhase) + int bandStride) { var bands = spectrum.BandDecibels; var floor = Math.Min(spectrum.NoiseFloorDb, bands.Min()); @@ -943,7 +942,7 @@ public sealed class MimirRuntime : IAquariumRuntime for (var index = 0; index < bands.Count; index++) { var isEdge = index == 0 || index == bands.Count - 1; - if (!isEdge && bandStride > 1 && index % bandStride != bandPhase) + if (!isEdge && bandStride > 1 && index % bandStride != 0) { continue; } @@ -1097,4 +1096,9 @@ public sealed class MimirRuntime : IAquariumRuntime ? "no sync state" : string.Join(" | ", states.Select(state => $"{state.SourceId}: {state.SmoothedDelaySamples:0.000} samples {state.DelayMicroseconds:0.0}us sro={state.SamplingRateOffsetPpm:0.0}ppm c={state.Confidence:0.00}")); } + + private readonly record struct MimirSpectrumHistoryFrame( + long Sequence, + float RuntimeSeconds, + IReadOnlyList Spectra); } diff --git a/state/evidence.jsonl b/state/evidence.jsonl index 6f9ca53..2e3afe8 100644 --- a/state/evidence.jsonl +++ b/state/evidence.jsonl @@ -129,3 +129,4 @@ {"status":"ok","note":"Corrected the previous overcut: the spectrum dashboard is back on a single AquariumBufferFieldFrame reservoir authority with direct spline triangles disabled. Defaults are 32768 reservoir samples, 8192 updates/frame, one candidate/update, and WorldCenterRadius=(0,0,0,1) to prevent sample radii from inflating into giant discs. Fensalir transparent-field splats no longer enforce the 0.035*fieldRadius minimum billboard radius. Runtime proof logs 32768 visible splats at about 3-6 ms CPU frame time and about 0.2 ms command recording. Compute sampling is still hash-jittered rather than true blue-noise texture driven.","type":"correction","ts":"2026-05-26T10:31:00+01:00"} {"ts": "2026-05-26T10:48:57+01:00", "type": "correction", "status": "ok", "note": "Spectrum dashboard authority is direct spline-tube SDF again: Mimir submits AquariumSplineFrame trails and leaves AquariumBufferFieldFrame empty, so fractal ReservoirSplats cannot own the view. The live run artifacts/runtime/mimir-spectrum-sdf-temporal-sparse.out.log showed four 192 kHz ASIO lanes, no D3D12 fractal reservoir lines, and about 4-7 ms D3D12 CPU frames with record near 3 ms using sparse rotating spline budgets."} {"ts": "2026-05-26T11:01:55+01:00", "type": "implementation", "status": "ok", "note": "Spectrum spline heat now drives HDR bloom instead of relying on flat color. Mimir maps normalized band energy through a hard-drive curve: quiet bins stay dim/transparent, hot bins raise alpha and HDR red/yellow/white energy before Fensalir bloom/ACES tonemapping. Live ASIO proof artifacts/runtime/mimir-spectrum-hot-bloom-screen.png shows loopback lanes glowing while quiet lanes stay subdued; frame timing remained about 4.5-6.9 ms with no ReservoirSplats/fractal dashboard path."} +{"ts": "2026-05-26T11:13:09+01:00", "type": "correction", "status": "ok", "note": "Rebuilt spectrum spline history around temporal identity instead of render-frame sampling. The failed path rotated row and band selection by frameIndex, so visible line identity changed every frame and older windows teleported by integer queue age. Mimir now stores timestamped spectrum history frames with sequence ids, computes Z from continuous age, keeps source ordering across the full history, and only decimates by stable history-sequence/frequency-bin identity. Full no-decimation proof was coherent but exposed Fensalir CPU spline expansion at ~25 ms record; stable LOD defaults restore ~6-8 ms without frame-phase flicker. Proof: artifacts/runtime/mimir-spectrum-coherent-lod-screen.png and .out.log."}