From 07a7864944cd3e7eeead13dd4f4fda0eb6a28c73 Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Wed, 27 May 2026 12:01:34 +0100 Subject: [PATCH] Upload spectrum ring slots by offset --- docs/implementation-plan.md | 8 +++++--- notes/current-system-map.md | 8 +++++--- src/Mimir.BufferSmoke/Program.cs | 25 ++++++++++++++++++------- src/Mimir.Runtime/MimirRuntime.cs | 24 ++++++++++++++---------- state/evidence.jsonl | 1 + state/map.yaml | 9 ++++++--- 6 files changed, 49 insertions(+), 26 deletions(-) diff --git a/docs/implementation-plan.md b/docs/implementation-plan.md index c6b58fd..6e621db 100644 --- a/docs/implementation-plan.md +++ b/docs/implementation-plan.md @@ -156,9 +156,11 @@ a named invariant that the native runtime cannot protect yet. truncated and reported in the runtime UI. Lowerings use that fixed capacity as `ColumnStride`. Physical history slots are addressed as a ring with `RollingOffset`, so logical age no longer requires reshuffling every column - before shader sampling. `MIMIR_SPECTRUM_TUBE_SUBDIVISIONS` controls the - requested Catmull-Rom subdivision count so Fensalir's overflow report has a - direct cost lever. + before shader sampling. Mimir emits one Float32 resource upload per sampled + history slot with an explicit element offset, rather than uploading the full + fixed-capacity backing buffer every frame. `MIMIR_SPECTRUM_TUBE_SUBDIVISIONS` + controls the requested Catmull-Rom subdivision count so Fensalir's overflow + report has a direct cost lever. - `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 0cec0a5..e170369 100644 --- a/notes/current-system-map.md +++ b/notes/current-system-map.md @@ -393,9 +393,11 @@ resize the GPU buffer while the rolling trail fills. `MIMIR_SPECTRUM_SOURCE_LANE sets the fixed lane budget; surplus sources are truncated and reported in the runtime UI. Physical history slots are now addressed as a ring with `RollingOffset`, so logical age no longer requires moving every column before -shader sampling. `MIMIR_SPECTRUM_TUBE_SUBDIVISIONS` is the runtime cost lever -for lowering Catmull-Rom subdivision count when Fensalir reports TubeField -truncation. +shader sampling. Mimir emits one Float32 resource upload per sampled history +slot with an explicit element offset instead of uploading the full +fixed-capacity backing buffer every frame. `MIMIR_SPECTRUM_TUBE_SUBDIVISIONS` +is the runtime cost lever for lowering Catmull-Rom subdivision count when +Fensalir reports TubeField truncation. ## Known Risks diff --git a/src/Mimir.BufferSmoke/Program.cs b/src/Mimir.BufferSmoke/Program.cs index b80f0c4..cba426b 100644 --- a/src/Mimir.BufferSmoke/Program.cs +++ b/src/Mimir.BufferSmoke/Program.cs @@ -655,8 +655,9 @@ static int RunMimirSpectrumUploadSmoke() var frame = runtime.Frame; var field = frame.Scene.FieldEvidenceFrame; - var upload = field.ResourceUploads.SingleOrDefault(upload => - string.Equals(upload.ResourceKey, "mimir:resource:spectrum:field-upload", StringComparison.Ordinal)); + var matchingUploads = field.ResourceUploads + .Where(upload => string.Equals(upload.ResourceKey, "mimir:resource:spectrum:field-upload", StringComparison.Ordinal)) + .ToArray(); var matchingResources = field.Resources .Where(resource => string.Equals(resource.ResourceKey, "mimir:resource:spectrum:field-upload", StringComparison.Ordinal)) .ToArray(); @@ -686,19 +687,30 @@ static int RunMimirSpectrumUploadSmoke() .ToArray(); Console.WriteLine( - $"mimir-spectrum-upload-smoke resources={field.Resources.Count} uploads={field.ResourceUploads.Count} claims={field.Claims.Count} tubeLowerings={field.TubeSplineLowerings.Count} planned={plan.Packets.Count} errors={validation.HasErrors} legacySplines={frame.Scene.SplineFrame.Splines.Count} bufferFields={frame.Scene.BufferFieldFrame.SplineTubeFields.Count + frame.Scene.BufferFieldFrame.TextureSplineFields.Count} uploadFloats={upload?.Float32Data.Count ?? 0}"); + $"mimir-spectrum-upload-smoke resources={field.Resources.Count} uploads={field.ResourceUploads.Count} claims={field.Claims.Count} tubeLowerings={field.TubeSplineLowerings.Count} planned={plan.Packets.Count} errors={validation.HasErrors} legacySplines={frame.Scene.SplineFrame.Splines.Count} bufferFields={frame.Scene.BufferFieldFrame.SplineTubeFields.Count + frame.Scene.BufferFieldFrame.TextureSplineFields.Count} uploadFloats={matchingUploads.Sum(upload => upload.Float32Data.Count)}"); return !validation.HasErrors && !frame.Scene.SplineFrame.HasInput && !frame.Scene.BufferFieldFrame.HasInput && - upload is not null && + matchingUploads.Length == expectedHistoryCount && matchingResources.Length == 1 && matchingRamps.Length == 1 && matchingLowerings.Length == 4 && matchingClaims.Length == matchingLowerings.Length && - upload.Version == resource.Version && - upload.Float32Data.Count == resource.DepthOrCount && + matchingUploads.All(upload => upload.Version == resource.Version) && + matchingUploads.Sum(upload => upload.Float32Data.Count) == lowering.Width * expectedSourceLaneCapacity * expectedHistoryCount && + matchingUploads.All(upload => + upload.Float32Data.Count == lowering.Width * expectedSourceLaneCapacity && + upload.ElementOffset >= 0 && + upload.ElementOffset + upload.Float32Data.Count <= resource.DepthOrCount) && + matchingUploads.Select(upload => upload.ElementOffset).Order().SequenceEqual( + new[] + { + 0, + lowering.Width * expectedSourceLaneCapacity * (expectedHistoryCapacity - 2), + lowering.Width * expectedSourceLaneCapacity * (expectedHistoryCapacity - 1), + }.Order()) && resource.Kind == AquariumFieldResourceKind.StructuredBuffer && resource.Residency == AquariumFieldResourceResidency.GpuResident && resource.Access == AquariumFieldShaderAccess.ShaderResource && @@ -710,7 +722,6 @@ static int RunMimirSpectrumUploadSmoke() string.Equals(ramp.Format, "Rgba8Unorm", StringComparison.OrdinalIgnoreCase) && string.Equals(ramp.SourceUri, @"D:\WIP4\Projects\Aetheria\Assets\Resources\Ramps\blackbody.png", StringComparison.OrdinalIgnoreCase) && resource.Height == expectedSourceLaneCapacity * expectedHistoryCapacity && - lowering.Width * resource.Height == upload.Float32Data.Count && matchingLowerings.All(item => item.ColumnCount == expectedHistoryCount && item.ColumnStride == expectedSourceLaneCapacity && diff --git a/src/Mimir.Runtime/MimirRuntime.cs b/src/Mimir.Runtime/MimirRuntime.cs index fa09633..77a416a 100644 --- a/src/Mimir.Runtime/MimirRuntime.cs +++ b/src/Mimir.Runtime/MimirRuntime.cs @@ -280,10 +280,12 @@ public sealed class MimirRuntime : IAquariumRuntime var resourceColumnCapacity = checked(spectrumSourceLaneCapacity * SpectrumHistoryWindowCount); var newestHistorySlot = PositiveModulo(-historyFrames[0].Sequence, SpectrumHistoryWindowCount); var rollingOffset = checked(newestHistorySlot * spectrumSourceLaneCapacity); - var samples = new float[width * resourceColumnCapacity]; + var resourceUploads = new List(historyFrames.Length); for (var historyIndex = 0; historyIndex < historyFrames.Length; historyIndex++) { var historySlot = PositiveModulo(-historyFrames[historyIndex].Sequence, SpectrumHistoryWindowCount); + var uploadColumnOffset = checked(historySlot * spectrumSourceLaneCapacity); + var samples = new float[width * spectrumSourceLaneCapacity]; foreach (var spectrum in historyFrames[historyIndex].Spectra) { if (!sourceIndexById.TryGetValue(spectrum.SourceId, out var sourceIndex)) @@ -291,9 +293,16 @@ public sealed class MimirRuntime : IAquariumRuntime continue; } - var physicalColumn = historySlot * spectrumSourceLaneCapacity + sourceIndex; - WriteNormalizedSpectrum(spectrum, samples.AsSpan(physicalColumn * width, width)); + WriteNormalizedSpectrum(spectrum, samples.AsSpan(sourceIndex * width, width)); } + + resourceUploads.Add(new AquariumFieldResourceUpload + { + ResourceKey = SpectrumFieldResourceKey, + Version = unchecked((ulong)Math.Max(0, spectrumHistorySequence)), + ElementOffset = checked(uploadColumnOffset * width), + Float32Data = samples, + }); } const string domainKey = "mimir:domain:spectrum:field-upload"; @@ -323,7 +332,7 @@ public sealed class MimirRuntime : IAquariumRuntime Format: "Float32", Width: width, Height: resourceColumnCapacity, - DepthOrCount: samples.Length, + DepthOrCount: checked(width * resourceColumnCapacity), StrideBytes: sizeof(float), ValidFromNs: 0, ValidUntilNs: 0, @@ -422,12 +431,7 @@ public sealed class MimirRuntime : IAquariumRuntime ResourceUploads = [ .. frame.ResourceUploads, - new AquariumFieldResourceUpload - { - ResourceKey = SpectrumFieldResourceKey, - Version = version, - Float32Data = samples, - }, + .. resourceUploads, ], TubeSplineLowerings = [.. frame.TubeSplineLowerings, .. lowerings], AccumulationWindowSeconds = frame.AccumulationWindowSeconds, diff --git a/state/evidence.jsonl b/state/evidence.jsonl index 14147d2..27c6192 100644 --- a/state/evidence.jsonl +++ b/state/evidence.jsonl @@ -167,3 +167,4 @@ {"ts": "2026-05-27T11:41:00+01:00", "type": "implementation", "status": "ok", "note": "Mimir now exposes MIMIR_SPECTRUM_TUBE_SUBDIVISIONS as the first runtime cost lever for TubeField overflow. The spectrum upload smoke sets it to 2 and verifies every emitted source-lane TubeSpline lowering uses that Catmull-Rom subdivision count, so Fensalir's requested/dispatched/truncated segment report can be answered without changing code."} {"ts": "2026-05-27T11:47:48+01:00", "type": "implementation", "status": "ok", "note": "Mimir spectrum upload now has fixed source-lane capacity as well as fixed history capacity. MIMIR_SPECTRUM_SOURCE_LANES sets the lane budget, TubeSpline lowerings use that budget as ColumnStride, surplus sources are truncated and reported in the runtime UI, and --mimir-spectrum-upload-smoke verifies 48 bands x 8 lanes x 40 history capacity = 15360 uploaded floats while emitting only four active TubeField claims/lowerings."} {"ts": "2026-05-27T11:54:44+01:00", "type": "implementation", "status": "ok", "note": "Mimir spectrum history now uses true ring addressing. Physical history slots are keyed by sequence modulo the fixed history capacity, TubeSpline lowerings carry RollingOffset for newest logical age, and --mimir-spectrum-upload-smoke verifies the expected offset with the fixed 8-lane resource. This makes partial column uploads possible without changing shader sampling semantics."} +{"ts": "2026-05-27T12:00:41+01:00", "type": "implementation", "status": "ok", "note": "Mimir now emits partial spectrum resource uploads for the active ring slots it samples. The declared GPU buffer remains fixed at 48 bands x 8 lanes x 40 history slots, but the smoke now verifies three offset uploads totaling 1152 floats for the three active ages instead of a full 15360-float backing-store upload. Headless Fensalir rendered the offset-copy path with empty stderr."} diff --git a/state/map.yaml b/state/map.yaml index 4ee42b2..7f35e5f 100644 --- a/state/map.yaml +++ b/state/map.yaml @@ -372,9 +372,12 @@ accepted_design: shared Float32 resource so each active lane renders a true age trail at `z = age * 0.1`. Physical history slots are addressed as a ring with `RollingOffset`, so logical age no longer requires moving every column in - the upload matrix before shader sampling. `MIMIR_SPECTRUM_TUBE_SUBDIVISIONS` - controls the Mimir-requested Catmull-Rom subdivisions so Fensalir's - TubeField truncation report has a direct cost lever. + the upload matrix before shader sampling. Mimir emits one Float32 resource + upload per sampled history slot with an explicit element offset instead of + uploading the full fixed-capacity backing buffer every frame. + `MIMIR_SPECTRUM_TUBE_SUBDIVISIONS` controls the Mimir-requested Catmull-Rom + subdivisions so Fensalir's TubeField truncation report has a direct cost + lever. - `config/perfect-machine.example.json` declares live owners, sensors, reservoir views, and runtime policy. - `src/Mimir.Runtime/Synchronization` now includes the Perfect Machine module