Prove Move proof surface in presented frame
This commit is contained in:
parent
20a296a4a9
commit
293a953b2d
8 changed files with 241 additions and 2 deletions
|
|
@ -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:<sequence>` 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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@
|
|||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="System.Drawing.Common" Version="10.0.0" />
|
||||
<ProjectReference Include="..\..\..\CultLib\src\GameCult.Caching.MessagePack\GameCult.Caching.MessagePack.csproj" />
|
||||
<ProjectReference Include="..\..\..\CultLib\src\GameCult.Mesh\GameCult.Mesh.csproj" />
|
||||
<ProjectReference Include="..\Mimir.Runtime\Mimir.Runtime.csproj" />
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -115,6 +115,11 @@ direct
|
|||
: null;
|
||||
nextAudioSyncSeconds = audioSyncUpdateIntervalSeconds;
|
||||
nextTelemetrySeconds = telemetryIntervalSeconds;
|
||||
if (MimirMoveProofDevSurface.IsEnabled())
|
||||
{
|
||||
PublishMoveProofSurface(MimirMoveProofDevSurface.Create());
|
||||
}
|
||||
|
||||
ui = CreateUi();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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."}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
Loading…
Reference in a new issue