From e4b155576fa157a94a02492d24527ec5e8a027f1 Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Sun, 5 Jul 2026 16:59:06 +0100 Subject: [PATCH] Remove RTS menu shell shortcuts --- .../Client/aetheria-rts-contract.ts | 8 - Aetheria.Rts.Web/Client/app.ts | 725 ++---------------- .../Electron/aetheria-cultmesh.ts | 315 +------- .../aetheria-rts-generated-bindings.ts | 8 - Aetheria.Rts.Web/Electron/main.ts | 26 +- Aetheria.Rts.Web/Electron/preload.cjs | 2 - Aetheria.Rts.Web/package.json | 2 +- .../scripts/copy-electron-preload.mjs | 4 +- .../scripts/generate-rts-bindings.mjs | 16 - .../scripts/verify-stage7b-rts-client.ps1 | 50 +- ...ll.ps1 => verify-stage7c-electron-rts.ps1} | 38 +- Aetheria.Rts.Web/wwwroot/index.html | 46 +- Aetheria.Rts.Web/wwwroot/styles.css | 400 +--------- Aetheria.State.Daemon/Program.cs | 79 +- Aetheria.State.Verify/Program.cs | 14 +- .../README.md | 3 +- .../Runtime/AetheriaClientState.cs | 8 +- ...theriaRuntimeClientTargetSurfaceBuilder.cs | 2 +- .../AetheriaRuntimeDaemonRenderQueries.cs | 2 +- .../AetheriaRuntimeEveSurfaceCatalog.cs | 8 +- .../AetheriaRuntimeMainMenuSurfaceBuilder.cs | 28 +- .../Runtime/AetheriaRuntimeStateBoundary.cs | 7 +- .../Runtime/AetheriaRuntimeVerseClient.cs | 20 +- 23 files changed, 272 insertions(+), 1539 deletions(-) rename Aetheria.Rts.Web/scripts/{verify-stage7c-electron-shell.ps1 => verify-stage7c-electron-rts.ps1} (57%) diff --git a/Aetheria.Rts.Web/Client/aetheria-rts-contract.ts b/Aetheria.Rts.Web/Client/aetheria-rts-contract.ts index d1c4e5975..a33ac9b09 100644 --- a/Aetheria.Rts.Web/Client/aetheria-rts-contract.ts +++ b/Aetheria.Rts.Web/Client/aetheria-rts-contract.ts @@ -343,13 +343,6 @@ export type BodyView = { }; -export type AetheriaMenuSurfaceRequest = { - surfaceId?: string; - inGame?: boolean; - canOpenRuntimeInputScreen?: boolean; - panelOnly?: boolean; -}; - export type AetheriaEveSurfaceRequest = { surfaceId?: string; recordKey?: string; @@ -424,7 +417,6 @@ export type AetheriaRtsApi = { surfaceCatalogIndex(): Promise; eveSurface(request: AetheriaEveSurfaceRequest): Promise; submitEveCommand(request: AetheriaEveCommandRequest): Promise; - mainMenuSurface(request: AetheriaMenuSurfaceRequest): Promise; debugSurface(): Promise; watchDebugSurface(callback: (surface: AetheriaMenuSurfaceDocument) => void): () => void; windowControl(action: "minimize" | "maximize" | "close"): Promise; diff --git a/Aetheria.Rts.Web/Client/app.ts b/Aetheria.Rts.Web/Client/app.ts index fef4f4c15..cf0c44b26 100644 --- a/Aetheria.Rts.Web/Client/app.ts +++ b/Aetheria.Rts.Web/Client/app.ts @@ -5,21 +5,10 @@ import { type EveSurfaceComponent, } from "../node_modules/@gamecult/eve-browser-lowering/dist/index.js"; import type { - AetheriaRtsApi, - AetheriaMenuSurfaceDocument, AetheriaMenuSurfaceComponent, - AuthorityStatusDocument, - AetheriaRuntimeViewportFeedSnapshot, - BodyView, - DaemonHealthDocument, - GravityInfluence, - InventoryItem, - InventoryDocument, - SelectedObjectDocument, - StarbridgeSessionDocument, - ViewObject, + AetheriaMenuSurfaceDocument, + AetheriaRtsApi, Viewport, - ViewportResponse, } from "./aetheria-rts-contract.js"; export {}; @@ -30,90 +19,28 @@ declare global { } } -const canvas = requiredElement("#map"); +const host = requiredElement("#eve-surface-host"); const statusEl = requiredElement("#status"); -const zoomLabel = requiredElement("#zoom-label"); -const selectionTitle = requiredElement("#selection-title"); -const selectionDetails = requiredElement("#selection-details"); -const frameDetails = requiredElement("#frame-details"); -const starbridgeDetails = requiredElement("#starbridge-details"); -const runtimeSurfaceDetails = requiredElement("#runtime-surface-details"); -const controlledList = requiredElement("#controlled-list"); -const debugSurfaceHost = requiredElement("#debug-surface-host"); -const ctx = requiredContext(canvas); - -let zoom = 1; -let centerX = 0; -let centerY = 0; -let selectedEntityIndex = -1; -let latest: ViewportResponse | null = null; -let latestSelected: SelectedObjectDocument | null = null; -let latestInventory: InventoryDocument | null = null; -let latestDaemonHealth: DaemonHealthDocument | null = null; -let latestAuthority: AuthorityStatusDocument | null = null; -let latestStarbridge: StarbridgeSessionDocument | null = null; -let latestSurfaceIndex: Awaited> | null = null; -let latestOperationReceipt: string | null = null; -let latestReceivedAt = 0; -let latestPollMs = 0; -let refreshInFlight = false; -let viewportFeedUnsubscribe: (() => void) | null = null; -let eveSurfacePoll: number | null = null; let latestEveSurfaceKey = ""; let activeMainMenuSurfaceId = "aetheria.main_menu.root"; let renderDaemonEveSurfaceNow: (() => Promise) | null = null; -const mainMenuMode = window.location.hash === "#main-menu"; -const legacyViewportMode = window.location.hash === "#legacy-viewport"; +let eveSurfacePoll: number | null = null; function requiredElement(selector: string): TElement { const element = document.querySelector(selector); if (!element) { - throw new Error(`Aetheria RTS shell is missing ${selector}.`); + throw new Error(`Aetheria RTS is missing ${selector}.`); } return element; } -function requiredContext(target: HTMLCanvasElement): CanvasRenderingContext2D { - const context = target.getContext("2d"); - if (!context) { - throw new Error("Canvas 2D context is unavailable."); - } - - return context; -} - -const worldSpan = () => 2200 / zoom; - -function currentViewport(): Viewport { - const span = worldSpan(); - const aspect = Math.max(0.25, canvas.clientWidth / Math.max(1, canvas.clientHeight)); - const width = span * aspect; - const height = span; - return { - minX: centerX - width * 0.5, - minY: centerY - height * 0.5, - maxX: centerX + width * 0.5, - maxY: centerY + height * 0.5 - }; -} - function setStatus(text: string): void { statusEl.textContent = text; } async function showDaemonEveSurface(): Promise { - document.body.classList.remove("main-menu-mode"); document.body.classList.add("eve-game-mode"); - viewportFeedUnsubscribe?.(); - - let host = document.querySelector("#main-menu-host"); - if (!host) { - host = document.createElement("section"); - host.id = "main-menu-host"; - host.className = "main-menu-host"; - document.body.append(host); - } host.setAttribute("aria-label", "Aetheria daemon Eve surface"); const renderLatest = async () => { @@ -123,19 +50,21 @@ async function showDaemonEveSurface(): Promise { }); const loweredSurface = withActiveMainMenuPanel(surface, activeMainMenuSurfaceId); const surfaceKey = `${surface.providerId}:${surface.surface.id}:${surface.version}:${surface.updatedAtUtc}:${activeMainMenuSurfaceId}`; - if (surfaceKey !== latestEveSurfaceKey) { - latestEveSurfaceKey = surfaceKey; - renderEveSurface(loweredSurface, host, { - body: document.body, - assetUrlResolver: resolveAetheriaAssetUrl, - clientId: "aetheria.rts.electron", - commandSink: intent => submitEveCommand(intent), - documentResolver: resolveEveDocument, - source: "Aetheria Daemon", - statusElement: statusEl, - }); - wireWindowControls(); + if (surfaceKey === latestEveSurfaceKey) { + return; } + + latestEveSurfaceKey = surfaceKey; + renderEveSurface(loweredSurface, host, { + body: document.body, + assetUrlResolver: resolveAetheriaAssetUrl, + clientId: "aetheria.rts.electron", + commandSink: intent => submitEveCommand(intent), + documentResolver: resolveEveDocument, + source: "Aetheria Daemon", + statusElement: statusEl, + }); + wireWindowControls(); } catch (error) { setStatus(error instanceof Error ? error.message : "Aetheria daemon Eve surface unavailable."); } @@ -219,11 +148,8 @@ async function resolveEveDocument( component: EveSurfaceComponent, ): Promise<{ document?: unknown; documentId: string; schemaId: string; surface?: AetheriaMenuSurfaceDocument["surface"] } | undefined> { if (request.schemaId === "gamecult.eve.surface.v1" || request.slotId === "mainMenuPanel") { - const surface = await window.aetheriaRts.mainMenuSurface({ + const surface = await window.aetheriaRts.eveSurface({ surfaceId: request.documentId, - inGame: false, - canOpenRuntimeInputScreen: false, - panelOnly: true, }); return { documentId: request.documentId, @@ -268,19 +194,22 @@ function viewportFromComponent(component: EveSurfaceComponent): Viewport { } function numberProp(value: unknown, fallback: number): number { - if (typeof value === "number" && Number.isFinite(value)) + if (typeof value === "number" && Number.isFinite(value)) { return value; + } if (typeof value === "string") { const parsed = Number.parseFloat(value); - if (Number.isFinite(parsed)) + if (Number.isFinite(parsed)) { return parsed; + } } return fallback; } function resolveAetheriaAssetUrl(uri: string): string { - if (!uri.startsWith("resources://")) + if (!uri.startsWith("resources://")) { return uri; + } const resourcePath = uri.slice("resources://".length).replace(/^\/+/, ""); const filePath = resourcePath.match(/\.[a-z0-9]+$/i) @@ -289,66 +218,6 @@ function resolveAetheriaAssetUrl(uri: string): string { return new URL(`../../Assets/Resources/${filePath}`, document.baseURI).href; } -async function startDebugSurface(): Promise { - try { - renderDebugSurface(await window.aetheriaRts.debugSurface()); - window.aetheriaRts.watchDebugSurface(renderDebugSurface); - } catch (error) { - debugSurfaceHost.textContent = error instanceof Error ? error.message : "CultUI debug surface unavailable."; - } -} - -function renderDebugSurface(surface: AetheriaMenuSurfaceDocument): void { - renderEveSurface(surface, debugSurfaceHost, { - body: document.body, - assetUrlResolver: resolveAetheriaAssetUrl, - clientId: "aetheria.rts.electron", - commandSink: intent => handleMenuCommand(intent.command, surface), - source: "CultUI Debug", - statusElement: statusEl, - }); -} - -function wireWindowControls(): void { - document.querySelectorAll("[data-window-control]").forEach(button => { - if (button.dataset.windowControlWired === "true") - return; - button.dataset.windowControlWired = "true"; - button.addEventListener("click", () => { - const action = button.dataset.windowControl as "minimize" | "maximize" | "close" | undefined; - if (action) - void window.aetheriaRts.windowControl(action); - }); - }); -} - -function handleMenuCommand(commandOrIntent: string | EveCommandIntent, surface?: AetheriaMenuSurfaceDocument): void { - const command = typeof commandOrIntent === "string" ? commandOrIntent : commandOrIntent.command; - switch (command) { - case "aetheria.main_menu.root.show_settings": - void showMainMenuPanel("aetheria.main_menu.settings"); - return; - case "aetheria.main_menu.settings.show_player_settings": - void showMainMenuPanel("aetheria.main_menu.player_settings"); - return; - case "aetheria.main_menu.settings.show_verse_settings": - void showMainMenuPanel("aetheria.main_menu.verse_settings"); - return; - case "aetheria.main_menu.settings.show_input_settings": - void showMainMenuPanel("aetheria.main_menu.input_settings"); - return; - case "aetheria.main_menu.settings.back_to_settings": - void showMainMenuPanel("aetheria.main_menu.settings"); - return; - case "aetheria.main_menu.settings.back_to_main": - void showMainMenuPanel("aetheria.main_menu.root"); - return; - default: - setStatus(`${surface?.title ?? "Aetheria Starbridge"}: ${command || "no command"}`); - return; - } -} - async function submitEveCommand(intent: EveCommandIntent): Promise { if (intent.command.startsWith("aetheria.main_menu.")) { handleMenuCommand(intent.command); @@ -370,519 +239,45 @@ async function submitEveCommand(intent: EveCommandIntent): Promise { } } -function stopEveSurfacePoll(): void { - if (eveSurfacePoll == null) - return; - window.clearInterval(eveSurfacePoll); - eveSurfacePoll = null; -} - -function startViewportFeed(): void { - const viewport = currentViewport(); - viewportFeedUnsubscribe?.(); - viewportFeedUnsubscribe = window.aetheriaRts.watchViewportFeed({ - viewport, - selectedEntityIndex - }, applyViewportSnapshot); -} - -function applyViewportSnapshot(snapshot: AetheriaRuntimeViewportFeedSnapshot): void { - latestPollMs = snapshot.sampleMs; - latestReceivedAt = Date.parse(snapshot.receivedAtUtc) || Date.now(); - latest = snapshot.viewport; - latestSelected = snapshot.selectedObject; - latestInventory = snapshot.inventory; - latestDaemonHealth = snapshot.daemonHealth; - latestAuthority = snapshot.authorityStatus; - latestStarbridge = snapshot.starbridgeSession; - - const controlled = snapshot.viewport.objects.find(object => object.controlled); - if (controlled && selectedEntityIndex < 0) { - selectedEntityIndex = controlled.entityIndex; - centerX = controlled.x; - centerY = controlled.y; - startViewportFeed(); - return; - } - - setStatus(`${snapshot.viewport.zoneName || `Zone ${snapshot.viewport.zoneIndex}`} frame ${snapshot.viewport.frameId}`); - render(); - renderPanels(); -} - -async function refreshRuntimeSurfaces(): Promise { - try { - latestSurfaceIndex = await window.aetheriaRts.surfaceCatalogIndex(); - renderPanels(); - } catch (error) { - runtimeSurfaceDetails.innerHTML = details([ - ["State", error instanceof Error ? error.message : "unavailable"] - ]); +function handleMenuCommand(command: string): void { + switch (command) { + case "aetheria.main_menu.root.show_settings": + void showMainMenuPanel("aetheria.main_menu.settings"); + return; + case "aetheria.main_menu.settings.show_player_settings": + void showMainMenuPanel("aetheria.main_menu.player_settings"); + return; + case "aetheria.main_menu.settings.show_verse_settings": + void showMainMenuPanel("aetheria.main_menu.verse_settings"); + return; + case "aetheria.main_menu.settings.show_input_settings": + void showMainMenuPanel("aetheria.main_menu.input_settings"); + return; + case "aetheria.main_menu.settings.back_to_settings": + void showMainMenuPanel("aetheria.main_menu.settings"); + return; + case "aetheria.main_menu.settings.back_to_main": + void showMainMenuPanel("aetheria.main_menu.root"); + return; + default: + setStatus(`Aetheria Starbridge: ${command || "no command"}`); + return; } } -function resizeCanvas(): void { - const ratio = Math.max(1, window.devicePixelRatio || 1); - const width = Math.max(1, Math.floor(canvas.clientWidth * ratio)); - const height = Math.max(1, Math.floor(canvas.clientHeight * ratio)); - if (canvas.width !== width || canvas.height !== height) { - canvas.width = width; - canvas.height = height; - } - ctx.setTransform(ratio, 0, 0, ratio, 0, 0); -} - -function worldToScreen(x: number, y: number): { x: number; y: number } { - const viewport = currentViewport(); - const sx = (x - viewport.minX) / (viewport.maxX - viewport.minX) * canvas.clientWidth; - const sy = canvas.clientHeight - (y - viewport.minY) / (viewport.maxY - viewport.minY) * canvas.clientHeight; - return { x: sx, y: sy }; -} - -function screenToWorld(x: number, y: number): { x: number; y: number } { - const viewport = currentViewport(); - return { - x: viewport.minX + x / Math.max(1, canvas.clientWidth) * (viewport.maxX - viewport.minX), - y: viewport.minY + (1 - y / Math.max(1, canvas.clientHeight)) * (viewport.maxY - viewport.minY) - }; -} - -function worldRadiusToScreen(radius: number): number { - const viewport = currentViewport(); - return radius / (viewport.maxX - viewport.minX) * canvas.clientWidth; -} - -function render(): void { - resizeCanvas(); - ctx.clearRect(0, 0, canvas.clientWidth, canvas.clientHeight); - drawGrid(); - if (!latest) { - return; - } - - for (const influence of latest.gravityInfluences) { - drawGravity(influence); - } - for (const body of latest.bodies) { - drawBody(body); - } - for (const object of latest.objects) { - drawObject(object); - } -} - -function drawGrid(): void { - const viewport = currentViewport(); - const spacing = chooseGridSpacing((viewport.maxX - viewport.minX) / 8); - const startX = Math.floor(viewport.minX / spacing) * spacing; - const startY = Math.floor(viewport.minY / spacing) * spacing; - ctx.lineWidth = 1; - ctx.strokeStyle = "#1b242a"; - ctx.beginPath(); - for (let x = startX; x <= viewport.maxX; x += spacing) { - const screen = worldToScreen(x, viewport.minY); - ctx.moveTo(screen.x, 0); - ctx.lineTo(screen.x, canvas.clientHeight); - } - for (let y = startY; y <= viewport.maxY; y += spacing) { - const screen = worldToScreen(viewport.minX, y); - ctx.moveTo(0, screen.y); - ctx.lineTo(canvas.clientWidth, screen.y); - } - ctx.stroke(); -} - -function chooseGridSpacing(target: number): number { - const power = 10 ** Math.floor(Math.log10(Math.max(1, target))); - const scaled = target / power; - if (scaled > 5) return power * 10; - if (scaled > 2) return power * 5; - return power * 2; -} - -function drawGravity(influence: GravityInfluence): void { - const screen = worldToScreen(influence.x, influence.y); - const radius = worldRadiusToScreen(influence.radius); - ctx.lineWidth = 1; - ctx.strokeStyle = influence.kind === "Sun" ? "rgba(246, 192, 82, 0.28)" : "rgba(97, 174, 197, 0.24)"; - ctx.fillStyle = influence.kind === "Sun" ? "rgba(246, 192, 82, 0.05)" : "rgba(97, 174, 197, 0.04)"; - ctx.beginPath(); - ctx.arc(screen.x, screen.y, Math.max(1, radius), 0, Math.PI * 2); - ctx.fill(); - ctx.stroke(); -} - -function drawBody(body: BodyView): void { - const screen = worldToScreen(body.x, body.y); - const radius = Math.max(3, Math.min(28, worldRadiusToScreen(body.radius) * 0.025)); - ctx.fillStyle = body.kind === "sun" ? "#f4bc5d" : body.isAsteroidBelt ? "#9d9080" : "#71a8b7"; - ctx.beginPath(); - ctx.arc(screen.x, screen.y, radius, 0, Math.PI * 2); - ctx.fill(); -} - -function drawObject(object: ViewObject): void { - const screen = worldToScreen(object.x, object.y); - const selected = object.entityIndex === selectedEntityIndex; - const station = object.kind.toLowerCase().includes("station"); - const size = selected ? 9 : object.controlled ? 7 : 5; - ctx.save(); - ctx.translate(screen.x, screen.y); - ctx.rotate(Math.atan2(object.directionY, object.directionX)); - ctx.fillStyle = object.isActive - ? selected ? "#ffffff" : object.controlled ? "#54d18a" : object.factionKey === "raider" ? "#e36b4b" : "#aeb7bd" - : "#56616a"; - ctx.strokeStyle = "#0d1013"; - ctx.lineWidth = 2; - ctx.beginPath(); - if (station) { - ctx.rect(-size, -size, size * 2, size * 2); - } else { - ctx.moveTo(size + 4, 0); - ctx.lineTo(-size, size * 0.7); - ctx.lineTo(-size * 0.45, 0); - ctx.lineTo(-size, -size * 0.7); - } - ctx.closePath(); - ctx.stroke(); - ctx.fill(); - ctx.restore(); - - if (object.targetEntityIndex >= 0) { - const target = latest?.objects.find(candidate => candidate.entityIndex === object.targetEntityIndex); - if (target) { - const targetScreen = worldToScreen(target.x, target.y); - ctx.strokeStyle = object.controlled ? "rgba(84, 209, 138, 0.42)" : "rgba(227, 107, 75, 0.36)"; - ctx.lineWidth = 1; - ctx.beginPath(); - ctx.moveTo(screen.x, screen.y); - ctx.lineTo(targetScreen.x, targetScreen.y); - ctx.stroke(); +function wireWindowControls(): void { + document.querySelectorAll("[data-window-control]").forEach(button => { + if (button.dataset.windowControlWired === "true") { + return; } - } - - if (selected) { - ctx.strokeStyle = "#ffffff"; - ctx.lineWidth = 1; - ctx.beginPath(); - ctx.arc(screen.x, screen.y, 14, 0, Math.PI * 2); - ctx.stroke(); - } -} - -function renderPanels(): void { - if (!latest) { - return; - } - - const selected = latestSelected?.selected ?? null; - selectionTitle.textContent = selected?.displayName || "No Selection"; - selectionDetails.innerHTML = selected - ? details([ - ["Entity", selected.entityIndex.toString()], - ["Key", selected.entityKey], - ["Kind", selected.kind || "unknown"], - ["Faction", selected.factionKey || "unknown"], - ["State", selected.isActive ? "active" : "disabled"], - ["Hull", selected.status.hull.toFixed(1)], - ["Shield", selected.status.shield.toFixed(1)], - ["Heat", selected.status.heat.toFixed(1)], - ["Position", `${selected.x.toFixed(1)}, ${selected.y.toFixed(1)}, z ${selected.z.toFixed(1)}`], - ["Velocity", `${selected.velocityX.toFixed(2)}, ${selected.velocityY.toFixed(2)}`], - ["Target", selected.targetEntityIndex >= 0 ? `#${selected.targetEntityIndex}` : "none"], - ["Controlled", selected.controlled ? "yes" : "no"], - ["Equipment", inventoryText(latestInventory?.equipment ?? [])], - ["Cargo", inventoryText(latestInventory?.cargo ?? [])] - ]) - : `
State
Click a visible object on the map.
`; - - frameDetails.innerHTML = details([ - ["Run", latest.runId], - ["Zone", `${latest.zoneIndex}`], - ["Frame", latest.frameId.toString()], - ["Sim Time", `${latest.simulationTimeSeconds.toFixed(2)}s`], - ["Frame Age", `${Math.max(0, Date.now() - latestReceivedAt)}ms`], - ["Viewport Poll", `${latestPollMs.toFixed(1)}ms`], - ["Visible", latest.objects.length.toString()], - ["Daemon", latestDaemonHealth ? `${latestDaemonHealth.status} / ${latestDaemonHealth.transport}` : "unknown"], - ["Authority", latestAuthority ? latestAuthority.policyId : "unknown"], - ["Mode", latestAuthority ? latestAuthority.defaultMode : "unknown"], - ["Last Op", latestOperationReceipt ?? "none"] - ]); - - runtimeSurfaceDetails.innerHTML = latestSurfaceIndex - ? details([ - ["Catalog", latestSurfaceIndex.catalogId], - ["Queries", surfaceListText(latestSurfaceIndex.queries)], - ["Live Feeds", surfaceListText(latestSurfaceIndex.liveFeeds)], - ["Operations", surfaceListText(latestSurfaceIndex.operations)], - ["Pointers", surfaceListText(latestSurfaceIndex.statePointers)], - ["Native Views", surfaceListText(latestSurfaceIndex.nativeSliceViews)] - ]) - : `
State
Discovering runtime surfaces.
`; - - starbridgeDetails.innerHTML = latestStarbridge - ? details([ - ["Scenario", latestStarbridge.scenarioName || latestStarbridge.scenarioId], - ["Phase", latestStarbridge.phase], - ["Wave", latestStarbridge.currentWaveIndex.toString()], - ["Base", latestStarbridge.baseStatus.displayName || "unknown"], - ["Hull", latestStarbridge.baseStatus.hull.toFixed(1)], - ["Shield", latestStarbridge.baseStatus.shield.toFixed(1)], - ["Heat", latestStarbridge.baseStatus.heat.toFixed(1)], - ["Next", waveText(latestStarbridge)], - ["Stock", stockText(latestStarbridge)], - ["Roles", roleText(latestStarbridge)] - ]) - : `
State
No Starbridge session.
`; - - controlledList.innerHTML = ""; - const controlledObjects = latest.objects.filter(object => object.controlled); - if (controlledObjects.length === 0) { - controlledList.innerHTML = `No controlled units in viewport.`; - return; - } - - for (const object of controlledObjects) { - const row = document.createElement("button"); - row.type = "button"; - row.className = "controlled-row"; - row.innerHTML = `${escapeHtml(object.displayName || object.entityKey)}#${object.entityIndex}`; - row.addEventListener("click", () => { - selectedEntityIndex = object.entityIndex; - centerX = object.x; - centerY = object.y; - void refresh(); + button.dataset.windowControlWired = "true"; + button.addEventListener("click", () => { + const action = button.dataset.windowControl as "minimize" | "maximize" | "close" | undefined; + if (action) { + void window.aetheriaRts.windowControl(action); + } }); - controlledList.append(row); - } -} - -function inventoryText(items: InventoryItem[]): string { - if (items.length === 0) { - return "empty"; - } - - return items - .map(item => `${item.itemKey} x${item.quantity}`) - .join(", "); -} - -function details(rows: Array<[string, string]>): string { - return rows - .map(([key, value]) => `
${escapeHtml(key)}
${escapeHtml(value)}
`) - .join(""); -} - -function escapeHtml(value: string): string { - return value.replace(/[&<>"']/g, character => { - switch (character) { - case "&": return "&"; - case "<": return "<"; - case ">": return ">"; - case "\"": return """; - case "'": return "'"; - default: return character; - } }); } -canvas.addEventListener("click", event => { - if (!latest) { - return; - } - - const rect = canvas.getBoundingClientRect(); - const click = screenToWorld(event.clientX - rect.left, event.clientY - rect.top); - const nearest = nearestObject(click.x, click.y); - - if (nearest && worldRadiusToScreen(nearest.distance) < 24) { - selectedEntityIndex = nearest.object.entityIndex; - void refresh(); - } -}); - -canvas.addEventListener("contextmenu", event => { - event.preventDefault(); - if (!latest) { - return; - } - - const actor = latest.objects.find(object => object.entityIndex === selectedEntityIndex && object.controlled); - if (!actor) { - setStatus("Select an owned pawn or station first."); - return; - } - - const rect = canvas.getBoundingClientRect(); - const click = screenToWorld(event.clientX - rect.left, event.clientY - rect.top); - const nearest = nearestObject(click.x, click.y); - if (nearest && nearest.object.entityIndex !== actor.entityIndex && worldRadiusToScreen(nearest.distance) < 28) { - void issueTargetCommand(actor, nearest.object); - return; - } - - void issueMoveCommand(actor, click.x, click.y); -}); - -function nearestObject(x: number, y: number): { object: ViewObject; distance: number } | null { - if (!latest) { - return null; - } - - let nearest: ViewObject | null = null; - let nearestDistance = Number.POSITIVE_INFINITY; - for (const object of latest.objects) { - const dx = object.x - x; - const dy = object.y - y; - const distance = Math.hypot(dx, dy); - if (distance < nearestDistance) { - nearest = object; - nearestDistance = distance; - } - } - - return nearest ? { object: nearest, distance: nearestDistance } : null; -} - -async function issueTargetCommand(actor: ViewObject, target: ViewObject): Promise { - const receipt = await window.aetheriaRts.setTarget({ - actorEntityKey: actor.entityKey, - targetEntityKey: target.entityKey, - observedFrameId: latest?.frameId - }); - latestOperationReceipt = formatOperationReceipt("setTarget", receipt, `${actor.entityKey} -> ${target.entityKey}`); - setStatus(latestOperationReceipt); - await refresh(); -} - -async function issueMoveCommand(actor: ViewObject, x: number, y: number): Promise { - const dx = x - actor.x; - const dy = y - actor.y; - const length = Math.max(0.001, Math.hypot(dx, dy)); - const receipt = await window.aetheriaRts.setMoveVector({ - actorEntityKey: actor.entityKey, - directionX: dx / length, - directionY: dy / length, - scalar: 1, - observedFrameId: latest?.frameId - }); - latestOperationReceipt = formatOperationReceipt("setMoveVector", receipt, `${actor.entityKey} ${dx.toFixed(1)},${dy.toFixed(1)}`); - setStatus(latestOperationReceipt); - await refresh(); -} - -function formatOperationReceipt( - label: string, - receipt: Awaited>, - detail: string -): string { - const accepted = receipt.accepted ? "accepted" : "rejected"; - return `${label} ${receipt.operationId} ${accepted} via ${receipt.route.kind} ${receipt.commandId} ${detail} @ frame ${latest?.frameId ?? "?"}`; -} - -document.querySelector("#zoom-in")?.addEventListener("click", () => { - zoom = Math.min(12, zoom * 1.4); - updateZoomLabel(); - void refresh(); -}); - -document.querySelector("#zoom-out")?.addEventListener("click", () => { - zoom = Math.max(0.2, zoom / 1.4); - updateZoomLabel(); - void refresh(); -}); - -document.querySelector("#refresh")?.addEventListener("click", () => { - void refresh(); -}); - -window.addEventListener("resize", () => { - render(); -}); - -function updateZoomLabel(): void { - zoomLabel.value = `${zoom.toFixed(1)}x`; -} - -async function refresh(): Promise { - if (refreshInFlight) { - return; - } - - refreshInFlight = true; - try { - startViewportFeed(); - } catch (error) { - setStatus(error instanceof Error ? error.message : "Unable to fetch viewport"); - } finally { - refreshInFlight = false; - } -} - -function surfaceListText(surfaces: ReadonlyArray<{ - readonly surfaceId: string; - readonly routeHint: { readonly kind: string; readonly description?: string }; - readonly sources: ReadonlyArray<{ readonly sourceId: string; readonly schemaId?: string }>; -}>): string { - if (surfaces.length === 0) { - return "none"; - } - - return surfaces - .map(surface => { - const route = surface.routeHint.description - ? `${surface.routeHint.kind} ${surface.routeHint.description}` - : surface.routeHint.kind; - const sources = surface.sources.length === 0 - ? "no sources" - : surface.sources - .map(source => source.schemaId ? `${source.sourceId} (${source.schemaId})` : source.sourceId) - .join(" + "); - return `${surface.surfaceId} -> ${route}; ${sources}`; - }) - .join(", "); -} - -function stockText(session: StarbridgeSessionDocument): string { - if (session.stationStock.length === 0) { - return "empty"; - } - - return session.stationStock - .slice(0, 4) - .map(item => `${item.itemKey} x${item.quantity}`) - .join(", "); -} - -function waveText(session: StarbridgeSessionDocument): string { - const wave = session.waveForecast[0]; - if (!wave) { - return "none"; - } - - const attackers = wave.attackerKeys.length > 0 ? ` / ${wave.attackerKeys.join(", ")}` : ""; - return `${wave.displayName || `Wave ${wave.waveIndex}`}${attackers}`; -} - -function roleText(session: StarbridgeSessionDocument): string { - if (session.runtimeRoles.length === 0) { - return "none"; - } - - return session.runtimeRoles - .map(role => `${role.role}:${role.runtimeId}`) - .join(", "); -} - -if (mainMenuMode) { - void showDaemonEveSurface(); -} else if (legacyViewportMode) { - document.body.classList.remove("main-menu-mode", "eve-game-mode"); - updateZoomLabel(); - void startDebugSurface(); - void refreshRuntimeSurfaces(); - void refresh(); -} else { - void showDaemonEveSurface(); -} +void showDaemonEveSurface(); diff --git a/Aetheria.Rts.Web/Electron/aetheria-cultmesh.ts b/Aetheria.Rts.Web/Electron/aetheria-cultmesh.ts index 7d796ae50..7091c2b39 100644 --- a/Aetheria.Rts.Web/Electron/aetheria-cultmesh.ts +++ b/Aetheria.Rts.Web/Electron/aetheria-cultmesh.ts @@ -96,13 +96,6 @@ export type AetheriaCultMeshDaemonTarget = { readonly endpoints?: readonly string[]; }; -export type AetheriaMenuSurfaceRequest = { - surfaceId?: string; - inGame?: boolean; - canOpenRuntimeInputScreen?: boolean; - panelOnly?: boolean; -}; - export type AetheriaEveSurfaceRequest = { surfaceId?: string; recordKey?: string; @@ -294,27 +287,27 @@ export class AetheriaCultMeshClient { } public async mapViewport(request: ViewportRequest): Promise { - return this.queryExecutors.mapViewport(request); + return this.aetheria.zone().viewport.within(request); } public async objectsViewport(request: ViewportRequest): Promise { - return this.queryExecutors.objectsViewport(request); + return this.aetheria.zone().objects.visibleWithin(request); } public async gravityViewport(request: ViewportRequest): Promise { - return this.queryExecutors.gravityViewport(request); + return this.aetheria.zone().gravity.within(request); } public async renderSplatsViewport(request: ViewportRequest): Promise { - return this.queryExecutors.renderSplatsViewport(request); + return this.aetheria.zone().renderSplats.within(request); } public async selectedObject(request: SelectedObjectRequest): Promise { - return this.queryExecutors.selectedObject(request); + return this.aetheria.selectedObject(request.entityIndex); } public async inventory(request: SelectedObjectRequest): Promise { - return this.queryExecutors.inventory(request); + return this.aetheria.inventory(request.entityIndex); } public async daemonHealth(): Promise { @@ -333,14 +326,6 @@ export class AetheriaCultMeshClient { return this.aetheria.daemon.assetManifest(); } - public async mainMenuSurface(request: AetheriaMenuSurfaceRequest = {}): Promise { - return buildMainMenuSurface( - request.surfaceId || "aetheria.main_menu.root", - request.canOpenRuntimeInputScreen === true, - request.inGame === true, - request.panelOnly === true); - } - public async eveSurface(request: AetheriaEveSurfaceRequest = {}): Promise { const recordKey = eveSurfaceRecordKey(request); const document = CultMesh.documentFromPublication( @@ -696,286 +681,6 @@ function isTransientPublicationReadError(error: unknown): boolean { message.includes("did not contain schema"); } -function buildMainMenuSurface( - surfaceId: string, - canOpenRuntimeInputScreen: boolean, - inGame: boolean, - _panelOnly = false, -): AetheriaMenuSurfaceDocument { - const updatedAtUtc = new Date().toISOString(); - const activeSurfaceId = normalizeMainMenuSurfaceId(surfaceId); - return buildMainMenuPanelSurface(activeSurfaceId, canOpenRuntimeInputScreen, inGame, updatedAtUtc); -} - -function buildMainMenuPanelSurface( - surfaceId: string, - canOpenRuntimeInputScreen: boolean, - inGame: boolean, - updatedAtUtc: string, -): AetheriaMenuSurfaceDocument { - if (surfaceId === "aetheria.main_menu.settings") { - return surfaceDocument( - surfaceId, - "Aetheria Settings", - updatedAtUtc, - [ - command("aetheria.main_menu.settings.show_player_settings", "Player Settings"), - command("aetheria.main_menu.settings.show_verse_settings", "Verse"), - command("aetheria.main_menu.settings.show_input_settings", "Input"), - command("aetheria.main_menu.settings.back_to_main", "Back"), - ], - menuPanel( - surfaceId, - [ - text("aetheria.mainMenu.settings.title", "SETTINGS", "text.title", { margin: "0 0 2.4rem 0" }, mainMenuTitleStyle("4.4rem")), - buttonColumn( - "aetheria.mainMenu.settings.actions", - button("aetheria.mainMenu.settings.playerSettings", "Player Settings", "aetheria.main_menu.settings.show_player_settings"), - button("aetheria.mainMenu.settings.verse", "Verse", "aetheria.main_menu.settings.show_verse_settings"), - button("aetheria.mainMenu.settings.input", "Input", "aetheria.main_menu.settings.show_input_settings"), - button("aetheria.mainMenu.settings.back", "Back", "aetheria.main_menu.settings.back_to_main"), - ), - ], - ), - ); - } - - if (surfaceId === "aetheria.main_menu.input_settings") { - const children = [ - text("aetheria.mainMenu.input.title", "INPUT", "text.title", { margin: "0 0 2.2rem 0" }, mainMenuTitleStyle("4.4rem")), - metric("aetheria.mainMenu.input.bindingOverrides", "Binding Overrides", "0"), - metric("aetheria.mainMenu.input.actionBarInputs", "Action-Bar Inputs", "0"), - text( - "aetheria.mainMenu.input.note", - canOpenRuntimeInputScreen && inGame - ? "The runtime Eve input screen owns low-level InputSystem rebinding and action-bar input edits." - : "This title shell reports typed player-settings state. Launch a run to open the runtime Eve input screen that owns low-level InputSystem rebinding."), - buttonColumn( - "aetheria.mainMenu.input.actions", - button("aetheria.mainMenu.input.back", "Back", "aetheria.main_menu.settings.back_to_settings"), - ), - ]; - return surfaceDocument( - surfaceId, - "Aetheria Input Settings", - updatedAtUtc, - [command("aetheria.main_menu.settings.back_to_settings", "Back")], - menuPanel(surfaceId, children)); - } - - if (surfaceId === "aetheria.main_menu.player_settings") { - return surfaceDocument( - surfaceId, - "Aetheria Player Settings", - updatedAtUtc, - [command("aetheria.main_menu.settings.back_to_settings", "Back")], - menuPanel( - surfaceId, - [ - text("aetheria.mainMenu.player.title", "PLAYER", "text.title", { margin: "0 0 2.2rem 0" }, mainMenuTitleStyle("4.4rem")), - metric("aetheria.mainMenu.player.temperatureUnit", "Temperature Unit", "Celsius"), - metric("aetheria.mainMenu.player.significantDigits", "Significant Digits", "3"), - metric("aetheria.mainMenu.player.shutdown", "Default Shutdown", "25%"), - buttonColumn( - "aetheria.mainMenu.player.actions", - button("aetheria.mainMenu.player.back", "Back", "aetheria.main_menu.settings.back_to_settings"), - ), - ])); - } - - if (surfaceId === "aetheria.main_menu.verse_settings") { - return surfaceDocument( - surfaceId, - "Aetheria Verse Settings", - updatedAtUtc, - [command("aetheria.main_menu.settings.back_to_settings", "Back")], - menuPanel( - surfaceId, - [ - text("aetheria.mainMenu.verse.title", "VERSE", "text.title", { margin: "0 0 2.2rem 0" }, mainMenuTitleStyle("4.4rem")), - metric("aetheria.mainMenu.verse.name", "Name", "Local Aetheria"), - metric("aetheria.mainMenu.verse.id", "Verse", "aetheria.local"), - metric("aetheria.mainMenu.verse.visibility", "Visibility", "local"), - buttonColumn( - "aetheria.mainMenu.verse.actions", - button("aetheria.mainMenu.verse.back", "Back", "aetheria.main_menu.settings.back_to_settings"), - ), - ])); - } - - const actionButtons = [ - ...(!inGame ? [button("aetheria.main_menu.root.continue", "Continue", "aetheria.main_menu.root.continue")] : []), - button("aetheria.main_menu.root.newGame", "New Game", "aetheria.main_menu.root.new_game"), - button("aetheria.main_menu.root.settings", "Settings", "aetheria.main_menu.root.show_settings"), - button("aetheria.main_menu.root.quit", "Quit", "aetheria.main_menu.root.quit"), - ]; - return surfaceDocument( - "aetheria.main_menu.root", - "Aetheria Starbridge", - updatedAtUtc, - [ - ...(!inGame ? [command("aetheria.main_menu.root.continue", "Continue")] : []), - command("aetheria.main_menu.root.new_game", "New Game"), - command("aetheria.main_menu.root.show_settings", "Settings"), - command("aetheria.main_menu.root.quit", "Quit"), - ], - menuPanel( - "aetheria.main_menu.root", - [ - text("aetheria.main_menu.root.title", "AETHERIA", "text.title", { margin: "0 0 -1.6rem 0" }, mainMenuTitleStyle("5.9rem")), - text("aetheria.main_menu.root.subtitle", "STARBRIDGE", "text.subtitle", { margin: "0 0 0.35rem 16.8rem" }, { - font: "100 2.6rem/1 Montserrat, sans-serif", - color: "rgba(232, 250, 255, 0.9)", - whiteSpace: "nowrap", - }), - buttonColumn("aetheria.main_menu.root.actions", ...actionButtons), - ]), - ); -} - -function normalizeMainMenuSurfaceId(surfaceId: string): string { - switch (surfaceId) { - case "": - return "aetheria.main_menu.root"; - case "aetheria.main_menu.root": - case "aetheria.main_menu.settings": - case "aetheria.main_menu.input_settings": - case "aetheria.main_menu.player_settings": - case "aetheria.main_menu.verse_settings": - return surfaceId; - default: - return "aetheria.main_menu.root"; - } -} - -function surfaceDocument( - surfaceId: string, - title: string, - updatedAtUtc: string, - commands: AetheriaMenuSurfaceCommand[], - ...children: AetheriaMenuSurfaceComponent[] -): AetheriaMenuSurfaceDocument { - return { - providerId: "aetheria", - providerKind: "game.menu", - title, - version: 1, - updatedAtUtc, - surface: { - id: surfaceId, - root: node( - `${surfaceId}.root`, - "surface", - {}, - { position: "relative", overflow: "hidden", width: "100%", height: "100vh", minHeight: "100vh" }, - { background: "rgba(0,0,0,0)" }, - ...children), - styles: [ - { name: "font.title.family", value: "Montserrat" }, - { name: "font.title.style", value: "Thin" }, - { name: "font.title.weight", value: "100" }, - { name: "font.body.family", value: "Ubuntu" }, - { name: "font.body.style", value: "Regular" }, - { name: "font.body.weight", value: "400" }, - { - name: "font.web.google", - value: "https://fonts.googleapis.com/css2?family=Montserrat:wght@100&family=Ubuntu:wght@400&display=swap", - }, - ], - }, - commands, - }; -} - -function command(commandId: string, label: string): AetheriaMenuSurfaceCommand { - return { command: commandId, label, transport: "cultmesh" }; -} - -function node( - id: string, - kind: string, - props: Record, - layoutOrChild?: Record | AetheriaMenuSurfaceComponent, - styleOrChild?: Record | AetheriaMenuSurfaceComponent, - ...children: AetheriaMenuSurfaceComponent[] -): AetheriaMenuSurfaceComponent { - const layout = isSurfaceComponent(layoutOrChild) ? undefined : layoutOrChild; - const style = isSurfaceComponent(styleOrChild) ? undefined : styleOrChild; - const normalizedChildren = [ - ...(isSurfaceComponent(layoutOrChild) ? [layoutOrChild] : []), - ...(isSurfaceComponent(styleOrChild) ? [styleOrChild] : []), - ...children, - ]; - return { id, kind, props, layout, style, children: normalizedChildren }; -} - -function text( - id: string, - value: string, - kind = "text", - layout?: Record, - style?: Record, -): AetheriaMenuSurfaceComponent { - return node(id, kind, { value }, layout, style); -} - -function metric(id: string, label: string, value: string): AetheriaMenuSurfaceComponent { - return node(id, "metric", { label, value }); -} - -function menuPanel(id: string, children: AetheriaMenuSurfaceComponent[]): AetheriaMenuSurfaceComponent { - return node( - `${id}.menu`, - "column", - {}, - { - position: "relative", - padding: "7.25rem 0 0 6.75rem", - gap: "1.1rem", - width: "44rem", - maxWidth: "calc(100vw - 3rem)", - minHeight: "100vh", - alignItems: "flex-start", - }, - { color: "#e9fbff", background: "rgba(0,0,0,0)" }, - ...children); -} - -function mainMenuTitleStyle(fontSize: string): Record { - return { - font: `100 ${fontSize}/0.98 Montserrat, sans-serif`, - color: "rgba(232, 250, 255, 0.94)", - whiteSpace: "nowrap", - }; -} - -function button(id: string, label: string, commandId: string): AetheriaMenuSurfaceComponent { - return node( - id, - "control.button", - { label, command: commandId }, - { minWidth: "0", width: "220px", height: "32px", padding: "0" }, - { - background: "rgba(0, 0, 0, 0)", - border: "0", - borderWidth: "0", - borderStyle: "solid", - borderRadius: "0", - boxShadow: "none", - font: "400 1.55rem/32px Ubuntu, sans-serif", - color: "#e8fbff", - textAlign: "left", - }); -} - -function buttonColumn(id: string, ...children: AetheriaMenuSurfaceComponent[]): AetheriaMenuSurfaceComponent { - return node(id, "column", {}, { gap: "0.18rem", alignItems: "flex-start" }, { color: "#e8fbff" }, ...children); -} - -function isSurfaceComponent(value: unknown): value is AetheriaMenuSurfaceComponent { - return !!value && typeof value === "object" && "kind" in value && "props" in value && "children" in value; -} - function stableToken(value: string): string { return value .replace(/[^a-zA-Z0-9]+/gu, "-") @@ -1090,6 +795,14 @@ function eveSurfaceRecordKey(request: AetheriaEveSurfaceRequest): string { return "eve:surface:aetheria.daemon.game.tui"; case "aetheria.main_menu.root": return "eve:surface:aetheria.main_menu.root"; + case "aetheria.main_menu.settings": + return "eve:surface:aetheria.main_menu.settings"; + case "aetheria.main_menu.input_settings": + return "eve:surface:aetheria.main_menu.input_settings"; + case "aetheria.main_menu.player_settings": + return "eve:surface:aetheria.main_menu.player_settings"; + case "aetheria.main_menu.verse_settings": + return "eve:surface:aetheria.main_menu.verse_settings"; case "aetheria.inventory.panel": return "eve:surface:aetheria.inventory.panel"; case "aetheria.inventory.panel.dropdown": diff --git a/Aetheria.Rts.Web/Electron/aetheria-rts-generated-bindings.ts b/Aetheria.Rts.Web/Electron/aetheria-rts-generated-bindings.ts index 2ec8c7244..1dd11d66b 100644 --- a/Aetheria.Rts.Web/Electron/aetheria-rts-generated-bindings.ts +++ b/Aetheria.Rts.Web/Electron/aetheria-rts-generated-bindings.ts @@ -120,7 +120,6 @@ export const AetheriaRtsIpcChannels = { surfaceCatalogIndex: "aetheria-rts:surface-catalog-index", eveSurface: "aetheria-rts:eve-surface", submitEveCommand: "aetheria-rts:submit-eve-command", - mainMenuSurface: "aetheria-rts:main-menu-surface", debugSurface: "aetheria-rts:debug-surface", debugSurfaceWatch: "aetheria-rts:debug-surface-watch", debugSurfaceWatchStop: "aetheria-rts:debug-surface-watch-stop", @@ -1912,13 +1911,6 @@ export type AetheriaRuntimeViewportFeedSnapshot = { sampleMs: number; }; -export type AetheriaMenuSurfaceRequest = { - surfaceId?: string; - inGame?: boolean; - canOpenRuntimeInputScreen?: boolean; - panelOnly?: boolean; -}; - export type AetheriaEveSurfaceRequest = { surfaceId?: string; recordKey?: string; diff --git a/Aetheria.Rts.Web/Electron/main.ts b/Aetheria.Rts.Web/Electron/main.ts index 50bff1c45..cc36b5b26 100644 --- a/Aetheria.Rts.Web/Electron/main.ts +++ b/Aetheria.Rts.Web/Electron/main.ts @@ -26,16 +26,12 @@ const rtsCultMeshPort = Number.parseInt(process.env.AETHERIA_RTS_CULTMESH_PORT ? const rtsVerseId = process.env.AETHERIA_RTS_VERSE_ID?.trim() || "aetheria.local"; const rtsDaemonId = process.env.AETHERIA_RTS_DAEMON_ID?.trim() || "starfire-rts"; const configuredDaemonUri = process.env.AETHERIA_RTS_CULTMESH_URI?.trim() ?? ""; -const legacyDaemonEndpoint = process.env.AETHERIA_RTS_DAEMON_ENDPOINT?.trim() ?? ""; -const launchLocalDaemon = configuredDaemonUri.length === 0 && legacyDaemonEndpoint.length === 0; +const launchLocalDaemon = configuredDaemonUri.length === 0; const rtsCultMeshUri = configuredDaemonUri || `cultmesh://aetheria/daemon/${encodeURIComponent(rtsDaemonId)}`; const rtsCultMeshAdvertiseHost = process.env.AETHERIA_RTS_CULTMESH_ADVERTISE_HOST?.trim() || "127.0.0.1"; -const removedResolvedRudpEndpoint = process.env.AETHERIA_RTS_RESOLVED_RUDP_ENDPOINT?.trim() ?? ""; -const removedPeerCultMeshEndpoints = process.env.AETHERIA_RTS_PEER_CULTMESH_ENDPOINTS?.trim() ?? ""; const localDaemonRudpEndpoint = launchLocalDaemon ? `rudp://127.0.0.1:${rtsCultMeshPort}` : ""; const electronSmoke = process.env.AETHERIA_RTS_ELECTRON_SMOKE === "1"; const electronSmokeResultPath = process.env.AETHERIA_RTS_ELECTRON_SMOKE_RESULT; -const rendererView = process.env.AETHERIA_RTS_VIEW?.trim() ?? ""; let daemonProcess: ChildProcessWithoutNullStreams | null = null; let mainWindow: BrowserWindow | null = null; @@ -86,16 +82,6 @@ app.whenReady().then(async () => { showStartup("Preparing Aetheria RTS", "Building daemon if needed."); try { - if (legacyDaemonEndpoint) { - throw new Error("AETHERIA_RTS_DAEMON_ENDPOINT has been removed. Configure AETHERIA_RTS_CULTMESH_URI and let Odin/CultMesh resolve the transport."); - } - if (removedResolvedRudpEndpoint) { - throw new Error("AETHERIA_RTS_RESOLVED_RUDP_ENDPOINT has been removed. Configure AETHERIA_RTS_CULTMESH_URI and let Odin/CultMesh resolve the daemon transport."); - } - if (removedPeerCultMeshEndpoints) { - throw new Error("AETHERIA_RTS_PEER_CULTMESH_ENDPOINTS has been removed. Configure AETHERIA_RTS_CULTMESH_URI and let Odin/CultMesh discover peer endpoints."); - } - if (launchLocalDaemon) { await ensureDotnetBuild(); showStartup("Launching Aetheria RTS", "Starting the Aetheria daemon."); @@ -138,11 +124,7 @@ app.whenReady().then(async () => { showStartup("Launching Aetheria RTS", "Waiting for the daemon CultMesh frame."); await rtsClient.waitForFrame(30000); - if (rendererView === "main-menu") { - await mainWindow.loadFile(rendererIndex, { hash: "main-menu" }); - } else { - await mainWindow.loadFile(rendererIndex); - } + await mainWindow.loadFile(rendererIndex); if (electronSmoke) { const result = await runElectronSmoke(mainWindow); writeElectronSmokeResult({ ok: true, result }); @@ -209,7 +191,7 @@ async function runElectronSmoke(window: BrowserWindow): Promise - requireClient().mainMenuSurface(request)); ipcMain.handle("aetheria-rts:debug-surface", () => readDebugSurface()); ipcMain.handle("aetheria-rts:debug-surface-watch", (event, subscriptionId: string) => { if (!subscriptionId) diff --git a/Aetheria.Rts.Web/Electron/preload.cjs b/Aetheria.Rts.Web/Electron/preload.cjs index 435aff32a..c58fc1390 100644 --- a/Aetheria.Rts.Web/Electron/preload.cjs +++ b/Aetheria.Rts.Web/Electron/preload.cjs @@ -23,7 +23,6 @@ const channels = { surfaceCatalogIndex: "aetheria-rts:surface-catalog-index", eveSurface: "aetheria-rts:eve-surface", submitEveCommand: "aetheria-rts:submit-eve-command", - mainMenuSurface: "aetheria-rts:main-menu-surface", debugSurface: "aetheria-rts:debug-surface", debugSurfaceWatch: "aetheria-rts:debug-surface-watch", debugSurfaceWatchStop: "aetheria-rts:debug-surface-watch-stop", @@ -63,7 +62,6 @@ contextBridge.exposeInMainWorld("aetheriaRts", { surfaceCatalogIndex: () => ipcRenderer.invoke(channels.surfaceCatalogIndex), eveSurface: request => ipcRenderer.invoke(channels.eveSurface, request), submitEveCommand: request => ipcRenderer.invoke(channels.submitEveCommand, request), - mainMenuSurface: request => ipcRenderer.invoke(channels.mainMenuSurface, request), debugSurface: () => ipcRenderer.invoke(channels.debugSurface), watchDebugSurface: callback => { const subscriptionId = `debug-surface-${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`; diff --git a/Aetheria.Rts.Web/package.json b/Aetheria.Rts.Web/package.json index b6775ed03..66bdeea64 100644 --- a/Aetheria.Rts.Web/package.json +++ b/Aetheria.Rts.Web/package.json @@ -12,7 +12,7 @@ "check:rts-bindings": "C:\\Progra~1\\nodejs\\node.exe scripts/generate-rts-bindings.mjs --check", "verify:stage7b": "C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe -NoProfile -ExecutionPolicy Bypass -File scripts\\verify-stage7b-rts-client.ps1", "verify:stage7c": "C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe -NoProfile -ExecutionPolicy Bypass -File scripts\\verify-stage7c-local-runtime.ps1", - "verify:stage7c:electron": "C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe -NoProfile -ExecutionPolicy Bypass -File scripts\\verify-stage7c-electron-shell.ps1", + "verify:stage7c:electron": "C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe -NoProfile -ExecutionPolicy Bypass -File scripts\\verify-stage7c-electron-rts.ps1", "verify:stage7d:remote": "C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe -NoProfile -ExecutionPolicy Bypass -File scripts\\verify-stage7d-remote-cultmesh.ps1", "electron": "npm run build && .\\node_modules\\electron\\dist\\electron.exe .", "watch": "C:\\Progra~1\\nodejs\\node.exe ./node_modules/typescript/bin/tsc -p tsconfig.json --watch" diff --git a/Aetheria.Rts.Web/scripts/copy-electron-preload.mjs b/Aetheria.Rts.Web/scripts/copy-electron-preload.mjs index 474429ac7..3b24a9bd1 100644 --- a/Aetheria.Rts.Web/scripts/copy-electron-preload.mjs +++ b/Aetheria.Rts.Web/scripts/copy-electron-preload.mjs @@ -3,8 +3,8 @@ import { resolve } from "node:path"; const root = resolve(import.meta.dirname, ".."); const source = resolve(root, "Electron", "preload.cjs"); -const legacyOutput = resolve(root, "electron-dist", "preload.js"); +const previousPreloadOutput = resolve(root, "electron-dist", "preload.js"); const output = resolve(root, "electron-dist", "preload.cjs"); -rmSync(legacyOutput, { force: true }); +rmSync(previousPreloadOutput, { force: true }); copyFileSync(source, output); diff --git a/Aetheria.Rts.Web/scripts/generate-rts-bindings.mjs b/Aetheria.Rts.Web/scripts/generate-rts-bindings.mjs index 3d02eaa8b..f5370becb 100644 --- a/Aetheria.Rts.Web/scripts/generate-rts-bindings.mjs +++ b/Aetheria.Rts.Web/scripts/generate-rts-bindings.mjs @@ -43,7 +43,6 @@ const ipcChannels = [ ["surfaceCatalogIndex", "aetheria-rts:surface-catalog-index"], ["eveSurface", "aetheria-rts:eve-surface"], ["submitEveCommand", "aetheria-rts:submit-eve-command"], - ["mainMenuSurface", "aetheria-rts:main-menu-surface"], ["debugSurface", "aetheria-rts:debug-surface"], ["debugSurfaceWatch", "aetheria-rts:debug-surface-watch"], ["debugSurfaceWatchStop", "aetheria-rts:debug-surface-watch-stop"], @@ -481,13 +480,6 @@ export type AetheriaRuntimeViewportFeedSnapshot = { sampleMs: number; }; -export type AetheriaMenuSurfaceRequest = { - surfaceId?: string; - inGame?: boolean; - canOpenRuntimeInputScreen?: boolean; - panelOnly?: boolean; -}; - export type AetheriaEveSurfaceRequest = { surfaceId?: string; recordKey?: string; @@ -1151,13 +1143,6 @@ export type AetheriaRuntimeViewportFeedSnapshot = { ${renderRtsDocumentTypes("Viewport")} -export type AetheriaMenuSurfaceRequest = { - surfaceId?: string; - inGame?: boolean; - canOpenRuntimeInputScreen?: boolean; - panelOnly?: boolean; -}; - export type AetheriaEveSurfaceRequest = { surfaceId?: string; recordKey?: string; @@ -1232,7 +1217,6 @@ export type AetheriaRtsApi = { surfaceCatalogIndex(): Promise; eveSurface(request: AetheriaEveSurfaceRequest): Promise; submitEveCommand(request: AetheriaEveCommandRequest): Promise; - mainMenuSurface(request: AetheriaMenuSurfaceRequest): Promise; debugSurface(): Promise; watchDebugSurface(callback: (surface: AetheriaMenuSurfaceDocument) => void): () => void; windowControl(action: "minimize" | "maximize" | "close"): Promise; diff --git a/Aetheria.Rts.Web/scripts/verify-stage7b-rts-client.ps1 b/Aetheria.Rts.Web/scripts/verify-stage7b-rts-client.ps1 index 82f27aa96..38318948a 100644 --- a/Aetheria.Rts.Web/scripts/verify-stage7b-rts-client.ps1 +++ b/Aetheria.Rts.Web/scripts/verify-stage7b-rts-client.ps1 @@ -50,32 +50,21 @@ foreach ($symbol in $required) { } } -if (-not (Get-Content Client/app.ts -Raw).Contains('window.aetheriaRts.surfaceCatalogIndex()')) { - Write-Error "Stage 7B verifier failed: RTS renderer does not read the shared CultMesh surface catalog index." -} - $rendererText = Get-Content Client/app.ts -Raw -if (-not $rendererText.Contains('surface.routeHint.kind') -or -not $rendererText.Contains('surface.sources.length')) { - Write-Error "Stage 7B verifier failed: RTS renderer does not expose CultMesh surface route and source metadata." +if (-not $rendererText.Contains('window.aetheriaRts.eveSurface') -or + -not $rendererText.Contains('renderEveSurface') -or + -not $rendererText.Contains('resolveEveDocument') -or + -not $rendererText.Contains('submitEveCommand')) { + Write-Error "Stage 7B verifier failed: RTS renderer is not lowering daemon Eve through the generated preload API." } -if (-not $rendererText.Contains('latestOperationReceipt') -or - -not $rendererText.Contains('receipt.commandId') -or - -not $rendererText.Contains('receipt.operationId') -or - -not $rendererText.Contains('receipt.accepted') -or - -not $rendererText.Contains('receipt.route.kind')) { - Write-Error "Stage 7B verifier failed: RTS renderer does not expose CultMesh operation receipt metadata." -} - -if (-not (Get-Content wwwroot/index.html -Raw).Contains('runtime-surface-details')) { - Write-Error "Stage 7B verifier failed: RTS renderer does not expose runtime surface diagnostics in the shell." +$indexText = Get-Content wwwroot/index.html -Raw +if (-not $indexText.Contains('eve-surface-host')) { + Write-Error "Stage 7B verifier failed: RTS renderer is not mounting the daemon Eve surface host." } $transportLayoutPatterns = @( - '\[[0-9]+\]', 'new Array', - 'command\[', - 'value\[', 'bounds\[', 'viewportSchema', 'commandSchema', @@ -426,17 +415,20 @@ if ($LASTEXITCODE -gt 1) { Write-Error "Stage 7B verifier could not run rg for duplicated renderer contract checks." } -$forbiddenRendererPollingHits = & rg -n 'setInterval|objectsViewport\(viewport\)|gravityViewport\(viewport\)' Client/app.ts 2>$null -if ($LASTEXITCODE -eq 0) { - Write-Error "Stage 7B verifier failed: renderer still owns the RTS viewport polling loop instead of subscribing to the generated feed.`n$forbiddenRendererPollingHits" -} -if ($LASTEXITCODE -gt 1) { - Write-Error "Stage 7B verifier could not run rg for renderer polling checks." -} +$requiredEveRendererSymbols = @( + 'renderEveSurface', + 'eve-surface-host', + 'window.aetheriaRts.eveSurface', + 'resolveEveDocument', + 'gamecult.eve.surface.v1', + 'aetheria.daemon.game' +) -& rg -q 'watchViewportFeed' Client/app.ts -if ($LASTEXITCODE -ne 0) { - Write-Error "Stage 7B verifier failed: renderer is not subscribing to the generated viewport feed." +foreach ($symbol in $requiredEveRendererSymbols) { + & rg -F -q $symbol Client/app.ts wwwroot/index.html + if ($LASTEXITCODE -ne 0) { + Write-Error "Stage 7B verifier failed: expected daemon Eve renderer symbol '$symbol' was not found." + } } $forbiddenBindingPatterns = @( diff --git a/Aetheria.Rts.Web/scripts/verify-stage7c-electron-shell.ps1 b/Aetheria.Rts.Web/scripts/verify-stage7c-electron-rts.ps1 similarity index 57% rename from Aetheria.Rts.Web/scripts/verify-stage7c-electron-shell.ps1 rename to Aetheria.Rts.Web/scripts/verify-stage7c-electron-rts.ps1 index 0f2437eba..51f4a71c2 100644 --- a/Aetheria.Rts.Web/scripts/verify-stage7c-electron-shell.ps1 +++ b/Aetheria.Rts.Web/scripts/verify-stage7c-electron-rts.ps1 @@ -5,48 +5,50 @@ Set-Location $root npm run build if ($LASTEXITCODE -ne 0) { - Write-Error "Stage 7C Electron shell verifier failed: RTS build failed." + Write-Error "Stage 7C Electron RTS verifier failed: RTS build failed." } -if (-not (Get-Content wwwroot\index.html -Raw).Contains('runtime-surface-details')) { - Write-Error "Stage 7C Electron shell verifier failed: built renderer markup does not expose runtime surface diagnostics." +if (-not (Get-Content wwwroot\index.html -Raw).Contains('eve-surface-host')) { + Write-Error "Stage 7C Electron RTS verifier failed: built renderer markup does not host the daemon Eve surface." } -if (-not (Get-Content wwwroot\app.js -Raw).Contains('window.aetheriaRts.surfaceCatalogIndex()')) { - Write-Error "Stage 7C Electron shell verifier failed: built renderer script does not read the CultMesh surface catalog index." +$appText = Get-Content wwwroot\app.js -Raw +if (-not $appText.Contains('window.aetheriaRts.eveSurface') -or + $appText.Contains('legacyViewportMode') -or + $appText.Contains('mainMenuSurface') -or + $appText.Contains('main-menu-mode')) { + Write-Error "Stage 7C Electron RTS verifier failed: renderer is not daemon-Eve-only." } $mainText = Get-Content Electron\main.ts -Raw if (-not $mainText.Contains('await api.surfaceCatalog()') -or -not $mainText.Contains('await api.surfaceCatalogIndex()')) { - Write-Error "Stage 7C Electron shell verifier failed: Electron smoke does not call the preload CultMesh surface catalog APIs." + Write-Error "Stage 7C Electron RTS verifier failed: Electron smoke does not call the preload CultMesh surface catalog APIs." } if (-not $mainText.Contains('await api.renderSplatsViewport') -or -not $mainText.Contains('eveFieldSurface') -or -not $mainText.Contains('embeddedDocuments') -or -not $mainText.Contains('fog.tint')) { - Write-Error "Stage 7C Electron shell verifier failed: Electron smoke does not verify daemon Eve field document lowering." + Write-Error "Stage 7C Electron RTS verifier failed: Electron smoke does not verify daemon Eve field document lowering." } if (-not $mainText.Contains('await api.submitEveCommand') -or -not $mainText.Contains('eveReceipt?.commandId') -or -not $mainText.Contains('eveReceipt?.accepted') -or -not $mainText.Contains('eveReceipt?.route')) { - Write-Error "Stage 7C Electron shell verifier failed: Electron smoke does not verify typed operation receipts through preload." + Write-Error "Stage 7C Electron RTS verifier failed: Electron smoke does not verify typed operation receipts through preload." } -if (-not $mainText.Contains('AETHERIA_RTS_RESOLVED_RUDP_ENDPOINT has been removed') -or - -not $mainText.Contains('AETHERIA_RTS_PEER_CULTMESH_ENDPOINTS has been removed') -or - -not $mainText.Contains('AETHERIA_RTS_VERSE_ID') -or +if (-not $mainText.Contains('AETHERIA_RTS_VERSE_ID') -or -not $mainText.Contains('--verse-id') -or -not $mainText.Contains('AETHERIA_RTS_DAEMON_ID') -or -not $mainText.Contains('--daemon-id')) { - Write-Error "Stage 7C Electron shell verifier failed: Electron launcher does not enforce CultMesh URI daemon targeting." + Write-Error "Stage 7C Electron RTS verifier failed: Electron launcher does not configure daemon Verse identity." } $electron = Join-Path $root "node_modules\electron\dist\electron.exe" if (!(Test-Path -LiteralPath $electron)) { - Write-Error "Stage 7C Electron shell verifier failed: electron.exe was not found at $electron." + Write-Error "Stage 7C Electron RTS verifier failed: electron.exe was not found at $electron." } $tempRoot = Join-Path ([System.IO.Path]::GetTempPath()) ("aetheria-stage7c-electron-" + [Guid]::NewGuid().ToString("N")) @@ -65,16 +67,16 @@ try { $env:AETHERIA_RTS_ELECTRON_SMOKE_RESULT = $resultPath $process = Start-Process -FilePath $electron -ArgumentList "." -WorkingDirectory $root -Wait -PassThru if ($process.ExitCode -ne 0) { - Write-Error "Stage 7C Electron shell verifier failed: Electron exited with code $($process.ExitCode)." + Write-Error "Stage 7C Electron RTS verifier failed: Electron exited with code $($process.ExitCode)." } if (!(Test-Path -LiteralPath $resultPath)) { - Write-Error "Stage 7C Electron shell verifier failed: Electron did not write a smoke result." + Write-Error "Stage 7C Electron RTS verifier failed: Electron did not write a smoke result." } $result = Get-Content -Raw -LiteralPath $resultPath | ConvertFrom-Json if ($result.ok -ne $true) { - Write-Error "Stage 7C Electron shell verifier failed: $($result.error)" + Write-Error "Stage 7C Electron RTS verifier failed: $($result.error)" } } finally { @@ -83,10 +85,8 @@ finally { Remove-Item Env:\AETHERIA_RTS_CULTMESH_PORT -ErrorAction SilentlyContinue Remove-Item Env:\AETHERIA_RTS_VERSE_ID -ErrorAction SilentlyContinue Remove-Item Env:\AETHERIA_RTS_DAEMON_ID -ErrorAction SilentlyContinue - Remove-Item Env:\AETHERIA_RTS_PEER_CULTMESH_ENDPOINTS -ErrorAction SilentlyContinue - Remove-Item Env:\AETHERIA_RTS_RESOLVED_RUDP_ENDPOINT -ErrorAction SilentlyContinue Remove-Item Env:\AETHERIA_RTS_ELECTRON_SMOKE_RESULT -ErrorAction SilentlyContinue Remove-Item -LiteralPath $tempRoot -Recurse -Force -ErrorAction SilentlyContinue } -Write-Host "Stage 7C Electron shell verifier passed." +Write-Host "Stage 7C Electron RTS verifier passed." diff --git a/Aetheria.Rts.Web/wwwroot/index.html b/Aetheria.Rts.Web/wwwroot/index.html index e92a0a70b..284e94870 100644 --- a/Aetheria.Rts.Web/wwwroot/index.html +++ b/Aetheria.Rts.Web/wwwroot/index.html @@ -8,56 +8,14 @@ -
-
-
-
- Aetheria RTS - Connecting -
-
- - 1x - - -
-
- -
- -
+
Connecting
- +
diff --git a/Aetheria.Rts.Web/wwwroot/styles.css b/Aetheria.Rts.Web/wwwroot/styles.css index 61d893687..4789911ba 100644 --- a/Aetheria.Rts.Web/wwwroot/styles.css +++ b/Aetheria.Rts.Web/wwwroot/styles.css @@ -1,8 +1,8 @@ :root { color-scheme: dark; - font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; - background: #111417; + background: #020909; color: #e8edf0; + font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; } * { @@ -11,189 +11,49 @@ html, body { + width: 100%; height: 100%; margin: 0; } body { + -webkit-app-region: drag; overflow: hidden; -} - -.main-menu-host { - display: none; -} - -body.main-menu-mode { - -webkit-app-region: drag; - background: #020606; -} - -body.main-menu-mode .shell { - display: none; -} - -body.eve-game-mode { - -webkit-app-region: drag; background: #020909; } -body.eve-game-mode .shell { - display: none; -} - -body.eve-game-mode .main-menu-host { +.eve-surface-host { -webkit-app-region: no-drag; display: block; + width: 100%; min-height: 100%; } -body.eve-game-mode .window-drag-region { +.status { + position: fixed; + right: 112px; + bottom: 10px; + z-index: 30; + max-width: min(560px, calc(100vw - 140px)); + overflow: hidden; + color: rgba(219, 250, 255, 0.45); + font: 400 11px/1.2 "Ubuntu", system-ui, sans-serif; + text-overflow: ellipsis; + white-space: nowrap; + pointer-events: none; +} + +.window-drag-region { -webkit-app-region: drag; position: fixed; top: 0; right: 0; left: 0; z-index: 15; - display: block; height: 44px; } -body.eve-game-mode .window-controls { - -webkit-app-region: no-drag; - position: fixed; - top: 8px; - right: 10px; - z-index: 20; - display: flex; - gap: 2px; -} - -body.eve-game-mode .window-control { - min-width: 0; - width: 30px; - height: 24px; - padding: 0; - border: 0; - border-radius: 0; - background: transparent; - color: rgba(219, 250, 255, 0.5); - font-family: "Ubuntu", system-ui, sans-serif; - font-size: 12px; - line-height: 24px; -} - -body.eve-game-mode .window-control:hover, -body.eve-game-mode .window-control:focus-visible { - background: rgba(219, 250, 255, 0.09); - color: rgba(240, 252, 255, 0.95); - outline: 0; -} - -body.eve-game-mode .window-control-close:hover, -body.eve-game-mode .window-control-close:focus-visible { - background: rgba(255, 88, 88, 0.28); -} - -body.main-menu-mode .main-menu-host { - -webkit-app-region: no-drag; - display: block; - min-height: 100%; - padding: 0; - background: transparent; -} - -body.main-menu-mode .eve-surface { - width: 100%; - min-height: 100vh; -} - -body.main-menu-mode .eve-text-title, -body.main-menu-mode .eve-text-subtitle { - width: 720px; - color: rgba(209, 242, 255, 0.96); - font-family: "Montserrat", sans-serif; - font-weight: 100; - line-height: 0.95; - text-align: right; -} - -body.main-menu-mode .eve-root-title { - margin: 0 0 2px; - font-size: 86px; -} - -body.main-menu-mode .eve-root-subtitle { - margin: 0 0 36px; - font-size: 39px; -} - -body.main-menu-mode .eve-text, -body.main-menu-mode .eve-metric, -body.main-menu-mode .eve-control-button { - font-family: "Ubuntu", system-ui, sans-serif; -} - -body.main-menu-mode .eve-column { - display: flex; - flex-direction: column; -} - -body.main-menu-mode .eve-control-button { - width: 220px; - min-width: 0; - height: 32px; - padding: 0; - border: 0; - border-radius: 0; - background: transparent; - color: rgba(219, 250, 255, 0.96); - font-size: 24px; - line-height: 32px; - text-align: left; - letter-spacing: 0; - transition: letter-spacing 160ms ease, color 160ms ease; -} - -body.main-menu-mode .eve-control-button:hover, -body.main-menu-mode .eve-control-button:focus-visible { - background: transparent; - color: white; - letter-spacing: 2px; - outline: 0; -} - -body.main-menu-mode .eve-metric { - display: grid; - grid-template-columns: 180px minmax(0, 1fr); - gap: 8px; - width: 520px; - margin: 0 0 8px; -} - -body.main-menu-mode .eve-text:not(.eve-text-title):not(.eve-text-subtitle) { - width: 520px; - margin: 0 0 14px; - color: rgba(219, 250, 255, 0.9); - font-size: 16px; - line-height: 1.25; -} - .window-controls { - display: none; -} - -body.main-menu-mode .window-drag-region { - -webkit-app-region: drag; - position: fixed; - top: 0; - right: 0; - left: 0; - z-index: 15; - display: block; - height: 44px; -} - -body.main-menu-mode .window-controls { -webkit-app-region: no-drag; position: fixed; top: 8px; @@ -203,7 +63,7 @@ body.main-menu-mode .window-controls { gap: 2px; } -body.main-menu-mode .window-control { +.window-control { min-width: 0; width: 30px; height: 24px; @@ -217,222 +77,14 @@ body.main-menu-mode .window-control { line-height: 24px; } -body.main-menu-mode .window-control:hover, -body.main-menu-mode .window-control:focus-visible { +.window-control:hover, +.window-control:focus-visible { background: rgba(219, 250, 255, 0.09); color: rgba(240, 252, 255, 0.95); outline: 0; } -body.main-menu-mode .window-control-close:hover, -body.main-menu-mode .window-control-close:focus-visible { +.window-control-close:hover, +.window-control-close:focus-visible { background: rgba(255, 88, 88, 0.28); } - -button { - min-width: 36px; - height: 32px; - border: 1px solid #41505a; - border-radius: 6px; - background: #20282d; - color: #eef6f8; - font: inherit; -} - -button:hover { - background: #2a353b; -} - -.shell { - display: grid; - grid-template-columns: minmax(0, 1fr) 320px; - height: 100%; - min-height: 0; -} - -.map-pane { - display: grid; - grid-template-rows: 52px minmax(0, 1fr); - min-width: 0; - min-height: 0; - background: #0f1215; -} - -.topbar { - display: flex; - align-items: center; - justify-content: space-between; - gap: 16px; - padding: 0 16px; - border-bottom: 1px solid #263038; - background: #171d21; -} - -.topbar span { - display: inline-block; - margin-left: 12px; - color: #9fb0b9; - font-size: 13px; -} - -.toolbar { - display: flex; - align-items: center; - gap: 8px; -} - -#zoom-label { - min-width: 52px; - color: #c5d2d7; - text-align: center; -} - -#map { - display: block; - width: 100%; - height: 100%; - background: #0d1013; - cursor: crosshair; -} - -.side-pane { - display: grid; - grid-auto-rows: min-content; - gap: 18px; - min-height: 0; - overflow: auto; - padding: 18px; - border-left: 1px solid #263038; - background: #171d21; -} - -.side-pane section { - min-width: 0; -} - -h1, -h2 { - margin: 0 0 10px; - font-size: 15px; - line-height: 1.2; -} - -h1 { - font-size: 18px; -} - -dl { - display: grid; - grid-template-columns: 96px minmax(0, 1fr); - gap: 7px 10px; - margin: 0; - font-size: 13px; -} - -dt { - color: #91a4ad; -} - -dd { - min-width: 0; - margin: 0; - overflow-wrap: anywhere; -} - -#controlled-list { - display: grid; - gap: 8px; - font-size: 13px; -} - -.controlled-row { - display: flex; - align-items: center; - justify-content: space-between; - gap: 8px; - padding: 7px 0; - border-bottom: 1px solid #263038; -} - -.muted { - color: #91a4ad; -} - -.debug-surface-host { - min-width: 0; -} - -.debug-surface-host .eve-surface, -.debug-surface-host .eve-column, -.debug-surface-host .eve-form { - display: flex; - min-width: 0; - flex-direction: column; - gap: 8px; -} - -.debug-surface-host .eve-row { - display: flex; - min-width: 0; - flex-wrap: wrap; - gap: 8px; -} - -.debug-surface-host .eve-text-title { - color: #d8f7ff; - font-size: 16px; - font-weight: 700; -} - -.debug-surface-host .eve-text { - color: #b8c7cf; - font-size: 13px; - line-height: 1.35; -} - -.debug-surface-host .eve-metric { - display: grid; - grid-template-columns: 86px minmax(0, 1fr); - gap: 6px 8px; - font-size: 12px; -} - -.debug-surface-host .eve-metric dt { - color: #91a4ad; -} - -.debug-surface-host .eve-metric dd { - min-width: 0; - margin: 0; - overflow-wrap: anywhere; -} - -.debug-surface-host .eve-control-button { - min-width: 82px; - height: 28px; - border-radius: 4px; - font-size: 12px; -} - -@media (max-width: 760px) { - .shell { - grid-template-columns: 1fr; - grid-template-rows: minmax(0, 1fr) 230px; - } - - .side-pane { - border-top: 1px solid #263038; - border-left: 0; - } - - .topbar { - align-items: flex-start; - flex-direction: column; - height: auto; - padding: 10px 12px; - } - - .map-pane { - grid-template-rows: auto minmax(0, 1fr); - } -} diff --git a/Aetheria.State.Daemon/Program.cs b/Aetheria.State.Daemon/Program.cs index a6be822c3..a50d244b6 100644 --- a/Aetheria.State.Daemon/Program.cs +++ b/Aetheria.State.Daemon/Program.cs @@ -402,6 +402,34 @@ static RudpCultNetSchemaServer StartRtsCultMeshHost( response, AetheriaRuntimeVerseRecordKeys.MainMenuSurface.ToString(), "main-menu").ConfigureAwait(false); + await InjectEveSurfaceSnapshotAsync( + node, + options, + request, + response, + AetheriaRuntimeVerseRecordKeys.MainMenuSettingsSurface.ToString(), + "main-menu-settings").ConfigureAwait(false); + await InjectEveSurfaceSnapshotAsync( + node, + options, + request, + response, + AetheriaRuntimeVerseRecordKeys.MainMenuInputSettingsSurface.ToString(), + "main-menu-input-settings").ConfigureAwait(false); + await InjectEveSurfaceSnapshotAsync( + node, + options, + request, + response, + AetheriaRuntimeVerseRecordKeys.MainMenuPlayerSettingsSurface.ToString(), + "main-menu-player-settings").ConfigureAwait(false); + await InjectEveSurfaceSnapshotAsync( + node, + options, + request, + response, + AetheriaRuntimeVerseRecordKeys.MainMenuVerseSettingsSurface.ToString(), + "main-menu-verse-settings").ConfigureAwait(false); await InjectEveSurfaceSnapshotAsync( node, options, @@ -829,6 +857,10 @@ static Task ReadEveSurfacePublicationAsync(AetheriaStateNod "editor" => node.MutableDocument(AetheriaRuntimeVerseRecordKeys.DaemonEditorSurface).ReadAsync(), "editor-tui" => node.MutableDocument(AetheriaRuntimeVerseRecordKeys.DaemonEditorTuiSurface).ReadAsync(), "main-menu" => node.MutableDocument(AetheriaRuntimeVerseRecordKeys.MainMenuSurface).ReadAsync(), + "main-menu-settings" => node.MutableDocument(AetheriaRuntimeVerseRecordKeys.MainMenuSettingsSurface).ReadAsync(), + "main-menu-input-settings" => node.MutableDocument(AetheriaRuntimeVerseRecordKeys.MainMenuInputSettingsSurface).ReadAsync(), + "main-menu-player-settings" => node.MutableDocument(AetheriaRuntimeVerseRecordKeys.MainMenuPlayerSettingsSurface).ReadAsync(), + "main-menu-verse-settings" => node.MutableDocument(AetheriaRuntimeVerseRecordKeys.MainMenuVerseSettingsSurface).ReadAsync(), "inventory-panel" => node.MutableDocument(AetheriaRuntimeVerseRecordKeys.InventoryPanelSurface).ReadAsync(), "inventory-dropdown" => node.MutableDocument(AetheriaRuntimeVerseRecordKeys.InventoryDropdownSurface).ReadAsync(), "map-menu" => node.MutableDocument(AetheriaRuntimeVerseRecordKeys.MapMenuSurface).ReadAsync(), @@ -1122,10 +1154,11 @@ static async Task PublishDaemonMenuSurfacesAsync( var verseHost = await EnsureVerseHostSettingsAsync(node, options, updatedAtUtc) .ConfigureAwait(false); + var stateBoot = AetheriaRuntimeStateBoot.Inspect( + new DirectoryInfo(Path.GetDirectoryName(node.StatePath) ?? "."), + node.StatePath); var mainMenu = AetheriaRuntimeMainMenuSurfaceBuilder.BuildRoot( - AetheriaRuntimeStateBoot.Inspect( - new DirectoryInfo(Path.GetDirectoryName(node.StatePath) ?? "."), - node.StatePath), + stateBoot, frame, AetheriaRuntimeVerseHostSettingsDocument.FromSnapshot(new AetheriaRuntimeVerseHostSettingsSnapshot( verseHost.ServiceId, @@ -1141,6 +1174,30 @@ static async Task PublishDaemonMenuSurfacesAsync( canOpenRuntimeInputScreen: true, inGame: true, updatedAtUtc); + var mainMenuSettings = AetheriaRuntimeMainMenuSurfaceBuilder.BuildSettings(updatedAtUtc); + var mainMenuInputSettings = AetheriaRuntimeMainMenuSurfaceBuilder.BuildInputSettings( + stateBoot, + playerSettings, + canOpenRuntimeInputScreen: true, + inGame: true, + updatedAtUtc); + var mainMenuPlayerSettings = AetheriaRuntimeMainMenuSurfaceBuilder.BuildPlayerSettings( + playerSettings, + updatedAtUtc); + var mainMenuVerseSettings = AetheriaRuntimeMainMenuSurfaceBuilder.BuildVerseSettings( + AetheriaRuntimeClientTargetSurfaceBuilder.Build( + stateBoot, + AetheriaRuntimeVerseHostSettingsDocument.FromSnapshot(new AetheriaRuntimeVerseHostSettingsSnapshot( + verseHost.ServiceId, + verseHost.VerseId, + verseHost.RootVerse, + verseHost.CanonicalService, + verseHost.LocatedService, + verseHost.CultMeshAddress, + verseHost.Title, + verseHost.Visibility, + verseHost.LastUpdatedAtUtc)), + updatedAtUtc)); var inventoryPanel = AetheriaRuntimeInventoryPanelSurfaceBuilder.BuildFromDocuments( currentEntity, stationRefit, @@ -1165,6 +1222,18 @@ static async Task PublishDaemonMenuSurfacesAsync( await node.MutableDocument(AetheriaRuntimeVerseRecordKeys.MainMenuSurface) .ReplaceAsync(AetheriaRuntimeSurfaceDocuments.ToPortableSurface(mainMenu)) .ConfigureAwait(false); + await node.MutableDocument(AetheriaRuntimeVerseRecordKeys.MainMenuSettingsSurface) + .ReplaceAsync(AetheriaRuntimeSurfaceDocuments.ToPortableSurface(mainMenuSettings)) + .ConfigureAwait(false); + await node.MutableDocument(AetheriaRuntimeVerseRecordKeys.MainMenuInputSettingsSurface) + .ReplaceAsync(AetheriaRuntimeSurfaceDocuments.ToPortableSurface(mainMenuInputSettings)) + .ConfigureAwait(false); + await node.MutableDocument(AetheriaRuntimeVerseRecordKeys.MainMenuPlayerSettingsSurface) + .ReplaceAsync(AetheriaRuntimeSurfaceDocuments.ToPortableSurface(mainMenuPlayerSettings)) + .ConfigureAwait(false); + await node.MutableDocument(AetheriaRuntimeVerseRecordKeys.MainMenuVerseSettingsSurface) + .ReplaceAsync(AetheriaRuntimeSurfaceDocuments.ToPortableSurface(mainMenuVerseSettings)) + .ConfigureAwait(false); await node.MutableDocument(AetheriaRuntimeVerseRecordKeys.InventoryPanelSurface) .ReplaceAsync(AetheriaRuntimeSurfaceDocuments.ToPortableSurface(inventoryPanel)) .ConfigureAwait(false); @@ -1377,6 +1446,10 @@ static async Task PublishOdinSurfaceAnnouncementsAsync( ("aetheria.daemon.editor", "Aetheria Daemon Editor", AetheriaRuntimeVerseRecordKeys.DaemonEditorSurface), ("aetheria.daemon.editor.tui", "Aetheria Daemon Editor TUI", AetheriaRuntimeVerseRecordKeys.DaemonEditorTuiSurface), ("aetheria.main_menu.root", "Main Menu", AetheriaRuntimeVerseRecordKeys.MainMenuSurface), + ("aetheria.main_menu.settings", "Main Menu Settings", AetheriaRuntimeVerseRecordKeys.MainMenuSettingsSurface), + ("aetheria.main_menu.input_settings", "Main Menu Input Settings", AetheriaRuntimeVerseRecordKeys.MainMenuInputSettingsSurface), + ("aetheria.main_menu.player_settings", "Main Menu Player Settings", AetheriaRuntimeVerseRecordKeys.MainMenuPlayerSettingsSurface), + ("aetheria.main_menu.verse_settings", "Main Menu Verse Settings", AetheriaRuntimeVerseRecordKeys.MainMenuVerseSettingsSurface), ("aetheria.inventory.panel", "Inventory Panel", AetheriaRuntimeVerseRecordKeys.InventoryPanelSurface), ("aetheria.inventory.panel.dropdown", "Inventory Dropdown", AetheriaRuntimeVerseRecordKeys.InventoryDropdownSurface), ("aetheria.map.zone_details", "Map Menu", AetheriaRuntimeVerseRecordKeys.MapMenuSurface), diff --git a/Aetheria.State.Verify/Program.cs b/Aetheria.State.Verify/Program.cs index 1743c9db1..2d562a469 100644 --- a/Aetheria.State.Verify/Program.cs +++ b/Aetheria.State.Verify/Program.cs @@ -1309,7 +1309,6 @@ static void RequireTypedBehaviorBodyKeys(string root) string.Join("; ", missingBehaviorSymbols)); } } - static void RequireNoScopedReactiveFacadeWrappers(string root) { var providerPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "AetheriaUnityRuntimeClientProvider.cs"); @@ -3758,7 +3757,7 @@ static void RequireMainMenuSettingsCommands(string root) var requiredMainMenuSymbols = new[] { - "ResolveMainMenuSurface(AetheriaRuntimeMainMenuCommands.PlayerSettingsShellSurfaceId)", + "ResolveMainMenuSurface(AetheriaRuntimeMainMenuCommands.PlayerSettingsSurfaceId)", ".RuntimeState(stateBoot, \"unity-main-menu\")", ".MainMenuSurface(surfaceId, CanOpenRuntimeInputScreen(), InGame)", "AetheriaEveUnitySurfaceHost.RenderRuntime(", @@ -3853,8 +3852,8 @@ static void RequireMainMenuSettingsShellUsesEveSurface(string root) "RenderMenuSurface(", "ResolveMainMenuSurface(AetheriaRuntimeMainMenuCommands.SettingsSurfaceId)", "ResolveMainMenuSurface(AetheriaRuntimeMainMenuCommands.InputSettingsSurfaceId)", - "ResolveMainMenuSurface(AetheriaRuntimeMainMenuCommands.PlayerSettingsShellSurfaceId)", - "ResolveMainMenuSurface(AetheriaRuntimeMainMenuCommands.VerseSettingsShellSurfaceId)", + "ResolveMainMenuSurface(AetheriaRuntimeMainMenuCommands.PlayerSettingsSurfaceId)", + "ResolveMainMenuSurface(AetheriaRuntimeMainMenuCommands.VerseSettingsSurfaceId)", ".MainMenuSurface(surfaceId, CanOpenRuntimeInputScreen(), InGame)", "HandleSettingsSurfaceCommand(", "HandleVerseSettingsSurfaceCommand(", @@ -3932,8 +3931,8 @@ static void RequireMainMenuSettingsShellUsesEveSurface(string root) "public static class AetheriaRuntimeMainMenuCommands", "BuildSettings(", "BuildInputSettings(", - "BuildPlayerSettingsShell(", - "BuildVerseSettingsShell(", + "BuildPlayerSettings(", + "BuildVerseSettings(", "private static AetheriaRuntimeSurfaceDocument BuildRoot(", "private static AetheriaRuntimeSurfaceDocument BuildInputSettings(", "var verseLabel = VerseLabel(verseTitle, verseId)", @@ -9520,7 +9519,6 @@ static void RequireClientTargetBootAuthority(string root) "RuntimeClientTargetFileName = \"aetheria-client.cc\"", "RuntimeReplicaDirectoryName = \"Verses\"", "RuntimeStatePathOverrideEnvironmentVariable = \"AETHERIA_STATE_PATH\"", - "LegacyRuntimeStatePathOverrideEnvironmentVariable = \"AETHERIA_EVE_STATE_PATH\"", "GetClientTargetPath", "GetReplicaStateFilePath", "ResolveStatePathOverride" @@ -10359,7 +10357,7 @@ static void RequireVerseSettingsShellAndBridge(string root) "AetheriaRuntimeMainMenuCommandKind.ShowVerseSettings", "ShowVerseSettingsSurface(", "HandleVerseSettingsSurfaceCommand(EveSurfaceCommandRequest request)", - "ResolveMainMenuSurface(AetheriaRuntimeMainMenuCommands.VerseSettingsShellSurfaceId)", + "ResolveMainMenuSurface(AetheriaRuntimeMainMenuCommands.VerseSettingsSurfaceId)", ".MainMenuSurface(surfaceId, CanOpenRuntimeInputScreen(), InGame)", "AetheriaRuntimeMainMenuSurfaceCommands.TryRead(request, out var command)", "AetheriaRuntimeMainMenuCommandKind.ClientTargetCommand", diff --git a/Packages/org.gamecult.aetheria.eve-runtime/README.md b/Packages/org.gamecult.aetheria.eve-runtime/README.md index f538bf562..2fd8ac07c 100644 --- a/Packages/org.gamecult.aetheria.eve-runtime/README.md +++ b/Packages/org.gamecult.aetheria.eve-runtime/README.md @@ -18,8 +18,7 @@ game surface, so Unity starts as a lowering surface for the daemon Verse member without a hand-wired scene object. Set `AETHERIA_EVE_SURFACE_ID` to mount a different daemon or provider surface such as `aetheria.game.tui`, `aetheria.daemon.editor`, or `aetheria.daemon.editor.tui`; set -`AETHERIA_STATE_PATH` to override the selected local `.cc` state file (`AETHERIA_EVE_STATE_PATH` -still works as a legacy fallback), or disable the automatic mount with +`AETHERIA_STATE_PATH` to override the selected local `.cc` state file, or disable the automatic mount with `AETHERIA_DISABLE_EVE_RUNTIME_BOOTSTRAP=true` or `--aetheria-disable-eve-runtime-bootstrap`. Batchmode disables the bootstrap so compile and smoke gates do not accidentally create renderer state. diff --git a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaClientState.cs b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaClientState.cs index 4804aac6f..eeb9a6bd3 100644 --- a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaClientState.cs +++ b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaClientState.cs @@ -26,7 +26,7 @@ namespace GameCult.Aetheria.State.Verse private readonly Func> _zoneDetailsSurface; private readonly Func> _inventoryPanelSurface; private readonly Func> _inventoryDropdownSurface; - private readonly Func> _mainMenuSurface; + private readonly Func> _mainMenuSurfaceDocument; private readonly Func> _selectedObject; private readonly Func> _inventory; private readonly Func> _starbridgePlayerSeat; @@ -65,7 +65,7 @@ namespace GameCult.Aetheria.State.Verse Func> zoneDetailsSurface, Func> inventoryPanelSurface, Func> inventoryDropdownSurface, - Func> mainMenuSurface, + Func> mainMenuSurfaceDocument, Func> selectedObject, Func> inventory, CultMeshDocumentHandle starbridgeScenario, @@ -102,7 +102,7 @@ namespace GameCult.Aetheria.State.Verse _zoneDetailsSurface = zoneDetailsSurface ?? throw new ArgumentNullException(nameof(zoneDetailsSurface)); _inventoryPanelSurface = inventoryPanelSurface ?? throw new ArgumentNullException(nameof(inventoryPanelSurface)); _inventoryDropdownSurface = inventoryDropdownSurface ?? throw new ArgumentNullException(nameof(inventoryDropdownSurface)); - _mainMenuSurface = mainMenuSurface ?? throw new ArgumentNullException(nameof(mainMenuSurface)); + _mainMenuSurfaceDocument = mainMenuSurfaceDocument ?? throw new ArgumentNullException(nameof(mainMenuSurfaceDocument)); _selectedObject = selectedObject ?? throw new ArgumentNullException(nameof(selectedObject)); _inventory = inventory ?? throw new ArgumentNullException(nameof(inventory)); StarbridgeScenario = starbridgeScenario ?? throw new ArgumentNullException(nameof(starbridgeScenario)); @@ -267,7 +267,7 @@ namespace GameCult.Aetheria.State.Verse if (string.IsNullOrWhiteSpace(surfaceId)) throw new ArgumentException("Main menu surface id must be non-empty.", nameof(surfaceId)); - return _mainMenuSurface(surfaceId, canOpenRuntimeInputScreen, inGame); + return _mainMenuSurfaceDocument(surfaceId, canOpenRuntimeInputScreen, inGame); } public CultMeshDocumentHandle SelectedObject(int zoneIndex) diff --git a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeClientTargetSurfaceBuilder.cs b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeClientTargetSurfaceBuilder.cs index 610e39444..195775cdc 100644 --- a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeClientTargetSurfaceBuilder.cs +++ b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeClientTargetSurfaceBuilder.cs @@ -90,7 +90,7 @@ namespace GameCult.Aetheria.State.Verse AetheriaRuntimeClientTargetCommands.DiscoverVerses), Button( "aetheria.clientTarget.discovery.refresh", - "Refresh Shell", + "Refresh", AetheriaRuntimeClientTargetCommands.Refresh))); return new AetheriaRuntimeSurfaceDocument( diff --git a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeDaemonRenderQueries.cs b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeDaemonRenderQueries.cs index 2bb715ea3..d31b4ec36 100644 --- a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeDaemonRenderQueries.cs +++ b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeDaemonRenderQueries.cs @@ -16,7 +16,7 @@ namespace GameCult.Aetheria.State.Verse } /// - /// Legacy Unity XZ viewport adapter. New daemon/client query code should use CultMath.rect in Aetheria XY space. + /// Unity XZ viewport boundary for callers that still present the world through Unity axes. /// public readonly struct AetheriaRuntimeXzRect { diff --git a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeEveSurfaceCatalog.cs b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeEveSurfaceCatalog.cs index 278d90057..ca0bc772c 100644 --- a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeEveSurfaceCatalog.cs +++ b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeEveSurfaceCatalog.cs @@ -64,10 +64,10 @@ namespace GameCult.Aetheria.State.Verse Surface(AetheriaRuntimeDaemonEditorSurfaceBuilder.SurfaceId, "Aetheria Daemon Editor", AetheriaRuntimeVerseRecordKeys.DaemonEditorSurface.ToString(), "operator", "Daemon editor and surface inventory.", providerId: "aetheria.daemon", providerKind: "editor.daemon", transport: "cultmesh-record"), Surface(AetheriaRuntimeDaemonEditorSurfaceBuilder.TuiSurfaceId, "Aetheria Daemon Editor TUI", AetheriaRuntimeVerseRecordKeys.DaemonEditorTuiSurface.ToString(), "agent", "Compact daemon editor and surface inventory.", providerId: "aetheria.daemon", providerKind: "editor.daemon", transport: "cultmesh-record"), Surface(AetheriaRuntimeMainMenuCommands.RootSurfaceId, "Main Menu", AetheriaRuntimeVerseRecordKeys.MainMenuSurface.ToString(), "player", "Runtime main menu root.", transport: "cultmesh-record"), - Surface(AetheriaRuntimeMainMenuCommands.SettingsSurfaceId, "Main Menu Settings", "", "player", "Runtime main menu settings shell.", status: "client-contextual"), - Surface(AetheriaRuntimeMainMenuCommands.InputSettingsSurfaceId, "Main Menu Input Settings", "", "player", "Main menu input settings handoff shell.", status: "client-contextual"), - Surface(AetheriaRuntimeMainMenuCommands.PlayerSettingsShellSurfaceId, "Main Menu Player Settings", "", "player", "Main menu player settings shell.", status: "client-contextual"), - Surface(AetheriaRuntimeMainMenuCommands.VerseSettingsShellSurfaceId, "Main Menu Verse Settings", "", "player", "Main menu Verse settings shell.", status: "client-contextual"), + Surface(AetheriaRuntimeMainMenuCommands.SettingsSurfaceId, "Main Menu Settings", AetheriaRuntimeVerseRecordKeys.MainMenuSettingsSurface.ToString(), "player", "Runtime main menu settings panel.", transport: "cultmesh-record"), + Surface(AetheriaRuntimeMainMenuCommands.InputSettingsSurfaceId, "Main Menu Input Settings", AetheriaRuntimeVerseRecordKeys.MainMenuInputSettingsSurface.ToString(), "player", "Main menu input settings panel.", transport: "cultmesh-record"), + Surface(AetheriaRuntimeMainMenuCommands.PlayerSettingsSurfaceId, "Main Menu Player Settings", AetheriaRuntimeVerseRecordKeys.MainMenuPlayerSettingsSurface.ToString(), "player", "Main menu player settings panel.", transport: "cultmesh-record"), + Surface(AetheriaRuntimeMainMenuCommands.VerseSettingsSurfaceId, "Main Menu Verse Settings", AetheriaRuntimeVerseRecordKeys.MainMenuVerseSettingsSurface.ToString(), "player", "Main menu Verse settings panel.", transport: "cultmesh-record"), Surface(AetheriaRuntimeInputSettingsCommands.SurfaceId, "Input Settings", "", "player", "Runtime input binding surface.", status: "client-contextual"), Surface(AetheriaRuntimeVerseHostCommands.SurfaceId, "Verse Host Settings", "", "operator", "Verse host visibility and discovery surface.", status: "client-contextual"), Surface(AetheriaRuntimeLoadoutTemplateCommands.SurfaceId, "Loadout Templates", "", "player", "Loadout template save surface.", status: "client-contextual"), diff --git a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeMainMenuSurfaceBuilder.cs b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeMainMenuSurfaceBuilder.cs index 1663c385c..b8a991974 100644 --- a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeMainMenuSurfaceBuilder.cs +++ b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeMainMenuSurfaceBuilder.cs @@ -12,8 +12,8 @@ namespace GameCult.Aetheria.State.Verse public const string RootSurfaceId = "aetheria.main_menu.root"; public const string SettingsSurfaceId = "aetheria.main_menu.settings"; public const string InputSettingsSurfaceId = "aetheria.main_menu.input_settings"; - public const string PlayerSettingsShellSurfaceId = "aetheria.main_menu.player_settings"; - public const string VerseSettingsShellSurfaceId = "aetheria.main_menu.verse_settings"; + public const string PlayerSettingsSurfaceId = "aetheria.main_menu.player_settings"; + public const string VerseSettingsSurfaceId = "aetheria.main_menu.verse_settings"; public const string ContinueRun = "aetheria.main_menu.root.continue"; public const string NewGame = "aetheria.main_menu.root.new_game"; @@ -128,14 +128,14 @@ namespace GameCult.Aetheria.State.Verse version); } - public static AetheriaRuntimeSurfaceDocument BuildPlayerSettingsShell( + public static AetheriaRuntimeSurfaceDocument BuildPlayerSettings( AetheriaRuntimePlayerSettingsDocument playerSettings, string updatedAtUtc, long version = 1) { return WithBackAction( AetheriaRuntimePlayerSettingsSurfaceBuilder.Build(playerSettings, updatedAtUtc, version), - AetheriaRuntimeMainMenuCommands.PlayerSettingsShellSurfaceId, + AetheriaRuntimeMainMenuCommands.PlayerSettingsSurfaceId, AetheriaRuntimeMainMenuCommands.BackToSettings, "Back"); } @@ -295,7 +295,7 @@ namespace GameCult.Aetheria.State.Verse if (canOpenRuntimeInputScreen) { builder.Text( - "The runtime Eve input screen owns low-level InputSystem rebinding and action-bar input edits. This title shell reports typed player-settings state and hands off to that owner.", + "The runtime Eve input screen owns low-level InputSystem rebinding and action-bar input edits. This panel reports typed player-settings state and hands off to that owner.", "aetheria.mainMenu.input.note"); } else if (inGame) @@ -307,7 +307,7 @@ namespace GameCult.Aetheria.State.Verse else { builder.Text( - "This title shell reports the typed player-settings state. Launch a run to open the runtime Eve input screen that owns low-level InputSystem rebinding.", + "This panel reports typed player-settings state. Launch a run to open the runtime Eve input screen that owns low-level InputSystem rebinding.", "aetheria.mainMenu.input.note"); } @@ -328,13 +328,13 @@ namespace GameCult.Aetheria.State.Verse return AetheriaRuntimeSurfaceDocuments.FromPortableSurface(builder.Build()); } - public static AetheriaRuntimeSurfaceDocument BuildVerseSettingsShell( + public static AetheriaRuntimeSurfaceDocument BuildVerseSettings( AetheriaRuntimeSurfaceDocument document, long version = 1) { return WithBackAction( document, - AetheriaRuntimeMainMenuCommands.VerseSettingsShellSurfaceId, + AetheriaRuntimeMainMenuCommands.VerseSettingsSurfaceId, AetheriaRuntimeMainMenuCommands.BackToSettings, "Back"); } @@ -968,10 +968,10 @@ namespace GameCult.Aetheria.State.Verse return TryReadSettings(operationId, out command); case AetheriaRuntimeMainMenuCommands.InputSettingsSurfaceId: return TryReadInputSettings(operationId, out command); - case AetheriaRuntimeMainMenuCommands.PlayerSettingsShellSurfaceId: - return TryReadPlayerSettingsShell(operationId, out command); - case AetheriaRuntimeMainMenuCommands.VerseSettingsShellSurfaceId: - return TryReadVerseSettingsShell(operationId, out command); + case AetheriaRuntimeMainMenuCommands.PlayerSettingsSurfaceId: + return TryReadPlayerSettings(operationId, out command); + case AetheriaRuntimeMainMenuCommands.VerseSettingsSurfaceId: + return TryReadVerseSettings(operationId, out command); default: return false; } @@ -1037,7 +1037,7 @@ namespace GameCult.Aetheria.State.Verse } } - private static bool TryReadPlayerSettingsShell(string commandText, out AetheriaRuntimeMainMenuCommand command) + private static bool TryReadPlayerSettings(string commandText, out AetheriaRuntimeMainMenuCommand command) { if (string.Equals(commandText, AetheriaRuntimeMainMenuCommands.BackToSettings, StringComparison.Ordinal)) { @@ -1055,7 +1055,7 @@ namespace GameCult.Aetheria.State.Verse return false; } - private static bool TryReadVerseSettingsShell(string commandText, out AetheriaRuntimeMainMenuCommand command) + private static bool TryReadVerseSettings(string commandText, out AetheriaRuntimeMainMenuCommand command) { if (string.Equals(commandText, AetheriaRuntimeMainMenuCommands.BackToSettings, StringComparison.Ordinal)) { diff --git a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeStateBoundary.cs b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeStateBoundary.cs index 6ebd4f7d7..e7e732167 100644 --- a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeStateBoundary.cs +++ b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeStateBoundary.cs @@ -21,7 +21,6 @@ namespace GameCult.Aetheria.State.Verse public const string RuntimeDaemonEditorSurfaceFileSuffix = ".daemon.editor.eve.cc"; public const string RuntimeDaemonEditorTuiSurfaceFileSuffix = ".daemon.editor.tui.cc"; public const string RuntimeStatePathOverrideEnvironmentVariable = "AETHERIA_STATE_PATH"; - public const string LegacyRuntimeStatePathOverrideEnvironmentVariable = "AETHERIA_EVE_STATE_PATH"; public const string RuntimeIdOverrideEnvironmentVariable = "AETHERIA_RUNTIME_ID"; public const string UnityRuntimeIdOverrideEnvironmentVariable = "AETHERIA_UNITY_RUNTIME_ID"; @@ -114,11 +113,7 @@ namespace GameCult.Aetheria.State.Verse public static string ResolveStatePathOverride() { - var configured = System.Environment.GetEnvironmentVariable(RuntimeStatePathOverrideEnvironmentVariable); - if (!string.IsNullOrWhiteSpace(configured)) - return configured; - - return System.Environment.GetEnvironmentVariable(LegacyRuntimeStatePathOverrideEnvironmentVariable) ?? ""; + return System.Environment.GetEnvironmentVariable(RuntimeStatePathOverrideEnvironmentVariable) ?? ""; } public static string ResolveRuntimeIdOverride() diff --git a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeVerseClient.cs b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeVerseClient.cs index e2b7212b2..3628a54ba 100644 --- a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeVerseClient.cs +++ b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeVerseClient.cs @@ -68,6 +68,18 @@ namespace GameCult.Aetheria.State.Verse public static CultRecordKey MainMenuSurface { get; } = new CultRecordKey("eve:surface:aetheria.main_menu.root"); + public static CultRecordKey MainMenuSettingsSurface { get; } = + new CultRecordKey("eve:surface:aetheria.main_menu.settings"); + + public static CultRecordKey MainMenuInputSettingsSurface { get; } = + new CultRecordKey("eve:surface:aetheria.main_menu.input_settings"); + + public static CultRecordKey MainMenuPlayerSettingsSurface { get; } = + new CultRecordKey("eve:surface:aetheria.main_menu.player_settings"); + + public static CultRecordKey MainMenuVerseSettingsSurface { get; } = + new CultRecordKey("eve:surface:aetheria.main_menu.verse_settings"); + public static CultRecordKey InventoryPanelSurface { get; } = new CultRecordKey("eve:surface:aetheria.inventory.panel"); @@ -802,16 +814,16 @@ namespace GameCult.Aetheria.State.Verse updatedAtUtc); } - if (string.Equals(surfaceId, AetheriaRuntimeMainMenuCommands.PlayerSettingsShellSurfaceId, StringComparison.Ordinal)) + if (string.Equals(surfaceId, AetheriaRuntimeMainMenuCommands.PlayerSettingsSurfaceId, StringComparison.Ordinal)) { - return AetheriaRuntimeMainMenuSurfaceBuilder.BuildPlayerSettingsShell( + return AetheriaRuntimeMainMenuSurfaceBuilder.BuildPlayerSettings( TryCurrentPlayerSettings(), updatedAtUtc); } - if (string.Equals(surfaceId, AetheriaRuntimeMainMenuCommands.VerseSettingsShellSurfaceId, StringComparison.Ordinal)) + if (string.Equals(surfaceId, AetheriaRuntimeMainMenuCommands.VerseSettingsSurfaceId, StringComparison.Ordinal)) { - return AetheriaRuntimeMainMenuSurfaceBuilder.BuildVerseSettingsShell( + return AetheriaRuntimeMainMenuSurfaceBuilder.BuildVerseSettings( AetheriaRuntimeClientTargetSurfaceBuilder.Build( stateBoot, TryCurrentVerseHostSettings(),