Quarantine Mimir's Eve operator surface: remove EveDashboard, EveBrowserReference and their contract documents

Parked at tag parked/Mimir-eve-surface per thing2:ruling:eve-dashboards-quarantined; rebuild on Thing 2. Mimir.EveSensorReceiver (camera frame ingest) stays. map.yaml no longer calls a live listener a tombstone.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
Meta Vens 2026-10-22 20:14:47 +02:00
commit 43cebb6fb9
15 changed files with 19 additions and 4584 deletions

View file

@ -44,7 +44,6 @@
"authorities": { "authorities": {
"configurationCalibrationLaunchStatus": "Mimir", "configurationCalibrationLaunchStatus": "Mimir",
"programComposition": "Mimir", "programComposition": "Mimir",
"operatorSurfaces": "Eve",
"sitePublication": "Yggdrasil publisher daemon", "sitePublication": "Yggdrasil publisher daemon",
"denseVisualFusion": "Fensalir", "denseVisualFusion": "Fensalir",
"gpuMaterialAndBrushReconciliation": "Fensalir", "gpuMaterialAndBrushReconciliation": "Fensalir",
@ -97,10 +96,8 @@
] ]
}, },
"operator": { "operator": {
"owner": "Mimir/Eve", "owner": "Mimir",
"surfaces": [ "surfaces": [
"eve-gui-compositor",
"eve-tui-operator",
"cultmesh-command-surface" "cultmesh-command-surface"
] ]
}, },
@ -120,7 +117,6 @@
"native-reservoir", "native-reservoir",
"Fensalir", "Fensalir",
"Faust", "Faust",
"Eve",
"Yggdrasil publisher daemon" "Yggdrasil publisher daemon"
], ],
"neverInCompatibilityEdges": [ "neverInCompatibilityEdges": [

View file

@ -29,7 +29,7 @@ Inputs become outputs like this:
- OBS can receive ordinary media-source URLs for compatibility; it does not need - OBS can receive ordinary media-source URLs for compatibility; it does not need
a plugin for this bridge path. a plugin for this bridge path.
- Audio remains separately mixable at the compatibility edge, but the durable - Audio remains separately mixable at the compatibility edge, but the durable
control surface belongs to Mimir/Eve. control surface belongs to Mimir.
- Ports are stable and derived from one base port plus explicit offsets. - Ports are stable and derived from one base port plus explicit offsets.
- Secrets, if used, stay in local config and are not committed. - Secrets, if used, stay in local config and are not committed.

View file

@ -16,7 +16,7 @@ Timestamp for this pass: 2026-05-23, starting at 23:24 Europe/London.
Mimir collects local and network audio/video samples into bounded rolling Mimir collects local and network audio/video samples into bounded rolling
runtime buffers, uses active/passive timing evidence to place audio streams on a runtime buffers, uses active/passive timing evidence to place audio streams on a
canonical timeline, and lets Mimir own program composition while Fensalir lowers canonical timeline, and lets Mimir own program composition while Fensalir lowers
pixels, Faust/native DSP moves audio, Eve exposes operator surfaces, and pixels, Faust/native DSP moves audio, and
Yggdrasil publishes the public feed. Yggdrasil publishes the public feed.
## Current Mechanism ## Current Mechanism
@ -61,8 +61,6 @@ reflection taps.
program video output. program video output.
- Faust/native DSP will own hot resampling, fractional delay, separation, - Faust/native DSP will own hot resampling, fractional delay, separation,
spatialization, and stems. spatialization, and stems.
- Eve GUI/TUI lowerers render Mimir operator surfaces without owning scene
truth.
- The Yggdrasil-facing publisher daemon consumes Mimir program output without - The Yggdrasil-facing publisher daemon consumes Mimir program output without
owning composition. owning composition.
- OBS is a compatibility sink only. - OBS is a compatibility sink only.
@ -91,7 +89,6 @@ flowchart TD
Probes["native/probes/*"] --> FrameEvents["MimirFrameEventProcessStreamSource"] Probes["native/probes/*"] --> FrameEvents["MimirFrameEventProcessStreamSource"]
FrameEvents --> Sources FrameEvents --> Sources
Runtime --> Scene["Mimir program scene graph"] Runtime --> Scene["Mimir program scene graph"]
Scene --> Eve["Eve operator surfaces"]
Scene --> Publisher["Yggdrasil site publisher"] Scene --> Publisher["Yggdrasil site publisher"]
Scene -. "compatibility" .-> Obs["OBS adapter"] Scene -. "compatibility" .-> Obs["OBS adapter"]
Reservoir["native/reservoir"] -. "future lower ABI" .-> Hub Reservoir["native/reservoir"] -. "future lower ABI" .-> Hub
@ -108,7 +105,7 @@ flowchart TD
- `native/probes/*` are measurement and diagnostic tools. - `native/probes/*` are measurement and diagnostic tools.
- `scripts/*` are bridge utilities, not the final synchronized program path. - `scripts/*` are bridge utilities, not the final synchronized program path.
- `docs/mimir-program-composition.md` is the authority map for Muninn capture - `docs/mimir-program-composition.md` is the authority map for Muninn capture
hosts, Mimir composition, Eve operation, Yggdrasil publication, and OBS hosts, Mimir composition, Yggdrasil publication, and OBS
compatibility. compatibility.
## Stage 0: Solution And App Host ## Stage 0: Solution And App Host

View file

@ -365,14 +365,12 @@ a named invariant that the native runtime cannot protect yet.
- Windows bridge scripts for sender discovery/start/stop and simple OBS Media - Windows bridge scripts for sender discovery/start/stop and simple OBS Media
Source compatibility ingest. Source compatibility ingest.
- `src/Mimir.Runtime/Synchronization` now contains the first typed program - `src/Mimir.Runtime/Synchronization` now contains the first typed program
composition contracts: `mimir.program_scene.v1`, `mimir.program_output.v1`, composition contracts: `mimir.program_scene.v1` and `mimir.program_output.v1`.
and `mimir.eve_operator_surface.v1`. These make Mimir's scene graph, These make Mimir's scene graph and Yggdrasil/site publication route
Yggdrasil/site publication route, and Eve operator control/preview surface
manifestable through CultMesh. manifestable through CultMesh.
- `docs/mimir-program-composition.md` is the current authority map for the new - `docs/mimir-program-composition.md` is the current authority map for the new
stream-program architecture: Muninn observes on Starfire/Nightwing/Raven-class stream-program architecture: Muninn observes on Starfire/Nightwing/Raven-class
hosts, Mimir consumes selected streams and composes the program, Eve lowers hosts, Mimir consumes selected streams and composes the program, Yggdrasil publishes to the site, and OBS is
controls/previews/stats, Yggdrasil publishes to the site, and OBS is
compatibility-only. compatibility-only.
- `src/Mimir.CultMeshMedia` is the first explicit CultMesh media/body bridge: - `src/Mimir.CultMeshMedia` is the first explicit CultMesh media/body bridge:
`relay` hosts the document relay on CultNet RUDP `3075`, `send` reads an `relay` hosts the document relay on CultNet RUDP `3075`, `send` reads an
@ -399,23 +397,13 @@ a named invariant that the native runtime cannot protect yet.
capture still defaults to FFmpeg desktop frames plus Mimir's WASAPI loopback capture still defaults to FFmpeg desktop frames plus Mimir's WASAPI loopback
capture muxed as H.264/AAC MPEG-TS; DirectShow audio remains an explicit capture muxed as H.264/AAC MPEG-TS; DirectShow audio remains an explicit
fallback. fallback.
- `src/Mimir.EveDashboard` is archived until the dashboard returns as a pure
CultMesh/Odin publisher and Eve lowering. The old HTTP/WebSocket deck broker,
local health route, socket command channel, and provider route catalog are not
daemon transport. Dashboard state must be published as typed CultMesh/Eve
documents through Odin, and dashboard commands must arrive as typed Odin/
CultMesh command documents.
- `src/Mimir.EveBrowserReference` serves static browser lowerings and can
publish its own `idunn.daemon_health` record over
`cultnet.transport.rudp.v0`. It remains a renderer/reference surface, not
program authority.
- Documentation for OBS receiver setup, native rebuild boundaries, the viable - Documentation for OBS receiver setup, native rebuild boundaries, the viable
stream app, and the Mimir Face. stream app, and the Mimir Face.
## Temporary ## Temporary
- Audio and video may still traverse separate OBS/SRT endpoints during bridge - Audio and video may still traverse separate OBS/SRT endpoints during bridge
testing, but Mimir/Eve owns independent controls. testing, but Mimir owns independent controls.
- The CultMesh media bridge still lowers to local UDP for compatibility sinks - The CultMesh media bridge still lowers to local UDP for compatibility sinks
because OBS is not a CultMesh consumer. Network transit between Raven, because OBS is not a CultMesh consumer. Network transit between Raven,
Yggdrasil, and Starfire is the Odin-discovered CultMesh/CultNet path; Yggdrasil, and Starfire is the Odin-discovered CultMesh/CultNet path;
@ -533,7 +521,7 @@ proxy impairment profiles and long soak remain the completion gate.
9. Implement the Mimir program scene graph as the shared commit primitive for 9. Implement the Mimir program scene graph as the shared commit primitive for
source subscription, transforms, crop, chroma key, visibility, layer order, source subscription, transforms, crop, chroma key, visibility, layer order,
preview, and output publication. Import the current OBS scene only as an preview, and output publication. Import the current OBS scene only as an
initial mirror, then make Eve GUI/TUI the operator surface. initial mirror; the operator surface returns on Thing 2.
10. Add the Yggdrasil-facing site publisher daemon that consumes the Mimir 10. Add the Yggdrasil-facing site publisher daemon that consumes the Mimir
program output and publishes it without owning a second composition. program output and publishes it without owning a second composition.
11. Lower `AquariumBufferFieldFrame` spline tube fields into Fensalir compute: 11. Lower `AquariumBufferFieldFrame` spline tube fields into Fensalir compute:

View file

@ -49,7 +49,7 @@ distributed capture surfaces into one realtime coherent volumetric field. The
field is the product. The bridge scripts are scaffolding and probes. field is the product. The bridge scripts are scaffolding and probes.
The live target is one native five-second rolling reservoir feeding Mimir-owned The live target is one native five-second rolling reservoir feeding Mimir-owned
program composition, Fensalir GPU fusion, Faust/native DSP, Eve operator program composition, Fensalir GPU fusion, Faust/native DSP, operator
surfaces, and a Yggdrasil site publisher. OBS is compatibility only. Retention surfaces, and a Yggdrasil site publisher. OBS is compatibility only. Retention
has one owner. Typed views are indexes, not private histories. has one owner. Typed views are indexes, not private histories.
@ -70,7 +70,6 @@ Invariants:
program texture output. program texture output.
- Faust/native DSP owns hot audio alignment, room suppression, voice separation, - Faust/native DSP owns hot audio alignment, room suppression, voice separation,
stems, and spatial bed generation. stems, and spatial bed generation.
- Eve lowerers render operator controls and preview without owning scene truth.
- The Yggdrasil publisher daemon ships the Mimir program to the site without - The Yggdrasil publisher daemon ships the Mimir program to the site without
owning composition. owning composition.
- FFmpeg owns simple capture, encode, and transport in V1 bridge mode. - FFmpeg owns simple capture, encode, and transport in V1 bridge mode.

View file

@ -11,14 +11,14 @@ temporary compatibility sink only.
- Owner: Mimir owns the program scene graph and subscription policy. - Owner: Mimir owns the program scene graph and subscription policy.
- Inputs: Muninn-published local and remote monitor, window, camera, audio, - Inputs: Muninn-published local and remote monitor, window, camera, audio,
loopback, and health streams. loopback, and health streams.
- Outputs: a Mimir program video surface, aligned audio stems or final bus, Eve - Outputs: a Mimir program video surface, aligned audio stems or final bus,
GUI/TUI operator surfaces, and a Yggdrasil-facing site publication feed. and a Yggdrasil-facing site publication feed.
- Derived state: OBS receives a compatibility feed when useful; it does not own - Derived state: OBS receives a compatibility feed when useful; it does not own
scene truth, broadcast truth, preview truth, stats, source selection, or scene truth, broadcast truth, preview truth, stats, source selection, or
publication. publication.
- Forbidden writers: OBS scenes, browser dashboards, site daemons, and Muninn - Forbidden writers: OBS scenes, browser dashboards, site daemons, and Muninn
capture hosts must not independently decide the program composition. capture hosts must not independently decide the program composition.
- Shared paths: direct operator edits, remote Eve commands, preset loads, - Shared paths: direct operator edits, remote commands, preset loads,
calibration-driven source selection, and recovery scripts must commit through calibration-driven source selection, and recovery scripts must commit through
the same Mimir scene graph command surface. the same Mimir scene graph command surface.
- Deletion line: old OBS-owned composition docs and config names are demoted to - Deletion line: old OBS-owned composition docs and config names are demoted to
@ -39,7 +39,6 @@ flowchart TD
F --> I["Faust/native DSP"] F --> I["Faust/native DSP"]
H --> J["program video surface"] H --> J["program video surface"]
I --> K["program audio stems/bus"] I --> K["program audio stems/bus"]
G --> L["Eve GUI/TUI operator surfaces"]
J --> M["Yggdrasil site publisher daemon"] J --> M["Yggdrasil site publisher daemon"]
K --> M K --> M
M --> N["GameCult site"] M --> N["GameCult site"]
@ -68,22 +67,7 @@ The current OBS scene can be mirrored as an initial Mimir scene:
active. active.
After import, OBS is not the editor of record. It may display the Mimir program After import, OBS is not the editor of record. It may display the Mimir program
feed, but operator edits happen through Mimir/Eve command surfaces. feed, but operator edits happen through Mimir command surfaces.
## Eve Operator Surface
Mimir publishes Eve GUI/TUI documents for:
- live program preview;
- source catalog and subscription state;
- layer transform, crop, chroma key, visibility, and ordering;
- audio stem gain/mute/route controls;
- stream health, latency, buffer depth, timing confidence, and publication
status;
- emergency scene selection and publisher recovery commands.
Eve lowerers render those controls on any device without owning the underlying
scene graph.
## Site Publisher ## Site Publisher

View file

@ -28,7 +28,6 @@ flowchart TD
N --> F["Faust/native DSP"] N --> F["Faust/native DSP"]
E --> G["Mimir program video"] E --> G["Mimir program video"]
F --> H["program stems + spatial bed"] F --> H["program stems + spatial bed"]
E --> I["Eve GUI/TUI operator surfaces"]
G --> J["Yggdrasil site publisher"] G --> J["Yggdrasil site publisher"]
H --> J H --> J
G -. "compatibility" .-> K["OBS adapter"] G -. "compatibility" .-> K["OBS adapter"]
@ -53,17 +52,10 @@ Ownership:
Fensalir-facing wand pose stream. Fensalir-facing wand pose stream.
- Mimir owns program composition, source subscription policy, preview/control - Mimir owns program composition, source subscription policy, preview/control
state, stats, and publication intent. state, stats, and publication intent.
- Mimir's old Eve dashboard broker is archived; it does not publish health,
provider catalogs, or command surfaces while the socket path is cut. The
browser reference remains a client lowering, not daemon truth. Dashboard state
must return as typed CultMesh/Eve documents through Odin; product/debug render
surfaces are client lowerings and compatibility evidence, not daemon truth.
- Fensalir owns dense visual fusion, material/brush/splat reconciliation, - Fensalir owns dense visual fusion, material/brush/splat reconciliation,
D3D12 interop, runtime UI lowering, and local program texture output. D3D12 interop, runtime UI lowering, and local program texture output.
- Faust/native DSP owns hot audio alignment, suppression, separation, - Faust/native DSP owns hot audio alignment, suppression, separation,
spatialization, and stem generation. spatialization, and stem generation.
- Eve GUI/TUI lowerers render Mimir's operator surfaces on any device without
owning scene truth.
- The Yggdrasil-facing publisher daemon consumes Mimir program output and - The Yggdrasil-facing publisher daemon consumes Mimir program output and
publishes it to the site without owning composition. publishes it to the site without owning composition.
- OBS is a temporary compatibility sink, not a composition or broadcast - OBS is a temporary compatibility sink, not a composition or broadcast
@ -302,7 +294,7 @@ evidence.
authority map for stream-program output. Muninn runs on Starfire, Nightwing, and authority map for stream-program output. Muninn runs on Starfire, Nightwing, and
Raven-class capture hosts to publish local stream capabilities and selected Raven-class capture hosts to publish local stream capabilities and selected
media bodies. Mimir consumes only the streams it needs for calibration and media bodies. Mimir consumes only the streams it needs for calibration and
composition, commits one typed scene graph, emits Eve GUI/TUI operator surfaces, composition, commits one typed scene graph,
and publishes program output locally plus through a Yggdrasil-facing site and publishes program output locally plus through a Yggdrasil-facing site
publisher. publisher.
@ -439,14 +431,6 @@ an RUDP contract exists.
Mimir's current daemon-health publishers are local RUDP witnesses: Mimir's current daemon-health publishers are local RUDP witnesses:
- `src/Mimir.EveDashboard` is a fail-closed archive tombstone. It no longer
starts a TCP listener, serves `/eve/deck`, accepts WebSocket commands, or
publishes daemon health. `mimir.eve_dashboard_state` must return through Odin/
CultMesh state publication before this service can be redeployed.
- `src/Mimir.EveBrowserReference` serves a static browser lowering and, when
configured with `--idunn-rudp-health` or
`MIMIR_EVE_BROWSER_REFERENCE_IDUNN_RUDP_HEALTH`, sends its own
`idunn.daemon_health` record over the same RUDP document path.
Invariant: health publication is not ownership. The daemon reports what it can Invariant: health publication is not ownership. The daemon reports what it can
honestly observe about itself; Idunn decides keepalive action through the honestly observe about itself; Idunn decides keepalive action through the

View file

@ -1477,10 +1477,6 @@ static int RunPerfectMachineProfileSmoke()
"mimir-site-program", "mimir-site-program",
scene.SceneId, scene.SceneId,
MimirProgramPublicationConfigurations.YggdrasilSiteProgram); MimirProgramPublicationConfigurations.YggdrasilSiteProgram);
var eveSurface = MimirCultMeshContractFactory.CreateOperatorSurface(
"mimir-eve-gui-compositor",
scene.SceneId,
MimirProgramPublicationConfigurations.OperatorSurfaces[0]);
var observations = new[] var observations = new[]
{ {
new MimirBioacousticWordObservation(0, MimirBioacousticTimeline.Default.EventForIndex(0).StartSeconds * MimirBioacousticTimeline.SampleRate + 317.375, 0.95), new MimirBioacousticWordObservation(0, MimirBioacousticTimeline.Default.EventForIndex(0).StartSeconds * MimirBioacousticTimeline.SampleRate + 317.375, 0.95),
@ -1565,7 +1561,7 @@ static int RunPerfectMachineProfileSmoke()
Console.WriteLine( Console.WriteLine(
$"perfect-machine-actuator source={actuator.SourceId} delay={actuator.TargetDelaySamples:0.000} ratio={actuator.ResampleRatio:0.000000000} controls={actuator.FaustControls.Length}"); $"perfect-machine-actuator source={actuator.SourceId} delay={actuator.TargetDelaySamples:0.000} ratio={actuator.ResampleRatio:0.000000000} controls={actuator.FaustControls.Length}");
Console.WriteLine( Console.WriteLine(
$"perfect-machine-program scene={scene.SceneId} layers={scene.Layers.Length} output={output.OutputId} route={output.PublisherRoute} eveCommands={eveSurface.CommandTopics.Length}"); $"perfect-machine-program scene={scene.SceneId} layers={scene.Layers.Length} output={output.OutputId} route={output.PublisherRoute}");
Console.WriteLine( Console.WriteLine(
$"perfect-machine-clock anchors={clock?.AnchorCount ?? 0} coverage={clock?.AnchorCoverage ?? 0:0.000} offset={clock?.SourceOffsetSamples ?? double.NaN:0.000} confidence={clock?.Confidence ?? 0:0.000}"); $"perfect-machine-clock anchors={clock?.AnchorCount ?? 0} coverage={clock?.AnchorCoverage ?? 0:0.000} offset={clock?.SourceOffsetSamples ?? double.NaN:0.000} confidence={clock?.Confidence ?? 0:0.000}");
Console.WriteLine( Console.WriteLine(
@ -3927,11 +3923,6 @@ static async Task<int> RunPerfectMachineContractSmokeAsync(string outputPath)
scene.SceneId, scene.SceneId,
MimirProgramPublicationConfigurations.YggdrasilSiteProgram, MimirProgramPublicationConfigurations.YggdrasilSiteProgram,
createdAt); createdAt);
var eveSurface = MimirCultMeshContractFactory.CreateOperatorSurface(
"mimir-eve-gui-compositor",
scene.SceneId,
MimirProgramPublicationConfigurations.OperatorSurfaces[0],
createdAt);
var starfireMove = MimirTrackingObservation.PsMove( var starfireMove = MimirTrackingObservation.PsMove(
"starfire:move:primary", "starfire:move:primary",
"psmove:starfire:00", "psmove:starfire:00",
@ -4004,10 +3995,6 @@ static async Task<int> RunPerfectMachineContractSmokeAsync(string outputPath)
output, output,
new CultRecordHandle<MimirProgramOutputDocument>(new CultRecordKey($"mimir-program-output:{output.OutputId}"))) new CultRecordHandle<MimirProgramOutputDocument>(new CultRecordKey($"mimir-program-output:{output.OutputId}")))
.ConfigureAwait(false); .ConfigureAwait(false);
await cache.UpsertAsync(
eveSurface,
new CultRecordHandle<MimirEveOperatorSurfaceDocument>(new CultRecordKey($"mimir-eve-surface:{eveSurface.SurfaceId}")))
.ConfigureAwait(false);
await cache.UpsertAsync( await cache.UpsertAsync(
starfireMove, starfireMove,
new CultRecordHandle<MimirTrackingObservation>(new CultRecordKey($"mimir-move-tracking:{starfireMove.ObservationId}"))) new CultRecordHandle<MimirTrackingObservation>(new CultRecordKey($"mimir-move-tracking:{starfireMove.ObservationId}")))
@ -4023,7 +4010,7 @@ static async Task<int> RunPerfectMachineContractSmokeAsync(string outputPath)
await cache.FlushAsync().ConfigureAwait(false); await cache.FlushAsync().ConfigureAwait(false);
Console.WriteLine( Console.WriteLine(
$"perfect-machine-contract-smoke path={outputPath} codebookMotifs={codebook.Motifs.Length} decoder={decoder.Configuration.Id} pathBands={pathState.BandResponses.Length} actuatorControls={actuator.FaustControls.Length} sceneLayers={scene.Layers.Length} programOutput={output.OutputId} eveCommands={eveSurface.CommandTopics.Length} moveTracking=2 fusedMovePose={fusedMovePose.PoseId} authority={fusedMovePose.FusionAuthorityId} consumer={fusedMovePose.ConsumerContract} hosts={starfireMove.HostId},{nightwingMove.HostId}"); $"perfect-machine-contract-smoke path={outputPath} codebookMotifs={codebook.Motifs.Length} decoder={decoder.Configuration.Id} pathBands={pathState.BandResponses.Length} actuatorControls={actuator.FaustControls.Length} sceneLayers={scene.Layers.Length} programOutput={output.OutputId} moveTracking=2 fusedMovePose={fusedMovePose.PoseId} authority={fusedMovePose.FusionAuthorityId} consumer={fusedMovePose.ConsumerContract} hosts={starfireMove.HostId},{nightwingMove.HostId}");
return 0; return 0;
} }
@ -4045,11 +4032,6 @@ static async Task<int> ImportObsProgramSceneAsync(string inputPath, string scene
scene.SceneId, scene.SceneId,
MimirProgramPublicationConfigurations.YggdrasilSiteProgram, MimirProgramPublicationConfigurations.YggdrasilSiteProgram,
importedAt); importedAt);
var eveSurface = MimirCultMeshContractFactory.CreateOperatorSurface(
"mimir-eve-gui-compositor",
scene.SceneId,
MimirProgramPublicationConfigurations.OperatorSurfaces[0],
importedAt);
Directory.CreateDirectory(Path.GetDirectoryName(Path.GetFullPath(outputPath)) ?? "."); Directory.CreateDirectory(Path.GetDirectoryName(Path.GetFullPath(outputPath)) ?? ".");
using var cache = await CultCacheMessagePack.OpenAsync(outputPath).ConfigureAwait(false); using var cache = await CultCacheMessagePack.OpenAsync(outputPath).ConfigureAwait(false);
@ -4061,10 +4043,6 @@ static async Task<int> ImportObsProgramSceneAsync(string inputPath, string scene
output, output,
new CultRecordHandle<MimirProgramOutputDocument>(new CultRecordKey($"mimir-program-output:{output.OutputId}"))) new CultRecordHandle<MimirProgramOutputDocument>(new CultRecordKey($"mimir-program-output:{output.OutputId}")))
.ConfigureAwait(false); .ConfigureAwait(false);
await cache.UpsertAsync(
eveSurface,
new CultRecordHandle<MimirEveOperatorSurfaceDocument>(new CultRecordKey($"mimir-eve-surface:{eveSurface.SurfaceId}")))
.ConfigureAwait(false);
await cache.FlushAsync().ConfigureAwait(false); await cache.FlushAsync().ConfigureAwait(false);
var visible = scene.Layers.Count(layer => layer.Visible); var visible = scene.Layers.Count(layer => layer.Visible);

View file

@ -1,17 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<AssemblyName>Mimir.EveBrowserReference</AssemblyName>
<RootNamespace>Mimir.EveBrowserReference</RootNamespace>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="$(CultLibRoot)\src\GameCult.Caching.MessagePack\GameCult.Caching.MessagePack.csproj" />
<ProjectReference Include="$(CultLibRoot)\src\GameCult.Networking\GameCult.Networking.csproj" />
</ItemGroup>
</Project>

View file

@ -1,734 +0,0 @@
using System.Globalization;
using System.Net;
using System.Net.Sockets;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
using GameCult.Caching;
using GameCult.Caching.MessagePack;
using GameCult.Networking;
using MessagePack;
var port = ParseInt(args, "--port", 8891);
var bind = ParseString(args, "--bind", Environment.GetEnvironmentVariable("MIMIR_EVE_BROWSER_REFERENCE_BIND") ?? "0.0.0.0");
var directory = ParseString(args, "--directory", Environment.GetEnvironmentVariable("MIMIR_EVE_BROWSER_REFERENCE_DIRECTORY") ?? Directory.GetCurrentDirectory());
var cultCacheWitnessPath = ParseString(
args,
"--cultcache-witness",
Environment.GetEnvironmentVariable("MIMIR_EVE_BROWSER_REFERENCE_CULTCACHE_WITNESS") ?? DefaultPaths.CultCacheWitnessPath);
var idunnRudpHealth = ParseString(args, "--idunn-rudp-health", Environment.GetEnvironmentVariable("MIMIR_EVE_BROWSER_REFERENCE_IDUNN_RUDP_HEALTH") ?? "");
var idunnDaemon = ParseString(args, "--idunn-daemon", Environment.GetEnvironmentVariable("MIMIR_EVE_BROWSER_REFERENCE_IDUNN_DAEMON") ?? "nightwing-eve-browser-reference");
var idunnHealthContract = ParseString(args, "--idunn-health-contract", Environment.GetEnvironmentVariable("MIMIR_EVE_BROWSER_REFERENCE_IDUNN_HEALTH_CONTRACT") ?? "nightwing.cultnet-rudp-browser-reference-health");
using var verseRuntime = EveBrowserReferenceVerseRuntime.Create(cultCacheWitnessPath);
using var server = new EveBrowserReferenceServer(
IPAddress.Parse(bind),
port,
Path.GetFullPath(directory),
verseRuntime,
new IdunnRudpHealthOptions(idunnRudpHealth, idunnDaemon, idunnHealthContract));
await server.RunAsync().ConfigureAwait(false);
static int ParseInt(IReadOnlyList<string> args, string name, int fallback)
{
for (var index = 0; index < args.Count - 1; index++)
{
if (string.Equals(args[index], name, StringComparison.OrdinalIgnoreCase) &&
int.TryParse(args[index + 1], CultureInfo.InvariantCulture, out var value))
{
return value;
}
}
return fallback;
}
static string ParseString(IReadOnlyList<string> args, string name, string fallback)
{
for (var index = 0; index < args.Count - 1; index++)
{
if (string.Equals(args[index], name, StringComparison.OrdinalIgnoreCase) &&
!string.IsNullOrWhiteSpace(args[index + 1]))
{
return args[index + 1];
}
}
return fallback;
}
internal static class DefaultPaths
{
public static string CultCacheWitnessPath =>
OperatingSystem.IsWindows()
? @"E:\Projects\Mimir\state\eve-browser-reference.service.cc"
: "/var/lib/gamecult/eve-browser-reference/cultcache/eve-browser-reference.service.cc";
}
internal sealed class EveBrowserReferenceServer(
IPAddress bindAddress,
int port,
string documentRoot,
EveBrowserReferenceVerseRuntime verseRuntime,
IdunnRudpHealthOptions idunnRudpHealth) : IDisposable
{
private static readonly JsonSerializerOptions JsonOptions = new()
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
};
private readonly TcpListener listener = new(bindAddress, port);
private readonly CancellationTokenSource stopping = new();
private readonly object idunnRudpPublishLock = new();
private string idunnRudpPublishStatus = string.IsNullOrWhiteSpace(idunnRudpHealth.Endpoint) ? "disabled" : "idle";
private string idunnRudpPublishError = "";
private string idunnRudpPublishObservedAt = "";
private bool idunnRudpPublishInFlight;
private DateTimeOffset lastHealthPublishAttemptAt = DateTimeOffset.MinValue;
private DateTimeOffset lastWitnessPublishAttemptAt = DateTimeOffset.MinValue;
public async Task RunAsync()
{
if (!Directory.Exists(documentRoot))
{
throw new DirectoryNotFoundException($"Eve browser reference document root does not exist: {documentRoot}");
}
listener.Start();
Console.WriteLine($"Mimir Eve browser reference serving {documentRoot} on http://{bindAddress}:{port}/");
Console.WriteLine(string.IsNullOrWhiteSpace(idunnRudpHealth.Endpoint)
? "Mimir Eve browser reference Idunn RUDP health publisher disabled."
: $"Mimir Eve browser reference Idunn RUDP health publisher targeting {idunnRudpHealth.Endpoint} as {idunnRudpHealth.DaemonId}.");
Console.WriteLine($"Mimir Eve browser reference witness store publishing to {verseRuntime.WitnessPath}");
_ = Task.Run(PublishHealthLoopAsync);
while (!stopping.IsCancellationRequested)
{
var client = await listener.AcceptTcpClientAsync(stopping.Token).ConfigureAwait(false);
_ = Task.Run(() => HandleAsync(client));
}
}
private async Task PublishHealthLoopAsync()
{
using var timer = new PeriodicTimer(TimeSpan.FromSeconds(1));
while (!stopping.IsCancellationRequested && await timer.WaitForNextTickAsync(stopping.Token).ConfigureAwait(false))
{
var observedAt = DateTimeOffset.UtcNow;
PublishWitnessIfDue(observedAt);
QueueIdunnRudpHealthIfDue(observedAt);
}
}
private async Task HandleAsync(TcpClient tcpClient)
{
await using var stream = tcpClient.GetStream();
var request = await ReadHttpRequestAsync(stream).ConfigureAwait(false);
if (request.Path == "/health")
{
var health = JsonSerializer.Serialize(new
{
ok = true,
documentRoot,
port,
transport = "static-http-lowering",
cultCacheWitness = verseRuntime.GetWitnessStatus(),
idunnRudpHealth = GetIdunnRudpHealthStatus(),
}, JsonOptions);
await WriteHttpResponseAsync(stream, "200 OK", "application/json", Encoding.UTF8.GetBytes(health)).ConfigureAwait(false);
return;
}
var filePath = ResolveStaticFilePath(request.Path);
if (filePath == null)
{
await WriteHttpResponseAsync(stream, "404 Not Found", "text/plain", Encoding.UTF8.GetBytes("not found")).ConfigureAwait(false);
return;
}
var bytes = await File.ReadAllBytesAsync(filePath, stopping.Token).ConfigureAwait(false);
await WriteHttpResponseAsync(stream, "200 OK", ContentType(filePath), bytes).ConfigureAwait(false);
}
private IdunnRudpHealthStatus GetIdunnRudpHealthStatus()
{
lock (idunnRudpPublishLock)
{
return new IdunnRudpHealthStatus(
idunnRudpHealth.Endpoint,
idunnRudpHealth.DaemonId,
idunnRudpHealth.HealthContract,
idunnRudpPublishStatus,
idunnRudpPublishError,
idunnRudpPublishInFlight,
idunnRudpPublishObservedAt);
}
}
private void PublishWitnessIfDue(DateTimeOffset observedAt)
{
if (lastWitnessPublishAttemptAt != DateTimeOffset.MinValue &&
observedAt - lastWitnessPublishAttemptAt < TimeSpan.FromSeconds(5))
{
return;
}
lastWitnessPublishAttemptAt = observedAt;
verseRuntime.Publish(
CreateDaemonHealthRecord(observedAt),
documentRoot,
port,
observedAt);
}
private void QueueIdunnRudpHealthIfDue(DateTimeOffset observedAt)
{
if (string.IsNullOrWhiteSpace(idunnRudpHealth.Endpoint))
{
idunnRudpPublishStatus = "disabled";
return;
}
if (lastHealthPublishAttemptAt != DateTimeOffset.MinValue &&
observedAt - lastHealthPublishAttemptAt < TimeSpan.FromSeconds(5))
{
return;
}
var health = CreateDaemonHealthRecord(observedAt);
var observedAtText = health.ObservedAt;
lock (idunnRudpPublishLock)
{
if (idunnRudpPublishInFlight)
{
return;
}
idunnRudpPublishInFlight = true;
lastHealthPublishAttemptAt = observedAt;
idunnRudpPublishObservedAt = observedAtText;
if (!string.Equals(idunnRudpPublishStatus, "published", StringComparison.Ordinal))
{
idunnRudpPublishStatus = "publishing";
idunnRudpPublishError = "";
}
}
_ = Task.Run(() =>
{
try
{
IdunnRudpHealthPublisher.Publish(idunnRudpHealth.Endpoint, health);
lock (idunnRudpPublishLock)
{
idunnRudpPublishStatus = "published";
idunnRudpPublishError = "";
idunnRudpPublishInFlight = false;
}
}
catch (Exception error)
{
lock (idunnRudpPublishLock)
{
if (!string.Equals(idunnRudpPublishStatus, "published", StringComparison.Ordinal))
{
idunnRudpPublishStatus = "publish-error";
}
idunnRudpPublishError = error.Message;
idunnRudpPublishInFlight = false;
}
}
});
}
private IdunnDaemonHealthRecord CreateDaemonHealthRecord(DateTimeOffset observedAt)
{
var observedAtText = observedAt.ToString("O", CultureInfo.InvariantCulture);
return new IdunnDaemonHealthRecord
{
DaemonId = idunnRudpHealth.DaemonId,
State = "healthy",
Detail = $"Nightwing Eve browser reference serving static lowering; root={documentRoot}; port={port}; witness={verseRuntime.WitnessPath}",
ObservedAt = observedAtText,
HealthContract = idunnRudpHealth.HealthContract,
PublicationSource = "daemon-published",
Transport = "cultnet.transport.rudp.v0",
};
}
private string? ResolveStaticFilePath(string path)
{
var requestPath = path.Split('?', 2)[0];
if (requestPath == "/")
{
requestPath = "/index.html";
}
var relativePath = Uri.UnescapeDataString(requestPath.TrimStart('/'))
.Replace('/', Path.DirectorySeparatorChar);
var candidate = Path.GetFullPath(Path.Combine(documentRoot, relativePath));
if (!candidate.StartsWith(documentRoot.TrimEnd(Path.DirectorySeparatorChar) + Path.DirectorySeparatorChar, StringComparison.Ordinal) &&
!string.Equals(candidate, documentRoot, StringComparison.Ordinal))
{
return null;
}
return File.Exists(candidate) ? candidate : null;
}
private static string ContentType(string path) =>
Path.GetExtension(path).ToLowerInvariant() switch
{
".html" => "text/html; charset=utf-8",
".css" => "text/css; charset=utf-8",
".js" => "application/javascript; charset=utf-8",
".json" => "application/json; charset=utf-8",
".png" => "image/png",
".jpg" or ".jpeg" => "image/jpeg",
".svg" => "image/svg+xml",
_ => "application/octet-stream",
};
private static async Task<HttpRequest> ReadHttpRequestAsync(NetworkStream stream)
{
var buffer = new byte[8192];
var length = await stream.ReadAsync(buffer).ConfigureAwait(false);
if (length == 0)
{
return new HttpRequest("/");
}
var text = Encoding.ASCII.GetString(buffer, 0, length);
var firstLineEnd = text.IndexOf("\r\n", StringComparison.Ordinal);
var firstLine = firstLineEnd >= 0 ? text[..firstLineEnd] : text;
var parts = firstLine.Split(' ', StringSplitOptions.RemoveEmptyEntries);
return new HttpRequest(parts.Length >= 2 ? parts[1] : "/");
}
private static async Task WriteHttpResponseAsync(NetworkStream stream, string status, string contentType, byte[] body)
{
var header = Encoding.ASCII.GetBytes(
$"HTTP/1.1 {status}\r\nContent-Type: {contentType}\r\nContent-Length: {body.Length}\r\nConnection: close\r\n\r\n");
await stream.WriteAsync(header).ConfigureAwait(false);
await stream.WriteAsync(body).ConfigureAwait(false);
}
public void Dispose()
{
stopping.Cancel();
listener.Stop();
stopping.Dispose();
}
}
internal sealed record HttpRequest(string Path);
internal sealed record IdunnRudpHealthOptions(string Endpoint, string DaemonId, string HealthContract);
internal sealed record CultCacheWitnessStatus(
string Path,
string Status,
string Error,
string ObservedAtUtc,
IReadOnlyList<string> PublishedSchemas);
internal sealed record IdunnRudpHealthStatus(
string Endpoint,
string Daemon,
string Contract,
string Status,
string Error,
bool InFlight,
string ObservedAtUtc);
[CultDocument("idunn.daemon_health", "idunn.daemon_health.v1")]
[MessagePackObject(AllowPrivate = true)]
internal sealed class IdunnDaemonHealthRecord
{
[Key(0)] public string DaemonId { get; set; } = "nightwing-eve-browser-reference";
[Key(1)] public string State { get; set; } = "active";
[Key(2)] public string Detail { get; set; } = "";
[Key(3)] public string ObservedAt { get; set; } = "";
[Key(4)] public string HealthContract { get; set; } = "nightwing.cultnet-rudp-browser-reference-health";
[Key(5)] public string PublicationSource { get; set; } = "daemon-published";
[Key(6)] public string Transport { get; set; } = "cultnet.transport.rudp.v0";
}
internal sealed class EveBrowserReferenceVerseRuntime : IDisposable
{
private static readonly CultRecordKey ManifestKey = new("nightwing-eve-browser-reference");
private static readonly CultRecordKey StaticSurfaceKey = new("nightwing-eve-browser-reference");
private static readonly CultRecordKey CommandBoundaryKey = new("nightwing-eve-browser-reference");
private static readonly CultRecordKey TransportProfileKey = new("nightwing-eve-browser-reference");
private static readonly string[] PublishedSchemas =
[
"mimir.eve_browser_reference_manifest.v1",
"mimir.eve_browser_reference_static_surface.v1",
"mimir.eve_browser_reference_command_boundary.v1",
"mimir.eve_browser_reference_transport_profile.v1",
"idunn.daemon_health.v1",
];
private readonly CultDocumentRegistry registry = new();
private readonly string witnessPath;
private readonly object publishLock = new();
private string witnessStatus = "starting";
private string witnessError = string.Empty;
private string witnessObservedAtUtc = string.Empty;
private EveBrowserReferenceVerseRuntime(string witnessPath)
{
this.witnessPath = Path.GetFullPath(witnessPath);
}
public string WitnessPath => witnessPath;
public static EveBrowserReferenceVerseRuntime Create(string witnessPath)
{
Directory.CreateDirectory(Path.GetDirectoryName(Path.GetFullPath(witnessPath)) ?? ".");
return new EveBrowserReferenceVerseRuntime(witnessPath);
}
public CultCacheWitnessStatus GetWitnessStatus()
{
lock (publishLock)
{
return new CultCacheWitnessStatus(
witnessPath,
witnessStatus,
witnessError,
witnessObservedAtUtc,
PublishedSchemas);
}
}
public void Publish(
IdunnDaemonHealthRecord healthRecord,
string documentRoot,
int port,
DateTimeOffset observedAt)
{
var observedAtUtc = observedAt.ToString("O", CultureInfo.InvariantCulture);
try
{
lock (publishLock)
{
var records = new List<CultPersistedRecord>();
var schemas = new Dictionary<string, CultSchemaCatalogEntry>(StringComparer.Ordinal);
var assets = EnumerateRelativeFiles(documentRoot);
var fixtures = assets
.Where(path => path.StartsWith("fixtures/", StringComparison.Ordinal))
.ToArray();
AddRecord(records, schemas, ToManifestDocument(documentRoot, port, assets), ManifestKey, observedAtUtc);
AddRecord(records, schemas, ToStaticSurfaceDocument(documentRoot, assets, fixtures, observedAtUtc), StaticSurfaceKey, observedAtUtc);
AddRecord(records, schemas, ToCommandBoundaryDocument(observedAtUtc), CommandBoundaryKey, observedAtUtc);
AddRecord(records, schemas, ToTransportProfileDocument(observedAtUtc), TransportProfileKey, observedAtUtc);
AddRecord(records, schemas, healthRecord, new CultRecordKey(healthRecord.DaemonId), observedAtUtc);
WriteSnapshot(new CultPersistedStoreSnapshot
{
FormatVersion = "cultcache.store.v1",
SchemaCatalog = schemas.Values.OrderBy(entry => entry.SchemaName, StringComparer.Ordinal).ToArray(),
Records = records.OrderBy(entry => entry.Key, StringComparer.Ordinal).ToArray(),
});
witnessStatus = "published";
witnessError = string.Empty;
witnessObservedAtUtc = observedAtUtc;
}
}
catch (Exception error)
{
lock (publishLock)
{
witnessStatus = "publish-error";
witnessError = error.Message;
witnessObservedAtUtc = observedAtUtc;
}
}
}
public void Dispose()
{
}
private void AddRecord<T>(
List<CultPersistedRecord> records,
Dictionary<string, CultSchemaCatalogEntry> schemas,
T document,
CultRecordKey key,
string observedAtUtc) where T : class
{
var descriptor = registry.GetRequired<T>();
var payload = MessagePackSerializer.Serialize(typeof(T), document, CultNetSchemaMessageSerialization.Options);
schemas[descriptor.SchemaId] = descriptor.ToCatalogEntry();
records.Add(new CultPersistedRecord
{
Key = key.Value,
SchemaId = descriptor.SchemaId,
StoredAt = observedAtUtc,
Payload = payload,
});
}
private void WriteSnapshot(CultPersistedStoreSnapshot snapshot)
{
var payload = CultDocumentMessagePackSerialization.SerializeSnapshot(snapshot);
var directory = Path.GetDirectoryName(witnessPath) ?? ".";
Directory.CreateDirectory(directory);
var tempPath = Path.Combine(directory, $".{Path.GetFileName(witnessPath)}.{Guid.NewGuid():N}.tmp");
try
{
using (var stream = new FileStream(
tempPath,
FileMode.CreateNew,
FileAccess.Write,
FileShare.None,
bufferSize: 4096,
FileOptions.WriteThrough))
{
stream.Write(payload, 0, payload.Length);
stream.Flush(true);
}
if (File.Exists(witnessPath))
{
File.Replace(tempPath, witnessPath, null);
}
else
{
File.Move(tempPath, witnessPath);
}
}
finally
{
if (File.Exists(tempPath))
{
File.Delete(tempPath);
}
}
}
private static EveBrowserReferenceManifestRecord ToManifestDocument(
string documentRoot,
int port,
IReadOnlyList<string> assets) =>
new()
{
DaemonId = "nightwing-eve-browser-reference",
SiteTitle = "Nightwing Eve Browser Reference",
DocumentRoot = documentRoot,
EntryPoint = "index.html",
Port = port,
Assets = assets.ToArray(),
SurfaceTransport = "static-http-lowering",
};
private static EveBrowserReferenceStaticSurfaceRecord ToStaticSurfaceDocument(
string documentRoot,
IReadOnlyList<string> assets,
IReadOnlyList<string> fixtures,
string observedAtUtc) =>
new()
{
DaemonId = "nightwing-eve-browser-reference",
UpdatedAtUtc = observedAtUtc,
DocumentRoot = documentRoot,
EntryPoint = "index.html",
Assets = assets.ToArray(),
FixtureDocuments = fixtures.ToArray(),
CurrentCutLine = "Static browser lowering assets are published into the daemon-owned witness store so Nightwing and Odin can reason about the lowering body without treating the HTTP service probe as lifecycle truth.",
};
private static EveBrowserReferenceCommandBoundaryRecord ToCommandBoundaryDocument(string observedAtUtc) =>
new()
{
DaemonId = "nightwing-eve-browser-reference",
UpdatedAtUtc = observedAtUtc,
Mode = "static-read-only-lowering",
WritesAccepted = false,
OperatorInputAuthority = "served browser assets only; no write path is accepted by this daemon",
LifecycleAuthority = "idunn.local-command.restart + compatibility.systemd.nightwing-eve-browser-reference.service",
AcceptedCommands = [],
RejectedCommands =
[
"surface-mutation",
"provider-catalog-ownership",
"daemon-health-override",
"service-restart",
],
};
private EveBrowserReferenceTransportProfileRecord ToTransportProfileDocument(string observedAtUtc) =>
new()
{
DaemonId = "nightwing-eve-browser-reference",
UpdatedAtUtc = observedAtUtc,
CurrentState = "partial-rudp-health-and-provider-store-live",
InputTransport = "compatibility.http-static-lowering",
OutputTransport = "daemon-owned-cultcache-boundary-store + daemon-published-rudp-health",
HealthContract = "nightwing.cultnet-rudp-browser-reference-health",
IdunnRudpHealth = "idunn.health-published-separately",
WitnessPath = witnessPath,
CurrentCutLine = "The Nightwing Eve browser reference runtime now owns its manifest, static surface witness, command boundary, and transport profile in a daemon-owned CultCache store; systemd and HTTP remain compatibility witnesses while projections learn to consume the typed store directly.",
};
private static string[] EnumerateRelativeFiles(string root)
{
if (!Directory.Exists(root))
{
return [];
}
return Directory
.EnumerateFiles(root, "*", SearchOption.AllDirectories)
.Select(path => Path.GetRelativePath(root, path).Replace('\\', '/'))
.OrderBy(path => path, StringComparer.Ordinal)
.ToArray();
}
}
[CultDocument("mimir.eve_browser_reference_manifest", "mimir.eve_browser_reference_manifest.v1")]
[MessagePackObject(AllowPrivate = true)]
internal sealed class EveBrowserReferenceManifestRecord
{
[Key(0)]
[CultName]
public string DaemonId { get; set; } = string.Empty;
[Key(1)] public string SiteTitle { get; set; } = string.Empty;
[Key(2)] public string DocumentRoot { get; set; } = string.Empty;
[Key(3)] public string EntryPoint { get; set; } = string.Empty;
[Key(4)] public int Port { get; set; }
[Key(5)] public string[] Assets { get; set; } = [];
[Key(6)] public string SurfaceTransport { get; set; } = string.Empty;
}
[CultDocument("mimir.eve_browser_reference_static_surface", "mimir.eve_browser_reference_static_surface.v1")]
[MessagePackObject(AllowPrivate = true)]
internal sealed class EveBrowserReferenceStaticSurfaceRecord
{
[Key(0)]
[CultName]
public string DaemonId { get; set; } = string.Empty;
[Key(1)] public string UpdatedAtUtc { get; set; } = string.Empty;
[Key(2)] public string DocumentRoot { get; set; } = string.Empty;
[Key(3)] public string EntryPoint { get; set; } = string.Empty;
[Key(4)] public string[] Assets { get; set; } = [];
[Key(5)] public string[] FixtureDocuments { get; set; } = [];
[Key(6)] public string CurrentCutLine { get; set; } = string.Empty;
}
[CultDocument("mimir.eve_browser_reference_command_boundary", "mimir.eve_browser_reference_command_boundary.v1")]
[MessagePackObject(AllowPrivate = true)]
internal sealed class EveBrowserReferenceCommandBoundaryRecord
{
[Key(0)]
[CultName]
public string DaemonId { get; set; } = string.Empty;
[Key(1)] public string UpdatedAtUtc { get; set; } = string.Empty;
[Key(2)] public string Mode { get; set; } = string.Empty;
[Key(3)] public bool WritesAccepted { get; set; }
[Key(4)] public string OperatorInputAuthority { get; set; } = string.Empty;
[Key(5)] public string LifecycleAuthority { get; set; } = string.Empty;
[Key(6)] public string[] AcceptedCommands { get; set; } = [];
[Key(7)] public string[] RejectedCommands { get; set; } = [];
}
[CultDocument("mimir.eve_browser_reference_transport_profile", "mimir.eve_browser_reference_transport_profile.v1")]
[MessagePackObject(AllowPrivate = true)]
internal sealed class EveBrowserReferenceTransportProfileRecord
{
[Key(0)]
[CultName]
public string DaemonId { get; set; } = string.Empty;
[Key(1)] public string UpdatedAtUtc { get; set; } = string.Empty;
[Key(2)] public string CurrentState { get; set; } = string.Empty;
[Key(3)] public string InputTransport { get; set; } = string.Empty;
[Key(4)] public string OutputTransport { get; set; } = string.Empty;
[Key(5)] public string HealthContract { get; set; } = string.Empty;
[Key(6)] public string IdunnRudpHealth { get; set; } = string.Empty;
[Key(7)] public string WitnessPath { get; set; } = string.Empty;
[Key(8)] public string CurrentCutLine { get; set; } = string.Empty;
}
internal static class IdunnRudpHealthPublisher
{
private const uint ConnectionId = 0x1d0d0001;
public static void Publish(string endpoint, IdunnDaemonHealthRecord health)
{
var remote = ParseEndpoint(endpoint);
using var socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
socket.Bind(new IPEndPoint(IPAddress.Any, 0));
socket.Blocking = false;
socket.ReceiveTimeout = 100;
socket.SendTimeout = 1000;
using var transport = new CultNetRudpSocketTransportConnection(new CultNetRudpSocketTransportOptions
{
RuntimeId = "mimir-eve-browser-reference",
Socket = socket,
Mode = CultNetRudpSocketMode.Client,
RemoteEndPoint = remote,
ConnectionId = ConnectionId,
InitialSequence = 1,
ResendDelayMs = 100,
});
if (!transport.ConnectAndWait(Array.Empty<byte>(), TimeSpan.FromSeconds(5), TimeSpan.FromMilliseconds(20)))
{
throw new TimeoutException($"timed out connecting Mimir Eve browser reference RUDP health publisher to {endpoint}");
}
var observedAt = string.IsNullOrWhiteSpace(health.ObservedAt)
? DateTimeOffset.UtcNow.ToString("O", CultureInfo.InvariantCulture)
: health.ObservedAt;
health.ObservedAt = observedAt;
var message = new CultNetDocumentPutRawMessage
{
MessageId = $"mimir-eve-browser-reference-health:{health.DaemonId}:{observedAt.Replace(':', '-')}",
Document = new CultNetRawDocumentRecord
{
SchemaId = "idunn.daemon_health",
RecordKey = health.DaemonId,
StoredAt = observedAt,
PayloadEncoding = "messagepack",
Payload = MessagePackSerializer.Serialize(health, CultNetSchemaMessageSerialization.Options),
SourceRuntimeId = "mimir-eve-browser-reference",
SourceRole = "daemon-health-publisher",
Tags = ["cultnet.transport.rudp.v0"],
},
};
transport.SendSchema(CultNetSchemaMessageSerialization.Serialize(message));
}
private static IPEndPoint ParseEndpoint(string value)
{
var trimmed = value.Trim();
var separator = trimmed.LastIndexOf(':');
if (separator <= 0 || separator == trimmed.Length - 1)
{
throw new ArgumentException($"Idunn RUDP endpoint must be host:port, got '{value}'.", nameof(value));
}
var host = trimmed[..separator];
var portText = trimmed[(separator + 1)..];
if (!int.TryParse(portText, CultureInfo.InvariantCulture, out var port))
{
throw new ArgumentException($"Idunn RUDP endpoint port must be numeric, got '{value}'.", nameof(value));
}
var addresses = Dns.GetHostAddresses(host);
var address = addresses.FirstOrDefault(static candidate => candidate.AddressFamily == AddressFamily.InterNetwork)
?? throw new ArgumentException($"Idunn RUDP endpoint host did not resolve to IPv4, got '{value}'.", nameof(value));
return new IPEndPoint(address, port);
}
}

View file

@ -1,19 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<AssemblyName>Mimir.EveDashboard</AssemblyName>
<RootNamespace>Mimir.EveDashboard</RootNamespace>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="$(CultLibRoot)\src\GameCult.Caching.MessagePack\GameCult.Caching.MessagePack.csproj" />
<ProjectReference Include="$(CultLibRoot)\src\GameCult.Mesh\GameCult.Mesh.csproj" />
<ProjectReference Include="$(CultLibRoot)\src\GameCult.Networking\GameCult.Networking.csproj" />
<ProjectReference Include="..\Mimir.Runtime\Mimir.Runtime.csproj" />
</ItemGroup>
</Project>

File diff suppressed because it is too large Load diff

View file

@ -157,153 +157,6 @@ public sealed record MimirProgramOutputDocument(
[property: Key(5)] string PublisherRoute, [property: Key(5)] string PublisherRoute,
[property: Key(6)] bool DiagnosticOnly); [property: Key(6)] bool DiagnosticOnly);
[CultDocument("mimir.eve_operator_surface", "mimir.eve_operator_surface.v1")]
[MessagePackObject]
public sealed record MimirEveOperatorSurfaceDocument(
[property: Key(0)]
[property: CultName]
string SurfaceId,
[property: Key(1)] string UpdatedAtUtc,
[property: Key(2)] string SceneId,
[property: Key(3)] bool CanPreviewProgram,
[property: Key(4)] bool CanEditComposition,
[property: Key(5)] bool CanSelectSources,
[property: Key(6)] bool CanShowStats,
[property: Key(7)] string[] CommandTopics);
[CultDocument("mimir.eve_dashboard_manifest", "mimir.eve_dashboard_manifest.v1")]
[MessagePackObject]
public sealed record MimirEveDashboardManifestDocument(
[property: Key(0)]
[property: CultName]
string ProviderId,
[property: Key(1)] string Title,
[property: Key(2)] string Description,
[property: Key(3)] string Version,
[property: Key(4)] string Endpoint,
[property: Key(5)] string[] Capabilities,
[property: Key(6)] bool UsesCultMesh,
[property: Key(7)] string Transport);
[CultDocument("mimir.eve_dashboard_state", "mimir.eve_dashboard_state.v1")]
[MessagePackObject]
public sealed record MimirEveDashboardStateDocument(
[property: Key(0)]
[property: CultName]
string ProviderId,
[property: Key(1)] string Title,
[property: Key(2)] long Version,
[property: Key(3)] string UpdatedAtUtc,
[property: Key(4)] string SelectedNodeId,
[property: Key(5)] string LutPreset,
[property: Key(6)] MimirEveDashboardNodeSnapshot[] Nodes,
[property: Key(7)] MimirEveDashboardSurfaceSnapshot? Surface);
[MessagePackObject]
public sealed record MimirEveDashboardNodeSnapshot(
[property: Key(0)] string Id,
[property: Key(1)] string Label,
[property: Key(2)] string Kind,
[property: Key(3)] bool Visible,
[property: Key(4)] double X,
[property: Key(5)] double Y,
[property: Key(6)] double Z,
[property: Key(7)] double Rotation,
[property: Key(8)] double Scale,
[property: Key(9)] double Width,
[property: Key(10)] double Height,
[property: Key(11)] string Health,
[property: Key(12)] string? ProviderId,
[property: Key(13)] string? Command,
[property: Key(14)] string? Endpoint);
[MessagePackObject]
public sealed record MimirEveDashboardSurfaceSnapshot(
[property: Key(0)] string Schema,
[property: Key(1)] string Id,
[property: Key(2)] string Title,
[property: Key(3)] MimirEveDashboardUiElementSnapshot Root,
[property: Key(4)] MimirEveDashboardSurfaceAssetSnapshot[] Assets);
[MessagePackObject]
public sealed record MimirEveDashboardSurfaceAssetSnapshot(
[property: Key(0)] string Id,
[property: Key(1)] string Kind,
[property: Key(2)] string Uri);
[MessagePackObject]
public sealed record MimirEveDashboardUiElementSnapshot(
[property: Key(0)] string Id,
[property: Key(1)] string Kind,
[property: Key(2)] string? Role,
[property: Key(3)] string? Text,
[property: Key(4)] string? AssetRef,
[property: Key(5)] string? AssetUri,
[property: Key(6)] string? BindNodeId,
[property: Key(7)] string? CommandId,
[property: Key(8)] MimirEveDashboardUiLayoutSnapshot? Layout,
[property: Key(9)] MimirEveDashboardUiStyleSnapshot? Style,
[property: Key(10)] MimirEveDashboardUiMetricSnapshot? Metric,
[property: Key(11)] MimirEveDashboardUiElementSnapshot[] Children,
[property: Key(12)] MimirEveDashboardUiBindingSnapshot? Binding = null);
[MessagePackObject]
public sealed record MimirEveDashboardUiBindingSnapshot(
[property: Key(0)] string DocumentSchema,
[property: Key(1)] string DocumentId,
[property: Key(2)] string Path,
[property: Key(3)] string ValueKind,
[property: Key(4)] string Access,
[property: Key(5)] string Authority,
[property: Key(6)] string? CommandId);
[MessagePackObject]
public sealed record MimirEveDashboardUiLayoutSnapshot(
[property: Key(0)] string Direction,
[property: Key(1)] double? Width,
[property: Key(2)] double? Height,
[property: Key(3)] double? Grow,
[property: Key(4)] double? Gap,
[property: Key(5)] double? Padding,
[property: Key(6)] string? Overflow,
[property: Key(7)] double? MinWidth = null,
[property: Key(8)] double? MinHeight = null,
[property: Key(9)] double? PreferredWidth = null,
[property: Key(10)] double? PreferredHeight = null,
[property: Key(11)] double? Priority = null,
[property: Key(12)] string? Density = null,
[property: Key(13)] string? ViewportMode = null);
[MessagePackObject]
public sealed record MimirEveDashboardUiStyleSnapshot(
[property: Key(0)] string Variant,
[property: Key(1)] string? Tone);
[MessagePackObject]
public sealed record MimirEveDashboardUiMetricSnapshot(
[property: Key(0)] string Label,
[property: Key(1)] double Value,
[property: Key(2)] string Tone);
[CultDocument("mimir.eve_dashboard_command", "mimir.eve_dashboard_command.v1")]
[MessagePackObject]
public sealed record MimirEveDashboardCommandDocument(
[property: Key(0)]
[property: CultName]
string CommandId,
[property: Key(1)] string DeviceId,
[property: Key(2)] string ClientId,
[property: Key(3)] string ProviderId,
[property: Key(4)] string Type,
[property: Key(5)] string NodeId,
[property: Key(6)] double? X,
[property: Key(7)] double? Y,
[property: Key(8)] double? Rotation,
[property: Key(9)] double? Scale,
[property: Key(10)] bool? Visible,
[property: Key(11)] long Sequence,
[property: Key(12)] long DeviceTimestampNs);
[CultDocument("mimir.eve_sensor_observation", "mimir.eve_sensor_observation.v1")] [CultDocument("mimir.eve_sensor_observation", "mimir.eve_sensor_observation.v1")]
[MessagePackObject] [MessagePackObject]
public sealed record MimirEveSensorObservationDocument( public sealed record MimirEveSensorObservationDocument(
@ -482,29 +335,6 @@ public static class MimirCultMeshContractFactory
publication.SitePublisherRoute, publication.SitePublisherRoute,
publication.DiagnosticOnly); publication.DiagnosticOnly);
public static MimirEveOperatorSurfaceDocument CreateOperatorSurface(
string surfaceId,
string sceneId,
MimirProgramControlSurface surface,
DateTimeOffset? updatedAt = null) =>
new(
surfaceId,
(updatedAt ?? DateTimeOffset.UtcNow).ToString("O"),
sceneId,
surface.CanPreviewProgram,
surface.CanEditComposition,
surface.CanSelectSources,
surface.CanShowStats,
CommandTopics:
[
"mimir.program.scene.select",
"mimir.program.layer.transform",
"mimir.program.layer.crop",
"mimir.program.layer.chroma_key",
"mimir.program.source.subscribe",
"mimir.program.output.publish"
]);
private static MimirBioacousticDecoderConfigurationSnapshot Snapshot(MimirBioacousticDecoderConfiguration configuration) => private static MimirBioacousticDecoderConfigurationSnapshot Snapshot(MimirBioacousticDecoderConfiguration configuration) =>
new( new(
configuration.Id, configuration.Id,

View file

@ -20,8 +20,6 @@ public enum MimirProgramAudioPublicationKind
public enum MimirProgramControlPublicationKind public enum MimirProgramControlPublicationKind
{ {
EveGui,
EveTui,
CultMeshCommandSurface CultMeshCommandSurface
} }
@ -70,25 +68,9 @@ public static class MimirProgramPublicationConfigurations
public static IReadOnlyList<MimirProgramControlSurface> OperatorSurfaces { get; } = public static IReadOnlyList<MimirProgramControlSurface> OperatorSurfaces { get; } =
[ [
new(
"eve-gui-compositor",
"Primary Eve GUI composition surface: preview, source selection, transforms, crop/key controls, audio stem controls, and stream health.",
MimirProgramControlPublicationKind.EveGui,
CanPreviewProgram: true,
CanEditComposition: true,
CanSelectSources: true,
CanShowStats: true),
new(
"eve-tui-operator",
"Compact Eve TUI operator surface for source health, live stats, scene selection, and emergency command paths.",
MimirProgramControlPublicationKind.EveTui,
CanPreviewProgram: false,
CanEditComposition: false,
CanSelectSources: true,
CanShowStats: true),
new( new(
"cultmesh-command-surface", "cultmesh-command-surface",
"Typed CultMesh command surface consumed by Eve lowerers and trusted devices; Mimir remains the scene authority.", "Typed CultMesh command surface consumed by trusted devices; Mimir remains the scene authority.",
MimirProgramControlPublicationKind.CultMeshCommandSurface, MimirProgramControlPublicationKind.CultMeshCommandSurface,
CanPreviewProgram: false, CanPreviewProgram: false,
CanEditComposition: true, CanEditComposition: true,

View file

@ -652,16 +652,6 @@ accepted_design:
receiver. The old `CultMesh.StartNodeAsync`/`CultMesh.ConnectClient` receiver. The old `CultMesh.StartNodeAsync`/`CultMesh.ConnectClient`
node/client path and raw sender/receiver host-port config are no longer node/client path and raw sender/receiver host-port config are no longer
transport authorities for this media lane. transport authorities for this media lane.
- `src/Mimir.EveDashboard` is archived as a fail-closed tombstone. It no
longer opens a TCP listener, serves `/eve/deck`, accepts WebSocket commands,
or publishes Idunn health. Its replacement must publish provider
advertisement, retained state, command boundary, and transport profile as
typed CultMesh/Odin documents; product/debug render surfaces remain client
lowerings.
- `src/Mimir.EveBrowserReference` serves a static Eve/browser lowering and
can publish its own `idunn.daemon_health` record over
`cultnet.transport.rudp.v0`. It is a renderer/reference health witness, not
an authority over Mimir program state or Idunn keepalive decisions.
- `research/perfect-machine-study-2026-05-23/` preserves the architecture - `research/perfect-machine-study-2026-05-23/` preserves the architecture
rumination, optimization ledger, research references, source/hotspot audits, rumination, optimization ledger, research references, source/hotspot audits,
Fensalir/native boundary maps, benchmark plan, calibration-session spec, Fensalir/native boundary maps, benchmark plan, calibration-session spec,
@ -690,13 +680,7 @@ active_subgoals:
replaces them. replaces them.
- Launch Raven's CultMesh sender directly on Raven because SSH to Raven over - Launch Raven's CultMesh sender directly on Raven because SSH to Raven over
WireGuard timed out during the initial relay deployment pass. WireGuard timed out during the initial relay deployment pass.
- Keep Mimir Eve dashboard and browser reference aligned with Odin/Idunn - `Mimir.EveSensorReceiver`, `Mimir.VerseRecorder`, the
doctrine: Odin accepts Verse/service visibility, Idunn owns keepalive, and
Mimir surfaces publish typed CultMesh/Odin state rather than private
watchdog loops, product status probes, or socket brokers.
- Rebuild `src/Mimir.EveDashboard` only after daemon truth, provider catalogs,
command boundaries, and typed program document paths use explicit CultMesh/
Odin documents. `Mimir.EveSensorReceiver`, `Mimir.VerseRecorder`, the
Nightwing typed witness publisher, and `start-nightwing-move-tracking.ps1` Nightwing typed witness publisher, and `start-nightwing-move-tracking.ps1`
are archived so the old witness subscription lane cannot retake transport are archived so the old witness subscription lane cannot retake transport
authority. `src/Mimir.CultMeshMedia` has completed this cut for the authority. `src/Mimir.CultMeshMedia` has completed this cut for the