Gate Move stereo calibration on observable evidence

This commit is contained in:
Meta Vens 2026-07-13 02:39:41 +01:00
commit d27f0d2d37
7 changed files with 237 additions and 8 deletions

View file

@ -215,6 +215,13 @@ both eyes. The same parent PID survived the run; transport health reported
5,302 produced frames, 5,301 handoffs, 771 sends, and zero local-ring
admissions. Full stereo calibration is still not implied: intrinsics and
spatially diverse correspondences must earn the camera matrices.
`MimirMoveStereoCalibrationAssessment` is the typed observability gate before
any fit. It accepts only same aggregate-frame, same-Move correspondences and
reports synchronized pair count, Move diversity, per-Eye 4x3 grid coverage,
radius range, and missing physical inputs. The corrected live capture produced
130 same-frame pairs with zero frame skew, but only 4/12 Eye 0 cells and 2/12
Eye 1 cells; intrinsics and measured orb radius are also absent, so promotion is
correctly false.
`MimirMoveProofSurface` is the first Fensalir-visible proof surface for this
chain. It consumes the Mimir admission receipt and the Mimir-owned pose stream
frame, emits `mimir.move_proof_surface.v1`, and lowers an observer-only

View file

@ -154,6 +154,17 @@ if (args.Any(arg => string.Equals(arg, "--muninn-move-visibility-window-smoke",
ParseStringOption(args, "--output", "artifacts/move-calibration/muninn-visibility-window.mpack"));
}
if (args.Any(arg => string.Equals(arg, "--move-stereo-calibration-assessment", StringComparison.OrdinalIgnoreCase)))
{
return RunMoveStereoCalibrationAssessment(
ParseStringOption(args, "--input", "artifacts/move-calibration/muninn-clocked-aggregator.mpack"),
ParseStringOption(args, "--output", "artifacts/move-calibration/stereo-assessment.mpack"),
ParseIntOption(args, "--image-width", 640),
ParseIntOption(args, "--image-height", 480),
args.Any(arg => string.Equals(arg, "--intrinsics-available", StringComparison.OrdinalIgnoreCase)),
ParseDoubleOption(args, "--orb-radius-meters", 0.0));
}
if (args.Any(arg => string.Equals(arg, "--move-fusion-smoke", StringComparison.OrdinalIgnoreCase)))
{
return RunMoveFusionSmoke();
@ -2071,16 +2082,20 @@ static int RunMuninnMoveVisibilityWindowSmoke(string odinCultMeshUri, int durati
}
var correspondences = observations
.GroupBy(observation => observation.MoveId, StringComparer.Ordinal)
.SelectMany(group =>
.GroupBy(observation => (observation.FrameId, observation.MoveId))
.Select(group =>
{
var first = group.Where(value => value.CameraId == cameraIds[0]).ToArray();
var second = group.Where(value => value.CameraId == cameraIds[1]).ToArray();
return first.Select(left => second
.Select(right => new MoveCrossCameraCorrespondence(left.MoveId, left, right, Math.Abs(left.PublishedAtNs - right.PublishedAtNs)))
.OrderBy(pair => pair.AbsoluteSkewNs).FirstOrDefault())
.Where(pair => pair is not null)!;
var first = group.FirstOrDefault(value => value.CameraId == cameraIds[0]);
var second = group.FirstOrDefault(value => value.CameraId == cameraIds[1]);
return first is null || second is null
? null
: new MoveCrossCameraCorrespondence(
group.Key.MoveId,
first,
second,
Math.Abs(first.PublishedAtNs - second.PublishedAtNs));
})
.Where(pair => pair is not null)
.Cast<MoveCrossCameraCorrespondence>()
.ToArray();
var receipt = new MoveVisibilityWindowReceipt(
@ -2115,6 +2130,65 @@ static int RunMuninnMoveVisibilityWindowSmoke(string odinCultMeshUri, int durati
}
}
static int RunMoveStereoCalibrationAssessment(
string inputPath,
string outputPath,
int imageWidth,
int imageHeight,
bool intrinsicsAvailable,
double orbRadiusMeters)
{
if (!File.Exists(inputPath))
{
Console.Error.WriteLine($"move-stereo-calibration-assessment missing-input path={inputPath}");
return 1;
}
var receipt = MessagePackSerializer.Deserialize<MoveVisibilityWindowReceipt>(File.ReadAllBytes(inputPath));
var cameraIds = receipt.Observations
.Select(value => value.CameraId)
.Distinct(StringComparer.Ordinal)
.Order(StringComparer.Ordinal)
.ToArray();
var assessment = MimirMoveStereoCalibrationAssessment.Assess(
receipt,
imageWidth,
imageHeight,
cameraIds,
intrinsicsAvailable,
orbRadiusMeters);
Directory.CreateDirectory(Path.GetDirectoryName(Path.GetFullPath(outputPath))!);
File.WriteAllBytes(outputPath, MessagePackSerializer.Serialize(assessment));
Console.WriteLine(JsonSerializer.Serialize(new
{
assessment.Schema,
inputPath,
outputPath,
assessment.CorrespondenceCount,
assessment.SynchronizedCorrespondenceCount,
assessment.SameFrameCorrespondenceCount,
assessment.DistinctMoveCount,
assessment.MedianAbsoluteSkewMilliseconds,
assessment.MaximumAbsoluteSkewMilliseconds,
assessment.IntrinsicsAvailable,
assessment.OrbRadiusMetersAvailable,
assessment.ReadyForRelativePoseFit,
assessment.Promoted,
assessment.MissingRequirements,
cameras = assessment.Cameras.Select(camera => new
{
camera.CameraId,
camera.ObservationCount,
camera.OccupiedGridCells,
camera.GridCellCount,
spanX = camera.MaximumXPx - camera.MinimumXPx,
spanY = camera.MaximumYPx - camera.MinimumYPx,
radiusRatio = camera.MinimumRadiusPx > 0.0 ? camera.MaximumRadiusPx / camera.MinimumRadiusPx : double.NaN
})
}));
return assessment.Promoted ? 0 : 2;
}
static int RunMuninnMoveCultMeshStreamSmoke(string nativeReservoirPath)
{
if (!File.Exists(nativeReservoirPath))

View file

@ -0,0 +1,139 @@
using GameCult.Caching;
using MessagePack;
namespace Mimir.Runtime.Synchronization;
[MessagePackObject]
public sealed record MimirMoveCameraCoverageAssessment(
[property: Key(0)] string CameraId,
[property: Key(1)] int ObservationCount,
[property: Key(2)] double MinimumXPx,
[property: Key(3)] double MaximumXPx,
[property: Key(4)] double MinimumYPx,
[property: Key(5)] double MaximumYPx,
[property: Key(6)] int OccupiedGridCells,
[property: Key(7)] int GridCellCount,
[property: Key(8)] double MinimumRadiusPx,
[property: Key(9)] double MaximumRadiusPx);
[CultDocument("mimir.move_stereo_calibration_assessment", "mimir.move_stereo_calibration_assessment.v1")]
[MessagePackObject]
public sealed record MimirMoveStereoCalibrationAssessmentDocument(
[property: Key(0)] string Schema,
[property: Key(1)] [property: CultName] string AssessmentId,
[property: Key(2)] string SourceSchema,
[property: Key(3)] long AssessedAtNs,
[property: Key(4)] int CorrespondenceCount,
[property: Key(5)] int SynchronizedCorrespondenceCount,
[property: Key(6)] int SameFrameCorrespondenceCount,
[property: Key(7)] int DistinctMoveCount,
[property: Key(8)] double MedianAbsoluteSkewMilliseconds,
[property: Key(9)] double MaximumAbsoluteSkewMilliseconds,
[property: Key(10)] MimirMoveCameraCoverageAssessment[] Cameras,
[property: Key(11)] bool IntrinsicsAvailable,
[property: Key(12)] bool OrbRadiusMetersAvailable,
[property: Key(13)] bool ReadyForRelativePoseFit,
[property: Key(14)] bool Promoted,
[property: Key(15)] string[] MissingRequirements);
public static class MimirMoveStereoCalibrationAssessment
{
public const string Schema = "mimir.move_stereo_calibration_assessment.v1";
public static MimirMoveStereoCalibrationAssessmentDocument Assess(
MoveVisibilityWindowReceipt receipt,
int imageWidth,
int imageHeight,
IReadOnlyCollection<string> cameraIds,
bool intrinsicsAvailable,
double orbRadiusMeters,
double maximumAssociationSkewMilliseconds = 20.0,
DateTimeOffset? assessedAt = null)
{
ArgumentNullException.ThrowIfNull(receipt);
ArgumentNullException.ThrowIfNull(cameraIds);
if (imageWidth <= 0 || imageHeight <= 0) throw new ArgumentOutOfRangeException(nameof(imageWidth));
if (cameraIds.Count != 2) throw new ArgumentException("Stereo calibration requires exactly two camera IDs.", nameof(cameraIds));
var cameras = cameraIds.Distinct(StringComparer.Ordinal).ToArray();
if (cameras.Length != 2) throw new ArgumentException("Stereo camera IDs must be distinct.", nameof(cameraIds));
var maximumSkewNs = maximumAssociationSkewMilliseconds * 1_000_000.0;
var correspondences = receipt.Correspondences
.Where(value => cameras.Contains(value.First.CameraId, StringComparer.Ordinal) &&
cameras.Contains(value.Second.CameraId, StringComparer.Ordinal))
.ToArray();
var synchronized = correspondences.Where(value => value.AbsoluteSkewNs <= maximumSkewNs).ToArray();
var sameFrame = synchronized.Where(value => string.Equals(value.First.FrameId, value.Second.FrameId, StringComparison.Ordinal)).ToArray();
var coverage = cameras.Select(cameraId => BuildCoverage(
cameraId,
receipt.Observations.Where(value => string.Equals(value.CameraId, cameraId, StringComparison.Ordinal)).ToArray(),
imageWidth,
imageHeight)).ToArray();
var missing = new List<string>();
if (!intrinsicsAvailable) missing.Add("calibrated-intrinsics-for-both-eyes");
if (!(double.IsFinite(orbRadiusMeters) && orbRadiusMeters > 0.0)) missing.Add("measured-orb-radius-meters");
if (synchronized.Length < 300) missing.Add("at-least-300-synchronized-correspondences");
if (sameFrame.Length < 100) missing.Add("at-least-100-same-frame-correspondences");
if (synchronized.Select(value => value.MoveId).Distinct(StringComparer.Ordinal).Count() < 2) missing.Add("at-least-two-stable-move-identities");
foreach (var camera in coverage)
{
if (camera.OccupiedGridCells < 6) missing.Add($"{camera.CameraId}:coverage-at-least-6-of-12-grid-cells");
if (camera.MaximumRadiusPx <= camera.MinimumRadiusPx * 1.25) missing.Add($"{camera.CameraId}:radius-range-at-least-1.25x");
}
var skews = correspondences.Select(value => value.AbsoluteSkewNs / 1_000_000.0).Order().ToArray();
var ready = missing.Count == 0;
return new MimirMoveStereoCalibrationAssessmentDocument(
Schema,
$"move-stereo-assessment:{receipt.StartedAtNs}:{receipt.EndedAtNs}",
receipt.Schema,
(assessedAt ?? DateTimeOffset.UtcNow).ToUnixTimeMilliseconds() * 1_000_000,
correspondences.Length,
synchronized.Length,
sameFrame.Length,
synchronized.Select(value => value.MoveId).Distinct(StringComparer.Ordinal).Count(),
Median(skews),
skews.Length == 0 ? double.NaN : skews[^1],
coverage,
intrinsicsAvailable,
double.IsFinite(orbRadiusMeters) && orbRadiusMeters > 0.0,
ready,
Promoted: false,
missing.Distinct(StringComparer.Ordinal).ToArray());
}
private static MimirMoveCameraCoverageAssessment BuildCoverage(
string cameraId,
MoveVisibilityObservation[] observations,
int imageWidth,
int imageHeight)
{
var valid = observations.Where(value =>
float.IsFinite(value.CenterXPx) && float.IsFinite(value.CenterYPx) &&
float.IsFinite(value.RadiusPx) && value.RadiusPx > 0.0f).ToArray();
var cells = valid.Select(value =>
{
var column = Math.Clamp((int)(value.CenterXPx * 4.0 / imageWidth), 0, 3);
var row = Math.Clamp((int)(value.CenterYPx * 3.0 / imageHeight), 0, 2);
return row * 4 + column;
}).Distinct().Count();
return new MimirMoveCameraCoverageAssessment(
cameraId,
valid.Length,
valid.Length == 0 ? double.NaN : valid.Min(value => value.CenterXPx),
valid.Length == 0 ? double.NaN : valid.Max(value => value.CenterXPx),
valid.Length == 0 ? double.NaN : valid.Min(value => value.CenterYPx),
valid.Length == 0 ? double.NaN : valid.Max(value => value.CenterYPx),
cells,
12,
valid.Length == 0 ? double.NaN : valid.Min(value => value.RadiusPx),
valid.Length == 0 ? double.NaN : valid.Max(value => value.RadiusPx));
}
private static double Median(double[] sorted) => sorted.Length switch
{
0 => double.NaN,
_ when sorted.Length % 2 == 1 => sorted[sorted.Length / 2],
_ => (sorted[sorted.Length / 2 - 1] + sorted[sorted.Length / 2]) * 0.5
};
}

View file

@ -184,3 +184,4 @@
{"ts": "2026-07-13T01:29:15+01:00", "type": "verification", "status": "partial", "note": "Per-camera optical evidence and exposure ownership restored Eye 0. At exposure 0.3 Eye 0 peak RGB rose from [72,25,43] to [178,119,112], calibrated 4 Moves, and produced 3,312 observations/latest 4; Eye 1 remains at exposure 0.1. Mimir's persistent window then saw all 4 IDs on Eye 0, 2 on Eye 1, and 2 same-ID cross-camera correspondences. Do not claim full stereo: Eye 1 overlap is incomplete and only 2 aggregate RUDP frames arrived in 20s and 60s windows."}
{"ts": "2026-07-13T01:41:29+01:00", "type": "verification", "status": "partial", "note": "After per-eye exposure tuning and latest-state RUDP handoff, Mimir's 15s dual-eye receipt saw all four stable Move IDs on both cameras: 16 camera observations and 8 pair records across 2 aggregate frames. Full overlap is proven; calibration is not. Only 2 unique frame IDs arrived, so directly instrument parent publication and RUDP send/receive counters before camera-matrix fitting."}
{"ts": "2026-07-13T02:32:31+01:00", "type": "verification", "status": "ok", "note": "2026-07-13 Nightwing single-provider dual-Eye field proof: one stable Muninn parent received 622 unique evidence frames in 15 s through Mimir, with 2,647 observations, 1,384 same-ID cross-camera correspondences, and all four Move IDs visible on both Eyes. Producer transport health reported 5,302 produced, 5,301 handoffs, 771 sends, and zero local-ring admissions. Artifact: artifacts/move-calibration/muninn-clocked-aggregator.mpack. Odin owner commits: 39c839a, a340c33, 8aefd1c, f82ca7a."}
{"ts": "2026-07-13T02:39:32+01:00", "type": "verification", "status": "rejected", "note": "2026-07-13 typed Move stereo assessment rejected calibration promotion. Corrected same-frame collector captured 798 frames and 130 same-frame/same-Move pairs at zero published-frame skew. Eye 0 covered 4/12 image grid cells; Eye 1 covered 2/12 and saw only two Move IDs in this window. Both calibrated intrinsics and measured orb radius were absent. No camera matrices were emitted. Artifacts: artifacts/move-calibration/muninn-same-frame-correspondence.mpack and muninn-same-frame-stereo-assessment.mpack."}

View file

@ -267,6 +267,14 @@ accepted_design:
remote handoffs, 771 sends during the subscription, and zero local-ring
admissions. Cadence and full overlap are earned; calibrated camera matrices
still require intrinsics and spatially diverse correspondence capture.
- `MimirMoveStereoCalibrationAssessment` owns the pre-fit observability gate
as `mimir.move_stereo_calibration_assessment.v1`. It consumes a typed
visibility receipt, counts only same-frame/same-Move stereo pairs, measures
per-Eye image-grid and radius coverage, and refuses promotion when
intrinsics, measured orb radius, synchronized sample count, or spatial
diversity are missing. The corrected 2026-07-13 live receipt contained 130
honest same-frame pairs at zero frame skew, but Eye 0 occupied only 4/12
grid cells and Eye 1 only 2/12; no camera matrices were promoted.
- `MimirMuninnMoveEvidenceAdapter.TryAdmitLatestCultMeshFrame` now exposes a
typed admission receipt with decoded frame id, producer, publish/read
timestamps, sample counts by kind, source/arrival ranges, native reservoir