Add Mimir Aquarium app scaffold

This commit is contained in:
Meta Vens 2026-05-21 13:17:02 +01:00
commit 075e8c3ddf
17 changed files with 305 additions and 4 deletions

4
.gitignore vendored
View file

@ -11,6 +11,10 @@ tools/obs-spout2-plugin/
tools/src/
target/
native/**/target/
bin/
obj/
**/bin/
**/obj/
/.voidbot/birth/
/.voidbot/logs/
/.voidbot/**/*.lock

6
Mimir.slnx Normal file
View file

@ -0,0 +1,6 @@
<Solution>
<Folder Name="/src/">
<Project Path="src/Mimir.App/Mimir.App.csproj" />
<Project Path="src/Mimir.Runtime/Mimir.Runtime.csproj" />
</Folder>
</Solution>

7
NuGet.config Normal file
View file

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<add key="AquariumLocal" value="E:\Projects\Aquarium-Engine\packages" />
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" />
</packageSources>
</configuration>

View file

@ -7,6 +7,26 @@
"expiredSamplePolicy": "offline-calibration-only",
"storageLayout": "single-native-rolling-buffer-with-typed-views"
},
"viableStreamApp": {
"owner": "Aquarium",
"solution": "Mimir.slnx",
"appProject": "src/Mimir.App/Mimir.App.csproj",
"runtimeProject": "src/Mimir.Runtime/Mimir.Runtime.csproj",
"windowingRenderingLibrary": "Aquarium.Engine",
"d3d12Bridge": "Aquarium.Engine",
"defaultReservoirSeconds": 5.0,
"runtimeState": "in-memory-localcast-runtime",
"uiAuthority": "debug-settings-output-management",
"ingest": {
"videoFeeds": "local-or-networked-producers",
"audioFeeds": "local-or-networked-producers",
"networkFeedsAre": "producers-not-clock-authorities"
},
"outputs": {
"video": "synchronized-program-video",
"audio": "separately-controllable-synchronized-stems"
}
},
"authorities": {
"configurationCalibrationLaunchStatus": "Mimir",
"denseVisualFusion": "Aquarium",

View file

@ -13,6 +13,14 @@ The native reservoir is now one time-ordered rolling buffer with typed views.
The previous shared-edge typed rings proved the retention invariant; the new
shape removes independent per-kind storage from the live foundation.
The near-term viable stream app is Aquarium-hosted: it ingests local or
networked video/audio feeds as producers into the same five-second in-memory
`LocalcastRuntime`, exposes debug/settings/output control through Aquarium UI,
and emits synchronized OBS program video plus separately controllable audio
stems. Mimir now owns its own C# solution while referencing Aquarium Engine as
the windowing/rendering library and D3D12 bridge. `docs/viable-stream-app.md`
is the current cut line for that app.
The v1 OBS bridge is now gated by a bounded smoke-test witness ledger. No
plugin/native receiver expansion is justified until
`calibration/runs/obs-v1-smoke-ledger.json` records sender capture, SRT receive,
@ -35,6 +43,13 @@ latency, and confidence.
behavior, and inherited memories. VoidBot is only the mouth/scheduler.
- Perfect Machine contract:
- `docs/perfect-machine.md` defines the target native machine: one five-second spatiotemporal reservoir, Aquarium-owned dense visual fusion/material/brush rendering, Faust-owned hot audio DSP, OBS-owned broadcast controls, and Mimir-owned calibration/config/status.
- `docs/viable-stream-app.md` defines the first useful application cut:
Aquarium hosts the in-memory five-second runtime, owns runtime UI and
output management, treats local/networked feeds as producers, and emits
synchronized OBS program surfaces.
- `Mimir.slnx` contains `src/Mimir.App` and `src/Mimir.Runtime`.
`Mimir.App` launches `AquariumHost` from Aquarium Engine, and
`Mimir.Runtime` supplies the Aquarium client runtime factory.
- `config/perfect-machine.example.json` declares the contract shape for six cameras, six microphones, reservoir typed views, native authorities, outputs, and the demotion of bridge scripts to tooling.
- `native/reservoir` is the first native Rust crate. It implements one
shared-edge five-second rolling buffer with typed views and proves that

94
docs/viable-stream-app.md Normal file
View file

@ -0,0 +1,94 @@
# Viable Stream App
## Objective
Build the smallest live Mimir application that can ingest multiple local or
networked video and audio feeds, hold a short synchronized window in memory,
and emit coherent OBS-facing program outputs while Aquarium provides the
operator UI.
The default reservoir horizon is five seconds. That delay is not decoration. It
is the budget that lets late network packets, independent capture clocks,
audio/video alignment, and debug controls converge before OBS sees the result.
## Current Mechanism
Today the repo has two partial machines:
- FFmpeg/SRT bridge scripts can capture sender video/audio and expose stable OBS
Media Source endpoints.
- The native `LocalcastRuntime` reservoir ABI can hold typed live sample handles,
and Aquarium can consume native render packets for GPU fusion.
Those parts are useful, but they are not yet the viable app. The viable app is
the join: ingest workers append live sample handles into one Aquarium-hosted
runtime window, Aquarium renders video from that window, Faust/native audio
builds synchronized stems from the same window, and OBS receives only the
program outputs.
```mermaid
flowchart TD
A["local camera workers"] --> R["Aquarium-hosted 5s LocalcastRuntime"]
B["network video ingest workers"] --> R
C["local mic/loopback workers"] --> R
D["network audio ingest workers"] --> R
R --> U["Aquarium debug/settings/output UI"]
R --> V["Aquarium GPU video composition"]
R --> F["Faust/native audio alignment + stems"]
V --> O["OBS video output"]
F --> P["OBS audio stem outputs"]
```
## Invariants
- One in-memory reservoir owns the live timing edge and expiry.
- Aquarium owns the running app, UI, debug state, settings changes, output
management, GPU fusion/rendering, and Spout/video publication.
- Faust/native DSP owns audio alignment, voice separation, suppression, stems,
and spatial bed generation.
- OBS receives synchronized program surfaces. Raw unsynchronized feeds may be
diagnostic inputs, not broadcast truth.
- Networked feeds are producers into the same reservoir. They do not own clocks,
private buffers, or a parallel synchronization model.
- Missing feeds become explicit absence or silent/blank placeholders inside the
program model. Stale samples do not get stretched into looking current.
- Python may inspect, calibrate, and test. It does not run the live hot path.
## First Viable Cut
The first cut should be deliberately boring:
1. Build Mimir as its own C# solution in this repo.
2. Reference Aquarium Engine as the windowing/rendering library and D3D12
bridge; do not copy engine host code into Mimir.
3. Start an Aquarium `LocalCastRuntime` app with a five-second native runtime.
4. Create one producer per configured feed using native source-id hashing.
5. Ingest video feeds as timed camera-frame or render-packet handles.
6. Ingest audio feeds as timed `LocalcastAudioBlockDescriptor` handles.
7. Expose Aquarium UI readouts for reservoir edge, window start, per-kind
counts, feed health, delay estimate, output status, and current settings.
8. Render one synchronized program video output from the reservoir.
9. Emit separately controllable OBS audio stems from the same reservoir window.
10. Keep the existing FFmpeg/SRT scripts as edge utilities for network capture
and OBS compatibility, not as the stream authority.
## Repo Shape
`Mimir.slnx` owns the Mimir app surface:
- `src/Mimir.App` launches Aquarium Engine and points it at the Mimir runtime
assembly.
- `src/Mimir.Runtime` implements the Aquarium client runtime factory and uses
Aquarium's LocalCast runtime until Mimir-specific ingest workers replace the
remaining diagnostic source.
## Cut Line
Do not add a new registry, bridge daemon, JSON live store, OBS plugin, or
per-feed buffer manager to make this happen. If the app needs one of those,
first move the missing authority into `LocalcastRuntime`, Aquarium, or
Faust/native DSP.
The app exists to make the live system coherent, not to preserve every
deadline-era surface. Deadline fossils may be used as diagnostic witnesses. They
do not get promoted because they still twitch.

View file

@ -55,6 +55,17 @@ not pretend to own GPU image memory or Faust audio buffers. Edge JSON may
declare schema or export diagnostics; live process/network data is typed
CultNet documents.
## Viable Stream App
`docs/viable-stream-app.md` defines the near-term app target while full native
capture work continues. Aquarium hosts the running Mimir app, keeps the default
five-second reservoir in memory, exposes debug/settings/output controls, and
emits synchronized OBS program video plus separately controllable audio stems.
Local and networked video/audio feeds are producers into that one runtime
window; they do not own clocks or private synchronization buffers. Mimir owns
`Mimir.slnx`; `src/Mimir.App` references Aquarium Engine as the host/windowing
and D3D12 bridge, while `src/Mimir.Runtime` provides the Aquarium client runtime.
```mermaid
flowchart TD
A["config/localcast.json"] --> B["sender-start.ps1"]

View file

@ -37,6 +37,13 @@ Get-Content .\state\evidence.jsonl -Tail 8
- The live synchronized program path is being rebuilt. The Python/OpenGL
deadline Spout bridge has been deleted.
- `docs/native-rebuild-plan.md` is the current cut plan.
- `docs/viable-stream-app.md` is the near-term stream target: Aquarium hosts
the in-memory five-second runtime and operator UI, local/networked feeds append
as producers, and OBS receives synchronized program outputs rather than raw
feed sync.
- `Mimir.slnx` is the repo-owned C# app surface. `src/Mimir.App` references
Aquarium Engine as the windowing/rendering/D3D12 host and loads
`src/Mimir.Runtime` as the Aquarium client runtime assembly.
- The reservoir is now one native time-ordered rolling buffer with typed
indexes/views. The previous typed-ring crate is gone.
- Native reservoir samples carry provenance flags; diagnostic/fallback samples

View file

@ -0,0 +1,17 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<AssemblyName>Mimir.App</AssemblyName>
<RootNamespace>Mimir.App</RootNamespace>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Mimir.Runtime\Mimir.Runtime.csproj" />
<ProjectReference Include="..\..\..\Aquarium-Engine\src\Aquarium.Engine\Aquarium.Engine.csproj" />
</ItemGroup>
</Project>

26
src/Mimir.App/Program.cs Normal file
View file

@ -0,0 +1,26 @@
using Aquarium.Engine;
var hostArgs = args.ToList();
if (!HasOption(hostArgs, "--client-assembly") && !HasOption(hostArgs, "--live-assembly"))
{
hostArgs.Add("--client-assembly");
hostArgs.Add(ResolveRuntimeAssemblyPath());
}
return AquariumHost.Run(hostArgs.ToArray());
static bool HasOption(IReadOnlyList<string> args, string option)
{
return args.Any(arg => string.Equals(arg, option, StringComparison.OrdinalIgnoreCase));
}
static string ResolveRuntimeAssemblyPath()
{
var configured = Environment.GetEnvironmentVariable("MIMIR_RUNTIME_ASSEMBLY");
if (!string.IsNullOrWhiteSpace(configured))
{
return configured;
}
return Path.Combine(AppContext.BaseDirectory, "Mimir.Runtime.dll");
}

View file

@ -0,0 +1,17 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<AssemblyName>Mimir.Runtime</AssemblyName>
<RootNamespace>Mimir.Runtime</RootNamespace>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\Aquarium-Engine\src\Aquarium.Engine.Contracts\Aquarium.Engine.Contracts.csproj" />
<ProjectReference Include="..\..\..\Aquarium-Engine\src\Aquarium.LocalCast\Aquarium.LocalCast.csproj" />
</ItemGroup>
</Project>

View file

@ -0,0 +1,12 @@
using Aquarium.Engine;
using Aquarium.LocalCast;
namespace Mimir.Runtime;
public sealed class MimirRuntimeFactory : IAquariumRuntimeFactory
{
public IAquariumRuntime Create(AquariumRuntimeOptions options)
{
return new LocalCastRuntime(options);
}
}

View file

@ -95,3 +95,5 @@
{"ts": "2026-05-19T13:16:35+00:00", "type": "decision", "status": "ok", "note": "Mimir Face voice corrected from dry technical steward to mythological being first: Norse well-memory, lost eye, severed voice, roots/ravens/runes, trusted witnesses, and contempt for false coherence now belong to docs/mimir-face.md plus .voidbot Face state. Job jurisdiction remains realtime field stewardship, but personal values now explain why the work matters."}
{"ts": "2026-05-21T09:18:48+00:00", "type": "decision", "status": "ok", "note": "V1 OBS receiver expansion is now gated on calibration/runs/obs-v1-smoke-ledger.json: scripts/obs_smoke_test.py and localcast.obs_smoke require sender_capture, srt_receive, and obs_present evidence per endpoint, with drift, end-to-end latency, and confidence before plugin/native receiver work proceeds."}
{"ts": "2026-05-21T09:26:02+00:00", "type": "implementation", "status": "ok", "note": "Repository content now presents the public project as Mimir: prose/docs/config examples/OBS source names/default Spout sender/Faust metadata/tool descriptions migrated from LocalCastBridge branding, while lowercase localcast package/schema/ABI contracts remain until a deliberate code rename cut."}
{"ts": "2026-05-21T12:09:51+00:00", "type": "decision", "status": "ok", "note": "Viable stream app cut pinned: Aquarium hosts one five-second in-memory LocalcastRuntime, local/networked feeds append as producers, Aquarium owns runtime UI/debug/settings/output management, Faust/native audio builds synchronized stems, and OBS receives program outputs rather than raw feed sync."}
{"ts": "2026-05-21T12:14:26+00:00", "type": "implementation", "status": "ok", "note": "Mimir now owns a C# app solution: Mimir.slnx contains src/Mimir.App, which launches AquariumHost from Aquarium Engine, and src/Mimir.Runtime, which provides the Aquarium runtime factory over Aquarium.LocalCast. Aquarium Engine remains the windowing/rendering/D3D12 library reference."}

View file

@ -18,7 +18,12 @@ objective:
current: >
Carve out Python live producers, the OpenGL Spout sink, JSON live stores,
and duplicated reservoir windows. Rebuild the live path around one native
rolling buffer with typed indexes/views and CultNet typed documents.
rolling buffer with typed indexes/views and CultNet typed documents. The
immediate viable stream app is Aquarium-hosted: local and networked
video/audio feeds append into one five-second in-memory runtime, Aquarium
provides debug/settings/output UI, and OBS receives synchronized program
outputs. Mimir owns its own C# solution and references Aquarium Engine as
the windowing/rendering library and D3D12 bridge.
constraints:
- Sender is Windows.
@ -31,6 +36,8 @@ constraints:
- No OpenGL production Spout sink.
- Edge JSON is schema/diagnostic only; live network/process data is typed
CultNet documents.
- Networked feeds are producers into the live runtime, not independent clock
or synchronization authorities.
invariants:
- OBS composition authority stays in OBS.
@ -44,6 +51,8 @@ invariants:
retention.
- Aquarium owns production Spout publication; Faust/native DSP owns hot audio.
- Python and JSON may diagnose or test the machine, not run it.
- Aquarium owns the viable stream app UI for runtime debugging, settings, and
output management.
accepted_design:
- `docs/mimir-face.md`, `.voidbot/voice/identity.json`, and
@ -55,6 +64,13 @@ accepted_design:
deadline Python/OpenGL bridge as a foundation and defines the next coherent
rebuild target.
- `docs/perfect-machine.md` and `config/perfect-machine.example.json` define the native target: one five-second spatiotemporal reservoir, Aquarium GPU fusion/rendering, Faust DSP, OBS output controls, and Mimir tooling.
- `docs/viable-stream-app.md` defines the near-term application cut: Aquarium
hosts the five-second in-memory `LocalcastRuntime`, local/networked feeds
append as producers, Aquarium exposes debug/settings/output controls, and
OBS receives synchronized program video plus controllable audio stems.
- `Mimir.slnx` owns the app surface. `src/Mimir.App` launches Aquarium Engine
as the host/window/D3D12 library, and `src/Mimir.Runtime` provides Mimir's
Aquarium client runtime factory.
- `native/reservoir` is the first native Rust crate. It now uses one
time-ordered rolling buffer with typed views, so retention has exactly one
owner while Aquarium/Faust still get kind-scoped queries.
@ -93,6 +109,9 @@ accepted_design:
- Persistence follows the active repo pattern: map, scratch, evidence, branches, handoff, system map, implementation plan, state helpers.
active_subgoals:
- Build the viable Aquarium-hosted stream app around one five-second
`LocalcastRuntime`: ingest local/networked video and audio producers,
expose runtime UI, and emit synchronized OBS program outputs.
- Bind Aquarium/Faust to the rolling-buffer `LocalcastRuntime` and
`LocalcastProducer` C ABI described by
`native/reservoir/include/localcast_reservoir.h`.

View file

@ -6,6 +6,10 @@ No active scratch subgoal.
## Notes
- Viable stream app contract promoted to docs/map/evidence: Aquarium hosts one
five-second in-memory runtime, local/networked feeds append as producers, and
OBS receives synchronized program outputs.
- Mimir app scaffold now lives in `Mimir.slnx` with `src/Mimir.App` and
`src/Mimir.Runtime`; Aquarium Engine remains an external project reference.
- Keep scratch short and disposable.
- Promote only durable lessons into `state/evidence.jsonl` or the map.

View file

@ -0,0 +1,35 @@
from pathlib import Path
import unittest
ROOT = Path(__file__).resolve().parents[1]
class MimirAppContractTests(unittest.TestCase):
def test_solution_owns_mimir_app_and_runtime_projects(self):
solution = (ROOT / "Mimir.slnx").read_text(encoding="utf-8")
self.assertIn("src/Mimir.App/Mimir.App.csproj", solution)
self.assertIn("src/Mimir.Runtime/Mimir.Runtime.csproj", solution)
def test_app_references_aquarium_engine_as_host_library(self):
app_project = (ROOT / "src" / "Mimir.App" / "Mimir.App.csproj").read_text(encoding="utf-8-sig")
program = (ROOT / "src" / "Mimir.App" / "Program.cs").read_text(encoding="utf-8")
self.assertIn("Aquarium.Engine.csproj", app_project)
self.assertIn("Mimir.Runtime.csproj", app_project)
self.assertIn("AquariumHost.Run", program)
self.assertIn("--client-assembly", program)
def test_runtime_references_localcast_runtime_client(self):
runtime_project = (ROOT / "src" / "Mimir.Runtime" / "Mimir.Runtime.csproj").read_text(encoding="utf-8-sig")
factory = (ROOT / "src" / "Mimir.Runtime" / "MimirRuntimeFactory.cs").read_text(encoding="utf-8")
self.assertIn("Aquarium.Engine.Contracts.csproj", runtime_project)
self.assertIn("Aquarium.LocalCast.csproj", runtime_project)
self.assertIn("IAquariumRuntimeFactory", factory)
self.assertIn("new LocalCastRuntime(options)", factory)
if __name__ == "__main__":
unittest.main()

View file

@ -8,16 +8,21 @@ ROOT = Path(__file__).resolve().parents[1]
class PerfectMachineContractTests(unittest.TestCase):
def test_contract_declares_native_reservoir_authorities(self):
data = json.loads((ROOT / "config" / "perfect-machine.example.json").read_text(encoding="utf-8"))
data = json.loads((ROOT / "config" / "perfect-machine.example.json").read_text(encoding="utf-8-sig"))
self.assertEqual("gamecult.localcast.perfect_machine.v1", data["schema"])
self.assertEqual(5.0, data["reservoir"]["durationSeconds"])
self.assertEqual("Aquarium", data["viableStreamApp"]["owner"])
self.assertEqual("Mimir.slnx", data["viableStreamApp"]["solution"])
self.assertEqual("Aquarium.Engine", data["viableStreamApp"]["windowingRenderingLibrary"])
self.assertEqual("in-memory-localcast-runtime", data["viableStreamApp"]["runtimeState"])
self.assertEqual("producers-not-clock-authorities", data["viableStreamApp"]["ingest"]["networkFeedsAre"])
self.assertEqual("Aquarium", data["authorities"]["denseVisualFusion"])
self.assertEqual("Faust", data["authorities"]["hotAudioDsp"])
self.assertEqual("Aquarium", data["outputs"]["video"]["owner"])
def test_contract_covers_six_cameras_six_mics_and_required_sample_views(self):
data = json.loads((ROOT / "config" / "perfect-machine.example.json").read_text(encoding="utf-8"))
data = json.loads((ROOT / "config" / "perfect-machine.example.json").read_text(encoding="utf-8-sig"))
self.assertEqual(6, len(data["visualSensors"]))
self.assertEqual(6, len(data["audioSensors"]))