Rebuild spectrum splines around temporal identity

This commit is contained in:
Meta Vens 2026-05-26 11:14:02 +01:00
commit 5f9e5b31da
4 changed files with 54 additions and 43 deletions

View file

@ -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

View file

@ -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

View file

@ -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<MimirAudioSynchronizationReport> lastAudioSynchronizationReports = [];
private IReadOnlyList<MimirAudioSpectrumSnapshot> lastAudioSpectra = [];
private readonly Queue<IReadOnlyList<MimirAudioSpectrumSnapshot>> spectrumHistory = new();
private long spectrumHistorySequence;
private readonly Queue<MimirSpectrumHistoryFrame> 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<AquariumSpline3D>(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<MimirAudioSpectrumSnapshot> Spectra);
}

View file

@ -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."}