From 293a953b2d7d0d3d10bbecef5b80d8a17e135673 Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Wed, 8 Jul 2026 22:56:52 +0100 Subject: [PATCH] Prove Move proof surface in presented frame --- docs/implementation-plan.md | 10 +- notes/current-system-map.md | 7 ++ .../Mimir.BufferSmoke.csproj | 1 + src/Mimir.BufferSmoke/Program.cs | 117 ++++++++++++++++++ src/Mimir.Runtime/MimirRuntime.cs | 5 + .../MimirMoveProofDevSurface.cs | 97 +++++++++++++++ state/evidence.jsonl | 1 + state/map.yaml | 5 + 8 files changed, 241 insertions(+), 2 deletions(-) create mode 100644 src/Mimir.Runtime/Synchronization/MimirMoveProofDevSurface.cs diff --git a/docs/implementation-plan.md b/docs/implementation-plan.md index 3a0f3ee..ab1ba2e 100644 --- a/docs/implementation-plan.md +++ b/docs/implementation-plan.md @@ -104,6 +104,12 @@ a named invariant that the native runtime cannot protect yet. duplicate frame admission. `Mimir.BufferSmoke --move-proof-runtime-driver-smoke` proves `MimirRuntime.Update` can pull one ring frame into the same-sequence proof chain and visible spline frame. +- `MimirMoveProofDevSurface` is a dev-only bootstrap gated by + `MIMIR_MOVE_PROOF_DEV_SURFACE`. It uses the same runtime proof attachment so + `Mimir.BufferSmoke --move-proof-presented-frame-smoke` can run `Mimir.App` + headless, capture the Fensalir-presented PNG, and pixel-check the named + proof probe. This verifies the renderer/probe layer; it is not live + Nightwing hardware evidence. - `MimirMoveCalibrationProtocol` publishes the typed calibration preflight for Starfire/Nightwing Moves: required Muninn evidence streams, optional Quest headset/controller pose witnesses, stillness/sweep/validation phases, and @@ -415,8 +421,8 @@ a named invariant that the native runtime cannot protect yet. 7. Wire real Move proof production into `MimirMoveProofRuntimeDriver`: attach real Muninn Nightwing and Starfire evidence rings, require calibrated optical witnesses, publish `mimir:starfire:move-pose:` through the shared - runtime path, and capture a presented Fensalir frame/probe for the same - sequence. + runtime path, and replace the dev-gated presented-frame smoke with a + same-sequence capture from real field evidence. 8. Bind Fensalir UI to the synchronization hub so buffer depth, stream cadence, source timestamps, and output settings are visible and adjustable. 9. Implement the Mimir program scene graph as the shared commit primitive for diff --git a/notes/current-system-map.md b/notes/current-system-map.md index bf21807..57ad56b 100644 --- a/notes/current-system-map.md +++ b/notes/current-system-map.md @@ -225,6 +225,13 @@ suffix, calls `MimirMoveProofPipeline`, suppresses duplicate frame admission, and lets `MimirRuntime.Update` publish the resulting proof surface. This proves the runtime-owned path for a ring frame; real Nightwing hardware and calibrated camera witnesses are still the missing field proof. +`MimirMoveProofDevSurface` is a dev-only bootstrap gated by +`MIMIR_MOVE_PROOF_DEV_SURFACE`. It publishes a synthetic calibrated proof +surface through the same `MimirRuntime.PublishMoveProofSurface` attachment so +`Mimir.BufferSmoke --move-proof-presented-frame-smoke` can run `Mimir.App` +headless, capture the actual Fensalir-presented PNG, and pixel-check that the +named proof probe is visible. This is a renderer/probe proof, not live hardware +evidence. ## Program Composition diff --git a/src/Mimir.BufferSmoke/Mimir.BufferSmoke.csproj b/src/Mimir.BufferSmoke/Mimir.BufferSmoke.csproj index 8cf0920..e4eb296 100644 --- a/src/Mimir.BufferSmoke/Mimir.BufferSmoke.csproj +++ b/src/Mimir.BufferSmoke/Mimir.BufferSmoke.csproj @@ -10,6 +10,7 @@ + diff --git a/src/Mimir.BufferSmoke/Program.cs b/src/Mimir.BufferSmoke/Program.cs index 0762eb7..18a5bc0 100644 --- a/src/Mimir.BufferSmoke/Program.cs +++ b/src/Mimir.BufferSmoke/Program.cs @@ -1,4 +1,5 @@ using System.Diagnostics; +using System.Drawing; using System.Numerics; using System.Security.Cryptography; using System.Text.Json; @@ -160,6 +161,12 @@ if (args.Any(arg => string.Equals(arg, "--move-proof-runtime-driver-smoke", Stri ParseStringOption(args, "--native-reservoir", DefaultNativeReservoirPath())); } +if (args.Any(arg => string.Equals(arg, "--move-proof-presented-frame-smoke", StringComparison.OrdinalIgnoreCase))) +{ + return RunMoveProofPresentedFrameSmoke( + ParseStringOption(args, "--output", "artifacts/runtime/move-proof-presented-frame-smoke.png")); +} + if (args.Any(arg => string.Equals(arg, "--move-calibration-protocol-smoke", StringComparison.OrdinalIgnoreCase))) { return await RunMoveCalibrationProtocolSmokeAsync( @@ -2411,6 +2418,116 @@ static int RunMoveProofRuntimeDriverSmoke(string nativeReservoirPath) : 1; } +static int RunMoveProofPresentedFrameSmoke(string outputPath) +{ + var absoluteOutputPath = Path.GetFullPath(outputPath); + Directory.CreateDirectory(Path.GetDirectoryName(absoluteOutputPath) ?? "."); + if (File.Exists(absoluteOutputPath)) + { + File.Delete(absoluteOutputPath); + } + + var startInfo = new ProcessStartInfo("dotnet") + { + UseShellExecute = false, + RedirectStandardOutput = true, + RedirectStandardError = true, + CreateNoWindow = true, + WorkingDirectory = Environment.CurrentDirectory, + }; + startInfo.ArgumentList.Add("run"); + startInfo.ArgumentList.Add("--project"); + startInfo.ArgumentList.Add(Path.GetFullPath("src/Mimir.App/Mimir.App.csproj")); + startInfo.ArgumentList.Add("--"); + startInfo.ArgumentList.Add("--headless"); + startInfo.ArgumentList.Add("--headless-width"); + startInfo.ArgumentList.Add("640"); + startInfo.ArgumentList.Add("--headless-height"); + startInfo.ArgumentList.Add("360"); + startInfo.ArgumentList.Add("--capture-frame"); + startInfo.ArgumentList.Add(absoluteOutputPath); + startInfo.Environment[MimirMoveProofDevSurface.EnableEnvironmentVariable] = "1"; + startInfo.Environment["AQUARIUM_HEADLESS_READY_FRAMES"] = "2"; + + using var process = Process.Start(startInfo); + if (process is null) + { + Console.Error.WriteLine("move-proof-presented-frame-smoke start-failed"); + return 1; + } + + var stdoutTask = process.StandardOutput.ReadToEndAsync(); + var stderrTask = process.StandardError.ReadToEndAsync(); + if (!process.WaitForExit((int)TimeSpan.FromSeconds(90).TotalMilliseconds)) + { + try + { + process.Kill(entireProcessTree: true); + } + catch + { + } + + Console.Error.WriteLine("move-proof-presented-frame-smoke timeout"); + return 1; + } + + var stdout = stdoutTask.GetAwaiter().GetResult(); + var stderr = stderrTask.GetAwaiter().GetResult(); + if (!string.IsNullOrWhiteSpace(stdout)) + { + Console.Write(stdout); + } + + if (!string.IsNullOrWhiteSpace(stderr)) + { + Console.Error.Write(stderr); + } + + if (process.ExitCode != 0 || !File.Exists(absoluteOutputPath)) + { + Console.Error.WriteLine($"move-proof-presented-frame-smoke failed exit={process.ExitCode} path={absoluteOutputPath}"); + return 1; + } + + var brightPixels = CountBrightPixels(absoluteOutputPath, threshold: 42); + var length = new FileInfo(absoluteOutputPath).Length; + var surface = MimirMoveProofDevSurface.Create(); + var captured = stdout.Contains("Headless Aquarium frame captured", StringComparison.Ordinal); + Console.WriteLine( + $"move-proof-presented-frame-smoke proof={surface.ProofId} frame={surface.FensalirFrameId} verdict={surface.Verdict} path={absoluteOutputPath} bytes={length} brightPixels={brightPixels} captured={captured}"); + + return surface.MuninnEvidenceFrameId == "muninn:nightwing:move-evidence:79" && + surface.MimirEvidenceFrameId == "mimir:starfire:move-evidence:79" && + surface.MimirPoseFrameId == "mimir:starfire:move-pose:79" && + surface.FensalirFrameId == "fensalir:starfire:presented-frame:79" && + surface.IsFullPose && + captured && + length > 2048 && + brightPixels > 12 + ? 0 + : 1; +} + +static int CountBrightPixels(string path, int threshold) +{ + using var bitmap = new Bitmap(path); + var count = 0; + for (var y = 0; y < bitmap.Height; y++) + { + for (var x = 0; x < bitmap.Width; x++) + { + var pixel = bitmap.GetPixel(x, y); + if (pixel.R + pixel.G + pixel.B >= threshold) + { + count++; + } + } + } + + return count; +} + static MimirMoveProofSurfaceDocument CreateMoveProofSurfaceSmokeDocument() { var pose = new MimirMoveControllerPoseDocument( diff --git a/src/Mimir.Runtime/MimirRuntime.cs b/src/Mimir.Runtime/MimirRuntime.cs index cdaa522..032e8aa 100644 --- a/src/Mimir.Runtime/MimirRuntime.cs +++ b/src/Mimir.Runtime/MimirRuntime.cs @@ -115,6 +115,11 @@ direct : null; nextAudioSyncSeconds = audioSyncUpdateIntervalSeconds; nextTelemetrySeconds = telemetryIntervalSeconds; + if (MimirMoveProofDevSurface.IsEnabled()) + { + PublishMoveProofSurface(MimirMoveProofDevSurface.Create()); + } + ui = CreateUi(); } diff --git a/src/Mimir.Runtime/Synchronization/MimirMoveProofDevSurface.cs b/src/Mimir.Runtime/Synchronization/MimirMoveProofDevSurface.cs new file mode 100644 index 0000000..55d8fa9 --- /dev/null +++ b/src/Mimir.Runtime/Synchronization/MimirMoveProofDevSurface.cs @@ -0,0 +1,97 @@ +namespace Mimir.Runtime.Synchronization; + +public static class MimirMoveProofDevSurface +{ + public const string EnableEnvironmentVariable = "MIMIR_MOVE_PROOF_DEV_SURFACE"; + + public static bool IsEnabled() => + IsTruthy(Environment.GetEnvironmentVariable(EnableEnvironmentVariable)); + + public static MimirMoveProofSurfaceDocument Create(int sequence = 79) + { + var sourceTimestampNs = 1_781_202_600_004_000_000L + sequence; + var pose = new MimirMoveControllerPoseDocument( + PoseId: $"move:0xA11CE:{sequence}", + WandId: "move:0xA11CE", + TrackingSpaceId: "mimir-stage-space", + CalibrationId: "mimir-move-stage-calibration-v1", + FusionAuthorityId: "mimir.runtime.move-fusion", + SourceTimestampNs: sourceTimestampNs, + EstimatedAtNs: sourceTimestampNs + 2_200_000L, + Sequence: (ulong)sequence, + PositionMeters: new MimirVector3Snapshot(0.0, 0.0, 4.0), + Orientation: new MimirQuaternionSnapshot(0.0, 0.0, 0.0, 1.0), + LinearVelocityMetersPerSecond: new MimirVector3Snapshot(0.0, 0.0, 0.0), + AngularVelocityRadiansPerSecond: new MimirVector3Snapshot(0.02, -0.01, 0.04), + Confidence: 0.82, + LatencyMilliseconds: 2.2, + Battery01: 0.72, + Buttons: + [ + new MimirTrackingButtonSnapshot("move", true, 1.0), + new MimirTrackingButtonSnapshot("trigger", true, 0.5) + ], + EvidenceStreamIds: + [ + "witness:0x00000000000A11CE", + "witness:0x0000000000000B0B", + "controller:0x00000000000A11CE" + ], + EvidenceKinds: + [ + "optical-marker:triangulated", + "controller-state:buttons-imu", + "orientation:imu-unresolved" + ], + ConsumerContract: "fensalir.move-controller-input"); + var admission = new MimirMuninnMoveEvidenceAdmission( + FrameId: $"muninn:nightwing:move-evidence:{sequence}", + ProducerPeerId: "muninn:nightwing", + PublishedAtNs: sourceTimestampNs + 1_000_000L, + MimirReadAtNs: checked((ulong)(sourceTimestampNs + 1_700_000L)), + SampleCount: 3, + OpticalMarkerCount: 2, + ControllerStateCount: 1, + SourceTimeMinNs: checked((ulong)sourceTimestampNs), + SourceTimeMaxNs: checked((ulong)sourceTimestampNs), + ArrivalMinNs: checked((ulong)(sourceTimestampNs + 500_000L)), + ArrivalMaxNs: checked((ulong)(sourceTimestampNs + 1_700_000L)), + Handle: new MimirNativeSampleHandle( + SensorIdHash: 0xF00D, + TimestampNs: checked((ulong)sourceTimestampNs), + ArrivalNs: checked((ulong)(sourceTimestampNs + 1_700_000L)), + Sequence: (ulong)sequence, + PayloadHandle: 0xBEEF, + Flags: 0, + Reserved: 0), + ReservoirEdgeNs: checked((ulong)sourceTimestampNs), + ReservoirWindowStartNs: checked((ulong)(sourceTimestampNs - 5_000_000_000L)), + ReservoirMoveEvidenceCount: 1); + var poseFrame = MimirMovePoseStream.CreateFrame( + frameId: $"mimir:starfire:move-pose:{sequence}", + producerPeerId: "mimir:starfire", + publishedAtNs: pose.EstimatedAtNs, + trackingSpaceId: pose.TrackingSpaceId, + calibrationId: pose.CalibrationId, + poses: [pose]); + + return MimirMoveProofSurface.Create( + admission, + poseFrame, + $"fensalir:starfire:presented-frame:{sequence}", + fensalirPresentedAtNs: checked((ulong)(sourceTimestampNs + 3_100_000L))); + } + + private static bool IsTruthy(string? value) + { + if (string.IsNullOrWhiteSpace(value)) + { + return false; + } + + return value.Equals("1", StringComparison.OrdinalIgnoreCase) + || value.Equals("true", StringComparison.OrdinalIgnoreCase) + || value.Equals("yes", StringComparison.OrdinalIgnoreCase) + || value.Equals("on", StringComparison.OrdinalIgnoreCase); + } +} diff --git a/state/evidence.jsonl b/state/evidence.jsonl index a78d59c..7cc7b31 100644 --- a/state/evidence.jsonl +++ b/state/evidence.jsonl @@ -163,3 +163,4 @@ {"ts": "2026-07-08T22:34:00+01:00", "type": "implementation", "status": "ok", "note": "Added MimirMoveProofPipeline as the shared commit primitive for one Move proof frame: it reads a Muninn CultMesh shared-memory evidence frame, admits samples to native move_evidence, runs MimirMoveFusion, emits the mimir:starfire move-pose frame, creates mimir.move_proof_surface.v1, and lowers the Fensalir spline probe in one pass. Verified dotnet build Mimir.Runtime, dotnet build Mimir.BufferSmoke, and BufferSmoke --move-proof-pipeline-smoke against the native reservoir debug DLL. This proves the reusable Mimir owner path; live Nightwing hardware, calibrated camera witnesses, and actual presented-frame capture remain pending."} {"ts": "2026-07-08T22:41:32+01:00", "type": "implementation", "status": "ok", "note": "Attached the Move proof surface to the actual MimirRuntime/Fensalir frame path: MimirRuntime.PublishMoveProofSurface stores the latest Mimir-owned mimir.move_proof_surface.v1 document, CreateFrame composes its observer-only move-proof-* splines into AquariumFrame.Scene.SplineFrame, and the UI exposes a display-only proof readout. Verified dotnet build Mimir.Runtime, dotnet build Mimir.BufferSmoke, BufferSmoke --move-proof-runtime-frame-smoke, --move-proof-surface-smoke, and --move-proof-pipeline-smoke against the native reservoir debug DLL. Live Nightwing hardware, calibrated camera witnesses, and real presented-frame capture remain pending."} {"ts": "2026-07-08T22:48:42+01:00", "type": "implementation", "status": "ok", "note": "Added MimirMoveProofRuntimeDriver as the runtime bridge from a Muninn CultMesh shared-memory evidence ring to MimirRuntime.PublishMoveProofSurface. The driver derives mimir:starfire evidence/pose ids and fensalir:starfire presented-frame id from the actual Muninn frame suffix, calls MimirMoveProofPipeline, and suppresses duplicate frame admission. Verified dotnet build Mimir.BufferSmoke, BufferSmoke --move-proof-runtime-driver-smoke against the native reservoir debug DLL, --move-proof-runtime-frame-smoke, --move-proof-surface-smoke, and --move-proof-pipeline-smoke. This proves the runtime-owned ring-to-visible-probe path with synthetic ring input; real Nightwing hardware, calibrated camera witnesses, and real presented-frame capture remain pending."} +{"ts": "2026-07-08T22:55:54+01:00", "type": "verification", "status": "ok", "note": "Added and verified the Fensalir presented-frame proof smoke. MIMIR_MOVE_PROOF_DEV_SURFACE gates a synthetic calibrated proof surface through the same MimirRuntime.PublishMoveProofSurface path, and BufferSmoke --move-proof-presented-frame-smoke runs Mimir.App headless, captures artifacts/runtime/move-proof-presented-frame-smoke.png, and pixel-checks the rendered proof probe. Latest run captured fensalir:starfire:presented-frame:79 for proof mimir:starfire:move-proof:79 with bytes=11260 and brightPixels=1069; adjacent runtime-driver, runtime-frame, proof-surface, and proof-pipeline smokes pass. This proves the renderer/probe layer for the named proof chain, not live Nightwing hardware or calibrated field evidence."} diff --git a/state/map.yaml b/state/map.yaml index d8fd33b..4b9821d 100644 --- a/state/map.yaml +++ b/state/map.yaml @@ -155,6 +155,11 @@ accepted_design: actual Muninn frame suffix, calls `MimirMoveProofPipeline`, and suppresses duplicate frame admission so the live loop cannot re-admit one frame and impersonate progress. + - `MimirMoveProofDevSurface` is a dev-only bootstrap gated by + `MIMIR_MOVE_PROOF_DEV_SURFACE`. It publishes a synthetic calibrated proof + surface through the same MimirRuntime attachment so headless Mimir.App can + capture and pixel-check a Fensalir-presented proof probe without making + Fensalir a proof owner. It is renderer evidence, not field evidence. - `MimirMoveCalibrationProtocol` defines the runnable Starfire/Nightwing Move calibration preflight as typed state: required Muninn Move evidence streams, optional Quest headset/controller pose witnesses over USB,