Publish field evidence from Mimir runtime

This commit is contained in:
Meta Vens 2026-05-26 14:51:06 +01:00
commit daa16136e2
2 changed files with 51 additions and 0 deletions

View file

@ -37,6 +37,7 @@ public sealed class MimirRuntime : IAquariumRuntime
private const int DefaultSpectrumSplineBandStride = 1;
private const int DefaultSpectrumSplineSubdivisions = 2;
private readonly MimirSynchronizationHub synchronization;
private readonly MimirFensalirFieldLowering fieldLowering;
private readonly MimirAudioSpectrumAnalyzer spectrumAnalyzer;
private readonly IReadOnlyList<MimirStreamSourceFactory> sourceFactories;
private readonly AquariumUiDocument ui;
@ -105,6 +106,9 @@ public sealed class MimirRuntime : IAquariumRuntime
{
Options = options;
synchronization = new MimirSynchronizationHub(settings);
fieldLowering = new MimirFensalirFieldLowering(new MimirFensalirLoweringOptions(
settings.BufferDuration.TotalSeconds,
settings.BufferDuration.TotalSeconds));
spectrumAnalyzer = new MimirAudioSpectrumAnalyzer(ParseSpectrumFftSize(), ParseSpectrumBandCount());
this.sourceFactories = sourceFactories.ToArray();
audioSyncSettings = settings.Audio;
@ -140,6 +144,7 @@ public sealed class MimirRuntime : IAquariumRuntime
private AquariumFrame CreateFrame()
{
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);
@ -165,11 +170,56 @@ public sealed class MimirRuntime : IAquariumRuntime
{
TraceHeightFieldSurface = false,
UseStarfieldBackground = false,
FieldEvidenceFrame = fieldEvidenceFrame,
BufferFieldFrame = AquariumBufferFieldFrame.Empty,
SplineFrame = splineFrame,
});
}
private AquariumFieldEvidenceFrame BuildFieldEvidenceFrame()
{
var buffers = synchronization.Buffers.Buffers;
var windows = new List<MimirRollingStreamWindow>(buffers.Count);
var observations = new List<MimirObservation>(buffers.Count);
MimirFensalirBridgeMapper.MapWindows(buffers, windows);
MimirFensalirBridgeMapper.MapLatestObservations(buffers, observations);
var constraints = new List<MimirCalibrationConstraint>(synchronization.AudioSynchronizationStates.Count);
MimirFensalirBridgeMapper.MapCalibrationConstraints(synchronization.AudioSynchronizationStates, constraints);
var observationsByWindow = observations.ToDictionary(observation => observation.WindowId, StringComparer.Ordinal);
var intents = new List<MimirSurfaceIntent>(observations.Count);
foreach (var window in windows)
{
if (!observationsByWindow.TryGetValue(window.WindowId, out var observation))
{
continue;
}
intents.Add(BuildSurfaceIntent(window, observation));
}
return fieldLowering.BuildFieldEvidenceFrame(windows, observations, constraints, intents);
}
private static MimirSurfaceIntent BuildSurfaceIntent(MimirRollingStreamWindow window, MimirObservation observation)
{
if (window.SourceKind == MimirStreamKind.Audio)
{
return new MimirSurfaceIntent(
IntentKey: $"{window.WindowId}:spectrum",
SourceObservationKeys: [observation.ObservationKey],
Domain: MimirSurfaceDomain.AudioSpectrum,
Axes: new MimirSurfaceAxes("frequency", "amplitude", "time-age", "stream"),
SupportPolicy: new MimirSurfaceSupportPolicy("rolling-spectrum", 0.0, window.Duration.TotalSeconds),
MaterialGraph: new MimirSurfaceMaterialIntent("spectrum-evidence", "source-evidence", observation.Confidence),
UpdateBudget: new MimirSurfaceUpdateBudget(0.0, 1, Math.Max(0, window.Payload.ByteLength)),
Purpose: MimirSurfaceIntentPurpose.Debug);
}
return MimirFensalirBridgeMapper.MapDefaultSurfaceIntent(window, observation);
}
private static Vector3 SpectrumCameraPosition(
int spectrumCount,
int windowCount,

View file

@ -137,3 +137,4 @@
{"ts": "2026-05-26T14:04:08+01:00", "type": "implementation", "status": "ok", "note": "Phase 1 Mimir bridge skeleton is live in src/Mimir.Runtime/Synchronization/MimirFensalirBridgeContracts.cs. It defines pure DTOs for rolling stream windows, observations, calibration constraints, and surface intent plus mappers from rolling buffers and audio sync state/report surfaces. No rendering, device reads, timers, JSON/base64 hot paths, or backend-specific visual choices were added. dotnet build Mimir.slnx passed with one pre-existing EveRelay CA2024 warning."}
{"ts": "2026-05-26T14:39:17+01:00", "type": "correction", "status": "ok", "note": "Corrected the Phase 1 field evidence boundary: Mimir no longer emits AquariumFieldBackendPacket rows with Backend=Unknown for surface intents. Surface intent now lowers only to field domains, claims, and candidates; backend packet selection remains Fensalir authority. dotnet build Mimir.slnx --no-restore passed with 0 warnings and 0 errors after the cut."}
{"ts": "2026-05-26T14:44:02+01:00", "type": "implementation", "status": "ok", "note": "Added BufferSmoke --fensalir-field-evidence-smoke as a hardware-free contract receipt. It builds a synthetic rolling audio window, observation, bioacoustic calibration constraint, and audio-spectrum surface intent, lowers them through MimirFensalirFieldLowering, validates with AquariumFieldEvidenceValidator, and verifies Fensalir receives three pending lowering requests and zero backend packets. Smoke passed: domains=4 claims=3 candidates=3 packets=0 requests=3 errors=False."}
{"ts": "2026-05-26T14:50:33+01:00", "type": "implementation", "status": "ok", "note": "MimirRuntime now publishes AquariumFieldEvidenceFrame on every AquariumSceneState from live rolling buffers, latest observations, audio sync calibration constraints, and per-source surface intent. Existing direct spectrum splines remain diagnostic; Fensalir receives backend-neutral domains/claims/candidates through FieldEvidenceFrame. dotnet build Mimir.slnx --no-restore passed with the pre-existing EveRelay CA2024 warning, and --fensalir-field-evidence-smoke still passes."}