Fix spectrum source lane budget
This commit is contained in:
parent
59d6c9c491
commit
b285acf481
6 changed files with 57 additions and 25 deletions
|
|
@ -149,12 +149,14 @@ a named invariant that the native runtime cannot protect yet.
|
|||
ramp PNG as a GPU-resident Texture2D resource and binds it by resource key.
|
||||
The uploaded spectrum resource is a rolling column matrix now: physical
|
||||
columns are flattened `(history age, source lane)` pairs, and Mimir emits one
|
||||
Tube claim/lowering per source with `ColumnStride=sourceCount` so each lane
|
||||
traverses its own age trail at `z = age * 0.1`. The resource uses a fixed
|
||||
history-column capacity so frame-to-frame content versions do not resize the
|
||||
GPU buffer while the history fills. `MIMIR_SPECTRUM_TUBE_SUBDIVISIONS`
|
||||
controls the Mimir-requested Catmull-Rom subdivision count so Fensalir's
|
||||
overflow report has a direct cost lever.
|
||||
Tube claim/lowering per active source. The resource uses fixed history and
|
||||
source-lane capacities, so frame-to-frame content versions and source
|
||||
topology changes do not resize the GPU buffer while the trail fills.
|
||||
`MIMIR_SPECTRUM_SOURCE_LANES` sets the lane capacity; surplus sources are
|
||||
truncated and reported in the runtime UI. Lowerings use that fixed capacity
|
||||
as `ColumnStride`, and `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
|
||||
|
|
|
|||
|
|
@ -387,9 +387,11 @@ blackbody ramp as a Texture2D field resource and binds the TubeField material by
|
|||
resource key. Runtime spectrum columns now represent `(history age, source
|
||||
lane)` pairs rather than only the latest spectra; Mimir emits one TubeField
|
||||
claim/lowering per source, each striding through the shared resource to render
|
||||
its own age trail at `z = age * 0.1`. The resource advertises a fixed history
|
||||
capacity, so content updates do not resize the GPU buffer while the rolling
|
||||
trail fills. `MIMIR_SPECTRUM_TUBE_SUBDIVISIONS` is the runtime cost lever for
|
||||
its own age trail at `z = age * 0.1`. The resource advertises fixed history and
|
||||
source-lane capacities, so content updates and source topology changes do not
|
||||
resize the GPU buffer while the rolling trail fills. `MIMIR_SPECTRUM_SOURCE_LANES`
|
||||
sets the fixed lane budget; surplus sources are truncated and reported in the
|
||||
runtime UI. `MIMIR_SPECTRUM_TUBE_SUBDIVISIONS` is the runtime cost lever for
|
||||
lowering Catmull-Rom subdivision count when Fensalir reports TubeField
|
||||
truncation.
|
||||
|
||||
|
|
|
|||
|
|
@ -631,8 +631,10 @@ static int RunMimirSpectrumUploadSmoke()
|
|||
{
|
||||
var previousSyntheticPreview = Environment.GetEnvironmentVariable("MIMIR_SYNTHETIC_SPECTRUM_PREVIEW");
|
||||
var previousTubeSubdivisions = Environment.GetEnvironmentVariable("MIMIR_SPECTRUM_TUBE_SUBDIVISIONS");
|
||||
var previousSourceLanes = Environment.GetEnvironmentVariable("MIMIR_SPECTRUM_SOURCE_LANES");
|
||||
Environment.SetEnvironmentVariable("MIMIR_SYNTHETIC_SPECTRUM_PREVIEW", "1");
|
||||
Environment.SetEnvironmentVariable("MIMIR_SPECTRUM_TUBE_SUBDIVISIONS", "2");
|
||||
Environment.SetEnvironmentVariable("MIMIR_SPECTRUM_SOURCE_LANES", "8");
|
||||
try
|
||||
{
|
||||
var settings = new MimirSynchronizationSettings
|
||||
|
|
@ -672,7 +674,7 @@ static int RunMimirSpectrumUploadSmoke()
|
|||
var lowering = matchingLowerings.FirstOrDefault();
|
||||
var validation = AquariumFieldEvidenceValidator.Validate(field);
|
||||
var plan = AquariumFieldLoweringPlanner.Plan(field);
|
||||
const int expectedSourceCount = 4;
|
||||
const int expectedSourceLaneCapacity = 8;
|
||||
const int expectedHistoryCount = 3;
|
||||
const int expectedHistoryCapacity = 40;
|
||||
const int expectedSubdivisions = 2;
|
||||
|
|
@ -706,11 +708,11 @@ static int RunMimirSpectrumUploadSmoke()
|
|||
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) &&
|
||||
resource.Height == expectedSourceCount * expectedHistoryCapacity &&
|
||||
resource.Height == expectedSourceLaneCapacity * expectedHistoryCapacity &&
|
||||
lowering.Width * resource.Height == upload.Float32Data.Count &&
|
||||
matchingLowerings.All(item =>
|
||||
item.ColumnCount == expectedHistoryCount &&
|
||||
item.ColumnStride == expectedSourceCount &&
|
||||
item.ColumnStride == expectedSourceLaneCapacity &&
|
||||
item.RollingModulo == resource.Height &&
|
||||
item.CatmullRomSubdivisions == expectedSubdivisions &&
|
||||
Math.Abs(item.ColumnStep.Z - 0.1f) < 0.0001f &&
|
||||
|
|
@ -726,6 +728,7 @@ static int RunMimirSpectrumUploadSmoke()
|
|||
{
|
||||
Environment.SetEnvironmentVariable("MIMIR_SYNTHETIC_SPECTRUM_PREVIEW", previousSyntheticPreview);
|
||||
Environment.SetEnvironmentVariable("MIMIR_SPECTRUM_TUBE_SUBDIVISIONS", previousTubeSubdivisions);
|
||||
Environment.SetEnvironmentVariable("MIMIR_SPECTRUM_SOURCE_LANES", previousSourceLanes);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ 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 int DefaultSpectrumSourceLaneCapacity = 8;
|
||||
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";
|
||||
|
|
@ -45,6 +46,7 @@ public sealed class MimirRuntime : IAquariumRuntime
|
|||
private readonly float audioSyncUpdateIntervalSeconds;
|
||||
private readonly float spectrumUpdateIntervalSeconds;
|
||||
private readonly int spectrumTubeSubdivisions;
|
||||
private readonly int spectrumSourceLaneCapacity;
|
||||
private readonly float calibrationGain;
|
||||
private readonly float watermarkGain;
|
||||
private readonly bool syntheticSpectrumPreview;
|
||||
|
|
@ -60,6 +62,7 @@ public sealed class MimirRuntime : IAquariumRuntime
|
|||
private double lastAudioSyncAnalysisMilliseconds;
|
||||
private double lastSpectrumAnalysisMilliseconds;
|
||||
private double lastPassiveSynchronizationConfidence;
|
||||
private int lastDroppedSpectrumSourceLaneCount;
|
||||
private float spectrumCameraDistanceMultiplier = SpectrumCameraDefaultDistanceMultiplier;
|
||||
private float spectrumCameraAngleDegrees = SpectrumCameraDefaultAngleDegrees;
|
||||
private AquariumCameraFrustum lastSpectrumFrustum = AquariumCameraFrustum.Default;
|
||||
|
|
@ -115,6 +118,7 @@ public sealed class MimirRuntime : IAquariumRuntime
|
|||
audioSyncUpdateIntervalSeconds = ParseAudioSyncIntervalSeconds();
|
||||
spectrumUpdateIntervalSeconds = ParseSpectrumUpdateIntervalSeconds();
|
||||
spectrumTubeSubdivisions = ParseSpectrumTubeSubdivisions();
|
||||
spectrumSourceLaneCapacity = ParseSpectrumSourceLaneCapacity();
|
||||
calibrationGain = settings.Audio.CalibrationGain;
|
||||
watermarkGain = settings.Audio.WatermarkGain;
|
||||
syntheticSpectrumPreview = IsTruthy(Environment.GetEnvironmentVariable("MIMIR_SYNTHETIC_SPECTRUM_PREVIEW"));
|
||||
|
|
@ -244,18 +248,23 @@ public sealed class MimirRuntime : IAquariumRuntime
|
|||
var historyFrames = spectrumHistory.Count > 0
|
||||
? spectrumHistory.Reverse().ToArray()
|
||||
: [new MimirSpectrumHistoryFrame(spectrumHistorySequence, runtimeSeconds, spectra)];
|
||||
var sourceIds = historyFrames
|
||||
var allSourceIds = historyFrames
|
||||
.SelectMany(static history => history.Spectra)
|
||||
.Where(static spectrum => spectrum.BandDecibels.Count >= 2)
|
||||
.Select(static spectrum => spectrum.SourceId)
|
||||
.Distinct(StringComparer.Ordinal)
|
||||
.OrderBy(static sourceId => sourceId, StringComparer.Ordinal)
|
||||
.ToArray();
|
||||
var sourceIds = allSourceIds
|
||||
.Take(spectrumSourceLaneCapacity)
|
||||
.ToArray();
|
||||
if (sourceIds.Length == 0)
|
||||
{
|
||||
lastDroppedSpectrumSourceLaneCount = 0;
|
||||
return frame;
|
||||
}
|
||||
|
||||
lastDroppedSpectrumSourceLaneCount = allSourceIds.Length - sourceIds.Length;
|
||||
var sourceIndexById = sourceIds
|
||||
.Select((sourceId, index) => (sourceId, index))
|
||||
.ToDictionary(static item => item.sourceId, static item => item.index, StringComparer.Ordinal);
|
||||
|
|
@ -268,7 +277,7 @@ public sealed class MimirRuntime : IAquariumRuntime
|
|||
var historyCount = historyFrames.Length;
|
||||
var sourceCount = sourceIds.Length;
|
||||
var activeColumnCount = checked(sourceCount * historyCount);
|
||||
var resourceColumnCapacity = checked(sourceCount * SpectrumHistoryWindowCount);
|
||||
var resourceColumnCapacity = checked(spectrumSourceLaneCapacity * SpectrumHistoryWindowCount);
|
||||
var samples = new float[width * resourceColumnCapacity];
|
||||
for (var historyIndex = 0; historyIndex < historyFrames.Length; historyIndex++)
|
||||
{
|
||||
|
|
@ -279,7 +288,7 @@ public sealed class MimirRuntime : IAquariumRuntime
|
|||
continue;
|
||||
}
|
||||
|
||||
var physicalColumn = historyIndex * sourceCount + sourceIndex;
|
||||
var physicalColumn = historyIndex * spectrumSourceLaneCapacity + sourceIndex;
|
||||
WriteNormalizedSpectrum(spectrum, samples.AsSpan(physicalColumn * width, width));
|
||||
}
|
||||
}
|
||||
|
|
@ -367,7 +376,7 @@ public sealed class MimirRuntime : IAquariumRuntime
|
|||
StrideBytes: sizeof(float),
|
||||
FirstColumn: sourceIndex,
|
||||
ColumnCount: historyCount,
|
||||
ColumnStride: sourceCount,
|
||||
ColumnStride: spectrumSourceLaneCapacity,
|
||||
RollingModulo: resourceColumnCapacity,
|
||||
RollingOffset: 0,
|
||||
Origin: new Vector3(-5.0f, sourceIndex * SpectrumChannelSeparation, 0.0f),
|
||||
|
|
@ -672,7 +681,13 @@ public sealed class MimirRuntime : IAquariumRuntime
|
|||
panel.Readout("Frustum", DescribeSpectrumFrustum);
|
||||
panel.Readout(
|
||||
"Tube budget",
|
||||
() => $"{spectrumHistory.Count} age columns x {lastAudioSpectra.Count} lanes x {spectrumTubeSubdivisions} subdivisions",
|
||||
() =>
|
||||
{
|
||||
var dropped = lastDroppedSpectrumSourceLaneCount > 0
|
||||
? $" dropped={lastDroppedSpectrumSourceLaneCount}"
|
||||
: "";
|
||||
return $"{spectrumHistory.Count}/{SpectrumHistoryWindowCount} age columns x {Math.Min(lastAudioSpectra.Count, spectrumSourceLaneCapacity)}/{spectrumSourceLaneCapacity} lanes x {spectrumTubeSubdivisions} subdivisions{dropped}";
|
||||
},
|
||||
"Current Mimir-side TubeField subdivision cost before Fensalir applies its fixed geometry budget.");
|
||||
panel.TextBox(
|
||||
"Spectra",
|
||||
|
|
@ -975,6 +990,13 @@ public sealed class MimirRuntime : IAquariumRuntime
|
|||
: 4;
|
||||
}
|
||||
|
||||
private static int ParseSpectrumSourceLaneCapacity()
|
||||
{
|
||||
return int.TryParse(Environment.GetEnvironmentVariable("MIMIR_SPECTRUM_SOURCE_LANES"), out var value)
|
||||
? Math.Clamp(value, 1, 32)
|
||||
: DefaultSpectrumSourceLaneCapacity;
|
||||
}
|
||||
|
||||
private static bool IsTruthy(string? value) =>
|
||||
string.Equals(value, "1", StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals(value, "true", StringComparison.OrdinalIgnoreCase) ||
|
||||
|
|
|
|||
|
|
@ -165,3 +165,4 @@
|
|||
{"ts": "2026-05-27T11:33:22+01:00", "type": "implementation", "status": "ok", "note": "Mimir spectrum upload is now a rolling column matrix instead of a latest-frame row dump. The shared Float32 resource flattens physical columns as history-age/source-lane pairs; Mimir emits one TubeField claim/lowering per source with ColumnStride=sourceCount and ColumnStep.z=0.1 so each source gets its own age trail through the shared GPU buffer."}
|
||||
{"ts": "2026-05-27T11:38:16+01:00", "type": "implementation", "status": "ok", "note": "Mimir spectrum resource declarations now use fixed history capacity instead of growing with the currently filled trail. The runtime smoke proves 48 bands x 4 source lanes x 40 history capacity = 7680 uploaded floats while only the active history count is emitted through each source lowering. This avoids frame-to-frame GPU buffer resizing while preserving the rolling age trail."}
|
||||
{"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."}
|
||||
|
|
|
|||
|
|
@ -363,14 +363,16 @@ accepted_design:
|
|||
GPU-resident Texture2D field resource and binds the TubeField material by
|
||||
`RampResourceKey`.
|
||||
Runtime spectrum columns now represent flattened `(history age, source
|
||||
lane)` pairs. Mimir emits one TubeField claim/lowering per source, each
|
||||
using `ColumnStride=sourceCount` through the shared Float32 resource so the
|
||||
generated geometry renders a true age trail at `z = age * 0.1`. The
|
||||
spectrum resource advertises a fixed history capacity so frame content
|
||||
versions do not resize the GPU buffer while the rolling trail fills.
|
||||
`MIMIR_SPECTRUM_TUBE_SUBDIVISIONS` controls the Mimir-requested Catmull-Rom
|
||||
subdivisions so Fensalir's TubeField truncation report has a direct cost
|
||||
lever.
|
||||
lane)` pairs. Mimir emits one TubeField claim/lowering per active source.
|
||||
The spectrum resource advertises fixed history and source-lane capacities
|
||||
so frame content versions and source topology changes do not resize the GPU
|
||||
buffer while the rolling trail fills. `MIMIR_SPECTRUM_SOURCE_LANES` sets
|
||||
the lane capacity; surplus sources are truncated and reported in the
|
||||
runtime UI. Lowerings use that capacity as `ColumnStride` through the
|
||||
shared Float32 resource so each active lane renders a true age trail at
|
||||
`z = age * 0.1`. `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
|
||||
|
|
|
|||
Loading…
Reference in a new issue