Remove RTS menu shell shortcuts
This commit is contained in:
parent
7c8a12261d
commit
e4b155576f
23 changed files with 265 additions and 1532 deletions
|
|
@ -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<CultMeshSurfaceCatalogIndexDiagnostic>;
|
||||
eveSurface(request: AetheriaEveSurfaceRequest): Promise<AetheriaMenuSurfaceDocument>;
|
||||
submitEveCommand(request: AetheriaEveCommandRequest): Promise<AetheriaRuntimeDaemonCommandReceipt>;
|
||||
mainMenuSurface(request: AetheriaMenuSurfaceRequest): Promise<AetheriaMenuSurfaceDocument>;
|
||||
debugSurface(): Promise<AetheriaMenuSurfaceDocument>;
|
||||
watchDebugSurface(callback: (surface: AetheriaMenuSurfaceDocument) => void): () => void;
|
||||
windowControl(action: "minimize" | "maximize" | "close"): Promise<void>;
|
||||
|
|
|
|||
|
|
@ -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<HTMLCanvasElement>("#map");
|
||||
const host = requiredElement<HTMLElement>("#eve-surface-host");
|
||||
const statusEl = requiredElement<HTMLElement>("#status");
|
||||
const zoomLabel = requiredElement<HTMLOutputElement>("#zoom-label");
|
||||
const selectionTitle = requiredElement<HTMLElement>("#selection-title");
|
||||
const selectionDetails = requiredElement<HTMLElement>("#selection-details");
|
||||
const frameDetails = requiredElement<HTMLElement>("#frame-details");
|
||||
const starbridgeDetails = requiredElement<HTMLElement>("#starbridge-details");
|
||||
const runtimeSurfaceDetails = requiredElement<HTMLElement>("#runtime-surface-details");
|
||||
const controlledList = requiredElement<HTMLElement>("#controlled-list");
|
||||
const debugSurfaceHost = requiredElement<HTMLElement>("#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<ReturnType<AetheriaRtsApi["surfaceCatalogIndex"]>> | 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<void>) | null = null;
|
||||
const mainMenuMode = window.location.hash === "#main-menu";
|
||||
const legacyViewportMode = window.location.hash === "#legacy-viewport";
|
||||
let eveSurfacePoll: number | null = null;
|
||||
|
||||
function requiredElement<TElement extends Element>(selector: string): TElement {
|
||||
const element = document.querySelector<TElement>(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<void> {
|
||||
document.body.classList.remove("main-menu-mode");
|
||||
document.body.classList.add("eve-game-mode");
|
||||
viewportFeedUnsubscribe?.();
|
||||
|
||||
let host = document.querySelector<HTMLElement>("#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<void> {
|
|||
});
|
||||
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<void> {
|
||||
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<HTMLButtonElement>("[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<void> {
|
||||
if (intent.command.startsWith("aetheria.main_menu.")) {
|
||||
handleMenuCommand(intent.command);
|
||||
|
|
@ -370,519 +239,45 @@ async function submitEveCommand(intent: EveCommandIntent): Promise<void> {
|
|||
}
|
||||
}
|
||||
|
||||
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<void> {
|
||||
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<HTMLButtonElement>("[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 ?? [])]
|
||||
])
|
||||
: `<dt class="muted">State</dt><dd class="muted">Click a visible object on the map.</dd>`;
|
||||
|
||||
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)]
|
||||
])
|
||||
: `<dt class="muted">State</dt><dd class="muted">Discovering runtime surfaces.</dd>`;
|
||||
|
||||
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)]
|
||||
])
|
||||
: `<dt class="muted">State</dt><dd class="muted">No Starbridge session.</dd>`;
|
||||
|
||||
controlledList.innerHTML = "";
|
||||
const controlledObjects = latest.objects.filter(object => object.controlled);
|
||||
if (controlledObjects.length === 0) {
|
||||
controlledList.innerHTML = `<span class="muted">No controlled units in viewport.</span>`;
|
||||
return;
|
||||
}
|
||||
|
||||
for (const object of controlledObjects) {
|
||||
const row = document.createElement("button");
|
||||
row.type = "button";
|
||||
row.className = "controlled-row";
|
||||
row.innerHTML = `<span>${escapeHtml(object.displayName || object.entityKey)}</span><span>#${object.entityIndex}</span>`;
|
||||
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]) => `<dt>${escapeHtml(key)}</dt><dd>${escapeHtml(value)}</dd>`)
|
||||
.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<void> {
|
||||
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<void> {
|
||||
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<ReturnType<AetheriaRtsApi["setMoveVector"]>>,
|
||||
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<HTMLButtonElement>("#zoom-in")?.addEventListener("click", () => {
|
||||
zoom = Math.min(12, zoom * 1.4);
|
||||
updateZoomLabel();
|
||||
void refresh();
|
||||
});
|
||||
|
||||
document.querySelector<HTMLButtonElement>("#zoom-out")?.addEventListener("click", () => {
|
||||
zoom = Math.max(0.2, zoom / 1.4);
|
||||
updateZoomLabel();
|
||||
void refresh();
|
||||
});
|
||||
|
||||
document.querySelector<HTMLButtonElement>("#refresh")?.addEventListener("click", () => {
|
||||
void refresh();
|
||||
});
|
||||
|
||||
window.addEventListener("resize", () => {
|
||||
render();
|
||||
});
|
||||
|
||||
function updateZoomLabel(): void {
|
||||
zoomLabel.value = `${zoom.toFixed(1)}x`;
|
||||
}
|
||||
|
||||
async function refresh(): Promise<void> {
|
||||
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();
|
||||
|
|
|
|||
|
|
@ -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<ViewportResponse> {
|
||||
return this.queryExecutors.mapViewport(request);
|
||||
return this.aetheria.zone().viewport.within(request);
|
||||
}
|
||||
|
||||
public async objectsViewport(request: ViewportRequest): Promise<ObjectsViewportResponse> {
|
||||
return this.queryExecutors.objectsViewport(request);
|
||||
return this.aetheria.zone().objects.visibleWithin(request);
|
||||
}
|
||||
|
||||
public async gravityViewport(request: ViewportRequest): Promise<GravityViewportResponse> {
|
||||
return this.queryExecutors.gravityViewport(request);
|
||||
return this.aetheria.zone().gravity.within(request);
|
||||
}
|
||||
|
||||
public async renderSplatsViewport(request: ViewportRequest): Promise<RenderSplatsViewportResponse> {
|
||||
return this.queryExecutors.renderSplatsViewport(request);
|
||||
return this.aetheria.zone().renderSplats.within(request);
|
||||
}
|
||||
|
||||
public async selectedObject(request: SelectedObjectRequest): Promise<SelectedObjectDocument> {
|
||||
return this.queryExecutors.selectedObject(request);
|
||||
return this.aetheria.selectedObject(request.entityIndex);
|
||||
}
|
||||
|
||||
public async inventory(request: SelectedObjectRequest): Promise<InventoryDocument> {
|
||||
return this.queryExecutors.inventory(request);
|
||||
return this.aetheria.inventory(request.entityIndex);
|
||||
}
|
||||
|
||||
public async daemonHealth(): Promise<DaemonHealthDocument> {
|
||||
|
|
@ -333,14 +326,6 @@ export class AetheriaCultMeshClient {
|
|||
return this.aetheria.daemon.assetManifest();
|
||||
}
|
||||
|
||||
public async mainMenuSurface(request: AetheriaMenuSurfaceRequest = {}): Promise<AetheriaMenuSurfaceDocument> {
|
||||
return buildMainMenuSurface(
|
||||
request.surfaceId || "aetheria.main_menu.root",
|
||||
request.canOpenRuntimeInputScreen === true,
|
||||
request.inGame === true,
|
||||
request.panelOnly === true);
|
||||
}
|
||||
|
||||
public async eveSurface(request: AetheriaEveSurfaceRequest = {}): Promise<AetheriaMenuSurfaceDocument> {
|
||||
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<string, string>,
|
||||
layoutOrChild?: Record<string, string> | AetheriaMenuSurfaceComponent,
|
||||
styleOrChild?: Record<string, string> | 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<string, string>,
|
||||
style?: Record<string, string>,
|
||||
): 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<string, string> {
|
||||
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":
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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<Record<string, u
|
|||
const api = window.aetheriaRts;
|
||||
const status = document.querySelector("#status")?.textContent ?? "";
|
||||
const bodyMode = document.body.className;
|
||||
const eveHostText = document.querySelector("#main-menu-host")?.textContent ?? "";
|
||||
const eveHostText = document.querySelector("#eve-surface-host")?.textContent ?? "";
|
||||
const eveSurface = api ? await api.eveSurface({ recordKey: "eve:surface:aetheria.daemon.game" }) : null;
|
||||
const health = api ? await api.daemonHealth() : null;
|
||||
const authority = api ? await api.authorityStatus() : null;
|
||||
|
|
@ -374,8 +356,6 @@ function registerIpc(): void {
|
|||
daemonRunning: daemonProcess != null && !daemonProcess.killed,
|
||||
daemonMode: launchLocalDaemon ? "local" : "remote",
|
||||
}));
|
||||
ipcMain.handle("aetheria-rts:main-menu-surface", async (_event, request) =>
|
||||
requireClient().mainMenuSurface(request));
|
||||
ipcMain.handle("aetheria-rts:debug-surface", () => readDebugSurface());
|
||||
ipcMain.handle("aetheria-rts:debug-surface-watch", (event, subscriptionId: string) => {
|
||||
if (!subscriptionId)
|
||||
|
|
|
|||
|
|
@ -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)}`;
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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<CultMeshSurfaceCatalogIndexDiagnostic>;
|
||||
eveSurface(request: AetheriaEveSurfaceRequest): Promise<AetheriaMenuSurfaceDocument>;
|
||||
submitEveCommand(request: AetheriaEveCommandRequest): Promise<AetheriaRuntimeDaemonCommandReceipt>;
|
||||
mainMenuSurface(request: AetheriaMenuSurfaceRequest): Promise<AetheriaMenuSurfaceDocument>;
|
||||
debugSurface(): Promise<AetheriaMenuSurfaceDocument>;
|
||||
watchDebugSurface(callback: (surface: AetheriaMenuSurfaceDocument) => void): () => void;
|
||||
windowControl(action: "minimize" | "maximize" | "close"): Promise<void>;
|
||||
|
|
|
|||
|
|
@ -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<unknown>',
|
||||
'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 = @(
|
||||
|
|
|
|||
|
|
@ -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."
|
||||
|
|
@ -8,56 +8,14 @@
|
|||
<link rel="stylesheet" href="styles.css">
|
||||
</head>
|
||||
<body>
|
||||
<main class="shell">
|
||||
<section class="map-pane">
|
||||
<header class="topbar">
|
||||
<div>
|
||||
<strong>Aetheria RTS</strong>
|
||||
<span id="status">Connecting</span>
|
||||
</div>
|
||||
<div class="toolbar">
|
||||
<button id="zoom-out" type="button" title="Zoom out">-</button>
|
||||
<output id="zoom-label">1x</output>
|
||||
<button id="zoom-in" type="button" title="Zoom in">+</button>
|
||||
<button id="refresh" type="button" title="Refresh viewport">Refresh</button>
|
||||
</div>
|
||||
</header>
|
||||
<canvas id="map"></canvas>
|
||||
</section>
|
||||
<aside class="side-pane">
|
||||
<section>
|
||||
<h1 id="selection-title">No Selection</h1>
|
||||
<dl id="selection-details"></dl>
|
||||
</section>
|
||||
<section>
|
||||
<h2>Controlled</h2>
|
||||
<div id="controlled-list"></div>
|
||||
</section>
|
||||
<section>
|
||||
<h2>Starbridge</h2>
|
||||
<dl id="starbridge-details"></dl>
|
||||
</section>
|
||||
<section>
|
||||
<h2>Frame</h2>
|
||||
<dl id="frame-details"></dl>
|
||||
</section>
|
||||
<section>
|
||||
<h2>Runtime Surfaces</h2>
|
||||
<dl id="runtime-surface-details"></dl>
|
||||
</section>
|
||||
<section>
|
||||
<h2>CultUI Debug</h2>
|
||||
<div id="debug-surface-host" class="debug-surface-host"></div>
|
||||
</section>
|
||||
</aside>
|
||||
</main>
|
||||
<div id="status" class="status">Connecting</div>
|
||||
<div class="window-drag-region" aria-hidden="true"></div>
|
||||
<div class="window-controls" aria-label="Window controls">
|
||||
<button class="window-control" type="button" data-window-control="minimize" aria-label="Minimize">-</button>
|
||||
<button class="window-control" type="button" data-window-control="maximize" aria-label="Maximize">[]</button>
|
||||
<button class="window-control window-control-close" type="button" data-window-control="close" aria-label="Close">x</button>
|
||||
</div>
|
||||
<section id="main-menu-host" class="main-menu-host" aria-label="Aetheria main menu"></section>
|
||||
<main id="eve-surface-host" class="eve-surface-host" aria-label="Aetheria daemon Eve surface"></main>
|
||||
<script type="module" src="app.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<EveSurfaceDocument?> ReadEveSurfacePublicationAsync(AetheriaStateNod
|
|||
"editor" => node.MutableDocument<EveSurfaceDocument>(AetheriaRuntimeVerseRecordKeys.DaemonEditorSurface).ReadAsync(),
|
||||
"editor-tui" => node.MutableDocument<EveSurfaceDocument>(AetheriaRuntimeVerseRecordKeys.DaemonEditorTuiSurface).ReadAsync(),
|
||||
"main-menu" => node.MutableDocument<EveSurfaceDocument>(AetheriaRuntimeVerseRecordKeys.MainMenuSurface).ReadAsync(),
|
||||
"main-menu-settings" => node.MutableDocument<EveSurfaceDocument>(AetheriaRuntimeVerseRecordKeys.MainMenuSettingsSurface).ReadAsync(),
|
||||
"main-menu-input-settings" => node.MutableDocument<EveSurfaceDocument>(AetheriaRuntimeVerseRecordKeys.MainMenuInputSettingsSurface).ReadAsync(),
|
||||
"main-menu-player-settings" => node.MutableDocument<EveSurfaceDocument>(AetheriaRuntimeVerseRecordKeys.MainMenuPlayerSettingsSurface).ReadAsync(),
|
||||
"main-menu-verse-settings" => node.MutableDocument<EveSurfaceDocument>(AetheriaRuntimeVerseRecordKeys.MainMenuVerseSettingsSurface).ReadAsync(),
|
||||
"inventory-panel" => node.MutableDocument<EveSurfaceDocument>(AetheriaRuntimeVerseRecordKeys.InventoryPanelSurface).ReadAsync(),
|
||||
"inventory-dropdown" => node.MutableDocument<EveSurfaceDocument>(AetheriaRuntimeVerseRecordKeys.InventoryDropdownSurface).ReadAsync(),
|
||||
"map-menu" => node.MutableDocument<EveSurfaceDocument>(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<EveSurfaceDocument>(AetheriaRuntimeVerseRecordKeys.MainMenuSurface)
|
||||
.ReplaceAsync(AetheriaRuntimeSurfaceDocuments.ToPortableSurface(mainMenu))
|
||||
.ConfigureAwait(false);
|
||||
await node.MutableDocument<EveSurfaceDocument>(AetheriaRuntimeVerseRecordKeys.MainMenuSettingsSurface)
|
||||
.ReplaceAsync(AetheriaRuntimeSurfaceDocuments.ToPortableSurface(mainMenuSettings))
|
||||
.ConfigureAwait(false);
|
||||
await node.MutableDocument<EveSurfaceDocument>(AetheriaRuntimeVerseRecordKeys.MainMenuInputSettingsSurface)
|
||||
.ReplaceAsync(AetheriaRuntimeSurfaceDocuments.ToPortableSurface(mainMenuInputSettings))
|
||||
.ConfigureAwait(false);
|
||||
await node.MutableDocument<EveSurfaceDocument>(AetheriaRuntimeVerseRecordKeys.MainMenuPlayerSettingsSurface)
|
||||
.ReplaceAsync(AetheriaRuntimeSurfaceDocuments.ToPortableSurface(mainMenuPlayerSettings))
|
||||
.ConfigureAwait(false);
|
||||
await node.MutableDocument<EveSurfaceDocument>(AetheriaRuntimeVerseRecordKeys.MainMenuVerseSettingsSurface)
|
||||
.ReplaceAsync(AetheriaRuntimeSurfaceDocuments.ToPortableSurface(mainMenuVerseSettings))
|
||||
.ConfigureAwait(false);
|
||||
await node.MutableDocument<EveSurfaceDocument>(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),
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ namespace GameCult.Aetheria.State.Verse
|
|||
private readonly Func<int, CultMeshDocumentHandle<AetheriaRuntimeSurfaceDocument>> _zoneDetailsSurface;
|
||||
private readonly Func<AetheriaRuntimeInventoryPanelSurfaceRequest, CultMeshDocumentHandle<AetheriaRuntimeSurfaceDocument>> _inventoryPanelSurface;
|
||||
private readonly Func<AetheriaRuntimeInventoryDropdownSurfaceRequest, CultMeshDocumentHandle<AetheriaRuntimeSurfaceDocument>> _inventoryDropdownSurface;
|
||||
private readonly Func<string, bool, bool, CultMeshDocumentHandle<AetheriaRuntimeSurfaceDocument>> _mainMenuSurface;
|
||||
private readonly Func<string, bool, bool, CultMeshDocumentHandle<AetheriaRuntimeSurfaceDocument>> _mainMenuSurfaceDocument;
|
||||
private readonly Func<int, CultMeshDocumentHandle<AetheriaRuntimeSelectedObjectDocument>> _selectedObject;
|
||||
private readonly Func<int, CultMeshDocumentHandle<AetheriaRuntimeInventoryDocument>> _inventory;
|
||||
private readonly Func<string, CultMeshDocumentHandle<AetheriaRuntimeStarbridgePlayerSeatDocument>> _starbridgePlayerSeat;
|
||||
|
|
@ -65,7 +65,7 @@ namespace GameCult.Aetheria.State.Verse
|
|||
Func<int, CultMeshDocumentHandle<AetheriaRuntimeSurfaceDocument>> zoneDetailsSurface,
|
||||
Func<AetheriaRuntimeInventoryPanelSurfaceRequest, CultMeshDocumentHandle<AetheriaRuntimeSurfaceDocument>> inventoryPanelSurface,
|
||||
Func<AetheriaRuntimeInventoryDropdownSurfaceRequest, CultMeshDocumentHandle<AetheriaRuntimeSurfaceDocument>> inventoryDropdownSurface,
|
||||
Func<string, bool, bool, CultMeshDocumentHandle<AetheriaRuntimeSurfaceDocument>> mainMenuSurface,
|
||||
Func<string, bool, bool, CultMeshDocumentHandle<AetheriaRuntimeSurfaceDocument>> mainMenuSurfaceDocument,
|
||||
Func<int, CultMeshDocumentHandle<AetheriaRuntimeSelectedObjectDocument>> selectedObject,
|
||||
Func<int, CultMeshDocumentHandle<AetheriaRuntimeInventoryDocument>> inventory,
|
||||
CultMeshDocumentHandle<AetheriaRuntimeStarbridgeScenarioDocument> 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<AetheriaRuntimeSelectedObjectDocument> SelectedObject(int zoneIndex)
|
||||
|
|
|
|||
|
|
@ -90,7 +90,7 @@ namespace GameCult.Aetheria.State.Verse
|
|||
AetheriaRuntimeClientTargetCommands.DiscoverVerses),
|
||||
Button(
|
||||
"aetheria.clientTarget.discovery.refresh",
|
||||
"Refresh Shell",
|
||||
"Refresh",
|
||||
AetheriaRuntimeClientTargetCommands.Refresh)));
|
||||
|
||||
return new AetheriaRuntimeSurfaceDocument(
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ namespace GameCult.Aetheria.State.Verse
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
public readonly struct AetheriaRuntimeXzRect
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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"),
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
|
|
|
|||
Loading…
Reference in a new issue