From c8d4496cacfa821b0dd66d823e7125bc10daa0ec Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Tue, 26 May 2026 23:51:13 +0100 Subject: [PATCH] Bind spectrum ramp as field resource --- docs/fensalir-rendering-rebuild-migration.md | 3 ++- docs/implementation-plan.md | 3 ++- notes/current-system-map.md | 4 +++- src/Mimir.BufferSmoke/Program.cs | 11 ++++++++++ src/Mimir.Runtime/MimirRuntime.cs | 22 +++++++++++++------- state/evidence.jsonl | 2 ++ state/map.yaml | 3 +++ 7 files changed, 37 insertions(+), 11 deletions(-) diff --git a/docs/fensalir-rendering-rebuild-migration.md b/docs/fensalir-rendering-rebuild-migration.md index 48aa07a..93b756f 100644 --- a/docs/fensalir-rendering-rebuild-migration.md +++ b/docs/fensalir-rendering-rebuild-migration.md @@ -233,7 +233,8 @@ Fensalir-owned structured buffer resource, so the TubeField path consumes live GPU buffer contents rather than an empty declaration. The runtime-level receipt is `Mimir.BufferSmoke --mimir-spectrum-upload-smoke`: it boots Mimir's frame path, advances spectrum analysis, and verifies one Float32 upload, one planned -TubeField packet, and no live legacy spline/buffer-field dashboard input. +TubeField packet, a resource-bound local blackbody ramp Texture2D, and no live +legacy spline/buffer-field dashboard input. ### 3. Calibration Constraint diff --git a/docs/implementation-plan.md b/docs/implementation-plan.md index dbcb7e0..330b34d 100644 --- a/docs/implementation-plan.md +++ b/docs/implementation-plan.md @@ -145,7 +145,8 @@ a named invariant that the native runtime cannot protect yet. --mimir-spectrum-upload-smoke` boots `MimirRuntime`, advances the synthetic spectrum preview path, and verifies that the frame contains the Float32 resource upload plus planned TubeField packet while legacy spline and buffer - field inputs stay empty. + field inputs stay empty. The same runtime path declares the local blackbody + ramp PNG as a GPU-resident Texture2D resource and binds it by resource key. - `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 93cc213..b66cbb9 100644 --- a/notes/current-system-map.md +++ b/notes/current-system-map.md @@ -382,7 +382,9 @@ a handle: Mimir uploads the current spectral amplitudes as row-major Float32 data into a Fensalir-owned GPU structured buffer before TubeField compute reads it. `Mimir.BufferSmoke --mimir-spectrum-upload-smoke` verifies this through the actual `MimirRuntime` frame path and confirms the legacy direct spline and -buffer-field dashboard inputs are empty. +buffer-field dashboard inputs are empty. The same path declares the local +blackbody ramp as a Texture2D field resource and binds the TubeField material by +resource key. ## Known Risks diff --git a/src/Mimir.BufferSmoke/Program.cs b/src/Mimir.BufferSmoke/Program.cs index b163bc8..08c8fb6 100644 --- a/src/Mimir.BufferSmoke/Program.cs +++ b/src/Mimir.BufferSmoke/Program.cs @@ -653,6 +653,9 @@ static int RunMimirSpectrumUploadSmoke() var matchingResources = field.Resources .Where(resource => string.Equals(resource.ResourceKey, "mimir:resource:spectrum:field-upload", StringComparison.Ordinal)) .ToArray(); + var matchingRamps = field.Resources + .Where(resource => string.Equals(resource.ResourceKey, "aquarium:resource:ramp:blackbody", StringComparison.Ordinal)) + .ToArray(); var matchingLowerings = field.TubeSplineLowerings .Where(lowering => string.Equals(lowering.ResourceKey, "mimir:resource:spectrum:field-upload", StringComparison.Ordinal)) .ToArray(); @@ -660,6 +663,7 @@ static int RunMimirSpectrumUploadSmoke() .Where(claim => string.Equals(claim.ClaimKey, "intent:mimir:spectrum:field-upload", StringComparison.Ordinal)) .ToArray(); var resource = matchingResources.FirstOrDefault(); + var ramp = matchingRamps.FirstOrDefault(); var lowering = matchingLowerings.FirstOrDefault(); var claim = matchingClaims.FirstOrDefault(); var validation = AquariumFieldEvidenceValidator.Validate(field); @@ -674,6 +678,7 @@ static int RunMimirSpectrumUploadSmoke() !frame.Scene.BufferFieldFrame.HasInput && upload is not null && matchingResources.Length == 1 && + matchingRamps.Length == 1 && matchingLowerings.Length == 1 && matchingClaims.Length == 1 && upload.Version == resource.Version && @@ -683,8 +688,14 @@ static int RunMimirSpectrumUploadSmoke() resource.Access == AquariumFieldShaderAccess.ShaderResource && string.Equals(resource.Format, "Float32", StringComparison.OrdinalIgnoreCase) && resource.StrideBytes == sizeof(float) && + ramp.Kind == AquariumFieldResourceKind.Texture2D && + ramp.Residency == AquariumFieldResourceResidency.GpuResident && + ramp.Access == AquariumFieldShaderAccess.ShaderResource && + string.Equals(ramp.Format, "Rgba8Unorm", StringComparison.OrdinalIgnoreCase) && + string.Equals(ramp.SourceUri, @"D:\WIP4\Projects\Aetheria\Assets\Resources\Ramps\blackbody.png", StringComparison.OrdinalIgnoreCase) && lowering.Width * lowering.Height == upload.Float32Data.Count && lowering.ClaimKey == claim.ClaimKey && + string.Equals(lowering.RampResourceKey, ramp.ResourceKey, StringComparison.Ordinal) && claim.PayloadHandle == resource.ResourceKey && plan.Packets.Any(packet => packet.Backend == AquariumFieldBackendKind.TubeField && diff --git a/src/Mimir.Runtime/MimirRuntime.cs b/src/Mimir.Runtime/MimirRuntime.cs index 473a94c..aca0f8f 100644 --- a/src/Mimir.Runtime/MimirRuntime.cs +++ b/src/Mimir.Runtime/MimirRuntime.cs @@ -31,6 +31,9 @@ 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 string SpectrumFieldResourceKey = "mimir:resource:spectrum:field-upload"; + private const string SpectrumRampResourceKey = "aquarium:resource:ramp:blackbody"; + private const string SpectrumRampTexturePath = @"D:\WIP4\Projects\Aetheria\Assets\Resources\Ramps\blackbody.png"; private readonly MimirSynchronizationHub synchronization; private readonly MimirFensalirFieldLowering fieldLowering; private readonly MimirAudioSpectrumAnalyzer spectrumAnalyzer; @@ -244,7 +247,6 @@ public sealed class MimirRuntime : IAquariumRuntime WriteNormalizedSpectrum(spectra[row], samples.AsSpan(row * width, width)); } - const string resourceKey = "mimir:resource:spectrum:field-upload"; const string domainKey = "mimir:domain:spectrum:field-upload"; const string claimKey = "intent:mimir:spectrum:field-upload"; var version = unchecked((ulong)Math.Max(0, spectrumHistorySequence)); @@ -264,7 +266,7 @@ public sealed class MimirRuntime : IAquariumRuntime RepresentedCandidateCount: height, Seed: 0x5EC7_0001u); var resource = new AquariumFieldResourceDeclaration( - ResourceKey: resourceKey, + ResourceKey: SpectrumFieldResourceKey, Kind: AquariumFieldResourceKind.StructuredBuffer, Residency: AquariumFieldResourceResidency.GpuResident, Access: AquariumFieldShaderAccess.ShaderResource, @@ -278,6 +280,10 @@ public sealed class MimirRuntime : IAquariumRuntime Version: version, NativeHandle: IntPtr.Zero, NativeHandleKind: "fensalir-owned-spectrum-upload"); + var ramp = AquariumFieldResourceDeclaration.LocalTexture2D( + SpectrumRampResourceKey, + SpectrumRampTexturePath, + version: 1); var claim = new AquariumFieldClaim( ClaimKey: claimKey, DomainKey: domainKey, @@ -286,7 +292,7 @@ public sealed class MimirRuntime : IAquariumRuntime Encoding: AquariumFieldEncoding.Tube, Support: support, Proposal: proposal, - PayloadHandle: resourceKey, + PayloadHandle: SpectrumFieldResourceKey, ObservedTimeNs: 0, Confidence: confidence); var axisStepX = width > 1 ? 10.0f / (width - 1) : 10.0f; @@ -320,13 +326,13 @@ public sealed class MimirRuntime : IAquariumRuntime AquariumFieldGuide.Valid(confidence, spectrumUpdateIntervalSeconds)), ], BackendPackets = frame.BackendPackets, - Resources = [.. frame.Resources, resource], + Resources = [.. frame.Resources, resource, ramp], ResourceUploads = [ .. frame.ResourceUploads, new AquariumFieldResourceUpload { - ResourceKey = resourceKey, + ResourceKey = SpectrumFieldResourceKey, Version = version, Float32Data = samples, }, @@ -337,7 +343,7 @@ public sealed class MimirRuntime : IAquariumRuntime new AquariumFieldTubeSplineLowering( LoweringKey: "tube-spline:mimir:spectrum:field-upload", ClaimKey: claimKey, - ResourceKey: resourceKey, + ResourceKey: SpectrumFieldResourceKey, Width: width, Height: height, StrideBytes: sizeof(float), @@ -357,8 +363,8 @@ public sealed class MimirRuntime : IAquariumRuntime RadiusScale: 0.030f, Alpha: 0.92f, Feather: 0.20f, - RampTexturePath: "", - RampResourceKey: "", + RampTexturePath: SpectrumRampTexturePath, + RampResourceKey: SpectrumRampResourceKey, EmissionScale: 10.0f, CatmullRomSubdivisions: 4).Normalized(), ], diff --git a/state/evidence.jsonl b/state/evidence.jsonl index f9d0798..57c3c2e 100644 --- a/state/evidence.jsonl +++ b/state/evidence.jsonl @@ -160,3 +160,5 @@ {"ts": "2026-05-26T23:36:00+01:00", "type": "implementation", "status": "ok", "note": "Mimir now feeds spectral frames into the GPU buffer path. Current audio spectra are normalized into a row-major Float32 matrix, declared as mimir:resource:spectrum:field-upload, attached as a field ResourceUpload, and consumed by the TubeField claim/lowering. Fensalir now uploads ResourceUpload Float32 data into resolved structured buffers before TubeField compute runs. Mimir build, Fensalir build/tests/headless reload, and both field smokes passed."} {"ts": "2026-05-26T23:44:26+01:00", "type": "verification", "status": "ok", "note": "Added Mimir.BufferSmoke --mimir-spectrum-upload-smoke to exercise the actual MimirRuntime frame path. The smoke verifies one Float32 spectrum ResourceUpload, one declared GPU structured buffer, one Tube claim/lowering, one planned TubeField packet, and no legacy AquariumSplineFrame or AquariumBufferFieldFrame dashboard input."} {"ts": "2026-05-26T23:48:24+01:00", "type": "verification", "status": "ok", "note": "Headless Fensalir with Mimir.Runtime and MIMIR_SYNTHETIC_SPECTRUM_PREVIEW=1 captured artifacts/runtime/mimir-spectrum-upload-headless.png. The screenshot shows visible TubeField spectrum arcs from the resource-upload path; stderr was empty. Use Mimir.Runtime.csproj, not Mimir.App.csproj, as the dev-reload client assembly because the runtime factory lives there."} +{"ts": "2026-05-26T23:49:49+01:00", "type": "implementation", "status": "ok", "note": "Mimir's runtime spectrum FieldEvidence path now declares the blackbody PNG as a GPU-resident Texture2D field resource and binds the TubeSpline lowering to that ramp by resource key, instead of falling back to unbound white/linear material."} +{"ts": "2026-05-26T23:51:02+01:00", "type": "verification", "status": "ok", "note": "Runtime blackbody ramp binding verified. Mimir build passed with one unrelated existing Mimir.EveRelay CA2024 warning; --mimir-spectrum-upload-smoke reports resources=2/uploads=1/planned=1/errors=False; both Fensalir field smokes pass; headless Fensalir captured artifacts/runtime/mimir-spectrum-upload-headless-ramp.png with visible ramp-colored TubeField arcs and empty stderr."} diff --git a/state/map.yaml b/state/map.yaml index c130454..f8cd6f8 100644 --- a/state/map.yaml +++ b/state/map.yaml @@ -359,6 +359,9 @@ accepted_design: `Mimir.BufferSmoke --mimir-spectrum-upload-smoke` verifies the live `MimirRuntime` frame path emits that Float32 upload and planned TubeField packet while direct spline and buffer-field dashboard inputs remain empty. + The runtime spectrum path also declares the local blackbody PNG as a + GPU-resident Texture2D field resource and binds the TubeField material by + `RampResourceKey`. - `config/perfect-machine.example.json` declares live owners, sensors, reservoir views, and runtime policy. - `src/Mimir.Runtime/Synchronization` now includes the Perfect Machine module