From e9d13797b15fc39b97cde236cefd008c81847848 Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Sat, 13 Jun 2026 16:31:45 +0100 Subject: [PATCH] Add Aetheria Eve runtime presenter --- .voidbot/state/aetheria.cc | Bin 37532 -> 38193 bytes .../ServerShared/Behaviors/Behaviors.cs | 4 +- .../ServerShared/Behaviors/Thruster.cs | 4 +- Assets/Scripts/ServerShared/ItemData.cs | 8 +- Packages/manifest.json | 2 + .../README.md | 15 +++ .../Runtime/AetheriaEveSurfacePresenter.cs | 104 ++++++++++++++++++ .../GameCult.Aetheria.EveRuntime.asmdef | 18 +++ .../package.json | 19 ++++ Packages/packages-lock.json | 17 +++ docs/aetheria-perfect-machine-map.md | 14 +++ 11 files changed, 197 insertions(+), 8 deletions(-) create mode 100644 Packages/org.gamecult.aetheria.eve-runtime/README.md create mode 100644 Packages/org.gamecult.aetheria.eve-runtime/Runtime/AetheriaEveSurfacePresenter.cs create mode 100644 Packages/org.gamecult.aetheria.eve-runtime/Runtime/GameCult.Aetheria.EveRuntime.asmdef create mode 100644 Packages/org.gamecult.aetheria.eve-runtime/package.json diff --git a/.voidbot/state/aetheria.cc b/.voidbot/state/aetheria.cc index 472ec14a4baa96036bc5260e16794d6f81cb46dc..fe3ace4cee27ff5df6841f787eeca1523b502171 100644 GIT binary patch delta 1746 zcma)7NpBoQ6ef-pFk6s`i5!+ZArUT_p2bTDK?+ug!j6f@K{7c|u6{jTOm|gNRXyWD zLZb+>NSsh#IUvQxBFZ7rT;OEo3POS#M*anyI3bR_>h2j$fFPuiX6AJ*-}{!=Uv5p@ zzAxl;&CtqA>c#HFmwJ-L~WII<5WIs$?9#v1|Rki=)QRQS_d0_JCiO>ERJU_{g z_OC9X4N(gQ%=gg;20E2i#0aaeK~t%yEi5D&4N8j|0;TDn>IffoMRg_ZINL!msSX%N z15EnBQwzqj2;q#9mI-Oj7e}69R8_4vum`^2Rw&74bq_d;BI9lGh>3W2uNfjp>_S#M z{lpRMU}cCWVZ6;+2(hBmOm@0Id(c-rjR}~6K&u$CC*M2L2O+H@@UN85z=m3-t&qMk z$2!(mq`Ii7tFEx2BGfI-sCU`Q*J_m1Bt4~GECNeujTW}&ZCjLv!2i1M*s7!gtTW5Q zSosJY>}wGS&Rlaq9~%k-G2@|-2&o~4&icXZgDOPJoeIhHH^nK&_QF;QD|-Y+&C%UZRhQdDjb9apEorn21tZRchrE zQHErMqK6^7oUmfm86a|yNq7~duAAdv6!@4pRwVC1nh;w)H58}Df>ajPk?xv_V_~f` z5}ASSZ0E4D+UJM*zrBqc*4Zjy#(*KlBcG@zNW-ScN-KqvmPd}u80VPeRw+2uS2X79d>pI+A(48A+|^Wz%m>XS>Zu-+z7Oe@44ZHd!ba$4O26H+AnE+6yd7 z(YuLmj!2FLO)m^CUHJZ?S6^Nxd!T;`3Q&bOWd>=wjEx&Qn$$>`4arVcL=kkAZkNd4 z$sXG{oPEF!W|#SMgFn}QnduiT)&3q@8cFb`YMG0iBD_&aU8XB#YCL+l*%s2@rUFUq zZ=009b#HfbNV+rISQ7I|^sj@jpHIF#sTyO|aXr$Hd&HFHhLHaq&eRPkh=my%`5&{z z`-Xzx?6zdTZDe~JwdwMW(O6j9w5&zuY!063txP_?mA8E}&q_JJ1|NO$$kfzmL-y1D w+2M<1p?Hz(T-g~`7U#;B&4KLtRW+(E=GFSOOQTA@bMW)EJ5$5biMh4E0CA0euK)l5 delta 1183 zcmZ8h&ubGw6xOsB>u-vsDu@p$^kBO7SK4Sql&V-k8`@MPx5@4#J9c+wnVC(DJw#MQ z+mo!|MM^=dP&~Bct|t*Zi5Cs{C-_JBW|Ixo%QE|B=DqKI-}h!`EA?hGwf#PIV0_}@ z#06_QlPSL3I~aXQttFA^u{a`n9(E&Bab$n*>C|J=9O<>sOg+6ywM!)x?Zgfvl; zM$$k)$h8P}DTNwQ9pVH>nk4mz#zba64NAoK6AYtmaoS=ukS@ZcsnM3t z5MB}?cZjTmBt}?Mi^nO-jH9fER;gST#4?-CtsN3GFy{vzvxy0{{@e(4C{22$s7f^Q z1&S$T#pg+d-2qR?+dIv5ag7!8L~$gYXBMAfV< z!fn)UftJKV*4Z`VZ@6)wVPk-$y-Uwt;^AsvE$AB z)T^_{K2qacZqs&&ndZ5B-wu|FdBdTT>kbeSgW#|R>J$s}pXCz{0JR{VXpc}biHt+n z^c@8A(MEkJTDOlzL1tfcibC_b^J_3#Sh+ONc^W3Ci|=r_vF(NC#>!%Ev+vR2{{HS} x^u6a~@ Entity.VisibilitySources[this]) + if (!Entity.VisibilitySources.TryGetValue(this, out var existingVisibility) || vis > existingVisibility) Entity.VisibilitySources[this] = vis; return true; } return false; } -} \ No newline at end of file +} diff --git a/Assets/Scripts/ServerShared/ItemData.cs b/Assets/Scripts/ServerShared/ItemData.cs index badfe1ed3..58c45933a 100644 --- a/Assets/Scripts/ServerShared/ItemData.cs +++ b/Assets/Scripts/ServerShared/ItemData.cs @@ -561,7 +561,7 @@ public class HardpointData : ITintInspector x => ColorMath.HsvToRgb(float3(frac((float)(int)x/hardpointTypes.Length + .25f), 1, 1))); } - return _tintColors.ContainsRuntimeCatalogKey(type) ? _tintColors[type] : _tintColors[HardpointType.Hull]; + return _tintColors.TryGetValue(type, out var color) ? color : _tintColors[HardpointType.Hull]; } private static Dictionary _tintColors; @@ -597,7 +597,7 @@ public class PerformanceStat public Dictionary GetScaleModifiers(Entity entity) { - if(!ScaleModifiers.ContainsRuntimeCatalogKey(entity)) + if(!ScaleModifiers.ContainsKey(entity)) ScaleModifiers[entity] = new Dictionary(); return ScaleModifiers[entity]; @@ -605,7 +605,7 @@ public class PerformanceStat public Dictionary GetConstantModifiers(Entity entity) { - if(!ConstantModifiers.ContainsRuntimeCatalogKey(entity)) + if(!ConstantModifiers.ContainsKey(entity)) ConstantModifiers[entity] = new Dictionary(); return ConstantModifiers[entity]; @@ -629,4 +629,4 @@ public class PersonalityAttribute : DatabaseEntry, INamedEntry get => Name; set => Name = value; } -} \ No newline at end of file +} diff --git a/Packages/manifest.json b/Packages/manifest.json index 258d9f2f0..6748b1d86 100644 --- a/Packages/manifest.json +++ b/Packages/manifest.json @@ -1,5 +1,7 @@ { "dependencies": { + "org.gamecult.aetheria.eve-runtime": "file:org.gamecult.aetheria.eve-runtime", + "org.gamecult.aetheria.state": "file:org.gamecult.aetheria.state", "org.gamecult.eve.surface": "file:org.gamecult.eve.surface", "org.gamecult.eve.unity-uitoolkit": "file:org.gamecult.eve.unity-uitoolkit", "com.unity.2d.sprite": "1.0.0", diff --git a/Packages/org.gamecult.aetheria.eve-runtime/README.md b/Packages/org.gamecult.aetheria.eve-runtime/README.md new file mode 100644 index 000000000..2448ec1df --- /dev/null +++ b/Packages/org.gamecult.aetheria.eve-runtime/README.md @@ -0,0 +1,15 @@ +# Aetheria Eve Runtime + +This package mounts provider-owned Eve surfaces from Aetheria's typed +CultCache state into Unity UI Toolkit. + +`AetheriaEveSurfacePresenter` is a projection bridge: + +- it reads `gamecult.eve.surface.v1` from `GameData/aetheria-world.cc`; +- it lowers the retained tree through `org.gamecult.eve.unity-uitoolkit`; +- it does not accept, mutate, or persist Aetheria state. + +Commands are surfaced as `gamecult.eve.command.v1` requests and currently log a +capability gap until the CultMesh command bridge is wired. That gap is visible +on purpose; renderer-local command effects would be the wrong organ wearing a +clean shirt. diff --git a/Packages/org.gamecult.aetheria.eve-runtime/Runtime/AetheriaEveSurfacePresenter.cs b/Packages/org.gamecult.aetheria.eve-runtime/Runtime/AetheriaEveSurfacePresenter.cs new file mode 100644 index 000000000..85dbed9f0 --- /dev/null +++ b/Packages/org.gamecult.aetheria.eve-runtime/Runtime/AetheriaEveSurfacePresenter.cs @@ -0,0 +1,104 @@ +using System; +using System.IO; +using System.Linq; +using GameCult.Aetheria.State.Unity; +using GameCult.Eve.Surface; +using GameCult.Eve.UnityUIToolkit; +using UnityEngine; +using UnityEngine.UIElements; + +#nullable enable + +namespace GameCult.Aetheria.EveRuntime +{ + [DisallowMultipleComponent] + [RequireComponent(typeof(UIDocument))] + public sealed class AetheriaEveSurfacePresenter : MonoBehaviour + { + [SerializeField] + private string surfaceId = "aetheria.catalog.operator"; + + [SerializeField] + private string stateFilePathOverride = ""; + + [SerializeField] + private bool mountOnEnable = true; + + private UIDocument? _document; + + public string SurfaceId + { + get => surfaceId; + set => surfaceId = value ?? ""; + } + + public string StateFilePathOverride + { + get => stateFilePathOverride; + set => stateFilePathOverride = value ?? ""; + } + + public void Mount() + { + var document = ResolveDocument(); + var root = document.rootVisualElement; + root.Clear(); + + var statePath = ResolveStateFilePath(); + if (!File.Exists(statePath)) + { + root.Add(BuildError($"Aetheria state file not found: {statePath}")); + return; + } + + var surface = AetheriaRuntimeCatalogStore.ReadEveSurfaces(statePath) + .FirstOrDefault(candidate => string.Equals(candidate.Surface.Id, surfaceId, StringComparison.Ordinal)); + if (surface == null) + { + root.Add(BuildError($"Eve surface not found: {surfaceId}")); + return; + } + + var lowerer = new EveUiToolkitSurfaceLowerer(); + root.Add(lowerer.Lower(surface, EmitCommand)); + } + + private void OnEnable() + { + if (mountOnEnable) + Mount(); + } + + private UIDocument ResolveDocument() + { + if (_document == null) + _document = GetComponent(); + return _document; + } + + private string ResolveStateFilePath() + { + if (!string.IsNullOrWhiteSpace(stateFilePathOverride)) + return Path.GetFullPath(stateFilePathOverride); + + var gameDataDirectory = new DirectoryInfo(Path.Combine(Application.dataPath, "..", "GameData")); + return AetheriaRuntimeStateBoundary.GetStateFilePath(gameDataDirectory); + } + + private static VisualElement BuildError(string message) + { + var container = new VisualElement(); + container.AddToClassList("aetheria-eve-runtime-error"); + var label = new Label(message); + label.AddToClassList("aetheria-eve-runtime-error-label"); + container.Add(label); + return container; + } + + private static void EmitCommand(EveSurfaceCommandRequest request) + { + Debug.LogWarning( + $"Eve command emitted without a CultMesh command bridge: {request.ProviderId}/{request.SurfaceId}/{request.Command}"); + } + } +} diff --git a/Packages/org.gamecult.aetheria.eve-runtime/Runtime/GameCult.Aetheria.EveRuntime.asmdef b/Packages/org.gamecult.aetheria.eve-runtime/Runtime/GameCult.Aetheria.EveRuntime.asmdef new file mode 100644 index 000000000..7f2ac2fa2 --- /dev/null +++ b/Packages/org.gamecult.aetheria.eve-runtime/Runtime/GameCult.Aetheria.EveRuntime.asmdef @@ -0,0 +1,18 @@ +{ + "name": "GameCult.Aetheria.EveRuntime", + "rootNamespace": "GameCult.Aetheria.EveRuntime", + "references": [ + "GameCult.Aetheria.State.Unity", + "GameCult.Eve.Surface", + "GameCult.Eve.UnityUIToolkit" + ], + "includePlatforms": [], + "excludePlatforms": [], + "allowUnsafeCode": false, + "overrideReferences": false, + "precompiledReferences": [], + "autoReferenced": true, + "defineConstraints": [], + "versionDefines": [], + "noEngineReferences": false +} diff --git a/Packages/org.gamecult.aetheria.eve-runtime/package.json b/Packages/org.gamecult.aetheria.eve-runtime/package.json new file mode 100644 index 000000000..8d1479157 --- /dev/null +++ b/Packages/org.gamecult.aetheria.eve-runtime/package.json @@ -0,0 +1,19 @@ +{ + "name": "org.gamecult.aetheria.eve-runtime", + "displayName": "Aetheria Eve Runtime", + "version": "0.1.0", + "unity": "2021.3", + "description": "Aetheria runtime bridge from typed CultCache Eve surfaces to Unity UI Toolkit projection.", + "dependencies": { + "org.gamecult.aetheria.state": "0.1.0", + "org.gamecult.eve.unity-uitoolkit": "0.1.0" + }, + "keywords": [ + "Aetheria", + "Eve", + "CultCache", + "UI Toolkit" + ], + "license": "MPL-2.0", + "category": "Scripting" +} diff --git a/Packages/packages-lock.json b/Packages/packages-lock.json index aaa45b08e..3a9b1c2c6 100644 --- a/Packages/packages-lock.json +++ b/Packages/packages-lock.json @@ -1,5 +1,22 @@ { "dependencies": { + "org.gamecult.aetheria.eve-runtime": { + "version": "file:org.gamecult.aetheria.eve-runtime", + "depth": 0, + "source": "embedded", + "dependencies": { + "org.gamecult.aetheria.state": "0.1.0", + "org.gamecult.eve.unity-uitoolkit": "0.1.0" + } + }, + "org.gamecult.aetheria.state": { + "version": "file:org.gamecult.aetheria.state", + "depth": 0, + "source": "embedded", + "dependencies": { + "org.gamecult.eve.surface": "0.1.0" + } + }, "org.gamecult.eve.surface": { "version": "file:org.gamecult.eve.surface", "depth": 0, diff --git a/docs/aetheria-perfect-machine-map.md b/docs/aetheria-perfect-machine-map.md index 87a38c837..3df9c5ce3 100644 --- a/docs/aetheria-perfect-machine-map.md +++ b/docs/aetheria-perfect-machine-map.md @@ -194,6 +194,10 @@ legacy catalog cache, and legacy UI paths should be migration-only or deleted. DTOs. This gives Unity the same provider-owned retained tree that the SDK-style state client reads, without JSON fixtures, HTTP dashboard summaries, or renderer-owned state. +- `AetheriaEveSurfacePresenter` is the first runtime UI Toolkit consumer of + those typed surface documents. It owns mounting only: state file resolution, + surface lookup, lowering, and visible capability gaps for commands. Provider + command acceptance still belongs to the future CultMesh command bridge. - `GameData/aetheria-world.cc` is now materialized from the importer as the project-local typed state file for the checked-in catalog. The importer stores relative provenance in the state document, not machine-local absolute paths. @@ -281,6 +285,11 @@ legacy catalog cache, and legacy UI paths should be migration-only or deleted. the first carries no-engine `gamecult.eve.surface.v1` contract DTOs, and the second lowers those retained trees into UI Toolkit `VisualElement` trees. These packages are renderer boundaries, not UI truth owners. + `org.gamecult.aetheria.eve-runtime` now adds the Aetheria-specific + `UIDocument` presenter that reads provider-owned Eve surfaces from + `GameData/aetheria-world.cc` and mounts them through the lowerer. Its command + path currently reports the missing CultMesh command bridge instead of + accepting renderer-local command effects. ## Invariants @@ -540,6 +549,11 @@ First Aetheria surfaces to publish: - Done: teach the embedded Unity state package to read `gamecult.eve.surface` records from the `.cc` store into that shared contract. + - Done: add `org.gamecult.aetheria.eve-runtime` with a `UIDocument` + presenter that mounts typed Eve surfaces from `GameData/aetheria-world.cc` + through UI Toolkit without giving the renderer state authority. + - Wire the presenter into a Unity scene/prefab for the first runtime surface + and replace a concrete uGUI screen. - Move the staged packages into the Eve repo once its worktree is clean, then import them back into Aetheria from Eve instead of carrying a local copy. - Replace the old IMGUI DB inspector first, because it is closest to state