From bd84882d2bf841d302cb984483a63cd43d689ca4 Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Tue, 16 Jun 2026 21:23:05 +0100 Subject: [PATCH] Move sector zone details onto Eve surfaces --- Aetheria.State.Verify/Program.cs | 62 +++++ Assets/Scripts/UI/Menu/SectorRenderer.cs | 299 +++++++++++++++++++---- docs/aetheria-perfect-machine-map.md | 15 +- 3 files changed, 321 insertions(+), 55 deletions(-) diff --git a/Aetheria.State.Verify/Program.cs b/Aetheria.State.Verify/Program.cs index 4b38c141f..83f36c2f9 100644 --- a/Aetheria.State.Verify/Program.cs +++ b/Aetheria.State.Verify/Program.cs @@ -33,6 +33,7 @@ RequireNoRendererLocalConsole(root); RequireNoRendererLocalDebugPanels(root); RequireMainMenuSettingsCommit(root); RequireMainMenuSettingsShellUsesEveSurface(root); +RequireSectorMapZoneDetailsUseEveSurface(root); RequirePlayerSettingsEveSurface(root); RequireMainMenuContinueRunState(root); RequirePropertiesPanelReadOnlyInspector(root); @@ -498,6 +499,7 @@ Console.WriteLine("Renderer-local console authority: deleted; UI commands flow t Console.WriteLine("Renderer-local debug panels: obsolete uGUI field tester authority is deleted"); Console.WriteLine("Main-menu settings authority: player name, gameplay, and graphics settings return through typed player-settings commits"); Console.WriteLine("Main-menu settings shell: settings/input/audio subpages lower through Eve UI Toolkit surfaces instead of PropertiesPanel buttons"); +Console.WriteLine("Sector-map zone details shell: zone inspection lowers through an Eve UI Toolkit surface instead of PropertiesPanel rows"); Console.WriteLine("Main-menu Continue authority: Continue selects typed run state instead of a null button"); Console.WriteLine("PropertiesPanel inspector authority: dead generic reflection inspector path is deleted"); Console.WriteLine("Typed behavior metadata authority: live heat/mining/thermotoggle payload kinds stay owned by package metadata"); @@ -2472,6 +2474,66 @@ static void RequireMainMenuSettingsShellUsesEveSurface(string root) } } +static void RequireSectorMapZoneDetailsUseEveSurface(string root) +{ + var sectorRendererPath = Path.Combine(root, "Assets", "Scripts", "UI", "Menu", "SectorRenderer.cs"); + if (!File.Exists(sectorRendererPath)) + { + throw new InvalidOperationException("Cannot verify sector-map zone details shell; SectorRenderer.cs is missing."); + } + + var source = File.ReadAllText(sectorRendererPath); + var requiredSymbols = new[] + { + "ZoneDetailsSurfaceId", + "CloseZoneDetailsCommand", + "RenderZoneDetailsSurface(", + "HandleZoneDetailsSurfaceCommand(", + "HideZoneDetailsSurface(", + "ResolveZoneDetailsSurfaceDocument(", + "BuildZoneDetailsSurfaceDefinition(", + "new EveUiToolkitSurfaceLowerer()" + }; + + var missingSymbols = requiredSymbols + .Where(symbol => !source.Contains(symbol, StringComparison.Ordinal)) + .ToArray(); + + if (missingSymbols.Length > 0) + { + throw new InvalidOperationException( + "SectorRenderer no longer lowers zone details through an Eve surface shell: " + + string.Join(", ", missingSymbols)); + } + + var forbiddenSymbols = new[] + { + "Properties.Clear();", + "Properties.Title.text = zone.Name;", + "Properties.AddProperty(\"Owner\"", + "Properties.AddProperty(\"Mass\"", + "Properties.AddProperty(\"Radius\"", + "Properties.AddProperty(\"Planets\"", + "Properties.AddProperty(\"Asteroid Belts\"", + "Properties.AddProperty(\"Gas Giants\"", + "Properties.AddProperty(\"Stars\"", + "Properties.AddProperty(\"Stations\"", + "Properties.AddProperty(\"Turrets\"", + "Properties.AddProperty(\"Ships\"" + }; + + var hits = forbiddenSymbols + .Where(symbol => source.Contains(symbol, StringComparison.Ordinal)) + .ToArray(); + + if (hits.Length > 0) + { + throw new InvalidOperationException( + "SectorRenderer still owns zone details through the old PropertiesPanel path: " + + string.Join(", ", hits)); + } +} + static void RequirePlayerSettingsEveSurface(string root) { var projectorPath = Path.Combine(root, "Aetheria.State", "AetheriaPlayerSettingsSurfaceProjector.cs"); diff --git a/Assets/Scripts/UI/Menu/SectorRenderer.cs b/Assets/Scripts/UI/Menu/SectorRenderer.cs index e40d48075..4afcb6aad 100644 --- a/Assets/Scripts/UI/Menu/SectorRenderer.cs +++ b/Assets/Scripts/UI/Menu/SectorRenderer.cs @@ -2,16 +2,26 @@ using System; using System.Collections; using System.Collections.Generic; using System.Linq; +using GameCult.Eve.Surface; +using GameCult.Eve.UnityUIToolkit; using UniRx; using UnityEngine; using UnityEngine.EventSystems; using UnityEngine.UI; +using UnityEngine.UIElements; using Unity.Mathematics; using static Unity.Mathematics.math; using float2 = Unity.Mathematics.float2; public class SectorRenderer : MonoBehaviour, IBeginDragHandler, IDragHandler, IScrollHandler { + private const string ZoneDetailsSurfaceType = "surface-state"; + private const string ZoneDetailsSurfaceSchema = "gamecult.eve.surface.v1"; + private const string ZoneDetailsSurfaceProviderId = "aetheria"; + private const string ZoneDetailsSurfaceProviderKind = "sector.map"; + private const string ZoneDetailsSurfaceId = "aetheria.sector_map.zone_details"; + private const string CloseZoneDetailsCommand = "aetheria.sector_map.zone_details.close"; + public ClickRaycaster Raycaster; public PropertiesPanel Properties; public Canvas Canvas; @@ -34,79 +44,32 @@ public class SectorRenderer : MonoBehaviour, IBeginDragHandler, IDragHandler, IS private Transform _sectorBackgroundTransform; private Transform _sectorCameraTransform; - private Image _outputImage; + private UnityEngine.UI.Image _outputImage; private RenderTexture _outputTexture; private int2 _size; private bool _init; private float _aspectRatio; private float _sectorBackgroundDepth; private float _sectorCameraDepth; + private UIDocument _zoneDetailsSurfaceDocument; private float2 _position = float2(0.5f); private float _viewSize = .5f; void Start() { - _outputImage = GetComponent(); + _outputImage = GetComponent(); _sectorBackgroundTransform = SectorBackgroundRenderer.transform; _sectorBackgroundDepth = _sectorBackgroundTransform.position.z; _sectorCameraTransform = SectorCamera.transform; _sectorCameraDepth = _sectorCameraTransform.position.z; Raycaster.OnClickMiss += data => { - Properties.gameObject.SetActive(false); + HideZoneDetailsSurface(); }; Map.ZoneClicked.Subscribe(zone => { - Properties.gameObject.SetActive(true); - Properties.Clear(); - Properties.Title.text = zone.Name; - Properties.AddProperty("Owner", () => zone.Owner?.Name ?? "None"); - var otherFactions = zone.Factions - .Where(f => !f.HasSameKey(zone.Owner)) - .Select(f=>f.Name).ToArray(); - if (otherFactions.Length > 0) - Properties.AddProperty("Factions Present", () => string.Join(", ", otherFactions)); - var density = saturate(ActionGameManager.CurrentGalaxy.Background.CloudDensity(zone.Position)/2); - var radius = GameManager.Settings.ZoneSettings.ZoneRadius.Evaluate(density); - var mass = GameManager.Settings.ZoneSettings.ZoneMass.Evaluate(density); - Properties.AddProperty("Mass", () => ActionGameManager.RuntimePlayerSettings.Format(mass)); - Properties.AddProperty("Radius", () => ActionGameManager.RuntimePlayerSettings.Format(radius)); - if (zone.Contents == null) - { - Properties.AddProperty(() => "Has not been visited"); - } - else - { - var runtimeZone = zone.Contents; - - var planetCount = runtimeZone.PlanetInstances.Values.Count(body => !(body is GasGiant)).ToString(); - Properties.AddProperty("Planets", () => planetCount); - - var beltCount = runtimeZone.AsteroidBelts.Count.ToString(); - Properties.AddProperty("Asteroid Belts", () => beltCount); - - var giantCount = runtimeZone.PlanetInstances.Values.Count(body => body is GasGiant && !(body is Sun)).ToString(); - Properties.AddProperty("Gas Giants", () => giantCount); - - var starCount = runtimeZone.PlanetInstances.Values.Count(body => body is Sun).ToString(); - Properties.AddProperty("Stars", () => starCount); - - var stationCount = runtimeZone.Entities - .Count(entity => HasHullType(entity, HullType.Station)) - .ToString(); - Properties.AddProperty("Stations", () => stationCount); - - var turretCount = runtimeZone.Entities - .Count(entity => HasHullType(entity, HullType.Turret)) - .ToString(); - Properties.AddProperty("Turrets", () => turretCount); - - var shipCount = runtimeZone.Entities - .Count(entity => HasHullType(entity, HullType.Ship)) - .ToString(); - Properties.AddProperty("Ships", () => shipCount); - } + RenderZoneDetailsSurface(zone); }); // PathAnimationButton.onClick.AddListener(() => @@ -122,6 +85,228 @@ public class SectorRenderer : MonoBehaviour, IBeginDragHandler, IDragHandler, IS string.Equals(typedItem.HullType, hullType.ToString(), StringComparison.Ordinal); } + private void RenderZoneDetailsSurface(GalaxyZone zone) + { + Properties.gameObject.SetActive(false); + + var document = ResolveZoneDetailsSurfaceDocument(); + document.gameObject.SetActive(true); + + var root = document.rootVisualElement; + root.Clear(); + root.style.flexGrow = 1; + root.style.position = Position.Absolute; + root.style.left = 0; + root.style.top = 0; + root.style.right = 0; + root.style.bottom = 0; + root.style.alignItems = Align.FlexEnd; + root.style.justifyContent = Justify.FlexStart; + root.style.paddingTop = 24; + root.style.paddingRight = 24; + root.pickingMode = PickingMode.Ignore; + + var shell = new VisualElement(); + shell.style.width = 360; + shell.style.maxWidth = 420; + shell.style.backgroundColor = new Color(0.08f, 0.1f, 0.14f, 0.94f); + shell.style.borderTopLeftRadius = 8; + shell.style.borderTopRightRadius = 8; + shell.style.borderBottomLeftRadius = 8; + shell.style.borderBottomRightRadius = 8; + shell.style.paddingLeft = 18; + shell.style.paddingRight = 18; + shell.style.paddingTop = 18; + shell.style.paddingBottom = 18; + shell.style.borderLeftWidth = 1; + shell.style.borderRightWidth = 1; + shell.style.borderTopWidth = 1; + shell.style.borderBottomWidth = 1; + shell.style.borderLeftColor = new Color(0.3f, 0.47f, 0.71f, 0.8f); + shell.style.borderRightColor = new Color(0.3f, 0.47f, 0.71f, 0.8f); + shell.style.borderTopColor = new Color(0.3f, 0.47f, 0.71f, 0.8f); + shell.style.borderBottomColor = new Color(0.3f, 0.47f, 0.71f, 0.8f); + shell.pickingMode = PickingMode.Position; + root.Add(shell); + + var lowerer = new EveUiToolkitSurfaceLowerer(); + shell.Add(lowerer.Lower(BuildZoneDetailsSurfaceDefinition(zone), HandleZoneDetailsSurfaceCommand)); + } + + private void HandleZoneDetailsSurfaceCommand(EveSurfaceCommandRequest request) + { + if (string.Equals(request.Command, CloseZoneDetailsCommand, StringComparison.Ordinal)) + { + HideZoneDetailsSurface(); + return; + } + + Debug.LogWarning($"Unknown sector-map zone details command: {request.Command}"); + } + + private void HideZoneDetailsSurface() + { + Properties.gameObject.SetActive(false); + if (_zoneDetailsSurfaceDocument == null) + return; + + _zoneDetailsSurfaceDocument.rootVisualElement.Clear(); + _zoneDetailsSurfaceDocument.gameObject.SetActive(false); + } + + private UIDocument ResolveZoneDetailsSurfaceDocument() + { + if (_zoneDetailsSurfaceDocument != null) + return _zoneDetailsSurfaceDocument; + + var host = new GameObject("Aetheria Sector Zone Details Surface"); + host.transform.SetParent(transform, false); + var document = host.AddComponent(); + document.sortingOrder = 1000; + host.SetActive(false); + _zoneDetailsSurfaceDocument = document; + return document; + } + + private EveSurfaceDocument BuildZoneDetailsSurfaceDefinition(GalaxyZone zone) + { + var density = saturate(ActionGameManager.CurrentGalaxy.Background.CloudDensity(zone.Position) / 2); + var radius = GameManager.Settings.ZoneSettings.ZoneRadius.Evaluate(density); + var mass = GameManager.Settings.ZoneSettings.ZoneMass.Evaluate(density); + var otherFactions = zone.Factions + .Where(faction => !faction.HasSameKey(zone.Owner)) + .Select(faction => faction.Name) + .Where(name => !string.IsNullOrWhiteSpace(name)) + .ToArray(); + + var children = new List + { + Card( + $"{ZoneDetailsSurfaceId}.card", + zone.Name, + Metric($"{ZoneDetailsSurfaceId}.owner", "Owner", zone.Owner?.Name ?? "None"), + Metric($"{ZoneDetailsSurfaceId}.mass", "Mass", ActionGameManager.RuntimePlayerSettings.Format(mass)), + Metric($"{ZoneDetailsSurfaceId}.radius", "Radius", ActionGameManager.RuntimePlayerSettings.Format(radius))) + }; + + if (otherFactions.Length > 0) + { + children.Add(Text( + $"{ZoneDetailsSurfaceId}.factions", + $"Factions Present: {string.Join(", ", otherFactions)}")); + } + + if (zone.Contents == null) + { + children.Add(Text( + $"{ZoneDetailsSurfaceId}.unvisited", + "Has not been visited.")); + } + else + { + var runtimeZone = zone.Contents; + children.Add(Card( + $"{ZoneDetailsSurfaceId}.contents", + "Contents", + Metric( + $"{ZoneDetailsSurfaceId}.planets", + "Planets", + runtimeZone.PlanetInstances.Values.Count(body => !(body is GasGiant)).ToString()), + Metric( + $"{ZoneDetailsSurfaceId}.belts", + "Asteroid Belts", + runtimeZone.AsteroidBelts.Count.ToString()), + Metric( + $"{ZoneDetailsSurfaceId}.giants", + "Gas Giants", + runtimeZone.PlanetInstances.Values.Count(body => body is GasGiant && !(body is Sun)).ToString()), + Metric( + $"{ZoneDetailsSurfaceId}.stars", + "Stars", + runtimeZone.PlanetInstances.Values.Count(body => body is Sun).ToString()), + Metric( + $"{ZoneDetailsSurfaceId}.stations", + "Stations", + runtimeZone.Entities.Count(entity => HasHullType(entity, HullType.Station)).ToString()), + Metric( + $"{ZoneDetailsSurfaceId}.turrets", + "Turrets", + runtimeZone.Entities.Count(entity => HasHullType(entity, HullType.Turret)).ToString()), + Metric( + $"{ZoneDetailsSurfaceId}.ships", + "Ships", + runtimeZone.Entities.Count(entity => HasHullType(entity, HullType.Ship)).ToString()))); + } + + children.Add(ButtonRow( + $"{ZoneDetailsSurfaceId}.actions", + Button($"{ZoneDetailsSurfaceId}.close", "Close", CloseZoneDetailsCommand))); + + return new EveSurfaceDocument( + ZoneDetailsSurfaceType, + ZoneDetailsSurfaceSchema, + ZoneDetailsSurfaceProviderId, + ZoneDetailsSurfaceProviderKind, + zone.Name, + version: 1, + DateTime.UtcNow.ToString("O"), + new EveSurfaceTree( + ZoneDetailsSurfaceId, + Node( + $"{ZoneDetailsSurfaceId}.root", + "surface", + Array.Empty<(string Key, string Value)>(), + children.ToArray()), + Array.Empty()), + new[] + { + new EveCommandTemplate(CloseZoneDetailsCommand, "Close", "unity-uitoolkit") + }); + } + + private static EveSurfaceComponent Card( + string id, + string title, + params EveSurfaceComponent[] children) + { + return Node(id, "card", new[] { ("title", title) }, children); + } + + private static EveSurfaceComponent Metric(string id, string label, string value) + { + return Node(id, "metric", new[] { ("label", label), ("value", value) }); + } + + private static EveSurfaceComponent Text(string id, string value) + { + return Node(id, "text", new[] { ("value", value) }); + } + + private static EveSurfaceComponent Button(string id, string label, string command) + { + return Node(id, "control.button", new[] { ("label", label), ("command", command) }); + } + + private static EveSurfaceComponent ButtonRow( + string id, + params EveSurfaceComponent[] children) + { + return Node(id, "row", Array.Empty<(string Key, string Value)>(), children); + } + + private static EveSurfaceComponent Node( + string id, + string kind, + IEnumerable<(string Key, string Value)> props, + params EveSurfaceComponent[] children) + { + return new EveSurfaceComponent( + id, + kind, + props.ToDictionary(prop => prop.Key, prop => prop.Value, StringComparer.Ordinal), + children ?? Array.Empty()); + } + // private IEnumerator AnimatePath() // { // var pathZones = ActionGameManager.CurrentSector.ExitPath; @@ -164,6 +349,7 @@ public class SectorRenderer : MonoBehaviour, IBeginDragHandler, IDragHandler, IS private void OnDisable() { + HideZoneDetailsSurface(); if (_outputTexture != null) { _outputTexture.Release(); @@ -172,6 +358,15 @@ public class SectorRenderer : MonoBehaviour, IBeginDragHandler, IDragHandler, IS SectorCamera.gameObject.SetActive(false); } + private void OnDestroy() + { + if (_zoneDetailsSurfaceDocument != null) + { + Destroy(_zoneDetailsSurfaceDocument.gameObject); + _zoneDetailsSurfaceDocument = null; + } + } + void LateUpdate() { var size = int2(Screen.width, Screen.height); diff --git a/docs/aetheria-perfect-machine-map.md b/docs/aetheria-perfect-machine-map.md index b440c54e7..37d42dd83 100644 --- a/docs/aetheria-perfect-machine-map.md +++ b/docs/aetheria-perfect-machine-map.md @@ -115,7 +115,9 @@ legacy catalog cache, and legacy UI paths should be migration-only or deleted. to defaults. The main-menu settings navigation, input, and audio subpages now also lower through local Eve/UI Toolkit surfaces instead of repopulating the old `PropertiesPanel` button shell, even though typed rebinding controls and - typed audio controls are still future work. + typed audio controls are still future work. Sector-map zone inspection now + lowers through a local Eve/UI Toolkit surface as well instead of rebuilding + `PropertiesPanel` rows on each zone click. - The combat schematic HUD uses typed runtime catalog weapon facets for its static weapon icon strip; missing typed weapon facets no longer fall back to legacy `WeaponItemData`. Schematic weapon-row selection now uses typed @@ -1546,12 +1548,16 @@ First Aetheria surfaces to publish: owns those pages. Input rebinding itself remains on the runtime-owned remap screen until Eve grows typed rebinding controls, and audio still lacks a typed state surface. + - Done: replace the sector-map zone details panel with an Eve-owned UI + Toolkit surface. `SectorRenderer` still owns reveal/camera behavior, but + zone inspection no longer rebuilds `PropertiesPanel` rows when the player + clicks a zone. - Done: move the shared `org.gamecult.eve.surface` and `org.gamecult.eve.unity-uitoolkit` packages into the Eve repo and retarget Aetheria to import them from there instead of carrying local staged copies. - - Replace the old IMGUI DB inspector first, because it is closest to state - authority. + - Done: delete the old IMGUI DB inspector; it is no longer a blocker on the + live UI migration path. - Then replace runtime HUD/menu/inventory/map screens. 7. Purge @@ -1904,6 +1910,9 @@ First Aetheria surfaces to publish: - Main-menu settings navigation, input, and audio pages now lower through a shared UI Toolkit `UIDocument` host and local Eve surface documents instead of rebuilding those pages through `PropertiesPanel.AddButton(...)` calls. +- Sector-map zone inspection now lowers through a shared UI Toolkit + `UIDocument` host and a local Eve surface document instead of rebuilding + `PropertiesPanel.AddProperty(...)` rows on each zone click. - Keyboard display layout parsing is no longer JSON-backed: `InputDisplayLayout` builds its static ANSI-104 display projection from typed `InputLayout` rows/columns. The dead commented Ink `ToJson` write path and