diff --git a/notes/fresh-workspace-handoff.md b/notes/fresh-workspace-handoff.md index ae578d3..dcc2abc 100644 --- a/notes/fresh-workspace-handoff.md +++ b/notes/fresh-workspace-handoff.md @@ -67,6 +67,11 @@ Get-Content .\state\evidence.jsonl -Tail 8 picture-in-picture at layer 1. `MimirSceneEditorState` applies matching source placements once when those feeds appear, then inspector/drag edits own subsequent placement. +- The left Video Sources rail is a direct source selector, not a previous/next + navigator. It renders visible rows for live video feeds using CultUI + row-level visibility, with a Select action and placement summary per source. + Keep configured/offline source inventory as a future source-state owner; + the current rail lists live synced video feeds. - Live camera image buffers should use Fensalir-owned texture leases when they are rendering inputs: Mimir asks the engine broker for a keyed D3D12 Texture2D/fence lease, writes decoded frames into that texture, commits the diff --git a/src/Mimir.Runtime/MimirRuntime.cs b/src/Mimir.Runtime/MimirRuntime.cs index f78d79b..35ac2b0 100644 --- a/src/Mimir.Runtime/MimirRuntime.cs +++ b/src/Mimir.Runtime/MimirRuntime.cs @@ -33,6 +33,7 @@ public sealed class MimirRuntime : IAquariumRuntime, IAquariumRuntimeServicesRec private const float SpectrumFrustumMinimumNear = 0.01f; private const float SpectrumSplineTubePadding = 0.18f; private const int DefaultSpectrumSourceLaneCapacity = 8; + private const int VideoSourceListSlotCount = 12; private const int AudioMixerChannelSlotCount = 6; private const string SpectrumFieldResourceKey = "mimir:resource:spectrum:field-upload"; private const string SpectrumRampResourceKey = AquariumBuiltInFieldResources.BlackbodyRampResourceKey; @@ -858,20 +859,24 @@ public sealed class MimirRuntime : IAquariumRuntime, IAquariumRuntimeServicesRec private AquariumUiDocument CreateUi() { return new AquariumUiDocument() - .Surface("mimir.editor.surface", "Mimir Editor", 18.0f, 56.0f, 1220.0f, 660.0f, surface => + .Surface("mimir.editor.surface", "", 18.0f, 56.0f, 1220.0f, 660.0f, surface => { surface.Horizontal("mimir.editor.shell", shell => { shell.Pane("mimir.video-sources", "Video Sources", graph => { - graph.Options("video.source-select", "Feed", () => presentationControls.SelectedVideoIndex, SelectVideoLayer, VideoFeedOptions()); graph.Text("video.program-summary", DescribeProgramVideo, "caption", weight: 0.75f); - graph.Text("video.layer-list", sceneEditor.DescribeHierarchy, "mono", weight: 3.8f); - graph.Row("video.nav", nav => + graph.Text("video.source-list-title", "Program Sources", "strong", weight: 0.55f); + for (var slot = 0; slot < VideoSourceListSlotCount; slot++) { - nav.Button("video.previous", "Previous", sceneEditor.SelectPrevious); - nav.Button("video.next", "Next", sceneEditor.SelectNext); - }); + var sourceIndex = slot; + graph.Row($"video.source-row.{slot}", row => + { + row.Button($"video.source-row.{slot}.select", "Select", () => SelectVideoSlot(sourceIndex), weight: 0.55f); + row.Text($"video.source-row.{slot}.label", () => DescribeVideoSourceSlot(sourceIndex), "caption", weight: 1.45f); + }, weight: 0.75f, isVisible: () => VideoSourceSlotVisible(sourceIndex)); + } + graph.Text("video.empty", () => presentationControls.VideoFeeds.Count == 0 ? "No synced video streams yet." : "", "caption", weight: 0.65f); graph.Text("video.selected", sceneEditor.DescribeSelection, "caption", weight: 1.2f); }, weight: 0.85f); @@ -1089,6 +1094,37 @@ public sealed class MimirRuntime : IAquariumRuntime, IAquariumRuntimeServicesRec } } + private bool VideoSourceSlotVisible(int index) => + index >= 0 && index < presentationControls.VideoFeeds.Count; + + private void SelectVideoSlot(int index) + { + if (!VideoSourceSlotVisible(index)) + { + return; + } + + SelectVideoLayer(index); + } + + private string DescribeVideoSourceSlot(int index) + { + var feeds = presentationControls.VideoFeeds; + if (index < 0 || index >= feeds.Count) + { + return ""; + } + + var feed = feeds[index]; + var selected = presentationControls.SelectedVideo is { } current && + string.Equals(current.SourceId, feed.SourceId, StringComparison.Ordinal); + var placement = sceneEditor.PlacementForSource(feed.SourceId); + var frame = placement is { } value + ? $"{value.CenterX:0.00},{value.CenterY:0.00} {value.Width:0.00}x{value.Height:0.00}" + : "unplaced"; + return $"{(selected ? "* " : " ")}{feed.Layer + 1}. {feed.DisplayName} {frame}"; + } + private void SetSelectedVideoVisible(bool visible) { sceneEditor.SetSelectedVisible(visible); diff --git a/state/evidence.jsonl b/state/evidence.jsonl index 13ca3aa..7b9e935 100644 --- a/state/evidence.jsonl +++ b/state/evidence.jsonl @@ -292,6 +292,8 @@ {"ts":"2026-05-31T23:05:00+01:00","type":"implementation","status":"ok","note":"Corrected the Mimir editor layout ownership after the flat-canvas pass: the center remains a chrome-free program compositor canvas, Audio Mixer is docked underneath that center canvas, and the right rail is again a Unity-style Inspector for the selected video layer's properties. Video Sources on the left now selects and navigates sources rather than carrying the property surface.","files":["src/Mimir.Runtime/MimirRuntime.cs","notes/fresh-workspace-handoff.md"],"source":"dotnet build src/Mimir.App/Mimir.App.csproj; dotnet run --no-build --project src/Mimir.BufferSmoke/Mimir.BufferSmoke.csproj -- --scene-editor-smoke; Mimir.App --headless --headless-width 1280 --headless-height 720 --capture-frame artifacts/runtime/mimir-editor-unity-inspector-audio-bottom-headless.png"} {"ts":"2026-05-31T23:09:31+01:00","type":"implementation","status":"ok","note":"Audio Mixer is now authored as a horizontal CultUI channel-strip bank beneath the compositor preview instead of a selected-stem inspector form. Each strip reads a live presentation audio feed slot, owns M/S/gain controls for that feed, and displays compact sync/chirp response status from MimirSynchronizationHub while the right rail remains the selected video-layer Inspector. The compositor preview is weighted ahead of the mixer dock. This is UI/control-surface wiring; DSP neutralization and sample-perfect alignment remain runtime/audio authority, not inspector-owned truth.","files":["src/Mimir.Runtime/MimirRuntime.cs"],"source":"dotnet build src/Mimir.App/Mimir.App.csproj --no-restore; dotnet run --no-build --project src/Mimir.BufferSmoke/Mimir.BufferSmoke.csproj -- --presentation-control-smoke; dotnet run --no-build --project src/Mimir.BufferSmoke/Mimir.BufferSmoke.csproj -- --scene-editor-smoke; dotnet run --no-build --project src/Mimir.App/Mimir.App.csproj -- --headless --headless-width 1280 --headless-height 720 --capture-frame artifacts/runtime/mimir-editor-horizontal-audio-dock-current-headless.png"} {"ts":"2026-06-01T00:17:29+01:00","type":"implementation","status":"ok","note":"Mimir program surface placement now has a persistent CultCache config at config/mimir-program-surface.cc. The default mimir.program.default document seeds raven-display as the full-frame layer and kiyo-pro-rgb as a tasteful top-left picture-in-picture layer. MimirRuntime loads the configured CultCache path or the default file, and MimirSceneEditorState applies each matching source placement once when feeds appear so the config seeds the compositor without fighting later user edits.","files":["config/mimir-program-surface.cc","src/Mimir.Runtime/Synchronization/MimirProgramSurfaceConfig.cs","src/Mimir.Runtime/Synchronization/MimirSceneEditorState.cs","src/Mimir.Runtime/MimirRuntime.cs"],"source":"dotnet build src/Mimir.App/Mimir.App.csproj --no-restore; temp CultCache readback reported raven=0.5,0.5,1,1 layer=0 and kiyo=0.165,0.165,0.26,0.26 layer=1 selected=kiyo-pro-rgb"} +{"ts":"2026-06-01T00:42:00+01:00","type":"correction","status":"ok","note":"The Video Sources rail no longer uses Previous/Next source navigation. It now renders direct visible rows for live video feeds, each with a Select action and placement summary, so a source can be selected immediately before editing its inspector properties. Fensalir CultUI builder now exposes the existing IsVisible hook on groups, rows, text, and buttons so Mimir can hide unused fixed source slots instead of showing empty controls.","files":["src/Mimir.Runtime/MimirRuntime.cs","E:/Projects/Fensalir/src/Aquarium.Engine.Contracts/Ui/AquariumUi.cs"],"source":"dotnet build E:/Projects/Fensalir/src/Aquarium.Engine.Contracts/Aquarium.Engine.Contracts.csproj --no-restore; dotnet build src/Mimir.App/Mimir.App.csproj --no-restore; dotnet run --no-build --project src/Mimir.BufferSmoke/Mimir.BufferSmoke.csproj -- --scene-editor-smoke; Mimir.App headless capture artifacts/runtime/mimir-editor-direct-source-list-headless.png"} +{"ts":"2026-06-01T00:45:00+01:00","type":"correction","status":"ok","note":"The Mimir editor surface no longer draws an in-surface app title. Fensalir DirectWriteOverlay treats a blank CultUI surface title as no header and gives the reclaimed vertical space to content; Mimir's editor surface now passes an empty title because the OS/window titlebar owns app identity chrome.","files":["src/Mimir.Runtime/MimirRuntime.cs","E:/Projects/Fensalir/src/Aquarium.Engine/Render/DirectWriteOverlay.cs"],"source":"dotnet build E:/Projects/Fensalir/src/Aquarium.Engine/Aquarium.Engine.csproj --no-restore; dotnet build src/Mimir.App/Mimir.App.csproj --no-restore; dotnet run --no-build --project src/Mimir.BufferSmoke/Mimir.BufferSmoke.csproj -- --scene-editor-smoke; Mimir.App headless capture artifacts/runtime/mimir-editor-no-surface-title-headless.png"} {"ts": "2026-05-31T22:52:47+01:00", "type": "implementation", "status": "ok", "note": "Extended MimirCameraFrustumCalibrationSolver beyond LED strips: it now also consumes MimirFeatureTrackFieldCandidate plus scene-space TrackId anchors, so PS Move sphere histories, sparse Eye tracks, and Eve-local marker tracks enter the same Starfire-owned frustum authority. BufferSmoke --frustum-calibration-smoke now proves five LED-spline frustums and five feature-track frustums for ps3-eye-0, ps3-eye-1, kiyo-pro-rgb, leap-stereo-ir, and eve-camera with mean clip error 0.000000."} {"ts": "2026-05-31T22:55:38+01:00", "type": "implementation", "status": "ok", "note": "Added mimir.local_frustum_hint_state.v1 as the CultMesh document for local witness frustum solves. It carries producer node, calibration id, candidate key, marker set id, per-camera pose/frustum snapshots, residuals, point counts, and confidence, so Eve/Nightwing can publish compact solved hints while Starfire remains canonical global placement authority."} {"ts": "2026-05-31T23:14:03+01:00", "type": "implementation", "status": "ok", "note": "Changed music_keyed_move_chirp_sync.py visual pulses from square on/off flashes into sampled Move visual words. The default mimir.move_visual_contour_word.v1 uses identity color bias, attack/release envelope, symbol-dependent hue glide, tremolo articulation, emphasized Move lane, and an explicit visual_gesture_hz rate knob. Synthetic dry smoke wrote contour samples; a short non-dry Nightwing HID test at 80 Hz completed without writer failure. The camera-decodable rate still needs offline recovery scoring from captured frames."}