Bind spectrum ramp as field resource

This commit is contained in:
Meta Vens 2026-05-26 23:51:13 +01:00
commit c8d4496cac
7 changed files with 37 additions and 11 deletions

View file

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

View file

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

View file

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

View file

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

View file

@ -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(),
],

View file

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

View file

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