diff --git a/Aetheria.Rts.Web/Client/aetheria-rts-contract.ts b/Aetheria.Rts.Web/Client/aetheria-rts-contract.ts index 83c37ccee..998bb3487 100644 --- a/Aetheria.Rts.Web/Client/aetheria-rts-contract.ts +++ b/Aetheria.Rts.Web/Client/aetheria-rts-contract.ts @@ -39,12 +39,12 @@ export type AetheriaRuntimeViewportFeedRequest = { export type AetheriaRuntimeViewportFeedSnapshot = { viewport: ViewportResponse; - selectedObject: SelectedObjectProjection | null; - inventory: InventoryProjection | null; - daemonHealth: DaemonHealthProjection; - authorityStatus: AuthorityStatusProjection; - starbridgeSession: StarbridgeSessionProjection; - assetManifest: AssetManifestProjection; + selectedObject: SelectedObjectDocument | null; + inventory: InventoryDocument | null; + daemonHealth: DaemonHealthDocument; + authorityStatus: AuthorityStatusDocument; + starbridgeSession: StarbridgeSessionDocument; + assetManifest: AssetManifestDocument; receivedAtUtc: string; sampleMs: number; }; @@ -130,7 +130,7 @@ export type InventoryItem = { iconAsset: AssetRef; }; -export type SelectedObjectProjection = { +export type SelectedObjectDocument = { schema: string; frameId: number; runId: string; @@ -139,7 +139,7 @@ export type SelectedObjectProjection = { selected: ViewObject | null; }; -export type InventoryProjection = { +export type InventoryDocument = { schema: string; frameId: number; runId: string; @@ -151,7 +151,7 @@ export type InventoryProjection = { cargo: InventoryItem[]; }; -export type DaemonHealthProjection = { +export type DaemonHealthDocument = { schema: string; daemonId: string; verseId: string; @@ -167,7 +167,7 @@ export type DaemonHealthProjection = { commandBoundaryPath: string; }; -export type AuthorityStatusProjection = { +export type AuthorityStatusDocument = { schema: string; verseId: string; policyId: string; @@ -175,10 +175,10 @@ export type AuthorityStatusProjection = { hostRuntimeId: string; defaultMode: string; updatedAtUtc: string; - rules: AuthorityRuleProjection[]; + rules: AuthorityRuleDocument[]; }; -export type AuthorityRuleProjection = { +export type AuthorityRuleDocument = { ruleId: string; subjectPrefix: string; claimKinds: string[]; @@ -188,7 +188,7 @@ export type AuthorityRuleProjection = { priority: number; }; -export type StarbridgeSessionProjection = { +export type StarbridgeSessionDocument = { schema: string; frameId: number; publishedAtUtc: string; @@ -200,13 +200,13 @@ export type StarbridgeSessionProjection = { zoneName: string; phase: string; currentWaveIndex: number; - baseStatus: StarbridgeBaseStatusProjection; - stationStock: StarbridgeStationStockItemProjection[]; - waveForecast: StarbridgeWaveForecastProjection[]; - runtimeRoles: StarbridgeRuntimeRoleProjection[]; + baseStatus: StarbridgeBaseStatusDocument; + stationStock: StarbridgeStationStockItemDocument[]; + waveForecast: StarbridgeWaveForecastDocument[]; + runtimeRoles: StarbridgeRuntimeRoleDocument[]; }; -export type StarbridgeBaseStatusProjection = { +export type StarbridgeBaseStatusDocument = { entityKey: string; displayName: string; hull: number; @@ -215,7 +215,7 @@ export type StarbridgeBaseStatusProjection = { isActive: boolean; }; -export type StarbridgeStationStockItemProjection = { +export type StarbridgeStationStockItemDocument = { itemKey: string; quantity: number; quality: number; @@ -224,7 +224,7 @@ export type StarbridgeStationStockItemProjection = { iconAsset: AssetRef; }; -export type AssetManifestProjection = { +export type AssetManifestDocument = { schema: string; publishedAtUtc: string; runId: string; @@ -250,7 +250,7 @@ export type AssetRef = { metadata: Record; }; -export type StarbridgeWaveForecastProjection = { +export type StarbridgeWaveForecastDocument = { waveIndex: number; displayName: string; attackerKeys: string[]; @@ -258,7 +258,7 @@ export type StarbridgeWaveForecastProjection = { recoveredTechnologyKeys: string[]; }; -export type StarbridgeRuntimeRoleProjection = { +export type StarbridgeRuntimeRoleDocument = { runtimeId: string; role: string; entityKey: string; @@ -294,12 +294,12 @@ export type AetheriaRtsApi = { mapViewport(request: Viewport): Promise; objectsViewport(request: Viewport): Promise; gravityViewport(request: Viewport): Promise; - selectedObject(request: AetheriaRuntimeSelectedObjectRequest): Promise; - inventory(request: AetheriaRuntimeSelectedObjectRequest): Promise; - daemonHealth(): Promise; - authorityStatus(): Promise; - starbridgeSession(): Promise; - assetManifest(): Promise; + selectedObject(request: AetheriaRuntimeSelectedObjectRequest): Promise; + inventory(request: AetheriaRuntimeSelectedObjectRequest): Promise; + daemonHealth(): Promise; + authorityStatus(): Promise; + starbridgeSession(): Promise; + assetManifest(): Promise; watchViewportFeed(request: AetheriaRuntimeViewportFeedRequest, callback: (snapshot: AetheriaRuntimeViewportFeedSnapshot) => void): () => void; setMoveVector(request: AetheriaRuntimeSetMoveVectorRequest): Promise; setTarget(request: AetheriaRuntimeSetTargetRequest): Promise; diff --git a/Aetheria.Rts.Web/Client/app.ts b/Aetheria.Rts.Web/Client/app.ts index 30aade699..4664b6192 100644 --- a/Aetheria.Rts.Web/Client/app.ts +++ b/Aetheria.Rts.Web/Client/app.ts @@ -1,14 +1,14 @@ import type { AetheriaRtsApi, - AuthorityStatusProjection, + AuthorityStatusDocument, AetheriaRuntimeViewportFeedSnapshot, BodyView, - DaemonHealthProjection, + DaemonHealthDocument, GravityInfluence, InventoryItem, - InventoryProjection, - SelectedObjectProjection, - StarbridgeSessionProjection, + InventoryDocument, + SelectedObjectDocument, + StarbridgeSessionDocument, ViewObject, Viewport, ViewportResponse, @@ -38,11 +38,11 @@ let centerX = 0; let centerY = 0; let selectedEntityIndex = -1; let latest: ViewportResponse | null = null; -let latestSelected: SelectedObjectProjection | null = null; -let latestInventory: InventoryProjection | null = null; -let latestDaemonHealth: DaemonHealthProjection | null = null; -let latestAuthority: AuthorityStatusProjection | null = null; -let latestStarbridge: StarbridgeSessionProjection | null = null; +let latestSelected: SelectedObjectDocument | null = null; +let latestInventory: InventoryDocument | null = null; +let latestDaemonHealth: DaemonHealthDocument | null = null; +let latestAuthority: AuthorityStatusDocument | null = null; +let latestStarbridge: StarbridgeSessionDocument | null = null; let latestSurfaceIndex: Awaited> | null = null; let latestOperationReceipt: string | null = null; let latestReceivedAt = 0; @@ -554,7 +554,7 @@ function surfaceListText(surfaces: ReadonlyArray<{ .join(", "); } -function stockText(session: StarbridgeSessionProjection): string { +function stockText(session: StarbridgeSessionDocument): string { if (session.stationStock.length === 0) { return "empty"; } @@ -565,7 +565,7 @@ function stockText(session: StarbridgeSessionProjection): string { .join(", "); } -function waveText(session: StarbridgeSessionProjection): string { +function waveText(session: StarbridgeSessionDocument): string { const wave = session.waveForecast[0]; if (!wave) { return "none"; @@ -575,7 +575,7 @@ function waveText(session: StarbridgeSessionProjection): string { return `${wave.displayName || `Wave ${wave.waveIndex}`}${attackers}`; } -function roleText(session: StarbridgeSessionProjection): string { +function roleText(session: StarbridgeSessionDocument): string { if (session.runtimeRoles.length === 0) { return "none"; } diff --git a/Aetheria.Rts.Web/Electron/aetheria-cultmesh.ts b/Aetheria.Rts.Web/Electron/aetheria-cultmesh.ts index 1e67160d7..e3886df46 100644 --- a/Aetheria.Rts.Web/Electron/aetheria-cultmesh.ts +++ b/Aetheria.Rts.Web/Electron/aetheria-cultmesh.ts @@ -18,45 +18,45 @@ import { AetheriaRtsSchemas, createAetheriaRuntimeRtsDocuments, createAetheriaRuntimeRtsOperationHandles, - createAetheriaRuntimeRtsVerseFacade, + createAetheriaRuntimeRtsVerseHandles, createAetheriaRuntimeRtsQueryHandles, describeAetheriaRuntimeRtsLiveFeedSurface, describeAetheriaRuntimeRtsQueryHandles, describeAetheriaRuntimeRtsSurfaceCatalog, type AetheriaRuntimeDaemonCommandReceipt, - type AssetManifestProjection, - type AuthorityStatusProjection, - type DaemonHealthProjection, + type AssetManifestDocument, + type AuthorityStatusDocument, + type DaemonHealthDocument, type GravityViewportResponse, - type InventoryProjection, + type InventoryDocument, type ObjectsViewportResponse, type AetheriaRuntimeRtsLiveFeedDiagnostic, - type AetheriaRuntimeRtsProjectionDiagnostic, + type AetheriaRuntimeRtsQueryDiagnostic, type AetheriaRuntimeRtsSurfaceCatalogDiagnostic, type AetheriaRuntimeRtsOperationHandles, - type AetheriaRuntimeRtsVerseFacade, + type AetheriaRuntimeRtsVerseHandles, type AetheriaRuntimeRtsDocuments, type AetheriaRuntimeViewportFeedRequest, type AetheriaRuntimeViewportFeedSnapshot, type RtsSetMoveVectorRequest, type RtsSetTargetRequest, - type SelectedObjectProjection, + type SelectedObjectDocument, type SelectedObjectRequest, - type StarbridgeSessionProjection, + type StarbridgeSessionDocument, type ViewportRequest, type ViewportResponse, } from "./aetheria-rts-bindings.js"; import { - projectAuthorityStatus, - projectAssetManifest, - projectDaemonHealth, - projectGravityViewportFromFrame, - projectInventoryFromFrame, - projectObjectsViewportFromFrame, - projectSelectedObjectFromFrame, - projectStarbridgeSessionSummary, - projectViewportFromFrame, -} from "./aetheria-rts-local-projection.js"; + readAuthorityStatusDocument, + readAssetManifestDocument, + readDaemonHealthDocument, + buildGravityViewportDocumentFromFrame, + buildInventoryDocumentFromFrame, + buildObjectsViewportDocumentFromFrame, + buildSelectedObjectDocumentFromFrame, + readStarbridgeSessionSummaryDocument, + buildViewportDocumentFromFrame, +} from "./aetheria-rts-local-documents.js"; const connectionId = 0x43554c54; @@ -81,9 +81,9 @@ export type { AetheriaRuntimeViewportFeedSnapshot, RtsSetMoveVectorRequest, RtsSetTargetRequest, - SelectedObjectProjection, + SelectedObjectDocument, SelectedObjectRequest, - StarbridgeSessionProjection, + StarbridgeSessionDocument, ViewObject, ViewportRequest, ViewportResponse, @@ -121,15 +121,15 @@ export class AetheriaCultMeshClient { }, ); const executors = { - mapViewport: async (request: ViewportRequest) => projectViewportFromFrame(await this.fetchLatestFrameDocument(), request), - objectsViewport: async (request: ViewportRequest) => projectObjectsViewportFromFrame(await this.fetchLatestFrameDocument(), request), - gravityViewport: async (request: ViewportRequest) => projectGravityViewportFromFrame(await this.fetchLatestFrameDocument(), request), - selectedObject: async (request: SelectedObjectRequest) => projectSelectedObjectFromFrame(await this.fetchLatestFrameDocument(), request), - inventory: async (request: SelectedObjectRequest) => projectInventoryFromFrame(await this.fetchLatestFrameDocument(), request), - daemonHealth: async () => projectDaemonHealth(await this.fetchDaemonHealthDocument()), - authorityStatus: async () => projectAuthorityStatus(await this.fetchAuthorityPolicyDocument()), - starbridgeSession: async () => projectStarbridgeSessionSummary(await this.fetchStarbridgeSessionSummaryDocument()), - assetManifest: async () => projectAssetManifest(await this.fetchAssetManifestDocument()), + mapViewport: async (request: ViewportRequest) => buildViewportDocumentFromFrame(await this.fetchLatestFrameDocument(), request), + objectsViewport: async (request: ViewportRequest) => buildObjectsViewportDocumentFromFrame(await this.fetchLatestFrameDocument(), request), + gravityViewport: async (request: ViewportRequest) => buildGravityViewportDocumentFromFrame(await this.fetchLatestFrameDocument(), request), + selectedObject: async (request: SelectedObjectRequest) => buildSelectedObjectDocumentFromFrame(await this.fetchLatestFrameDocument(), request), + inventory: async (request: SelectedObjectRequest) => buildInventoryDocumentFromFrame(await this.fetchLatestFrameDocument(), request), + daemonHealth: async () => readDaemonHealthDocument(await this.fetchDaemonHealthDocument()), + authorityStatus: async () => readAuthorityStatusDocument(await this.fetchAuthorityPolicyDocument()), + starbridgeSession: async () => readStarbridgeSessionSummaryDocument(await this.fetchStarbridgeSessionSummaryDocument()), + assetManifest: async () => readAssetManifestDocument(await this.fetchAssetManifestDocument()), }; this.queries = createAetheriaRuntimeRtsQueryHandles( executors, @@ -159,7 +159,7 @@ export class AetheriaCultMeshClient { (commandId, issuedAtUtc, command, context) => this.sendCommandDocument(commandId, issuedAtUtc, command, context), ); - this.aetheria = createAetheriaRuntimeRtsVerseFacade( + this.aetheria = createAetheriaRuntimeRtsVerseHandles( this.queryVerse.context, this.commandVerse.context, this.queries, @@ -173,7 +173,7 @@ export class AetheriaCultMeshClient { private readonly queries: ReturnType; private readonly documents: AetheriaRuntimeRtsDocuments; private readonly operations: AetheriaRuntimeRtsOperationHandles; - private readonly aetheria: AetheriaRuntimeRtsVerseFacade; + private readonly aetheria: AetheriaRuntimeRtsVerseHandles; public async close(): Promise { this.#peer?.close(); @@ -208,31 +208,31 @@ export class AetheriaCultMeshClient { return this.aetheria.zone().gravity.within(request); } - public async selectedObject(request: SelectedObjectRequest): Promise { + public async selectedObject(request: SelectedObjectRequest): Promise { return this.aetheria.selectedObject(request.entityIndex); } - public async inventory(request: SelectedObjectRequest): Promise { + public async inventory(request: SelectedObjectRequest): Promise { return this.aetheria.inventory(request.entityIndex); } - public async daemonHealth(): Promise { + public async daemonHealth(): Promise { return this.aetheria.daemon.health(); } - public async authorityStatus(): Promise { + public async authorityStatus(): Promise { return this.aetheria.daemon.authorityStatus(); } - public async starbridgeSession(): Promise { + public async starbridgeSession(): Promise { return this.aetheria.daemon.starbridgeSession(); } - public async assetManifest(): Promise { + public async assetManifest(): Promise { return this.aetheria.daemon.assetManifest(); } - public projectionDiagnostics(): Readonly> { + public queryDiagnostics(): Readonly> { return describeAetheriaRuntimeRtsQueryHandles(this.queries); } @@ -492,7 +492,7 @@ function createAetheriaPublicationDocuments( sourceId: "daemon:aetheria.frame.latest.v1", }, ), - localPath: `${statePath}.daemon.frame.cc`, + localPath: statePath, }, { ...CultMesh.publicationDocument( @@ -503,7 +503,7 @@ function createAetheriaPublicationDocuments( sourceId: "daemon:aetheria.health.latest.v1", }, ), - localPath: `${statePath}.daemon.health.cc`, + localPath: statePath, remoteRecordKey: "daemon:aetheria.health.v1", }, { @@ -515,7 +515,7 @@ function createAetheriaPublicationDocuments( sourceId: "daemon:aetheria.authority.policy.latest.v1", }, ), - localPath: `${statePath}.authority.policy.cc`, + localPath: statePath, remoteRecordKey: "global:aetheria.verse_authority_policy.v1", }, { @@ -527,7 +527,7 @@ function createAetheriaPublicationDocuments( sourceId: "daemon:aetheria.starbridge.session.latest.v1", }, ), - localPath: `${statePath}.daemon.starbridge.session.cc`, + localPath: statePath, }, { ...CultMesh.publicationDocument( @@ -538,7 +538,7 @@ function createAetheriaPublicationDocuments( sourceId: "daemon:aetheria.asset_manifest.latest.v1", }, ), - localPath: `${statePath}.daemon.assets.cc`, + localPath: statePath, }, ]; diff --git a/Aetheria.Rts.Web/Electron/aetheria-rts-bindings.ts b/Aetheria.Rts.Web/Electron/aetheria-rts-bindings.ts index 5cd3ddfb4..0c39a973e 100644 --- a/Aetheria.Rts.Web/Electron/aetheria-rts-bindings.ts +++ b/Aetheria.Rts.Web/Electron/aetheria-rts-bindings.ts @@ -8,7 +8,7 @@ export { createAetheriaRuntimeRtsOperationHandles, createAetheriaRuntimeRtsQueryHandles, createAetheriaRuntimeRtsStatePointers, - createAetheriaRuntimeRtsVerseFacade, + createAetheriaRuntimeRtsVerseHandles, describeAetheriaRuntimeRtsLiveFeedSurface, describeAetheriaRuntimeRtsQueryHandles, describeAetheriaRuntimeRtsSurfaceCatalog, @@ -20,35 +20,35 @@ export type { AetheriaRuntimeRtsDocumentResolvers, AetheriaRuntimeRtsDocuments, AetheriaRuntimeRtsOperationHandles, - AetheriaRuntimeRtsProjectionDiagnostic, + AetheriaRuntimeRtsQueryDiagnostic, AetheriaRuntimeRtsStatePointerResolvers, AetheriaRuntimeRtsStatePointers, AetheriaRuntimeRtsSurfaceCatalogDiagnostic, - AetheriaRuntimeRtsVerseFacade, + AetheriaRuntimeRtsVerseHandles, AetheriaRuntimeSelectedObjectRequest as SelectedObjectRequest, AetheriaRuntimeSetMoveVectorRequest as RtsSetMoveVectorRequest, AetheriaRuntimeSetTargetRequest as RtsSetTargetRequest, AetheriaRuntimeViewportFeedRequest, AetheriaRuntimeViewportFeedSnapshot, AssetManifestEntry, - AssetManifestProjection, + AssetManifestDocument, AssetRef, - AuthorityRuleProjection, - AuthorityStatusProjection, + AuthorityRuleDocument, + AuthorityStatusDocument, BodyView, - DaemonHealthProjection, + DaemonHealthDocument, EntityStatus, GravityInfluence, GravityViewportResponse, InventoryItem, - InventoryProjection, + InventoryDocument, ObjectsViewportResponse, - SelectedObjectProjection, - StarbridgeBaseStatusProjection, - StarbridgeRuntimeRoleProjection, - StarbridgeSessionProjection, - StarbridgeStationStockItemProjection, - StarbridgeWaveForecastProjection, + SelectedObjectDocument, + StarbridgeBaseStatusDocument, + StarbridgeRuntimeRoleDocument, + StarbridgeSessionDocument, + StarbridgeStationStockItemDocument, + StarbridgeWaveForecastDocument, ViewObject, ViewportResponse, } from "./aetheria-rts-generated-bindings.js"; diff --git a/Aetheria.Rts.Web/Electron/aetheria-rts-generated-bindings.ts b/Aetheria.Rts.Web/Electron/aetheria-rts-generated-bindings.ts index 63872cb40..610a19b9f 100644 --- a/Aetheria.Rts.Web/Electron/aetheria-rts-generated-bindings.ts +++ b/Aetheria.Rts.Web/Electron/aetheria-rts-generated-bindings.ts @@ -2,7 +2,7 @@ // Generated by scripts/generate-rts-bindings.mjs from Aetheria C# MessagePack documents. import { CultMesh } from "cultmesh-ts"; -import type { CultMeshLiveFeedDiagnostic, CultMeshOperationContext, CultMeshOperationReceipt, CultMeshProjectionSource, CultMeshQueryContext, CultMeshQuerySurfaceDiagnostic, CultMeshQueryWatcher, CultMeshRouteHint, CultMeshSurfaceCatalogDiagnostic, CultMeshSurfaceCatalogIndexDiagnostic, CultMeshUnsubscribe, CultMeshVerseContext, CultMeshViewportRequest } from "cultmesh-ts"; +import type { CultMeshLiveFeedDiagnostic, CultMeshOperationContext, CultMeshOperationReceipt, CultMeshQuerySource, CultMeshQueryContext, CultMeshQuerySurfaceDiagnostic, CultMeshQueryWatcher, CultMeshRouteHint, CultMeshSurfaceCatalogDiagnostic, CultMeshSurfaceCatalogIndexDiagnostic, CultMeshUnsubscribe, CultMeshVerseContext, CultMeshViewportRequest } from "cultmesh-ts"; import type { IpcMain } from "electron"; export const AetheriaRtsSchemas = { @@ -720,7 +720,7 @@ export type AetheriaRuntimeZoneRenderDocumentSlot = | "asteroidBeltPoses" | "wormholeExits" | "droppedPickups" - | "entityFacades" + | "entitySnapshots" | "orbits" | "bodies"; @@ -740,7 +740,7 @@ export const aetheriaRuntimeZoneRenderDocumentSlots: Record; }; -export type StarbridgeWaveForecastProjection = { +export type StarbridgeWaveForecastDocument = { waveIndex: number; displayName: string; attackerKeys: string[]; @@ -1652,7 +1652,7 @@ export type StarbridgeWaveForecastProjection = { recoveredTechnologyKeys: string[]; }; -export type StarbridgeRuntimeRoleProjection = { +export type StarbridgeRuntimeRoleDocument = { runtimeId: string; role: string; entityKey: string; @@ -1713,12 +1713,12 @@ export type AetheriaRuntimeViewportFeedRequest = { export type AetheriaRuntimeViewportFeedSnapshot = { viewport: ViewportResponse; - selectedObject: SelectedObjectProjection | null; - inventory: InventoryProjection | null; - daemonHealth: DaemonHealthProjection; - authorityStatus: AuthorityStatusProjection; - starbridgeSession: StarbridgeSessionProjection; - assetManifest: AssetManifestProjection; + selectedObject: SelectedObjectDocument | null; + inventory: InventoryDocument | null; + daemonHealth: DaemonHealthDocument; + authorityStatus: AuthorityStatusDocument; + starbridgeSession: StarbridgeSessionDocument; + assetManifest: AssetManifestDocument; receivedAtUtc: string; sampleMs: number; }; @@ -1727,12 +1727,12 @@ export type AetheriaRtsMainClient = { mapViewport(request: CultMeshViewportRequest): Promise; objectsViewport(request: CultMeshViewportRequest): Promise; gravityViewport(request: CultMeshViewportRequest): Promise; - selectedObject(request: AetheriaRuntimeSelectedObjectRequest): Promise; - inventory(request: AetheriaRuntimeSelectedObjectRequest): Promise; - daemonHealth(): Promise; - authorityStatus(): Promise; - starbridgeSession(): Promise; - assetManifest(): Promise; + selectedObject(request: AetheriaRuntimeSelectedObjectRequest): Promise; + inventory(request: AetheriaRuntimeSelectedObjectRequest): Promise; + daemonHealth(): Promise; + authorityStatus(): Promise; + starbridgeSession(): Promise; + assetManifest(): Promise; watchViewportFeed(request: AetheriaRuntimeViewportFeedRequest, callback: (snapshot: AetheriaRuntimeViewportFeedSnapshot) => void): CultMeshUnsubscribe; setMoveVector(request: AetheriaRuntimeSetMoveVectorRequest): Promise; setTarget(request: AetheriaRuntimeSetTargetRequest): Promise; @@ -1806,27 +1806,27 @@ export type AetheriaRuntimeRtsQueryExecutors = { mapViewport: AetheriaRuntimeViewportQueryExecutor; objectsViewport: AetheriaRuntimeViewportQueryExecutor; gravityViewport: AetheriaRuntimeViewportQueryExecutor; - selectedObject: AetheriaRuntimeQueryExecutor; - inventory: AetheriaRuntimeQueryExecutor; - daemonHealth: AetheriaRuntimeQueryExecutor; - authorityStatus: AetheriaRuntimeQueryExecutor; - starbridgeSession: AetheriaRuntimeQueryExecutor; - assetManifest: AetheriaRuntimeQueryExecutor; + selectedObject: AetheriaRuntimeQueryExecutor; + inventory: AetheriaRuntimeQueryExecutor; + daemonHealth: AetheriaRuntimeQueryExecutor; + authorityStatus: AetheriaRuntimeQueryExecutor; + starbridgeSession: AetheriaRuntimeQueryExecutor; + assetManifest: AetheriaRuntimeQueryExecutor; }; export type AetheriaRuntimeRtsQueryWatchers = Partial<{ mapViewport: CultMeshQueryWatcher; objectsViewport: CultMeshQueryWatcher; gravityViewport: CultMeshQueryWatcher; - selectedObject: CultMeshQueryWatcher; - inventory: CultMeshQueryWatcher; - daemonHealth: CultMeshQueryWatcher; - authorityStatus: CultMeshQueryWatcher; - starbridgeSession: CultMeshQueryWatcher; - assetManifest: CultMeshQueryWatcher; + selectedObject: CultMeshQueryWatcher; + inventory: CultMeshQueryWatcher; + daemonHealth: CultMeshQueryWatcher; + authorityStatus: CultMeshQueryWatcher; + starbridgeSession: CultMeshQueryWatcher; + assetManifest: CultMeshQueryWatcher; }>; -export type AetheriaRuntimeRtsProjectionDiagnostic = CultMeshQuerySurfaceDiagnostic; +export type AetheriaRuntimeRtsQueryDiagnostic = CultMeshQuerySurfaceDiagnostic; export type AetheriaRuntimeRtsLiveFeedDiagnostic = CultMeshLiveFeedDiagnostic; @@ -1848,24 +1848,24 @@ export type AetheriaRuntimeRtsDocumentResolvers = Partial<{ export type AetheriaRuntimeRtsStatePointerResolvers = AetheriaRuntimeRtsDocumentResolvers; -export const AetheriaRuntimeRtsProjectionSources = { - daemonFrame: CultMesh.projectionSource("daemon:aetheria.frame.latest.v1", { +export const AetheriaRuntimeRtsDocumentSources = { + daemonFrame: CultMesh.querySource("daemon:aetheria.frame.latest.v1", { schemaId: AetheriaRtsSchemas.daemonFrame, description: "latest daemon frame" }), - daemonHealth: CultMesh.projectionSource("daemon:aetheria.health.latest.v1", { + daemonHealth: CultMesh.querySource("daemon:aetheria.health.latest.v1", { schemaId: AetheriaRtsSchemas.daemonHealth, description: "latest daemon health" }), - authorityPolicy: CultMesh.projectionSource("daemon:aetheria.authority.policy.latest.v1", { + authorityPolicy: CultMesh.querySource("daemon:aetheria.authority.policy.latest.v1", { schemaId: AetheriaRtsSchemas.verseAuthorityPolicy, description: "latest Verse authority policy" }), - starbridgeSession: CultMesh.projectionSource("daemon:aetheria.starbridge.session.latest.v1", { + starbridgeSession: CultMesh.querySource("daemon:aetheria.starbridge.session.latest.v1", { schemaId: AetheriaRtsSchemas.starbridgeSessionSummary, description: "latest Starbridge session summary" }), - assetManifest: CultMesh.projectionSource("daemon:aetheria.asset_manifest.latest.v1", { + assetManifest: CultMesh.querySource("daemon:aetheria.asset_manifest.latest.v1", { schemaId: AetheriaRtsSchemas.assetManifest, description: "latest daemon asset manifest" }) @@ -1877,48 +1877,48 @@ export function createAetheriaRuntimeRtsDocuments( ) { return { daemonFrame: CultMesh.document( - AetheriaRuntimeRtsProjectionSources.daemonFrame.sourceId, + AetheriaRuntimeRtsDocumentSources.daemonFrame.sourceId, { schemaId: AetheriaRtsSchemas.daemonFrame }, async (context) => resolvers.daemonFrame?.(context), { routeHint, - sources: [AetheriaRuntimeRtsProjectionSources.daemonFrame], + sources: [AetheriaRuntimeRtsDocumentSources.daemonFrame], }, ), daemonHealth: CultMesh.document( - AetheriaRuntimeRtsProjectionSources.daemonHealth.sourceId, + AetheriaRuntimeRtsDocumentSources.daemonHealth.sourceId, { schemaId: AetheriaRtsSchemas.daemonHealth }, async (context) => resolvers.daemonHealth?.(context), { routeHint, - sources: [AetheriaRuntimeRtsProjectionSources.daemonHealth], + sources: [AetheriaRuntimeRtsDocumentSources.daemonHealth], }, ), authorityPolicy: CultMesh.document( - AetheriaRuntimeRtsProjectionSources.authorityPolicy.sourceId, + AetheriaRuntimeRtsDocumentSources.authorityPolicy.sourceId, { schemaId: AetheriaRtsSchemas.verseAuthorityPolicy }, async (context) => resolvers.authorityPolicy?.(context), { routeHint, - sources: [AetheriaRuntimeRtsProjectionSources.authorityPolicy], + sources: [AetheriaRuntimeRtsDocumentSources.authorityPolicy], }, ), starbridgeSession: CultMesh.document( - AetheriaRuntimeRtsProjectionSources.starbridgeSession.sourceId, + AetheriaRuntimeRtsDocumentSources.starbridgeSession.sourceId, { schemaId: AetheriaRtsSchemas.starbridgeSessionSummary }, async (context) => resolvers.starbridgeSession?.(context), { routeHint, - sources: [AetheriaRuntimeRtsProjectionSources.starbridgeSession], + sources: [AetheriaRuntimeRtsDocumentSources.starbridgeSession], }, ), assetManifest: CultMesh.document( - AetheriaRuntimeRtsProjectionSources.assetManifest.sourceId, + AetheriaRuntimeRtsDocumentSources.assetManifest.sourceId, { schemaId: AetheriaRtsSchemas.assetManifest }, async (context) => resolvers.assetManifest?.(context), { routeHint, - sources: [AetheriaRuntimeRtsProjectionSources.assetManifest], + sources: [AetheriaRuntimeRtsDocumentSources.assetManifest], }, ), } as const; @@ -1932,60 +1932,51 @@ export function createAetheriaRuntimeRtsQueryHandles( watchers: AetheriaRuntimeRtsQueryWatchers = {}, ) { return { - mapViewport: CultMesh.projectionRecipe( + mapViewport: CultMesh.query( AetheriaRtsSchemas.rtsViewport, - [AetheriaRuntimeRtsProjectionSources.daemonFrame], executors.mapViewport, - { routeHint, watchProjection: watchers.mapViewport }, - ).asQuerySurface(), - objectsViewport: CultMesh.projectionRecipe( + { routeHint, sources: [AetheriaRuntimeRtsDocumentSources.daemonFrame], watchQuery: watchers.mapViewport }, + ), + objectsViewport: CultMesh.query( AetheriaRtsSchemas.objectsViewport, - [AetheriaRuntimeRtsProjectionSources.daemonFrame], executors.objectsViewport, - { routeHint, watchProjection: watchers.objectsViewport }, - ).asQuerySurface(), - gravityViewport: CultMesh.projectionRecipe( + { routeHint, sources: [AetheriaRuntimeRtsDocumentSources.daemonFrame], watchQuery: watchers.objectsViewport }, + ), + gravityViewport: CultMesh.query( AetheriaRtsSchemas.gravityViewport, - [AetheriaRuntimeRtsProjectionSources.daemonFrame], executors.gravityViewport, - { routeHint, watchProjection: watchers.gravityViewport }, - ).asQuerySurface(), - selectedObject: CultMesh.projectionRecipe( + { routeHint, sources: [AetheriaRuntimeRtsDocumentSources.daemonFrame], watchQuery: watchers.gravityViewport }, + ), + selectedObject: CultMesh.query( AetheriaRtsSchemas.selectedObject, - [AetheriaRuntimeRtsProjectionSources.daemonFrame], executors.selectedObject, - { routeHint, watchProjection: watchers.selectedObject }, - ).asQuerySurface(), - inventory: CultMesh.projectionRecipe( + { routeHint, sources: [AetheriaRuntimeRtsDocumentSources.daemonFrame], watchQuery: watchers.selectedObject }, + ), + inventory: CultMesh.query( AetheriaRtsSchemas.inventory, - [AetheriaRuntimeRtsProjectionSources.daemonFrame], executors.inventory, - { routeHint, watchProjection: watchers.inventory }, - ).asQuerySurface(), - daemonHealth: CultMesh.projectionRecipe( + { routeHint, sources: [AetheriaRuntimeRtsDocumentSources.daemonFrame], watchQuery: watchers.inventory }, + ), + daemonHealth: CultMesh.query( AetheriaRtsSchemas.daemonHealth, - [AetheriaRuntimeRtsProjectionSources.daemonHealth], executors.daemonHealth, - { routeHint, watchProjection: watchers.daemonHealth }, - ).asQuerySurface(), - authorityStatus: CultMesh.projectionRecipe( + { routeHint, sources: [AetheriaRuntimeRtsDocumentSources.daemonHealth], watchQuery: watchers.daemonHealth }, + ), + authorityStatus: CultMesh.query( AetheriaRtsSchemas.verseAuthorityPolicy, - [AetheriaRuntimeRtsProjectionSources.authorityPolicy], executors.authorityStatus, - { routeHint, watchProjection: watchers.authorityStatus }, - ).asQuerySurface(), - starbridgeSession: CultMesh.projectionRecipe( + { routeHint, sources: [AetheriaRuntimeRtsDocumentSources.authorityPolicy], watchQuery: watchers.authorityStatus }, + ), + starbridgeSession: CultMesh.query( AetheriaRtsSchemas.starbridgeSessionSummary, - [AetheriaRuntimeRtsProjectionSources.starbridgeSession], executors.starbridgeSession, - { routeHint, watchProjection: watchers.starbridgeSession }, - ).asQuerySurface(), - assetManifest: CultMesh.projectionRecipe( + { routeHint, sources: [AetheriaRuntimeRtsDocumentSources.starbridgeSession], watchQuery: watchers.starbridgeSession }, + ), + assetManifest: CultMesh.query( AetheriaRtsSchemas.assetManifest, - [AetheriaRuntimeRtsProjectionSources.assetManifest], executors.assetManifest, - { routeHint, watchProjection: watchers.assetManifest }, - ).asQuerySurface(), + { routeHint, sources: [AetheriaRuntimeRtsDocumentSources.assetManifest], watchQuery: watchers.assetManifest }, + ), } as const; } @@ -2040,15 +2031,15 @@ export function describeAetheriaRuntimeRtsSurfaceCatalog( function describeAetheriaRuntimeRtsQuerySurface(surface: { queryId: string; routeHint: CultMeshRouteHint; - sources: readonly CultMeshProjectionSource[]; -}): AetheriaRuntimeRtsProjectionDiagnostic { + sources: readonly CultMeshQuerySource[]; +}): AetheriaRuntimeRtsQueryDiagnostic { return CultMesh.describeQuerySurface(surface); } export function describeAetheriaRuntimeRtsLiveFeedSurface(surface: { feedId: string; routeHint: CultMeshRouteHint; - sources: readonly CultMeshProjectionSource[]; + sources: readonly CultMeshQuerySource[]; }): AetheriaRuntimeRtsLiveFeedDiagnostic { return CultMesh.describeLiveFeed(surface); } @@ -2095,9 +2086,9 @@ export function createAetheriaRuntimeRtsOperationHandles( } as const; } -export type AetheriaRuntimeRtsVerseFacade = ReturnType; +export type AetheriaRuntimeRtsVerseHandles = ReturnType; -export function createAetheriaRuntimeRtsVerseFacade( +export function createAetheriaRuntimeRtsVerseHandles( queryVerse: CultMeshVerseContext, commandVerse: CultMeshVerseContext, queries: ReturnType, diff --git a/Aetheria.Rts.Web/Electron/aetheria-rts-local-projection.ts b/Aetheria.Rts.Web/Electron/aetheria-rts-local-documents.ts similarity index 93% rename from Aetheria.Rts.Web/Electron/aetheria-rts-local-projection.ts rename to Aetheria.Rts.Web/Electron/aetheria-rts-local-documents.ts index c049a89bc..775c5bf5d 100644 --- a/Aetheria.Rts.Web/Electron/aetheria-rts-local-projection.ts +++ b/Aetheria.Rts.Web/Electron/aetheria-rts-local-documents.ts @@ -23,27 +23,27 @@ import { } from "./aetheria-rts-generated-bindings.js"; import { cultMeshRectFromBounds, cultMeshViewportRequest } from "cultmesh-ts"; import type { - AssetManifestProjection, + AssetManifestDocument, AssetRef, - AuthorityStatusProjection, + AuthorityStatusDocument, BodyView, - DaemonHealthProjection, + DaemonHealthDocument, GravityViewportResponse, GravityInfluence, - InventoryProjection, + InventoryDocument, InventoryItem, ObjectsViewportResponse, - SelectedObjectProjection, + SelectedObjectDocument, SelectedObjectRequest, - StarbridgeSessionProjection, + StarbridgeSessionDocument, ViewObject, ViewportRequest, ViewportResponse, } from "./aetheria-rts-bindings.js"; -export function projectViewportFromFrame(frameDocument: unknown, request: ViewportRequest): ViewportResponse { - const objects = projectObjectsViewportFromFrame(frameDocument, request); - const gravity = projectGravityViewportFromFrame(frameDocument, request); +export function buildViewportDocumentFromFrame(frameDocument: unknown, request: ViewportRequest): ViewportResponse { + const objects = buildObjectsViewportDocumentFromFrame(frameDocument, request); + const gravity = buildGravityViewportDocumentFromFrame(frameDocument, request); return { schema: AetheriaRtsSchemas.rtsViewport, frameId: objects.frameId, @@ -61,7 +61,7 @@ export function projectViewportFromFrame(frameDocument: unknown, request: Viewpo }; } -export function projectObjectsViewportFromFrame( +export function buildObjectsViewportDocumentFromFrame( frameDocument: unknown, request: ViewportRequest, ): ObjectsViewportResponse { @@ -102,7 +102,7 @@ export function projectObjectsViewportFromFrame( }; } -export function projectGravityViewportFromFrame( +export function buildGravityViewportDocumentFromFrame( frameDocument: unknown, request: ViewportRequest, ): GravityViewportResponse { @@ -132,10 +132,10 @@ export function projectGravityViewportFromFrame( }; } -export function projectSelectedObjectFromFrame( +export function buildSelectedObjectDocumentFromFrame( frameDocument: unknown, request: SelectedObjectRequest, -): SelectedObjectProjection { +): SelectedObjectDocument { const context = frameContext(frameDocument); const entity = context.entities.find(candidate => num(candidate[entitySlots.entityIndex], -1) === request.entityIndex); return { @@ -148,7 +148,7 @@ export function projectSelectedObjectFromFrame( }; } -export function projectInventoryFromFrame(frameDocument: unknown, request: SelectedObjectRequest): InventoryProjection { +export function buildInventoryDocumentFromFrame(frameDocument: unknown, request: SelectedObjectRequest): InventoryDocument { const context = frameContext(frameDocument); const entity = context.entities.find(candidate => num(candidate[entitySlots.entityIndex], -1) === request.entityIndex); const allItems = entity ? inventory(entity) : []; @@ -165,7 +165,7 @@ export function projectInventoryFromFrame(frameDocument: unknown, request: Selec }; } -export function projectDaemonHealth(healthDocument: unknown): DaemonHealthProjection { +export function readDaemonHealthDocument(healthDocument: unknown): DaemonHealthDocument { const health = arr(healthDocument); return { schema: str(health[healthSlots.schema]) || AetheriaRtsSchemas.daemonHealth, @@ -184,7 +184,7 @@ export function projectDaemonHealth(healthDocument: unknown): DaemonHealthProjec }; } -export function projectAuthorityStatus(policyDocument: unknown): AuthorityStatusProjection { +export function readAuthorityStatusDocument(policyDocument: unknown): AuthorityStatusDocument { const policy = arr(policyDocument); return { schema: str(policy[authorityPolicySlots.schema]) || AetheriaRtsSchemas.verseAuthorityPolicy, @@ -206,7 +206,7 @@ export function projectAuthorityStatus(policyDocument: unknown): AuthorityStatus }; } -export function projectStarbridgeSessionSummary(summaryDocument: unknown): StarbridgeSessionProjection { +export function readStarbridgeSessionSummaryDocument(summaryDocument: unknown): StarbridgeSessionDocument { const summary = arr(summaryDocument); return { schema: str(summary[starbridgeSummarySlots.schema]) || AetheriaRtsSchemas.starbridgeSessionSummary, @@ -227,7 +227,7 @@ export function projectStarbridgeSessionSummary(summaryDocument: unknown): Starb }; } -export function projectAssetManifest(assetManifestDocument: unknown): AssetManifestProjection { +export function readAssetManifestDocument(assetManifestDocument: unknown): AssetManifestDocument { const manifest = arr(assetManifestDocument); return { schema: str(manifest[assetManifestSlots.schema]) || AetheriaRtsSchemas.assetManifest, @@ -296,7 +296,7 @@ function toViewObject(entity: unknown[], runId: string, zoneIndex: number): View }; } -function toStarbridgeBaseStatus(status: unknown[]): StarbridgeSessionProjection["baseStatus"] { +function toStarbridgeBaseStatus(status: unknown[]): StarbridgeSessionDocument["baseStatus"] { return { entityKey: str(status[starbridgeBaseSlots.entityKey]), displayName: str(status[starbridgeBaseSlots.displayName]), @@ -307,7 +307,7 @@ function toStarbridgeBaseStatus(status: unknown[]): StarbridgeSessionProjection[ }; } -function toStarbridgeStationStockItem(item: unknown[]): StarbridgeSessionProjection["stationStock"][number] { +function toStarbridgeStationStockItem(item: unknown[]): StarbridgeSessionDocument["stationStock"][number] { const itemKey = str(item[starbridgeStockSlots.itemKey]); return { itemKey, @@ -319,7 +319,7 @@ function toStarbridgeStationStockItem(item: unknown[]): StarbridgeSessionProject }; } -function toStarbridgeWaveForecast(wave: unknown[]): StarbridgeSessionProjection["waveForecast"][number] { +function toStarbridgeWaveForecast(wave: unknown[]): StarbridgeSessionDocument["waveForecast"][number] { return { waveIndex: num(wave[starbridgeWaveForecastSlots.waveIndex]), displayName: str(wave[starbridgeWaveForecastSlots.displayName]), @@ -329,7 +329,7 @@ function toStarbridgeWaveForecast(wave: unknown[]): StarbridgeSessionProjection[ }; } -function toStarbridgeRuntimeRole(role: unknown[]): StarbridgeSessionProjection["runtimeRoles"][number] { +function toStarbridgeRuntimeRole(role: unknown[]): StarbridgeSessionDocument["runtimeRoles"][number] { return { runtimeId: str(role[starbridgeRoleSlots.runtimeId]), role: str(role[starbridgeRoleSlots.role]), @@ -439,7 +439,7 @@ function entityKey(runId: string, zoneIndex: number, entityIndex: number): strin return `global:aetheria.run_state.${runId}.zone.${zoneIndex}.entity.${entityIndex}.v1`; } -function toAssetManifestEntry(entry: unknown[]): AssetManifestProjection["assets"][number] { +function toAssetManifestEntry(entry: unknown[]): AssetManifestDocument["assets"][number] { return { ref: assetRef(arr(entry[assetManifestEntrySlots.ref])), sizeBytes: num(entry[assetManifestEntrySlots.sizeBytes]), diff --git a/Aetheria.Rts.Web/scripts/generate-rts-bindings.mjs b/Aetheria.Rts.Web/scripts/generate-rts-bindings.mjs index d5edbcca7..24f6eb727 100644 --- a/Aetheria.Rts.Web/scripts/generate-rts-bindings.mjs +++ b/Aetheria.Rts.Web/scripts/generate-rts-bindings.mjs @@ -387,7 +387,7 @@ function render(enums, documents) { .map(document => renderSlotBlock(document.exportName, document.slots)) .join("\n\n"); - return `// \n// Generated by scripts/generate-rts-bindings.mjs from Aetheria C# MessagePack documents.\n\nimport { CultMesh } from "cultmesh-ts";\nimport type { CultMeshLiveFeedDiagnostic, CultMeshOperationContext, CultMeshOperationReceipt, CultMeshProjectionSource, CultMeshQueryContext, CultMeshQuerySurfaceDiagnostic, CultMeshQueryWatcher, CultMeshRouteHint, CultMeshSurfaceCatalogDiagnostic, CultMeshSurfaceCatalogIndexDiagnostic, CultMeshUnsubscribe, CultMeshVerseContext, CultMeshViewportRequest } from "cultmesh-ts";\nimport type { IpcMain } from "electron";\n\nexport const AetheriaRtsSchemas = {\n${schemas}\n} as const;\n\n${enumBlocks}\n\nexport const AetheriaRtsIpcChannels = {\n${renderIpcChannelLines()}\n} as const;\n\n${slotBlocks}\n\n${renderProjectionTypes("CultMeshViewportRequest")}\n\n${renderRtsOperations()}\n`; + return `// \n// Generated by scripts/generate-rts-bindings.mjs from Aetheria C# MessagePack documents.\n\nimport { CultMesh } from "cultmesh-ts";\nimport type { CultMeshLiveFeedDiagnostic, CultMeshOperationContext, CultMeshOperationReceipt, CultMeshQuerySource, CultMeshQueryContext, CultMeshQuerySurfaceDiagnostic, CultMeshQueryWatcher, CultMeshRouteHint, CultMeshSurfaceCatalogDiagnostic, CultMeshSurfaceCatalogIndexDiagnostic, CultMeshUnsubscribe, CultMeshVerseContext, CultMeshViewportRequest } from "cultmesh-ts";\nimport type { IpcMain } from "electron";\n\nexport const AetheriaRtsSchemas = {\n${schemas}\n} as const;\n\n${enumBlocks}\n\nexport const AetheriaRtsIpcChannels = {\n${renderIpcChannelLines()}\n} as const;\n\n${slotBlocks}\n\n${renderRtsDocumentTypes("CultMeshViewportRequest")}\n\n${renderRtsOperations()}\n`; } function renderEnumBlock(exportName, entries) { @@ -442,12 +442,12 @@ export type AetheriaRuntimeViewportFeedRequest = { export type AetheriaRuntimeViewportFeedSnapshot = { viewport: ViewportResponse; - selectedObject: SelectedObjectProjection | null; - inventory: InventoryProjection | null; - daemonHealth: DaemonHealthProjection; - authorityStatus: AuthorityStatusProjection; - starbridgeSession: StarbridgeSessionProjection; - assetManifest: AssetManifestProjection; + selectedObject: SelectedObjectDocument | null; + inventory: InventoryDocument | null; + daemonHealth: DaemonHealthDocument; + authorityStatus: AuthorityStatusDocument; + starbridgeSession: StarbridgeSessionDocument; + assetManifest: AssetManifestDocument; receivedAtUtc: string; sampleMs: number; }; @@ -456,12 +456,12 @@ export type AetheriaRtsMainClient = { mapViewport(request: CultMeshViewportRequest): Promise; objectsViewport(request: CultMeshViewportRequest): Promise; gravityViewport(request: CultMeshViewportRequest): Promise; - selectedObject(request: AetheriaRuntimeSelectedObjectRequest): Promise; - inventory(request: AetheriaRuntimeSelectedObjectRequest): Promise; - daemonHealth(): Promise; - authorityStatus(): Promise; - starbridgeSession(): Promise; - assetManifest(): Promise; + selectedObject(request: AetheriaRuntimeSelectedObjectRequest): Promise; + inventory(request: AetheriaRuntimeSelectedObjectRequest): Promise; + daemonHealth(): Promise; + authorityStatus(): Promise; + starbridgeSession(): Promise; + assetManifest(): Promise; watchViewportFeed(request: AetheriaRuntimeViewportFeedRequest, callback: (snapshot: AetheriaRuntimeViewportFeedSnapshot) => void): CultMeshUnsubscribe; setMoveVector(request: AetheriaRuntimeSetMoveVectorRequest): Promise; setTarget(request: AetheriaRuntimeSetTargetRequest): Promise; @@ -535,27 +535,27 @@ export type AetheriaRuntimeRtsQueryExecutors = { mapViewport: AetheriaRuntimeViewportQueryExecutor; objectsViewport: AetheriaRuntimeViewportQueryExecutor; gravityViewport: AetheriaRuntimeViewportQueryExecutor; - selectedObject: AetheriaRuntimeQueryExecutor; - inventory: AetheriaRuntimeQueryExecutor; - daemonHealth: AetheriaRuntimeQueryExecutor; - authorityStatus: AetheriaRuntimeQueryExecutor; - starbridgeSession: AetheriaRuntimeQueryExecutor; - assetManifest: AetheriaRuntimeQueryExecutor; + selectedObject: AetheriaRuntimeQueryExecutor; + inventory: AetheriaRuntimeQueryExecutor; + daemonHealth: AetheriaRuntimeQueryExecutor; + authorityStatus: AetheriaRuntimeQueryExecutor; + starbridgeSession: AetheriaRuntimeQueryExecutor; + assetManifest: AetheriaRuntimeQueryExecutor; }; export type AetheriaRuntimeRtsQueryWatchers = Partial<{ mapViewport: CultMeshQueryWatcher; objectsViewport: CultMeshQueryWatcher; gravityViewport: CultMeshQueryWatcher; - selectedObject: CultMeshQueryWatcher; - inventory: CultMeshQueryWatcher; - daemonHealth: CultMeshQueryWatcher; - authorityStatus: CultMeshQueryWatcher; - starbridgeSession: CultMeshQueryWatcher; - assetManifest: CultMeshQueryWatcher; + selectedObject: CultMeshQueryWatcher; + inventory: CultMeshQueryWatcher; + daemonHealth: CultMeshQueryWatcher; + authorityStatus: CultMeshQueryWatcher; + starbridgeSession: CultMeshQueryWatcher; + assetManifest: CultMeshQueryWatcher; }>; -export type AetheriaRuntimeRtsProjectionDiagnostic = CultMeshQuerySurfaceDiagnostic; +export type AetheriaRuntimeRtsQueryDiagnostic = CultMeshQuerySurfaceDiagnostic; export type AetheriaRuntimeRtsLiveFeedDiagnostic = CultMeshLiveFeedDiagnostic; @@ -577,24 +577,24 @@ export type AetheriaRuntimeRtsDocumentResolvers = Partial<{ export type AetheriaRuntimeRtsStatePointerResolvers = AetheriaRuntimeRtsDocumentResolvers; -export const AetheriaRuntimeRtsProjectionSources = { - daemonFrame: CultMesh.projectionSource("daemon:aetheria.frame.latest.v1", { +export const AetheriaRuntimeRtsDocumentSources = { + daemonFrame: CultMesh.querySource("daemon:aetheria.frame.latest.v1", { schemaId: AetheriaRtsSchemas.daemonFrame, description: "latest daemon frame" }), - daemonHealth: CultMesh.projectionSource("daemon:aetheria.health.latest.v1", { + daemonHealth: CultMesh.querySource("daemon:aetheria.health.latest.v1", { schemaId: AetheriaRtsSchemas.daemonHealth, description: "latest daemon health" }), - authorityPolicy: CultMesh.projectionSource("daemon:aetheria.authority.policy.latest.v1", { + authorityPolicy: CultMesh.querySource("daemon:aetheria.authority.policy.latest.v1", { schemaId: AetheriaRtsSchemas.verseAuthorityPolicy, description: "latest Verse authority policy" }), - starbridgeSession: CultMesh.projectionSource("daemon:aetheria.starbridge.session.latest.v1", { + starbridgeSession: CultMesh.querySource("daemon:aetheria.starbridge.session.latest.v1", { schemaId: AetheriaRtsSchemas.starbridgeSessionSummary, description: "latest Starbridge session summary" }), - assetManifest: CultMesh.projectionSource("daemon:aetheria.asset_manifest.latest.v1", { + assetManifest: CultMesh.querySource("daemon:aetheria.asset_manifest.latest.v1", { schemaId: AetheriaRtsSchemas.assetManifest, description: "latest daemon asset manifest" }) @@ -606,48 +606,48 @@ export function createAetheriaRuntimeRtsDocuments( ) { return { daemonFrame: CultMesh.document( - AetheriaRuntimeRtsProjectionSources.daemonFrame.sourceId, + AetheriaRuntimeRtsDocumentSources.daemonFrame.sourceId, { schemaId: AetheriaRtsSchemas.daemonFrame }, async (context) => resolvers.daemonFrame?.(context), { routeHint, - sources: [AetheriaRuntimeRtsProjectionSources.daemonFrame], + sources: [AetheriaRuntimeRtsDocumentSources.daemonFrame], }, ), daemonHealth: CultMesh.document( - AetheriaRuntimeRtsProjectionSources.daemonHealth.sourceId, + AetheriaRuntimeRtsDocumentSources.daemonHealth.sourceId, { schemaId: AetheriaRtsSchemas.daemonHealth }, async (context) => resolvers.daemonHealth?.(context), { routeHint, - sources: [AetheriaRuntimeRtsProjectionSources.daemonHealth], + sources: [AetheriaRuntimeRtsDocumentSources.daemonHealth], }, ), authorityPolicy: CultMesh.document( - AetheriaRuntimeRtsProjectionSources.authorityPolicy.sourceId, + AetheriaRuntimeRtsDocumentSources.authorityPolicy.sourceId, { schemaId: AetheriaRtsSchemas.verseAuthorityPolicy }, async (context) => resolvers.authorityPolicy?.(context), { routeHint, - sources: [AetheriaRuntimeRtsProjectionSources.authorityPolicy], + sources: [AetheriaRuntimeRtsDocumentSources.authorityPolicy], }, ), starbridgeSession: CultMesh.document( - AetheriaRuntimeRtsProjectionSources.starbridgeSession.sourceId, + AetheriaRuntimeRtsDocumentSources.starbridgeSession.sourceId, { schemaId: AetheriaRtsSchemas.starbridgeSessionSummary }, async (context) => resolvers.starbridgeSession?.(context), { routeHint, - sources: [AetheriaRuntimeRtsProjectionSources.starbridgeSession], + sources: [AetheriaRuntimeRtsDocumentSources.starbridgeSession], }, ), assetManifest: CultMesh.document( - AetheriaRuntimeRtsProjectionSources.assetManifest.sourceId, + AetheriaRuntimeRtsDocumentSources.assetManifest.sourceId, { schemaId: AetheriaRtsSchemas.assetManifest }, async (context) => resolvers.assetManifest?.(context), { routeHint, - sources: [AetheriaRuntimeRtsProjectionSources.assetManifest], + sources: [AetheriaRuntimeRtsDocumentSources.assetManifest], }, ), } as const; @@ -661,60 +661,51 @@ export function createAetheriaRuntimeRtsQueryHandles( watchers: AetheriaRuntimeRtsQueryWatchers = {}, ) { return { - mapViewport: CultMesh.projectionRecipe( + mapViewport: CultMesh.query( AetheriaRtsSchemas.rtsViewport, - [AetheriaRuntimeRtsProjectionSources.daemonFrame], executors.mapViewport, - { routeHint, watchProjection: watchers.mapViewport }, - ).asQuerySurface(), - objectsViewport: CultMesh.projectionRecipe( + { routeHint, sources: [AetheriaRuntimeRtsDocumentSources.daemonFrame], watchQuery: watchers.mapViewport }, + ), + objectsViewport: CultMesh.query( AetheriaRtsSchemas.objectsViewport, - [AetheriaRuntimeRtsProjectionSources.daemonFrame], executors.objectsViewport, - { routeHint, watchProjection: watchers.objectsViewport }, - ).asQuerySurface(), - gravityViewport: CultMesh.projectionRecipe( + { routeHint, sources: [AetheriaRuntimeRtsDocumentSources.daemonFrame], watchQuery: watchers.objectsViewport }, + ), + gravityViewport: CultMesh.query( AetheriaRtsSchemas.gravityViewport, - [AetheriaRuntimeRtsProjectionSources.daemonFrame], executors.gravityViewport, - { routeHint, watchProjection: watchers.gravityViewport }, - ).asQuerySurface(), - selectedObject: CultMesh.projectionRecipe( + { routeHint, sources: [AetheriaRuntimeRtsDocumentSources.daemonFrame], watchQuery: watchers.gravityViewport }, + ), + selectedObject: CultMesh.query( AetheriaRtsSchemas.selectedObject, - [AetheriaRuntimeRtsProjectionSources.daemonFrame], executors.selectedObject, - { routeHint, watchProjection: watchers.selectedObject }, - ).asQuerySurface(), - inventory: CultMesh.projectionRecipe( + { routeHint, sources: [AetheriaRuntimeRtsDocumentSources.daemonFrame], watchQuery: watchers.selectedObject }, + ), + inventory: CultMesh.query( AetheriaRtsSchemas.inventory, - [AetheriaRuntimeRtsProjectionSources.daemonFrame], executors.inventory, - { routeHint, watchProjection: watchers.inventory }, - ).asQuerySurface(), - daemonHealth: CultMesh.projectionRecipe( + { routeHint, sources: [AetheriaRuntimeRtsDocumentSources.daemonFrame], watchQuery: watchers.inventory }, + ), + daemonHealth: CultMesh.query( AetheriaRtsSchemas.daemonHealth, - [AetheriaRuntimeRtsProjectionSources.daemonHealth], executors.daemonHealth, - { routeHint, watchProjection: watchers.daemonHealth }, - ).asQuerySurface(), - authorityStatus: CultMesh.projectionRecipe( + { routeHint, sources: [AetheriaRuntimeRtsDocumentSources.daemonHealth], watchQuery: watchers.daemonHealth }, + ), + authorityStatus: CultMesh.query( AetheriaRtsSchemas.verseAuthorityPolicy, - [AetheriaRuntimeRtsProjectionSources.authorityPolicy], executors.authorityStatus, - { routeHint, watchProjection: watchers.authorityStatus }, - ).asQuerySurface(), - starbridgeSession: CultMesh.projectionRecipe( + { routeHint, sources: [AetheriaRuntimeRtsDocumentSources.authorityPolicy], watchQuery: watchers.authorityStatus }, + ), + starbridgeSession: CultMesh.query( AetheriaRtsSchemas.starbridgeSessionSummary, - [AetheriaRuntimeRtsProjectionSources.starbridgeSession], executors.starbridgeSession, - { routeHint, watchProjection: watchers.starbridgeSession }, - ).asQuerySurface(), - assetManifest: CultMesh.projectionRecipe( + { routeHint, sources: [AetheriaRuntimeRtsDocumentSources.starbridgeSession], watchQuery: watchers.starbridgeSession }, + ), + assetManifest: CultMesh.query( AetheriaRtsSchemas.assetManifest, - [AetheriaRuntimeRtsProjectionSources.assetManifest], executors.assetManifest, - { routeHint, watchProjection: watchers.assetManifest }, - ).asQuerySurface(), + { routeHint, sources: [AetheriaRuntimeRtsDocumentSources.assetManifest], watchQuery: watchers.assetManifest }, + ), } as const; } @@ -769,15 +760,15 @@ export function describeAetheriaRuntimeRtsSurfaceCatalog( function describeAetheriaRuntimeRtsQuerySurface(surface: { queryId: string; routeHint: CultMeshRouteHint; - sources: readonly CultMeshProjectionSource[]; -}): AetheriaRuntimeRtsProjectionDiagnostic { + sources: readonly CultMeshQuerySource[]; +}): AetheriaRuntimeRtsQueryDiagnostic { return CultMesh.describeQuerySurface(surface); } export function describeAetheriaRuntimeRtsLiveFeedSurface(surface: { feedId: string; routeHint: CultMeshRouteHint; - sources: readonly CultMeshProjectionSource[]; + sources: readonly CultMeshQuerySource[]; }): AetheriaRuntimeRtsLiveFeedDiagnostic { return CultMesh.describeLiveFeed(surface); } @@ -824,9 +815,9 @@ export function createAetheriaRuntimeRtsOperationHandles( } as const; } -export type AetheriaRuntimeRtsVerseFacade = ReturnType; +export type AetheriaRuntimeRtsVerseHandles = ReturnType; -export function createAetheriaRuntimeRtsVerseFacade( +export function createAetheriaRuntimeRtsVerseHandles( queryVerse: CultMeshVerseContext, commandVerse: CultMeshVerseContext, queries: ReturnType, @@ -1027,28 +1018,28 @@ export type AetheriaRuntimeViewportFeedRequest = { export type AetheriaRuntimeViewportFeedSnapshot = { viewport: ViewportResponse; - selectedObject: SelectedObjectProjection | null; - inventory: InventoryProjection | null; - daemonHealth: DaemonHealthProjection; - authorityStatus: AuthorityStatusProjection; - starbridgeSession: StarbridgeSessionProjection; - assetManifest: AssetManifestProjection; + selectedObject: SelectedObjectDocument | null; + inventory: InventoryDocument | null; + daemonHealth: DaemonHealthDocument; + authorityStatus: AuthorityStatusDocument; + starbridgeSession: StarbridgeSessionDocument; + assetManifest: AssetManifestDocument; receivedAtUtc: string; sampleMs: number; }; -${renderProjectionTypes("Viewport")} +${renderRtsDocumentTypes("Viewport")} export type AetheriaRtsApi = { mapViewport(request: Viewport): Promise; objectsViewport(request: Viewport): Promise; gravityViewport(request: Viewport): Promise; - selectedObject(request: AetheriaRuntimeSelectedObjectRequest): Promise; - inventory(request: AetheriaRuntimeSelectedObjectRequest): Promise; - daemonHealth(): Promise; - authorityStatus(): Promise; - starbridgeSession(): Promise; - assetManifest(): Promise; + selectedObject(request: AetheriaRuntimeSelectedObjectRequest): Promise; + inventory(request: AetheriaRuntimeSelectedObjectRequest): Promise; + daemonHealth(): Promise; + authorityStatus(): Promise; + starbridgeSession(): Promise; + assetManifest(): Promise; watchViewportFeed(request: AetheriaRuntimeViewportFeedRequest, callback: (snapshot: AetheriaRuntimeViewportFeedSnapshot) => void): () => void; setMoveVector(request: AetheriaRuntimeSetMoveVectorRequest): Promise; setTarget(request: AetheriaRuntimeSetTargetRequest): Promise; @@ -1059,7 +1050,7 @@ export type AetheriaRtsApi = { `; } -function renderProjectionTypes(viewportTypeName) { +function renderRtsDocumentTypes(viewportTypeName) { return `export type ViewportResponse = { schema: string; frameId: number; @@ -1141,7 +1132,7 @@ export type InventoryItem = { iconAsset: AssetRef; }; -export type SelectedObjectProjection = { +export type SelectedObjectDocument = { schema: string; frameId: number; runId: string; @@ -1150,7 +1141,7 @@ export type SelectedObjectProjection = { selected: ViewObject | null; }; -export type InventoryProjection = { +export type InventoryDocument = { schema: string; frameId: number; runId: string; @@ -1162,7 +1153,7 @@ export type InventoryProjection = { cargo: InventoryItem[]; }; -export type DaemonHealthProjection = { +export type DaemonHealthDocument = { schema: string; daemonId: string; verseId: string; @@ -1178,7 +1169,7 @@ export type DaemonHealthProjection = { commandBoundaryPath: string; }; -export type AuthorityStatusProjection = { +export type AuthorityStatusDocument = { schema: string; verseId: string; policyId: string; @@ -1186,10 +1177,10 @@ export type AuthorityStatusProjection = { hostRuntimeId: string; defaultMode: string; updatedAtUtc: string; - rules: AuthorityRuleProjection[]; + rules: AuthorityRuleDocument[]; }; -export type AuthorityRuleProjection = { +export type AuthorityRuleDocument = { ruleId: string; subjectPrefix: string; claimKinds: string[]; @@ -1199,7 +1190,7 @@ export type AuthorityRuleProjection = { priority: number; }; -export type StarbridgeSessionProjection = { +export type StarbridgeSessionDocument = { schema: string; frameId: number; publishedAtUtc: string; @@ -1211,13 +1202,13 @@ export type StarbridgeSessionProjection = { zoneName: string; phase: string; currentWaveIndex: number; - baseStatus: StarbridgeBaseStatusProjection; - stationStock: StarbridgeStationStockItemProjection[]; - waveForecast: StarbridgeWaveForecastProjection[]; - runtimeRoles: StarbridgeRuntimeRoleProjection[]; + baseStatus: StarbridgeBaseStatusDocument; + stationStock: StarbridgeStationStockItemDocument[]; + waveForecast: StarbridgeWaveForecastDocument[]; + runtimeRoles: StarbridgeRuntimeRoleDocument[]; }; -export type StarbridgeBaseStatusProjection = { +export type StarbridgeBaseStatusDocument = { entityKey: string; displayName: string; hull: number; @@ -1226,7 +1217,7 @@ export type StarbridgeBaseStatusProjection = { isActive: boolean; }; -export type StarbridgeStationStockItemProjection = { +export type StarbridgeStationStockItemDocument = { itemKey: string; quantity: number; quality: number; @@ -1235,7 +1226,7 @@ export type StarbridgeStationStockItemProjection = { iconAsset: AssetRef; }; -export type AssetManifestProjection = { +export type AssetManifestDocument = { schema: string; publishedAtUtc: string; runId: string; @@ -1261,7 +1252,7 @@ export type AssetRef = { metadata: Record; }; -export type StarbridgeWaveForecastProjection = { +export type StarbridgeWaveForecastDocument = { waveIndex: number; displayName: string; attackerKeys: string[]; @@ -1269,7 +1260,7 @@ export type StarbridgeWaveForecastProjection = { recoveredTechnologyKeys: string[]; }; -export type StarbridgeRuntimeRoleProjection = { +export type StarbridgeRuntimeRoleDocument = { runtimeId: string; role: string; entityKey: string; diff --git a/Aetheria.Rts.Web/scripts/verify-stage7b-rts-client.ps1 b/Aetheria.Rts.Web/scripts/verify-stage7b-rts-client.ps1 index 312691688..c1a1a6a0d 100644 --- a/Aetheria.Rts.Web/scripts/verify-stage7b-rts-client.ps1 +++ b/Aetheria.Rts.Web/scripts/verify-stage7b-rts-client.ps1 @@ -146,8 +146,8 @@ $requiredGeneratedBindingSymbols = @( 'aetheriaRuntimeDaemonCommandDocumentSlots', 'encodeSetMoveVectorCommand', 'encodeSetTargetCommand', - 'AetheriaRuntimeRtsProjectionSources', - 'AetheriaRuntimeRtsProjectionDiagnostic', + 'AetheriaRuntimeRtsDocumentSources', + 'AetheriaRuntimeRtsQueryDiagnostic', 'AetheriaRuntimeRtsLiveFeedDiagnostic', 'AetheriaRuntimeRtsSurfaceCatalogDiagnostic', 'AetheriaRuntimeRtsDocuments', @@ -161,9 +161,9 @@ $requiredGeneratedBindingSymbols = @( 'surfaceCatalogIndexDiagnostics(): CultMeshSurfaceCatalogIndexDiagnostic', 'ipcMain.handle(AetheriaRtsIpcChannels.surfaceCatalog', 'ipcMain.handle(AetheriaRtsIpcChannels.surfaceCatalogIndex', - 'CultMesh.projectionSource', - 'CultMesh.projectionRecipe', - 'CultMeshProjectionSource', + 'CultMesh.querySource', + 'CultMesh.query', + 'CultMeshQuerySource', 'routeHint: CultMeshRouteHint', 'CultMeshQueryWatcher', 'AetheriaRuntimeRtsQueryWatchers', @@ -171,9 +171,8 @@ $requiredGeneratedBindingSymbols = @( 'CultMesh.document', 'CultMesh.bindDocument', 'CultMesh.describeSurface(documents.daemonFrame)', - 'routeHint, watchProjection', - 'watchProjection: watchers.objectsViewport', - '.asQuerySurface()', + 'watchQuery: watchers.objectsViewport', + 'sources: [AetheriaRuntimeRtsDocumentSources.daemonFrame]', 'describeAetheriaRuntimeRtsQueryHandles', 'describeAetheriaRuntimeRtsLiveFeedSurface', 'describeAetheriaRuntimeRtsSurfaceCatalog', @@ -186,19 +185,19 @@ $requiredGeneratedBindingSymbols = @( 'CultMesh.operation', 'CultMesh.operationReceipt', 'AetheriaRuntimeRtsOperationHandles', - 'AetheriaRuntimeRtsVerseFacade', - 'createAetheriaRuntimeRtsVerseFacade', + 'AetheriaRuntimeRtsVerseHandles', + 'createAetheriaRuntimeRtsVerseHandles', 'CultMesh.bindQuery', 'CultMesh.bindOperation', 'zone: (_zoneId =', 'visibleWithin', 'entity: (entityKey: string)', 'pilot', - 'selectedObject: CultMesh.projectionRecipe', - 'inventory: CultMesh.projectionRecipe', - 'daemonHealth: CultMesh.projectionRecipe', - 'authorityStatus: CultMesh.projectionRecipe', - 'starbridgeSession: CultMesh.projectionRecipe' + 'selectedObject: CultMesh.query', + 'inventory: CultMesh.query', + 'daemonHealth: CultMesh.query', + 'authorityStatus: CultMesh.query', + 'starbridgeSession: CultMesh.query' ) foreach ($symbol in $requiredGeneratedBindingSymbols) { @@ -208,17 +207,17 @@ foreach ($symbol in $requiredGeneratedBindingSymbols) { } } -$forbiddenGeneratedQuerySurfaceHits = & rg -n 'CultMesh\.query\s*[<(]' Electron/aetheria-rts-generated-bindings.ts 2>$null +$forbiddenGeneratedQuerySurfaceHits = & rg -n 'CultMesh\.(projectionRecipe|projectionSource)\s*[<(]' Electron/aetheria-rts-generated-bindings.ts 2>$null if ($LASTEXITCODE -eq 0) { - Write-Error "Stage 7B verifier failed: generated RTS read surfaces should use CultMesh projection recipes, not raw query wrappers.`n$forbiddenGeneratedQuerySurfaceHits" + Write-Error "Stage 7B verifier failed: generated RTS read surfaces should use CultMesh query/document handles, not projection recipes or projection sources.`n$forbiddenGeneratedQuerySurfaceHits" } if ($LASTEXITCODE -gt 1) { - Write-Error "Stage 7B verifier could not run rg for generated projection recipe checks." + Write-Error "Stage 7B verifier could not run rg for generated projection vocabulary checks." } $forbiddenGeneratedContextPlumbingHits = & rg -n 'CultMesh\.(queryContextFromVerse|operationContextFromVerse)|\.execute\([^,\n]+,\s*queryContext\(|\.invoke\([^,\n]+,\s*operationContext\(' Electron/aetheria-rts-generated-bindings.ts 2>$null if ($LASTEXITCODE -eq 0) { - Write-Error "Stage 7B verifier failed: generated RTS facade should bind surfaces to Verse contexts instead of threading context plumbing.`n$forbiddenGeneratedContextPlumbingHits" + Write-Error "Stage 7B verifier failed: generated RTS handles should bind surfaces to Verse contexts instead of threading context plumbing.`n$forbiddenGeneratedContextPlumbingHits" } if ($LASTEXITCODE -gt 1) { Write-Error "Stage 7B verifier could not run rg for generated Verse binding checks." @@ -247,7 +246,7 @@ if (-not $clientText.Contains('this.verse = CultMesh.verse("aetheria.local", thi if (-not $clientText.Contains('? this.verse.withRoute("network", this.publicationDescription)') -or -not $clientText.Contains(': this.verse.withRoute("shared-memory", this.publicationDescription)')) { - Write-Error "Stage 7B verifier failed: RTS client is not deriving publication-mode Verse views for projection reads." + Write-Error "Stage 7B verifier failed: RTS client is not deriving publication-mode Verse views for query reads." } if (-not $clientText.Contains('this.commandVerse = this.verse') -or @@ -257,14 +256,14 @@ if (-not $clientText.Contains('this.commandVerse = this.verse') -or } if (-not $clientText.Contains('this.publicationDescription = publicationMode === "remote" ? this.endpoint : statePath')) { - Write-Error "Stage 7B verifier failed: RTS client projection route metadata does not include the local publication source description." + Write-Error "Stage 7B verifier failed: RTS client query route metadata does not include the local publication source description." } if (-not $clientText.Contains('return this.queryVerse.queryContext();')) { Write-Error "Stage 7B verifier failed: RTS query call sites should derive query contexts from the shared Verse primitive." } -if (-not $clientText.Contains('this.aetheria = createAetheriaRuntimeRtsVerseFacade(')) { +if (-not $clientText.Contains('this.aetheria = createAetheriaRuntimeRtsVerseHandles(')) { Write-Error "Stage 7B verifier failed: RTS client should bind generated Aetheria domain sugar to its Verse contexts." } @@ -282,12 +281,12 @@ if ($LASTEXITCODE -gt 1) { Write-Error "Stage 7B verifier could not run rg for raw context checks." } -if (-not $clientText.Contains('projectionDiagnostics()')) { - Write-Error "Stage 7B verifier failed: RTS client does not expose generated projection diagnostics." +if (-not $clientText.Contains('queryDiagnostics()')) { + Write-Error "Stage 7B verifier failed: RTS client does not expose generated query diagnostics." } if (-not $clientText.Contains('describeAetheriaRuntimeRtsQueryHandles(this.queries)')) { - Write-Error "Stage 7B verifier failed: RTS projection diagnostics should be read from generated query handle metadata." + Write-Error "Stage 7B verifier failed: RTS query diagnostics should be read from generated query handle metadata." } if (-not $clientText.Contains('liveFeedDiagnostics()')) { @@ -374,20 +373,20 @@ if ($LASTEXITCODE -ne 0) { Write-Error "Stage 7B verifier failed: Electron main is not using generated RTS IPC handler registration." } -$forbiddenClientDirectProjectionReturns = @( +$forbiddenClientDirectDocumentReturns = @( 'return projectSelectedObjectFromFrame', 'return projectInventoryFromFrame', 'return projectDaemonHealth', 'return projectAuthorityStatus', 'return projectStarbridgeSessionSummary' ) -$forbiddenClientDirectProjectionPattern = ($forbiddenClientDirectProjectionReturns -join '|') -$forbiddenClientDirectProjectionHits = & rg -n $forbiddenClientDirectProjectionPattern Electron/aetheria-cultmesh.ts 2>$null +$forbiddenClientDirectDocumentPattern = ($forbiddenClientDirectDocumentReturns -join '|') +$forbiddenClientDirectDocumentHits = & rg -n $forbiddenClientDirectDocumentPattern Electron/aetheria-cultmesh.ts 2>$null if ($LASTEXITCODE -eq 0) { - Write-Error "Stage 7B verifier failed: RTS client read methods bypass generated query handles.`n$forbiddenClientDirectProjectionHits" + Write-Error "Stage 7B verifier failed: RTS client read methods bypass generated query handles.`n$forbiddenClientDirectDocumentHits" } if ($LASTEXITCODE -gt 1) { - Write-Error "Stage 7B verifier could not run rg for direct projection return checks." + Write-Error "Stage 7B verifier could not run rg for direct document return checks." } $requiredRendererContractSymbols = @( @@ -462,30 +461,30 @@ if ($LASTEXITCODE -gt 1) { Write-Error "Stage 7B verifier could not run rg for stale binding checks." } -& rg -q 'projectViewportFromFrame' Electron/aetheria-rts-local-projection.ts +& rg -q 'buildViewportDocumentFromFrame' Electron/aetheria-rts-local-documents.ts if ($LASTEXITCODE -ne 0) { - Write-Error "Stage 7B verifier failed: local RTS frame projection entry point was not found." + Write-Error "Stage 7B verifier failed: local RTS frame document entry point was not found." } -$requiredProjectionSymbols = @( - 'projectObjectsViewportFromFrame', - 'projectGravityViewportFromFrame', +$requiredLocalDocumentSymbols = @( + 'buildObjectsViewportDocumentFromFrame', + 'buildGravityViewportDocumentFromFrame', 'AetheriaRtsSchemas.objectsViewport', 'AetheriaRtsSchemas.gravityViewport', - 'projectSelectedObjectFromFrame', - 'projectInventoryFromFrame', + 'buildSelectedObjectDocumentFromFrame', + 'buildInventoryDocumentFromFrame', 'AetheriaRtsSchemas.selectedObject', 'AetheriaRtsSchemas.inventory', - 'projectDaemonHealth', - 'projectAuthorityStatus', - 'projectStarbridgeSessionSummary', + 'readDaemonHealthDocument', + 'readAuthorityStatusDocument', + 'readStarbridgeSessionSummaryDocument', 'AetheriaRtsSchemas.starbridgeSessionSummary' ) -foreach ($symbol in $requiredProjectionSymbols) { - & rg -q $symbol Electron/aetheria-rts-local-projection.ts +foreach ($symbol in $requiredLocalDocumentSymbols) { + & rg -q $symbol Electron/aetheria-rts-local-documents.ts if ($LASTEXITCODE -ne 0) { - Write-Error "Stage 7B verifier failed: expected local RTS projection symbol '$symbol' was not found." + Write-Error "Stage 7B verifier failed: expected local RTS document symbol '$symbol' was not found." } } @@ -507,9 +506,17 @@ if ($LASTEXITCODE -ne 0) { Write-Error "Stage 7B verifier failed: Starbridge summary publication binding was not found." } -& rg -q '.daemon.starbridge.session.cc' Electron/aetheria-cultmesh.ts +$forbiddenLocalPublicationSidecars = & rg -n '\.(daemon\.frame|daemon\.health|authority\.policy|daemon\.starbridge\.session|daemon\.assets)\.cc' Electron/aetheria-cultmesh.ts 2>$null +if ($LASTEXITCODE -eq 0) { + Write-Error "Stage 7B verifier failed: RTS client should read local publications from the canonical daemon store, not obsolete sidecar files.`n$forbiddenLocalPublicationSidecars" +} +if ($LASTEXITCODE -gt 1) { + Write-Error "Stage 7B verifier could not run rg for obsolete local publication sidecar checks." +} + +& rg -q 'localPath: statePath' Electron/aetheria-cultmesh.ts if ($LASTEXITCODE -ne 0) { - Write-Error "Stage 7B verifier failed: RTS client is not reading the daemon Starbridge sidecar publication." + Write-Error "Stage 7B verifier failed: RTS client local publication bindings do not point at the canonical daemon store." } $requiredGeneratedBindingSymbols = @( diff --git a/Aetheria.Rts.Web/scripts/verify-stage7c-local-runtime.ps1 b/Aetheria.Rts.Web/scripts/verify-stage7c-local-runtime.ps1 index 1fb13b0da..de50e8331 100644 --- a/Aetheria.Rts.Web/scripts/verify-stage7c-local-runtime.ps1 +++ b/Aetheria.Rts.Web/scripts/verify-stage7c-local-runtime.ps1 @@ -15,10 +15,17 @@ $statePath = Join-Path $tempRoot "aetheria-rts.cc" $endpoint = $null try { - $daemonOutput = dotnet run --project (Join-Path $repoRoot "Aetheria.State.Daemon\Aetheria.State.Daemon.csproj") -- ` - --state $statePath ` - --once ` - --rts-cultmesh-port 0 2>&1 + $previousErrorActionPreference = $ErrorActionPreference + $ErrorActionPreference = "Continue" + try { + $daemonOutput = dotnet run --project (Join-Path $repoRoot "Aetheria.State.Daemon\Aetheria.State.Daemon.csproj") -- ` + --state $statePath ` + --once ` + --rts-cultmesh-port 0 2>&1 + } + finally { + $ErrorActionPreference = $previousErrorActionPreference + } $daemonOutput | Out-Host if ($LASTEXITCODE -ne 0) { Write-Error "Stage 7C verifier failed: one-shot daemon did not complete." @@ -52,9 +59,9 @@ const viewport = await client.mapViewport({ minX: -5000, minY: -5000, maxX: 5000 const objectsViewport = await client.objectsViewport({ minX: -5000, minY: -5000, maxX: 5000, maxY: 5000 }); const gravityViewport = await client.gravityViewport({ minX: -5000, minY: -5000, maxX: 5000, maxY: 5000 }); if (!Number.isFinite(viewport.frameId)) - throw new Error("Viewport projection did not produce a frame id."); + throw new Error("Viewport query did not produce a frame id."); if (!Array.isArray(viewport.objects) || viewport.objects.length === 0) - throw new Error("Viewport projection did not expose visible objects."); + throw new Error("Viewport query did not expose visible objects."); if (objectsViewport.schema !== "gamecult.aetheria.objects_viewport.v1") throw new Error(`Objects viewport used unexpected schema: ${objectsViewport.schema}`); if (gravityViewport.schema !== "gamecult.aetheria.gravity_viewport.v1") @@ -66,19 +73,19 @@ if (!Array.isArray(gravityViewport.gravityInfluences) || gravityViewport.gravity const selectedIndex = viewport.controlledEntityIndices[0] ?? viewport.objects[0]?.entityIndex; if (!Number.isFinite(selectedIndex)) - throw new Error("No entity was available for selected-object projection."); + throw new Error("No entity was available for the selected-object query."); const selected = await client.selectedObject({ entityIndex: selectedIndex }); if (!selected.selected) - throw new Error(`Selected-object projection did not resolve entity ${selectedIndex}.`); + throw new Error(`Selected-object query did not resolve entity ${selectedIndex}.`); const inventory = await client.inventory({ entityIndex: selectedIndex }); if (!Array.isArray(inventory.equipment) || !Array.isArray(inventory.cargo)) - throw new Error("Inventory projection did not return equipment and cargo arrays."); + throw new Error("Inventory query did not return equipment and cargo arrays."); const health = await client.daemonHealth(); if (health.status !== "healthy") - throw new Error(`Daemon health projection was not healthy: ${health.status}`); + throw new Error(`Daemon health document was not healthy: ${health.status}`); const authority = await client.authorityStatus(); if (authority.policyId !== "aetheria.trusted-coop.v1") @@ -146,7 +153,7 @@ console.log(JSON.stringify({ $env:AETHERIA_STAGE7C_CLIENT_MODULE = Join-Path $root "electron-dist\aetheria-cultmesh.js" node $smokeScript if ($LASTEXITCODE -ne 0) { - Write-Error "Stage 7C verifier failed: local Electron runtime facade could not read daemon publications." + Write-Error "Stage 7C verifier failed: local Electron runtime handles could not read daemon publications." } } finally { diff --git a/Aetheria.State.AuthoritySmoke/Program.cs b/Aetheria.State.AuthoritySmoke/Program.cs index 245e42e61..cb0942c1b 100644 --- a/Aetheria.State.AuthoritySmoke/Program.cs +++ b/Aetheria.State.AuthoritySmoke/Program.cs @@ -25,7 +25,7 @@ internal sealed class AuthoritySmokeChecks PreRejectedCommandsEnterFrameReceipts(); ClientTargetCarriesRuntimeIdentity(); TwoLocalRuntimeDelegatedPolicyHarness(); - RtsProjectionProjectsLocalViewportFromFrame(); + RtsDocumentsBuildLocalViewportFromFrame(); StarbridgeSessionSummaryProjectsScenarioFacts(); await AetheriaClientStateDocumentsProjectAndSubmitAsync().ConfigureAwait(false); await DaemonOncePublishesStarbridgeSessionFactsAsync().ConfigureAwait(false); @@ -282,28 +282,28 @@ internal sealed class AuthoritySmokeChecks Require(starfireFrame.AccountedCommandIds.Contains(hostileByStarfire.CommandId), "starfire node should account applied hostile command"); } - private static void RtsProjectionProjectsLocalViewportFromFrame() + private static void RtsDocumentsBuildLocalViewportFromFrame() { var run = new AetheriaRuntimeRunCheckpointCommit { - RunId = "rts-projection-smoke", + RunId = "rts-documents-smoke", CurrentZoneIndex = 0, EntranceZoneIndex = 0, ExitZoneIndex = 1, DiscoveredZoneIndices = [0], - CurrentEntityKey = EntityKey("rts-projection-smoke", 0, 0), + CurrentEntityKey = EntityKey("rts-documents-smoke", 0, 0), Zones = [ new AetheriaRuntimeZoneSnapshotCommit { ZoneIndex = 0, - Name = "Projection Zone", + Name = "Document Zone", PositionX = 12, PositionY = 34, AdjacentZoneIndices = [1], Entities = [ - ProjectionEntity( + SnapshotEntity( 0, "Raven", "player", @@ -312,7 +312,7 @@ internal sealed class AuthoritySmokeChecks visibility: 80, hull: 120, cargoItem: "reactor-cell"), - ProjectionEntity( + SnapshotEntity( 1, "Starfire", "player", @@ -321,7 +321,7 @@ internal sealed class AuthoritySmokeChecks visibility: 160, hull: 140, cargoItem: "sensor-pack"), - ProjectionEntity( + SnapshotEntity( 2, "Raider In Shared Sight", "raider", @@ -330,7 +330,7 @@ internal sealed class AuthoritySmokeChecks visibility: 60, hull: 70, cargoItem: "scrap"), - ProjectionEntity( + SnapshotEntity( 3, "Outside Viewport", "raider", @@ -372,7 +372,7 @@ internal sealed class AuthoritySmokeChecks new AetheriaRuntimeZoneSnapshotCommit { ZoneIndex = 1, - Name = "Projection Neighbor", + Name = "Document Neighbor", PositionX = 72, PositionY = 34, AdjacentZoneIndices = [0], @@ -383,13 +383,13 @@ internal sealed class AuthoritySmokeChecks }; var frame = AetheriaRuntimeDaemonFrameDocument.Create( run, - "projection-daemon", - "projection-session", + "documents-daemon", + "documents-session", 12, 0.24, 0.02); - var projection = AetheriaRuntimeRtsProjection.ProjectViewport( + var viewportDocument = AetheriaRuntimeRtsDocuments.Viewport( frame, new AetheriaRuntimeRtsViewportBounds { @@ -398,45 +398,45 @@ internal sealed class AuthoritySmokeChecks MaxX = -50, MaxY = 100 }); - var objectProjection = AetheriaRuntimeRtsProjection.ProjectObjectsViewport( + var objectsViewport = AetheriaRuntimeRtsDocuments.ObjectsViewport( frame, - projection.Viewport); - var gravityProjection = AetheriaRuntimeRtsProjection.ProjectGravityViewport( + viewportDocument.Viewport); + var gravityViewport = AetheriaRuntimeRtsDocuments.GravityViewport( frame, - projection.Viewport); - var currentZone = AetheriaRuntimeRtsProjection.ProjectCurrentZone(frame); - var sectorMap = AetheriaRuntimeRtsProjection.ProjectSectorMap(frame); + viewportDocument.Viewport); + var currentZone = AetheriaRuntimeRtsDocuments.CurrentZone(frame); + var sectorMap = AetheriaRuntimeRtsDocuments.SectorMap(frame); - RequireEqual(AetheriaRuntimeDaemonSchemas.RtsViewport, projection.Schema, "projection schema"); - RequireEqual(AetheriaRuntimeDaemonSchemas.ObjectsViewport, objectProjection.Schema, "objects viewport projection schema"); - RequireEqual(AetheriaRuntimeDaemonSchemas.GravityViewport, gravityProjection.Schema, "gravity viewport projection schema"); - RequireEqual(AetheriaRuntimeDaemonSchemas.CurrentZone, currentZone.Schema, "current-zone projection schema"); - RequireEqual(AetheriaRuntimeDaemonSchemas.SectorMap, sectorMap.Schema, "sector-map projection schema"); - RequireEqual("Projection Zone", currentZone.ZoneName, "current-zone projection zone name"); - RequireEqual(12.0, currentZone.PositionX, "current-zone projection position x"); - RequireEqual(34.0, currentZone.PositionY, "current-zone projection position y"); - RequireEqual(2, sectorMap.Zones.Count, "sector-map projection zone count"); + RequireEqual(AetheriaRuntimeDaemonSchemas.RtsViewport, viewportDocument.Schema, "viewport document schema"); + RequireEqual(AetheriaRuntimeDaemonSchemas.ObjectsViewport, objectsViewport.Schema, "objects viewport document schema"); + RequireEqual(AetheriaRuntimeDaemonSchemas.GravityViewport, gravityViewport.Schema, "gravity viewport document schema"); + RequireEqual(AetheriaRuntimeDaemonSchemas.CurrentZone, currentZone.Schema, "current-zone document schema"); + RequireEqual(AetheriaRuntimeDaemonSchemas.SectorMap, sectorMap.Schema, "sector-map document schema"); + RequireEqual("Document Zone", currentZone.ZoneName, "current-zone document zone name"); + RequireEqual(12.0, currentZone.PositionX, "current-zone document position x"); + RequireEqual(34.0, currentZone.PositionY, "current-zone document position y"); + RequireEqual(2, sectorMap.Zones.Count, "sector-map document zone count"); Require(sectorMap.Zones.Any(zone => zone.ZoneIndex == 0 && zone.Current && zone.Entrance && zone.Discovered), "sector-map should mark current entrance zone"); Require(sectorMap.Zones.Any(zone => zone.ZoneIndex == 1 && zone.Exit && !zone.Discovered), "sector-map should mark undiscovered exit zone"); Require(sectorMap.Links.Any(link => link.FromZoneIndex == 0 && link.ToZoneIndex == 1 && !link.Discovered), "sector-map should include normalized topology link"); - RequireEqual(12L, projection.FrameId, "projection frame id"); - RequireEqual(-50.0, projection.Viewport.MinX, "normalized viewport min x"); - RequireEqual(500.0, projection.Viewport.MaxX, "normalized viewport max x"); - Require(projection.ControlledEntityIndices.SequenceEqual([0, 1]), "projection should expose controlled unit set"); - Require(projection.Objects.Any(item => item.DisplayName == "Raider In Shared Sight"), "projection should use union of controlled unit visibility"); - Require(!projection.Objects.Any(item => item.DisplayName == "Outside Viewport"), "projection should exclude objects outside viewport"); - Require(objectProjection.Objects.Select(item => item.EntityIndex).SequenceEqual(projection.Objects.Select(item => item.EntityIndex)), "objects viewport should match composed map object set"); - var raven = projection.Objects.First(item => item.DisplayName == "Raven"); - RequireEqual(120.0, raven.Status.Hull, "projection should include entity status"); - Require(raven.Inventory.Any(item => item.ItemKey == "reactor-cell" && item.Source == "cargo"), "projection should include inventory"); - Require(projection.GravityInfluences.Any(item => item.BodyKey == "body:near"), "projection should include intersecting gravity influence"); - Require(!projection.GravityInfluences.Any(item => item.BodyKey == "body:far"), "projection should exclude non-intersecting gravity influence"); - Require(gravityProjection.GravityInfluences.Select(item => item.BodyKey).SequenceEqual(projection.GravityInfluences.Select(item => item.BodyKey)), "gravity viewport should match composed map gravity set"); + RequireEqual(12L, viewportDocument.FrameId, "viewport document frame id"); + RequireEqual(-50.0, viewportDocument.Viewport.MinX, "normalized viewport min x"); + RequireEqual(500.0, viewportDocument.Viewport.MaxX, "normalized viewport max x"); + Require(viewportDocument.ControlledEntityIndices.SequenceEqual([0, 1]), "viewport document should expose controlled unit set"); + Require(viewportDocument.Objects.Any(item => item.DisplayName == "Raider In Shared Sight"), "viewport document should use union of controlled unit visibility"); + Require(!viewportDocument.Objects.Any(item => item.DisplayName == "Outside Viewport"), "viewport document should exclude objects outside viewport"); + Require(objectsViewport.Objects.Select(item => item.EntityIndex).SequenceEqual(viewportDocument.Objects.Select(item => item.EntityIndex)), "objects viewport should match composed map object set"); + var raven = viewportDocument.Objects.First(item => item.DisplayName == "Raven"); + RequireEqual(120.0, raven.Status.Hull, "viewport document should include entity status"); + Require(raven.Inventory.Any(item => item.ItemKey == "reactor-cell" && item.Source == "cargo"), "viewport document should include inventory"); + Require(viewportDocument.GravityInfluences.Any(item => item.BodyKey == "body:near"), "viewport document should include intersecting gravity influence"); + Require(!viewportDocument.GravityInfluences.Any(item => item.BodyKey == "body:far"), "viewport document should exclude non-intersecting gravity influence"); + Require(gravityViewport.GravityInfluences.Select(item => item.BodyKey).SequenceEqual(viewportDocument.GravityInfluences.Select(item => item.BodyKey)), "gravity viewport should match composed map gravity set"); } private static void StarbridgeSessionSummaryProjectsScenarioFacts() { - var baseEntity = ProjectionEntity( + var baseEntity = SnapshotEntity( 4, "Starbridge Base", "player", @@ -460,7 +460,7 @@ internal sealed class AuthoritySmokeChecks Entities = [ baseEntity, - ProjectionEntity(5, "Pilot Raven", "player", 120, 0, 300, 140, "repair-pod") + SnapshotEntity(5, "Pilot Raven", "player", 120, 0, 300, 140, "repair-pod") ] } ] @@ -532,7 +532,7 @@ internal sealed class AuthoritySmokeChecks CurrentWaveIndex = 1 }; - var summary = AetheriaRuntimeStarbridgeProjection.ProjectSessionSummary(frame, scenario, session); + var summary = AetheriaRuntimeStarbridgeDocuments.SessionSummary(frame, scenario, session); RequireEqual(AetheriaRuntimeDaemonSchemas.StarbridgeSessionSummary, summary.Schema, "starbridge session summary schema"); RequireEqual("starbridge.frontier-fabricator", summary.ScenarioId, "starbridge scenario id"); @@ -579,8 +579,8 @@ internal sealed class AuthoritySmokeChecks Name = "Client State Documents Zone", Entities = [ - ProjectionEntity(0, "Document Raven", "player", 0, 0, 600, 140, "document-cargo"), - ProjectionEntity(1, "Document Raider", "raider", 100, 0, 120, 80, "raider-cargo") + SnapshotEntity(0, "Document Raven", "player", 0, 0, 600, 140, "document-cargo"), + SnapshotEntity(1, "Document Raider", "raider", 100, 0, 120, 80, "raider-cargo") ] } ] @@ -660,58 +660,53 @@ internal sealed class AuthoritySmokeChecks using var client = await AetheriaClient .OpenAsync(statePath, "raven-unity", sessionId: "state-documents-session", pullOnOpen: true) .ConfigureAwait(false); - var aetheria = client.Aetheria(); - var viewport = await client - .Aetheria() - .Viewports - .Map(new AetheriaRuntimeRtsViewportBounds { MinX = -20, MinY = -20, MaxX = 150, MaxY = 20 }) + var state = client.State; + var viewportBounds = new AetheriaRuntimeRtsViewportBounds { MinX = -20, MinY = -20, MaxX = 150, MaxY = 20 }; + var viewport = await state + .RtsViewport(viewportBounds) .LatestAsync() .ConfigureAwait(false); Require(viewport.Objects.Any(item => item.DisplayName == "Document Raven"), "managed map document should include controlled object"); Require(viewport.Objects.Any(item => item.DisplayName == "Document Raider"), "managed map document should include visible hostile object"); - var objectsViewport = await client - .Aetheria() - .Viewports - .Objects(new AetheriaRuntimeRtsViewportBounds { MinX = -20, MinY = -20, MaxX = 150, MaxY = 20 }) + var objectsViewport = await state + .ObjectsViewport(viewportBounds) .LatestAsync() .ConfigureAwait(false); RequireEqual(AetheriaRuntimeDaemonSchemas.ObjectsViewport, objectsViewport.Schema, "managed objects viewport document schema"); Require(objectsViewport.Objects.Any(item => item.DisplayName == "Document Raven"), "managed objects viewport document should include controlled object"); Require(objectsViewport.Objects.Any(item => item.DisplayName == "Document Raider"), "managed objects viewport document should include visible hostile object"); - var gravityViewport = await client - .Aetheria() - .Viewports - .Gravity(new AetheriaRuntimeRtsViewportBounds { MinX = -20, MinY = -20, MaxX = 150, MaxY = 20 }) + var gravityViewport = await state + .GravityViewport(viewportBounds) .LatestAsync() .ConfigureAwait(false); RequireEqual(AetheriaRuntimeDaemonSchemas.GravityViewport, gravityViewport.Schema, "managed gravity viewport document schema"); - var currentZone = await aetheria.Current.Zone.LatestAsync().ConfigureAwait(false); + var currentZone = await state.CurrentZone.LatestAsync().ConfigureAwait(false); RequireEqual(AetheriaRuntimeDaemonSchemas.CurrentZone, currentZone.Schema, "managed current-zone document schema"); RequireEqual("Client State Documents Zone", currentZone.ZoneName, "managed current-zone document name"); - var sectorMap = await aetheria.SectorMap.LatestAsync().ConfigureAwait(false); + var sectorMap = await state.SectorMap.LatestAsync().ConfigureAwait(false); RequireEqual(AetheriaRuntimeDaemonSchemas.SectorMap, sectorMap.Schema, "managed sector-map document schema"); Require(sectorMap.Zones.Any(zone => zone.ZoneIndex == currentZone.ZoneIndex && zone.Current), "managed sector-map document should include current zone marker"); - var selected = await aetheria.Details.SelectedObject(0).LatestAsync().ConfigureAwait(false); + var selected = await state.SelectedObject(0).LatestAsync().ConfigureAwait(false); RequireEqual(AetheriaRuntimeDaemonSchemas.SelectedObject, selected.Schema, "managed selected-object document schema"); Require(selected.Selected?.DisplayName == "Document Raven", "managed selected-object document should resolve entity"); - var inventory = await aetheria.Details.Inventory(0).LatestAsync().ConfigureAwait(false); + var inventory = await state.Inventory(0).LatestAsync().ConfigureAwait(false); RequireEqual(AetheriaRuntimeDaemonSchemas.Inventory, inventory.Schema, "managed inventory document schema"); Require(inventory.Cargo.Any(item => item.ItemKey == "document-cargo"), "managed inventory document should expose cargo"); - var starbridgeSummary = await aetheria.Starbridge.Summary.LatestAsync().ConfigureAwait(false); + var starbridgeSummary = await state.StarbridgeSummary.LatestAsync().ConfigureAwait(false); RequireEqual("Document Starbridge", starbridgeSummary.ScenarioName, "managed starbridge summary document should project scenario name"); RequireEqual("pre-wave", starbridgeSummary.Phase, "managed starbridge summary document should project session phase"); Require(starbridgeSummary.StationStock.Any(item => item.ItemKey == "document-cargo"), "managed starbridge summary document should expose station stock"); - var projectedHealth = await aetheria.Daemon.Health.LatestAsync().ConfigureAwait(false); - Require(projectedHealth?.Status == "healthy", "managed daemon health document should read local health"); + var healthDocument = await state.Health.LatestAsync().ConfigureAwait(false); + Require(healthDocument?.Status == "healthy", "managed daemon health document should read local health"); - var projectedPolicy = await aetheria.Daemon.AuthorityPolicy.LatestAsync().ConfigureAwait(false); - RequireEqual("aetheria.trusted-coop.v1", projectedPolicy?.PolicyId, "managed authority policy document id"); + var authorityPolicy = await state.AuthorityPolicy.LatestAsync().ConfigureAwait(false); + RequireEqual("aetheria.trusted-coop.v1", authorityPolicy?.PolicyId, "managed authority policy document id"); var command = client.SetMoveVector(1, 0, 0.5); RequireEqual(AetheriaRuntimeDaemonCommandKinds.SetMoveVector, command.Kind, "managed client command kind"); @@ -741,7 +736,7 @@ internal sealed class AuthoritySmokeChecks using var client = await AetheriaClient .OpenAsync(statePath, "starbridge-smoke-client", sessionId: "authority-smoke", pullOnOpen: true) .ConfigureAwait(false); - var summary = await client.Aetheria().Starbridge.Summary.LatestAsync().ConfigureAwait(false); + var summary = await client.State.StarbridgeSummary.LatestAsync().ConfigureAwait(false); RequireEqual(AetheriaRuntimeDaemonSchemas.StarbridgeSessionSummary, summary.Schema, "daemon starbridge summary schema"); RequireEqual("starbridge.frontier-fabricator", summary.ScenarioId, "daemon starbridge scenario id"); @@ -1718,7 +1713,7 @@ internal sealed class AuthoritySmokeChecks .First(candidate => candidate.EntityIndex == entityIndex); } - private static AetheriaRuntimeEntitySnapshotCommit ProjectionEntity( + private static AetheriaRuntimeEntitySnapshotCommit SnapshotEntity( int entityIndex, string name, string factionKey, diff --git a/Aetheria.State.Daemon/Program.cs b/Aetheria.State.Daemon/Program.cs index 4f4cf0c24..57603eeab 100644 --- a/Aetheria.State.Daemon/Program.cs +++ b/Aetheria.State.Daemon/Program.cs @@ -550,7 +550,7 @@ static RudpCultNetSchemaServer StartRtsCultMeshHost( if (hasFrame && frame != null && TryGetRtsViewportRequest(request, out var viewportRecordKey, out var viewport)) { - var viewportDocument = AetheriaRuntimeRtsProjection.ProjectViewport(frame, viewport); + var viewportDocument = AetheriaRuntimeRtsDocuments.Viewport(frame, viewport); var viewportPut = node.Database.Documents.CreateRawDocumentPutMessage( response.MessageId, new CultRecordHandle( @@ -780,19 +780,19 @@ static async Task PublishDaemonApiDocumentsAsync( .ConfigureAwait(false); if (result.GameSurface != null) await node.MutableDocument(AetheriaRuntimeVerseRecordKeys.DaemonGameSurface) - .ReplaceAsync(AetheriaRuntimeEveSurfaceAdapter.ToEveSurfaceState(result.GameSurface)) + .ReplaceAsync(AetheriaRuntimeSurfaceDocuments.ToEveSurfaceState(result.GameSurface)) .ConfigureAwait(false); if (result.GameTuiSurface != null) await node.MutableDocument(AetheriaRuntimeVerseRecordKeys.DaemonGameTuiSurface) - .ReplaceAsync(AetheriaRuntimeEveSurfaceAdapter.ToEveSurfaceState(result.GameTuiSurface)) + .ReplaceAsync(AetheriaRuntimeSurfaceDocuments.ToEveSurfaceState(result.GameTuiSurface)) .ConfigureAwait(false); if (result.EditorSurface != null) await node.MutableDocument(AetheriaRuntimeVerseRecordKeys.DaemonEditorSurface) - .ReplaceAsync(AetheriaRuntimeEveSurfaceAdapter.ToEveSurfaceState(result.EditorSurface)) + .ReplaceAsync(AetheriaRuntimeSurfaceDocuments.ToEveSurfaceState(result.EditorSurface)) .ConfigureAwait(false); if (result.EditorTuiSurface != null) await node.MutableDocument(AetheriaRuntimeVerseRecordKeys.DaemonEditorTuiSurface) - .ReplaceAsync(AetheriaRuntimeEveSurfaceAdapter.ToEveSurfaceState(result.EditorTuiSurface)) + .ReplaceAsync(AetheriaRuntimeSurfaceDocuments.ToEveSurfaceState(result.EditorTuiSurface)) .ConfigureAwait(false); } diff --git a/Aetheria.State.Freeze/Program.cs b/Aetheria.State.Freeze/Program.cs index c9b6e691e..1ae4b11fc 100644 --- a/Aetheria.State.Freeze/Program.cs +++ b/Aetheria.State.Freeze/Program.cs @@ -117,17 +117,17 @@ static async Task ExportFixtureSetAsync( MaxX = options.ViewportMaxX, MaxY = options.ViewportMaxY }; - WriteArtifact(outputDirectory, manifest, "projection.rts-viewport", AetheriaRuntimeDaemonSchemas.RtsViewport, AetheriaRuntimeRtsProjection.ProjectViewport(frame, viewport)); - WriteArtifact(outputDirectory, manifest, "projection.objects-viewport", AetheriaRuntimeDaemonSchemas.ObjectsViewport, AetheriaRuntimeRtsProjection.ProjectObjectsViewport(frame, viewport)); - WriteArtifact(outputDirectory, manifest, "projection.gravity-viewport", AetheriaRuntimeDaemonSchemas.GravityViewport, AetheriaRuntimeRtsProjection.ProjectGravityViewport(frame, viewport)); - WriteArtifact(outputDirectory, manifest, "projection.current-zone", AetheriaRuntimeDaemonSchemas.CurrentZone, AetheriaRuntimeRtsProjection.ProjectCurrentZone(frame)); - WriteArtifact(outputDirectory, manifest, "projection.current-entity", AetheriaRuntimeDaemonSchemas.CurrentEntity, AetheriaRuntimeRtsProjection.ProjectCurrentEntity(frame)); - WriteArtifact(outputDirectory, manifest, "projection.sector-map", AetheriaRuntimeDaemonSchemas.SectorMap, AetheriaRuntimeRtsProjection.ProjectSectorMap(frame)); - WriteArtifact(outputDirectory, manifest, "projection.zone-render", AetheriaRuntimeDaemonSchemas.ZoneRender, AetheriaRuntimeRtsProjection.ProjectZoneRender(frame)); + WriteArtifact(outputDirectory, manifest, "document.rts-viewport", AetheriaRuntimeDaemonSchemas.RtsViewport, AetheriaRuntimeRtsDocuments.Viewport(frame, viewport)); + WriteArtifact(outputDirectory, manifest, "document.objects-viewport", AetheriaRuntimeDaemonSchemas.ObjectsViewport, AetheriaRuntimeRtsDocuments.ObjectsViewport(frame, viewport)); + WriteArtifact(outputDirectory, manifest, "document.gravity-viewport", AetheriaRuntimeDaemonSchemas.GravityViewport, AetheriaRuntimeRtsDocuments.GravityViewport(frame, viewport)); + WriteArtifact(outputDirectory, manifest, "document.current-zone", AetheriaRuntimeDaemonSchemas.CurrentZone, AetheriaRuntimeRtsDocuments.CurrentZone(frame)); + WriteArtifact(outputDirectory, manifest, "document.current-entity", AetheriaRuntimeDaemonSchemas.CurrentEntity, AetheriaRuntimeRtsDocuments.CurrentEntity(frame)); + WriteArtifact(outputDirectory, manifest, "document.sector-map", AetheriaRuntimeDaemonSchemas.SectorMap, AetheriaRuntimeRtsDocuments.SectorMap(frame)); + WriteArtifact(outputDirectory, manifest, "document.zone-render", AetheriaRuntimeDaemonSchemas.ZoneRender, AetheriaRuntimeRtsDocuments.ZoneRender(frame)); var selectedEntityIndex = ResolveSelectedEntityIndex(frame); - WriteArtifact(outputDirectory, manifest, "projection.selected-object", AetheriaRuntimeDaemonSchemas.SelectedObject, AetheriaRuntimeRtsProjection.ProjectSelectedObject(frame, selectedEntityIndex)); - WriteArtifact(outputDirectory, manifest, "projection.inventory", AetheriaRuntimeDaemonSchemas.Inventory, AetheriaRuntimeRtsProjection.ProjectInventory(frame, selectedEntityIndex)); + WriteArtifact(outputDirectory, manifest, "document.selected-object", AetheriaRuntimeDaemonSchemas.SelectedObject, AetheriaRuntimeRtsDocuments.SelectedObject(frame, selectedEntityIndex)); + WriteArtifact(outputDirectory, manifest, "document.inventory", AetheriaRuntimeDaemonSchemas.Inventory, AetheriaRuntimeRtsDocuments.Inventory(frame, selectedEntityIndex)); WriteJson(Path.Combine(outputDirectory, "manifest.json"), manifest); } diff --git a/Aetheria.State.Smoke/Program.cs b/Aetheria.State.Smoke/Program.cs index 770b37a19..6e8326b4b 100644 --- a/Aetheria.State.Smoke/Program.cs +++ b/Aetheria.State.Smoke/Program.cs @@ -173,7 +173,7 @@ await using (var node = await AetheriaStateNode.OpenAsync(statePath, "aetheria-s } } await node.MutableDocument(AetheriaRuntimeVerseRecordKeys.DaemonGameSurface) - .ReplaceAsync(AetheriaRuntimeEveSurfaceAdapter.ToEveSurfaceState(daemonGameSurface)); + .ReplaceAsync(AetheriaRuntimeSurfaceDocuments.ToEveSurfaceState(daemonGameSurface)); await node.MutableDocument(AetheriaStateNode.RuntimeSessionKey("smoke-runtime")).ReplaceAsync(new AetheriaRuntimeSession { RuntimeId = "smoke-runtime", diff --git a/Aetheria.State.Verify/Program.cs b/Aetheria.State.Verify/Program.cs index 63608b90b..ee54545b3 100644 --- a/Aetheria.State.Verify/Program.cs +++ b/Aetheria.State.Verify/Program.cs @@ -76,7 +76,9 @@ RequireDeadPropertiesPanelShellDeleted(root); RequireTypedBehaviorMetadataCoverage(root); RequireNameToolsUsesUiToolkit(root); RequireRuntimeStateReaderOwnsUnityStateAcquisition(root); +RequireNoScopedReactiveFacadeWrappers(root); RequireNoDeadRuntimeProjectionCaches(root); +RequireNoDeadRuntimeProjectionClasses(root); RequireNoDeadInspectorMetadata(root); RequireDaemonHostDoesNotDrainRuntimeCommits(root); RequireRuntimeSimulationTuningRequests(root); @@ -707,18 +709,24 @@ static void RequireSharedEvePackagesImportedFromEveRepo(string root) "org.gamecult.eve.unity-uitoolkit", "Runtime", "EveUiToolkitSurfaceLowerer.cs"); - var runtimeAdapterPath = Path.Combine( - root, - "Packages", - "org.gamecult.aetheria.state", - "Runtime", - "AetheriaRuntimeEveSurfaceAdapter.cs"); var runtimeSurfaceBuilderPath = Path.Combine( root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimePlayerSettingsSurfaceBuilder.cs"); + var runtimeSurfaceDocumentsPath = Path.Combine( + root, + "Packages", + "org.gamecult.aetheria.state", + "Runtime", + "AetheriaRuntimeSurfaceDocuments.cs"); + var deletedRuntimeEveSurfaceAdapterPath = Path.Combine( + root, + "Packages", + "org.gamecult.aetheria.state", + "Runtime", + "AetheriaRuntimeEveSurfaceAdapter.cs"); var runtimeCultCacheDocumentStorePath = Path.Combine( root, "Packages", @@ -759,12 +767,12 @@ static void RequireSharedEvePackagesImportedFromEveRepo(string root) var upstreamUnityUiToolkitLowerer = File.Exists(upstreamUnityUiToolkitLowererPath) ? File.ReadAllText(upstreamUnityUiToolkitLowererPath) : throw new InvalidOperationException("Cannot verify shared Eve package ownership; upstream EveUiToolkitSurfaceLowerer.cs is missing."); - var runtimeAdapter = File.Exists(runtimeAdapterPath) - ? File.ReadAllText(runtimeAdapterPath) - : throw new InvalidOperationException("Cannot verify shared Eve package ownership; AetheriaRuntimeEveSurfaceAdapter.cs is missing."); var runtimeSurfaceBuilder = File.Exists(runtimeSurfaceBuilderPath) ? File.ReadAllText(runtimeSurfaceBuilderPath) : throw new InvalidOperationException("Cannot verify shared Eve package ownership; AetheriaRuntimePlayerSettingsSurfaceBuilder.cs is missing."); + var runtimeSurfaceDocuments = File.Exists(runtimeSurfaceDocumentsPath) + ? File.ReadAllText(runtimeSurfaceDocumentsPath) + : throw new InvalidOperationException("Cannot verify shared Eve package ownership; AetheriaRuntimeSurfaceDocuments.cs is missing."); var runtimeCultCacheDocumentStore = File.Exists(runtimeCultCacheDocumentStorePath) ? File.ReadAllText(runtimeCultCacheDocumentStorePath) : throw new InvalidOperationException("Cannot verify shared Eve package ownership; AetheriaRuntimeCultCacheDocumentStore.cs is missing."); @@ -855,35 +863,40 @@ static void RequireSharedEvePackagesImportedFromEveRepo(string root) if (!upstreamUnityUiToolkitLowerer.Contains("ResolveOperation(document, command)", StringComparison.Ordinal) || !upstreamUnityUiToolkitLowerer.Contains("CultMesh.OperationInvocation(template.Operation)", StringComparison.Ordinal) || - !upstreamUnityUiToolkitLowerer.Contains("CultMesh.OperationPayload(component.Props)", StringComparison.Ordinal)) + !upstreamUnityUiToolkitLowerer.Contains("CultMesh.OperationPayload(component.Props)", StringComparison.Ordinal) || + !upstreamUnityUiToolkitLowerer.Contains("LowerEmbeddedDocument(", StringComparison.Ordinal) || + !upstreamUnityUiToolkitLowerer.Contains("component.EmbeddedDocuments", StringComparison.Ordinal)) { throw new InvalidOperationException( - "The shared Eve Unity UI Toolkit lowerer no longer emits CultMesh operation invocation descriptors and payloads."); + "The shared Eve Unity UI Toolkit lowerer no longer emits CultMesh operation invocation descriptors/payloads or nested document slots."); } - if (runtimeSurfaceBuilder.Contains("public sealed class AetheriaRuntimeSurfaceStateBinding", StringComparison.Ordinal) || - !runtimeSurfaceBuilder.Contains("public IReadOnlyList StateBindings", StringComparison.Ordinal) || + if (runtimeSurfaceDocuments.Contains("public sealed class AetheriaRuntimeSurfaceStateBinding", StringComparison.Ordinal) || + !runtimeSurfaceDocuments.Contains("public IReadOnlyList StateBindings", StringComparison.Ordinal) || + !runtimeSurfaceDocuments.Contains("public IReadOnlyList EmbeddedDocuments", StringComparison.Ordinal) || + !runtimeSurfaceDocuments.Contains("public sealed class AetheriaRuntimeEmbeddedDocumentSlot", StringComparison.Ordinal) || !runtimeSurfaceBuilder.Contains("public static CultMeshStateBindingDescriptor ForDaemonStateRef(", StringComparison.Ordinal) || - !runtimeSurfaceBuilder.Contains("public CultMeshOperationBindingDescriptor Operation", StringComparison.Ordinal)) + !runtimeSurfaceDocuments.Contains("public CultMeshOperationBindingDescriptor Operation", StringComparison.Ordinal)) { throw new InvalidOperationException( - "Aetheria runtime surfaces should use CultMesh state and operation descriptors directly instead of local live binding DTOs."); + "Aetheria runtime surfaces should use CultMesh state, operation, and embedded document descriptors directly instead of local live binding DTOs."); } - if (!runtimeAdapter.Contains("component.StateBindings.Select(ToCultMeshStateBinding).ToArray()", StringComparison.Ordinal) || - !runtimeAdapter.Contains("private static CultMeshStateBindingDescriptor ToCultMeshStateBinding(", StringComparison.Ordinal)) + if (!runtimeSurfaceDocuments.Contains("component.StateBindings.Select(ToCultMeshStateBinding).ToArray()", StringComparison.Ordinal) || + !runtimeSurfaceDocuments.Contains("component.EmbeddedDocuments.Select(ToEveEmbeddedDocumentSlot).ToArray()", StringComparison.Ordinal) || + !runtimeSurfaceDocuments.Contains("private static CultMeshStateBindingDescriptor ToCultMeshStateBinding(", StringComparison.Ordinal)) { throw new InvalidOperationException( - "Aetheria runtime surfaces no longer hand CultMesh state bindings to the shared Eve component model."); + "Aetheria runtime surfaces no longer hand CultMesh state bindings and embedded document slots to the shared Eve component model."); } if (!runtimeSurfaceBuilder.Contains("CultMesh.StateBindingRecord(binding)", StringComparison.Ordinal) || - !runtimeAdapter.Contains("CultMesh.StateBindingRecord(", StringComparison.Ordinal) || - !runtimeAdapter.Contains(".ToBinding()", StringComparison.Ordinal) || + !runtimeSurfaceDocuments.Contains("CultMesh.StateBindingRecord(", StringComparison.Ordinal) || + !runtimeSurfaceDocuments.Contains(".ToBinding()", StringComparison.Ordinal) || runtimeSurfaceBuilder.Contains("CultMesh.RouteRecord(binding.RouteHint)", StringComparison.Ordinal) || - runtimeAdapter.Contains("CultMesh.RouteRecord(binding.RouteKind, binding.RouteDescription)", StringComparison.Ordinal) || + runtimeSurfaceDocuments.Contains("CultMesh.RouteRecord(binding.RouteKind, binding.RouteDescription)", StringComparison.Ordinal) || runtimeSurfaceBuilder.Contains("ParseRouteKind(", StringComparison.Ordinal) || - runtimeAdapter.Contains("ParseRouteKind(", StringComparison.Ordinal)) + runtimeSurfaceDocuments.Contains("ParseRouteKind(", StringComparison.Ordinal)) { throw new InvalidOperationException( "Aetheria runtime surfaces reintroduced local state binding flattening; state binding persistence belongs to CultMesh.StateBindingRecord."); @@ -896,25 +909,31 @@ static void RequireSharedEvePackagesImportedFromEveRepo(string root) if (File.Exists(deletedRuntimeSurfaceStateProjectorPath)) { throw new InvalidOperationException( - "AetheriaRuntimeEveSurfaceStateProjector is dead conversion chaff; AetheriaRuntimeEveSurfaceAdapter owns runtime surface to Eve state lowering."); + "AetheriaRuntimeEveSurfaceStateProjector is dead conversion chaff; AetheriaRuntimeSurfaceDocuments owns runtime surface to Eve state lowering."); + } + + if (File.Exists(deletedRuntimeEveSurfaceAdapterPath)) + { + throw new InvalidOperationException( + "AetheriaRuntimeEveSurfaceAdapter is dead conversion chaff; runtime surface lowering belongs on AetheriaRuntimeSurfaceDocuments."); } var surfaceCommandDocuments = string.Join( "\n", - runtimeAdapter, + runtimeSurfaceDocuments, playerSettingsSurfaceDocuments, catalogSurfaceDocuments, operationsSurfaceDocuments); if (!runtimeCultCacheDocumentStore.Contains("CultMesh.OperationBindingRecord(command.Operation)", StringComparison.Ordinal) || !runtimeCultCacheDocumentStore.Contains("CultMesh.OperationBindingRecord(", StringComparison.Ordinal) || !runtimeCultCacheDocumentStore.Contains("routeDescription).ToBinding()", StringComparison.Ordinal) || - !runtimeAdapter.Contains("CultMesh.OperationBindingRecord(", StringComparison.Ordinal) || - !runtimeAdapter.Contains("command.SchemaId", StringComparison.Ordinal) || - !runtimeAdapter.Contains("public static EveSurfaceState ToEveSurfaceState(AetheriaRuntimeSurfaceDocument document)", StringComparison.Ordinal) || - !runtimeAdapter.Contains("global::Aetheria.State.Documents.EveSurfaceComponent ToEveSurfaceState(", StringComparison.Ordinal) || + !runtimeSurfaceDocuments.Contains("CultMesh.OperationBindingRecord(", StringComparison.Ordinal) || + !runtimeSurfaceDocuments.Contains("command.SchemaId", StringComparison.Ordinal) || + !runtimeSurfaceDocuments.Contains("public static EveSurfaceState ToEveSurfaceState(AetheriaRuntimeSurfaceDocument document)", StringComparison.Ordinal) || + !runtimeSurfaceDocuments.Contains("global::Aetheria.State.Documents.EveSurfaceComponent ToEveSurfaceState(", StringComparison.Ordinal) || !surfaceCommandDocuments.Contains("CultMesh.OperationBindingRecord(", StringComparison.Ordinal) || runtimeCultCacheDocumentStore.Contains("new CultMeshOperationBindingDescriptor(", StringComparison.Ordinal) || - runtimeAdapter.Contains("new CultMeshOperationBindingDescriptor(", StringComparison.Ordinal)) + runtimeSurfaceDocuments.Contains("new CultMeshOperationBindingDescriptor(", StringComparison.Ordinal)) { throw new InvalidOperationException( "Aetheria runtime surfaces reintroduced local operation binding flattening; surface command persistence belongs to CultMesh.OperationBindingRecord."); @@ -1013,6 +1032,8 @@ static void RequirePackageSerializerBoundary(string root) "AetheriaRuntimeAssetDocuments.cs", "AetheriaRuntimeRenderSplatDocuments.cs", "AetheriaRuntimeSettingsDocuments.cs", + "AetheriaRuntimeSurfaceDocuments.cs", + "AetheriaRuntimeInventoryDropdownSurfaceDocuments.cs", "AetheriaRuntimeStarbridgeDocuments.cs", "AetheriaRuntimeStarbridgePlayerSeatDocuments.cs", "AetheriaRuntimeVerseAuthorityPolicy.cs" @@ -1313,6 +1334,102 @@ static void RequireTypedBehaviorBodyKeys(string root) } } +static void RequireNoScopedReactiveFacadeWrappers(string root) +{ + var providerPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "AetheriaUnityRuntimeClientProvider.cs"); + var clientStatePath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaClientState.cs"); + var requiredPaths = new[] { providerPath, clientStatePath }; + var missingPaths = requiredPaths.Where(path => !File.Exists(path)).ToArray(); + if (missingPaths.Length > 0) + { + throw new InvalidOperationException( + "Cannot verify scoped reactive facade cleanup; missing sources: " + + string.Join(", ", missingPaths.Select(path => Path.GetRelativePath(root, path)))); + } + + var reactiveFacadeSymbols = new[] + { + "ReactiveProviderAdvertisement(", + "ReactiveHealth(", + "ReactiveCommandBoundary(", + "ReactiveAuthorityPolicy(", + "ReactiveStarbridgeScenario(", + "ReactiveStarbridgeSession(", + "ReactiveStarbridgeSummary(", + "ReactiveDaemonFrame(", + "ReactiveDaemonSoaView(", + "ReactiveZoneRender(", + "ReactiveLoadoutTemplates(", + "ReactiveSectorMap(", + "ReactiveCurrentZone(", + "ReactiveZoneContacts(", + "ReactiveVerseHostSettings(", + "ReactiveCurrentDocking(", + "ReactiveCurrentEntity(", + "ReactiveStationRefit(", + "ReactiveCatalogSnapshot(", + "ReactivePlayerSettingsDocument(", + "ReactiveEveSurface(", + "ReactiveRtsViewport(", + "ReactiveObjectsViewport(", + "ReactiveGravityViewport(", + "ReactiveRenderSplatsViewport(", + "ReactiveZoneDetails(", + "ReactiveZoneDetailsSurface(", + "ReactiveInventoryPanelSurface(", + "ReactiveInventoryDropdownSurface(", + "ReactiveSelectedObject(", + "ReactiveInventory(", + "ReactiveStarbridgePlayerSeat(" + }; + + var facadeHits = requiredPaths + .Select(path => new + { + Path = path, + Source = File.ReadAllText(path) + }) + .SelectMany(entry => reactiveFacadeSymbols + .Where(symbol => entry.Source.Contains(symbol, StringComparison.Ordinal)) + .Select(symbol => $"{Path.GetRelativePath(root, entry.Path)}:{symbol}")) + .ToArray(); + if (facadeHits.Length > 0) + { + throw new InvalidOperationException( + "Aetheria client/provider state must expose typed document handles, not scoped ReactiveX facade methods. Use Handle.Reactive() at the caller edge: " + + string.Join(", ", facadeHits)); + } + + var sourceRoots = new[] + { + Path.Combine(root, "Assets", "Scripts"), + Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime"), + Path.Combine(root, "Packages", "org.gamecult.aetheria.eve-runtime", "Runtime") + }; + var scopedReactiveCallHits = sourceRoots + .Where(Directory.Exists) + .SelectMany(path => Directory.EnumerateFiles(path, "*.cs", SearchOption.AllDirectories)) + .Where(path => !path.Contains(Path.DirectorySeparatorChar + "Plugins" + Path.DirectorySeparatorChar, StringComparison.OrdinalIgnoreCase)) + .Select(path => new + { + Path = path, + Source = File.ReadAllText(path) + }) + .SelectMany(entry => System.Text.RegularExpressions.Regex + .Matches(entry.Source, @"\.Reactive(?!Async\b)[A-Z][A-Za-z0-9_]*\s*\(") + .Select(match => $"{Path.GetRelativePath(root, entry.Path)}:{match.Value}")) + .Distinct(StringComparer.Ordinal) + .ToArray(); + if (scopedReactiveCallHits.Length > 0) + { + throw new InvalidOperationException( + "Aetheria source still calls scoped ReactiveX wrappers. Grab the typed document handle once and call .Reactive() on that handle: " + + string.Join(", ", scopedReactiveCallHits)); + } + + Console.WriteLine("Scoped reactive facade cleanup: Unity/Aetheria source uses typed document handles plus Handle.Reactive(), not ReactiveX wrappers"); +} + static void RequireNoDeadRuntimeProjectionCaches(string root) { var runtimeDocumentsFactoryDirectory = Path.Combine(root, "Assets", "Scripts", "ServerShared", "RuntimeProjection"); @@ -1351,6 +1468,38 @@ static void RequireNoDeadRuntimeProjectionCaches(string root) } } +static void RequireNoDeadRuntimeProjectionClasses(string root) +{ + var forbiddenSymbols = new[] + { + "AetheriaRuntimeRtsProjection", + "AetheriaRuntimeStarbridgeProjection" + }; + + var hits = Directory.EnumerateFiles(root, "*.cs", SearchOption.AllDirectories) + .Where(path => + !path.Contains($"{Path.DirectorySeparatorChar}Assets{Path.DirectorySeparatorChar}Plugins{Path.DirectorySeparatorChar}", StringComparison.Ordinal) && + !path.Contains($"{Path.DirectorySeparatorChar}bin{Path.DirectorySeparatorChar}", StringComparison.OrdinalIgnoreCase) && + !path.Contains($"{Path.DirectorySeparatorChar}obj{Path.DirectorySeparatorChar}", StringComparison.OrdinalIgnoreCase) && + !path.Contains($"{Path.DirectorySeparatorChar}Temp{Path.DirectorySeparatorChar}", StringComparison.OrdinalIgnoreCase) && + !path.EndsWith(Path.Combine("Aetheria.State.Verify", "Program.cs"), StringComparison.OrdinalIgnoreCase)) + .SelectMany(path => + { + var text = File.ReadAllText(path); + return forbiddenSymbols + .Where(symbol => text.Contains(symbol, StringComparison.Ordinal)) + .Select(symbol => $"{Path.GetRelativePath(root, path)} contains {symbol}"); + }) + .ToArray(); + + if (hits.Length > 0) + { + throw new InvalidOperationException( + "Dead runtime projection class names must stay deleted; use typed document builders/handles instead: " + + string.Join("; ", hits)); + } +} + static void RequireNoDeadInspectorMetadata(string root) { var checkedFiles = new[] @@ -2561,9 +2710,12 @@ static void RequireDaemonRenderQueryAuthority(string root) { "CatalogSnapshot()", "CurrentEntityHudStatus()", - "AetheriaUnityRuntimeClientProvider.CurrentCatalog(\"unity-schematic-display\")", - "AetheriaUnityRuntimeClientProvider.CurrentPlayerSettings(\"unity-schematic-display\")", - ".CurrentEntityState(\"unity-schematic-display\")", + "private AetheriaClientState _runtimeState;", + "private AetheriaClientState RuntimeState", + "AetheriaUnityRuntimeClientProvider.RuntimeState(\"unity-schematic-display\")", + "RuntimeState.Catalog.Latest()", + "RuntimeState.PlayerSettings.Latest()", + "RuntimeState.CurrentEntity.Latest()?.Hud", "?.Hud ?? new AetheriaRuntimeCurrentEntityHudStatus();", "hud.OverrideShutdown", "hud.HeatsinksEnabled", @@ -3218,12 +3370,13 @@ static void RequireEveRuntimeBootstrap(string root) "private string surfaceId = AetheriaRuntimeDaemonGameSurfaceBuilder.SurfaceId", "ReadDaemonSurface(stateBoot)", ".RuntimeState(stateBoot, \"unity-eve-surface-presenter\")", - ".ReactiveEveSurface(surfaceId)", + ".EveSurfaceDocument(surfaceId)", + "?.Reactive()", "AetheriaUnityRuntimeClientProvider.EveSurfaceStateRefResolver(", "CultMeshReactiveDocument", "ResolveReactiveDaemonSurfaceState(", "DisposeReactiveSurfaceState()", - "AetheriaRuntimeEveSurfaceAdapter.ToEveSurfaceDocument(", + "AetheriaRuntimeSurfaceDocuments.ToEveSurfaceDocument(", "private bool ShouldMountSurface(", "_mountedSurfaceVersion != surface.Version", "private static readonly AetheriaEveUnitySurfaceChrome RootOnlyChrome", @@ -4240,7 +4393,8 @@ static void RequireRuntimeInputScreenUsesEveSurface(string root) "AetheriaRuntimeEveCommandKind.SetActionBarEnabled", "RequireLocalStateBoot()", "AetheriaUnityRuntimeClientProvider", - ".CurrentPlayerSettings(RequireLocalStateBoot(), \"unity-input-screen\")", + "AetheriaUnityRuntimeClientProvider.RuntimeState(", + "_runtimeState.PlayerSettings.Latest()", "action.ApplyBindingOverride", "new InputAction(\"Aetheria Input Capture\")", "AetheriaRuntimeInputSettingsSurfaceBuilder.IsSupportedCapturePath(", @@ -4573,8 +4727,31 @@ static void RequireSectorMapZoneDetailsUseEveSurface(string root) throw new InvalidOperationException("Cannot verify sector-map zone details shell; shared runtime zone details surface builder is missing."); } + var clientStatePath = Path.Combine( + root, + "Packages", + "org.gamecult.aetheria.state", + "Runtime", + "AetheriaClientState.cs"); + var verseClientPath = Path.Combine( + root, + "Packages", + "org.gamecult.aetheria.state", + "Runtime", + "AetheriaRuntimeVerseClient.cs"); + if (!File.Exists(clientStatePath)) + { + throw new InvalidOperationException("Cannot verify sector-map zone details shell; AetheriaClientState.cs is missing."); + } + if (!File.Exists(verseClientPath)) + { + throw new InvalidOperationException("Cannot verify sector-map zone details shell; AetheriaRuntimeVerseClient.cs is missing."); + } + var source = File.ReadAllText(sectorRendererPath); var zoneDetailsSurfaceBuilder = File.ReadAllText(zoneDetailsSurfaceBuilderPath); + var clientState = File.ReadAllText(clientStatePath); + var verseClient = File.ReadAllText(verseClientPath); var requiredSymbols = new[] { "RenderZoneDetailsSurface(", @@ -4582,17 +4759,10 @@ static void RequireSectorMapZoneDetailsUseEveSurface(string root) "HideZoneDetailsSurface(", "AetheriaEveUnitySurfaceHost.RenderRuntime(", "AetheriaEveUnitySurfaceHost.Hide(_zoneDetailsSurfaceDocument)", - "AetheriaRuntimeZoneDetailsSurfaceBuilder.Build(", - "AetheriaRuntimeZoneDetailsSurfaceBuilder.Facts(", - "ResolveZoneName(sectorZone, zoneDetails)", - "zoneFacts.Bodies", - "zoneFacts.Entities", "AetheriaUnityRuntimeClientProvider", - "AetheriaUnityRuntimeClientProvider.CurrentSectorMap(\"unity-sector-renderer\")", - "AetheriaUnityRuntimeClientProvider.CurrentZoneDetails(zoneIndex, \"unity-sector-renderer\")", - "AetheriaUnityRuntimeClientProvider.CurrentCatalog(\"unity-sector-renderer\")", - "AetheriaUnityRuntimeClientProvider.CurrentPlayerSettings(\"unity-sector-renderer\")", - "AetheriaUnityRuntimeClientProvider.CurrentZoneState(\"unity-sector-renderer\")", + "AetheriaUnityRuntimeClientProvider.RuntimeState(\"unity-sector-renderer\")", + "RuntimeState.ZoneDetailsSurface(zoneIndex).Latest()", + "RuntimeState.CurrentZone.Latest()", "AetheriaRuntimeZoneDetailsSurfaceCommands.TryRead(request, out var command)", "AetheriaRuntimeZoneDetailsCommandKind.Close" }; @@ -4608,8 +4778,64 @@ static void RequireSectorMapZoneDetailsUseEveSurface(string root) string.Join(", ", missingSymbols)); } + var requiredSharedSymbols = new[] + { + "BuildFromDocuments(", + "AetheriaRuntimeZoneDetailsDocument zoneDetails", + "AetheriaRuntimeSectorMapDocument sectorMap", + "AetheriaRuntimeCatalogSnapshot catalog", + "AetheriaRuntimePlayerSettingsDocument playerSettings" + }; + var missingSharedSymbols = requiredSharedSymbols + .Where(symbol => !zoneDetailsSurfaceBuilder.Contains(symbol, StringComparison.Ordinal)) + .ToArray(); + if (missingSharedSymbols.Length > 0) + { + throw new InvalidOperationException( + "Shared runtime no longer owns sector-map zone-details surface composition: " + + string.Join(", ", missingSharedSymbols)); + } + + var requiredManagedHandleSymbols = new[] + { + "ZoneDetailsSurface(int zoneIndex)" + }; + var missingManagedHandleSymbols = requiredManagedHandleSymbols + .Where(symbol => !clientState.Contains(symbol, StringComparison.Ordinal)) + .ToArray(); + if (missingManagedHandleSymbols.Length > 0) + { + throw new InvalidOperationException( + "AetheriaClientState no longer exposes managed zone-details surface document handles: " + + string.Join(", ", missingManagedHandleSymbols)); + } + + var requiredVerseSurfaceSymbols = new[] + { + "IndexedDocumentId(\"aetheria.zone.details.surface\", zoneIndex)", + "ZoneDetailsSurfaceAsync(frame, zoneIndex)", + "AetheriaRuntimeZoneDetailsSurfaceBuilder.BuildFromDocuments(", + "RequireManagedCatalog()", + "RequireManagedPlayerSettings()" + }; + var missingVerseSurfaceSymbols = requiredVerseSurfaceSymbols + .Where(symbol => !verseClient.Contains(symbol, StringComparison.Ordinal)) + .ToArray(); + if (missingVerseSurfaceSymbols.Length > 0) + { + throw new InvalidOperationException( + "AetheriaRuntimeVerseClient no longer provides zone-details surfaces as managed CultMesh documents: " + + string.Join(", ", missingVerseSurfaceSymbols)); + } + var forbiddenAccessSymbols = new[] { + "AetheriaRuntimeZoneDetailsSurfaceBuilder.Build(", + "AetheriaRuntimeZoneDetailsSurfaceBuilder.Facts(", + "AetheriaUnityRuntimeClientProvider.CurrentSectorMap(\"unity-sector-renderer\")", + "AetheriaUnityRuntimeClientProvider.CurrentZoneDetails(zoneIndex, \"unity-sector-renderer\")", + "AetheriaUnityRuntimeClientProvider.CurrentCatalog(\"unity-sector-renderer\")", + "AetheriaUnityRuntimeClientProvider.CurrentPlayerSettings(\"unity-sector-renderer\")", "CultMeshReactiveDocument<", ".ReactiveSectorMap(\"unity-sector-renderer\")", ".ReactiveCurrentZone(\"unity-sector-renderer\")", @@ -4804,7 +5030,8 @@ static void RequireRuntimeMenuTabsUseEveSurface(string root) "new AetheriaRuntimeSurfaceCommandTemplate(", "ResolveVisibleTabs(", "TryResolveCurrentDocking(out var docking)", - "AetheriaUnityRuntimeClientProvider.CurrentDockingState(\"unity-runtime-menu-tabs\")", + "AetheriaUnityRuntimeClientProvider.RuntimeState(\"unity-runtime-menu-tabs\")", + "_runtimeState.CurrentDocking.Latest()", "docking.IsDocked", "GetTabLabel(", "ToRuntimeTabKey(", @@ -4829,7 +5056,7 @@ static void RequireRuntimeMenuTabsUseEveSurface(string root) { "public static class AetheriaEveUnitySurfaceHost", "public static UIDocument Render(", - "new EveUiToolkitSurfaceLowerer()", + "new EveUiToolkitSurfaceLowerer(new EveUiToolkitSurfaceOptions(embeddedDocumentResolver))", "shell.Add(lowerer.Lower(surface, commandHandler))" }; @@ -4866,11 +5093,11 @@ static void RequireRuntimeMenuTabsUseEveSurface(string root) ".ReactiveCurrentDocking(\"unity-runtime-menu-tabs\")", "docking = _currentDocking.Current", "AetheriaRuntimeObservedDockingState", - "SetObservedEntityIndex(AetheriaUnityObservedEntityIndex observedEntityIndex)", - "private AetheriaUnityObservedEntityIndex _observedEntityIndex;", + "SetPresentationEntityIndex(AetheriaUnityPresentationEntityIndex presentationEntityIndex)", + "private AetheriaUnityPresentationEntityIndex _presentationEntityIndex;", "private AetheriaUnityObservedDockingIndex _observedDockingIndex;", "TryResolveObservedDockingIndex(out AetheriaUnityObservedDockingIndex dockingIndex)", - "new AetheriaUnityObservedDockingIndex(_observedEntityIndex)", + "new AetheriaUnityObservedDockingIndex(_presentationEntityIndex)", "ComposeTabSurface(", "ProjectTabSurfaceState(", "ProjectTabSurface(", @@ -4894,7 +5121,8 @@ static void RequireRuntimeMenuTabsUseEveSurface(string root) if (source.Contains("GameManager.IsObservedDocked", StringComparison.Ordinal) || source.Contains("GameManager.TryGetObservedDockedLocalStory", StringComparison.Ordinal) || source.Contains("AetheriaClientReactiveDockingState _dockingState", StringComparison.Ordinal) || - source.Contains("private AetheriaClientDockingSnapshot ResolveCurrentDocking()", StringComparison.Ordinal)) + source.Contains("private AetheriaClientDockingSnapshot ResolveCurrentDocking()", StringComparison.Ordinal) || + source.Contains("AetheriaUnityRuntimeClientProvider.CurrentDockingState(\"unity-runtime-menu-tabs\")", StringComparison.Ordinal)) { throw new InvalidOperationException( "MenuPanel tab visibility must read current typed docking state directly instead of direct docking caches, ActionGameManager observed adapters, or Unity observed docking index bridges."); @@ -4908,13 +5136,16 @@ static void RequireRuntimeMenuTabsUseEveSurface(string root) var requiredDockedStoryObserverSymbols = new[] { - "TryResolveDockedLocalStory(out _currentLocation)", - "private bool TryResolveDockedLocalStory(out LocationStory story)", - "SetObservedEntityIndex(AetheriaUnityObservedEntityIndex observedEntityIndex)", + "TryResolveDockedLocalInkStory(out _currentLocalInkLocation)", + "private bool TryResolveDockedLocalInkStory(out LocationStory story)", + "SetPresentationEntityIndex(AetheriaUnityPresentationEntityIndex presentationEntityIndex)", "CurrentDockingSnapshot()", - "AetheriaUnityRuntimeClientProvider.CurrentDockingState(\"unity-local-menu\")", + "AetheriaUnityRuntimeClientProvider.RuntimeState(\"unity-local-menu\")", + "_runtimeState.CurrentDocking.Latest()", "docking.IsDocked", - "_observedEntityIndex.TryResolveDockingBayByRecordKey(", + "Ink story execution is still a Unity-local island.", + "local story state becomes a daemon-owned CultMesh document.", + "_presentationEntityIndex.TryGetPresentationDockingBayByRecordKey(", "docking.DockParentEntityKey", "docking.DockingBayIndex", "dockingBay?.Entity is not OrbitalEntity { Story: { } dockedStory }", @@ -4931,7 +5162,7 @@ static void RequireRuntimeMenuTabsUseEveSurface(string root) if (missingDockedStoryObserverSymbols.Length > 0) { throw new InvalidOperationException( - "Runtime menu/local story visibility no longer flows through daemon-observed dock state: " + + "Runtime menu/local story visibility no longer flows through typed docking state plus explicit Unity presentation lookup: " + string.Join(", ", missingDockedStoryObserverSymbols)); } @@ -4939,10 +5170,12 @@ static void RequireRuntimeMenuTabsUseEveSurface(string root) source.Contains("GameManager.DockedEntity as OrbitalEntity", StringComparison.Ordinal) || localMenu.Contains("AetheriaClientReactiveDockingState _dockingState", StringComparison.Ordinal) || localMenu.Contains("private bool TryResolveDockingState(out AetheriaClientDockingSnapshot dockingState)", StringComparison.Ordinal) || + localMenu.Contains("TryResolveDockedLocalStory(", StringComparison.Ordinal) || + localMenu.Contains("_currentLocation", StringComparison.Ordinal) || localMenu.Contains("private AetheriaUnityObservedDockingIndex _observedDockingIndex", StringComparison.Ordinal) || localMenu.Contains("TryResolveObservedDockingIndex(out AetheriaUnityObservedDockingIndex dockingIndex)", StringComparison.Ordinal) || localMenu.Contains("TryResolveObservedDockingIndex(out var dockingIndex)", StringComparison.Ordinal) || - localMenu.Contains("new AetheriaUnityObservedDockingIndex(_observedEntityIndex)", StringComparison.Ordinal) || + localMenu.Contains("new AetheriaUnityObservedDockingIndex(_presentationEntityIndex)", StringComparison.Ordinal) || localMenu.Contains("dockingIndex.TryResolveCurrentDockingBay", StringComparison.Ordinal) || localMenu.Contains("ActionGameManager.Instance.DockedEntity", StringComparison.Ordinal) || localMenu.Contains("TryGetObservedDockedLocalStory", StringComparison.Ordinal) || @@ -5029,13 +5262,13 @@ static void RequireInventoryShipSettingsUseEveSurface(string root) "ResolveDefaultShutdownPerformance()", "PlayerSettingsSnapshot()?.DefaultShutdownPerformance", "TryReadCurrentEntity(out var currentEntity)", - "SetObservedEntityIndex(AetheriaUnityObservedEntityIndex observedEntityIndex)", + "SetPresentationEntityIndex(AetheriaUnityPresentationEntityIndex presentationEntityIndex)", "private AetheriaRuntimeCurrentEntityDocument _shipSettingsCurrentEntity;", "!TryReadCurrentEntity(out var latestCurrentEntity)", "RequestEntityShutdownPerformance(", "latestCurrentEntity.EntityKey", "(float)latestCurrentEntity.ShutdownPerformance", - "AetheriaUnityRuntimeClientProvider.CurrentEntityState(\"unity-inventory-menu\")", + "RuntimeState.CurrentEntity.Latest()", "CurrentEntitySnapshot()", "currentEntity = CurrentEntitySnapshot();" }; @@ -5157,12 +5390,12 @@ static void RequireInventoryCargoItemDetailsUseEveSurface(string root) "org.gamecult.aetheria.eve-runtime", "Runtime", "AetheriaEveUnitySurfaceHost.cs"); - var runtimeEveSurfaceAdapterPath = Path.Combine( + var runtimeSurfaceDocumentsPath = Path.Combine( root, "Packages", "org.gamecult.aetheria.state", "Runtime", - "AetheriaRuntimeEveSurfaceAdapter.cs"); + "AetheriaRuntimeSurfaceDocuments.cs"); var cargoItemSurfaceBuilderPath = Path.Combine( root, "Packages", @@ -5183,7 +5416,7 @@ static void RequireInventoryCargoItemDetailsUseEveSurface(string root) surfaceDocumentPath, daemonItemStatQueriesPath, eveUnitySurfaceHostPath, - runtimeEveSurfaceAdapterPath, + runtimeSurfaceDocumentsPath, cargoItemSurfaceBuilderPath, equippedItemSurfaceBuilderPath, unityProjectPath @@ -5205,7 +5438,7 @@ static void RequireInventoryCargoItemDetailsUseEveSurface(string root) var daemonItemStatQueries = File.ReadAllText(daemonItemStatQueriesPath); var unityProject = File.ReadAllText(unityProjectPath); var eveUnitySurfaceHost = File.ReadAllText(eveUnitySurfaceHostPath); - var runtimeEveSurfaceAdapter = File.ReadAllText(runtimeEveSurfaceAdapterPath); + var runtimeSurfaceDocuments = File.ReadAllText(runtimeSurfaceDocumentsPath); var cargoItemSurfaceBuilder = File.ReadAllText(cargoItemSurfaceBuilderPath); var equippedItemSurfaceBuilder = File.ReadAllText(equippedItemSurfaceBuilderPath); var requiredSymbols = new[] @@ -5233,9 +5466,12 @@ static void RequireInventoryCargoItemDetailsUseEveSurface(string root) string.Join(", ", missingSymbols)); } - if (!source.Contains("RenderCargoItemDetailsSurface(item);", StringComparison.Ordinal)) + if (source.Contains("InventoryCargoEventData", StringComparison.Ordinal) || + source.Contains("Occupancy[", StringComparison.Ordinal) || + source.Contains("RenderCargoItemDetailsSurface(item);", StringComparison.Ordinal)) { - throw new InvalidOperationException("InventoryMenu cargo click path no longer routes item inspection through the Eve surface."); + throw new InvalidOperationException( + "InventoryMenu must not open cargo-item inspection by decoding Unity inventory cells; item selection belongs to the daemon-presented inventory surface."); } var runtimeStateRefResolverPath = Path.Combine( @@ -5281,11 +5517,11 @@ static void RequireInventoryCargoItemDetailsUseEveSurface(string root) !eveUnitySurfaceHost.Contains("CreateDefaultStateRefResolver()", StringComparison.Ordinal) || !eveUnitySurfaceHost.Contains("AetheriaUnityRuntimeClientProvider", StringComparison.Ordinal) || !eveUnitySurfaceHost.Contains("AetheriaUnityRuntimeClientProvider.EveSurfaceStateRefResolver(", StringComparison.Ordinal) || - !runtimeEveSurfaceAdapter.Contains("public static EveSurfaceDocument ResolveStateRefs(", StringComparison.Ordinal) || - !runtimeEveSurfaceAdapter.Contains("ResolvePropRefs(props, resolveStateRef)", StringComparison.Ordinal) || - !runtimeEveSurfaceAdapter.Contains("ResolvePropRef(props, AetheriaRuntimeSurfaceStateRefs.Source, \"value\", resolveStateRef)", StringComparison.Ordinal) || - !runtimeEveSurfaceAdapter.Contains("IsStatePointerProp(prop.Key)", StringComparison.Ordinal) || - !runtimeEveSurfaceAdapter.Contains("ResolvePointerValueKey(refProp.Key)", StringComparison.Ordinal) || + !runtimeSurfaceDocuments.Contains("public static EveSurfaceDocument ResolveStateRefs(", StringComparison.Ordinal) || + !runtimeSurfaceDocuments.Contains("ResolvePropRefs(props, resolveStateRef)", StringComparison.Ordinal) || + !runtimeSurfaceDocuments.Contains("ResolvePropRef(props, AetheriaRuntimeSurfaceStateRefs.Source, \"value\", resolveStateRef)", StringComparison.Ordinal) || + !runtimeSurfaceDocuments.Contains("IsStatePointerProp(prop.Key)", StringComparison.Ordinal) || + !runtimeSurfaceDocuments.Contains("ResolvePointerValueKey(refProp.Key)", StringComparison.Ordinal) || !cargoItemSurfaceBuilder.Contains("public string ValueRef { get; }", StringComparison.Ordinal) || !cargoItemSurfaceBuilder.Contains("props.Add(AetheriaRuntimeSurfaceStateRefs.ValueRef(valueRef))", StringComparison.Ordinal) || !equippedItemSurfaceBuilder.Contains("public string ValueRef { get; }", StringComparison.Ordinal) || @@ -5791,8 +6027,8 @@ static void RequireTradeCargoSelectorUseEveSurface(string root) "AetheriaRuntimeTradeCargoSelectorSurfaceCommands.TryRead(request, out var command)", "AetheriaRuntimeTradeCargoSelectorCommandKind.Close", "AetheriaRuntimeTradeCargoSelectorCommandKind.Select", - "AetheriaUnityRuntimeClientProvider.CurrentStationRefit(\"unity-trade\")", - "AetheriaUnityRuntimeClientProvider.CurrentDockingState(\"unity-trade\")", + "RuntimeState.StationRefit.Latest()", + "RuntimeState.CurrentDocking.Latest()", "SetTargetCargo(", "selection.EntityKey", "OwnedQuantity", @@ -5835,11 +6071,11 @@ static void RequireTradeCargoSelectorUseEveSurface(string root) "AetheriaRuntimeTradeCargoSelectorSurfaceBuilder.Compose(", "_cargoSelectorSurfaceProjection", "AetheriaRuntimeObservedDockingState", - "SetObservedEntityIndex(AetheriaUnityObservedEntityIndex observedEntityIndex)", - "private AetheriaUnityObservedEntityIndex _observedEntityIndex;", + "SetPresentationEntityIndex(AetheriaUnityPresentationEntityIndex presentationEntityIndex)", + "private AetheriaUnityPresentationEntityIndex _presentationEntityIndex;", "private AetheriaUnityObservedDockingIndex _observedDockingIndex;", "TryResolveObservedDockingIndex(out AetheriaUnityObservedDockingIndex dockingIndex)", - "new AetheriaUnityObservedDockingIndex(_observedEntityIndex)", + "new AetheriaUnityObservedDockingIndex(_presentationEntityIndex)", "dockingIndex.StationRefitSnapshot()", "dockingIndex.TryResolveCurrentDocking(out docking)", "ClearClientCaches()", @@ -6330,11 +6566,10 @@ static void RequireTradeItemValuesUseRuntimeQueries(string root) string.Join(", ", missingCatalogItemEnumSymbols)); } - if (!inventoryPanel.Contains("FindTypedInventoryItem(item)?.TryGetHardpointType(out HardpointType typedHardpoint) == true", StringComparison.Ordinal) || - inventoryPanel.Contains("TryGetTypedHardpointType(", StringComparison.Ordinal)) + if (inventoryPanel.Contains("TryGetTypedHardpointType(", StringComparison.Ordinal)) { throw new InvalidOperationException( - "InventoryPanel must ask AetheriaRuntimeCatalogItem for typed hardpoint classification instead of parsing catalog strings locally."); + "InventoryPanel must not parse hardpoint classification locally; daemon-owned inventory surfaces consume typed catalog state."); } if (inventoryPanel.Contains("inventory projection", StringComparison.OrdinalIgnoreCase) || @@ -6443,6 +6678,12 @@ static void RequireInventoryDropdownUseEveSurface(string root) "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeInventoryDropdownSurfaceBuilder.cs"); + var inventoryDropdownSurfaceDocumentsPath = Path.Combine( + root, + "Packages", + "org.gamecult.aetheria.state", + "Runtime", + "AetheriaRuntimeInventoryDropdownSurfaceDocuments.cs"); if (!File.Exists(inventoryPanelPath)) { throw new InvalidOperationException("Cannot verify inventory dropdown shell; InventoryPanel.cs is missing."); @@ -6455,38 +6696,45 @@ static void RequireInventoryDropdownUseEveSurface(string root) { throw new InvalidOperationException("Cannot verify inventory dropdown shell; shared runtime inventory dropdown surface builder is missing."); } + if (!File.Exists(inventoryDropdownSurfaceDocumentsPath)) + { + throw new InvalidOperationException("Cannot verify inventory dropdown shell; shared runtime inventory dropdown surface documents are missing."); + } var source = File.ReadAllText(inventoryPanelPath); var inventoryMenu = File.ReadAllText(inventoryMenuPath); - var inventoryDropdownSurfaceBuilder = File.ReadAllText(inventoryDropdownSurfaceBuilderPath); + var inventoryDropdownSurfaceBuilder = File.ReadAllText(inventoryDropdownSurfaceBuilderPath) + "\n" + + File.ReadAllText(inventoryDropdownSurfaceDocumentsPath); var requiredSymbols = new[] { "RenderDropdownSurface(", - "BuildDropdownSurfaceModel(", - "_dropdownSurfaceModel = BuildDropdownSurfaceModel();", + "ResolveDropdownSurface(", + "_dropdownSurface = ResolveDropdownSurface();", + "BuildDropdownSurfaceRequest(", + "RuntimeState.InventoryDropdownSurface(BuildDropdownSurfaceRequest()).Latest()", "HandleDropdownSurfaceCommand(", "ExecuteDropdownSelection(", "AetheriaEveUnitySurfaceHost.RenderRuntime(", "AetheriaEveUnitySurfaceHost.Hide(_dropdownSurfaceDocument)", - "_dropdownSurfaceModel.Document", - "AetheriaRuntimeInventoryDropdownSurfaceBuilder.Build(", + "_dropdownSurface,", "AetheriaRuntimeInventoryDropdownSurfaceCommands.TryRead(request, out var command)", "AetheriaRuntimeInventoryDropdownCommandKind.Close", "AetheriaRuntimeInventoryDropdownCommandKind.Select", - "_dropdownSurfaceModel?.TryResolve(command.Command, out var selection) == true", + "TryResolveDropdownSelection(request, command.Command, out var selection)", + "AetheriaRuntimeInventoryDropdownSurfaceCommands.TryReadSelection(command, selectionKey, out selection)", "AetheriaRuntimeInventoryDropdownSelectionKind.EntityBay", "AetheriaRuntimeInventoryDropdownSelectionKind.Loadout", - "new AetheriaRuntimeInventoryDropdownEntityOption(", - "new AetheriaRuntimeInventoryDropdownLoadoutOption(", "selection.EntityKey", "_displayedEntityKey", "_displayedCargoEntityKey", "TryResolveStationRefitEntity(string entityKey", - "SetObservedEntityIndex(AetheriaUnityObservedEntityIndex observedEntityIndex)", - "_observedEntityIndex.TryResolveEntityByRecordKey", - "_observedEntityIndex.TryResolveDockingBayByRecordKey", + "SetPresentationEntityIndex(AetheriaUnityPresentationEntityIndex presentationEntityIndex)", + "_presentationEntityIndex.TryGetPresentationEntityByRecordKey", + "_presentationEntityIndex.TryGetPresentationDockingBayByRecordKey", + "RenderInventoryPanelSurface()", + "AetheriaEveUnitySurfaceHost.RenderRuntime(", + "HandleInventoryPanelSurfaceCommand", "StationRefitSnapshot", - "currentEntityKey = CurrentEntitySnapshot()?.EntityKey ?? \"\"", "LoadoutRestoreOptions" }; @@ -6526,6 +6774,8 @@ static void RequireInventoryDropdownUseEveSurface(string root) "ProjectDropdownSurface(", "ComposeDropdownSurface(", "_dropdownSurfaceModel.State", + "_dropdownSurfaceModel", + "AetheriaRuntimeInventoryDropdownSurfaceModel", "AetheriaRuntimeInventoryDropdownSurfaceBuilder.Compose(", "_dropdownSurfaceProjection", "GameManager.DockingBay", @@ -6545,15 +6795,14 @@ static void RequireInventoryDropdownUseEveSurface(string root) } if (!inventoryDropdownSurfaceBuilder.Contains("public string EntityKey { get; }", StringComparison.Ordinal) || + !inventoryDropdownSurfaceBuilder.Contains("BuildFromDocuments(", StringComparison.Ordinal) || !inventoryDropdownSurfaceBuilder.Contains("entityKey: entity.EntityKey", StringComparison.Ordinal) || !source.Contains("TryResolveStationRefitEntity(selection.EntityKey", StringComparison.Ordinal) || - !source.Contains("TryResolveCurrentDockingBayRow(out var currentDockingBay)", StringComparison.Ordinal) || !source.Contains("TryResolveCurrentDockingBay(out var dockingBay)", StringComparison.Ordinal) || - !source.Contains("_observedEntityIndex.TryResolveDockingBayByRecordKey", StringComparison.Ordinal) || + !source.Contains("_presentationEntityIndex.TryGetPresentationDockingBayByRecordKey", StringComparison.Ordinal) || !source.Contains("StationRefitSnapshot", StringComparison.Ordinal) || - !source.Contains("currentEntityKey = CurrentEntitySnapshot()?.EntityKey ?? \"\"", StringComparison.Ordinal) || - !source.Contains("currentDockingBay.DockingBayIndex", StringComparison.Ordinal) || - !source.Contains("StationRefitSnapshot()?.DockParentEntityKey", StringComparison.Ordinal) || + !source.Contains("RuntimeState.InventoryDropdownSurface(BuildDropdownSurfaceRequest()).Latest()", StringComparison.Ordinal) || + !source.Contains("RuntimeState.StationRefit.Latest()", StringComparison.Ordinal) || source.Contains("TryResolveObservedDockingIndex(out var dockingIndex)", StringComparison.Ordinal) || source.Contains("dockingIndex.TryResolveCurrentDockingBay", StringComparison.Ordinal) || source.Contains("var hasDockingBay = TryGetTypedCurrentDockingBayFacade", StringComparison.Ordinal) || @@ -6567,8 +6816,8 @@ static void RequireInventoryDropdownUseEveSurface(string root) if (!inventoryMenu.Contains("TryResolveCurrentEntity(out var currentEntity)", StringComparison.Ordinal) || !inventoryMenu.Contains("TryResolveCurrentDockingBay(out var dockingBay)", StringComparison.Ordinal) || - !inventoryMenu.Contains("_observedEntityIndex.TryResolveEntityByRecordKey(currentEntityKey, out currentEntity)", StringComparison.Ordinal) || - !inventoryMenu.Contains("_observedEntityIndex.TryResolveDockingBayByRecordKey", StringComparison.Ordinal) || + !inventoryMenu.Contains("_presentationEntityIndex.TryGetPresentationEntityByRecordKey(currentEntityKey, out currentEntity)", StringComparison.Ordinal) || + !inventoryMenu.Contains("_presentationEntityIndex.TryGetPresentationDockingBayByRecordKey", StringComparison.Ordinal) || !inventoryMenu.Contains("currentEntityKey = CurrentEntitySnapshot()?.EntityKey ?? \"\"", StringComparison.Ordinal) || !inventoryMenu.Contains("currentEntity = CurrentEntitySnapshot();", StringComparison.Ordinal) || !inventoryMenu.Contains("StationRefitSnapshot", StringComparison.Ordinal) || @@ -6602,12 +6851,11 @@ static void RequireInventoryDropdownUseEveSurface(string root) "AetheriaRuntimeInventoryDropdownBayOption", "AetheriaRuntimeInventoryDropdownLoadoutOption", "AetheriaRuntimeInventoryDropdownSelectionKind", - "AetheriaRuntimeInventoryDropdownSurfaceModel", - "public AetheriaRuntimeSurfaceDocument Document { get; }", "public string EntityKey { get; }", "entityKey: entity.EntityKey", - "public static AetheriaRuntimeInventoryDropdownSurfaceModel Build(", - "public bool TryResolve(", + "public static AetheriaRuntimeSurfaceDocument Build(", + "selectionKind", + "templateIndex", "public static string EntityEquipmentCommand(", "public static string EntityBayCommand(", "public static string EntityCommand(", @@ -6628,11 +6876,11 @@ static void RequireInventoryDropdownUseEveSurface(string root) if (inventoryDropdownSurfaceBuilder.Contains("AetheriaRuntimeInventoryDropdownSurfaceState", StringComparison.Ordinal) || inventoryDropdownSurfaceBuilder.Contains("public AetheriaRuntimeInventoryDropdownSurfaceState State { get; }", StringComparison.Ordinal) || - inventoryDropdownSurfaceBuilder.Contains("public static AetheriaRuntimeSurfaceDocument Build(", StringComparison.Ordinal) || + inventoryDropdownSurfaceBuilder.Contains("AetheriaRuntimeInventoryDropdownSurfaceModel", StringComparison.Ordinal) || inventoryDropdownSurfaceBuilder.Contains("public static AetheriaRuntimeInventoryDropdownSurfaceModel Compose(", StringComparison.Ordinal)) { throw new InvalidOperationException( - "Shared runtime inventory dropdown builder must return a model with a finished surface document; do not rebuild a shadow SurfaceState projection layer."); + "Shared runtime inventory dropdown builder must return the finished surface document and carry typed selections in command payload; do not rebuild a shadow SurfaceState/model projection layer."); } } @@ -6714,7 +6962,7 @@ static void RequireStationRefitDockingBaysUseTypedDocuments(string root) "OccupiedHullItemKey = assignedEntity?.HullItemKey", "OccupiedByCurrentEntity = assignedEntityIndex >= 0 && assignedEntityIndex == currentEntityIndex", "CargoItems = dockingBayIndex < contents.Count", - "ProjectStationStock(contents[dockingBayIndex], dockingBayIndex)", + "StationStock(contents[dockingBayIndex], dockingBayIndex)", "var cargoTargets = parent == null", "CargoTargets = cargoTargets", "ProjectStationCargoTargets(parentKey, dockingBayIndex, dockingBays, availableEntities)", @@ -6723,9 +6971,9 @@ static void RequireStationRefitDockingBaysUseTypedDocuments(string root) "AetheriaRuntimeTradeCargoTargetKind.ShipBay", "currentDockingBay?.CargoItems", "entity.CargoItems ?? Array.Empty()", - "ProjectStationStockTradeFacts(", - "private static IReadOnlyList ProjectStationStockTradeFacts(", - "private static int ProjectStationStockPrice(", + "StationStockTradeFacts(", + "private static IReadOnlyList StationStockTradeFacts(", + "private static int StationStockPrice(", "private static int CountStationOwnedQuantity(", "AetheriaRuntimeDaemonTradeItemQueries.TradeItemValue(", "CanAfford = price >= 0 && credits >= price", @@ -6751,11 +6999,9 @@ static void RequireStationRefitDockingBaysUseTypedDocuments(string root) var requiredPanelSymbols = new[] { - "TryResolveCurrentDockingBayRow(out var currentDockingBay)", - "AetheriaRuntimeStationDockingBayRow dockingBay", "StationRefitSnapshot", - "refit.DockingBays ?? Array.Empty()", - "currentDockingBay.DockingBayIndex" + "RuntimeState.StationRefit.Latest()", + "_presentationEntityIndex.TryGetPresentationDockingBayByRecordKey" }; var missingPanelSymbols = requiredPanelSymbols .Where(symbol => !inventoryPanel.Contains(symbol, StringComparison.Ordinal)) @@ -6764,7 +7010,7 @@ static void RequireStationRefitDockingBaysUseTypedDocuments(string root) if (missingPanelSymbols.Length > 0) { throw new InvalidOperationException( - "InventoryPanel must validate current docking-bay display through StationRefitAsync typed docking-bay rows: " + + "InventoryPanel must resolve remaining docking presentation from managed station-refit identity before touching Unity docking objects: " + string.Join(", ", missingPanelSymbols)); } @@ -7196,16 +7442,16 @@ static void RequireInventoryValidationUsesManagedTypedDocuments(string root) var clientState = File.ReadAllText(clientStatePath); if (!clientState.Contains("public CultMeshDocumentHandle Inventory(", StringComparison.Ordinal) || - !clientState.Contains("public CultMeshReactiveDocument ReactiveInventory(", StringComparison.Ordinal) || !clientState.Contains("public CultMeshDocumentHandle ZoneDetails(", StringComparison.Ordinal) || - !clientState.Contains("public CultMeshReactiveDocument ReactiveZoneDetails(", StringComparison.Ordinal) || + clientState.Contains("public CultMeshReactiveDocument ReactiveInventory(", StringComparison.Ordinal) || + clientState.Contains("public CultMeshReactiveDocument ReactiveZoneDetails(", StringComparison.Ordinal) || clientState.Contains("AetheriaClientState.Zone(", StringComparison.Ordinal) || clientState.Contains("AetheriaClientState.Entity(", StringComparison.Ordinal) || clientState.Contains("AetheriaClientZone", StringComparison.Ordinal) || clientState.Contains("AetheriaClientEntity", StringComparison.Ordinal)) { throw new InvalidOperationException( - "AetheriaClientState must expose native typed document handles and indexed reactive typed documents for current entity, station refit, inventory, and zone documents."); + "AetheriaClientState must expose native typed document handles for current entity, station refit, inventory, and zone documents; callers use Handle.Reactive() when they need a stream."); } var sources = new Dictionary @@ -7219,35 +7465,55 @@ static void RequireInventoryValidationUsesManagedTypedDocuments(string root) var runtimeId = name == "InventoryMenu.cs" ? "unity-inventory-menu" : "unity-inventory"; - var currentEntityAccess = $"AetheriaUnityRuntimeClientProvider.CurrentEntityState(\"{runtimeId}\")"; - var stationRefitAccess = $"AetheriaUnityRuntimeClientProvider.CurrentStationRefit(\"{runtimeId}\")"; - var inventoryAccess = $"AetheriaUnityRuntimeClientProvider.CurrentInventory(entityIndex, \"{runtimeId}\")"; var requiredSymbols = new[] { + $"AetheriaUnityRuntimeClientProvider.RuntimeState(\"{runtimeId}\")", "CurrentEntitySnapshot()", "StationRefitSnapshot", - "InventorySnapshot(entityIndex)" + "RuntimeState.CurrentEntity.Latest()", + "RuntimeState.StationRefit.Latest()" }; var missingSymbols = requiredSymbols .Where(symbol => !source.Contains(symbol, StringComparison.Ordinal)) .ToArray(); - var requiredCompactedSymbols = new[] + var forbiddenShortcutSymbols = new[] { - currentEntityAccess, - stationRefitAccess, - inventoryAccess - } - .Select(CompactSource) - .ToArray(); - missingSymbols = missingSymbols - .Concat(requiredCompactedSymbols.Where(symbol => !compact.Contains(symbol, StringComparison.Ordinal))) + $"AetheriaUnityRuntimeClientProvider.CurrentEntityState(\"{runtimeId}\")", + $"AetheriaUnityRuntimeClientProvider.CurrentStationRefit(\"{runtimeId}\")", + $"AetheriaUnityRuntimeClientProvider.CurrentInventory(entityIndex, \"{runtimeId}\")" + }; + var shortcutHits = forbiddenShortcutSymbols + .Where(symbol => compact.Contains(CompactSource(symbol), StringComparison.Ordinal)) .ToArray(); if (missingSymbols.Length > 0) { throw new InvalidOperationException( - $"{name} no longer validates inventory slots through managed typed client documents: " + + $"{name} no longer samples inventory shell context through managed typed client documents: " + string.Join(", ", missingSymbols)); } + if (shortcutHits.Length > 0) + { + throw new InvalidOperationException( + $"{name} still validates inventory slots through provider snapshot shortcuts instead of its managed runtime state handle: " + + string.Join(", ", shortcutHits)); + } + + var forbiddenInventoryValidationSymbols = new[] + { + "InventorySnapshot(entityIndex)", + "RuntimeState.Inventory(entityIndex).Latest()", + "TryResolveManagedInventoryRows(", + "TryValidateTypedCargoSlot(", + "TryValidateTypedEquipmentSlot(" + } + .Where(symbol => source.Contains(symbol, StringComparison.Ordinal)) + .ToArray(); + if (forbiddenInventoryValidationSymbols.Length > 0) + { + throw new InvalidOperationException( + $"{name} still locally validates inventory mutation state instead of lowering the daemon-presented inventory surface: " + + string.Join(", ", forbiddenInventoryValidationSymbols)); + } var forbiddenCompactedSymbols = new[] { @@ -7295,7 +7561,7 @@ static void RequireInventoryValidationUsesManagedTypedDocuments(string root) } } - Console.WriteLine("Inventory validation: cargo/equipment checks read managed typed client documents instead of manual handle walks"); + Console.WriteLine("Inventory validation: Unity samples shell context only; mutation authority belongs to daemon-presented typed inventory surfaces"); } static void RequireMenuDockingUsesManagedTypedSnapshot(string root) @@ -7313,9 +7579,7 @@ static void RequireMenuDockingUsesManagedTypedSnapshot(string root) var requiredClientSymbols = new[] { "public CultMeshDocumentHandle StationRefit { get; }", - "public CultMeshDocumentHandle CurrentDocking { get; }", - "public CultMeshReactiveDocument ReactiveStationRefit()", - "public CultMeshReactiveDocument ReactiveCurrentDocking()" + "public CultMeshDocumentHandle CurrentDocking { get; }" }; var missingClientSymbols = requiredClientSymbols .Where(symbol => !clientState.Contains(symbol, StringComparison.Ordinal)) @@ -7426,11 +7690,23 @@ static void RequireMenuDockingUsesManagedTypedSnapshot(string root) string.Join(", ", directDockingOffenders)); } - Console.WriteLine("Menu docking state: Unity menus sample managed typed docking documents through native current access"); + Console.WriteLine("Menu docking state: Unity menus sample managed typed docking documents through native state handles"); } static bool HasManagedDockingSnapshotAccess(string source) { + if (source.Contains("AetheriaUnityRuntimeClientProvider.RuntimeState(", StringComparison.Ordinal) && + (source.Contains("RuntimeState.CurrentDocking.Latest()", StringComparison.Ordinal) || + source.Contains("_runtimeState.CurrentDocking.Latest()", StringComparison.Ordinal) || + source.Contains("RuntimeState.StationRefit.Latest()", StringComparison.Ordinal) || + source.Contains("_runtimeState.StationRefit.Latest()", StringComparison.Ordinal)) && + !source.Contains("AetheriaClientReactiveDockingState _reactiveDockingState", StringComparison.Ordinal) && + !source.Contains("CultMeshReactiveDocument", StringComparison.Ordinal) && + !source.Contains("CultMeshReactiveDocument", StringComparison.Ordinal)) + { + return true; + } + if (source.Contains("AetheriaUnityRuntimeClientProvider", StringComparison.Ordinal) && (source.Contains("CurrentDockingState(\"", StringComparison.Ordinal) || source.Contains("CurrentStationRefit(\"", StringComparison.Ordinal) || @@ -7543,7 +7819,8 @@ static void RequireUnitySharedDocumentAccessorErgonomics(string root) { "public abstract class ActionBarBinding", "AetheriaUnityRuntimeClientProvider", - ".CurrentCatalog()", + "AetheriaUnityRuntimeClientProvider.RuntimeState(\"unity-action-bar\")", + "s_runtimeState.Catalog.Latest()?.FindItem(item, x => x.ItemKey)", ".FindItem(item, x => x.ItemKey)", "private void OnDestroy()" }; @@ -7553,12 +7830,13 @@ static void RequireUnitySharedDocumentAccessorErgonomics(string root) if (missingActionBarSymbols.Length > 0) { throw new InvalidOperationException( - "ActionBarSlot should resolve runtime catalog items through a managed reactive catalog document with binding lifetime disposal: " + + "ActionBarSlot should resolve runtime catalog items through a managed typed catalog handle: " + string.Join(", ", missingActionBarSymbols)); } if (actionBarSlot.Contains("AetheriaRuntimeCatalogSession _catalog", StringComparison.Ordinal) || actionBarSlot.Contains("CultMeshReactiveDocument _catalog", StringComparison.Ordinal) || + actionBarSlot.Contains(".CurrentCatalog()", StringComparison.Ordinal) || actionBarSlot.Contains(".ReactiveCatalogSnapshot(\"unity-action-bar\")", StringComparison.Ordinal) || actionBarSlot.Contains("_catalog?.Current?.FindItem", StringComparison.Ordinal) || actionBarSlot.Contains("binding?.Dispose()", StringComparison.Ordinal) || @@ -7585,9 +7863,10 @@ static void RequireUnitySharedDocumentAccessorErgonomics(string root) "AetheriaRuntimePlayerHudState.cs"); var requiredSchematicDisplaySymbols = new[] { - "AetheriaUnityRuntimeClientProvider.CurrentCatalog(\"unity-schematic-display\")", - "AetheriaUnityRuntimeClientProvider.CurrentPlayerSettings(\"unity-schematic-display\")", - ".CurrentEntityState(\"unity-schematic-display\")", + "AetheriaUnityRuntimeClientProvider.RuntimeState(\"unity-schematic-display\")", + "RuntimeState.Catalog.Latest()", + "RuntimeState.PlayerSettings.Latest()", + "RuntimeState.CurrentEntity.Latest()?.Hud", "CatalogSnapshot()?.FindItem(item, x => x.ItemKey)", "private AetheriaRuntimeCatalogSnapshot CatalogSnapshot()", "private AetheriaRuntimePlayerSettingsDocument PlayerSettingsSnapshot()", @@ -7655,7 +7934,8 @@ static void RequireUnitySharedDocumentAccessorErgonomics(string root) var requiredVolumeCloudSymbols = new[] { "AetheriaUnityRuntimeClientProvider", - ".CurrentPlayerSettings(\"unity-volume-cloud-renderer\")" + "AetheriaUnityRuntimeClientProvider.RuntimeState(\"unity-volume-cloud-renderer\")", + "_runtimeState.PlayerSettings.Latest()" }; var missingVolumeCloudSymbols = requiredVolumeCloudSymbols .Where(symbol => !volumeCloudRenderer.Contains(symbol, StringComparison.Ordinal)) @@ -7663,11 +7943,12 @@ static void RequireUnitySharedDocumentAccessorErgonomics(string root) if (missingVolumeCloudSymbols.Length > 0) { throw new InvalidOperationException( - "VolumeCloudRenderer should sample player settings through named current typed state: " + + "VolumeCloudRenderer should sample player settings through its managed typed state handle: " + string.Join(", ", missingVolumeCloudSymbols)); } if (volumeCloudRenderer.Contains("CultMeshReactiveDocument", StringComparison.Ordinal) || + volumeCloudRenderer.Contains(".CurrentPlayerSettings(\"unity-volume-cloud-renderer\")", StringComparison.Ordinal) || volumeCloudRenderer.Contains(".ReactivePlayerSettingsDocument(\"unity-volume-cloud-renderer\")", StringComparison.Ordinal) || volumeCloudRenderer.Contains("_playerSettings?.Dispose()", StringComparison.Ordinal) || volumeCloudRenderer.Contains("_playerSettings?.Current", StringComparison.Ordinal) || @@ -7743,7 +8024,8 @@ static void RequireUnitySharedDocumentAccessorErgonomics(string root) .Concat(new[] { ".MapViewportDocuments(viewport,\"unity-map-renderer\")", - "AetheriaUnityRuntimeClientProvider.CurrentPlayerSettings(\"unity-map-renderer\")" + "AetheriaUnityRuntimeClientProvider.RuntimeState(\"unity-map-renderer\")", + "_runtimeState.PlayerSettings.Latest()" } .Select(CompactSource) .Where(symbol => !compactMapRenderer.Contains(symbol, StringComparison.Ordinal))) @@ -7764,6 +8046,7 @@ static void RequireUnitySharedDocumentAccessorErgonomics(string root) "AetheriaRuntimeObjectsViewportSession _objectsViewport", "AetheriaRuntimeRenderSplatsViewportSession _renderSplatsViewport", "CultMeshReactiveDocument", + "AetheriaUnityRuntimeClientProvider.CurrentPlayerSettings(\"unity-map-renderer\")", ".ReactivePlayerSettingsDocument(\"unity-map-renderer\")", "AetheriaRuntimePlayerSettingsSession", ".ObserveObjects(viewport)", @@ -7790,9 +8073,11 @@ static void RequireUnitySharedDocumentAccessorErgonomics(string root) var requiredMainMenuSharedDocumentSymbols = new[] { "AetheriaUnityRuntimeClientProvider", - ".CurrentSectorMap(stateBoot, \"unity-main-menu\")", - ".CurrentPlayerSettings(stateBoot, \"unity-main-menu\")", - ".CurrentVerseHostSettings(stateBoot, \"unity-main-menu\")", + ".RuntimeState(stateBoot, \"unity-main-menu\")", + ".SectorMap", + ".PlayerSettings", + ".VerseHostSettings", + ".Latest()", "private void OnDestroy()" }; var missingMainMenuSharedDocumentSymbols = requiredMainMenuSharedDocumentSymbols @@ -7801,7 +8086,7 @@ static void RequireUnitySharedDocumentAccessorErgonomics(string root) if (missingMainMenuSharedDocumentSymbols.Length > 0) { throw new InvalidOperationException( - "MainMenu should sample sector, player, and Verse host state through named current typed Aetheria documents: " + + "MainMenu should sample sector, player, and Verse host state through a managed typed Aetheria runtime state handle: " + string.Join(", ", missingMainMenuSharedDocumentSymbols)); } @@ -7840,8 +8125,11 @@ static void RequireUnitySharedDocumentAccessorErgonomics(string root) var compactTradeMenu = CompactSource(tradeMenu); var requiredTradeMenuSharedDocumentSymbols = new[] { - "AetheriaUnityRuntimeClientProvider.CurrentCatalog(\"unity-trade\")", - "AetheriaUnityRuntimeClientProvider.CurrentPlayerSettings(\"unity-trade\")", + "AetheriaUnityRuntimeClientProvider.RuntimeState(\"unity-trade\")", + "RuntimeState.Catalog.Latest()", + "RuntimeState.PlayerSettings.Latest()", + "RuntimeState.StationRefit.Latest()", + "RuntimeState.CurrentDocking.Latest()", "private void OnDestroy()" }; var missingTradeMenuSharedDocumentSymbols = requiredTradeMenuSharedDocumentSymbols @@ -7851,13 +8139,17 @@ static void RequireUnitySharedDocumentAccessorErgonomics(string root) if (missingTradeMenuSharedDocumentSymbols.Length > 0) { throw new InvalidOperationException( - "TradeMenu should sample shared catalog/settings through named current typed Aetheria documents: " + + "TradeMenu should sample shared documents through its managed runtime state handle: " + string.Join(", ", missingTradeMenuSharedDocumentSymbols)); } var forbiddenTradeMenuSharedDocumentSymbols = new[] { "CultMeshReactiveDocument<", + "AetheriaUnityRuntimeClientProvider.CurrentCatalog(\"unity-trade\")", + "AetheriaUnityRuntimeClientProvider.CurrentPlayerSettings(\"unity-trade\")", + "AetheriaUnityRuntimeClientProvider.CurrentStationRefit(\"unity-trade\")", + "AetheriaUnityRuntimeClientProvider.CurrentDockingState(\"unity-trade\")", ".ReactiveCatalogSnapshot(\"unity-trade\")", ".ReactivePlayerSettingsDocument(\"unity-trade\")", "AetheriaRuntimeCatalogSession", @@ -7886,8 +8178,11 @@ static void RequireUnitySharedDocumentAccessorErgonomics(string root) var compactInventoryMenu = CompactSource(inventoryMenu); var requiredInventoryMenuSharedDocumentSymbols = new[] { - "AetheriaUnityRuntimeClientProvider.CurrentCatalog(\"unity-inventory-menu\")", - "AetheriaUnityRuntimeClientProvider.CurrentPlayerSettings(\"unity-inventory-menu\")", + "AetheriaUnityRuntimeClientProvider.RuntimeState(\"unity-inventory-menu\")", + "RuntimeState.Catalog.Latest()", + "RuntimeState.PlayerSettings.Latest()", + "RuntimeState.CurrentEntity.Latest()", + "RuntimeState.StationRefit.Latest()", "private void OnDestroy()" }; var missingInventoryMenuSharedDocumentSymbols = requiredInventoryMenuSharedDocumentSymbols @@ -7897,13 +8192,18 @@ static void RequireUnitySharedDocumentAccessorErgonomics(string root) if (missingInventoryMenuSharedDocumentSymbols.Length > 0) { throw new InvalidOperationException( - "InventoryMenu should sample shared catalog/settings through named current typed Aetheria documents: " + + "InventoryMenu should sample shared documents through its managed runtime state handle: " + string.Join(", ", missingInventoryMenuSharedDocumentSymbols)); } var forbiddenInventoryMenuSharedDocumentSymbols = new[] { "CultMeshReactiveDocument<", + "AetheriaUnityRuntimeClientProvider.CurrentCatalog(\"unity-inventory-menu\")", + "AetheriaUnityRuntimeClientProvider.CurrentPlayerSettings(\"unity-inventory-menu\")", + "AetheriaUnityRuntimeClientProvider.CurrentEntityState(\"unity-inventory-menu\")", + "AetheriaUnityRuntimeClientProvider.CurrentStationRefit(\"unity-inventory-menu\")", + "AetheriaUnityRuntimeClientProvider.CurrentInventory(entityIndex, \"unity-inventory-menu\")", ".ReactiveCatalogSnapshot(\"unity-inventory-menu\")", ".ReactivePlayerSettingsDocument(\"unity-inventory-menu\")", "AetheriaRuntimeCatalogSession", @@ -7932,8 +8232,14 @@ static void RequireUnitySharedDocumentAccessorErgonomics(string root) var compactInventoryPanel = CompactSource(inventoryPanel); var requiredInventoryPanelSharedDocumentSymbols = new[] { - "AetheriaUnityRuntimeClientProvider.CurrentCatalog(\"unity-inventory\")", - "AetheriaUnityRuntimeClientProvider.CurrentPlayerSettings(\"unity-inventory\")", + "AetheriaUnityRuntimeClientProvider.RuntimeState(\"unity-inventory\")", + "RuntimeState.CurrentEntity.Latest()", + "RuntimeState.StationRefit.Latest()", + "RuntimeState.DaemonFrame.Latest()", + "RuntimeState.InventoryPanelSurface(BuildInventoryPanelSurfaceRequest()).Latest()", + "RuntimeState.InventoryDropdownSurface(BuildDropdownSurfaceRequest()).Latest()", + "BuildInventoryPanelSurfaceRequest()", + "DragSession.TryGetDraggedItem(out var itemDragObject)", "private void OnDestroy()" }; var missingInventoryPanelSharedDocumentSymbols = requiredInventoryPanelSharedDocumentSymbols @@ -7943,13 +8249,21 @@ static void RequireUnitySharedDocumentAccessorErgonomics(string root) if (missingInventoryPanelSharedDocumentSymbols.Length > 0) { throw new InvalidOperationException( - "InventoryPanel should sample shared catalog/settings through named current typed Aetheria documents: " + + "InventoryPanel should sample shared documents through its managed runtime state handle: " + string.Join(", ", missingInventoryPanelSharedDocumentSymbols)); } var forbiddenInventoryPanelSharedDocumentSymbols = new[] { "CultMeshReactiveDocument<", + "AetheriaUnityRuntimeClientProvider.CurrentCatalog(\"unity-inventory\")", + "AetheriaUnityRuntimeClientProvider.CurrentPlayerSettings(\"unity-inventory\")", + "AetheriaUnityRuntimeClientProvider.CurrentEntityState(\"unity-inventory\")", + "AetheriaUnityRuntimeClientProvider.CurrentStationRefit(\"unity-inventory\")", + "AetheriaUnityRuntimeClientProvider.CurrentInventory(entityIndex, \"unity-inventory\")", + "AetheriaUnityRuntimeClientProvider.CurrentInventoryPanelSurface(", + "AetheriaUnityRuntimeClientProvider.CurrentInventoryDropdownSurface(", + "AetheriaUnityRuntimeClientProvider.CurrentDaemonFrame(\"unity-inventory\")", ".ReactiveCatalogSnapshot(\"unity-inventory\")", ".ReactivePlayerSettingsDocument(\"unity-inventory\")", "AetheriaRuntimeCatalogSession", @@ -7968,6 +8282,88 @@ static void RequireUnitySharedDocumentAccessorErgonomics(string root) string.Join(", ", forbiddenInventoryPanelSharedDocumentSymbols)); } + var forbiddenInventoryPanelUnityCellSymbols = new[] + { + "NodePrototype", + "NodeTextures", + "ThermalTextures", + "TemperatureDisplay", + "GearOccupancy", + "Occupancy[", + "CellInstances.Add", + "TryValidateTypedCargoSlot", + "TryValidateTypedEquipmentSlot", + "RuntimeState.Inventory(entityIndex).Latest()", + "origin.Cargo.TryGetValue(item, out var originPosition)", + "RequestDraggedItemToEntity", + "RequestDraggedItemToCargo", + "RegisterTarget(drag" + } + .Where(symbol => inventoryPanel.Contains(symbol, StringComparison.Ordinal)) + .ToArray(); + if (forbiddenInventoryPanelUnityCellSymbols.Length > 0) + { + throw new InvalidOperationException( + "InventoryPanel must not own inventory cell composition, drag/drop mutation, or thermal grid rendering; those belong to the daemon-presented CultUI surface: " + + string.Join(", ", forbiddenInventoryPanelUnityCellSymbols)); + } + + var inventoryPanelSurfaceBuilderPath = Path.Combine( + root, + "Packages", + "org.gamecult.aetheria.state", + "Runtime", + "AetheriaRuntimeInventoryPanelSurfaceBuilder.cs"); + var inventoryPanelSurfaceBuilder = File.Exists(inventoryPanelSurfaceBuilderPath) + ? File.ReadAllText(inventoryPanelSurfaceBuilderPath) + : throw new InvalidOperationException("Cannot verify shared inventory panel surface; AetheriaRuntimeInventoryPanelSurfaceBuilder.cs is missing."); + var requiredInventoryPanelSurfaceSymbols = new[] + { + "BuildFromDocuments(", + "AetheriaRuntimeInventoryDocument displayedInventory", + "inventory.drag_session", + "AddAssetProps(props, \"backgroundAtlas\"", + "AddAssetProps(props, \"foregroundAtlas\"", + "AddAssetProps(props, \"thermalLayer\"", + "AddAssetProps(props, \"icon\"" + } + .Where(symbol => !inventoryPanelSurfaceBuilder.Contains(symbol, StringComparison.Ordinal)) + .ToArray(); + if (requiredInventoryPanelSurfaceSymbols.Length > 0) + { + throw new InvalidOperationException( + "Inventory panel surface composition must live in the shared runtime package and expose drag/drop state plus renderer-independent asset refs: " + + string.Join(", ", requiredInventoryPanelSurfaceSymbols)); + } + + var runtimeAssetsPath = Path.Combine( + root, + "Packages", + "org.gamecult.aetheria.state", + "Runtime", + "AetheriaRuntimeAssets.cs"); + var runtimeAssets = File.Exists(runtimeAssetsPath) + ? File.ReadAllText(runtimeAssetsPath) + : throw new InvalidOperationException("Cannot verify runtime asset manifest; AetheriaRuntimeAssets.cs is missing."); + var requiredInventoryAssetSymbols = new[] + { + "InventoryUiAssets()", + "inventory.cell.background_atlas", + "inventory.cell.foreground_atlas", + "inventory.cell.thermal_layer_atlas", + "Sprites/Flat UI/Nodes/Nodes8BG", + "Sprites/Flat UI/Nodes/Nodes8", + "Sprites/Flat UI/pipes" + } + .Where(symbol => !runtimeAssets.Contains(symbol, StringComparison.Ordinal)) + .ToArray(); + if (requiredInventoryAssetSymbols.Length > 0) + { + throw new InvalidOperationException( + "Runtime asset manifest must publish inventory UI spritesheets and palettes for CultUI inventory rendering: " + + string.Join(", ", requiredInventoryAssetSymbols)); + } + var sectorRenderer = File.ReadAllText(Path.Combine( root, "Assets", @@ -7978,11 +8374,9 @@ static void RequireUnitySharedDocumentAccessorErgonomics(string root) var compactSectorRenderer = CompactSource(sectorRenderer); var requiredSectorRendererSharedDocumentSymbols = new[] { - "AetheriaUnityRuntimeClientProvider.CurrentSectorMap(\"unity-sector-renderer\")", - "AetheriaUnityRuntimeClientProvider.CurrentZoneState(\"unity-sector-renderer\")", - "AetheriaUnityRuntimeClientProvider.CurrentZoneDetails(zoneIndex, \"unity-sector-renderer\")", - "AetheriaUnityRuntimeClientProvider.CurrentCatalog(\"unity-sector-renderer\")", - "AetheriaUnityRuntimeClientProvider.CurrentPlayerSettings(\"unity-sector-renderer\")" + "AetheriaUnityRuntimeClientProvider.RuntimeState(\"unity-sector-renderer\")", + "RuntimeState.CurrentZone.Latest()", + "RuntimeState.ZoneDetailsSurface(zoneIndex).Latest()" } .Select(CompactSource) .Where(symbol => !compactSectorRenderer.Contains(symbol, StringComparison.Ordinal)) @@ -8036,7 +8430,8 @@ static void RequireUnitySharedDocumentAccessorErgonomics(string root) "ZoneRenderer.cs")); var requiredZoneRendererSharedDocumentSymbols = new[] { - ".CurrentCatalog(\"unity-zone-renderer\")", + "AetheriaUnityRuntimeClientProvider.RuntimeState(\"unity-zone-renderer\")", + "_runtimeState.Catalog.Latest()", "AetheriaUnityRtsViewportDocuments _zonePresentationDocuments", ".ZonePresentationDocuments(", "_zonePresentationDocuments?.Dispose()", @@ -8059,10 +8454,10 @@ static void RequireUnitySharedDocumentAccessorErgonomics(string root) ".ReactiveZoneContacts(\"unity-zone-renderer\")", "_zoneContacts?.Current", "_zoneContacts?.Dispose()", - ".RuntimeState(\"unity-zone-renderer\")", "AetheriaRuntimeZoneContactsSession _zoneContacts", "ResolveClient().State.ObserveZoneContacts()", "CultMeshReactiveDocument", + "AetheriaUnityRuntimeClientProvider.CurrentCatalog(\"unity-zone-renderer\")", ".ReactiveCatalogSnapshot(\"unity-zone-renderer\")", "AetheriaRuntimeCatalogSession _catalog", "ResolveClient().State.ObserveCatalog()", @@ -8096,11 +8491,8 @@ static void RequireUnityViewportAndMapReadsUseManagedAccessors(string root) var requiredClientSymbols = new[] { "public CultMeshDocumentHandle ObjectsViewport(", - "public CultMeshReactiveDocument ReactiveObjectsViewport(", "public CultMeshDocumentHandle RenderSplatsViewport(", - "public CultMeshReactiveDocument ReactiveRenderSplatsViewport(", - "public CultMeshDocumentHandle ZoneDetails(", - "public CultMeshReactiveDocument ReactiveZoneDetails(" + "public CultMeshDocumentHandle ZoneDetails(" }; var missingClientSymbols = requiredClientSymbols .Where(symbol => !clientState.Contains(symbol, StringComparison.Ordinal)) @@ -8112,6 +8504,22 @@ static void RequireUnityViewportAndMapReadsUseManagedAccessors(string root) string.Join(", ", missingClientSymbols)); } + var forbiddenClientSymbols = new[] + { + "public CultMeshReactiveDocument ReactiveObjectsViewport(", + "public CultMeshReactiveDocument ReactiveRenderSplatsViewport(", + "public CultMeshReactiveDocument ReactiveZoneDetails(" + }; + var survivingClientSymbols = forbiddenClientSymbols + .Where(symbol => clientState.Contains(symbol, StringComparison.Ordinal)) + .ToArray(); + if (survivingClientSymbols.Length > 0) + { + throw new InvalidOperationException( + "AetheriaClientState must expose parameterized map/viewport/indexed state as handles; callers use Handle.Reactive() when needed: " + + string.Join(", ", survivingClientSymbols)); + } + var unityPaths = new[] { Path.Combine(root, "Assets", "Scripts", "Gameplay", "AetheriaUnityTargetPresentation.cs"), @@ -8294,88 +8702,38 @@ static void RequireAetheriaManagedStateAccessorsCoverDomainDocuments(string root { "public enum AetheriaClientEveSurface", "public CultMeshDocumentHandle ProviderAdvertisement { get; }", - "public CultMeshReactiveDocument ReactiveProviderAdvertisement()", "public CultMeshDocumentHandle Health { get; }", - "public CultMeshReactiveDocument ReactiveHealth()", "public CultMeshDocumentHandle CommandBoundary { get; }", - "public CultMeshReactiveDocument ReactiveCommandBoundary()", "public CultMeshDocumentHandle AuthorityPolicy { get; }", - "public CultMeshReactiveDocument ReactiveAuthorityPolicy()", "public CultMeshDocumentHandle DaemonFrame { get; }", - "public AetheriaRuntimeDaemonFrameDocument CurrentDaemonFrame()", - "public CultMeshReactiveDocument ReactiveDaemonFrame()", "public CultMeshDocumentHandle DaemonSoaView { get; }", - "public AetheriaRuntimeDaemonSoaViewDocument CurrentDaemonSoaView()", - "public CultMeshReactiveDocument ReactiveDaemonSoaView()", "public CultMeshDocumentHandle ZoneRender { get; }", - "public AetheriaRuntimeZoneRenderDocument CurrentZoneRender()", - "public CultMeshReactiveDocument ReactiveZoneRender()", "public CultMeshDocumentHandle LoadoutTemplates { get; }", - "public AetheriaRuntimeLoadoutTemplatesDocument CurrentLoadoutTemplates()", - "public CultMeshReactiveDocument ReactiveLoadoutTemplates()", "public CultMeshDocumentHandle SectorMap { get; }", - "public AetheriaRuntimeSectorMapDocument CurrentSectorMap()", - "public CultMeshReactiveDocument ReactiveSectorMap()", "public CultMeshDocumentHandle CurrentZone { get; }", - "public AetheriaRuntimeCurrentZoneDocument CurrentZoneState()", - "public CultMeshReactiveDocument ReactiveCurrentZone()", "public CultMeshDocumentHandle ZoneContacts { get; }", - "public AetheriaRuntimeZoneContactsDocument CurrentZoneContacts()", - "public CultMeshReactiveDocument ReactiveZoneContacts()", "public CultMeshDocumentHandle VerseHostSettings { get; }", - "public AetheriaRuntimeVerseHostSettingsDocument CurrentVerseHostSettings()", - "public CultMeshReactiveDocument ReactiveVerseHostSettings()", "public CultMeshDocumentHandle CurrentDocking { get; }", - "public AetheriaRuntimeCurrentDockingDocument CurrentDockingState()", - "public CultMeshReactiveDocument ReactiveCurrentDocking()", "public CultMeshDocumentHandle CurrentEntity { get; }", - "public AetheriaRuntimeCurrentEntityDocument CurrentEntityState()", - "public CultMeshReactiveDocument ReactiveCurrentEntity()", "public CultMeshDocumentHandle StationRefit { get; }", - "public AetheriaRuntimeStationRefitDocument CurrentStationRefit()", - "public CultMeshReactiveDocument ReactiveStationRefit()", "public CultMeshDocumentHandle Catalog { get; }", - "public AetheriaRuntimeCatalogSnapshot CurrentCatalog()", - "public CultMeshReactiveDocument ReactiveCatalogSnapshot()", "public CultMeshDocumentHandle PlayerSettings { get; }", - "public AetheriaRuntimePlayerSettingsDocument CurrentPlayerSettings()", - "public CultMeshReactiveDocument ReactivePlayerSettingsDocument()", "public static bool TryResolveEveSurface(", "public CultMeshDocumentHandle? EveSurfaceDocument(", - "public CultMeshReactiveDocument? ReactiveEveSurface(", "public CultMeshDocumentHandle RtsViewport(", - "public AetheriaRuntimeRtsViewportDocument CurrentRtsViewport(", - "public CultMeshReactiveDocument ReactiveRtsViewport(", "public CultMeshDocumentHandle ObjectsViewport(", - "public AetheriaRuntimeObjectsViewportDocument CurrentObjectsViewport(", - "public CultMeshReactiveDocument ReactiveObjectsViewport(", "public CultMeshDocumentHandle GravityViewport(", - "public AetheriaRuntimeGravityViewportDocument CurrentGravityViewport(", - "public CultMeshReactiveDocument ReactiveGravityViewport(", "public CultMeshDocumentHandle RenderSplatsViewport(", - "public AetheriaRuntimeRenderSplatsViewportDocument CurrentRenderSplatsViewport(", - "public CultMeshReactiveDocument ReactiveRenderSplatsViewport(", "public CultMeshDocumentHandle ZoneDetails(", - "public AetheriaRuntimeZoneDetailsDocument CurrentZoneDetails(", - "public CultMeshReactiveDocument ReactiveZoneDetails(", + "public CultMeshDocumentHandle ZoneDetailsSurface(", + "public CultMeshDocumentHandle InventoryPanelSurface(", + "public CultMeshDocumentHandle InventoryDropdownSurface(", "public CultMeshDocumentHandle SelectedObject(", - "public AetheriaRuntimeSelectedObjectDocument CurrentSelectedObject(", - "public CultMeshReactiveDocument ReactiveSelectedObject(", "public CultMeshDocumentHandle Inventory(", - "public AetheriaRuntimeInventoryDocument CurrentInventory(", - "public CultMeshReactiveDocument ReactiveInventory(", "public CultMeshDocumentHandle StarbridgeScenario { get; }", - "public AetheriaRuntimeStarbridgeScenarioDocument CurrentStarbridgeScenario()", - "public CultMeshReactiveDocument ReactiveStarbridgeScenario()", "public CultMeshDocumentHandle StarbridgeSession { get; }", - "public AetheriaRuntimeStarbridgeSessionDocument CurrentStarbridgeSession()", - "public CultMeshReactiveDocument ReactiveStarbridgeSession()", "public CultMeshDocumentHandle StarbridgeSummary { get; }", - "public AetheriaRuntimeStarbridgeSessionSummaryDocument CurrentStarbridgeSummary()", - "public CultMeshReactiveDocument ReactiveStarbridgeSummary()", - "public CultMeshDocumentHandle StarbridgePlayerSeat(", - "public AetheriaRuntimeStarbridgePlayerSeatDocument CurrentStarbridgePlayerSeat(", - "public CultMeshReactiveDocument ReactiveStarbridgePlayerSeat(" + "public CultMeshDocumentHandle StarbridgePlayerSeat(" }; var missingClientSymbols = requiredClientSymbols .Where(symbol => !clientState.Contains(symbol, StringComparison.Ordinal)) @@ -8387,6 +8745,51 @@ static void RequireAetheriaManagedStateAccessorsCoverDomainDocuments(string root string.Join(", ", missingClientSymbols)); } + var forbiddenFixedReactiveSymbols = new[] + { + "public CultMeshReactiveDocument ReactiveProviderAdvertisement()", + "public CultMeshReactiveDocument ReactiveHealth()", + "public CultMeshReactiveDocument ReactiveCommandBoundary()", + "public CultMeshReactiveDocument ReactiveAuthorityPolicy()", + "public CultMeshReactiveDocument ReactiveDaemonFrame()", + "public CultMeshReactiveDocument ReactiveDaemonSoaView()", + "public CultMeshReactiveDocument ReactiveZoneRender()", + "public CultMeshReactiveDocument ReactiveLoadoutTemplates()", + "public CultMeshReactiveDocument ReactiveSectorMap()", + "public CultMeshReactiveDocument ReactiveCurrentZone()", + "public CultMeshReactiveDocument ReactiveZoneContacts()", + "public CultMeshReactiveDocument ReactiveVerseHostSettings()", + "public CultMeshReactiveDocument ReactiveCurrentDocking()", + "public CultMeshReactiveDocument ReactiveCurrentEntity()", + "public CultMeshReactiveDocument ReactiveStationRefit()", + "public CultMeshReactiveDocument ReactiveCatalogSnapshot()", + "public CultMeshReactiveDocument ReactivePlayerSettingsDocument()", + "public CultMeshReactiveDocument ReactiveStarbridgeScenario()", + "public CultMeshReactiveDocument ReactiveStarbridgeSession()", + "public CultMeshReactiveDocument ReactiveStarbridgeSummary()", + "public CultMeshReactiveDocument? ReactiveEveSurface(", + "public CultMeshReactiveDocument ReactiveRtsViewport(", + "public CultMeshReactiveDocument ReactiveObjectsViewport(", + "public CultMeshReactiveDocument ReactiveGravityViewport(", + "public CultMeshReactiveDocument ReactiveRenderSplatsViewport(", + "public CultMeshReactiveDocument ReactiveZoneDetails(", + "public CultMeshReactiveDocument ReactiveZoneDetailsSurface(", + "public CultMeshReactiveDocument ReactiveInventoryPanelSurface(", + "public CultMeshReactiveDocument ReactiveInventoryDropdownSurface(", + "public CultMeshReactiveDocument ReactiveSelectedObject(", + "public CultMeshReactiveDocument ReactiveInventory(", + "public CultMeshReactiveDocument ReactiveStarbridgePlayerSeat(" + }; + var survivingFixedReactiveSymbols = forbiddenFixedReactiveSymbols + .Where(symbol => clientState.Contains(symbol, StringComparison.Ordinal)) + .ToArray(); + if (survivingFixedReactiveSymbols.Length > 0) + { + throw new InvalidOperationException( + "AetheriaClientState must expose fixed documents as handles; callers use Handle.Reactive() instead of wrapper methods: " + + string.Join(", ", survivingFixedReactiveSymbols)); + } + if (clientState.Contains("public Task LatestAsync", StringComparison.Ordinal) || clientState.Contains("public TDocument Latest", StringComparison.Ordinal) || clientState.Contains("public TDocument Current", StringComparison.Ordinal) || @@ -8413,6 +8816,45 @@ static void RequireAetheriaManagedStateAccessorsCoverDomainDocuments(string root "AetheriaClientState must not reintroduce generic document catalogs, latest/watch shortcuts, or fixed FooDocument() wrappers; callers should use named typed document handles directly."); } + var forbiddenCurrentStateAccessors = new[] + { + "public AetheriaRuntimeDaemonFrameDocument CurrentDaemonFrame()", + "public AetheriaRuntimeDaemonSoaViewDocument CurrentDaemonSoaView()", + "public AetheriaRuntimeCatalogSnapshot CurrentCatalog()", + "public AetheriaRuntimeLoadoutTemplatesDocument CurrentLoadoutTemplates()", + "public AetheriaRuntimePlayerSettingsDocument CurrentPlayerSettings()", + "public AetheriaRuntimeVerseHostSettingsDocument CurrentVerseHostSettings()", + "public AetheriaRuntimeCurrentZoneDocument CurrentZoneState()", + "public AetheriaRuntimeCurrentEntityDocument CurrentEntityState()", + "public AetheriaRuntimeCurrentDockingDocument CurrentDockingState()", + "public AetheriaRuntimeZoneContactsDocument CurrentZoneContacts()", + "public AetheriaRuntimeStationRefitDocument CurrentStationRefit()", + "public AetheriaRuntimeSectorMapDocument CurrentSectorMap()", + "public AetheriaRuntimeZoneRenderDocument CurrentZoneRender()", + "public AetheriaRuntimeRtsViewportDocument CurrentRtsViewport(", + "public AetheriaRuntimeObjectsViewportDocument CurrentObjectsViewport(", + "public AetheriaRuntimeGravityViewportDocument CurrentGravityViewport(", + "public AetheriaRuntimeRenderSplatsViewportDocument CurrentRenderSplatsViewport(", + "public AetheriaRuntimeZoneDetailsDocument CurrentZoneDetails(", + "public AetheriaRuntimeSurfaceDocument CurrentZoneDetailsSurface(", + "public AetheriaRuntimeSurfaceDocument CurrentInventoryPanelSurface(", + "public AetheriaRuntimeSurfaceDocument CurrentInventoryDropdownSurface(", + "public AetheriaRuntimeSelectedObjectDocument CurrentSelectedObject(", + "public AetheriaRuntimeInventoryDocument CurrentInventory(", + "public AetheriaRuntimeStarbridgeScenarioDocument CurrentStarbridgeScenario()", + "public AetheriaRuntimeStarbridgeSessionDocument CurrentStarbridgeSession()", + "public AetheriaRuntimeStarbridgeSessionSummaryDocument CurrentStarbridgeSummary()", + "public AetheriaRuntimeStarbridgePlayerSeatDocument CurrentStarbridgePlayerSeat(" + } + .Where(symbol => clientState.Contains(symbol, StringComparison.Ordinal)) + .ToArray(); + if (forbiddenCurrentStateAccessors.Length > 0) + { + throw new InvalidOperationException( + "AetheriaClientState must expose document handles; CurrentX snapshot helpers hide the managed typed document access model: " + + string.Join(", ", forbiddenCurrentStateAccessors)); + } + var forbiddenFixedReactiveWrappers = new[] { "LatestCatalog(", @@ -8616,8 +9058,7 @@ static void RequireAetheriaManagedStateAccessorsCoverDomainDocuments(string root var compactClientState = CompactSource(clientState); var requiredStarbridgeParameterizedSymbols = new[] { - "publicCultMeshDocumentHandleStarbridgePlayerSeat(stringseatId)", - "publicCultMeshReactiveDocumentReactiveStarbridgePlayerSeat(" + "publicCultMeshDocumentHandleStarbridgePlayerSeat(stringseatId)" }; var missingStarbridgeParameterizedSymbols = requiredStarbridgeParameterizedSymbols .Where(symbol => !compactClientState.Contains(symbol, StringComparison.Ordinal)) @@ -8625,7 +9066,7 @@ static void RequireAetheriaManagedStateAccessorsCoverDomainDocuments(string root if (missingStarbridgeParameterizedSymbols.Length > 0) { throw new InvalidOperationException( - "AetheriaClientState must expose named parameterized document access for player-seat state: " + + "AetheriaClientState must expose named parameterized document handles for player-seat state: " + string.Join(", ", missingStarbridgeParameterizedSymbols)); } @@ -8664,10 +9105,13 @@ static void RequireAetheriaManagedStateAccessorsCoverDomainDocuments(string root "AetheriaRuntimeObservedDockingState must not return as a docking aggregate wrapper; callers should own the managed typed docking documents directly."); } - if (!daemonRuntimeDocumentTests.Contains("using var currentEntityDocumentReactive = client.State.ReactiveCurrentEntity()", StringComparison.Ordinal) || - !daemonRuntimeDocumentTests.Contains("using var currentDockingReactive = client.State.ReactiveCurrentDocking()", StringComparison.Ordinal) || - !daemonRuntimeDocumentTests.Contains("using var stationRefitReactive = client.State.ReactiveStationRefit()", StringComparison.Ordinal) || + if (!daemonRuntimeDocumentTests.Contains("using var currentEntityDocumentReactive = client.State.CurrentEntity.Reactive()", StringComparison.Ordinal) || + !daemonRuntimeDocumentTests.Contains("using var currentDockingReactive = client.State.CurrentDocking.Reactive()", StringComparison.Ordinal) || + !daemonRuntimeDocumentTests.Contains("using var stationRefitReactive = client.State.StationRefit.Reactive()", StringComparison.Ordinal) || !daemonRuntimeDocumentTests.Contains("currentDockingReactive.Current.CurrentEntityKey", StringComparison.Ordinal) || + daemonRuntimeDocumentTests.Contains("client.State.ReactiveCurrentEntity()", StringComparison.Ordinal) || + daemonRuntimeDocumentTests.Contains("client.State.ReactiveCurrentDocking()", StringComparison.Ordinal) || + daemonRuntimeDocumentTests.Contains("client.State.ReactiveStationRefit()", StringComparison.Ordinal) || daemonRuntimeDocumentTests.Contains("AetheriaRuntimeObservedDockingState", StringComparison.Ordinal)) { throw new InvalidOperationException( @@ -8768,10 +9212,13 @@ static void RequireAetheriaManagedStateAccessorsCoverDomainDocuments(string root } if (!checkedSources["StarbridgePlayerSeatDocumentTests.cs"].Contains( - ".ReactiveStarbridgePlayerSeat(seat.SeatId)", + ".StarbridgePlayerSeat(seat.SeatId)", StringComparison.Ordinal) || !checkedSources["StarbridgePlayerSeatDocumentTests.cs"].Contains( - ".StarbridgePlayerSeat(seat.SeatId)", + ".StarbridgePlayerSeat(seat.SeatId).Reactive()", + StringComparison.Ordinal) || + checkedSources["StarbridgePlayerSeatDocumentTests.cs"].Contains( + ".ReactiveStarbridgePlayerSeat(seat.SeatId)", StringComparison.Ordinal) || checkedSources["StarbridgePlayerSeatDocumentTests.cs"].Contains( ".Latest(seat.SeatId)", @@ -9215,7 +9662,7 @@ static void RequireClientTargetBootAuthority(string root) clientTargetCommands.Contains("public enum AetheriaClientTargetOperationKind", StringComparison.Ordinal)) { throw new InvalidOperationException( - "Aetheria client-target sugar still exposes generic operation-bag dispatch instead of native request methods plus a surface adapter."); + "Aetheria client-target sugar still exposes generic operation-bag dispatch instead of native request methods plus a surface document API."); } var forbiddenClientTargetSetters = new[] @@ -9337,8 +9784,9 @@ static void RequireClientTargetBootAuthority(string root) "Aetheria runtime state file: {stateBoot.StateFilePath}", "!stateBoot.SupportsLocalStateFileRead", "stateBoot.StateFileExists", - "AetheriaUnityRuntimeClientProvider.CurrentCatalog(stateBoot, stateBoot.RuntimeId)", - "AetheriaUnityRuntimeClientProvider.CurrentSectorMap(stateBoot, stateBoot.RuntimeId)", + "AetheriaUnityRuntimeClientProvider.RuntimeState(stateBoot, stateBoot.RuntimeId)", + "runtimeState.Catalog.Latest()", + "runtimeState.SectorMap.Latest()", "new ItemManager(", "new AetheriaUnityLoadoutItemFactory(itemManager, runtimeCatalog)", "ZoneRenderer.SetDroppedPickupItemFactory(loadoutItemFactory.CreateLoadoutItem)", @@ -9364,7 +9812,8 @@ static void RequireClientTargetBootAuthority(string root) gameplayBootShell.Contains("runtimeCatalogDocument.Current", StringComparison.Ordinal) || gameplayBootShell.Contains(".ReactiveSectorMap()", StringComparison.Ordinal) || gameplayBootShell.Contains("sectorMapDocument.Current", StringComparison.Ordinal) || - gameplayBootShell.Contains("AetheriaUnityRuntimeClientProvider.RuntimeState(stateBoot, stateBoot.RuntimeId)", StringComparison.Ordinal) || + gameplayBootShell.Contains("AetheriaUnityRuntimeClientProvider.CurrentCatalog(stateBoot, stateBoot.RuntimeId)", StringComparison.Ordinal) || + gameplayBootShell.Contains("AetheriaUnityRuntimeClientProvider.CurrentSectorMap(stateBoot, stateBoot.RuntimeId)", StringComparison.Ordinal) || gameplayBootShell.Contains("AetheriaUnityRuntimeClientProvider.ResolveClient(stateBoot.StateFilePath, stateBoot.RuntimeId)", StringComparison.Ordinal) || gameplayBootShell.Contains(".State;")) { @@ -9401,15 +9850,17 @@ static void RequireClientTargetBootAuthority(string root) "CurrentStateBoot()", "ResolveSectorMap(AetheriaRuntimeStateBootReport stateBoot)", "AetheriaUnityRuntimeClientProvider", - ".CurrentSectorMap(stateBoot, \"unity-main-menu\")", + ".RuntimeState(stateBoot, \"unity-main-menu\")", + ".SectorMap", + ".Latest()", "ResolveVerseHostSettings(AetheriaRuntimeStateBootReport stateBoot)", - ".CurrentVerseHostSettings(stateBoot, \"unity-main-menu\")", + ".VerseHostSettings", "AetheriaState.At(AetheriaUnityRuntimePaths.GameDataDirectory)", ".ClientTarget", "RequestClientTargetCommand(request)", "AetheriaRuntimeClientTargetSurfaceCommands.TryRequest(", "ResolvePlayerSettings(AetheriaRuntimeStateBootReport stateBoot)", - ".CurrentPlayerSettings(stateBoot, \"unity-main-menu\")", + ".PlayerSettings", "AetheriaRuntimeClientTargetSurfaceBuilder.Build(", "AetheriaRuntimeMainMenuSurfaceBuilder.BuildRoot(", "AetheriaRuntimeMainMenuSurfaceBuilder.BuildSettings(", @@ -9815,7 +10266,7 @@ static void RequireVerseSettingsShellAndBridge(string root) clientTargetCommands.Contains("public enum AetheriaClientTargetOperationKind", StringComparison.Ordinal)) { throw new InvalidOperationException( - "Verse settings sugar still exposes generic operation-bag dispatch instead of native request methods plus a surface adapter."); + "Verse settings sugar still exposes generic operation-bag dispatch instead of native request methods plus a surface document API."); } var forbiddenClientTargetSetters = new[] @@ -10756,7 +11207,7 @@ static void RequireDaemonVersePublication(string root) var daemonHostProgramPath = Path.Combine(root, "Aetheria.State.Daemon", "Program.cs"); var documentRegistryPath = Path.Combine(root, "Aetheria.State", "AetheriaDocumentRegistry.cs"); var stateNodePath = Path.Combine(root, "Aetheria.State", "AetheriaStateNode.cs"); - var runtimeEveSurfaceAdapterPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeEveSurfaceAdapter.cs"); + var runtimeSurfaceDocumentsPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeSurfaceDocuments.cs"); var providerAdvertisementPath = Path.Combine(root, "Aetheria.State", "AetheriaEveSurfaceDocuments.cs"); var documentStorePath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeCultCacheDocumentStore.cs"); var boundaryPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeStateBoundary.cs"); @@ -10782,7 +11233,7 @@ static void RequireDaemonVersePublication(string root) daemonHostProgramPath, documentRegistryPath, stateNodePath, - runtimeEveSurfaceAdapterPath, + runtimeSurfaceDocumentsPath, providerAdvertisementPath, documentStorePath, boundaryPath, @@ -10817,7 +11268,7 @@ static void RequireDaemonVersePublication(string root) var daemonHostProgram = File.ReadAllText(daemonHostProgramPath); var documentRegistry = File.ReadAllText(documentRegistryPath); var stateNode = File.ReadAllText(stateNodePath); - var runtimeEveSurfaceAdapter = File.ReadAllText(runtimeEveSurfaceAdapterPath); + var runtimeSurfaceDocuments = File.ReadAllText(runtimeSurfaceDocumentsPath); var providerAdvertisement = File.ReadAllText(providerAdvertisementPath); var documentStore = File.ReadAllText(documentStorePath); var boundary = File.ReadAllText(boundaryPath); @@ -11094,14 +11545,14 @@ static void RequireDaemonVersePublication(string root) if (File.Exists(deletedDaemonSurfaceProjectorPath)) { throw new InvalidOperationException( - "Daemon Eve surface state lowering still lives in Aetheria.State projector chaff instead of the runtime Eve surface adapter."); + "Daemon Eve surface state lowering still lives in Aetheria.State projector chaff instead of the runtime surface documents."); } - if (!runtimeEveSurfaceAdapter.Contains("public static EveSurfaceState ToEveSurfaceState(AetheriaRuntimeSurfaceDocument document)", StringComparison.Ordinal) || - !runtimeEveSurfaceAdapter.Contains("EveCommandTemplate", StringComparison.Ordinal) || - !runtimeEveSurfaceAdapter.Contains("global::Aetheria.State.Documents.EveSurfaceComponent", StringComparison.Ordinal)) + if (!runtimeSurfaceDocuments.Contains("public static EveSurfaceState ToEveSurfaceState(AetheriaRuntimeSurfaceDocument document)", StringComparison.Ordinal) || + !runtimeSurfaceDocuments.Contains("EveCommandTemplate", StringComparison.Ordinal) || + !runtimeSurfaceDocuments.Contains("global::Aetheria.State.Documents.EveSurfaceComponent", StringComparison.Ordinal)) { - throw new InvalidOperationException("Runtime Eve surface adapter no longer lowers daemon surfaces into registered Eve surface state."); + throw new InvalidOperationException("Runtime surface documents no longer lowers daemon surfaces into registered Eve surface state."); } if (!daemonDocuments.Contains("AetheriaRuntimeDaemonCommandKinds", StringComparison.Ordinal)) @@ -11748,7 +12199,7 @@ static void RequireDaemonVersePublication(string root) "AetheriaClient UI submission did not appear as a typed Eve state record.", "node.MutableDocument(AetheriaRuntimeVerseRecordKeys.DaemonFrameLatest)", "node.MutableDocument(AetheriaRuntimeVerseRecordKeys.DaemonGameSurface)", - ".ReplaceAsync(AetheriaRuntimeEveSurfaceAdapter.ToEveSurfaceState(daemonGameSurface))", + ".ReplaceAsync(AetheriaRuntimeSurfaceDocuments.ToEveSurfaceState(daemonGameSurface))", "reopened.MutableDocument(AetheriaRuntimeVerseRecordKeys.DaemonProviderAdvertisement)", "reopened.MutableDocument(AetheriaRuntimeVerseRecordKeys.DaemonHealth)", "reopened.MutableDocument(AetheriaRuntimeVerseRecordKeys.DaemonCommandBoundary)", @@ -12110,11 +12561,12 @@ static void RequireAetheriaRuntimeVerseClientContract(string root) "AetheriaRuntimeLoadoutTemplatesDocument", "StarbridgeSummaryAsync", "CultMeshReactiveDocument? managedDaemonFrame = null;", - "managedDaemonFrame = state.ReactiveDaemonFrame();", - "managedCatalog = state.ReactiveCatalogSnapshot();", - "managedLoadoutTemplates = state.ReactiveLoadoutTemplates();", - "managedStarbridgeScenario = state.ReactiveStarbridgeScenario();", - "managedStarbridgeSession = state.ReactiveStarbridgeSession();", + "managedDaemonFrame = state.DaemonFrame.Reactive();", + "managedCatalog = state.Catalog.Reactive();", + "managedLoadoutTemplates = state.LoadoutTemplates.Reactive();", + "managedPlayerSettings = state.PlayerSettings.Reactive();", + "managedStarbridgeScenario = state.StarbridgeScenario.Reactive();", + "managedStarbridgeSession = state.StarbridgeSession.Reactive();", "AetheriaRuntimeDaemonFrameDocument RequireManagedFrame()", "RequireManagedFrame()", "RequireManagedCatalog()", @@ -12275,17 +12727,15 @@ static void RequireAetheriaRuntimeVerseClientContract(string root) "public CultMeshStateRefResolver CreateEveSurfaceCultMeshStateRefResolver()", "public sealed class AetheriaClientState : IDisposable", "public CultMeshDocumentHandle Inventory(", - "public CultMeshReactiveDocument ReactiveInventory(", "public CultMeshDocumentHandle StarbridgeSession", - "public CultMeshReactiveDocument ReactiveStarbridgeSession()", "CultMeshReactiveDocument? _eveStateRefFrame", "CultMeshReactiveDocument? _eveStateRefHealth", "CultMeshReactiveDocument? _eveStateRefCommandBoundary", "CultMeshReactiveDocument? _eveStateRefCatalog", - "_eveStateRefFrame ??= ReactiveDaemonFrame()", - "_eveStateRefHealth ??= ReactiveHealth()", - "_eveStateRefCommandBoundary ??= ReactiveCommandBoundary()", - "_eveStateRefCatalog ??= ReactiveCatalogSnapshot()", + "_eveStateRefFrame ??= DaemonFrame.Reactive()", + "_eveStateRefHealth ??= Health.Reactive()", + "_eveStateRefCommandBoundary ??= CommandBoundary.Reactive()", + "_eveStateRefCatalog ??= Catalog.Reactive()", "() => _eveStateRefFrame.Current", "() => _eveStateRefHealth.Current", "() => _eveStateRefCommandBoundary.Current", @@ -13070,7 +13520,9 @@ static void RequireMainMenuVerseHostDocumentAccess(string root) { "ResolveVerseHostSettings(AetheriaRuntimeStateBootReport stateBoot)", "AetheriaUnityRuntimeClientProvider", - ".CurrentVerseHostSettings(stateBoot, \"unity-main-menu\")", + ".RuntimeState(stateBoot, \"unity-main-menu\")", + ".VerseHostSettings", + ".Latest()", "AetheriaRuntimeClientTargetSurfaceBuilder.Build(", "AetheriaRuntimeMainMenuSurfaceBuilder.BuildRoot(" }; @@ -13254,7 +13706,9 @@ static void RequireMainMenuContinueRunState(string root) { "ResolveSectorMap", "AetheriaUnityRuntimeClientProvider", - ".CurrentSectorMap(stateBoot, \"unity-main-menu\")", + ".RuntimeState(stateBoot, \"unity-main-menu\")", + ".SectorMap", + ".Latest()", "ContinueGame()", "SceneManager.LoadScene(\"ARPG\")" }; @@ -13293,18 +13747,20 @@ static void RequireMainMenuContinueRunState(string root) "private AetheriaUnityObservedFrameApplier ObservedFrameApplier =>", "private Galaxy ObservedGalaxy { get; set; }", "ObservedGalaxy = boot.ObservedGalaxy", - "ResolveObservedGalaxyZone", + "ObservedZoneContextFactory.ResolveGalaxyZone", "private AetheriaUnityObservedZoneContextFactory ObservedZoneContextFactory =>", "entity => CurrentEntityBinder.RestoreBinding(entity)", "ResolveObservedTarget = entity => _targetPresentation.GetObservedTarget(entity)", "TargetPresentation = _targetPresentation", - "private readonly AetheriaUnityObservedEntityIndex _observedEntityIndex", + "private readonly AetheriaUnityPresentationEntityIndex _presentationEntityIndex", "private AetheriaUnityEntityBlueprintMaterializer EntityBlueprintMaterializer =>", "EntityBlueprintMaterializer.MaterializeObservedEntity", "_loadoutItemFactory.CreateLoadoutItem", "ResolveDockParent = ResolveDockParentFromCurrentDocking", "CurrentDockingSnapshot()", - "AetheriaUnityRuntimeClientProvider.CurrentDockingState(\"unity-action-game-manager\")", + "private AetheriaClientState _gameplayState;", + "AetheriaUnityRuntimeClientProvider.RuntimeState(\"unity-action-game-manager\")", + "GameplayState.CurrentDocking.Latest()", "IsCurrentEntityObservedUndocked(", "private AetheriaUnityCurrentEntityBinder CurrentEntityBinder =>", "private readonly AetheriaUnityCurrentEntityPresentation _currentEntityPresentation", @@ -13393,7 +13849,7 @@ static void RequireMainMenuContinueRunState(string root) "entity.RestoreThermalExposure((float)entitySnapshot.Heatstroke, (float)entitySnapshot.Hypothermia)", "RestoreActiveConsumables(entity, entitySnapshot)", "RestoreRuntimeBehaviorState(entity, entitySnapshot, restoredEntities)", - "_entityIndex.EntitiesByRecordKey", + "_entityIndex.PresentationEntitiesByRecordKey", "ResolveRuntimeBehavior(entity, weaponState.OwnerKind, weaponState.OwnerIndex, weaponState.BehaviorIndex)", "lockWeapon.RestoreRuntimeState(", "drive.RestoreRuntimeState(", @@ -13543,9 +13999,9 @@ static void RequireMainMenuContinueRunState(string root) "_zoneContextFactory.ResolveContext(targetZone, render)", "_entityRestorer.Replace(entitySnapshots, currentEntityKey, _getZone())", "_restoreCurrentEntityBinding(currentEntity)", - "_entityIndex.TryResolveEntityByRecordKey(currentEntityKey, out var currentEntity)", - "_entityIndex.EntitiesByDaemonIndex", - "zoneRenderer?.LoadDaemonZoneView(_entityIndex.EntitiesByDaemonIndex, render)", + "_entityIndex.TryGetPresentationEntityByRecordKey(currentEntityKey, out var currentEntity)", + "_entityIndex.PresentationEntitiesByDaemonIndex", + "zoneRenderer?.LoadDaemonZoneView(_entityIndex.PresentationEntitiesByDaemonIndex, render)", "zoneRenderer?.RestoreDroppedPickupsFromZoneRender(render)" }; var missingObservedFrameApplierSymbols = requiredObservedFrameApplierSymbols @@ -13569,7 +14025,7 @@ static void RequireMainMenuContinueRunState(string root) "AetheriaRuntimeEntitySnapshotProjector.CreateSnapshots(runId, daemonZone)", "ObservedEntityRestorer.TryApplyInPlace(", "ObservedEntityRestorer.Replace(entitySnapshots, currentEntityKey, Zone)", - "ZoneRenderer?.LoadDaemonZoneView(_observedEntityIndex.EntitiesByDaemonIndex, render)", + "ZoneRenderer?.LoadDaemonZoneView(_presentationEntityIndex.PresentationEntitiesByDaemonIndex, render)", "ZoneRenderer?.RestoreDroppedPickupsFromDaemonZoneState(daemonZone)", "render.ActionBarBindings", "private static AetheriaRuntimeActionBarBindingSnapshot ToActionBarBindingSnapshot(" @@ -13874,14 +14330,14 @@ static void RequireMainMenuContinueRunState(string root) actionGameManager.Contains("public bool TryGetObservedDockingBay(out EquippedDockingBay dockingBay)", StringComparison.Ordinal)) { throw new InvalidOperationException( - "ActionGameManager must not expose broad observed current-entity or docking-bay read shims; clients must use typed current projections and key-scoped facade adapters."); + "ActionGameManager must not expose broad observed current-entity or docking-bay read shims; clients must use managed typed current/docking documents and narrow Unity object adapters."); } if (actionGameManager.Contains("public EquippedDockingBay DockingBay", StringComparison.Ordinal) || actionGameManager.Contains("public Entity DockedEntity", StringComparison.Ordinal)) { throw new InvalidOperationException( - "ActionGameManager must not expose renderer-local docked entity or docking bay state as public gameplay truth; clients must use typed current-docking projections."); + "ActionGameManager must not expose renderer-local docked entity or docking bay state as public gameplay truth; clients must use the managed current-docking document."); } if (actionGameManager.Contains("public Entity TowingStation", StringComparison.Ordinal) || @@ -13944,7 +14400,9 @@ static void RequireMainMenuContinueRunState(string root) "presentation.RuntimeCatalog = runtimeCatalog", "public void ConfigureTargetPresentation(", "presentation.ResolveZoneContacts = ReadZoneContacts", - "AetheriaUnityRuntimeClientProvider.CurrentZoneContacts(\"unity-target-presentation\")", + "private AetheriaClientState _targetPresentationState;", + "AetheriaUnityRuntimeClientProvider.RuntimeState(\"unity-target-presentation\")", + "TargetPresentationState.ZoneContacts.Latest()", "presentation.RuntimeCatalog = runtimeCatalog" }; @@ -14317,7 +14775,7 @@ static void RequireUnityObserverDoesNotTickLocalSimulation(string root) "private AetheriaUnityObservedFrameApplier ObservedFrameApplier =>", "private Galaxy ObservedGalaxy { get; set; }", "ObservedGalaxy = boot.ObservedGalaxy", - "ResolveObservedGalaxyZone", + "ObservedZoneContextFactory.ResolveGalaxyZone", "private AetheriaUnityObservedZoneContextFactory ObservedZoneContextFactory =>", "entity => CurrentEntityBinder.RestoreBinding(entity)", "private AetheriaUnityEntityBlueprintMaterializer EntityBlueprintMaterializer =>", @@ -14344,7 +14802,8 @@ static void RequireUnityObserverDoesNotTickLocalSimulation(string root) var requiredSceneWiringObserverSymbols = new[] { "presentation.ResolveZoneContacts = ReadZoneContacts", - "AetheriaUnityRuntimeClientProvider.CurrentZoneContacts(\"unity-target-presentation\")" + "AetheriaUnityRuntimeClientProvider.RuntimeState(\"unity-target-presentation\")", + "TargetPresentationState.ZoneContacts.Latest()" }; var missingSceneWiringObserverSymbols = requiredSceneWiringObserverSymbols @@ -14482,10 +14941,10 @@ static void RequireUnityObserverDoesNotTickLocalSimulation(string root) ".OrderBy(entity => entity.EntityIndex)", "_entityRestorer.Replace(entitySnapshots, currentEntityKey, _getZone())", "_entityRestorer.TryApplyInPlace(", - "_entityIndex.TryResolveEntityByRecordKey(currentEntityKey, out var currentEntity)", - "_entityIndex.EntitiesByDaemonIndex", + "_entityIndex.TryGetPresentationEntityByRecordKey(currentEntityKey, out var currentEntity)", + "_entityIndex.PresentationEntitiesByDaemonIndex", "_zoneContextFactory.ResolveContext(targetZone, render)", - "zoneRenderer?.LoadDaemonZoneView(_entityIndex.EntitiesByDaemonIndex, render)", + "zoneRenderer?.LoadDaemonZoneView(_entityIndex.PresentationEntitiesByDaemonIndex, render)", "zoneRenderer?.ApplyZoneRender(render)", "zoneRenderer?.RestoreDroppedPickupsFromZoneRender(render)", "_restoreCurrentEntityBinding(currentEntity)" @@ -14511,7 +14970,7 @@ static void RequireUnityObserverDoesNotTickLocalSimulation(string root) "AetheriaRuntimeEntitySnapshotProjector.CreateSnapshots(runId, daemonZone)", "ObservedEntityRestorer.Replace(entitySnapshots, currentEntityKey, Zone)", "ObservedEntityRestorer.TryApplyInPlace(", - "ZoneRenderer?.LoadDaemonZoneView(_observedEntityIndex.EntitiesByDaemonIndex, render)", + "ZoneRenderer?.LoadDaemonZoneView(_presentationEntityIndex.PresentationEntitiesByDaemonIndex, render)", "ZoneRenderer?.ApplyZoneRender(render)", "ZoneRenderer?.RestoreDroppedPickupsFromDaemonZoneState(daemonZone)", "render.ActionBarBindings", @@ -14580,7 +15039,7 @@ static void RequireUnityObserverDoesNotTickLocalSimulation(string root) "public void RequestTargetReticle()", "public void RequestDock()", "public void RequestUndock()", - "_observedEntityIndex.TryResolveEntityRecordKey(target, out var targetEntityKey)", + "_presentationEntityIndex.TryGetRecordKeyForPresentationEntity(target, out var targetEntityKey)", "Submit(operations => operations.Interact(), \"interact\")", "Submit(operations => operations.ClearTarget(), \"target clear\")", "operations => operations.SetTarget(targetEntityKey)", @@ -14718,7 +15177,7 @@ static void RequireUnityObserverDoesNotTickLocalSimulation(string root) if (actionGameManager.Contains("public static Galaxy ObservedGalaxy", StringComparison.Ordinal)) { throw new InvalidOperationException( - "ActionGameManager must not expose the raw observed galaxy projection as public gameplay state; keep it as a private renderer adapter behind typed client projections."); + "ActionGameManager must not expose the raw observed galaxy projection as public gameplay state; keep it as a private renderer adapter behind managed typed documents."); } if (actionGameManager.Contains("public static void ProjectObservedDaemonRun(", StringComparison.Ordinal) || @@ -14732,7 +15191,7 @@ static void RequireUnityObserverDoesNotTickLocalSimulation(string root) mainMenu.Contains("ActionGameManager.IsTutorial", StringComparison.Ordinal)) { throw new InvalidOperationException( - "ActionGameManager must not mirror tutorial mode as public manager-global gameplay state; tutorial/run mode belongs to typed daemon run projections."); + "ActionGameManager must not mirror tutorial mode as public manager-global gameplay state; tutorial/run mode belongs to managed daemon run documents."); } if (actionGameManager.Contains("public static DirectoryInfo GameDataDirectory", StringComparison.Ordinal) || @@ -14786,19 +15245,18 @@ static void RequireUnityObserverDoesNotTickLocalSimulation(string root) var compactSectorMap = CompactSource(sectorMap); if (!mapRenderer.Contains("AetheriaUnityRuntimeClientProvider", StringComparison.Ordinal) || !compactMapRenderer.Contains(".MapViewportDocuments(viewport,\"unity-map-renderer\")", StringComparison.Ordinal) || - !compactMapRenderer.Contains(CompactSource("AetheriaUnityRuntimeClientProvider.CurrentPlayerSettings(\"unity-map-renderer\")"), StringComparison.Ordinal) || + !compactMapRenderer.Contains(CompactSource("AetheriaUnityRuntimeClientProvider.RuntimeState(\"unity-map-renderer\")"), StringComparison.Ordinal) || + !compactMapRenderer.Contains(CompactSource("_runtimeState.PlayerSettings.Latest()"), StringComparison.Ordinal) || !sectorRenderer.Contains("AetheriaUnityRuntimeClientProvider", StringComparison.Ordinal) || - !sectorRenderer.Contains("AetheriaUnityRuntimeClientProvider.CurrentSectorMap(\"unity-sector-renderer\")", StringComparison.Ordinal) || - !sectorRenderer.Contains("AetheriaUnityRuntimeClientProvider.CurrentZoneDetails(zoneIndex, \"unity-sector-renderer\")", StringComparison.Ordinal) || - !sectorRenderer.Contains("AetheriaUnityRuntimeClientProvider.CurrentCatalog(\"unity-sector-renderer\")", StringComparison.Ordinal) || - !sectorRenderer.Contains("AetheriaUnityRuntimeClientProvider.CurrentPlayerSettings(\"unity-sector-renderer\")", StringComparison.Ordinal) || - !sectorRenderer.Contains("AetheriaUnityRuntimeClientProvider.CurrentZoneState(\"unity-sector-renderer\")", StringComparison.Ordinal) || + !sectorRenderer.Contains("AetheriaUnityRuntimeClientProvider.RuntimeState(\"unity-sector-renderer\")", StringComparison.Ordinal) || + !sectorRenderer.Contains("RuntimeState.ZoneDetailsSurface(zoneIndex).Latest()", StringComparison.Ordinal) || + !sectorRenderer.Contains("RuntimeState.CurrentZone.Latest()", StringComparison.Ordinal) || !sectorMap.Contains("AetheriaUnityRuntimeClientProvider", StringComparison.Ordinal) || - !sectorMap.Contains(".CurrentSectorMap(\"unity-sector-map\")", StringComparison.Ordinal) || - !sectorRenderer.Contains("AetheriaRuntimeZoneDetailsSurfaceBuilder.Facts(", StringComparison.Ordinal)) + !sectorMap.Contains("AetheriaUnityRuntimeClientProvider.RuntimeState(\"unity-sector-map\")", StringComparison.Ordinal) || + !sectorMap.Contains("_sectorMap = _runtimeState.SectorMap.Latest()", StringComparison.Ordinal)) { throw new InvalidOperationException( - "Map and sector UI must request projected galaxy/zone data through explicit observed daemon boundaries."); + "Map and sector UI must request managed typed map, zone, and surface documents through the Unity runtime client provider."); } var forbiddenMapRendererSettingsSymbols = new[] @@ -15478,7 +15936,9 @@ static void RequireUnityObserverDoesNotTickLocalSimulation(string root) "TryStartDaemonObservedGame", "ResolveSectorMap(stateBoot)", "AetheriaUnityRuntimeClientProvider", - ".CurrentSectorMap(stateBoot, \"unity-main-menu\")", + ".RuntimeState(stateBoot, \"unity-main-menu\")", + ".SectorMap", + ".Latest()", "sectorMap.FrameId", "SceneManager.LoadScene(\"ARPG\")" }; @@ -16075,7 +16535,7 @@ static void RequireNameToolsUsesUiToolkit(string root) static void RequireRuntimeStateReaderOwnsUnityStateAcquisition(string root) { var runtimeStateRefResolverPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeStateRefResolver.cs"); - var runtimeEveSurfaceAdapterPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeEveSurfaceAdapter.cs"); + var runtimeSurfaceDocumentsPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeSurfaceDocuments.cs"); var unityPackageProjectPath = Path.Combine(root, "GameCult.Aetheria.State.Unity.csproj"); var actionGameManagerPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "ActionGameManager.cs"); var gameplayBootShellPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "AetheriaUnityGameplayBootShell.cs"); @@ -16086,7 +16546,7 @@ static void RequireRuntimeStateReaderOwnsUnityStateAcquisition(string root) var requiredPaths = new[] { runtimeStateRefResolverPath, - runtimeEveSurfaceAdapterPath, + runtimeSurfaceDocumentsPath, unityPackageProjectPath, actionGameManagerPath, gameplayBootShellPath, @@ -16108,7 +16568,7 @@ static void RequireRuntimeStateReaderOwnsUnityStateAcquisition(string root) } var runtimeStateRefResolver = File.ReadAllText(runtimeStateRefResolverPath); - var runtimeEveSurfaceAdapter = File.ReadAllText(runtimeEveSurfaceAdapterPath); + var runtimeSurfaceDocuments = File.ReadAllText(runtimeSurfaceDocumentsPath); var unityPackageProject = File.ReadAllText(unityPackageProjectPath); var actionGameManager = File.ReadAllText(actionGameManagerPath); var gameplayBootShell = File.ReadAllText(gameplayBootShellPath); @@ -16237,9 +16697,9 @@ static void RequireRuntimeStateReaderOwnsUnityStateAcquisition(string root) "GameCult.Aetheria.State.Unity.csproj does not include the shared daemon state-ref vocabulary."); } - var requiredAdapterSymbols = new[] + var requiredSurfaceDocumentSymbols = new[] { - "public static class AetheriaRuntimeEveSurfaceAdapter", + "public static class AetheriaRuntimeSurfaceDocuments", "public static EveSurfaceDocument ToEveSurfaceDocument(AetheriaRuntimeSurfaceDocument document)", "public static EveSurfaceDocument ResolveStateRefs(", "CultMeshStateRefResolver? stateRefResolver", @@ -16251,20 +16711,20 @@ static void RequireRuntimeStateReaderOwnsUnityStateAcquisition(string root) "new EveSurfaceDocument(", "new EveSurfaceComponent(" }; - var missingAdapterSymbols = requiredAdapterSymbols - .Where(symbol => !runtimeEveSurfaceAdapter.Contains(symbol, StringComparison.Ordinal)) + var missingSurfaceDocumentSymbols = requiredSurfaceDocumentSymbols + .Where(symbol => !runtimeSurfaceDocuments.Contains(symbol, StringComparison.Ordinal)) .ToArray(); - if (missingAdapterSymbols.Length > 0) + if (missingSurfaceDocumentSymbols.Length > 0) { throw new InvalidOperationException( - "Shared runtime Eve surface adapter is incomplete: " + - string.Join(", ", missingAdapterSymbols)); + "Shared runtime surface documents are incomplete: " + + string.Join(", ", missingSurfaceDocumentSymbols)); } - if (!unityPackageProject.Contains("AetheriaRuntimeEveSurfaceAdapter.cs", StringComparison.Ordinal)) + if (!unityPackageProject.Contains("AetheriaRuntimeSurfaceDocuments.cs", StringComparison.Ordinal)) { throw new InvalidOperationException( - "GameCult.Aetheria.State.Unity.csproj does not include the shared runtime Eve surface adapter."); + "GameCult.Aetheria.State.Unity.csproj does not include the shared runtime surface documents."); } var daemonObserverPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "AetheriaDaemonObserver.cs"); @@ -16386,8 +16846,9 @@ static void RequireRuntimeStateReaderOwnsUnityStateAcquisition(string root) "public sealed class AetheriaUnityGameplayBootShell", "public AetheriaUnityGameplayBootResult Boot()", "AetheriaRuntimeStateBoot.Inspect(AetheriaUnityRuntimePaths.GameDataDirectory)", - "AetheriaUnityRuntimeClientProvider.CurrentCatalog(stateBoot, stateBoot.RuntimeId)", - "AetheriaUnityRuntimeClientProvider.CurrentSectorMap(stateBoot, stateBoot.RuntimeId)", + "AetheriaUnityRuntimeClientProvider.RuntimeState(stateBoot, stateBoot.RuntimeId)", + "runtimeState.Catalog.Latest()", + "runtimeState.SectorMap.Latest()", "new ItemManager(", "new AetheriaUnityLoadoutItemFactory(itemManager, runtimeCatalog)", "ZoneRenderer.SetDroppedPickupItemFactory(loadoutItemFactory.CreateLoadoutItem)", @@ -16411,7 +16872,8 @@ static void RequireRuntimeStateReaderOwnsUnityStateAcquisition(string root) if (gameplayBootShell.Contains(".ObserveCatalog()", StringComparison.Ordinal) || gameplayBootShell.Contains(".ObserveSectorMap()", StringComparison.Ordinal) || - gameplayBootShell.Contains("AetheriaUnityRuntimeClientProvider.RuntimeState(stateBoot, stateBoot.RuntimeId)", StringComparison.Ordinal) || + gameplayBootShell.Contains("AetheriaUnityRuntimeClientProvider.CurrentCatalog(stateBoot, stateBoot.RuntimeId)", StringComparison.Ordinal) || + gameplayBootShell.Contains("AetheriaUnityRuntimeClientProvider.CurrentSectorMap(stateBoot, stateBoot.RuntimeId)", StringComparison.Ordinal) || gameplayBootShell.Contains("AetheriaUnityRuntimeClientProvider.ResolveClient(stateBoot.StateFilePath, stateBoot.RuntimeId)", StringComparison.Ordinal) || gameplayBootShell.Contains(".State;")) { @@ -16427,28 +16889,10 @@ static void RequireRuntimeStateReaderOwnsUnityStateAcquisition(string root) "private static AetheriaClient ResolveClient(AetheriaRuntimeStateBootReport stateBoot, string runtimeId = \"\")", "private static AetheriaClient RuntimeClient(string runtimeId = \"\")", "public static AetheriaClientState RuntimeState(string runtimeId = \"\")", - "public static AetheriaRuntimeCurrentDockingDocument CurrentDockingState(string runtimeId = \"\")", - "public static AetheriaRuntimeCurrentDockingDocument CurrentDockingState(", - "public static AetheriaRuntimeCatalogSnapshot CurrentCatalog(string runtimeId = \"\")", - "public static AetheriaRuntimePlayerSettingsDocument CurrentPlayerSettings(string runtimeId = \"\")", - "public static AetheriaRuntimeCurrentEntityDocument CurrentEntityState(string runtimeId = \"\")", - "public static AetheriaRuntimeCurrentZoneDocument CurrentZoneState(string runtimeId = \"\")", - "public static AetheriaRuntimeZoneContactsDocument CurrentZoneContacts(string runtimeId = \"\")", - "public static CultMeshReactiveDocument ReactiveZoneContacts(", - "public static AetheriaRuntimeStationRefitDocument CurrentStationRefit(string runtimeId = \"\")", - "public static AetheriaRuntimeSectorMapDocument CurrentSectorMap(string runtimeId = \"\")", - "public static AetheriaRuntimeSectorMapDocument CurrentSectorMap(", - "public static AetheriaRuntimeZoneDetailsDocument CurrentZoneDetails(int zoneIndex, string runtimeId = \"\")", - "public static AetheriaRuntimeInventoryDocument CurrentInventory(int entityIndex, string runtimeId = \"\")", - "public static CultMeshReactiveDocument ReactiveObjectsViewport(", - "public static CultMeshReactiveDocument ReactiveRenderSplatsViewport(", "public static AetheriaUnityRtsViewportDocuments MapViewportDocuments(", "public static AetheriaUnityRtsViewportDocuments ZonePresentationDocuments(", "public static AetheriaUnityRtsViewportDocuments RenderSplatViewportDocuments(", - "public static AetheriaRuntimeDaemonFrameDocument CurrentDaemonFrame(string runtimeId = \"\")", - "public static AetheriaRuntimeDaemonFrameDocument CurrentDaemonFrame(", "public static AetheriaUnityDaemonRenderDocuments DaemonRenderDocuments(", - "public static AetheriaRuntimeVerseHostSettingsDocument CurrentVerseHostSettings(", "public static AetheriaControl Control(string runtimeId = \"\")", "public static AetheriaUi Ui(string runtimeId = \"\")", "public static AetheriaUi Ui(", @@ -16459,7 +16903,6 @@ static void RequireRuntimeStateReaderOwnsUnityStateAcquisition(string root) "RuntimeClients[cacheKey] = runtimeClient", "AetheriaClient", ".State", - "CurrentPlayerSettings()", "OpenAsync(", "pullOnOpen: true", "ApplyPlayerSettings(settings, stored)" @@ -16475,6 +16918,9 @@ static void RequireRuntimeStateReaderOwnsUnityStateAcquisition(string root) } if (runtimeClientProvider.Contains("public static CultMeshReactiveDocument Reactive", StringComparison.Ordinal) || + runtimeClientProvider.Contains("public static CultMeshReactiveDocument ReactiveZoneContacts(", StringComparison.Ordinal) || + runtimeClientProvider.Contains("public static CultMeshReactiveDocument ReactiveObjectsViewport(", StringComparison.Ordinal) || + runtimeClientProvider.Contains("public static CultMeshReactiveDocument ReactiveRenderSplatsViewport(", StringComparison.Ordinal) || runtimeClientProvider.Contains("public static CultMeshReactiveDocument ReactiveEveSurface(", StringComparison.Ordinal) || runtimeClientProvider.Contains("private static CultMeshReactiveDocument _playerSettingsDocument", StringComparison.Ordinal) || runtimeClientProvider.Contains("RuntimeState().ReactivePlayerSettingsDocument()", StringComparison.Ordinal) || @@ -16485,6 +16931,26 @@ static void RequireRuntimeStateReaderOwnsUnityStateAcquisition(string root) "AetheriaUnityRuntimeClientProvider should expose named current/reactive typed handles; do not rebuild generic static ReactiveX facade wrappers or cached reactive state."); } + if (runtimeClientProvider.Contains("public static AetheriaRuntimeCurrentDockingDocument CurrentDockingState(", StringComparison.Ordinal) || + runtimeClientProvider.Contains("public static AetheriaRuntimeCatalogSnapshot CurrentCatalog(", StringComparison.Ordinal) || + runtimeClientProvider.Contains("public static AetheriaRuntimePlayerSettingsDocument CurrentPlayerSettings(", StringComparison.Ordinal) || + runtimeClientProvider.Contains("public static AetheriaRuntimeCurrentEntityDocument CurrentEntityState(", StringComparison.Ordinal) || + runtimeClientProvider.Contains("public static AetheriaRuntimeCurrentZoneDocument CurrentZoneState(", StringComparison.Ordinal) || + runtimeClientProvider.Contains("public static AetheriaRuntimeZoneContactsDocument CurrentZoneContacts(", StringComparison.Ordinal) || + runtimeClientProvider.Contains("public static AetheriaRuntimeStationRefitDocument CurrentStationRefit(", StringComparison.Ordinal) || + runtimeClientProvider.Contains("public static AetheriaRuntimeSectorMapDocument CurrentSectorMap(", StringComparison.Ordinal) || + runtimeClientProvider.Contains("public static AetheriaRuntimeZoneDetailsDocument CurrentZoneDetails(", StringComparison.Ordinal) || + runtimeClientProvider.Contains("public static AetheriaRuntimeSurfaceDocument CurrentZoneDetailsSurface(", StringComparison.Ordinal) || + runtimeClientProvider.Contains("public static AetheriaRuntimeSurfaceDocument CurrentInventoryPanelSurface(", StringComparison.Ordinal) || + runtimeClientProvider.Contains("public static AetheriaRuntimeSurfaceDocument CurrentInventoryDropdownSurface(", StringComparison.Ordinal) || + runtimeClientProvider.Contains("public static AetheriaRuntimeInventoryDocument CurrentInventory(", StringComparison.Ordinal) || + runtimeClientProvider.Contains("public static AetheriaRuntimeDaemonFrameDocument CurrentDaemonFrame(", StringComparison.Ordinal) || + runtimeClientProvider.Contains("public static AetheriaRuntimeVerseHostSettingsDocument CurrentVerseHostSettings(", StringComparison.Ordinal)) + { + throw new InvalidOperationException( + "AetheriaUnityRuntimeClientProvider must expose managed AetheriaClientState handles; provider-level CurrentX snapshot shortcuts are heretek."); + } + if (runtimeClientProvider.Contains("AetheriaRuntimePlayerSettingsSession _playerSettingsDocument", StringComparison.Ordinal) || runtimeClientProvider.Contains("public static AetheriaClient ResolveClient", StringComparison.Ordinal) || runtimeClientProvider.Contains("public static AetheriaClient RuntimeClient", StringComparison.Ordinal) || @@ -16618,7 +17084,7 @@ static void RequireRuntimeStateReaderOwnsUnityStateAcquisition(string root) "public AetheriaClientState State => _state;", "var frame = CurrentDaemonFrame();", "private CultMeshReactiveDocument? _daemonFrame;", - "_daemonFrame ??= State.ReactiveDaemonFrame();" + "_daemonFrame ??= State.DaemonFrame.Reactive();" }; var missingManagedClientConnectionSymbols = requiredManagedClientConnectionSymbols .Where(symbol => !aetheriaClient.Contains(symbol, StringComparison.Ordinal)) @@ -16687,7 +17153,7 @@ static void RequireRuntimeStateReaderOwnsUnityStateAcquisition(string root) var requiredManagedCommandFrameSymbols = new[] { "internal AetheriaRuntimeDaemonFrameDocument? CurrentDaemonFrame()", - "_daemonFrame ??= State.ReactiveDaemonFrame();", + "_daemonFrame ??= State.DaemonFrame.Reactive();", "return _daemonFrame.Current;", "Func submit", "frame?.SessionId ?? _sessionId" @@ -16703,11 +17169,14 @@ static void RequireRuntimeStateReaderOwnsUnityStateAcquisition(string root) } if (!daemonRuntimeDocumentTests.Contains("ManagedStateSamplesCurrentDaemonRenderViewFromReactiveDocuments()", StringComparison.Ordinal) || - !daemonRuntimeDocumentTests.Contains("using var observedFrame = client.State.ReactiveDaemonFrame();", StringComparison.Ordinal) || - !daemonRuntimeDocumentTests.Contains("using var observedSoaView = client.State.ReactiveDaemonSoaView();", StringComparison.Ordinal) || - !daemonRuntimeDocumentTests.Contains("using var observedZoneRender = client.State.ReactiveZoneRender();", StringComparison.Ordinal) || + !daemonRuntimeDocumentTests.Contains("using var observedFrame = client.State.DaemonFrame.Reactive();", StringComparison.Ordinal) || + !daemonRuntimeDocumentTests.Contains("using var observedSoaView = client.State.DaemonSoaView.Reactive();", StringComparison.Ordinal) || + !daemonRuntimeDocumentTests.Contains("using var observedZoneRender = client.State.ZoneRender.Reactive();", StringComparison.Ordinal) || !daemonRuntimeDocumentTests.Contains("AetheriaRuntimeDaemonRenderView.TryCreateCurrent(", StringComparison.Ordinal) || - daemonRuntimeDocumentTests.Contains("client.State.CurrentObservedDaemon()", StringComparison.Ordinal)) + daemonRuntimeDocumentTests.Contains("client.State.CurrentObservedDaemon()", StringComparison.Ordinal) || + daemonRuntimeDocumentTests.Contains("using var observedFrame = client.State.ReactiveDaemonFrame();", StringComparison.Ordinal) || + daemonRuntimeDocumentTests.Contains("using var observedSoaView = client.State.ReactiveDaemonSoaView();", StringComparison.Ordinal) || + daemonRuntimeDocumentTests.Contains("using var observedZoneRender = client.State.ReactiveZoneRender();", StringComparison.Ordinal)) { throw new InvalidOperationException( "Daemon runtime document tests must teach observed daemon sampling through direct reactive daemon documents."); @@ -16970,8 +17439,11 @@ static void RequireRuntimeStateReaderOwnsUnityStateAcquisition(string root) if (!mainMenu.Contains("AetheriaUnityRuntimeClientProvider", StringComparison.Ordinal) || !mainMenu.Contains("ResolveSectorMap(AetheriaRuntimeStateBootReport stateBoot)", StringComparison.Ordinal) || - !mainMenu.Contains(".CurrentSectorMap(stateBoot, \"unity-main-menu\")", StringComparison.Ordinal) || - !mainMenu.Contains(".CurrentPlayerSettings(stateBoot, \"unity-main-menu\")", StringComparison.Ordinal)) + !mainMenu.Contains(".RuntimeState(stateBoot, \"unity-main-menu\")", StringComparison.Ordinal) || + !mainMenu.Contains(".SectorMap", StringComparison.Ordinal) || + !mainMenu.Contains(".PlayerSettings", StringComparison.Ordinal) || + !mainMenu.Contains(".VerseHostSettings", StringComparison.Ordinal) || + !mainMenu.Contains(".Latest()", StringComparison.Ordinal)) { throw new InvalidOperationException( "MainMenu must read menu state through provider-native managed typed documents."); @@ -17007,8 +17479,9 @@ static void RequireRuntimeStateReaderOwnsUnityStateAcquisition(string root) var requiredGameplayBootSymbols = new[] { - "AetheriaUnityRuntimeClientProvider.CurrentCatalog(stateBoot, stateBoot.RuntimeId)", - "AetheriaUnityRuntimeClientProvider.CurrentSectorMap(stateBoot, stateBoot.RuntimeId)", + "AetheriaUnityRuntimeClientProvider.RuntimeState(stateBoot, stateBoot.RuntimeId)", + "runtimeState.Catalog.Latest()", + "runtimeState.SectorMap.Latest()", "Galaxy.ProjectObservedSectorMap(", "sectorMap.IsTutorial", "sectorMap.GenerationSeed", @@ -17029,10 +17502,11 @@ static void RequireRuntimeStateReaderOwnsUnityStateAcquisition(string root) gameplayBootShell.Contains("runtimeCatalogDocument.Current", StringComparison.Ordinal) || gameplayBootShell.Contains(".ReactiveSectorMap()", StringComparison.Ordinal) || gameplayBootShell.Contains("sectorMapDocument.Current", StringComparison.Ordinal) || - gameplayBootShell.Contains("AetheriaUnityRuntimeClientProvider.RuntimeState(stateBoot, stateBoot.RuntimeId)", StringComparison.Ordinal)) + gameplayBootShell.Contains("AetheriaUnityRuntimeClientProvider.CurrentCatalog(stateBoot, stateBoot.RuntimeId)", StringComparison.Ordinal) || + gameplayBootShell.Contains("AetheriaUnityRuntimeClientProvider.CurrentSectorMap(stateBoot, stateBoot.RuntimeId)", StringComparison.Ordinal)) { throw new InvalidOperationException( - "AetheriaUnityGameplayBootShell should use named current typed state reads for one-shot gameplay boot catalog/sector-map access."); + "AetheriaUnityGameplayBootShell should use the managed runtime state handle for one-shot gameplay boot catalog/sector-map access."); } var observedRunProjectionPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "AetheriaUnityObservedRunProjection.cs"); @@ -17058,7 +17532,8 @@ static void RequireRuntimeStateReaderOwnsUnityStateAcquisition(string root) } if (!eveSurfacePresenter.Contains(".RuntimeState(stateBoot, \"unity-eve-surface-presenter\")", StringComparison.Ordinal) || - !eveSurfacePresenter.Contains(".ReactiveEveSurface(surfaceId)", StringComparison.Ordinal) || + !eveSurfacePresenter.Contains(".EveSurfaceDocument(surfaceId)", StringComparison.Ordinal) || + !eveSurfacePresenter.Contains("?.Reactive()", StringComparison.Ordinal) || !eveSurfacePresenter.Contains("ReadDaemonSurface(stateBoot)", StringComparison.Ordinal)) { throw new InvalidOperationException( @@ -17166,7 +17641,7 @@ static void RequireRuntimeStateReaderOwnsUnityStateAcquisition(string root) if (localSurfaceAdapterHits.Length > 0) { throw new InvalidOperationException( - "Unity UI lowerers still duplicate runtime-to-Eve surface conversion instead of using the shared adapter: " + + "Unity UI lowerers still duplicate runtime-to-Eve surface conversion instead of using the shared runtime surface documents: " + string.Join("; ", localSurfaceAdapterHits)); } } @@ -17292,14 +17767,30 @@ static void RequireHullConductivityRequestAuthority(string root) var inventoryPanel = File.Exists(inventoryPanelPath) ? File.ReadAllText(inventoryPanelPath) : throw new InvalidOperationException("Cannot verify hull conductivity authority; InventoryPanel.cs is missing."); + var daemonOperationClientPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeDaemonOperationClient.cs"); + var aetheriaControlPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaControl.cs"); + var daemonOperationClient = File.Exists(daemonOperationClientPath) + ? File.ReadAllText(daemonOperationClientPath) + : throw new InvalidOperationException("Cannot verify hull conductivity authority; daemon operation client is missing."); + var aetheriaControl = File.Exists(aetheriaControlPath) + ? File.ReadAllText(aetheriaControlPath) + : throw new InvalidOperationException("Cannot verify hull conductivity authority; AetheriaControl.cs is missing."); - if (!inventoryPanel.Contains("RequestHullConductivityToggle", StringComparison.Ordinal) || - !inventoryPanel.Contains("operations => operations.ToggleHullConductivity", StringComparison.Ordinal)) + if (!daemonOperationClient.Contains("internal AetheriaRuntimeDaemonCommandEnvelope ToggleHullConductivity(", StringComparison.Ordinal) || + !daemonOperationClient.Contains("AetheriaRuntimeDaemonCommandKinds.ToggleHullConductivity", StringComparison.Ordinal) || + !aetheriaControl.Contains("public CultMeshOperationReceipt ToggleHullConductivity(", StringComparison.Ordinal)) { throw new InvalidOperationException( "Hull conductivity no longer has a typed daemon request primitive."); } + if (inventoryPanel.Contains("RequestHullConductivityToggle", StringComparison.Ordinal) || + inventoryPanel.Contains("ToggleHullConductivity", StringComparison.Ordinal)) + { + throw new InvalidOperationException( + "InventoryPanel must not own hull conductivity cell-click authority; thermal pipe edits belong to the daemon-presented inventory surface."); + } + var forbiddenUnityAcceptanceSymbols = new[] { "entity?.HullConductivity == null", @@ -17386,9 +17877,11 @@ static void RequireInventoryEntityRenameRequestAuthority(string root) throw new InvalidOperationException("InventoryPanel still routes entity rename through ActionGameManager instead of its typed operation client."); } - var renameRequest = inventoryPanel.IndexOf("RequestEntityName(_displayedEntity", StringComparison.Ordinal); - var titleRefreshAfterRename = inventoryPanel.IndexOf("Title.text = _displayedEntity.Name", renameRequest, StringComparison.Ordinal); - if (renameRequest >= 0 && titleRefreshAfterRename >= 0) + var renameRequest = inventoryPanel.IndexOf("RequestEntityName(", StringComparison.Ordinal); + var titleRefreshAfterRename = renameRequest < 0 + ? -1 + : inventoryPanel.IndexOf("Title.text = _displayedEntity.Name", renameRequest, StringComparison.Ordinal); + if (titleRefreshAfterRename >= 0) { throw new InvalidOperationException( "InventoryPanel still refreshes entity title immediately after submitting a daemon rename request."); @@ -17755,27 +18248,58 @@ static void RequireInventoryDoubleClickTransferRequestAuthority(string root) throw new InvalidOperationException("Cannot verify inventory transfer authority; ActionGameManager.cs is missing."); } - var requiredRequests = new[] + var operationClientPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeDaemonOperationClient.cs"); + var operationClient = File.Exists(operationClientPath) + ? File.ReadAllText(operationClientPath) + : throw new InvalidOperationException("Cannot verify inventory transfer authority; AetheriaRuntimeDaemonOperationClient.cs is missing."); + + var panelSurfacePath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeInventoryPanelSurfaceBuilder.cs"); + var panelSurface = File.Exists(panelSurfacePath) + ? File.ReadAllText(panelSurfacePath) + : throw new InvalidOperationException("Cannot verify inventory transfer authority; AetheriaRuntimeInventoryPanelSurfaceBuilder.cs is missing."); + + var requiredDaemonRequests = new[] { - "RequestCargoItemTransfer", - "RequestCargoItemEquip", - "RequestEquippedItemStore", - "RequestEquippedItemEquip", - "operations => operations.TransferCargoItem(", - "operations => operations.EquipItem(", - "operations => operations.StoreItem(" + "TransferCargoItem(", + "EquipItem(", + "StoreItem(", + "AetheriaRuntimeDaemonCommandKinds.TransferCargoItem", + "AetheriaRuntimeDaemonCommandKinds.EquipItem", + "AetheriaRuntimeDaemonCommandKinds.StoreItem" }; - var inventoryTransferSources = inventoryPanel + "\n" + inventoryMenu; - var missingRequests = requiredRequests - .Where(symbol => !inventoryTransferSources.Contains(symbol, StringComparison.Ordinal)) + var missingDaemonRequests = requiredDaemonRequests + .Where(symbol => !operationClient.Contains(symbol, StringComparison.Ordinal)) .ToArray(); - if (missingRequests.Length > 0) + if (missingDaemonRequests.Length > 0) { throw new InvalidOperationException( - "Inventory transfer no longer has complete typed daemon request primitives: " + - string.Join(", ", missingRequests)); + "Inventory transfer no longer has complete typed daemon operation primitives: " + + string.Join(", ", missingDaemonRequests)); + } + + var requiredSurfaceSymbols = new[] + { + "AetheriaRuntimeInventoryPanelSurfaceBuilder", + "\"inventory.grid\"", + "\"inventory.item\"", + "\"itemKey\"", + "\"source\"", + "\"sourceIndex\"", + "\"x\"", + "\"y\"", + "\"quantity\"", + "AetheriaRuntimeSurfaceCommandTemplate.CultMeshTransport" + }; + var missingSurfaceSymbols = requiredSurfaceSymbols + .Where(symbol => !panelSurface.Contains(symbol, StringComparison.Ordinal)) + .ToArray(); + if (missingSurfaceSymbols.Length > 0) + { + throw new InvalidOperationException( + "Inventory panel surface must publish typed item identity and command transport data through CultUI: " + + string.Join(", ", missingSurfaceSymbols)); } var forbiddenUnityInventoryAcceptanceSymbols = new[] @@ -17786,15 +18310,33 @@ static void RequireInventoryDoubleClickTransferRequestAuthority(string root) "Equipment.Contains(", "origin == destination", "ReferenceEquals(equippedItem.Entity, origin)", - "!origin.Cargo.TryGetValue(item, out var sourcePosition)" + "!origin.Cargo.TryGetValue(item, out var sourcePosition)", + "origin.Cargo.TryGetValue(item, out var originPosition)", + "TryResolveManagedInventoryRows(", + "TryValidateTypedCargoSlot(", + "TryValidateTypedEquipmentSlot(", + "RuntimeState.Inventory(entityIndex).Latest()", + "RequestCargoItemTransfer", + "RequestCargoItemEquip", + "RequestEquippedItemTransfer", + "RequestEquippedItemStore", + "RequestDraggedItemToEntity", + "RequestDraggedItemToCargo", + "InventoryCargoEventData", + "InventoryEntityEventData", + "GearOccupancy[", + "Occupancy[", + "CellInstances[", + "OnClickAsObservable().Subscribe" }; + var inventoryTransferSources = inventoryPanel + "\n" + inventoryMenu; var unityInventoryAcceptanceHits = forbiddenUnityInventoryAcceptanceSymbols .Where(symbol => inventoryTransferSources.Contains(symbol, StringComparison.Ordinal)) .ToArray(); if (unityInventoryAcceptanceHits.Length > 0) { throw new InvalidOperationException( - "Unity inventory request code still rejects requests through renderer-local capacity or membership checks: " + + "Unity inventory UI still owns inventory cell interpretation, transfer validation, or transfer submission: " + string.Join(", ", unityInventoryAcceptanceHits)); } @@ -17839,69 +18381,6 @@ static void RequireInventoryDoubleClickTransferRequestAuthority(string root) string.Join("; ", inventoryUiAcceptanceHits)); } - var forbiddenInventorySubmissionAcceptanceSymbols = new[] - { - "Unable to move item!", - "Verify that cargo bays are empty before un-equipping them.", - "var success = RequestDraggedItemTo", - "var submitted = RequestDraggedItemTo", - "if (RequestCargoItemTransfer(", - "if (RequestEquippedItemTransfer(", - "ShowUnableToSubmitItemMoveRequestDialog(" - }; - var inventorySubmissionAcceptanceHits = new[] { inventoryMenuPath, inventoryPanelPath } - .SelectMany(path => File.ReadLines(path) - .Select((line, index) => new { Path = path, LineNumber = index + 1, Line = line })) - .Where(line => forbiddenInventorySubmissionAcceptanceSymbols.Any(symbol => line.Line.Contains(symbol, StringComparison.Ordinal))) - .Select(line => $"{Path.GetRelativePath(root, line.Path)}:{line.LineNumber}: {line.Line.Trim()}") - .ToArray(); - if (inventorySubmissionAcceptanceHits.Length > 0) - { - throw new InvalidOperationException( - "Inventory UI still treats daemon transfer submission as accepted inventory movement: " + - string.Join("; ", inventorySubmissionAcceptanceHits)); - } - - var requiredTypedSlotValidationSymbols = new[] - { - "TryResolveTypedInventoryRows(", - "TryValidateTypedCargoSlot(", - "TryValidateTypedEquipmentSlot(", - "origin.Cargo.TryGetValue(item, out var originPosition)", - "SourceIndex == cargoIndex", - "SourceIndex == equipmentIndex", - "row.X == originPosition.x", - "row.Y == originPosition.y", - "row.X == item.Position.x", - "row.Y == item.Position.y" - }; - var missingTypedSlotValidationSymbols = requiredTypedSlotValidationSymbols - .Where(symbol => - !inventoryMenu.Contains(symbol, StringComparison.Ordinal) || - !inventoryPanel.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (missingTypedSlotValidationSymbols.Length > 0) - { - throw new InvalidOperationException( - "Inventory cargo/equipment submissions must validate Unity inventory items against typed inventory document slot identity: " + - string.Join(", ", missingTypedSlotValidationSymbols)); - } - var requiredMenuInventoryAccess = - "AetheriaUnityRuntimeClientProvider.CurrentInventory(entityIndex, \"unity-inventory-menu\")"; - var requiredPanelInventoryAccess = - "AetheriaUnityRuntimeClientProvider.CurrentInventory(entityIndex, \"unity-inventory\")"; - if (!inventoryMenu.Contains(requiredMenuInventoryAccess, StringComparison.Ordinal) || - !inventoryPanel.Contains(requiredPanelInventoryAccess, StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Inventory cargo/equipment submissions must read typed inventory slot identity through the appropriate managed document access: " + - string.Join(", ", new[] - { - inventoryMenu.Contains(requiredMenuInventoryAccess, StringComparison.Ordinal) ? null : requiredMenuInventoryAccess, - inventoryPanel.Contains(requiredPanelInventoryAccess, StringComparison.Ordinal) ? null : requiredPanelInventoryAccess - }.Where(symbol => symbol != null))); - } - var forbiddenUnknownOriginSymbols = new[] { "int.MinValue" }; var unknownOriginHits = new[] { inventoryMenuPath, inventoryPanelPath } .SelectMany(path => File.ReadLines(path) @@ -17962,27 +18441,6 @@ static void RequireInventoryDoubleClickTransferRequestAuthority(string root) "Inventory drag/request APIs still expose submission as public acceptance state: " + string.Join("; ", publicAcceptanceApiHits)); } - - var requiredUiCalls = new[] - { - "RequestCargoItemTransfer", - "RequestCargoItemEquip", - "RequestEquippedItemStore", - "RequestEquippedItemEquip" - }; - - var missingUiCalls = requiredUiCalls - .Where(symbol => - !inventoryMenu.Contains(symbol, StringComparison.Ordinal) && - !inventoryPanel.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - - if (missingUiCalls.Length > 0) - { - throw new InvalidOperationException( - "Inventory UI no longer routes transfer requests through ActionGameManager: " + - string.Join(", ", missingUiCalls)); - } } static void RequireTradePurchaseRequestAuthority(string root) @@ -18429,16 +18887,14 @@ static void RequireInventoryLoadoutSaveRequestAuthority(string root) var requiredInventoryPanelSymbols = new[] { - "RequestLoadoutTemplateSave(Entity entity)", - "TryResolveEntityRecordKey(entity, out var targetEntityKey)", - "CreateLoadoutTemplate(targetEntityKey)", + "RequestLoadoutTemplateSave(string targetEntityKey)", ".CreateLoadoutTemplate(targetEntityKey ?? \"\")", ".Ui(\"unity-inventory\")", ".SaveLoadoutTemplateAsync(loadout, \"unity-inventory\")" }; var missingInventoryPanelSymbols = requiredInventoryPanelSymbols .Where(symbol => !inventoryPanel.Contains(symbol, StringComparison.Ordinal)) - .Concat(new[] { "AetheriaUnityRuntimeClientProvider.CurrentDaemonFrame(\"unity-inventory\")" } + .Concat(new[] { "RuntimeState.DaemonFrame.Latest()" } .Where(symbol => !inventoryPanel.Contains(symbol, StringComparison.Ordinal))) .ToArray(); if (missingInventoryPanelSymbols.Length > 0) @@ -18543,7 +18999,7 @@ static void RequireInventoryLoadoutSaveRequestAuthority(string root) string.Join(", ", forbiddenActionHits)); } - if (!inventoryPanel.Contains("RequestLoadoutTemplateSave(_displayedEntity)", StringComparison.Ordinal) || + if (!inventoryPanel.Contains("RequestLoadoutTemplateSave(_displayedEntityKey)", StringComparison.Ordinal) || inventoryPanel.Contains("GameManager.RequestLoadoutTemplateSave(_displayedEntity)", StringComparison.Ordinal) || inventoryPanel.Contains("EntityConstructionBlueprintCapture.Capture(_displayedEntity)", StringComparison.Ordinal) || inventoryPanel.Contains("EntityConstructionBlueprintProjector.CaptureBlueprint(_displayedEntity)", StringComparison.Ordinal)) @@ -18621,12 +19077,11 @@ static void RequireInventoryLoadoutRestoreRequestAuthority(string root) "RequestRuntimeLoadoutRestore", "operations => operations.RestoreLoadout", "AetheriaRuntimeStationLoadoutRestoreOption", - "_dropdownStationRefitLoadouts", - "stationRefit?.LoadoutRestoreOptions", + "TryResolveStationRefitLoadout(", + "StationRefitSnapshot()?.LoadoutRestoreOptions", "loadout.TargetEntityKey", "loadout.TemplateName", - "loadout.Price", - "loadout.CanRestore" + "loadout.Price" }; var missingSymbols = requiredSymbols @@ -18641,10 +19096,11 @@ static void RequireInventoryLoadoutRestoreRequestAuthority(string root) } if (actionGameManager.Contains("price > Credits", StringComparison.Ordinal) || - inventoryPanel.Contains("price > Credits", StringComparison.Ordinal)) + inventoryPanel.Contains("price > Credits", StringComparison.Ordinal) || + inventoryPanel.Contains("loadout.CanRestore", StringComparison.Ordinal)) { throw new InvalidOperationException( - "Unity loadout restore still rejects requests through renderer-local credits instead of daemon acceptance."); + "Unity loadout restore still rejects requests through renderer-local credits or availability flags instead of daemon acceptance."); } if (actionGameManager.Contains("ObservedTradeValueSettings()", StringComparison.Ordinal) || @@ -18729,7 +19185,8 @@ static void RequireInventoryLoadoutRestoreRequestAuthority(string root) string.Join("; ", localAcceptanceHits)); } - if (!inventoryPanel.Contains("RequestRuntimeLoadoutRestore(_dropdownStationRefitLoadouts[selection.TemplateIndex])", StringComparison.Ordinal) || + if (!inventoryPanel.Contains("TryResolveStationRefitLoadout(selection.TemplateIndex, out var loadout)", StringComparison.Ordinal) || + !inventoryPanel.Contains("RequestRuntimeLoadoutRestore(loadout)", StringComparison.Ordinal) || inventoryPanel.Contains("GameManager.RequestRuntimeLoadoutRestore", StringComparison.Ordinal)) { throw new InvalidOperationException("InventoryPanel no longer owns typed loadout restore operations directly."); @@ -18870,8 +19327,8 @@ static void RequireDockedCurrentShipRequestAuthority(string root) throw new InvalidOperationException("InventoryPanel no longer owns typed current-ship selection operations directly."); } - if (!inventoryPanel.Contains("TryResolveCurrentEntityKey(out var currentEntityKey)", StringComparison.Ordinal) || - !inventoryPanel.Contains("currentEntityKey = CurrentEntitySnapshot()?.EntityKey ?? \"\"", StringComparison.Ordinal) || + if (!inventoryPanel.Contains("CurrentEntitySnapshot()?.EntityKey ?? \"\"", StringComparison.Ordinal) || + !inventoryPanel.Contains("_displayedEntityKey", StringComparison.Ordinal) || inventoryPanel.Contains("AetheriaClientReactiveDockingState _reactiveDockingState", StringComparison.Ordinal) || inventoryPanel.Contains("GameManager.CurrentEntity", StringComparison.Ordinal)) { diff --git a/Aetheria.State/AetheriaDocumentRegistry.cs b/Aetheria.State/AetheriaDocumentRegistry.cs index c3c1a9303..c685726f7 100644 --- a/Aetheria.State/AetheriaDocumentRegistry.cs +++ b/Aetheria.State/AetheriaDocumentRegistry.cs @@ -63,6 +63,7 @@ public static class AetheriaDocumentRegistry typeof(AetheriaRuntimeDaemonCommandDocument), typeof(AetheriaRuntimeCommittedCommandFactDocument), typeof(AetheriaRuntimeEveCommandDocument), + typeof(AetheriaRuntimeSurfaceDocument), typeof(EveSurfaceState), typeof(EveProviderAdvertisementState) ]; diff --git a/Aetheria.State/docs/cultmesh-cross-runtime-primitives.md b/Aetheria.State/docs/cultmesh-cross-runtime-primitives.md index 553b4e075..5231fea03 100644 --- a/Aetheria.State/docs/cultmesh-cross-runtime-primitives.md +++ b/Aetheria.State/docs/cultmesh-cross-runtime-primitives.md @@ -15,7 +15,7 @@ tooling hosts. This is not a minimalist transport library. CultMesh should grow the shared abstractions that make daemon state feel intimate: typed state pointers, reactive wrappers, query handles, operation handles, native slice descriptors, -authority claims, locality routing, schema-generated facades, UI bindings, +authority claims, locality routing, schema-generated handles, UI bindings, diagnostics, and deterministic math/query primitives. "Primitive" does not only mean a small value type; it means any reusable cross-runtime developer affordance that can be hoisted into the shared library layer. The goal is a @@ -72,12 +72,13 @@ that make that simple. First implementation footholds now exist in CultLib: - `E:/Projects/CultLib/src/GameCult.Mesh/CultMeshPrimitives.cs` defines typed operation handles, query surfaces, state pointers, route hints, authority claims, and native slice descriptors. -- `E:/Projects/CultLib/src/GameCult.Mesh/CultMeshPrimitives.cs` also exposes fluent operation/query context builders, with `CultMesh.OperationContextFor(...)` and `CultMesh.QueryContextFor(...)` facade entrypoints. Application code should read like a typed Verse call, not like manual transport setup. +- `E:/Projects/CultLib/src/GameCult.Mesh/CultMeshPrimitives.cs` also exposes fluent operation/query context builders, with `CultMesh.OperationContextFor(...)` and `CultMesh.QueryContextFor(...)` entrypoints. Application code should read like a typed Verse call, not like manual transport setup. - `E:/Projects/CultLib/src/GameCult.Geometry/CultGeometryPrimitives.cs` defines shared `CultVec2`, `CultVec3`, `CultRect`, `CultCircle`, and `CultSphere` values for query and physics contracts. -- `E:/Projects/CultLib/packages/cultmesh-ts/src/index.ts` now exposes TS `CultMeshVec2`, `CultMeshRect`, `CultMeshViewportRequest`, `CultMeshQuerySurface`, `CultMeshQueryContext`, `CultMeshOperationHandle`, `CultMeshOperationContext`, `CultMeshOperationPayload`, `CultMeshDocumentHandle`, `CultMeshDocumentCatalog`, `CultMeshCollectionHandle`, `CultMeshStatePointer`, `CultMeshStateBindingDescriptor`, `CultMeshOperationBindingDescriptor`, `CultMeshOperationInvocationDescriptor`, native slice descriptors, authority claims, route hints, fluent context builders, and facade helpers like `CultMesh.rectFromBounds(...)`, `CultMesh.viewportRequest(...)`, `CultMesh.query(...)`, `CultMesh.operation(...)`, `CultMesh.document(...)`, `CultMesh.documents(...)`, `CultMesh.collection(...)`, `CultMesh.operationPayload(...)`, `CultMesh.statePointer(...)`, `CultMesh.stateBinding(...)`, `CultMesh.operationBinding(...)`, `CultMesh.operationInvocation(...)`, `CultMesh.nativeSliceView(...)`, `CultMesh.operationContextFor(...)`, and `CultMesh.queryContextFor(...)`. +- `E:/Projects/CultLib/packages/cultmesh-ts/src/index.ts` now exposes TS `CultMeshVec2`, `CultMeshRect`, `CultMeshViewportRequest`, `CultMeshQuerySurface`, `CultMeshQueryContext`, `CultMeshOperationHandle`, `CultMeshOperationContext`, `CultMeshOperationPayload`, `CultMeshDocumentHandle`, `CultMeshDocumentCatalog`, `CultMeshCollectionHandle`, `CultMeshStatePointer`, `CultMeshStateBindingDescriptor`, `CultMeshOperationBindingDescriptor`, `CultMeshOperationInvocationDescriptor`, native slice descriptors, authority claims, route hints, fluent context builders, and handle helpers like `CultMesh.rectFromBounds(...)`, `CultMesh.viewportRequest(...)`, `CultMesh.query(...)`, `CultMesh.querySource(...)`, `CultMesh.operation(...)`, `CultMesh.document(...)`, `CultMesh.documents(...)`, `CultMesh.collection(...)`, `CultMesh.operationPayload(...)`, `CultMesh.statePointer(...)`, `CultMesh.stateBinding(...)`, `CultMesh.operationBinding(...)`, `CultMesh.operationInvocation(...)`, `CultMesh.nativeSliceView(...)`, `CultMesh.operationContextFor(...)`, and `CultMesh.queryContextFor(...)`. - `Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimePlayerSettingsSurfaceBuilder.cs` and `E:/Projects/Eve/packages/org.gamecult.eve.surface/Runtime/EveSurfaceDocument.cs` now expose component `StateBindings` as `IReadOnlyList`. Eve no longer owns a parallel live binding DTO, and Aetheria no longer keeps one in its live runtime surface model. Aetheria only keeps a MessagePack DTO at the persisted document boundary and converts to the shared CultMesh primitive before handing surfaces to renderers. +- `E:/Projects/Eve/packages/org.gamecult.eve.surface/Runtime/EveSurfaceDocument.cs` also exposes component `EmbeddedDocuments` as first-class CultUI slots. Aetheria runtime surfaces mirror that shape with `AetheriaRuntimeEmbeddedDocumentSlot`, so daemon-owned UI can compose nested synced surfaces such as inventory dropdowns without a Unity-only model, facade, or projector. - Aetheria and Eve live command templates now carry `CultMeshOperationBindingDescriptor`. Eve command requests carry `CultMeshOperationInvocationDescriptor` plus `CultMeshOperationPayload`, so renderer click/change events preserve operation id, schema, route hint, idempotency, and scalar field reads as shared CultMesh metadata. Legacy `command`, `label`, `transport`, and string payload fields remain compatibility projections and persisted DTO fields, but the live API no longer exposes raw command-string/dictionary constructors; controls point at typed CultMesh operations and renderers build requests from shared CultMesh primitives. -- `Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeDaemonOperationsClient.cs` is now a typed operation facade that returns `CultMeshOperationReceipt` from semantic verbs such as `SetMoveVector`, `SetTarget`, `DockNearest`, and inventory transfers. The lower-level daemon command envelope remains an internal transport wrapper, but public client code should receive shared CultMesh receipts, not Aetheria-specific command envelopes. +- `Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeDaemonOperationsClient.cs` is now a typed operation handle surface that returns `CultMeshOperationReceipt` from semantic verbs such as `SetMoveVector`, `SetTarget`, `DockNearest`, and inventory transfers. The lower-level daemon command envelope remains an internal transport wrapper, but public client code should receive shared CultMesh receipts, not Aetheria-specific command envelopes. - `Packages/org.gamecult.aetheria.state/Runtime/AetheriaUi.cs` applies the same rule to Eve/CultUI commands. Unity UI and Eve presenter code should call `AetheriaClient.Ui` and receive `CultMeshOperationReceipt`; `AetheriaRuntimeEveCommandEnvelope` remains a bridge/document persistence detail for the Verse command boundary, not the shape application code leans on. - `AetheriaRuntimeEveCommands` is internal now. It may help smoke tests and internal bridge code manufacture persisted command documents, but it is not the public renderer/client API. Public callers use `AetheriaClient.Ui`, and the shared receipt is the public outcome type. - `Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeDaemonRenderQueries.cs` now exposes daemon render gravity/body query overloads around `CultMath.rect` in Aetheria XY space. `AetheriaRuntimeXzRect` remains only as a Unity legacy adapter. @@ -85,6 +86,41 @@ First implementation footholds now exist in CultLib: - `E:/Projects/CultLib/packages/cultcache-ts/src/single-file-messagepack-backing-store.ts` and `E:/Projects/CultLib/packages/cultcache-rs/src/lib.rs` now mirror the same schema-stamped recovery for single-file MessagePack snapshots. Their cache registries resolve recovered schema names and normalize envelopes back to registered public document types, so browser/Electron/Rust clients can share durable Verse state without depending on a stale hot catalog. - `E:/Projects/CultLib/packages/cultcache-py/src/cultcache_py/stores.py`, `E:/Projects/CultLib/packages/cultcache-py/src/cultnet_py/replication.py`, and `E:/Projects/CultLib/packages/cultcache-ts/src/cult-cache-inspector.ts` now carry the same primitive into Python stores, raw CultNet replication helpers, and tooling inspection. A stale schema id should be a recoverable runtime detail when the cold payload is schema-stamped, not a client-visible failure mode. +### Nested CultUI Surface Discovery + +Nested CultUI surfaces are a shared Eve/CultMesh feature, not an Aetheria-only +inventory workaround. Discovery starts in `E:/Projects/Eve/tools/parity/parity-manifest.json`, +which lists the canonical `embedded-surface` fixture and the active runtime +claims for web, Flutter desktop/Linux/Android, iOS/UIKit, Android/Kotlin, Unity +UI Toolkit, and Rust document sync. A renderer that claims CultUI GUI parity must +advertise `embeddedDocuments` there and require the `embedded-surface` fixture. +Every target runtime must be named in that matrix as tested, pending, or +unsupported; an omitted runtime is a broken discovery story, not a harmless +documentation gap. + +Run the shared evidence from the owning repos: + +```powershell +cd E:\Projects\Eve +node --test web\eve-dsl.test.mjs +powershell -ExecutionPolicy Bypass -File .\scripts\run-parity-harness.ps1 + +cd E:\Projects\CultLib\packages\cultnet-rs +cargo test rust_preserves_cultui_embedded_surface_slots_through_typed_document_sync +``` + +When Flutter is installed, also run from `E:/Projects/Eve/flutter/eve_parity`: + +```powershell +flutter test --plain-name embedded_surface_fixture_contract +``` + +Aetheria's local proof stays thin: build the Unity package and run +`Aetheria.State.Verify`. The verifier should prove Aetheria consumes the shared +contract through `AetheriaRuntimeSurfaceDocuments`, `AetheriaRuntimeEmbeddedDocumentSlot`, +and the Eve UI Toolkit lowerer, not through local projectors, facades, or +adapter-shaped child state. + ### Typed Document Handles Current leak: @@ -149,7 +185,7 @@ const objects = await verse .watch(); ``` -Queries are derived-state surfaces, not bespoke HTTP endpoints or local projection helpers. CultMesh should own query identity, parameters, caching, invalidation, locality routing, and remote execution. A co-located daemon can execute directly over slabs; a remote peer can execute through the Verse; a browser can consume the same query shape through generated TS/WASM bindings. The RTS binding generator now emits projection response types, `createAetheriaRuntimeRtsQueryHandles(...)` for every RTS read surface, renderer `aetheria-rts-contract.ts`, `AetheriaRtsIpcChannels`, the preload bridge, and `registerAetheriaRtsIpcHandlers(...)`. Those generated read handles are backed by `CultMesh.projectionRecipe(...).asQuerySurface()` with explicit daemon frame, health, authority, and Starbridge source descriptors. The RTS client passes its local publication route into the generated handles once, then uses automatic query contexts so the recipe owns locality defaults. Because `CultMeshQuerySurface` preserves `sources` and `routeHint`, the RTS client can expose `projectionDiagnostics()` directly from generated handle metadata instead of maintaining a parallel diagnostic table. Electron and browser clients consume generated contracts instead of duplicating query/response shapes, IPC channel names, handler registration, or viewport methods as the primary abstraction. +Queries are derived-state surfaces, not bespoke HTTP endpoints or local helper layers. CultMesh should own query identity, parameters, caching, invalidation, locality routing, and remote execution. A co-located daemon can execute directly over slabs; a remote peer can execute through the Verse; a browser can consume the same query shape through generated TS/WASM bindings. The RTS binding generator now emits response document types, `createAetheriaRuntimeRtsQueryHandles(...)` for every RTS read surface, renderer `aetheria-rts-contract.ts`, `AetheriaRtsIpcChannels`, the preload bridge, and `registerAetheriaRtsIpcHandlers(...)`. Those generated read handles are backed by `CultMesh.query(...)` with explicit daemon frame, health, authority, and Starbridge source descriptors. The RTS client passes its local publication route into the generated handles once, then uses automatic query contexts so the query handle owns locality defaults. Because `CultMeshQuerySurface` preserves `sources` and `routeHint`, the RTS client exposes `queryDiagnostics()` directly from generated handle metadata instead of maintaining a parallel diagnostic table. Electron and browser clients consume generated contracts instead of duplicating query/response shapes, IPC channel names, handler registration, or viewport methods as the primary abstraction. ### Reactive State Pointers @@ -225,7 +261,7 @@ var context = CultMesh.OperationContextFor("unity-raven") ```ts const context = CultMesh.operationContextFor("browser-starfire") .claim("simulation-authority", { shardId: "zone:starfire" }) - .route("wasm", "browser-local projection") + .route("wasm", "browser-local query") .idempotency("move:starfire:1") .build(); ``` @@ -316,7 +352,7 @@ const selected = CultMesh.statePointer( ); ``` -That pointer is still only a primitive, not the final generated Verse facade. +That pointer is still only a primitive, not the final generated Verse handle. The next step is generator-owned state paths, codecs, unavailable-state diagnostics, and UI binding metadata. @@ -328,19 +364,19 @@ Bifrost should consume: inspect typed pointer surfaces, resolve/watch them through the local Verse, and leave record keys and daemon-specific resolver rules below the shared primitive layer. -Those generated pointers are now Verse-bound in the RTS facade as well. The +Those generated pointers are now Verse-bound in the RTS handle surface as well. The Electron client supplies publication-reader resolvers once, generated code binds the pointers with `CultMesh.bindStatePointer(queryVerse, pointer)`, and callers -can resolve the underlying daemon documents through the facade without manually +can resolve the underlying daemon documents through the handle surface without manually constructing query contexts or knowing where the local publication files live. The same rule applies to projected documents and collections. Browser, Electron, Unity, Eve, and tool runtimes should ask CultMesh for a typed document or collection handle, bind it to the Verse once, and then call `latest`, `watch`, `replace`, or `watchChanges`. Local CultCache records, -remote Verse documents, shared-memory projections, and quorum-backed +remote Verse documents, shared-memory queries, and quorum-backed authorities are configuration details behind that handle. Generated Aetheria -RTS facades should target this shape instead of emitting schema dictionaries, +RTS handles should target this shape instead of emitting schema dictionaries, transport-specific lookups, or hand-written state-file readers. ## Aetheria Proofs diff --git a/Aetheria.State/scripts/verify-stage7d-unity-parity.ps1 b/Aetheria.State/scripts/verify-stage7d-unity-parity.ps1 index 51f62fcc5..39bf1707e 100644 --- a/Aetheria.State/scripts/verify-stage7d-unity-parity.ps1 +++ b/Aetheria.State/scripts/verify-stage7d-unity-parity.ps1 @@ -29,10 +29,11 @@ $mainMenu = Read-Text "Assets\Scripts\UI\MainMenu.cs" Assert-Contains $mainMenu "SetRuntimeInputScreenShell(Func canOpenRuntimeInputScreen, Action openRuntimeInputScreen)" "MainMenu must receive runtime input-screen handoff explicitly." Assert-Contains $mainMenu "_canOpenRuntimeInputScreen?.Invoke() == true" "MainMenu must query runtime input-screen availability through injected shell." Assert-Contains $mainMenu "_openRuntimeInputScreen?.Invoke();" "MainMenu must open runtime input screen through injected shell." -Assert-Contains $mainMenu "LatestSectorMap" "MainMenu observed-game boot must read typed sector-map state." -Assert-Contains $mainMenu ".Aetheria()" "MainMenu observed-game boot must enter sector-map state through the semantic Aetheria facade." +Assert-Contains $mainMenu "AetheriaUnityRuntimeClientProvider" "MainMenu observed-game boot must enter state through the Unity runtime client provider." +Assert-Contains $mainMenu ".RuntimeState(stateBoot, ""unity-main-menu"")" "MainMenu observed-game boot must grab typed runtime state." Assert-Contains $mainMenu ".SectorMap" "MainMenu observed-game boot must read the typed sector-map document handle." -Assert-Contains $mainMenu ".LatestAsync()" "MainMenu observed-game boot must read a typed sector-map snapshot." +Assert-Contains $mainMenu ".PlayerSettings" "MainMenu must read player settings through the typed document handle." +Assert-Contains $mainMenu ".VerseHostSettings" "MainMenu must read Verse host settings through the typed document handle." Assert-NotContains $mainMenu ".SectorMapAsync()" "MainMenu observed-game boot must not use the transitional sector-map helper." Assert-NotContains $mainMenu "LatestDaemonFrame" "MainMenu must not read whole daemon frames for observed-game boot." Assert-NotContains $mainMenu "LatestAuthoritativeRunFrameAsync" "MainMenu must not read whole daemon frames for observed-game boot." @@ -40,13 +41,12 @@ Assert-NotContains $mainMenu "ActionGameManager.Instance.ShowInputScreenFromMenu Assert-NotContains $mainMenu "ActionGameManager.Instance.CanShowInputScreenFromMenu" "MainMenu must not query runtime input screen through ActionGameManager.Instance." $mapRenderer = Read-Text "Assets\Scripts\UI\Menu\MapRenderer.cs" -Assert-Contains $mapRenderer ".Aetheria()" "MapRenderer must enter map viewport state through the semantic Aetheria facade." -Assert-Contains $mapRenderer ".Viewports" "MapRenderer must exercise the managed viewport document facade." -Assert-Contains $mapRenderer ".Objects(viewport)" "MapRenderer must read the split objects viewport document handle." -Assert-Contains $mapRenderer ".RenderSplats(viewport)" "MapRenderer must read the split render-splats viewport document handle." -Assert-Contains $mapRenderer ".Settings" "MapRenderer must read player settings through managed typed state." -Assert-Contains $mapRenderer ".Player" "MapRenderer must read player settings through the player settings document handle." -Assert-Contains $mapRenderer ".Latest()" "MapRenderer must read a typed player settings snapshot." +Assert-Contains $mapRenderer "AetheriaUnityRuntimeClientProvider" "MapRenderer must enter state through the Unity runtime client provider." +Assert-Contains $mapRenderer ".MapViewportDocuments(viewport, ""unity-map-renderer"")" "MapRenderer must read managed viewport documents." +Assert-Contains $mapRenderer "CurrentObjectsViewport" "MapRenderer must read the split objects viewport document." +Assert-Contains $mapRenderer "CurrentRenderSplatsViewport" "MapRenderer must read the split render-splats viewport document." +Assert-Contains $mapRenderer "AetheriaUnityRuntimeClientProvider.RuntimeState(""unity-map-renderer"")" "MapRenderer must keep a managed typed state handle." +Assert-Contains $mapRenderer "_runtimeState.PlayerSettings.Latest()" "MapRenderer must read typed player settings through its typed state handle." Assert-NotContains $mapRenderer "public ActionGameManager" "MapRenderer must not regain an ActionGameManager serialized dependency." Assert-NotContains $mapRenderer "LatestAuthoritativeRunFrameAsync" "MapRenderer must not regress to whole-frame reads for map viewport data." Assert-NotContains $mapRenderer "ObjectsViewportAsync" "MapRenderer must not use the transitional objects viewport helper." @@ -55,19 +55,18 @@ Assert-NotContains $mapRenderer "PlayerSettingsAsync" "MapRenderer must not use Assert-NotContains $mapRenderer "ActionGameManager.RuntimePlayerSettings" "MapRenderer must not read manager-global player settings." $sectorRenderer = Read-Text "Assets\Scripts\UI\Menu\SectorRenderer.cs" -Assert-Contains $sectorRenderer "AetheriaClient" "SectorRenderer must keep explicit local Verse client ownership." -Assert-Contains $sectorRenderer ".Aetheria()" "SectorRenderer must enter state through the semantic Aetheria facade." -Assert-Contains $sectorRenderer ".Current" "SectorRenderer must center from the current-state facade." -Assert-Contains $sectorRenderer ".Zone" "SectorRenderer must read the typed current-zone document handle." -Assert-Contains $sectorRenderer ".SectorMap" "SectorRenderer zone details must read the typed sector-map document handle." -Assert-Contains $sectorRenderer ".LatestAsync()" "SectorRenderer must read typed state snapshots." +Assert-Contains $sectorRenderer "AetheriaUnityRuntimeClientProvider.RuntimeState(""unity-sector-renderer"")" "SectorRenderer must keep a managed typed state handle." +Assert-Contains $sectorRenderer "RuntimeState.ZoneDetailsSurface(zoneIndex).Latest()" "SectorRenderer zone details must read the managed zone-details surface document through its typed state handle." +Assert-Contains $sectorRenderer "RuntimeState.CurrentZone.Latest()" "SectorRenderer must read current zone through its typed state handle." +Assert-Contains $sectorRenderer "RenderRuntime(" "SectorRenderer must only render the managed zone-details surface document." Assert-NotContains $sectorRenderer ".CurrentZoneAsync()" "SectorRenderer must not use the transitional current-zone helper." Assert-NotContains $sectorRenderer ".SectorMapAsync()" "SectorRenderer must not use the transitional sector-map helper." -Assert-Contains $sectorRenderer ".Details" "SectorRenderer zone details must read daemon zone facts through managed detail documents." -Assert-Contains $sectorRenderer ".Zone(zoneIndex)" "SectorRenderer zone details must read the typed zone-details document handle." -Assert-Contains $sectorRenderer ".Catalog" "SectorRenderer zone details must read catalog data through managed typed state." -Assert-Contains $sectorRenderer ".Settings" "SectorRenderer zone details must read formatting settings through managed typed state." -Assert-Contains $sectorRenderer ".Player" "SectorRenderer zone details must read the player settings document handle." +Assert-NotContains $sectorRenderer "AetheriaRuntimeZoneDetailsSurfaceBuilder.Build(" "SectorRenderer must not compose zone-details Eve surfaces locally." +Assert-NotContains $sectorRenderer "AetheriaRuntimeZoneDetailsSurfaceBuilder.Facts(" "SectorRenderer must not derive zone-detail display facts locally." +Assert-NotContains $sectorRenderer "CurrentSectorMap(" "SectorRenderer zone details must not read sector-map state directly." +Assert-NotContains $sectorRenderer "CurrentZoneDetails(" "SectorRenderer zone details must not read zone-details state directly." +Assert-NotContains $sectorRenderer "CurrentCatalog(" "SectorRenderer zone details must not read catalog state directly." +Assert-NotContains $sectorRenderer "CurrentPlayerSettings(" "SectorRenderer zone details must not read formatting settings directly." Assert-NotContains $sectorRenderer "ZoneDetailsAsync" "SectorRenderer zone details must not use the transitional zone-details helper." Assert-NotContains $sectorRenderer "OpenRuntimeCatalog" "SectorRenderer zone details must not use the transitional catalog helper." Assert-NotContains $sectorRenderer "PlayerSettingsAsync" "SectorRenderer zone details must not use the transitional player settings helper." @@ -78,33 +77,54 @@ Assert-NotContains $sectorRenderer "ActionGameManager.RuntimePlayerSettings" "Se Assert-NotContains $sectorRenderer "TryGetObservedZoneSnapshot" "SectorRenderer zone details must not ask ActionGameManager for daemon zone snapshots." Assert-NotContains $sectorRenderer "LatestAuthoritativeRunFrameAsync" "SectorRenderer zone details must not read whole daemon frames." +$zoneDetailsSurfaceBuilder = Read-Text "Packages\org.gamecult.aetheria.state\Runtime\AetheriaRuntimeZoneDetailsSurfaceBuilder.cs" +$verseClient = Read-Text "Packages\org.gamecult.aetheria.state\Runtime\AetheriaRuntimeVerseClient.cs" +$clientState = Read-Text "Packages\org.gamecult.aetheria.state\Runtime\AetheriaClientState.cs" +Assert-Contains $zoneDetailsSurfaceBuilder "BuildFromDocuments(" "Zone-details surface composition must live in the shared runtime package." +Assert-Contains $zoneDetailsSurfaceBuilder "AetheriaRuntimeZoneDetailsDocument zoneDetails" "Zone-details surface composition must consume the typed zone-details document." +Assert-Contains $zoneDetailsSurfaceBuilder "AetheriaRuntimeSectorMapDocument sectorMap" "Zone-details surface composition must consume the typed sector-map document." +Assert-Contains $zoneDetailsSurfaceBuilder "AetheriaRuntimeCatalogSnapshot catalog" "Zone-details surface composition must consume the typed catalog document." +Assert-Contains $zoneDetailsSurfaceBuilder "AetheriaRuntimePlayerSettingsDocument playerSettings" "Zone-details surface composition must consume typed player settings." +Assert-Contains $clientState "ZoneDetailsSurface(int zoneIndex)" "AetheriaClientState must expose a managed zone-details surface handle." +Assert-Contains $verseClient "IndexedDocumentId(""aetheria.zone.details.surface"", zoneIndex)" "Verse client must provide the zone-details surface as a managed CultMesh document." +Assert-Contains $verseClient "ZoneDetailsSurfaceAsync(frame, zoneIndex)" "Verse client must derive zone-details surfaces through the managed document handle." +Assert-Contains $verseClient "AetheriaRuntimeZoneDetailsSurfaceBuilder.BuildFromDocuments(" "Verse client must hoist zone-details surface composition out of Unity." +Assert-Contains $verseClient "RequireManagedCatalog()" "Managed zone-details surface must use the typed catalog document." +Assert-Contains $verseClient "RequireManagedPlayerSettings()" "Managed zone-details surface must use typed player settings." + $sectorMap = Read-Text "Assets\Scripts\UI\Menu\SectorMap.cs" -Assert-Contains $sectorMap ".Aetheria()" "SectorMap must enter state through the semantic Aetheria facade." -Assert-Contains $sectorMap ".SectorMap" "SectorMap must render from the typed sector-map document handle." -Assert-Contains $sectorMap ".LatestAsync()" "SectorMap must read a typed sector-map snapshot." +Assert-Contains $sectorMap "AetheriaUnityRuntimeClientProvider" "SectorMap must enter state through the ergonomic Unity runtime provider." +Assert-Contains $sectorMap "AetheriaUnityRuntimeClientProvider.RuntimeState(""unity-sector-map"")" "SectorMap must keep a managed typed state handle." +Assert-Contains $sectorMap "_sectorMap = _runtimeState.SectorMap.Latest()" "SectorMap must render from the managed typed sector-map document through its typed state handle." Assert-NotContains $sectorMap ".SectorMapAsync()" "SectorMap must not use the transitional sector-map helper." +Assert-NotContains $sectorMap ".LatestAsync()" "SectorMap must not walk document handles at the call site." Assert-Contains $sectorMap "Subject" "SectorMap clicks must expose typed zone indices, not Unity GalaxyZone objects." Assert-NotContains $sectorMap "TryGetObservedGalaxy" "SectorMap must not ask ActionGameManager for observed galaxy state." Assert-NotContains $sectorMap "GalaxyZone" "SectorMap must not retain the legacy GalaxyZone graph." Assert-NotContains $sectorMap "Dictionary" "ActionGameManager should delegate Unity scene dependency wiring to a focused helper." -Assert-Contains $actionGameManager "SceneWiring.ConfigureObservedEntityIndex(_observedEntityIndex)" "ActionGameManager should delegate entity-index injection to scene wiring." +Assert-Contains $actionGameManager "SceneWiring.ConfigurePresentationEntityIndex(_presentationEntityIndex)" "ActionGameManager should delegate entity-index injection to scene wiring." Assert-Contains $sceneWiring "public sealed class AetheriaUnityGameplaySceneWiring" "Unity scene wiring should live outside ActionGameManager." -Assert-Contains $sceneWiring "Inventory?.SetObservedEntityIndex(observedEntityIndex)" "Scene wiring should inject entity adaptation into InventoryMenu." +Assert-Contains $sceneWiring "Inventory?.SetPresentationEntityIndex(presentationEntityIndex)" "Scene wiring should inject entity adaptation into InventoryMenu." Assert-NotContains $actionGameManager "AetheriaUnityObservedFacadeIndex" "ActionGameManager must not keep the generic observed facade index." Assert-NotContains $sceneWiring "SetObservedFacadeIndex" "Scene wiring must not inject the generic observed facade index." Assert-NotContains $actionGameManager "public bool TryResolveObservedEntityRecordKey" "ActionGameManager must not expose facade record-key lookup as public manager API." @@ -310,8 +372,8 @@ Assert-NotContains $actionGameManager "public bool TryResolveObservedDockingBayF Assert-Contains $actionGameManager "var boot = GameplayBootShell.Boot();" "ActionGameManager should delegate runtime boot/catalog setup to the gameplay boot shell." Assert-Contains $actionGameManager "SceneWiring.ConfigureCurrentEntityPresentation(_currentEntityPresentation, boot.RuntimeCatalog);" "ActionGameManager should delegate boot catalog injection into current-entity presentation." Assert-Contains $actionGameManager "SceneWiring.ConfigureTargetPresentation(" "ActionGameManager should delegate boot catalog injection into target presentation." -Assert-Contains $actionGameManager "() => ResolveDaemonObserver()?.Client" "ActionGameManager should give target queries the typed local client, not a raw zone snapshot delegate." -Assert-Contains $sceneWiring "observedEntityIndex.TryResolveEntityByDaemonIndex" "Scene wiring should adapt target presentation entities through the observed entity index." +Assert-Contains $sceneWiring "presentation.ResolveZoneContacts = ReadZoneContacts;" "Scene wiring should give target presentation typed zone-contact state, not a raw zone snapshot delegate." +Assert-Contains $sceneWiring "presentationEntityIndex.TryGetPresentationEntityByDaemonIndex" "Scene wiring should adapt target presentation entities through the observed entity index." Assert-Contains $actionGameManager "SceneWiring.ConfigureActionBarPresentation(" "ActionGameManager should delegate boot catalog injection into local action-bar presentation." Assert-NotContains $actionGameManager "private void ConfigureCurrentEntityPresentation(" "ActionGameManager must not own current-entity presentation scene wiring." Assert-NotContains $actionGameManager "private void ConfigureTargetPresentation(" "ActionGameManager must not own target presentation scene wiring." @@ -325,9 +387,9 @@ Assert-Contains $actionGameManager "ItemManager = boot.ItemManager;" "ActionGame Assert-Contains $actionGameManager "_loadoutItemFactory = boot.LoadoutItemFactory;" "ActionGameManager should receive loadout item factory from the gameplay boot shell." Assert-Contains $gameplayBootShell "public sealed class AetheriaUnityGameplayBootShell" "Unity runtime boot/catalog setup should live outside ActionGameManager." Assert-Contains $gameplayBootShell "AetheriaRuntimeStateBoot.Inspect(AetheriaUnityRuntimePaths.GameDataDirectory)" "Gameplay boot shell should inspect the runtime target." -Assert-Contains $gameplayBootShell "AetheriaUnityRuntimeClientProvider.ResolveClient(stateBoot.StateFilePath, stateBoot.RuntimeId)" "Gameplay boot shell should resolve the typed runtime client with the inspected Verse runtime id." +Assert-Contains $gameplayBootShell "AetheriaUnityRuntimeClientProvider.RuntimeState(stateBoot, stateBoot.RuntimeId)" "Gameplay boot shell should resolve managed typed runtime state with the inspected Verse runtime id." Assert-Contains $gameplayBootShell "Aetheria runtime id: {stateBoot.RuntimeId}" "Gameplay boot shell should log the inspected Verse runtime id." -Assert-Contains $gameplayBootShell ".Aetheria()" "Gameplay boot shell should enter managed typed state for runtime catalog." +Assert-Contains $gameplayBootShell "AetheriaUnityRuntimeClientProvider" "Gameplay boot shell should enter managed typed state for runtime catalog." Assert-Contains $gameplayBootShell ".Catalog" "Gameplay boot shell should read the managed runtime catalog document handle." Assert-Contains $gameplayBootShell ".Latest()" "Gameplay boot shell should read the latest typed runtime catalog snapshot." Assert-NotContains $gameplayBootShell ".OpenRuntimeCatalog()" "Gameplay boot shell should not use the transitional runtime catalog helper." @@ -357,30 +419,36 @@ Assert-NotContains $actionGameManager "ResolveClient(stateBoot.StateFilePath).Op Assert-NotContains $actionGameManager "ItemManager = new ItemManager(" "ActionGameManager must not construct ItemManager directly." Assert-NotContains $actionGameManager "ZoneRenderer.SetDroppedPickupItemFactory(" "ActionGameManager must not configure renderer pickup factory binding directly." Assert-NotContains $actionGameManager "ZoneRenderer.BodySettingsCollections = Settings.BodySettingsCollections" "ActionGameManager must not configure renderer body settings directly." -Assert-Contains $observedFrameApplier ".Aetheria()" "Unity observed frame application must enter state through the semantic Aetheria facade." -Assert-Contains $observedFrameApplier ".ZoneRender" "Unity observed frame application must consume the typed zone-render document handle." -Assert-Contains $observedFrameApplier ".LatestAsync()" "Unity observed frame application must read typed zone-render snapshots." +Assert-Contains $daemonRenderDocuments "state.DaemonFrame.Reactive()" "Unity observed frame state must bind daemon frame by grabbing the managed typed document handle directly." +Assert-Contains $daemonRenderDocuments "state.DaemonSoaView.Reactive()" "Unity observed frame state must bind SoA view by grabbing the managed typed document handle directly." +Assert-Contains $daemonRenderDocuments "state.ZoneRender.Reactive()" "Unity observed frame state must bind zone render by grabbing the managed typed document handle directly." +Assert-NotContains $daemonRenderDocuments "state.ReactiveDaemonFrame()" "Unity observed frame state must not use per-document reactive wrapper methods." +Assert-NotContains $daemonRenderDocuments "state.ReactiveDaemonSoaView()" "Unity observed frame state must not use per-document reactive wrapper methods." +Assert-NotContains $daemonRenderDocuments "state.ReactiveZoneRender()" "Unity observed frame state must not use per-document reactive wrapper methods." +Assert-Contains $observedFrameApplier "observer.LastRenderView?.ZoneRender" "Unity observed frame application must consume the managed render view, not reacquire raw daemon state." Assert-NotContains $observedFrameApplier ".ZoneRenderAsync()" "Unity observed frame application must not use the transitional zone-render helper." Assert-NotContains $observedFrameApplier "ProjectZoneRender(" "Unity observed frame application must not rebuild zone-render feeds from raw frames." Assert-NotContains $observedFrameApplier "LastObservedState?.Run" "Unity observed frame application must not read the raw observed run for current-zone lookups." Assert-NotContains $observedFrameApplier "FramePath" "Unity observed frame application should key the presentation cache by typed projection identity, not publication file paths." -Assert-Contains $observedTargetQuery ".Aetheria()" "Unity target queries must enter state through the semantic Aetheria facade." -Assert-Contains $observedTargetQuery ".ZoneContacts" "Unity target queries must consume the typed zone-contacts document handle." -Assert-Contains $observedTargetQuery ".LatestAsync()" "Unity target queries must read typed zone-contacts snapshots." -Assert-NotContains $observedTargetQuery ".ZoneContactsAsync()" "Unity target queries must not use the transitional zone-contacts helper." -Assert-NotContains $observedTargetQuery "AetheriaRuntimeZoneSnapshotCommit" "Unity target queries must not receive raw zone snapshots." -Assert-NotContains $observedTargetQuery "AetheriaRuntimeDaemonRenderQueries.TryQueryEntityContact" "Unity target queries must not query contacts from raw zone snapshots." -Assert-NotContains $observedTargetQuery "AetheriaRuntimeDaemonRenderQueries.TryQueryEntityTarget" "Unity target queries must not query targets from raw zone snapshots." -Assert-Contains $targetPresentation "ResolveVisibleContacts" "Unity target presentation must receive typed contact rows." +Assert-Contains $sceneWiring "private AetheriaClientState _targetPresentationState;" "Unity target presentation wiring must cache the managed CultMesh state handle." +Assert-Contains $sceneWiring "AetheriaUnityRuntimeClientProvider.RuntimeState(""unity-target-presentation"")" "Unity target presentation wiring must grab typed runtime state through the Unity provider." +Assert-Contains $sceneWiring "TargetPresentationState.ZoneContacts.Latest()" "Unity target presentation wiring must consume the typed zone-contacts document handle." +Assert-NotContains $sceneWiring ".CurrentZoneContacts(""unity-target-presentation"")" "Unity target presentation wiring must not use provider snapshot shortcuts at the access site." +Assert-NotContains $targetPresentation "AetheriaRuntimeZoneSnapshotCommit" "Unity target presentation must not receive raw zone snapshots." +Assert-NotContains $targetPresentation "AetheriaRuntimeDaemonRenderQueries.TryQueryEntityContact" "Unity target presentation must not query contacts from raw zone snapshots." +Assert-NotContains $targetPresentation "AetheriaRuntimeDaemonRenderQueries.TryQueryEntityTarget" "Unity target presentation must not query targets from raw zone snapshots." +Assert-Contains $targetPresentation "AetheriaRuntimeZoneContactRow[]" "Unity target presentation must receive typed contact rows." +Assert-Contains $targetPresentation "GetObservedVisibleContacts" "Unity target presentation must derive visible indicators from typed contact rows." Assert-NotContains $targetPresentation "QueryEntityContacts(" "Unity target presentation must not query contacts from raw zone snapshots." -Assert-NotContains $targetPresentation "ResolveZone" "Unity target presentation must not hold raw zone snapshot access." -Assert-Contains $observedDockingIndex ".Aetheria()" "Unity docking state must enter state through the semantic Aetheria facade." -Assert-Contains $observedDockingIndex ".DockingState" "Unity docking state must read the typed CultMesh-backed docking snapshot." -Assert-NotContains $observedDockingIndex ".CurrentDockingAsync()" "Unity docking state must not use the transitional current-docking helper." -Assert-Contains $observedDockingIndex "TryResolveEntityByRecordKey(docking.DockParentEntityKey" "Unity docking facade adaptation must be keyed by typed dock parent identity." -Assert-NotContains $observedDockingIndex "AetheriaRuntimeZoneSnapshotCommit" "Unity docking state must not receive raw zone snapshots." -Assert-NotContains $observedDockingIndex "TryGetParentSnapshot" "Unity docking state must not scan raw snapshot parent relationships." -Assert-NotContains $observedDockingIndex "TryGetEntitySnapshot" "Unity docking state must not scan raw entity snapshots." +Assert-NotContains $targetPresentation "ResolveZoneSnapshot" "Unity target presentation must not hold raw zone snapshot access." +Assert-NotContains $targetPresentation "Func MenuShell.ToggleFullscreenMenu(MenuShell.HelpScreen);" "Gameplay input shell should route the runtime input-screen hotkey through the shared menu shell." -Assert-Contains $gameplayInputShell "Input.Player.TargetNearest.performed += context => PilotOperationAdapter.RequestTargetNearest();" "Gameplay input shell should lower player targeting input through typed pilot operations." +Assert-Contains $gameplayInputShell "Input.Player.TargetNearest.performed += context => PilotOperationController.RequestTargetNearest();" "Gameplay input shell should lower player targeting input through typed pilot operations." +Assert-Contains $pilotOperationController "Submit(operations => operations.TargetNearest(), ""target nearest"");" "Pilot operation controller should lower nearest-target input to typed pilot operations." Assert-Contains $gameplayInputShell "private void RegisterActionBarInput()" "Gameplay input shell should own action-bar control registration." Assert-Contains $gameplayInputShell "DragSession.RegisterTarget(dragAction => ActionBarPresentation.RequestBinding(slot, dragAction));" "Gameplay input shell should wire action-bar drag targets through injected presentation." Assert-NotContains $actionGameManager "private bool CanOpenRuntimeInputScreen()" "ActionGameManager should not own runtime input-screen availability." @@ -416,12 +485,14 @@ Assert-NotContains $actionGameManager "private void RequestInteract()" "ActionGa Assert-NotContains $actionGameManager "private void RequestTowToStation()" "ActionGameManager should not keep gameplay request pass-through wrappers." Assert-NotContains $actionGameManager "_towingStation" "ActionGameManager should not cache stale towing context." Assert-NotContains $actionGameManager "UpdateTowingStation" "ActionGameManager should not receive stale towing context callbacks." -Assert-Contains $actionGameManager "AetheriaUnityObservedRunProjection.FindZone" "ActionGameManager must not own observed galaxy zone lookup logic." -Assert-Contains $actionGameManager "() => AetheriaUnityObservedRunProjection.Galaxy" "ActionGameManager may pass the observed sector projection only into the quarantined zone context adapter." +Assert-Contains $actionGameManager "ObservedZoneContextFactory.ResolveGalaxyZone" "ActionGameManager must delegate observed galaxy zone lookup logic." +Assert-Contains $observedZoneContextFactory "public GalaxyZone ResolveGalaxyZone(int daemonZoneIndex)" "Observed zone context factory must own observed galaxy zone lookup logic." +Assert-Contains $actionGameManager "() => ObservedGalaxy" "ActionGameManager may pass the observed sector projection only into the quarantined zone context adapter." Assert-NotContains $actionGameManager "private static Galaxy ObservedGalaxy" "ActionGameManager must not keep an observed galaxy property." Assert-NotContains $actionGameManager "FindObservedGalaxyZone" "ActionGameManager must not own observed galaxy zone lookup logic." +Assert-NotContains $actionGameManager "private GalaxyZone ResolveObservedGalaxyZone" "ActionGameManager must not own observed galaxy zone lookup logic." Assert-NotContains $actionGameManager "public static void ProjectObservedDaemonRun(" "ActionGameManager must not own daemon run projection." -Assert-Contains $mainMenu "AetheriaUnityObservedRunProjection.Project(" "MainMenu must project daemon run boot data through the explicit observed-run projection holder." +Assert-Contains $mainMenu "ResolveSectorMap(stateBoot)" "MainMenu must boot observed games from the typed sector-map document." Assert-NotContains $actionGameManager "public bool CanShowInputScreenFromMenu()" "ActionGameManager must not expose input-screen availability as public manager API." Assert-NotContains $actionGameManager "public void ShowInputScreenFromMenu()" "ActionGameManager must not expose input-screen opening as public manager API." Assert-NotContains $actionGameManager "public static Galaxy ObservedGalaxy" "ActionGameManager must not expose the observed galaxy as public gameplay state." @@ -443,14 +514,12 @@ Assert-NotContains $actionGameManager "new AetheriaRuntimeDaemonRenderSettings(" Assert-NotContains $actionGameManager "public EntityConstructionBlueprint CreateEntityConstructionBlueprint(AetheriaRuntimeLoadoutTemplateSnapshot template)" "ActionGameManager loadout template lowering must remain private renderer/bootstrap adaptation." Assert-NotContains $actionGameManager "public static RuntimePlayerSettings RuntimePlayerSettings" "ActionGameManager must not expose player settings as manager-global gameplay state." Assert-Contains $actionGameManager "AetheriaUnityRuntimeClientProvider.PlayerSettings" "ActionGameManager should read Unity runtime player settings through the runtime client provider." -Assert-Contains $gameplayBootShell "AetheriaUnityRuntimeClientProvider.ResolveClient(stateBoot.StateFilePath, stateBoot.RuntimeId)" "Gameplay boot shell should open typed catalog through the runtime client provider." +Assert-Contains $gameplayBootShell "AetheriaUnityRuntimeClientProvider.RuntimeState(stateBoot, stateBoot.RuntimeId)" "Gameplay boot shell should open typed state through the runtime client provider." Assert-Contains $runtimeClientProvider "public static class AetheriaUnityRuntimeClientProvider" "Unity runtime client cache should live outside ActionGameManager." -Assert-Contains $runtimeClientProvider "public static RuntimePlayerSettings PlayerSettings =>" "Unity runtime client provider should own player settings loading." -Assert-Contains $runtimeClientProvider "public static AetheriaClient ResolveClient(string stateFilePath, string runtimeId = """")" "Unity runtime client provider should own runtime-id-aware typed client caching." -Assert-Contains $runtimeClientProvider ".Aetheria()" "Unity runtime client provider should read player settings through managed typed state." -Assert-Contains $runtimeClientProvider ".Settings" "Unity runtime client provider should read player settings through the settings facade." -Assert-Contains $runtimeClientProvider ".Player" "Unity runtime client provider should read the player settings document handle." -Assert-Contains $runtimeClientProvider ".Latest()" "Unity runtime client provider should read a typed player settings snapshot." +Assert-Contains $runtimeClientProvider "public static RuntimePlayerSettings PlayerSettings" "Unity runtime client provider should own player settings loading." +Assert-Contains $runtimeClientProvider "private static AetheriaClient ResolveClient(string stateFilePath, string runtimeId = """")" "Unity runtime client provider should own runtime-id-aware typed client caching." +Assert-Contains $runtimeClientProvider ".State" "Unity runtime client provider should read player settings through managed typed state." +Assert-Contains $runtimeClientProvider "RuntimeState().PlayerSettings.Latest()" "Unity runtime client provider should read player settings through the named document handle." Assert-NotContains $runtimeClientProvider "PlayerSettingsAsync()" "Unity runtime client provider must not use the transitional player settings helper." Assert-NotContains $actionGameManager "ResolveRuntimeClient(" "ActionGameManager should not own typed client caching." Assert-NotContains $actionGameManager "PlayerSettingsAsync()" "ActionGameManager should not load player settings directly." @@ -547,11 +616,9 @@ Assert-NotContains $hitscanEffect "ActionGameManager.Instance" "Hitscan effects Assert-NotContains $guidedProjectile "ActionGameManager.Instance" "Guided projectiles must not reach through ActionGameManager.Instance." $client = Read-Text "Packages\org.gamecult.aetheria.state\Runtime\AetheriaClient.cs" -$loadoutProjector = Read-Text "Packages\org.gamecult.aetheria.state\Runtime\AetheriaRuntimeLoadoutSnapshotProjector.cs" -Assert-Contains $loadoutProjector "ProjectLoadoutTemplate(" "Typed loadout projection must live in the shared runtime package." -Assert-Contains $loadoutProjector "AetheriaRuntimeEntitySnapshotCommit entity" "Shared loadout projection must support daemon entity snapshots." -Assert-Contains $loadoutProjector "ProjectLoadoutTemplateAsync(" "Shared loadout projection must compose loadout templates from managed typed state." -Assert-Contains $loadoutProjector "AetheriaClientState state" "Shared loadout projection must accept the managed client state surface." +Assert-Contains $clientState "CultMeshDocumentHandle LoadoutTemplates" "Loadout templates must be a named managed CultMesh document." +Assert-Contains $inventoryPanel "CreateLoadoutTemplate(targetEntityKey ?? """")" "InventoryPanel loadout saves must compose from typed daemon-frame state." +Assert-Contains $inventoryPanel "StationRefitSnapshot()?.LoadoutRestoreOptions" "InventoryPanel loadout restores must use typed station-refit restore options." Assert-NotContains $client "LoadoutTemplateAsync" "AetheriaClient must not expose transitional loadout template projection helpers." $inventoryPanel = Read-Text "Assets\Scripts\UI\Menu\InventoryPanel.cs" @@ -585,8 +652,7 @@ Assert-Contains $stateDocs "ZoneDetails" "Daemon schema catalog must advertise z Assert-Contains $stateDocs "ZoneRender" "Daemon schema catalog must advertise zone render feed." $catalogStore = Read-Text "Packages\org.gamecult.aetheria.state\Runtime\AetheriaRuntimeCatalogStore.cs" -Assert-Contains $catalogStore "ProjectTradeValuePolicySurfaceDocument" "Catalog store must expose typed trade-value policy as a designer Eve surface." -Assert-Contains $catalogStore "AetheriaRuntimeTradeValuePolicySurfaceBuilder.Build" "Catalog store must build trade-value policy through the shared Eve surface builder." +Assert-NotContains $catalogStore "ProjectTradeValuePolicySurfaceDocument" "Catalog store must not own trade-value policy surface projection." $tradeValuePolicySurface = Read-Text "Packages\org.gamecult.aetheria.state\Runtime\AetheriaRuntimeTradeValuePolicySurfaceBuilder.cs" Assert-Contains $tradeValuePolicySurface "SurfaceId = ""aetheria.tradeValuePolicy""" "Trade-value policy surface must have a stable designer surface id." @@ -606,14 +672,15 @@ Assert-Contains $eveCommandClient "ReadTradeValuePolicyBody" "Eve command client $eveCommandBridge = Read-Text "Aetheria.State\AetheriaEveCommandBridge.cs" Assert-Contains $eveCommandBridge "ExecuteTradeValuePolicyCommandAsync" "Eve command bridge must persist typed trade-value policy edits." -Assert-Contains $eveCommandBridge "PutTradeValuePolicyAsync" "Trade-value policy edits must write the typed policy document." +Assert-Contains $eveCommandBridge "MutableDocument(AetheriaStateNode.TradeValuePolicyKey)" "Trade-value policy edits must write the typed policy document." +Assert-Contains $eveCommandBridge "tradeValuePolicy.ReplaceAsync(policy)" "Trade-value policy edits must persist the typed policy document." $tickRunner = Read-Text "Packages\org.gamecult.aetheria.state\Runtime\AetheriaRuntimeDaemonTickRunner.cs" -Assert-Contains $tickRunner "ProjectTradeValuePolicySurfaceDocument(stateFilePath)" "Daemon editor surface must publish the trade-value policy designer surface." +Assert-Contains $tickRunner "AetheriaRuntimeTradeValuePolicySurfaceBuilder.BuildFromCatalog(catalog)" "Daemon editor surface must publish the trade-value policy designer surface." $client = Read-Text "Packages\org.gamecult.aetheria.state\Runtime\AetheriaClient.cs" -Assert-Contains $client "AetheriaClientState State" "AetheriaClient must expose the semantic typed state facade." -Assert-Contains $client "Aetheria()" "AetheriaClient must expose the canonical domain facade entrypoint." +Assert-Contains $client "AetheriaClientState State" "AetheriaClient must expose the managed typed state object." +Assert-NotContains $client "Aetheria() => State" "AetheriaClient must not keep an Aetheria() alias over State." Assert-Contains $client "AetheriaControl Control" "AetheriaClient must expose command controls separately from managed state documents." Assert-Contains $client "AetheriaUi Ui" "AetheriaClient must expose UI command submission separately from managed state documents." Assert-NotContains $client "Document()" "AetheriaClient must not forward typed document lookup; use AetheriaClientState." @@ -623,42 +690,43 @@ Assert-NotContains $client "Watch()" "AetheriaClient must not forward Assert-NotContains $client "ReactiveAsync" "AetheriaClient must not forward reactive document creation; use AetheriaClientState." $verseClient = Read-Text "Packages\org.gamecult.aetheria.state\Runtime\AetheriaRuntimeVerseClient.cs" -Assert-Contains $verseClient "AetheriaClientState Aetheria()" "AetheriaRuntimeVerseClient must expose the shared domain state facade." -Assert-Contains $verseClient "CreateAetheriaStateFacade" "AetheriaRuntimeVerseClient must own projected state facade creation." +Assert-Contains $verseClient "AetheriaClientState Aetheria()" "AetheriaRuntimeVerseClient must expose the managed typed state root for low-level bootstrap." +Assert-Contains $verseClient "private AetheriaClientState CreateAetheriaState()" "AetheriaRuntimeVerseClient must own managed state creation." $clientState = Read-Text "Packages\org.gamecult.aetheria.state\Runtime\AetheriaClientState.cs" -Assert-Contains $clientState "CultMeshDocumentHandle" "Aetheria state documents must expose CultMesh typed document handles directly." -Assert-Contains $clientState "CultMeshDocumentCatalog" "Aetheria state facade must delegate type/schema lookup to CultMesh." -Assert-Contains $clientState "CultMesh.Documents" "Aetheria state facade must build its projected document catalog through CultMesh." -Assert-Contains $clientState "ICultMeshDocumentHandle" "Aetheria schema lookup must expose CultMesh schema metadata independent of exact CLR type." -Assert-NotContains $clientState "AetheriaRuntimeProjectedDocument" "Aetheria state facade must not wrap CultMesh document handles in a private projected-document abstraction." -Assert-NotContains $clientState "_documentsByType" "Aetheria state facade must not maintain a private type index beside CultMeshDocumentCatalog." -Assert-Contains $clientState "TryGetDocument" "Aetheria state facade must support safe typed document lookup." -Assert-Contains $clientState "TryGetDocumentBySchema" "Aetheria state facade must support safe schema document lookup." -Assert-Contains $clientState "Document()" "Aetheria state facade must support direct typed document lookup." -Assert-Contains $clientState "DocumentBySchema(string schemaVersion)" "Aetheria state facade must support direct schema document lookup." -Assert-NotContains $clientState "CultDocumentMessagePackSerialization" "Aetheria projected document aliasing must be owned by CultMesh, not the Aetheria facade." -Assert-Contains $clientState "LatestAsync()" "Aetheria state facade must support one-call typed document reads." -Assert-Contains $clientState "Watch()" "Aetheria state facade must support reactive typed document lookup." +Assert-Contains $clientState "CultMeshDocumentHandle<" "Aetheria state documents must expose CultMesh typed document handles directly." +Assert-Contains $clientState "public CultMeshDocumentHandle DaemonFrame" "Aetheria state must expose named managed typed document handles." +Assert-Contains $clientState "public CultMeshDocumentHandle Catalog" "Aetheria state must expose named managed typed document handles." +Assert-Contains $clientState "public CultMeshDocumentHandle CurrentEntity" "Aetheria state must expose named managed typed document handles." +Assert-Contains $clientState "public CultMeshDocumentHandle Inventory(" "Aetheria state must expose parameterized typed document handles." +Assert-NotContains $clientState "AetheriaRuntimeProjectedDocument" "Aetheria state must not wrap CultMesh document handles in a private projected-document abstraction." +Assert-NotContains $clientState "_documentsByType" "Aetheria state must not maintain a private type index beside named handles." +Assert-NotContains $clientState "TryGetDocument" "Aetheria state must not expose generic document lookup beside named handles." +Assert-NotContains $clientState "TryGetDocumentBySchema" "Aetheria state must not expose schema-string lookup beside named handles." +Assert-NotContains $clientState "Document()" "Aetheria state must not expose generic document lookup beside named handles." +Assert-NotContains $clientState "DocumentBySchema(" "Aetheria state must not expose schema-string lookup beside named handles." +Assert-NotContains $clientState "CultMeshDocumentCatalog" "Aetheria state must not maintain a generic document catalog beside named handles." +Assert-NotContains $clientState "LatestAsync()" "Aetheria state must not expose generic one-call reads beside named handles." +Assert-NotContains $clientState "Watch()" "Aetheria state must not expose generic watch shortcuts beside named handles." -$projection = Read-Text "Packages\org.gamecult.aetheria.state\Runtime\AetheriaRuntimeRtsProjection.cs" -Assert-Contains $projection "ProjectZoneDetails" "Shared projection code must own the zone-details feed." -Assert-Contains $projection "ProjectZoneContacts" "Shared projection code must own the zone-contacts feed." -Assert-Contains $projection "ProjectZoneTargetRow" "Zone-contacts projection must carry target geometry rows." -Assert-Contains $projection "Distance = Math.Sqrt(deltaX * deltaX + deltaZ * deltaZ)" "Zone-contacts projection must carry target/contact distances." -Assert-Contains $projection "ProjectZoneRender" "Shared projection code must own the zone-render feed." -Assert-Contains $projection "ProjectCurrentEntity" "Shared projection code must own the current-entity feed." -Assert-Contains $projection "ProjectCurrentDocking" "Shared projection code must own the current-docking feed." -Assert-Contains $projection "ProjectStationRefit" "Shared projection code must own the station-refit feed." -Assert-Contains $projection "Credits = context.Run.Credits" "Station-refit projection must carry typed run credits." -Assert-Contains $projection "ProjectStationStock" "Station-refit projection must carry typed station stock." -Assert-Contains $projection "CargoItems = ProjectStationStock(entity)" "Station-refit entity options must carry typed cargo rows for target counts." -Assert-Contains $projection "ProjectStationStockTradeFacts" "Station-refit projection must enrich station stock with trade facts." -Assert-Contains $projection "OwnedQuantity = CountStationOwnedQuantity" "Station-refit projection must publish owned counts for trade rows." -Assert-Contains $projection "CanAfford = price >= 0 && credits >= price" "Station-refit projection must publish affordability for trade rows." -Assert-Contains $projection "SourceIndex = sourceIndex" "Inventory projections must carry source slot identity for cargo/equipment operations." -Assert-Contains $projection "X = slot.X" "Inventory projections must carry item grid X for cargo/equipment operations." -Assert-Contains $projection "Y = slot.Y" "Inventory projections must carry item grid Y for cargo/equipment operations." +$rtsDocuments = Read-Text "Packages\org.gamecult.aetheria.state\Runtime\AetheriaRuntimeRtsDocuments.cs" +Assert-Contains $rtsDocuments "ZoneDetails(" "Shared document code must own the zone-details feed." +Assert-Contains $rtsDocuments "ZoneContacts(" "Shared document code must own the zone-contacts feed." +Assert-Contains $rtsDocuments "ToZoneTargetRow" "Zone-contacts document must carry target geometry rows." +Assert-Contains $rtsDocuments "Distance = Math.Sqrt(deltaX * deltaX + deltaZ * deltaZ)" "Zone-contacts document must carry target/contact distances." +Assert-Contains $rtsDocuments "ZoneRender(" "Shared document code must own the zone-render feed." +Assert-Contains $rtsDocuments "CurrentEntity(" "Shared document code must own the current-entity feed." +Assert-Contains $rtsDocuments "CurrentDocking(" "Shared document code must own the current-docking feed." +Assert-Contains $rtsDocuments "StationRefit(" "Shared document code must own the station-refit feed." +Assert-Contains $rtsDocuments "Credits = context.Run.Credits" "Station-refit document must carry typed run credits." +Assert-Contains $rtsDocuments "StationStock(" "Station-refit document must carry typed station stock." +Assert-Contains $rtsDocuments "CargoItems = StationStock(entity)" "Station-refit entity options must carry typed cargo rows for target counts." +Assert-Contains $rtsDocuments "StationStockTradeFacts(" "Station-refit document must enrich station stock with trade facts." +Assert-Contains $rtsDocuments "OwnedQuantity = CountStationOwnedQuantity" "Station-refit document must publish owned counts for trade rows." +Assert-Contains $rtsDocuments "CanAfford = price >= 0 && credits >= price" "Station-refit document must publish affordability for trade rows." +Assert-Contains $rtsDocuments "SourceIndex = sourceIndex" "Inventory documents must carry source slot identity for cargo/equipment operations." +Assert-Contains $rtsDocuments "X = slot.X" "Inventory documents must carry item grid X for cargo/equipment operations." +Assert-Contains $rtsDocuments "Y = slot.Y" "Inventory documents must carry item grid Y for cargo/equipment operations." $zoneRenderDocs = Read-Text "Packages\org.gamecult.aetheria.state\Runtime\AetheriaRuntimeRtsViewportDocuments.cs" Assert-Contains $zoneRenderDocs "public int SourceIndex" "Inventory item projection documents must carry source index." @@ -689,6 +757,7 @@ Assert-NotContains $zoneRenderDocs "public AetheriaRuntimeRunCheckpointCommit Ru Assert-NotContains $zoneRenderDocs "public AetheriaRuntimeZoneSnapshotCommit Zone" "Zone-render feed must not expose the whole daemon zone snapshot." $zoneRenderer = Read-Text "Assets\Scripts\Zone Display\ZoneRenderer.cs" +$rtsViewportDocuments = Read-Text "Assets\Scripts\Gameplay\AetheriaUnityRtsViewportDocuments.cs" Assert-Contains $zoneRenderer "ApplyZoneRender" "ZoneRenderer must consume the typed zone-render feed." Assert-Contains $zoneRenderer "_daemonCurrentEntityKey" "ZoneRenderer must track current entity as a render-feed fact, not through a run snapshot." Assert-Contains $zoneRenderer "_daemonSimulationTimeSeconds" "ZoneRenderer must track simulation time as a render-feed fact." @@ -696,14 +765,19 @@ Assert-Contains $zoneRenderer "_zoneRenderBodyPoses" "ZoneRenderer must consume Assert-Contains $zoneRenderer "_zoneRenderAsteroidBeltPoses" "ZoneRenderer must consume asteroid belt poses from the zone-render feed." Assert-Contains $zoneRenderer "beltPose.InstancePoses" "ZoneRenderer asteroid rendering must consume typed asteroid instance poses from the zone-render feed." Assert-Contains $zoneRenderer "RestoreDroppedPickupsFromZoneRender" "ZoneRenderer dropped-pickup restore must consume the typed zone-render feed." -Assert-Contains $zoneRenderer ".Aetheria()" "ZoneRenderer target distance cache must enter state through the semantic Aetheria facade." -Assert-Contains $zoneRenderer ".ZoneContacts" "ZoneRenderer target distance cache must read the typed zone-contacts document handle." -Assert-Contains $zoneRenderer ".LatestAsync()" "ZoneRenderer target distance cache must read a typed zone-contacts snapshot." +Assert-Contains $zoneRenderer "AetheriaUnityRuntimeClientProvider" "ZoneRenderer target distance cache must enter state through managed typed documents." +Assert-Contains $zoneRenderer ".ZonePresentationDocuments(" "ZoneRenderer target distance cache must bind managed presentation documents." +Assert-Contains $zoneRenderer "CurrentContacts" "ZoneRenderer target distance cache must read the typed zone-contacts document from the presentation bundle." +Assert-Contains $rtsViewportDocuments "state.ZoneContacts.Reactive()" "Zone presentation documents must bind zone contacts by grabbing the managed typed document handle directly." +Assert-NotContains $rtsViewportDocuments "state.ReactiveZoneContacts()" "Zone presentation documents must not use per-document reactive wrapper methods." Assert-NotContains $zoneRenderer ".ZoneContactsAsync()" "ZoneRenderer target distance cache must not use the transitional zone-contacts helper." Assert-Contains $zoneRenderer "_daemonTargetRowsByEntityIndex" "ZoneRenderer target distance cache must store typed target rows." Assert-Contains $zoneRenderer "_daemonContactRows" "ZoneRenderer compass and visibility state must cache typed contact rows." -Assert-Contains $zoneRenderer ".Viewports" "ZoneRenderer presentation loading must use managed viewport document handles." -Assert-Contains $zoneRenderer ".Objects(ToViewportBounds(viewport))" "ZoneRenderer presentation loading must use the typed objects viewport document handle." +Assert-Contains $zoneRenderer ".ZonePresentationDocuments(viewportBounds, ""unity-zone-renderer"")" "ZoneRenderer presentation loading must use managed viewport document handles." +Assert-Contains $rtsViewportDocuments "state.ObjectsViewport(viewport).Reactive()" "Zone presentation documents must use the typed objects viewport document handle directly." +Assert-Contains $rtsViewportDocuments "state.RenderSplatsViewport(viewport).Reactive()" "Zone presentation documents must use the typed render-splats viewport document handle directly." +Assert-NotContains $rtsViewportDocuments "state.ReactiveObjectsViewport(viewport)" "Zone presentation documents must not use per-document reactive wrapper methods." +Assert-NotContains $rtsViewportDocuments "state.ReactiveRenderSplatsViewport(viewport)" "Zone presentation documents must not use per-document reactive wrapper methods." Assert-NotContains $zoneRenderer "ObjectsViewportAsync(ToViewportBounds(viewport))" "ZoneRenderer presentation loading must not use the transitional objects viewport helper." Assert-NotContains $zoneRenderer "GravityViewportAsync(viewport)" "ZoneRenderer body view loading must not use the transitional gravity viewport helper." Assert-Contains $zoneRenderDocs "public AetheriaRuntimeBodySnapshotCommit Body" "Gravity/body viewport rows must carry the typed body payload needed for Unity presentation lowering." @@ -731,7 +805,7 @@ $actionGameManager = Read-Text "Assets\Scripts\Gameplay\ActionGameManager.cs" $observedFrameApplier = Read-Text "Assets\Scripts\Gameplay\AetheriaUnityObservedFrameApplier.cs" $gameplayLoopShell = Read-Text "Assets\Scripts\Gameplay\AetheriaUnityGameplayLoopShell.cs" Assert-Contains $actionGameManager "ObservedFrameApplier.ApplyLatestZoneRender()" "ActionGameManager must delegate typed zone-render application to the observed frame applier." -Assert-Contains $actionGameManager ".Aetheria()" "ActionGameManager current-entity binding must enter zone-render state through the semantic Aetheria facade." +Assert-Contains $actionGameManager "AetheriaUnityRuntimeClientProvider" "ActionGameManager current-entity binding must enter zone-render state through managed typed documents." Assert-Contains $actionGameManager ".ZoneRender" "ActionGameManager current-entity binding must resolve the typed zone-render document handle." Assert-NotContains $actionGameManager ".ZoneRenderAsync()" "ActionGameManager current-entity binding must not use the transitional zone-render helper." Assert-Contains $actionGameManager "private AetheriaUnityGameplayLoopShell GameplayLoopShell =>" "ActionGameManager should delegate per-frame Unity presentation/input ticking to the gameplay loop shell." @@ -740,17 +814,17 @@ Assert-Contains $actionGameManager "GameplayLoopShell.LateTick()" "ActionGameMan Assert-Contains $gameplayLoopShell "public sealed class AetheriaUnityGameplayLoopShell" "Unity gameplay loop presentation ticking should live outside ActionGameManager." Assert-Contains $gameplayLoopShell "ApplyLatestZoneRender?.Invoke();" "Gameplay loop shell should apply the latest typed zone-render feed." Assert-Contains $gameplayLoopShell "CurrentEntityPresentation?.Tick(deltaTime);" "Gameplay loop shell should tick current-entity presentation." -Assert-Contains $gameplayLoopShell "PilotFrameAdapter?.Tick(ResolveCurrentEntity?.Invoke(), deltaTime, time);" "Gameplay loop shell should tick pilot frame presentation/input." +Assert-Contains $gameplayLoopShell "PilotFrameController?.Tick(ResolveCurrentEntity?.Invoke(), deltaTime, time);" "Gameplay loop shell should tick pilot frame presentation/input." Assert-Contains $gameplayLoopShell "TargetPresentation?.UpdateTargetIndicators(" "Gameplay loop shell should tick target indicators." Assert-NotContains $actionGameManager "_currentEntityPresentation.Tick(Time.deltaTime);" "ActionGameManager must not tick current-entity presentation directly." -Assert-NotContains $actionGameManager "PilotFrameAdapter.Tick(CurrentEntity, Time.deltaTime, Time.time)" "ActionGameManager must not tick pilot frame presentation/input directly." +Assert-NotContains $actionGameManager "PilotFrameController.Tick(CurrentEntity, Time.deltaTime, Time.time)" "ActionGameManager must not tick pilot frame presentation/input directly." Assert-NotContains $actionGameManager "_targetPresentation.UpdateTargetIndicators(" "ActionGameManager must not tick target indicators directly." Assert-NotContains $actionGameManager "_paused" "ActionGameManager must not own pause state; the loop shell owns it." Assert-NotContains $actionGameManager "_time" "ActionGameManager must not keep unused local simulation time." Assert-Contains $observedFrameApplier "TryRestoreEntityGraphFromZoneRender" "Observed frame applier must restore Unity shell state from the typed zone-render feed." Assert-Contains $observedFrameApplier "ApplyLatestZoneRender" "Observed frame applier must name the handoff as zone-render application." Assert-Contains $observedFrameApplier ".ZoneRender" "Observed frame applier must use the shared zone-render document handle at the frame handoff." -Assert-Contains $observedFrameApplier "render.EntitySnapshots" "Observed frame applier entity restoration must consume the typed zone-render entity snapshot payload." +Assert-Contains $observedFrameApplier "render.CreateEntitySnapshots()" "Observed frame applier entity restoration must consume the typed zone-render entity snapshot payload." Assert-NotContains $observedFrameApplier "Func" "Observed frame applier must not depend on the whole observed galaxy facade." Assert-Contains $observedFrameApplier "_resolveObservedZone" "Observed frame applier should name the narrow target-zone resolver explicitly." Assert-NotContains $observedFrameApplier "resolveObservedGalaxy" "Observed frame applier must resolve only the target zone needed for Unity shell adaptation." diff --git a/Aetheria.sln b/Aetheria.sln index 7e9eba219..8a6b426c0 100644 --- a/Aetheria.sln +++ b/Aetheria.sln @@ -5,12 +5,12 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Assembly-CSharp-firstpass", EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Assembly-CSharp", "Assembly-CSharp.csproj", "{47728ed0-fa53-fa99-9dca-8eb78b1dbfe4}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GameCult.Aetheria.State.Unity", "GameCult.Aetheria.State.Unity.csproj", "{d6094cfd-26d1-83d1-2366-5762cca2bc33}" -EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UniRx", "UniRx.csproj", "{99486114-c78b-e9b0-654f-710398497645}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GameCult.Aetheria.EveRuntime", "GameCult.Aetheria.EveRuntime.csproj", "{a1a8b65b-ca79-2fa2-3803-5f4833778bdb}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GameCult.Aetheria.State.Unity", "GameCult.Aetheria.State.Unity.csproj", "{d6094cfd-26d1-83d1-2366-5762cca2bc33}" +EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Aetheria.Shared.Unity", "Aetheria.Shared.Unity.csproj", "{31542707-f1f7-33c1-eef6-37982be14f82}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Assembly-CSharp-Editor-firstpass", "Assembly-CSharp-Editor-firstpass.csproj", "{b690125b-09ac-e423-4873-1648ff205166}" @@ -42,12 +42,12 @@ Global {7c9c511c-1c6f-9c1f-c455-88359f498803}.Debug|Any CPU.Build.0 = Debug|Any CPU {47728ed0-fa53-fa99-9dca-8eb78b1dbfe4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {47728ed0-fa53-fa99-9dca-8eb78b1dbfe4}.Debug|Any CPU.Build.0 = Debug|Any CPU - {d6094cfd-26d1-83d1-2366-5762cca2bc33}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {d6094cfd-26d1-83d1-2366-5762cca2bc33}.Debug|Any CPU.Build.0 = Debug|Any CPU {99486114-c78b-e9b0-654f-710398497645}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {99486114-c78b-e9b0-654f-710398497645}.Debug|Any CPU.Build.0 = Debug|Any CPU {a1a8b65b-ca79-2fa2-3803-5f4833778bdb}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {a1a8b65b-ca79-2fa2-3803-5f4833778bdb}.Debug|Any CPU.Build.0 = Debug|Any CPU + {d6094cfd-26d1-83d1-2366-5762cca2bc33}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {d6094cfd-26d1-83d1-2366-5762cca2bc33}.Debug|Any CPU.Build.0 = Debug|Any CPU {31542707-f1f7-33c1-eef6-37982be14f82}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {31542707-f1f7-33c1-eef6-37982be14f82}.Debug|Any CPU.Build.0 = Debug|Any CPU {b690125b-09ac-e423-4873-1648ff205166}.Debug|Any CPU.ActiveCfg = Debug|Any CPU diff --git a/Assets/Editor/AetheriaEditorPlayModeTrigger.cs b/Assets/Editor/AetheriaEditorPlayModeTrigger.cs new file mode 100644 index 000000000..acd7f9d24 --- /dev/null +++ b/Assets/Editor/AetheriaEditorPlayModeTrigger.cs @@ -0,0 +1,93 @@ +using System.IO; +using UnityEditor; +using UnityEngine; + +[InitializeOnLoad] +public static class AetheriaEditorPlayModeTrigger +{ + private const string FlagPath = "Temp/request-playmode.flag"; + private const string ScreenshotFlagPath = "Temp/request-screenshot.flag"; + private const string ScreenshotPath = "Temp/aetheria-game-view-capture.png"; + private const string ScreenshotStatusPath = "Temp/aetheria-game-view-capture-status.txt"; + private const string EnsureUrpFlagPath = "Temp/request-ensure-urp.flag"; + + static AetheriaEditorPlayModeTrigger() + { + EditorApplication.update += Poll; + } + + private static void Poll() + { + if (TryConsumeFlag(FlagPath)) + { + Debug.Log("Aetheria editor playmode trigger consumed Temp/request-playmode.flag."); + RequestPlayMode(); + } + + if (TryConsumeFlag(ScreenshotFlagPath)) + { + EditorApplication.delayCall += CaptureScreenshot; + } + + if (TryConsumeFlag(EnsureUrpFlagPath)) + { + Debug.Log("Aetheria editor URP bootstrap trigger consumed Temp/request-ensure-urp.flag."); + EditorApplication.delayCall += AetheriaUrpBootstrap.EnsureUrpAssetsAndAssign; + } + } + + private static bool TryConsumeFlag(string path) + { + try + { + if (!File.Exists(path)) + return false; + + File.Delete(path); + return true; + } + catch (IOException) + { + return false; + } + } + + private static void RequestPlayMode() + { + if (EditorApplication.isCompiling || EditorApplication.isUpdating) + { + EditorApplication.delayCall += RequestPlayMode; + return; + } + + if (!EditorApplication.isPlayingOrWillChangePlaymode) + EditorApplication.isPlaying = true; + } + + private static void CaptureScreenshot() + { + File.WriteAllText( + ScreenshotStatusPath, + $"CaptureScreenshot invoked. compiling={EditorApplication.isCompiling} updating={EditorApplication.isUpdating} playing={EditorApplication.isPlaying}"); + + if (EditorApplication.isCompiling || EditorApplication.isUpdating) + { + EditorApplication.delayCall += CaptureScreenshot; + return; + } + + if (!EditorApplication.isPlaying) + { + RequestPlayMode(); + EditorApplication.delayCall += CaptureScreenshot; + return; + } + + Directory.CreateDirectory(Path.GetDirectoryName(ScreenshotPath)); + ScreenCapture.CaptureScreenshot(ScreenshotPath); + File.WriteAllText( + ScreenshotStatusPath, + $"CaptureScreenshot requested {ScreenshotPath} at {System.DateTime.UtcNow:O}"); + Debug.Log($"Aetheria editor screenshot trigger captured {ScreenshotPath}."); + } +} diff --git a/Assets/Editor/AetheriaEditorPlayModeTrigger.cs.meta b/Assets/Editor/AetheriaEditorPlayModeTrigger.cs.meta new file mode 100644 index 000000000..b1720711d --- /dev/null +++ b/Assets/Editor/AetheriaEditorPlayModeTrigger.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 74234565bd344818b6152d034d86f937 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Editor/AetheriaRenderProbe.cs b/Assets/Editor/AetheriaRenderProbe.cs new file mode 100644 index 000000000..cf7269d3c --- /dev/null +++ b/Assets/Editor/AetheriaRenderProbe.cs @@ -0,0 +1,296 @@ +using System; +using System.IO; +using System.Text; +using GameCult.Aetheria.State.Verse; +using UnityEditor; +using UnityEngine; +using UnityEngine.SceneManagement; + +[InitializeOnLoad] +public static class AetheriaRenderProbe +{ + private const string RelativeOutputDirectory = "Temp/AetheriaRenderProbe"; + + private static string ProjectRoot => + Path.GetFullPath(Path.Combine(Application.dataPath, "..")); + + private static string OutputDirectory => + Path.Combine(ProjectRoot, RelativeOutputDirectory); + + private static string RequestPath => + Path.Combine(OutputDirectory, "request.txt"); + + static AetheriaRenderProbe() + { + EditorApplication.update += Poll; + } + + private static void Poll() + { + if (!File.Exists(RequestPath)) + return; + + if (EditorApplication.isCompiling || EditorApplication.isUpdating) + return; + + File.Delete(RequestPath); + EditorApplication.delayCall += CaptureRequested; + } + + private static void CaptureRequested() + { + try + { + Capture(); + } + catch (Exception error) + { + Directory.CreateDirectory(OutputDirectory); + File.WriteAllText( + Path.Combine(OutputDirectory, "error.txt"), + error.ToString()); + Debug.LogException(error); + } + } + + [MenuItem("Aetheria/Rendering/Capture Render Probe")] + public static void Capture() + { + Directory.CreateDirectory(OutputDirectory); + + var summary = new StringBuilder(); + summary.AppendLine($"CapturedAtUtc: {DateTime.UtcNow:O}"); + summary.AppendLine($"ActiveScene: {SceneManager.GetActiveScene().path}"); + summary.AppendLine($"Playing: {EditorApplication.isPlaying}"); + summary.AppendLine(); + + var camera = ResolveCamera(); + summary.AppendLine(camera != null + ? $"Camera: {camera.name} orthographic={camera.orthographic} size={camera.orthographicSize} pixel={camera.pixelWidth}x{camera.pixelHeight}" + : "Camera: "); + + if (camera != null) + VolumeSampling.RenderForCamera(camera); + + DumpVolumeSamplers(summary); + DumpVolumeCloudRenderers(camera, summary); + DumpGlobalTexture(summary, "_NebulaSurfaceHeight", "nebula-surface-height.png"); + DumpGlobalTexture(summary, "_NebulaPatchHeight", "nebula-patch-height.png"); + DumpGlobalTexture(summary, "_NebulaPatch", "nebula-patch.png"); + DumpGlobalTexture(summary, "_NebulaTint", "nebula-tint.png"); + DumpGlobalTexture(summary, "_AetheriaGravityHeight", "gravity-height.png"); + + if (camera != null) + DumpCamera(camera, summary); + + File.WriteAllText(Path.Combine(OutputDirectory, "summary.txt"), summary.ToString()); + AssetDatabase.Refresh(); + Debug.Log($"Aetheria render probe wrote {Path.GetFullPath(OutputDirectory)}"); + } + + private static Camera ResolveCamera() + { + if (Camera.main != null) + return Camera.main; + + foreach (var camera in Resources.FindObjectsOfTypeAll()) + { + if (camera == null || + !camera.gameObject.scene.IsValid() || + !camera.gameObject.scene.isLoaded || + !camera.enabled) + { + continue; + } + + return camera; + } + + return null; + } + + private static void DumpVolumeCloudRenderers(Camera camera, StringBuilder summary) + { + summary.AppendLine(); + summary.AppendLine("VolumeCloudRenderers:"); + + foreach (var renderer in Resources.FindObjectsOfTypeAll()) + { + if (renderer == null || + !renderer.gameObject.scene.IsValid() || + !renderer.gameObject.scene.isLoaded) + { + continue; + } + + summary.AppendLine($"- {renderer.name} enabled={renderer.enabled} active={renderer.gameObject.activeInHierarchy}"); + summary.AppendLine($" registeredForCamera={VolumeCloudRenderer.TryGetRenderer(camera, out var registered) && registered == renderer}"); + summary.AppendLine($" renderCount={renderer.DebugRenderCount} lastFrame={renderer.DebugLastRenderFrame} firstFrame={renderer.DebugFirstFrame}"); + summary.AppendLine($" Undersample={TextureFacts(renderer.DebugUndersampleBuffer)}"); + summary.AppendLine($" History={TextureFacts(renderer.DebugHistoryBuffer)}"); + summary.AppendLine($" CurrentCloud={TextureFacts(renderer.DebugCurrentCloudBuffer)}"); + + if (renderer.DebugUndersampleBuffer != null) + SaveTexture(renderer.DebugUndersampleBuffer, Path.Combine(OutputDirectory, "cloud-undersample.png")); + if (renderer.DebugHistoryBuffer != null) + SaveTexture(renderer.DebugHistoryBuffer, Path.Combine(OutputDirectory, "cloud-history.png")); + if (renderer.DebugCurrentCloudBuffer != null) + SaveTexture(renderer.DebugCurrentCloudBuffer, Path.Combine(OutputDirectory, "cloud-current.png")); + } + } + + private static void DumpVolumeSamplers(StringBuilder summary) + { + summary.AppendLine(); + summary.AppendLine("VolumeSamplers:"); + + foreach (var sampler in Resources.FindObjectsOfTypeAll()) + { + if (sampler == null || + !sampler.gameObject.scene.IsValid() || + !sampler.gameObject.scene.isLoaded) + { + continue; + } + + summary.AppendLine($"- {sampler.name} enabled={sampler.enabled} active={sampler.gameObject.activeInHierarchy}"); + summary.AppendLine($" GridCamera={NameOf(sampler.GridCamera)} GridMesh={NameOf(sampler.GridMesh)}"); + summary.AppendLine($" SceneSplatSource={NameOf(sampler.SceneSplatSource)} SplatLayerRenderer={NameOf(sampler.SplatLayerRenderer)}"); + summary.AppendLine($" Surface={TextureFacts(sampler.NebulaSurfaceHeight)}"); + summary.AppendLine($" PatchHeight={TextureFacts(sampler.NebulaPatchHeight)}"); + summary.AppendLine($" Patch={TextureFacts(sampler.NebulaPatch)}"); + summary.AppendLine($" Tint={TextureFacts(sampler.NebulaTint)}"); + DumpSamplerEnvironment(sampler, summary); + + if (sampler.SceneSplatSource != null) + DumpSceneSplatDocument(sampler, summary); + } + } + + private static void DumpSamplerEnvironment(VolumeSampling sampler, StringBuilder summary) + { + var settings = sampler.Settings; + var environment = settings != null ? settings.DefaultEnvironment : null; + summary.AppendLine($" Settings={NameOf(settings)}"); + if (environment == null) + { + summary.AppendLine(" Environment="); + return; + } + + summary.AppendLine( + $" Nebula FillDensity={environment.Nebula.FillDensity} FillDistance={environment.Nebula.FillDistance} FillExponent={environment.Nebula.FillExponent} FillOffset={environment.Nebula.FillOffset}"); + summary.AppendLine( + $" Nebula FloorOffset={environment.Nebula.FloorOffset} FloorBlend={environment.Nebula.FloorBlend} PatchDensity={environment.Nebula.PatchDensity} PatchBlend={environment.Nebula.PatchBlend}"); + summary.AppendLine( + $" Nebula Luminance={environment.Nebula.Luminance} Extinction={environment.Nebula.Extinction} TintLodExponent={environment.Nebula.TintLodExponent} SafetyDistance={environment.Nebula.SafetyDistance}"); + summary.AppendLine( + $" Lighting DynamicSkyBoost={environment.Lighting.DynamicSkyBoost} DynamicLodHigh={environment.Lighting.DynamicLodHigh} DynamicLodLow={environment.Lighting.DynamicLodLow} DynamicIntensity={environment.Lighting.DynamicIntensity}"); + summary.AppendLine( + $" Noise Scale={environment.Noise.Scale} Amplitude={environment.Noise.Amplitude} Exponent={environment.Noise.Exponent} Speed={environment.Noise.Speed} SlopeExponent={environment.Noise.SlopeExponent}"); + summary.AppendLine( + $" Flow GlobalScale={environment.Flow.GlobalScale} GlobalAmplitude={environment.Flow.GlobalAmplitude} GlobalScrollSpeed={environment.Flow.GlobalScrollSpeed} Period={environment.Flow.Period} SlopeAmplitude={environment.Flow.SlopeAmplitude} SwirlAmplitude={environment.Flow.SwirlAmplitude}"); + summary.AppendLine( + $" Grid Enabled={environment.Grid.Enabled} Offset={environment.Grid.Offset}"); + } + + private static void DumpSceneSplatDocument(VolumeSampling sampler, StringBuilder summary) + { + var size = sampler.GridCamera != null + ? new Vector2(sampler.GridCamera.orthographicSize * 2f, sampler.GridCamera.orthographicSize * 2f) + : new Vector2(1024f, 1024f); + var centerTransform = sampler.GridCamera != null ? sampler.GridCamera.transform : sampler.transform; + var center = centerTransform != null + ? new Vector2(centerTransform.position.x, centerTransform.position.z) + : Vector2.zero; + var half = size * 0.5f; + var document = sampler.SceneSplatSource.BuildDocument(new AetheriaRuntimeRtsViewportBounds + { + MinX = center.x - half.x, + MinY = center.y - half.y, + MaxX = center.x + half.x, + MaxY = center.y + half.y + }); + + summary.AppendLine($" SceneDocument layers={document.Layers?.Count ?? 0} splats={document.Splats?.Count ?? 0}"); + } + + private static void DumpGlobalTexture(StringBuilder summary, string globalName, string fileName) + { + var texture = Shader.GetGlobalTexture(globalName); + summary.AppendLine($"{globalName}: {TextureFacts(texture)}"); + if (texture != null) + SaveTexture(texture, Path.Combine(OutputDirectory, fileName)); + } + + private static void DumpCamera(Camera camera, StringBuilder summary) + { + var width = Mathf.Max(1, camera.pixelWidth > 0 ? camera.pixelWidth : 1920); + var height = Mathf.Max(1, camera.pixelHeight > 0 ? camera.pixelHeight : 1080); + var target = RenderTexture.GetTemporary(width, height, 24, RenderTextureFormat.ARGB32); + var previousTarget = camera.targetTexture; + var previousActive = RenderTexture.active; + try + { + camera.targetTexture = target; + camera.Render(); + SaveRenderTexture(target, Path.Combine(OutputDirectory, "camera-render.png")); + summary.AppendLine($"CameraRender: {width}x{height} -> camera-render.png"); + } + finally + { + camera.targetTexture = previousTarget; + RenderTexture.active = previousActive; + RenderTexture.ReleaseTemporary(target); + } + } + + private static void SaveTexture(Texture texture, string path) + { + var width = Mathf.Max(1, texture.width); + var height = Mathf.Max(1, texture.height); + var target = RenderTexture.GetTemporary(width, height, 0, RenderTextureFormat.ARGB32); + var previous = RenderTexture.active; + try + { + Graphics.Blit(texture, target); + SaveRenderTexture(target, path); + } + finally + { + RenderTexture.active = previous; + RenderTexture.ReleaseTemporary(target); + } + } + + private static void SaveRenderTexture(RenderTexture target, string path) + { + var previous = RenderTexture.active; + RenderTexture.active = target; + var image = new Texture2D(target.width, target.height, TextureFormat.RGBA32, false); + try + { + image.ReadPixels(new Rect(0, 0, target.width, target.height), 0, 0); + image.Apply(); + File.WriteAllBytes(path, image.EncodeToPNG()); + } + finally + { + UnityEngine.Object.DestroyImmediate(image); + RenderTexture.active = previous; + } + } + + private static string NameOf(UnityEngine.Object value) + { + return value != null ? value.name : ""; + } + + private static string TextureFacts(Texture texture) + { + if (texture == null) + return ""; + + return $"{texture.name} {texture.width}x{texture.height} {texture.GetType().Name}"; + } +} diff --git a/Assets/Editor/AetheriaRenderProbe.cs.meta b/Assets/Editor/AetheriaRenderProbe.cs.meta new file mode 100644 index 000000000..19a84b01d --- /dev/null +++ b/Assets/Editor/AetheriaRenderProbe.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 53a42c6de14b4ed2bb0c02b0faeb95d2 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Editor/AetheriaUrpBootstrap.cs b/Assets/Editor/AetheriaUrpBootstrap.cs index c2f8fa996..611709829 100644 --- a/Assets/Editor/AetheriaUrpBootstrap.cs +++ b/Assets/Editor/AetheriaUrpBootstrap.cs @@ -1,13 +1,26 @@ using UnityEditor; +using UnityEditor.SceneManagement; +using Aetheria.Rendering.PostProcessing; using UnityEngine; using UnityEngine.Rendering; using UnityEngine.Rendering.Universal; +using System.Linq; +using System; public static class AetheriaUrpBootstrap { private const string SettingsFolder = "Assets/Settings/Rendering"; private const string PipelineAssetPath = SettingsFolder + "/AetheriaURPAsset.asset"; private const string RendererAssetPath = SettingsFolder + "/AetheriaUniversalRenderer.asset"; + private const string MainMenuVolumeProfilePath = SettingsFolder + "/AetheriaMainMenuVolumeProfile.asset"; + private const string DefaultPostProcessDataPath = + "Packages/com.unity.render-pipelines.universal/Runtime/Data/PostProcessData.asset"; + + [InitializeOnLoadMethod] + private static void EnsureAetheriaRendererFeaturesOnLoad() + { + EditorApplication.delayCall += EnsureUrpAssetsAndAssign; + } [MenuItem("Aetheria/Rendering/Ensure URP Assets")] public static void EnsureUrpAssetsAndAssign() @@ -28,6 +41,27 @@ public static class AetheriaUrpBootstrap AssetDatabase.CreateAsset(pipelineAsset, PipelineAssetPath); } + EnsureRendererFeature( + rendererData, + "AetheriaVolumeSamplingRendererFeature", + "Aetheria Volume Sampling"); + EnsureRendererFeature( + rendererData, + "AetheriaVolumeCloudRendererFeature", + "Aetheria Volume Clouds"); + EnsureRendererFeature( + rendererData, + "AetheriaStardustRendererFeature", + "Aetheria Stardust"); + EnsureRendererFeature( + rendererData, + "Aetheria.Rendering.PostProcessing.AetheriaFogBlurRendererFeature", + "Aetheria Fog Blur"); + EnsurePostProcessing(rendererData); + var mainMenuVolumeProfile = EnsureMainMenuVolumeProfile(); + EnsureLoadedCameraPostProcessing(); + EnsureLoadedMainMenuVolumes(mainMenuVolumeProfile); + GraphicsSettings.defaultRenderPipeline = pipelineAsset; QualitySettings.renderPipeline = pipelineAsset; @@ -39,6 +73,210 @@ public static class AetheriaUrpBootstrap Debug.Log($"Aetheria URP asset assigned: {PipelineAssetPath}"); } + private static void EnsureRendererFeature( + UniversalRendererData rendererData, + string featureTypeName, + string featureName) + { + var featureType = AppDomain.CurrentDomain + .GetAssemblies() + .Select(assembly => assembly.GetType(featureTypeName)) + .FirstOrDefault(type => type != null); + if (featureType == null || !typeof(ScriptableRendererFeature).IsAssignableFrom(featureType)) + { + Debug.LogWarning($"Aetheria URP bootstrap could not find renderer feature type {featureTypeName}."); + return; + } + + var existing = AssetDatabase + .LoadAllAssetsAtPath(RendererAssetPath) + .OfType() + .FirstOrDefault(feature => feature.GetType() == featureType); + if (existing == null) + { + existing = (ScriptableRendererFeature)ScriptableObject.CreateInstance(featureType); + existing.name = featureName; + AssetDatabase.AddObjectToAsset(existing, rendererData); + } + + if (!rendererData.rendererFeatures.Contains(existing)) + rendererData.rendererFeatures.Add(existing); + } + + private static void EnsurePostProcessing(UniversalRendererData rendererData) + { + var serialized = new SerializedObject(rendererData); + var postProcessData = serialized.FindProperty("postProcessData"); + if (postProcessData == null || postProcessData.objectReferenceValue != null) + return; + + postProcessData.objectReferenceValue = + AssetDatabase.LoadAssetAtPath(DefaultPostProcessDataPath); + serialized.ApplyModifiedPropertiesWithoutUndo(); + } + + private static VolumeProfile EnsureMainMenuVolumeProfile() + { + var profile = AssetDatabase.LoadAssetAtPath(MainMenuVolumeProfilePath); + if (profile == null) + { + profile = ScriptableObject.CreateInstance(); + profile.name = "AetheriaMainMenuVolumeProfile"; + AssetDatabase.CreateAsset(profile, MainMenuVolumeProfilePath); + } + + EnsureVolumeComponent(profile, out Tonemapping tonemapping); + tonemapping.active = true; + tonemapping.mode.overrideState = true; + tonemapping.mode.value = TonemappingMode.ACES; + + EnsureVolumeComponent(profile, out ColorAdjustments colorAdjustments); + colorAdjustments.active = true; + colorAdjustments.postExposure.overrideState = true; + colorAdjustments.postExposure.value = -1f; + + EnsureVolumeComponent(profile, out Bloom bloom); + bloom.active = true; + bloom.threshold.overrideState = true; + bloom.threshold.value = 1.5f; + bloom.intensity.overrideState = true; + bloom.intensity.value = 3f; + bloom.scatter.overrideState = true; + bloom.scatter.value = 0.9f; + bloom.tint.overrideState = true; + bloom.tint.value = Color.white; + + EnsureVolumeComponent(profile, out Vignette vignette); + vignette.active = true; + vignette.intensity.overrideState = true; + vignette.intensity.value = 0.15f; + vignette.smoothness.overrideState = true; + vignette.smoothness.value = 0.45f; + + EnsureVolumeComponent(profile, out AetheriaFogBlur fogBlur); + fogBlur.active = true; + fogBlur.focusDistance.overrideState = true; + fogBlur.focusDistance.value = 10f; + fogBlur.aperture.overrideState = true; + fogBlur.aperture.value = 5.6f; + fogBlur.focalLength.overrideState = true; + fogBlur.focalLength.value = 50f; + fogBlur.kernelSize.overrideState = true; + fogBlur.kernelSize.value = (int)AetheriaFogBlurKernelSize.Medium; + + EditorUtility.SetDirty(profile); + return profile; + } + + private static void EnsureVolumeComponent(VolumeProfile profile, out T component) + where T : VolumeComponent + { + if (!profile.TryGet(out component)) + { + component = profile.Add(true); + } + } + + private static void EnsureLoadedCameraPostProcessing() + { + foreach (var camera in Resources.FindObjectsOfTypeAll()) + { + if (camera == null || !camera.gameObject.scene.IsValid() || !camera.gameObject.scene.isLoaded) + continue; + + var hasAetheriaVolumeRenderer = camera.GetComponent() != null; + if (!camera.CompareTag("MainCamera") && !hasAetheriaVolumeRenderer) + continue; + + var data = camera.GetComponent(); + if (data == null) + data = camera.gameObject.AddComponent(); + + var changed = false; + changed |= SetIfDifferent(() => data.renderPostProcessing, value => data.renderPostProcessing = value, true); + changed |= SetIfDifferent( + () => data.volumeLayerMask, + value => data.volumeLayerMask = value, + LayerMask.GetMask("PostProcessing")); + changed |= SetIfDifferent(() => data.volumeTrigger, value => data.volumeTrigger = value, camera.transform); + changed |= SetIfDifferent(() => data.requiresDepthOption, value => data.requiresDepthOption = value, CameraOverrideOption.On); + changed |= SetIfDifferent(() => data.stopNaN, value => data.stopNaN = value, true); + changed |= SetIfDifferent(() => data.dithering, value => data.dithering = value, true); + + changed |= RemoveLegacyPostProcessLayer(camera.gameObject); + + if (!changed) + continue; + + EditorUtility.SetDirty(camera.gameObject); + EditorUtility.SetDirty(data); + EditorSceneManager.MarkSceneDirty(camera.gameObject.scene); + } + } + + private static bool RemoveLegacyPostProcessLayer(GameObject cameraObject) + { + var removed = false; + foreach (var component in cameraObject.GetComponents()) + { + if (component == null) + continue; + + var type = component.GetType(); + if (type.FullName != "UnityEngine.Rendering.PostProcessing.PostProcessLayer") + continue; + + UnityEngine.Object.DestroyImmediate(component); + removed = true; + } + + return removed; + } + + private static void EnsureLoadedMainMenuVolumes(VolumeProfile mainMenuVolumeProfile) + { + if (mainMenuVolumeProfile == null) + return; + + foreach (var volume in Resources.FindObjectsOfTypeAll()) + { + if (volume == null || !volume.gameObject.scene.IsValid() || !volume.gameObject.scene.isLoaded) + continue; + + var isMainMenuVolume = volume.gameObject.name == "Postprocessing" && + volume.gameObject.scene.path.Replace('\\', '/').EndsWith("/Main Menu.unity", StringComparison.OrdinalIgnoreCase); + if (!isMainMenuVolume) + continue; + + var changed = false; + changed |= SetIfDifferent(() => volume.isGlobal, value => volume.isGlobal = value, true); + changed |= SetIfDifferent(() => volume.sharedProfile, value => volume.sharedProfile = value, mainMenuVolumeProfile); + + var postProcessingLayer = LayerMask.NameToLayer("PostProcessing"); + if (postProcessingLayer >= 0 && volume.gameObject.layer != postProcessingLayer) + { + volume.gameObject.layer = postProcessingLayer; + changed = true; + } + + if (!changed) + continue; + + EditorUtility.SetDirty(volume); + EditorUtility.SetDirty(volume.gameObject); + EditorSceneManager.MarkSceneDirty(volume.gameObject.scene); + } + } + + private static bool SetIfDifferent(Func get, Action set, T value) + { + if (Equals(get(), value)) + return false; + + set(value); + return true; + } + private static void EnsureFolder(string folderPath) { var parts = folderPath.Split('/'); diff --git a/Assets/Resources/Fog Patch Displacement.renderTexture b/Assets/Resources/Fog Patch Displacement.renderTexture index 5efa37ab4..5644c49d7 100644 --- a/Assets/Resources/Fog Patch Displacement.renderTexture +++ b/Assets/Resources/Fog Patch Displacement.renderTexture @@ -16,7 +16,7 @@ RenderTexture: m_Height: 256 m_AntiAliasing: 1 m_MipCount: -1 - m_DepthStencilFormat: 0 + m_DepthStencilFormat: 90 m_ColorFormat: 45 m_MipMap: 0 m_GenerateMips: 1 diff --git a/Assets/Resources/Fog Patch.renderTexture b/Assets/Resources/Fog Patch.renderTexture index 2721e0152..88657eee2 100644 --- a/Assets/Resources/Fog Patch.renderTexture +++ b/Assets/Resources/Fog Patch.renderTexture @@ -16,7 +16,7 @@ RenderTexture: m_Height: 512 m_AntiAliasing: 1 m_MipCount: -1 - m_DepthStencilFormat: 0 + m_DepthStencilFormat: 90 m_ColorFormat: 45 m_MipMap: 0 m_GenerateMips: 1 diff --git a/Assets/Resources/Fog Surface Displacement.renderTexture b/Assets/Resources/Fog Surface Displacement.renderTexture index 1f5d7f5f6..2b8fb9322 100644 --- a/Assets/Resources/Fog Surface Displacement.renderTexture +++ b/Assets/Resources/Fog Surface Displacement.renderTexture @@ -16,7 +16,7 @@ RenderTexture: m_Height: 2048 m_AntiAliasing: 1 m_MipCount: -1 - m_DepthStencilFormat: 0 + m_DepthStencilFormat: 90 m_ColorFormat: 49 m_MipMap: 0 m_GenerateMips: 1 diff --git a/Assets/Resources/Fog Tint.renderTexture b/Assets/Resources/Fog Tint.renderTexture index 52583c135..b2ca0189a 100644 --- a/Assets/Resources/Fog Tint.renderTexture +++ b/Assets/Resources/Fog Tint.renderTexture @@ -16,7 +16,7 @@ RenderTexture: m_Height: 512 m_AntiAliasing: 1 m_MipCount: -1 - m_DepthStencilFormat: 0 + m_DepthStencilFormat: 90 m_ColorFormat: 74 m_MipMap: 1 m_GenerateMips: 1 diff --git a/Assets/Resources/Influence.renderTexture b/Assets/Resources/Influence.renderTexture index af64aa5df..3a50fbbbf 100644 --- a/Assets/Resources/Influence.renderTexture +++ b/Assets/Resources/Influence.renderTexture @@ -16,7 +16,7 @@ RenderTexture: m_Height: 512 m_AntiAliasing: 1 m_MipCount: -1 - m_DepthStencilFormat: 0 + m_DepthStencilFormat: 90 m_ColorFormat: 45 m_MipMap: 0 m_GenerateMips: 1 diff --git a/Assets/Resources/Minimap.renderTexture b/Assets/Resources/Minimap.renderTexture index 6b37d07f2..30bcf3b21 100644 --- a/Assets/Resources/Minimap.renderTexture +++ b/Assets/Resources/Minimap.renderTexture @@ -16,7 +16,7 @@ RenderTexture: m_Height: 512 m_AntiAliasing: 1 m_MipCount: -1 - m_DepthStencilFormat: 0 + m_DepthStencilFormat: 90 m_ColorFormat: 74 m_MipMap: 1 m_GenerateMips: 1 diff --git a/Assets/Resources/MinimapGravity.renderTexture b/Assets/Resources/MinimapGravity.renderTexture index 3c4a395c4..60d1d1092 100644 --- a/Assets/Resources/MinimapGravity.renderTexture +++ b/Assets/Resources/MinimapGravity.renderTexture @@ -16,7 +16,7 @@ RenderTexture: m_Height: 128 m_AntiAliasing: 1 m_MipCount: -1 - m_DepthStencilFormat: 0 + m_DepthStencilFormat: 90 m_ColorFormat: 49 m_MipMap: 0 m_GenerateMips: 1 diff --git a/Assets/Resources/MinimapInfluence.renderTexture b/Assets/Resources/MinimapInfluence.renderTexture index 14db24b89..a1426e698 100644 --- a/Assets/Resources/MinimapInfluence.renderTexture +++ b/Assets/Resources/MinimapInfluence.renderTexture @@ -16,7 +16,7 @@ RenderTexture: m_Height: 128 m_AntiAliasing: 1 m_MipCount: -1 - m_DepthStencilFormat: 0 + m_DepthStencilFormat: 90 m_ColorFormat: 45 m_MipMap: 0 m_GenerateMips: 1 diff --git a/Assets/Resources/Slime Gravity.renderTexture b/Assets/Resources/Slime Gravity.renderTexture index 1217fbd13..379f2632d 100644 --- a/Assets/Resources/Slime Gravity.renderTexture +++ b/Assets/Resources/Slime Gravity.renderTexture @@ -18,7 +18,7 @@ RenderTexture: m_Height: 1024 m_AntiAliasing: 1 m_MipCount: -1 - m_DepthFormat: 0 + m_DepthFormat: 16 m_ColorFormat: 49 m_MipMap: 0 m_GenerateMips: 1 diff --git a/Assets/Resources/UnityDefaultRuntimeTheme.tss b/Assets/Resources/UnityDefaultRuntimeTheme.tss new file mode 100644 index 000000000..4fc4aaf68 --- /dev/null +++ b/Assets/Resources/UnityDefaultRuntimeTheme.tss @@ -0,0 +1,2 @@ +@import url("unity-theme://default"); +VisualElement {} diff --git a/Assets/Resources/UnityDefaultRuntimeTheme.tss.meta b/Assets/Resources/UnityDefaultRuntimeTheme.tss.meta new file mode 100644 index 000000000..3b764d19e --- /dev/null +++ b/Assets/Resources/UnityDefaultRuntimeTheme.tss.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: c26065dab33d48c4cad13c4a4fbafe9b +ScriptedImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 2 + userData: + assetBundleName: + assetBundleVariant: + script: {fileID: 12388, guid: 0000000000000000e000000000000000, type: 0} + disableValidation: 0 + unsupportedSelectorAction: 0 diff --git a/Assets/Scenes/ARPG_Profiles/Postprocessing Profile Main Menu.asset b/Assets/Scenes/ARPG_Profiles/Postprocessing Profile Main Menu.asset index 815941f7c..bd609b844 100644 --- a/Assets/Scenes/ARPG_Profiles/Postprocessing Profile Main Menu.asset +++ b/Assets/Scenes/ARPG_Profiles/Postprocessing Profile Main Menu.asset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:49eb8589a5a153df28beddeffb7a00c095788a8790d4a4a84c72eef4eb0fac4f -size 27015 +oid sha256:a395059cbabe86f59ecb8405f6c44c11bc165925093bbcf6f60d11f578eb1618 +size 27028 diff --git a/Assets/Scenes/Main Menu.unity b/Assets/Scenes/Main Menu.unity index 3effa9f34..1ba4a828a 100644 --- a/Assets/Scenes/Main Menu.unity +++ b/Assets/Scenes/Main Menu.unity @@ -13,7 +13,7 @@ OcclusionCullingSettings: --- !u!104 &2 RenderSettings: m_ObjectHideFlags: 0 - serializedVersion: 9 + serializedVersion: 10 m_Fog: 0 m_FogColor: {r: 0.5, g: 0.5, b: 0.5, a: 1} m_FogMode: 3 @@ -38,13 +38,12 @@ RenderSettings: m_ReflectionIntensity: 1 m_CustomReflection: {fileID: 0} m_Sun: {fileID: 0} - m_IndirectSpecularColor: {r: 0, g: 0, b: 0, a: 1} m_UseRadianceAmbientProbe: 0 --- !u!157 &3 LightmapSettings: m_ObjectHideFlags: 0 - serializedVersion: 12 - m_GIWorkflowMode: 1 + serializedVersion: 13 + m_BakeOnSceneLoad: 0 m_GISettings: serializedVersion: 2 m_BounceScale: 1 @@ -67,9 +66,6 @@ LightmapSettings: m_LightmapParameters: {fileID: 0} m_LightmapsBakeMode: 1 m_TextureCompression: 1 - m_FinalGather: 0 - m_FinalGatherFiltering: 1 - m_FinalGatherRayCount: 256 m_ReflectionCompression: 2 m_MixedBakeMode: 2 m_BakeBackend: 1 @@ -104,7 +100,7 @@ NavMeshSettings: serializedVersion: 2 m_ObjectHideFlags: 0 m_BuildSettings: - serializedVersion: 2 + serializedVersion: 3 agentTypeID: 0 agentRadius: 0.5 agentHeight: 2 @@ -117,7 +113,7 @@ NavMeshSettings: cellSize: 0.16666667 manualTileSize: 0 tileSize: 256 - accuratePlacement: 0 + buildHeightMesh: 0 maxJobWorkers: 0 preserveTilesOutsideBounds: 0 debug: @@ -148,13 +144,13 @@ Transform: m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 128306751} + serializedVersion: 2 m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 1734434094} - m_RootOrder: 2 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!23 &128306753 MeshRenderer: @@ -173,6 +169,11 @@ MeshRenderer: m_ReflectionProbeUsage: 0 m_RayTracingMode: 2 m_RayTraceProcedural: 0 + m_RayTracingAccelStructBuildFlagsOverride: 0 + m_RayTracingAccelStructBuildFlags: 1 + m_SmallMeshCulling: 1 + m_ForceMeshLod: -1 + m_MeshLodSelectionBias: 0 m_RenderingLayerMask: 1 m_RendererPriority: 0 m_Materials: @@ -194,9 +195,11 @@ MeshRenderer: m_AutoUVMaxDistance: 0.5 m_AutoUVMaxAngle: 89 m_LightmapParameters: {fileID: 0} + m_GlobalIlluminationMeshLod: 0 m_SortingLayerID: 0 m_SortingLayer: 0 m_SortingOrder: 0 + m_MaskInteraction: 0 m_AdditionalVertexStreams: {fileID: 0} --- !u!33 &128306754 MeshFilter: @@ -243,6 +246,7 @@ Transform: m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 335549370} + serializedVersion: 2 m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} @@ -250,7 +254,6 @@ Transform: m_Children: - {fileID: 801832274} m_Father: {fileID: 0} - m_RootOrder: 7 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!1 &356429273 GameObject: @@ -277,13 +280,13 @@ Transform: m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 356429273} + serializedVersion: 2 m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 1734434094} - m_RootOrder: 1 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!23 &356429275 MeshRenderer: @@ -302,6 +305,11 @@ MeshRenderer: m_ReflectionProbeUsage: 0 m_RayTracingMode: 2 m_RayTraceProcedural: 0 + m_RayTracingAccelStructBuildFlagsOverride: 0 + m_RayTracingAccelStructBuildFlags: 1 + m_SmallMeshCulling: 1 + m_ForceMeshLod: -1 + m_MeshLodSelectionBias: 0 m_RenderingLayerMask: 1 m_RendererPriority: 0 m_Materials: @@ -323,9 +331,11 @@ MeshRenderer: m_AutoUVMaxDistance: 0.5 m_AutoUVMaxAngle: 89 m_LightmapParameters: {fileID: 0} + m_GlobalIlluminationMeshLod: 0 m_SortingLayerID: 0 m_SortingLayer: 0 m_SortingOrder: 0 + m_MaskInteraction: 0 m_AdditionalVertexStreams: {fileID: 0} --- !u!33 &356429276 MeshFilter: @@ -362,13 +372,13 @@ Transform: m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 412917865} + serializedVersion: 2 m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} m_LocalPosition: {x: 512.312, y: 512.416, z: 29.00662} m_LocalScale: {x: 1, y: 1, z: 1} m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 741153269} - m_RootOrder: 0 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!114 &412917867 MonoBehaviour: @@ -527,6 +537,8 @@ MonoBehaviour: type: 3} m_DeselectOnBackgroundClick: 1 m_PointerBehavior: 0 + m_CursorLockBehavior: 0 + m_ScrollDeltaPerTick: 6 --- !u!114 &553528108 MonoBehaviour: m_ObjectHideFlags: 0 @@ -549,13 +561,13 @@ Transform: m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 553528106} + serializedVersion: 2 m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} m_LocalPosition: {x: -0.03, y: 0, z: -0.01} m_LocalScale: {x: 1, y: 1, z: 1} m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 0} - m_RootOrder: 10 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!1 &564164468 GameObject: @@ -582,13 +594,13 @@ Transform: m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 564164468} + serializedVersion: 2 m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalScale: {x: 0.25, y: 0.25, z: 0.25} m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 1340266884} - m_RootOrder: 1 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!23 &564164471 MeshRenderer: @@ -607,6 +619,11 @@ MeshRenderer: m_ReflectionProbeUsage: 0 m_RayTracingMode: 2 m_RayTraceProcedural: 0 + m_RayTracingAccelStructBuildFlagsOverride: 0 + m_RayTracingAccelStructBuildFlags: 1 + m_SmallMeshCulling: 1 + m_ForceMeshLod: -1 + m_MeshLodSelectionBias: 0 m_RenderingLayerMask: 1 m_RendererPriority: 0 m_Materials: @@ -628,9 +645,11 @@ MeshRenderer: m_AutoUVMaxDistance: 0.5 m_AutoUVMaxAngle: 89 m_LightmapParameters: {fileID: 0} + m_GlobalIlluminationMeshLod: 0 m_SortingLayerID: 0 m_SortingLayer: 0 m_SortingOrder: 0 + m_MaskInteraction: 0 m_AdditionalVertexStreams: {fileID: 0} --- !u!33 &564164472 MeshFilter: @@ -665,13 +684,13 @@ Transform: m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 591465737} + serializedVersion: 2 m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 1340266884} - m_RootOrder: 0 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!23 &591465739 MeshRenderer: @@ -690,6 +709,11 @@ MeshRenderer: m_ReflectionProbeUsage: 0 m_RayTracingMode: 2 m_RayTraceProcedural: 0 + m_RayTracingAccelStructBuildFlagsOverride: 0 + m_RayTracingAccelStructBuildFlags: 1 + m_SmallMeshCulling: 1 + m_ForceMeshLod: -1 + m_MeshLodSelectionBias: 0 m_RenderingLayerMask: 1 m_RendererPriority: 0 m_Materials: @@ -711,9 +735,11 @@ MeshRenderer: m_AutoUVMaxDistance: 0.5 m_AutoUVMaxAngle: 89 m_LightmapParameters: {fileID: 0} + m_GlobalIlluminationMeshLod: 0 m_SortingLayerID: 0 m_SortingLayer: 0 m_SortingOrder: 0 + m_MaskInteraction: 0 m_AdditionalVertexStreams: {fileID: 0} --- !u!33 &591465740 MeshFilter: @@ -746,6 +772,7 @@ Transform: m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 678287590} + serializedVersion: 2 m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} @@ -755,7 +782,6 @@ Transform: - {fileID: 1872107549} - {fileID: 1662321235} m_Father: {fileID: 1340266884} - m_RootOrder: 3 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!1 &741153267 GameObject: @@ -803,7 +829,8 @@ MonoBehaviour: ModeOverride: 0 LensShift: {x: 0, y: 0} GateFit: 2 - m_SensorSize: {x: 1.7777778, y: 1} + FocusDistance: 10 + m_SensorSize: {x: 1, y: 1} m_Transitions: m_BlendHint: 0 m_InheritPosition: 0 @@ -819,6 +846,7 @@ Transform: m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 741153267} + serializedVersion: 2 m_LocalRotation: {x: 0.17419034, y: 0.82403576, z: -0.4098015, w: 0.3502649} m_LocalPosition: {x: -255.6498, y: 261.96085, z: 220.16467} m_LocalScale: {x: 1, y: 1, z: 1} @@ -826,7 +854,6 @@ Transform: m_Children: - {fileID: 412917866} m_Father: {fileID: 0} - m_RootOrder: 2 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!1 &750375533 GameObject: @@ -854,13 +881,13 @@ Transform: m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 750375533} + serializedVersion: 2 m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 0} - m_RootOrder: 3 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!114 &750375535 MonoBehaviour: @@ -885,10 +912,12 @@ MonoBehaviour: Spacing: 6 Ceiling: -0.1 Floor: -30 + MinHeadroom: 25 + MaxHeadroom: 100 HeightExponent: 3 NebulaSurfaceHeight: {fileID: 8400000, guid: 2fc21a9d3e9647948b56e384cd9378b3, type: 2} - NebulaPatchHeight: {fileID: 8400000, guid: c618a6b20b3c7394590c59a9fa467e86, type: 2} - NebulaPatch: {fileID: 8400000, guid: 06ec5ef8a5830fc44b1ac1a6e123add5, type: 2} + NebulaPatchHeight: {fileID: 8400000, guid: 06ec5ef8a5830fc44b1ac1a6e123add5, type: 2} + NebulaPatch: {fileID: 8400000, guid: c618a6b20b3c7394590c59a9fa467e86, type: 2} NebulaTint: {fileID: 8400000, guid: d196505464047f04a97f28689041209b, type: 2} Heightmap: {fileID: 8400000, guid: 2fc21a9d3e9647948b56e384cd9378b3, type: 2} ParticleColors: {fileID: 2800000, guid: 56db63da398b2d04394ddbccedce943c, type: 3} @@ -943,13 +972,13 @@ Transform: m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 765320284} + serializedVersion: 2 m_LocalRotation: {x: 0, y: 0.7071068, z: -0.7071068, w: 0} m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 1172538221} - m_RootOrder: 1 m_LocalEulerAnglesHint: {x: 90, y: 180, z: 0} --- !u!20 &765320286 Camera: @@ -965,9 +994,17 @@ Camera: m_projectionMatrixMode: 1 m_GateFitMode: 2 m_FOVAxisMode: 0 + m_Iso: 200 + m_ShutterSpeed: 0.005 + m_Aperture: 16 + m_FocusDistance: 10 + m_FocalLength: 50 + m_BladeCount: 5 + m_Curvature: {x: 2, y: 11} + m_BarrelClipping: 0.25 + m_Anamorphism: 0 m_SensorSize: {x: 36, y: 24} m_LensShift: {x: 0, y: 0} - m_FocalLength: 50 m_NormalizedViewPortRect: serializedVersion: 2 x: 0 @@ -999,6 +1036,7 @@ PrefabInstance: m_ObjectHideFlags: 0 serializedVersion: 2 m_Modification: + serializedVersion: 3 m_TransformParent: {fileID: 335549372} m_Modifications: - target: {fileID: 7852221720480299766, guid: 046dde0f4600bce41ad33c476f99b309, @@ -1083,7 +1121,7 @@ PrefabInstance: objectReference: {fileID: 0} - target: {fileID: 7852221720816817929, guid: 046dde0f4600bce41ad33c476f99b309, type: 3} - propertyPath: m_Materials.Array.data[0] + propertyPath: 'm_Materials.Array.data[0]' value: objectReference: {fileID: 2100000, guid: b9f52544f98f47146b486fb6e27179bb, type: 2} - target: {fileID: 7852221720969441490, guid: 046dde0f4600bce41ad33c476f99b309, @@ -1117,6 +1155,9 @@ PrefabInstance: value: -56.5 objectReference: {fileID: 0} m_RemovedComponents: [] + m_RemovedGameObjects: [] + m_AddedGameObjects: [] + m_AddedComponents: [] m_SourcePrefab: {fileID: 100100000, guid: 046dde0f4600bce41ad33c476f99b309, type: 3} --- !u!4 &801832274 stripped Transform: @@ -1160,13 +1201,13 @@ Transform: m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 850359435} + serializedVersion: 2 m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 0} - m_RootOrder: 11 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!1 &965376321 GameObject: @@ -1178,10 +1219,11 @@ GameObject: m_Component: - component: {fileID: 965376324} - component: {fileID: 965376323} - - component: {fileID: 965376325} + - component: {fileID: 965376330} - component: {fileID: 965376322} - component: {fileID: 965376328} - component: {fileID: 965376329} + - component: {fileID: 965376331} m_Layer: 0 m_Name: Main Camera m_TagString: MainCamera @@ -1237,9 +1279,17 @@ Camera: m_projectionMatrixMode: 1 m_GateFitMode: 2 m_FOVAxisMode: 0 + m_Iso: 200 + m_ShutterSpeed: 0.005 + m_Aperture: 16 + m_FocusDistance: 10 + m_FocalLength: 50 + m_BladeCount: 5 + m_Curvature: {x: 2, y: 11} + m_BarrelClipping: 0.25 + m_Anamorphism: 0 m_SensorSize: {x: 36, y: 24} m_LensShift: {x: 0, y: 0} - m_FocalLength: 50 m_NormalizedViewPortRect: serializedVersion: 2 x: 0 @@ -1273,76 +1323,14 @@ Transform: m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 965376321} + serializedVersion: 2 m_LocalRotation: {x: 0.17419034, y: 0.82403576, z: -0.4098015, w: 0.3502649} m_LocalPosition: {x: -255.6498, y: 261.96085, z: 220.16467} m_LocalScale: {x: 1, y: 1, z: 1} m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 0} - m_RootOrder: 0 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} ---- !u!114 &965376325 -MonoBehaviour: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 965376321} - m_Enabled: 1 - m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: 948f4100a11a5c24981795d21301da5c, type: 3} - m_Name: - m_EditorClassIdentifier: - volumeTrigger: {fileID: 965376324} - volumeLayer: - serializedVersion: 2 - m_Bits: 256 - stopNaNPropagation: 1 - finalBlitToCameraTarget: 0 - antialiasingMode: 3 - temporalAntialiasing: - jitterSpread: 0.1 - sharpness: 0.5 - stationaryBlending: 0.975 - motionBlending: 0.95 - subpixelMorphologicalAntialiasing: - quality: 2 - fastApproximateAntialiasing: - fastMode: 0 - keepAlpha: 0 - fog: - enabled: 0 - excludeSkybox: 1 - debugLayer: - lightMeter: - width: 512 - height: 256 - showCurves: 1 - histogram: - width: 512 - height: 256 - channel: 3 - waveform: - exposure: 0.12 - height: 256 - vectorscope: - size: 256 - exposure: 0.12 - overlaySettings: - linearDepth: 0 - motionColorIntensity: 4 - motionGridSize: 64 - colorBlindnessType: 0 - colorBlindnessStrength: 1 - m_Resources: {fileID: 11400000, guid: d82512f9c8e5d4a4d938b575d47f88d4, type: 2} - m_ShowToolkit: 0 - m_ShowCustomSorter: 0 - breakBeforeColorGrading: 0 - m_BeforeTransparentBundles: [] - m_BeforeStackBundles: - - assemblyQualifiedName: Aetheria.Rendering.PostProcessing.AetheriaFogBlur, Assembly-CSharp, - Version=0.0.0.0, Culture=neutral, PublicKeyToken=null - m_AfterStackBundles: [] --- !u!114 &965376328 MonoBehaviour: m_ObjectHideFlags: 0 @@ -1355,12 +1343,19 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: 8c03374f22d0a3d4882018de66894a84, type: 3} m_Name: m_EditorClassIdentifier: + GridMesh: {fileID: 0} GridCamera: {fileID: 2103364771} Settings: {fileID: 11400000, guid: cd2344e2001a37544aab0960c164d4ff, type: 2} NebulaSurfaceHeight: {fileID: 8400000, guid: 2fc21a9d3e9647948b56e384cd9378b3, type: 2} NebulaPatchHeight: {fileID: 8400000, guid: 06ec5ef8a5830fc44b1ac1a6e123add5, type: 2} NebulaPatch: {fileID: 8400000, guid: c618a6b20b3c7394590c59a9fa467e86, type: 2} NebulaTint: {fileID: 8400000, guid: d196505464047f04a97f28689041209b, type: 2} + SplatViewportSource: {fileID: 0} + SplatLayerRenderer: {fileID: 965376331} + SceneSplatSource: {fileID: 0} + useRuntimeRenderSplats: 0 + renderSplatsEveryFrame: 0 + splatRenderIntervalSeconds: 0.25 --- !u!114 &965376329 MonoBehaviour: m_ObjectHideFlags: 0 @@ -1376,6 +1371,125 @@ MonoBehaviour: downSample: 0 quality: 2 cloudShader: {fileID: 4800000, guid: 84e97169589c5e94e8521fe4deb2e3f8, type: 3} + ditherTexture: {fileID: 2800000, guid: 7c0a392de8821f345824a79dea0d0d41, type: 3} +--- !u!114 &965376330 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 965376321} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: a79441f348de89743a2939f4d699eac1, type: 3} + m_Name: + m_EditorClassIdentifier: + m_RenderShadows: 1 + m_RequiresDepthTextureOption: 1 + m_RequiresOpaqueTextureOption: 2 + m_CameraType: 0 + m_Cameras: [] + m_RendererIndex: -1 + m_VolumeLayerMask: + serializedVersion: 2 + m_Bits: 256 + m_VolumeTrigger: {fileID: 965376324} + m_VolumeFrameworkUpdateModeOption: 2 + m_RenderPostProcessing: 1 + m_Antialiasing: 3 + m_AntialiasingQuality: 2 + m_StopNaN: 1 + m_Dithering: 1 + m_ClearDepth: 1 + m_AllowXRRendering: 1 + m_AllowHDROutput: 1 + m_UseScreenCoordOverride: 0 + m_ScreenSizeOverride: {x: 0, y: 0, z: 0, w: 0} + m_ScreenCoordScaleBias: {x: 0, y: 0, z: 0, w: 0} + m_RequiresDepthTexture: 0 + m_RequiresColorTexture: 0 + m_TaaSettings: + m_Quality: 3 + m_FrameInfluence: 0.1 + m_JitterScale: 1 + m_MipBias: 0 + m_VarianceClampScale: 0.9 + m_ContrastAdaptiveSharpening: 0 + m_Version: 2 +--- !u!114 &965376331 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 965376321} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 0f3c143f0a844f38a109f2de02ce4423, type: 3} + m_Name: + m_EditorClassIdentifier: Assembly-CSharp::AetheriaRenderSplatLayerRenderer + rasterizer: {fileID: 0} + bindings: + - Enabled: 1 + LayerKey: gravity.height + Display: {fileID: 0} + MaterialTextureProperty: _DetailTex + GlobalTextureName: _AetheriaGravityHeight + GraphicsFormatOverride: + WidthScale: 1 + HeightScale: 1 + UseMipMaps: 0 + FilterMode: 1 + - Enabled: 1 + LayerKey: fog.tint + Display: {fileID: 0} + MaterialTextureProperty: _DetailTex + GlobalTextureName: _NebulaTint + GraphicsFormatOverride: + WidthScale: 1 + HeightScale: 1 + UseMipMaps: 0 + FilterMode: 1 + - Enabled: 1 + LayerKey: fog.surface_height + Display: {fileID: 0} + MaterialTextureProperty: _DetailTex + GlobalTextureName: _NebulaSurfaceHeight + GraphicsFormatOverride: + WidthScale: 0.5 + HeightScale: 0.5 + UseMipMaps: 0 + FilterMode: 1 + - Enabled: 1 + LayerKey: fog.patch_height + Display: {fileID: 0} + MaterialTextureProperty: _DetailTex + GlobalTextureName: _NebulaPatchHeight + GraphicsFormatOverride: + WidthScale: 0.5 + HeightScale: 0.5 + UseMipMaps: 0 + FilterMode: 1 + - Enabled: 1 + LayerKey: fog.patch + Display: {fileID: 0} + MaterialTextureProperty: _DetailTex + GlobalTextureName: _NebulaPatch + GraphicsFormatOverride: + WidthScale: 0.5 + HeightScale: 0.5 + UseMipMaps: 0 + FilterMode: 1 + - Enabled: 1 + LayerKey: influence.mask + Display: {fileID: 0} + MaterialTextureProperty: _DetailTex + GlobalTextureName: _AetheriaInfluence + GraphicsFormatOverride: + WidthScale: 1 + HeightScale: 1 + UseMipMaps: 0 + FilterMode: 1 --- !u!4 &971662241 stripped Transform: m_CorrespondingSourceObject: {fileID: 7852221720971737233, guid: 046dde0f4600bce41ad33c476f99b309, @@ -1407,13 +1521,13 @@ Transform: m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 1131053606} + serializedVersion: 2 m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 1734434094} - m_RootOrder: 0 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!23 &1131053608 MeshRenderer: @@ -1432,6 +1546,11 @@ MeshRenderer: m_ReflectionProbeUsage: 0 m_RayTracingMode: 2 m_RayTraceProcedural: 0 + m_RayTracingAccelStructBuildFlagsOverride: 0 + m_RayTracingAccelStructBuildFlags: 1 + m_SmallMeshCulling: 1 + m_ForceMeshLod: -1 + m_MeshLodSelectionBias: 0 m_RenderingLayerMask: 1 m_RendererPriority: 0 m_Materials: @@ -1453,9 +1572,11 @@ MeshRenderer: m_AutoUVMaxDistance: 0.5 m_AutoUVMaxAngle: 89 m_LightmapParameters: {fileID: 0} + m_GlobalIlluminationMeshLod: 0 m_SortingLayerID: 0 m_SortingLayer: 0 m_SortingOrder: 0 + m_MaskInteraction: 0 m_AdditionalVertexStreams: {fileID: 0} --- !u!33 &1131053609 MeshFilter: @@ -1488,6 +1609,7 @@ Transform: m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 1172538219} + serializedVersion: 2 m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} m_LocalPosition: {x: 0, y: 10, z: 0} m_LocalScale: {x: 1000, y: 1000, z: 1000} @@ -1498,7 +1620,6 @@ Transform: - {fileID: 2103364770} - {fileID: 1988716753} m_Father: {fileID: 0} - m_RootOrder: 6 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!1 &1311800795 GameObject: @@ -1525,13 +1646,13 @@ Transform: m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 1311800795} + serializedVersion: 2 m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 678287591} - m_RootOrder: 0 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!23 &1311800797 MeshRenderer: @@ -1550,6 +1671,11 @@ MeshRenderer: m_ReflectionProbeUsage: 0 m_RayTracingMode: 2 m_RayTraceProcedural: 0 + m_RayTracingAccelStructBuildFlagsOverride: 0 + m_RayTracingAccelStructBuildFlags: 1 + m_SmallMeshCulling: 1 + m_ForceMeshLod: -1 + m_MeshLodSelectionBias: 0 m_RenderingLayerMask: 1 m_RendererPriority: 0 m_Materials: @@ -1571,9 +1697,11 @@ MeshRenderer: m_AutoUVMaxDistance: 0.5 m_AutoUVMaxAngle: 89 m_LightmapParameters: {fileID: 0} + m_GlobalIlluminationMeshLod: 0 m_SortingLayerID: 0 m_SortingLayer: 0 m_SortingOrder: 0 + m_MaskInteraction: 0 m_AdditionalVertexStreams: {fileID: 0} --- !u!33 &1311800798 MeshFilter: @@ -1592,7 +1720,7 @@ GameObject: serializedVersion: 6 m_Component: - component: {fileID: 1323077762} - - component: {fileID: 1323077761} + - component: {fileID: 1323077763} m_Layer: 8 m_Name: Postprocessing m_TagString: Untagged @@ -1600,7 +1728,22 @@ GameObject: m_NavMeshLayer: 0 m_StaticEditorFlags: 0 m_IsActive: 1 ---- !u!114 &1323077761 +--- !u!4 &1323077762 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1323077760} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 0} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!114 &1323077763 MonoBehaviour: m_ObjectHideFlags: 0 m_CorrespondingSourceObject: {fileID: 0} @@ -1609,29 +1752,14 @@ MonoBehaviour: m_GameObject: {fileID: 1323077760} m_Enabled: 1 m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: 8b9a305e18de0c04dbd257a21cd47087, type: 3} + m_Script: {fileID: 11500000, guid: 172515602e62fb746b5d573b38a5fe58, type: 3} m_Name: - m_EditorClassIdentifier: - sharedProfile: {fileID: 11400000, guid: c0300fb6953b52548967234468a15850, type: 2} - isGlobal: 1 + m_EditorClassIdentifier: Unity.RenderPipelines.Core.Runtime::UnityEngine.Rendering.Volume + m_IsGlobal: 1 + priority: 0 blendDistance: 0 weight: 1 - priority: 0 ---- !u!4 &1323077762 -Transform: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 1323077760} - m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} - m_LocalPosition: {x: 0, y: 0, z: 0} - m_LocalScale: {x: 1, y: 1, z: 1} - m_ConstrainProportionsScale: 0 - m_Children: [] - m_Father: {fileID: 0} - m_RootOrder: 1 - m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + sharedProfile: {fileID: 11400000, guid: 621d661451ce43259c2e712812fc2a5c, type: 2} --- !u!1 &1340266883 GameObject: m_ObjectHideFlags: 0 @@ -1655,6 +1783,7 @@ Transform: m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 1340266883} + serializedVersion: 2 m_LocalRotation: {x: 0.7071068, y: 0, z: 0, w: 0.7071068} m_LocalPosition: {x: 0, y: -5, z: 0} m_LocalScale: {x: 3000, y: 3000, z: 3000} @@ -1665,7 +1794,6 @@ Transform: - {fileID: 1734434094} - {fileID: 678287591} m_Father: {fileID: 0} - m_RootOrder: 5 m_LocalEulerAnglesHint: {x: 90, y: 0, z: 0} --- !u!1 &1362573162 GameObject: @@ -1690,19 +1818,20 @@ Transform: m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 1362573162} + serializedVersion: 2 m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} m_LocalPosition: {x: 0, y: -125, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 0} - m_RootOrder: 8 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!1001 &1429393530 PrefabInstance: m_ObjectHideFlags: 0 serializedVersion: 2 m_Modification: + serializedVersion: 3 m_TransformParent: {fileID: 0} m_Modifications: - target: {fileID: 463084220523011477, guid: 4a77c480b4bb2a148954bd57dfe91d0b, @@ -1766,6 +1895,9 @@ PrefabInstance: value: Wormhole 1 objectReference: {fileID: 0} m_RemovedComponents: [] + m_RemovedGameObjects: [] + m_AddedGameObjects: [] + m_AddedComponents: [] m_SourcePrefab: {fileID: 100100000, guid: 4a77c480b4bb2a148954bd57dfe91d0b, type: 3} --- !u!1 &1572994234 GameObject: @@ -1798,9 +1930,17 @@ Camera: m_projectionMatrixMode: 1 m_GateFitMode: 2 m_FOVAxisMode: 0 + m_Iso: 200 + m_ShutterSpeed: 0.005 + m_Aperture: 16 + m_FocusDistance: 10 + m_FocalLength: 50 + m_BladeCount: 5 + m_Curvature: {x: 2, y: 11} + m_BarrelClipping: 0.25 + m_Anamorphism: 0 m_SensorSize: {x: 36, y: 24} m_LensShift: {x: 0, y: 0} - m_FocalLength: 50 m_NormalizedViewPortRect: serializedVersion: 2 x: 0 @@ -1834,13 +1974,13 @@ Transform: m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 1572994234} + serializedVersion: 2 m_LocalRotation: {x: 0, y: 0.7071068, z: -0.7071068, w: 0} m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 1172538221} - m_RootOrder: 0 m_LocalEulerAnglesHint: {x: 90, y: 180, z: 0} --- !u!1 &1662321234 GameObject: @@ -1867,13 +2007,13 @@ Transform: m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 1662321234} + serializedVersion: 2 m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 678287591} - m_RootOrder: 2 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!23 &1662321236 MeshRenderer: @@ -1892,6 +2032,11 @@ MeshRenderer: m_ReflectionProbeUsage: 0 m_RayTracingMode: 2 m_RayTraceProcedural: 0 + m_RayTracingAccelStructBuildFlagsOverride: 0 + m_RayTracingAccelStructBuildFlags: 1 + m_SmallMeshCulling: 1 + m_ForceMeshLod: -1 + m_MeshLodSelectionBias: 0 m_RenderingLayerMask: 1 m_RendererPriority: 0 m_Materials: @@ -1913,9 +2058,11 @@ MeshRenderer: m_AutoUVMaxDistance: 0.5 m_AutoUVMaxAngle: 89 m_LightmapParameters: {fileID: 0} + m_GlobalIlluminationMeshLod: 0 m_SortingLayerID: 0 m_SortingLayer: 0 m_SortingOrder: 0 + m_MaskInteraction: 0 m_AdditionalVertexStreams: {fileID: 0} --- !u!33 &1662321237 MeshFilter: @@ -1950,15 +2097,14 @@ Light: m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 1663455619} m_Enabled: 1 - serializedVersion: 10 + serializedVersion: 13 m_Type: 1 - m_Shape: 0 m_Color: {r: 1, g: 1, b: 1, a: 1} m_Intensity: 8 m_Range: 10 m_SpotAngle: 30 m_InnerSpotAngle: 21.80208 - m_CookieSize: 10 + m_CookieSize2D: {x: 10, y: 10} m_Shadows: m_Type: 0 m_Resolution: -1 @@ -2002,8 +2148,12 @@ Light: m_BoundingSphereOverride: {x: 0, y: 0, z: 0, w: 0} m_UseBoundingSphereOverride: 0 m_UseViewFrustumForShadowCasterCull: 1 - m_ShadowRadius: 0 + m_ForceVisible: 0 + m_ShapeRadius: 0 m_ShadowAngle: 0 + m_LightUnit: 1 + m_LuxAtDistance: 1 + m_EnableSpotReflector: 1 --- !u!4 &1663455621 Transform: m_ObjectHideFlags: 0 @@ -2011,13 +2161,13 @@ Transform: m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 1663455619} + serializedVersion: 2 m_LocalRotation: {x: 0.7071068, y: 0, z: 0, w: 0.7071068} m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 0} - m_RootOrder: 12 m_LocalEulerAnglesHint: {x: 90, y: 0, z: 0} --- !u!1 &1734434093 GameObject: @@ -2042,6 +2192,7 @@ Transform: m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 1734434093} + serializedVersion: 2 m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} @@ -2051,7 +2202,6 @@ Transform: - {fileID: 356429274} - {fileID: 128306752} m_Father: {fileID: 1340266884} - m_RootOrder: 2 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!1 &1872107548 GameObject: @@ -2078,13 +2228,13 @@ Transform: m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 1872107548} + serializedVersion: 2 m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 678287591} - m_RootOrder: 1 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!23 &1872107550 MeshRenderer: @@ -2103,6 +2253,11 @@ MeshRenderer: m_ReflectionProbeUsage: 0 m_RayTracingMode: 2 m_RayTraceProcedural: 0 + m_RayTracingAccelStructBuildFlagsOverride: 0 + m_RayTracingAccelStructBuildFlags: 1 + m_SmallMeshCulling: 1 + m_ForceMeshLod: -1 + m_MeshLodSelectionBias: 0 m_RenderingLayerMask: 1 m_RendererPriority: 0 m_Materials: @@ -2124,9 +2279,11 @@ MeshRenderer: m_AutoUVMaxDistance: 0.5 m_AutoUVMaxAngle: 89 m_LightmapParameters: {fileID: 0} + m_GlobalIlluminationMeshLod: 0 m_SortingLayerID: 0 m_SortingLayer: 0 m_SortingOrder: 0 + m_MaskInteraction: 0 m_AdditionalVertexStreams: {fileID: 0} --- !u!33 &1872107551 MeshFilter: @@ -2160,13 +2317,13 @@ Transform: m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 1988716752} + serializedVersion: 2 m_LocalRotation: {x: 0, y: 0.7071068, z: -0.7071068, w: 0} m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 1172538221} - m_RootOrder: 3 m_LocalEulerAnglesHint: {x: 90, y: 180, z: 0} --- !u!20 &1988716754 Camera: @@ -2182,9 +2339,17 @@ Camera: m_projectionMatrixMode: 1 m_GateFitMode: 2 m_FOVAxisMode: 0 + m_Iso: 200 + m_ShutterSpeed: 0.005 + m_Aperture: 16 + m_FocusDistance: 10 + m_FocalLength: 50 + m_BladeCount: 5 + m_Curvature: {x: 2, y: 11} + m_BarrelClipping: 0.25 + m_Anamorphism: 0 m_SensorSize: {x: 36, y: 24} m_LensShift: {x: 0, y: 0} - m_FocalLength: 50 m_NormalizedViewPortRect: serializedVersion: 2 x: 0 @@ -2235,13 +2400,13 @@ Transform: m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 2103364769} + serializedVersion: 2 m_LocalRotation: {x: 0, y: 0.7071068, z: -0.7071068, w: 0} m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 1172538221} - m_RootOrder: 2 m_LocalEulerAnglesHint: {x: 90, y: 180, z: 0} --- !u!20 &2103364771 Camera: @@ -2257,9 +2422,17 @@ Camera: m_projectionMatrixMode: 1 m_GateFitMode: 2 m_FOVAxisMode: 0 + m_Iso: 200 + m_ShutterSpeed: 0.005 + m_Aperture: 16 + m_FocusDistance: 10 + m_FocalLength: 50 + m_BladeCount: 5 + m_Curvature: {x: 2, y: 11} + m_BarrelClipping: 0.25 + m_Anamorphism: 0 m_SensorSize: {x: 36, y: 24} m_LensShift: {x: 0, y: 0} - m_FocalLength: 50 m_NormalizedViewPortRect: serializedVersion: 2 x: 0 @@ -2291,6 +2464,7 @@ PrefabInstance: m_ObjectHideFlags: 0 serializedVersion: 2 m_Modification: + serializedVersion: 3 m_TransformParent: {fileID: 0} m_Modifications: - target: {fileID: 350889890962861326, guid: 3d8d9cbf10b12214e98891c1f03c1e99, @@ -2438,20 +2612,30 @@ PrefabInstance: propertyPath: m_AnchoredPosition.y value: 0 objectReference: {fileID: 0} + - target: {fileID: 2967358985619754986, guid: 3d8d9cbf10b12214e98891c1f03c1e99, + type: 3} + propertyPath: 'm_ActiveFontFeatures.Array.data[0]' + value: 1801810542 + objectReference: {fileID: 0} + - target: {fileID: 2967358987018748071, guid: 3d8d9cbf10b12214e98891c1f03c1e99, + type: 3} + propertyPath: 'm_ActiveFontFeatures.Array.data[0]' + value: 1801810542 + objectReference: {fileID: 0} - target: {fileID: 3664209305474245377, guid: 3d8d9cbf10b12214e98891c1f03c1e99, type: 3} propertyPath: m_AnchorMax.y - value: 0 + value: 1 objectReference: {fileID: 0} - target: {fileID: 3664209305474245377, guid: 3d8d9cbf10b12214e98891c1f03c1e99, type: 3} propertyPath: m_AnchorMin.y - value: 0 + value: 1 objectReference: {fileID: 0} - target: {fileID: 3664209305474245377, guid: 3d8d9cbf10b12214e98891c1f03c1e99, type: 3} propertyPath: m_SizeDelta.x - value: 0 + value: 57.96 objectReference: {fileID: 0} - target: {fileID: 3664209305474245377, guid: 3d8d9cbf10b12214e98891c1f03c1e99, type: 3} @@ -2461,22 +2645,22 @@ PrefabInstance: - target: {fileID: 3664209305474245377, guid: 3d8d9cbf10b12214e98891c1f03c1e99, type: 3} propertyPath: m_AnchoredPosition.y - value: 0 + value: -16 objectReference: {fileID: 0} - target: {fileID: 3664209306888449100, guid: 3d8d9cbf10b12214e98891c1f03c1e99, type: 3} propertyPath: m_AnchorMax.y - value: 0 + value: 1 objectReference: {fileID: 0} - target: {fileID: 3664209306888449100, guid: 3d8d9cbf10b12214e98891c1f03c1e99, type: 3} propertyPath: m_AnchorMin.y - value: 0 + value: 1 objectReference: {fileID: 0} - target: {fileID: 3664209306888449100, guid: 3d8d9cbf10b12214e98891c1f03c1e99, type: 3} propertyPath: m_SizeDelta.x - value: 0 + value: 47.35 objectReference: {fileID: 0} - target: {fileID: 3664209306888449100, guid: 3d8d9cbf10b12214e98891c1f03c1e99, type: 3} @@ -2486,7 +2670,7 @@ PrefabInstance: - target: {fileID: 3664209306888449100, guid: 3d8d9cbf10b12214e98891c1f03c1e99, type: 3} propertyPath: m_AnchoredPosition.y - value: 0 + value: -16 objectReference: {fileID: 0} - target: {fileID: 4533226043449861658, guid: 3d8d9cbf10b12214e98891c1f03c1e99, type: 3} @@ -2608,6 +2792,11 @@ PrefabInstance: propertyPath: m_Name value: Main Menu Canvas objectReference: {fileID: 0} + - target: {fileID: 4533226043829246278, guid: 3d8d9cbf10b12214e98891c1f03c1e99, + type: 3} + propertyPath: 'm_ActiveFontFeatures.Array.data[0]' + value: 1801810542 + objectReference: {fileID: 0} - target: {fileID: 4533226043829246279, guid: 3d8d9cbf10b12214e98891c1f03c1e99, type: 3} propertyPath: m_AnchorMax.y @@ -2714,6 +2903,9 @@ PrefabInstance: value: 0 objectReference: {fileID: 0} m_RemovedComponents: [] + m_RemovedGameObjects: [] + m_AddedGameObjects: [] + m_AddedComponents: [] m_SourcePrefab: {fileID: 100100000, guid: 3d8d9cbf10b12214e98891c1f03c1e99, type: 3} --- !u!223 &4533226044378988780 stripped Canvas: @@ -2721,3 +2913,20 @@ Canvas: type: 3} m_PrefabInstance: {fileID: 4533226044378988779} m_PrefabAsset: {fileID: 0} +--- !u!1660057539 &9223372036854775807 +SceneRoots: + m_ObjectHideFlags: 0 + m_Roots: + - {fileID: 965376324} + - {fileID: 1323077762} + - {fileID: 741153269} + - {fileID: 750375534} + - {fileID: 1429393530} + - {fileID: 1340266884} + - {fileID: 1172538221} + - {fileID: 335549372} + - {fileID: 1362573163} + - {fileID: 4533226044378988779} + - {fileID: 553528109} + - {fileID: 850359437} + - {fileID: 1663455621} diff --git a/Assets/Scripts/Gameplay/ActionBarSlot.cs b/Assets/Scripts/Gameplay/ActionBarSlot.cs index 67f0d813c..2d86f0ab1 100644 --- a/Assets/Scripts/Gameplay/ActionBarSlot.cs +++ b/Assets/Scripts/Gameplay/ActionBarSlot.cs @@ -1,6 +1,7 @@ using System; using System.Linq; using GameCult.Aetheria.State.Verse; +using GameCult.Mesh; using TMPro; using UniRx.Triggers; using UnityEngine; @@ -52,6 +53,7 @@ public class ActionBarSlot : MonoBehaviour public abstract class ActionBarBinding { + private static AetheriaClientState s_runtimeState; public Entity Entity { get; } private readonly Func _resolveControl; protected ActionBarSlot Slot { get; } @@ -97,9 +99,8 @@ public abstract class ActionBarBinding { try { - return AetheriaUnityRuntimeClientProvider - .CurrentCatalog() - .FindItem(item, x => x.ItemKey); + s_runtimeState ??= AetheriaUnityRuntimeClientProvider.RuntimeState("unity-action-bar"); + return s_runtimeState.Catalog.Latest()?.FindItem(item, x => x.ItemKey); } catch (Exception ex) { diff --git a/Assets/Scripts/Gameplay/ActionGameManager.cs b/Assets/Scripts/Gameplay/ActionGameManager.cs index e3f942b52..eda4ee58b 100644 --- a/Assets/Scripts/Gameplay/ActionGameManager.cs +++ b/Assets/Scripts/Gameplay/ActionGameManager.cs @@ -5,8 +5,9 @@ using System; using Cinemachine; using GameCult.Aetheria.State.Verse; +using GameCult.Mesh; using UnityEngine; -using UnityEngine.Rendering.PostProcessing; +using UnityEngine.Rendering; using UnityEngine.Serialization; using UnityEngine.UI; using float2 = Unity.Mathematics.float2; @@ -29,13 +30,17 @@ public class ActionGameManager : MonoBehaviour private AetheriaUnityGameplayLoopShell _gameplayLoopShell; private AetheriaUnityGameplayBootShell _gameplayBootShell; private AetheriaUnityGameplaySceneWiring _sceneWiring; + private AetheriaClientState _gameplayState; + private AetheriaClientState GameplayState => + _gameplayState ??= AetheriaUnityRuntimeClientProvider.RuntimeState("unity-action-game-manager"); + private AetheriaUnityPilotCommandSender PilotCommands => _pilotCommands ??= new AetheriaUnityPilotCommandSender( () => AetheriaUnityRuntimeClientProvider.Control("unity-pilot-commands"), () => Time.unscaledTime); private AetheriaUnityObservedEntityRestorer ObservedEntityRestorer => _observedEntityRestorer ??= new AetheriaUnityObservedEntityRestorer( - _observedEntityIndex, + _presentationEntityIndex, ItemManager, EntityBlueprintMaterializer.MaterializeObservedEntity, _loadoutItemFactory.CreateLoadoutItem, @@ -81,16 +86,16 @@ public class ActionGameManager : MonoBehaviour private AetheriaUnityPilotOperationController PilotOperationController => _pilotOperationController ??= new AetheriaUnityPilotOperationController( () => PilotCommands, - _observedEntityIndex, + _presentationEntityIndex, () => _viewDirection, () => CurrentEntity); private AetheriaUnityObservedFrameApplier ObservedFrameApplier => _observedFrameApplier ??= new AetheriaUnityObservedFrameApplier( ResolveDaemonObserver, - ResolveObservedGalaxyZone, + ObservedZoneContextFactory.ResolveGalaxyZone, () => Zone, zone => Zone = zone, - _observedEntityIndex, + _presentationEntityIndex, ObservedEntityRestorer, ObservedZoneContextFactory, () => ZoneRenderer, @@ -200,7 +205,7 @@ public class ActionGameManager : MonoBehaviour TargetShieldsFill = TargetShieldsFill, MainMenu = MainMenu }; - private readonly AetheriaUnityObservedEntityIndex _observedEntityIndex = new AetheriaUnityObservedEntityIndex(); + private readonly AetheriaUnityPresentationEntityIndex _presentationEntityIndex = new AetheriaUnityPresentationEntityIndex(); private static RuntimePlayerSettings RuntimePlayerSettings => AetheriaUnityRuntimeClientProvider.PlayerSettings; @@ -211,9 +216,9 @@ public class ActionGameManager : MonoBehaviour public float TargetSpottedBlinkOffset = -.25f; [Header("Postprocessing")] - public PostProcessVolume DeathPost; - public PostProcessVolume HeatstrokePost; - public PostProcessVolume SevereHeatstrokePost; + public Volume DeathPost; + public Volume HeatstrokePost; + public Volume SevereHeatstrokePost; [Header("Scene Links")] public GameObject UiRoot; @@ -303,7 +308,7 @@ public class ActionGameManager : MonoBehaviour SceneWiring.ConfigureTargetPresentation( _targetPresentation, boot.RuntimeCatalog, - _observedEntityIndex); + _presentationEntityIndex); SceneWiring.ConfigureInventoryDragSession(_dragSession); SceneWiring.ConfigureActionBarPresentation( _actionBarPresentation, @@ -313,7 +318,7 @@ public class ActionGameManager : MonoBehaviour () => CurrentEntity, () => AetheriaUnityRuntimeClientProvider.Control("unity-action-bar")); SceneWiring.ConfigureRuntimeInputScreenShell(MenuShell); - SceneWiring.ConfigureObservedEntityIndex(_observedEntityIndex); + SceneWiring.ConfigurePresentationEntityIndex(_presentationEntityIndex); // TODO: Process Stories @@ -347,7 +352,7 @@ public class ActionGameManager : MonoBehaviour return false; } - if (_observedEntityIndex.TryResolveEntityRecordKey(CurrentEntity, out var currentEntityKey) && + if (_presentationEntityIndex.TryGetRecordKeyForPresentationEntity(CurrentEntity, out var currentEntityKey) && !string.IsNullOrWhiteSpace(docking.CurrentEntityKey) && !string.Equals(docking.CurrentEntityKey, currentEntityKey, StringComparison.Ordinal)) { @@ -369,23 +374,23 @@ public class ActionGameManager : MonoBehaviour return null; } - if (_observedEntityIndex.TryResolveEntityRecordKey(currentEntity, out var currentEntityKey) && + if (_presentationEntityIndex.TryGetRecordKeyForPresentationEntity(currentEntity, out var currentEntityKey) && !string.IsNullOrWhiteSpace(docking.CurrentEntityKey) && !string.Equals(docking.CurrentEntityKey, currentEntityKey, StringComparison.Ordinal)) { return null; } - return _observedEntityIndex.TryResolveEntityByRecordKey(docking.DockParentEntityKey, out var dockParent) + return _presentationEntityIndex.TryGetPresentationEntityByRecordKey(docking.DockParentEntityKey, out var dockParent) ? dockParent : null; } - private static AetheriaRuntimeCurrentDockingDocument CurrentDockingSnapshot() + private AetheriaRuntimeCurrentDockingDocument CurrentDockingSnapshot() { try { - return AetheriaUnityRuntimeClientProvider.CurrentDockingState("unity-action-game-manager"); + return GameplayState.CurrentDocking.Latest(); } catch (Exception ex) { @@ -394,20 +399,6 @@ public class ActionGameManager : MonoBehaviour } } - private GalaxyZone ResolveObservedGalaxyZone(int daemonZoneIndex) - { - if (daemonZoneIndex < 0 || ObservedGalaxy?.Zones == null) - return null; - - foreach (var zone in ObservedGalaxy.Zones) - { - if (zone != null && zone.ZoneIndex == daemonZoneIndex) - return zone; - } - - return null; - } - // public void ToggleEditMode() // { // _editMode = !_editMode; diff --git a/Assets/Scripts/Gameplay/AetheriaSceneRenderSplatSource.cs b/Assets/Scripts/Gameplay/AetheriaSceneRenderSplatSource.cs new file mode 100644 index 000000000..f4a849c0a --- /dev/null +++ b/Assets/Scripts/Gameplay/AetheriaSceneRenderSplatSource.cs @@ -0,0 +1,271 @@ +using System; +using System.Collections.Generic; +using GameCult.Aetheria.State.Verse; +using UnityEngine; + +public sealed class AetheriaSceneRenderSplatSource : MonoBehaviour +{ + [SerializeField] + private string documentName = "Scene Mock"; + + [SerializeField] + private AetheriaSceneRenderSplat[] splats = + { + AetheriaSceneRenderSplat.Animated( + AetheriaRuntimeRenderSplatLayerKeys.FogSurfaceHeight, + "scene.fog_surface", + Vector2.zero, + new Vector2(512, 512), + Color.white, + 4, + 4, + 0.015f), + AetheriaSceneRenderSplat.Animated( + AetheriaRuntimeRenderSplatLayerKeys.FogPatchHeight, + "scene.fog_patch_height", + Vector2.zero, + new Vector2(512, 512), + Color.white, + 9, + 9, + 0.02f), + AetheriaSceneRenderSplat.Animated( + AetheriaRuntimeRenderSplatLayerKeys.FogPatch, + "scene.fog_patch", + Vector2.zero, + new Vector2(512, 512), + Color.white, + 6, + 6, + 0.01f) + }; + + [SerializeField] + private AetheriaSceneRenderSplatFixture[] fixtures = Array.Empty(); + + public AetheriaRuntimeRenderSplatsViewportDocument BuildDocument(AetheriaRuntimeRtsViewportBounds viewport) + { + viewport ??= new AetheriaRuntimeRtsViewportBounds(); + var normalizedViewport = Normalize(viewport); + var layers = BuildLayers(); + var layerIndices = new Dictionary(StringComparer.Ordinal); + for (var index = 0; index < layers.Length; index++) + layerIndices[layers[index].LayerKey] = index; + + var builder = new Builder(); + foreach (var splat in splats ?? Array.Empty()) + splat.AddTo(builder, layerIndices); + foreach (var fixture in fixtures ?? Array.Empty()) + fixture.AddTo(builder, layerIndices); + + return new AetheriaRuntimeRenderSplatsViewportDocument + { + PublishedAtUtc = DateTimeOffset.UtcNow.ToString("O"), + SimulationTimeSeconds = Application.isPlaying ? Time.timeAsDouble : 0, + RunId = "scene", + ZoneName = string.IsNullOrWhiteSpace(documentName) ? gameObject.scene.name : documentName, + Viewport = normalizedViewport, + Layers = layers, + Splats = builder.Build() + }; + } + + private static AetheriaRuntimeRtsViewportBounds Normalize(AetheriaRuntimeRtsViewportBounds viewport) + { + return new AetheriaRuntimeRtsViewportBounds + { + MinX = Math.Min(viewport.MinX, viewport.MaxX), + MinY = Math.Min(viewport.MinY, viewport.MaxY), + MaxX = Math.Max(viewport.MinX, viewport.MaxX), + MaxY = Math.Max(viewport.MinY, viewport.MaxY) + }; + } + + private static AetheriaRuntimeRenderSplatLayerDefinition[] BuildLayers() + { + return new[] + { + Layer(AetheriaRuntimeRenderSplatLayerKeys.GravityHeight, "Gravity Height", AetheriaRuntimeRenderSplatChannels.Gravity), + Layer(AetheriaRuntimeRenderSplatLayerKeys.GravityWave, "Gravity Wave", AetheriaRuntimeRenderSplatChannels.GravityWave), + Layer(AetheriaRuntimeRenderSplatLayerKeys.Visibility, "Visibility Mask", AetheriaRuntimeRenderSplatChannels.Visibility, AetheriaRuntimeRenderSplatBlendModes.Max), + Layer(AetheriaRuntimeRenderSplatLayerKeys.FogSurfaceHeight, "Fog Surface Height", AetheriaRuntimeRenderSplatChannels.Tint), + Layer(AetheriaRuntimeRenderSplatLayerKeys.FogPatchHeight, "Fog Patch Height", AetheriaRuntimeRenderSplatChannels.Tint), + Layer(AetheriaRuntimeRenderSplatLayerKeys.FogPatch, "Fog Patch", AetheriaRuntimeRenderSplatChannels.Tint, AetheriaRuntimeRenderSplatBlendModes.Max), + Layer(AetheriaRuntimeRenderSplatLayerKeys.FogTint, "Fog Tint", AetheriaRuntimeRenderSplatChannels.Tint, graphicsFormat: "B10G11R11_UFloatPack32"), + Layer(AetheriaRuntimeRenderSplatLayerKeys.Influence, "Influence", AetheriaRuntimeRenderSplatChannels.Influence) + }; + } + + private static AetheriaRuntimeRenderSplatLayerDefinition Layer( + string key, + string displayName, + int channel, + string blendMode = AetheriaRuntimeRenderSplatBlendModes.Add, + string graphicsFormat = "R16_SFloat") + { + return new AetheriaRuntimeRenderSplatLayerDefinition + { + LayerKey = key, + DisplayName = displayName, + Channel = channel, + BlendMode = blendMode, + GraphicsFormat = graphicsFormat + }; + } + + public sealed class Builder + { + private readonly List _centerX = new List(); + private readonly List _centerY = new List(); + private readonly List _halfExtentX = new List(); + private readonly List _halfExtentY = new List(); + private readonly List _rotationCos = new List(); + private readonly List _rotationSin = new List(); + private readonly List _channel = new List(); + private readonly List _falloff = new List(); + private readonly List _valueR = new List(); + private readonly List _valueG = new List(); + private readonly List _valueB = new List(); + private readonly List _valueA = new List(); + private readonly List _sourceKey = new List(); + private readonly List _layerIndex = new List(); + private readonly List _sourceKind = new List(); + private readonly List _frequencyX = new List(); + private readonly List _frequencyY = new List(); + private readonly List _phaseX = new List(); + private readonly List _phaseY = new List(); + private readonly List _animationSpeed = new List(); + private readonly List _sourceFlags = new List(); + + public void Add(AetheriaSceneRenderSplat splat, int layerIndex) + { + var rotationRadians = splat.RotationDegrees * Mathf.Deg2Rad; + _layerIndex.Add(layerIndex); + _centerX.Add(splat.Center.x); + _centerY.Add(splat.Center.y); + _halfExtentX.Add(Math.Max(0, splat.HalfExtent.x)); + _halfExtentY.Add(Math.Max(0, splat.HalfExtent.y)); + _rotationCos.Add(Math.Cos(rotationRadians)); + _rotationSin.Add(Math.Sin(rotationRadians)); + _channel.Add(splat.Channel); + _falloff.Add(splat.Falloff); + _valueR.Add(splat.Value.r); + _valueG.Add(splat.Value.g); + _valueB.Add(splat.Value.b); + _valueA.Add(splat.Value.a); + _sourceKey.Add(splat.SourceKey ?? ""); + _sourceKind.Add(splat.SourceKind); + _frequencyX.Add(splat.Frequency.x); + _frequencyY.Add(splat.Frequency.y); + _phaseX.Add(splat.Phase.x); + _phaseY.Add(splat.Phase.y); + _animationSpeed.Add(splat.AnimationSpeed); + _sourceFlags.Add(splat.SourceFlags); + } + + public AetheriaRuntimeRenderSplatSoa Build() + { + return new AetheriaRuntimeRenderSplatSoa + { + Count = _centerX.Count, + CenterX = _centerX.ToArray(), + CenterY = _centerY.ToArray(), + HalfExtentX = _halfExtentX.ToArray(), + HalfExtentY = _halfExtentY.ToArray(), + RotationCos = _rotationCos.ToArray(), + RotationSin = _rotationSin.ToArray(), + Channel = _channel.ToArray(), + Falloff = _falloff.ToArray(), + ValueR = _valueR.ToArray(), + ValueG = _valueG.ToArray(), + ValueB = _valueB.ToArray(), + ValueA = _valueA.ToArray(), + SourceKey = _sourceKey.ToArray(), + LayerIndex = _layerIndex.ToArray(), + SourceKind = _sourceKind.ToArray(), + FrequencyX = _frequencyX.ToArray(), + FrequencyY = _frequencyY.ToArray(), + PhaseX = _phaseX.ToArray(), + PhaseY = _phaseY.ToArray(), + AnimationSpeed = _animationSpeed.ToArray(), + SourceFlags = _sourceFlags.ToArray() + }; + } + } +} + +[Serializable] +public sealed class AetheriaSceneRenderSplat +{ + public string LayerKey = AetheriaRuntimeRenderSplatLayerKeys.FogTint; + public string SourceKey = ""; + public Vector2 Center; + public Vector2 HalfExtent = Vector2.one * 32; + public float RotationDegrees; + public int Channel = AetheriaRuntimeRenderSplatChannels.Tint; + public int Falloff = AetheriaRuntimeRenderSplatFalloffs.Smooth; + public Color Value = Color.white; + public int SourceKind = AetheriaRuntimeRenderSplatSourceKinds.Constant; + public Vector2 Frequency = Vector2.one; + public Vector2 Phase; + public float AnimationSpeed; + public float SourceFlags; + + public static AetheriaSceneRenderSplat Animated( + string layerKey, + string sourceKey, + Vector2 center, + Vector2 halfExtent, + Color value, + float frequencyX, + float frequencyY, + float animationSpeed) + { + return new AetheriaSceneRenderSplat + { + LayerKey = layerKey, + SourceKey = sourceKey, + Center = center, + HalfExtent = halfExtent, + Channel = AetheriaRuntimeRenderSplatChannels.Tint, + Falloff = AetheriaRuntimeRenderSplatFalloffs.Solid, + Value = value, + SourceKind = AetheriaRuntimeRenderSplatSourceKinds.AnimatedSimplexNoise, + Frequency = new Vector2(frequencyX, frequencyY), + AnimationSpeed = animationSpeed, + SourceFlags = 1 + }; + } + + public void AddTo(AetheriaSceneRenderSplatSource.Builder builder, IReadOnlyDictionary layerIndices) + { + if (builder == null || !layerIndices.TryGetValue(LayerKey ?? "", out var layerIndex)) + return; + + builder.Add(this, layerIndex); + } +} + +[Serializable] +public sealed class AetheriaSceneRenderSplatFixture +{ + public Transform Transform; + public AetheriaSceneRenderSplat Splat = new AetheriaSceneRenderSplat(); + + public void AddTo(AetheriaSceneRenderSplatSource.Builder builder, IReadOnlyDictionary layerIndices) + { + if (Transform == null || Splat == null) + return; + + var previousCenter = Splat.Center; + Splat.Center = new Vector2(Transform.position.x, Transform.position.z); + try + { + Splat.AddTo(builder, layerIndices); + } + finally + { + Splat.Center = previousCenter; + } + } +} diff --git a/Assets/Scripts/Gameplay/AetheriaSceneRenderSplatSource.cs.meta b/Assets/Scripts/Gameplay/AetheriaSceneRenderSplatSource.cs.meta new file mode 100644 index 000000000..ea46b8942 --- /dev/null +++ b/Assets/Scripts/Gameplay/AetheriaSceneRenderSplatSource.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 6c9073f8e2eb4b52b9a11ec60dcfc451 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Gameplay/AetheriaUnityCurrentEntityBinder.cs b/Assets/Scripts/Gameplay/AetheriaUnityCurrentEntityBinder.cs index 3c7e33649..d57ee6b76 100644 --- a/Assets/Scripts/Gameplay/AetheriaUnityCurrentEntityBinder.cs +++ b/Assets/Scripts/Gameplay/AetheriaUnityCurrentEntityBinder.cs @@ -7,13 +7,13 @@ using System.Collections.Generic; using System.Linq; using GameCult.Aetheria.State.Verse; using UnityEngine; -using UnityEngine.Rendering.PostProcessing; +using UnityEngine.Rendering; using float3 = Unity.Mathematics.float3; public sealed class AetheriaUnityCurrentEntityBinder { public ZoneRenderer ZoneRenderer { get; set; } - public PostProcessVolume DeathPost { get; set; } + public Volume DeathPost { get; set; } public CanvasGroup GameplayUI { get; set; } public AetheriaUnityCurrentEntityPresentation CurrentEntityPresentation { get; set; } public AetheriaUnityTargetPresentation TargetPresentation { get; set; } diff --git a/Assets/Scripts/Gameplay/AetheriaUnityDaemonRenderDocuments.cs b/Assets/Scripts/Gameplay/AetheriaUnityDaemonRenderDocuments.cs index a51d48767..cab4ed320 100644 --- a/Assets/Scripts/Gameplay/AetheriaUnityDaemonRenderDocuments.cs +++ b/Assets/Scripts/Gameplay/AetheriaUnityDaemonRenderDocuments.cs @@ -13,9 +13,9 @@ public sealed class AetheriaUnityDaemonRenderDocuments : IDisposable if (state == null) throw new ArgumentNullException(nameof(state)); - _frame = state.ReactiveDaemonFrame(); - _soaView = state.ReactiveDaemonSoaView(); - _zoneRender = state.ReactiveZoneRender(); + _frame = state.DaemonFrame.Reactive(); + _soaView = state.DaemonSoaView.Reactive(); + _zoneRender = state.ZoneRender.Reactive(); } public AetheriaRuntimeDaemonRenderView Current => diff --git a/Assets/Scripts/Gameplay/AetheriaUnityEveRuntimeHooks.cs b/Assets/Scripts/Gameplay/AetheriaUnityEveRuntimeHooks.cs new file mode 100644 index 000000000..ba95baa32 --- /dev/null +++ b/Assets/Scripts/Gameplay/AetheriaUnityEveRuntimeHooks.cs @@ -0,0 +1,23 @@ +using GameCult.Aetheria.EveRuntime; +using GameCult.Aetheria.State.Verse; +using UnityEngine; + +public static class AetheriaUnityEveRuntimeHooks +{ + [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)] + private static void Install() + { + AetheriaEveRuntimeUnityHooks.ResolveStateBoot = + overridePath => AetheriaRuntimeStateBoot.Inspect( + AetheriaUnityRuntimePaths.GameDataDirectory, + overridePath ?? ""); + AetheriaEveRuntimeUnityHooks.RuntimeState = + (stateBoot, runtimeId) => AetheriaUnityRuntimeClientProvider.RuntimeState(stateBoot, runtimeId); + AetheriaEveRuntimeUnityHooks.Control = + (stateBoot, runtimeId) => AetheriaUnityRuntimeClientProvider.Control(stateBoot, runtimeId); + AetheriaEveRuntimeUnityHooks.Ui = + (stateBoot, runtimeId) => AetheriaUnityRuntimeClientProvider.Ui(stateBoot, runtimeId); + AetheriaEveRuntimeUnityHooks.StateRefResolver = + (stateBoot, runtimeId) => AetheriaUnityRuntimeClientProvider.EveSurfaceStateRefResolver(stateBoot, runtimeId); + } +} diff --git a/Assets/Scripts/Gameplay/AetheriaUnityEveRuntimeHooks.cs.meta b/Assets/Scripts/Gameplay/AetheriaUnityEveRuntimeHooks.cs.meta new file mode 100644 index 000000000..edad1dbcc --- /dev/null +++ b/Assets/Scripts/Gameplay/AetheriaUnityEveRuntimeHooks.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 72069f7b0885490fb690dc771e422aac +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Gameplay/AetheriaUnityGameplayBootShell.cs b/Assets/Scripts/Gameplay/AetheriaUnityGameplayBootShell.cs index c29a933be..6c080b541 100644 --- a/Assets/Scripts/Gameplay/AetheriaUnityGameplayBootShell.cs +++ b/Assets/Scripts/Gameplay/AetheriaUnityGameplayBootShell.cs @@ -4,6 +4,7 @@ using System; using GameCult.Aetheria.State.Verse; +using GameCult.Mesh; public sealed class AetheriaUnityGameplayBootShell { @@ -34,10 +35,11 @@ public sealed class AetheriaUnityGameplayBootShell $"Aetheria runtime state file is missing at {stateBoot.StateFilePath}; gameplay requires an authoritative daemon mirror."); } - var runtimeCatalog = AetheriaUnityRuntimeClientProvider.CurrentCatalog(stateBoot, stateBoot.RuntimeId); + var runtimeState = AetheriaUnityRuntimeClientProvider.RuntimeState(stateBoot, stateBoot.RuntimeId); + var runtimeCatalog = runtimeState.Catalog.Latest(); if (runtimeCatalog == null) throw new InvalidOperationException("Aetheria typed runtime catalog is required before gameplay boot."); - var sectorMap = AetheriaUnityRuntimeClientProvider.CurrentSectorMap(stateBoot, stateBoot.RuntimeId); + var sectorMap = runtimeState.SectorMap.Latest(); if (sectorMap == null) throw new InvalidOperationException("Aetheria typed sector map is required before gameplay boot."); diff --git a/Assets/Scripts/Gameplay/AetheriaUnityGameplaySceneWiring.cs b/Assets/Scripts/Gameplay/AetheriaUnityGameplaySceneWiring.cs index d6caf174f..5635d6821 100644 --- a/Assets/Scripts/Gameplay/AetheriaUnityGameplaySceneWiring.cs +++ b/Assets/Scripts/Gameplay/AetheriaUnityGameplaySceneWiring.cs @@ -5,8 +5,8 @@ using System; using Cinemachine; using GameCult.Aetheria.State.Verse; +using GameCult.Mesh; using UnityEngine; -using UnityEngine.Rendering.PostProcessing; using UnityEngine.UI; public sealed class AetheriaUnityGameplaySceneWiring @@ -34,6 +34,17 @@ public sealed class AetheriaUnityGameplaySceneWiring public Sprite NoShieldIcon { get; set; } public Prototype HostileTargetIndicator { get; set; } public Prototype FriendlyTargetIndicator { get; set; } + + private AetheriaClientState _targetPresentationState; + + private AetheriaClientState TargetPresentationState + { + get + { + _targetPresentationState ??= AetheriaUnityRuntimeClientProvider.RuntimeState("unity-target-presentation"); + return _targetPresentationState; + } + } public PlaceUIElementWorldspace ViewDot { get; set; } public PlaceUIElementWorldspace TargetIndicator { get; set; } public Image TargetHitpointsFill { get; set; } @@ -78,7 +89,7 @@ public sealed class AetheriaUnityGameplaySceneWiring public void ConfigureTargetPresentation( AetheriaUnityTargetPresentation presentation, AetheriaRuntimeCatalogSnapshot runtimeCatalog, - AetheriaUnityObservedEntityIndex observedEntityIndex) + AetheriaUnityPresentationEntityIndex presentationEntityIndex) { if (presentation == null) return; @@ -94,18 +105,18 @@ public sealed class AetheriaUnityGameplaySceneWiring presentation.VisibilityToTargetFill = VisibilityToTargetFill; presentation.TargetShieldsFill = TargetShieldsFill; presentation.ResolveEntity = daemonEntityIndex => - observedEntityIndex != null && - observedEntityIndex.TryResolveEntityByDaemonIndex(daemonEntityIndex, out var entity) + presentationEntityIndex != null && + presentationEntityIndex.TryGetPresentationEntityByDaemonIndex(daemonEntityIndex, out var entity) ? entity : null; presentation.ResolveZoneContacts = ReadZoneContacts; } - private static AetheriaRuntimeZoneContactsDocument ReadZoneContacts() + private AetheriaRuntimeZoneContactsDocument ReadZoneContacts() { try { - return AetheriaUnityRuntimeClientProvider.CurrentZoneContacts("unity-target-presentation"); + return TargetPresentationState.ZoneContacts.Latest(); } catch (Exception ex) { @@ -143,12 +154,12 @@ public sealed class AetheriaUnityGameplaySceneWiring MainMenu?.SetRuntimeInputScreenShell(menuShell.CanOpenRuntimeInputScreen, menuShell.ShowRuntimeInputScreen); } - public void ConfigureObservedEntityIndex(AetheriaUnityObservedEntityIndex observedEntityIndex) + public void ConfigurePresentationEntityIndex(AetheriaUnityPresentationEntityIndex presentationEntityIndex) { - Inventory?.SetObservedEntityIndex(observedEntityIndex); - ShipPanel?.SetObservedEntityIndex(observedEntityIndex); - TargetShipPanel?.SetObservedEntityIndex(observedEntityIndex); + Inventory?.SetPresentationEntityIndex(presentationEntityIndex); + ShipPanel?.SetPresentationEntityIndex(presentationEntityIndex); + TargetShipPanel?.SetPresentationEntityIndex(presentationEntityIndex); foreach (var localMenu in UnityEngine.Object.FindObjectsByType(FindObjectsSortMode.None)) - localMenu.SetObservedEntityIndex(observedEntityIndex); + localMenu.SetPresentationEntityIndex(presentationEntityIndex); } } diff --git a/Assets/Scripts/Gameplay/AetheriaUnityObservedEntityIndex.cs.meta b/Assets/Scripts/Gameplay/AetheriaUnityObservedEntityIndex.cs.meta deleted file mode 100644 index 7b4844908..000000000 --- a/Assets/Scripts/Gameplay/AetheriaUnityObservedEntityIndex.cs.meta +++ /dev/null @@ -1,2 +0,0 @@ -fileFormatVersion: 2 -guid: 1561fad48f5f1aa43a9070d788f6eaa7 \ No newline at end of file diff --git a/Assets/Scripts/Gameplay/AetheriaUnityObservedEntityRestorer.cs b/Assets/Scripts/Gameplay/AetheriaUnityObservedEntityRestorer.cs index 4851336de..0e92027ae 100644 --- a/Assets/Scripts/Gameplay/AetheriaUnityObservedEntityRestorer.cs +++ b/Assets/Scripts/Gameplay/AetheriaUnityObservedEntityRestorer.cs @@ -8,14 +8,14 @@ using GameCult.Aetheria.State.Verse; public sealed class AetheriaUnityObservedEntityRestorer { - private readonly AetheriaUnityObservedEntityIndex _entityIndex; + private readonly AetheriaUnityPresentationEntityIndex _entityIndex; private readonly ItemManager _itemManager; private readonly Func _createBlueprint; private readonly Func _createLoadoutItem; private readonly Action _logWarning; public AetheriaUnityObservedEntityRestorer( - AetheriaUnityObservedEntityIndex entityIndex, + AetheriaUnityPresentationEntityIndex entityIndex, ItemManager itemManager, Func createBlueprint, Func createLoadoutItem, @@ -53,7 +53,7 @@ public sealed class AetheriaUnityObservedEntityRestorer } ApplyInPlace(entitySnapshots); - if (_entityIndex.TryResolveEntityByRecordKey(currentEntityKey, out var resolvedCurrentEntity) && + if (_entityIndex.TryGetPresentationEntityByRecordKey(currentEntityKey, out var resolvedCurrentEntity) && currentEntity != resolvedCurrentEntity) { reboundCurrentEntity = resolvedCurrentEntity; @@ -107,7 +107,7 @@ public sealed class AetheriaUnityObservedEntityRestorer { foreach (var entitySnapshot in entitySnapshots) { - if (!_entityIndex.TryResolveEntityByRecordKey(entitySnapshot.RecordKey, out var entity)) + if (!_entityIndex.TryGetPresentationEntityByRecordKey(entitySnapshot.RecordKey, out var entity)) continue; ApplyPoseAndSimpleRuntimeState(entity, entitySnapshot); @@ -115,7 +115,7 @@ public sealed class AetheriaUnityObservedEntityRestorer if (entity.Settings != null) entity.Settings.ShutdownPerformance = (float)entitySnapshot.ShutdownPerformance; entity.RestoreThermalExposure((float)entitySnapshot.Heatstroke, (float)entitySnapshot.Hypothermia); - RestoreRuntimeBehaviorState(entity, entitySnapshot, _entityIndex.EntitiesByRecordKey); + RestoreRuntimeBehaviorState(entity, entitySnapshot, _entityIndex.PresentationEntitiesByRecordKey); } _entityIndex.RefreshDaemonIndex(); @@ -123,6 +123,7 @@ public sealed class AetheriaUnityObservedEntityRestorer private static void ApplyPoseAndSimpleRuntimeState(Entity entity, AetheriaRuntimeEntitySnapshot entitySnapshot) { + entity.DaemonRecordKey = entitySnapshot.RecordKey ?? ""; entity.DaemonEntityIndex = entitySnapshot.EntityIndex; entity.Name = entitySnapshot.Name ?? ""; entity.CultPosition = new CultMath.float3((float)entitySnapshot.PositionX, (float)entitySnapshot.PositionY, (float)entitySnapshot.PositionZ); diff --git a/Assets/Scripts/Gameplay/AetheriaUnityObservedFrameApplier.cs b/Assets/Scripts/Gameplay/AetheriaUnityObservedFrameApplier.cs index ab457c46b..e36ddbad6 100644 --- a/Assets/Scripts/Gameplay/AetheriaUnityObservedFrameApplier.cs +++ b/Assets/Scripts/Gameplay/AetheriaUnityObservedFrameApplier.cs @@ -13,7 +13,7 @@ public sealed class AetheriaUnityObservedFrameApplier private readonly Func _resolveObservedZone; private readonly Func _getZone; private readonly Action _setZone; - private readonly AetheriaUnityObservedEntityIndex _entityIndex; + private readonly AetheriaUnityPresentationEntityIndex _entityIndex; private readonly AetheriaUnityObservedEntityRestorer _entityRestorer; private readonly AetheriaUnityObservedZoneContextFactory _zoneContextFactory; private readonly Func _resolveZoneRenderer; @@ -31,7 +31,7 @@ public sealed class AetheriaUnityObservedFrameApplier Func resolveObservedZone, Func getZone, Action setZone, - AetheriaUnityObservedEntityIndex entityIndex, + AetheriaUnityPresentationEntityIndex entityIndex, AetheriaUnityObservedEntityRestorer entityRestorer, AetheriaUnityObservedZoneContextFactory zoneContextFactory, Func resolveZoneRenderer, @@ -146,8 +146,8 @@ public sealed class AetheriaUnityObservedFrameApplier } _entityRestorer.Replace(entitySnapshots, currentEntityKey, _getZone()); - zoneRenderer?.LoadDaemonZoneView(_entityIndex.EntitiesByDaemonIndex, render); - if (_entityIndex.TryResolveEntityByRecordKey(currentEntityKey, out var currentEntity)) + zoneRenderer?.LoadDaemonZoneView(_entityIndex.PresentationEntitiesByDaemonIndex, render); + if (_entityIndex.TryGetPresentationEntityByRecordKey(currentEntityKey, out var currentEntity)) _restoreCurrentEntityBinding(currentEntity); zoneRenderer?.RestoreDroppedPickupsFromZoneRender(render); _lastAppliedZoneRenderFrameId = render.FrameId; diff --git a/Assets/Scripts/Gameplay/AetheriaUnityObservedZoneContextFactory.cs b/Assets/Scripts/Gameplay/AetheriaUnityObservedZoneContextFactory.cs index 7eab7e8ef..329d5d689 100644 --- a/Assets/Scripts/Gameplay/AetheriaUnityObservedZoneContextFactory.cs +++ b/Assets/Scripts/Gameplay/AetheriaUnityObservedZoneContextFactory.cs @@ -67,6 +67,15 @@ public sealed class AetheriaUnityObservedZoneContextFactory return observedZoneContext; } + public GalaxyZone ResolveGalaxyZone(int daemonZoneIndex) + { + if (daemonZoneIndex < 0) + return null; + + _observedGalaxy ??= _resolveObservedGalaxy(); + return _observedGalaxy?.Zones?.FirstOrDefault(zone => zone != null && zone.ZoneIndex == daemonZoneIndex); + } + private static ZoneConstructionBlueprint CreateZoneConstructionBlueprint(AetheriaRuntimeZoneRenderDocument render) { var blueprint = new ZoneConstructionBlueprint diff --git a/Assets/Scripts/Gameplay/AetheriaUnityPilotFrameController.cs b/Assets/Scripts/Gameplay/AetheriaUnityPilotFrameController.cs index 4adcb2fdb..8892bb130 100644 --- a/Assets/Scripts/Gameplay/AetheriaUnityPilotFrameController.cs +++ b/Assets/Scripts/Gameplay/AetheriaUnityPilotFrameController.cs @@ -6,7 +6,7 @@ using System; using Unity.Mathematics; using UnityEngine; using UnityEngine.InputSystem; -using UnityEngine.Rendering.PostProcessing; +using UnityEngine.Rendering; using static Unity.Mathematics.math; public sealed class AetheriaUnityPilotFrameController @@ -23,8 +23,8 @@ public sealed class AetheriaUnityPilotFrameController public AetheriaInput Input { get; set; } public AetheriaUnityTargetPresentation TargetPresentation { get; set; } public AetheriaUnityPilotCommandSender PilotCommands { get; set; } - public PostProcessVolume HeatstrokePost { get; set; } - public PostProcessVolume SevereHeatstrokePost { get; set; } + public Volume HeatstrokePost { get; set; } + public Volume SevereHeatstrokePost { get; set; } public float2 Sensitivity { get; set; } public void Tick(Entity currentEntity, float deltaTime, float timeSeconds) diff --git a/Assets/Scripts/Gameplay/AetheriaUnityPilotOperationController.cs b/Assets/Scripts/Gameplay/AetheriaUnityPilotOperationController.cs index 43e6e4d0b..25f9654fd 100644 --- a/Assets/Scripts/Gameplay/AetheriaUnityPilotOperationController.cs +++ b/Assets/Scripts/Gameplay/AetheriaUnityPilotOperationController.cs @@ -9,18 +9,18 @@ using Unity.Mathematics; public sealed class AetheriaUnityPilotOperationController { private readonly Func _resolvePilotCommands; - private readonly AetheriaUnityObservedEntityIndex _observedEntityIndex; + private readonly AetheriaUnityPresentationEntityIndex _presentationEntityIndex; private readonly Func _resolveViewDirection; private readonly Func _resolveCurrentEntity; public AetheriaUnityPilotOperationController( Func resolvePilotCommands, - AetheriaUnityObservedEntityIndex observedEntityIndex, + AetheriaUnityPresentationEntityIndex presentationEntityIndex, Func resolveViewDirection, Func resolveCurrentEntity) { _resolvePilotCommands = resolvePilotCommands ?? throw new ArgumentNullException(nameof(resolvePilotCommands)); - _observedEntityIndex = observedEntityIndex ?? throw new ArgumentNullException(nameof(observedEntityIndex)); + _presentationEntityIndex = presentationEntityIndex ?? throw new ArgumentNullException(nameof(presentationEntityIndex)); _resolveViewDirection = resolveViewDirection ?? (() => default); _resolveCurrentEntity = resolveCurrentEntity ?? (() => null); } @@ -38,7 +38,7 @@ public sealed class AetheriaUnityPilotOperationController return; } - if (!_observedEntityIndex.TryResolveEntityRecordKey(target, out var targetEntityKey) || + if (!_presentationEntityIndex.TryGetRecordKeyForPresentationEntity(target, out var targetEntityKey) || string.IsNullOrWhiteSpace(targetEntityKey)) { return; diff --git a/Assets/Scripts/Gameplay/AetheriaUnityObservedEntityIndex.cs b/Assets/Scripts/Gameplay/AetheriaUnityPresentationEntityIndex.cs similarity index 68% rename from Assets/Scripts/Gameplay/AetheriaUnityObservedEntityIndex.cs rename to Assets/Scripts/Gameplay/AetheriaUnityPresentationEntityIndex.cs index b0c88a2cd..b04d7627b 100644 --- a/Assets/Scripts/Gameplay/AetheriaUnityObservedEntityIndex.cs +++ b/Assets/Scripts/Gameplay/AetheriaUnityPresentationEntityIndex.cs @@ -1,14 +1,14 @@ using System; using System.Collections.Generic; -public sealed class AetheriaUnityObservedEntityIndex +public sealed class AetheriaUnityPresentationEntityIndex { private readonly Dictionary _entitiesByRecordKey = new Dictionary(StringComparer.Ordinal); private readonly Dictionary _entitiesByDaemonIndex = new Dictionary(); - public IReadOnlyDictionary EntitiesByRecordKey => _entitiesByRecordKey; + public IReadOnlyDictionary PresentationEntitiesByRecordKey => _entitiesByRecordKey; - public IReadOnlyDictionary EntitiesByDaemonIndex => _entitiesByDaemonIndex; + public IReadOnlyDictionary PresentationEntitiesByDaemonIndex => _entitiesByDaemonIndex; public int Count => _entitiesByRecordKey.Count; @@ -26,7 +26,10 @@ public sealed class AetheriaUnityObservedEntityIndex foreach (var pair in entitiesByRecordKey) { if (!string.IsNullOrWhiteSpace(pair.Key) && pair.Value != null) + { + pair.Value.DaemonRecordKey = pair.Key; _entitiesByRecordKey[pair.Key] = pair.Value; + } } } @@ -38,12 +41,20 @@ public sealed class AetheriaUnityObservedEntityIndex RebuildDaemonIndex(); } - public bool TryResolveEntityRecordKey(Entity entity, out string recordKey) + public bool TryGetRecordKeyForPresentationEntity(Entity entity, out string recordKey) { recordKey = ""; if (entity == null) return false; + if (!string.IsNullOrWhiteSpace(entity.DaemonRecordKey) && + _entitiesByRecordKey.TryGetValue(entity.DaemonRecordKey, out var indexedEntity) && + ReferenceEquals(indexedEntity, entity)) + { + recordKey = entity.DaemonRecordKey; + return true; + } + foreach (var pair in _entitiesByRecordKey) { if (ReferenceEquals(pair.Value, entity)) @@ -56,26 +67,26 @@ public sealed class AetheriaUnityObservedEntityIndex return false; } - public bool TryResolveEntityByRecordKey(string recordKey, out Entity entity) + public bool TryGetPresentationEntityByRecordKey(string recordKey, out Entity entity) { entity = null; return !string.IsNullOrWhiteSpace(recordKey) && _entitiesByRecordKey.TryGetValue(recordKey, out entity); } - public bool TryResolveEntityByDaemonIndex(int daemonEntityIndex, out Entity entity) + public bool TryGetPresentationEntityByDaemonIndex(int daemonEntityIndex, out Entity entity) { return _entitiesByDaemonIndex.TryGetValue(daemonEntityIndex, out entity); } - public bool TryResolveDockingBayByRecordKey( + public bool TryGetPresentationDockingBayByRecordKey( string parentRecordKey, int dockingBayIndex, out EquippedDockingBay dockingBay) { dockingBay = null; if (dockingBayIndex < 0 || - !TryResolveEntityByRecordKey(parentRecordKey, out var parent) || + !TryGetPresentationEntityByRecordKey(parentRecordKey, out var parent) || parent?.DockingBays == null || dockingBayIndex >= parent.DockingBays.Count) { diff --git a/Assets/Scripts/Gameplay/AetheriaUnityPresentationEntityIndex.cs.meta b/Assets/Scripts/Gameplay/AetheriaUnityPresentationEntityIndex.cs.meta new file mode 100644 index 000000000..072149d5a --- /dev/null +++ b/Assets/Scripts/Gameplay/AetheriaUnityPresentationEntityIndex.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 1561fad48f5f1aa43a9070d788f6eaa7 diff --git a/Assets/Scripts/Gameplay/AetheriaUnityRenderSplatViewportSource.cs b/Assets/Scripts/Gameplay/AetheriaUnityRenderSplatViewportSource.cs index 5ad76b995..665c56e54 100644 --- a/Assets/Scripts/Gameplay/AetheriaUnityRenderSplatViewportSource.cs +++ b/Assets/Scripts/Gameplay/AetheriaUnityRenderSplatViewportSource.cs @@ -27,6 +27,8 @@ public sealed class AetheriaUnityRenderSplatViewportSource : MonoBehaviour private float _nextRefreshTime; private AetheriaUnityRtsViewportDocuments _viewportDocuments; + private AetheriaRuntimeRenderSplatsViewportDocument _fallbackRenderSplatsViewport; + private string _lastWarningMessage = ""; public RenderTexture TargetTexture { @@ -39,6 +41,15 @@ public sealed class AetheriaUnityRenderSplatViewportSource : MonoBehaviour } } + public AetheriaRuntimeRenderSplatsViewportDocument CurrentDocument => + _viewportDocuments?.CurrentRenderSplatsViewport ?? _fallbackRenderSplatsViewport; + + public bool RenderInLateUpdate + { + get => renderInLateUpdate; + set => renderInLateUpdate = value; + } + private void Reset() { rasterizer = GetComponent(); @@ -63,7 +74,7 @@ public sealed class AetheriaUnityRenderSplatViewportSource : MonoBehaviour return false; RefreshDocument(false); - var document = _viewportDocuments?.CurrentRenderSplatsViewport; + var document = _viewportDocuments?.CurrentRenderSplatsViewport ?? _fallbackRenderSplatsViewport; if (document == null) return false; @@ -118,13 +129,36 @@ public sealed class AetheriaUnityRenderSplatViewportSource : MonoBehaviour .RenderSplatViewportDocuments(viewport, "unity-render-splat-viewport"); ClearViewportDocument(); _viewportDocuments = nextDocuments; + _fallbackRenderSplatsViewport = null; + _lastWarningMessage = ""; } catch (Exception ex) { - Debug.LogWarning($"Failed to read Aetheria render splats from local Verse state: {ex.Message}"); + ClearViewportDocument(); + _fallbackRenderSplatsViewport = EmptyRenderSplatsViewport(viewport); + WarnOnce($"Failed to read Aetheria render splats from local Verse state: {ex.Message}"); } } + private void WarnOnce(string message) + { + if (string.Equals(_lastWarningMessage, message, StringComparison.Ordinal)) + return; + + _lastWarningMessage = message; + Debug.LogWarning(message); + } + + private static AetheriaRuntimeRenderSplatsViewportDocument EmptyRenderSplatsViewport( + AetheriaRuntimeRtsViewportBounds viewport) + { + return new AetheriaRuntimeRenderSplatsViewportDocument + { + PublishedAtUtc = DateTimeOffset.UtcNow.ToString("O"), + Viewport = viewport ?? new AetheriaRuntimeRtsViewportBounds() + }; + } + private AetheriaRuntimeRtsViewportBounds ResolveViewportBounds() { var halfSize = size * 0.5f; diff --git a/Assets/Scripts/Gameplay/AetheriaUnityRtsViewportDocuments.cs b/Assets/Scripts/Gameplay/AetheriaUnityRtsViewportDocuments.cs index 67ae88d37..2d70f1369 100644 --- a/Assets/Scripts/Gameplay/AetheriaUnityRtsViewportDocuments.cs +++ b/Assets/Scripts/Gameplay/AetheriaUnityRtsViewportDocuments.cs @@ -21,9 +21,9 @@ public sealed class AetheriaUnityRtsViewportDocuments : IDisposable throw new ArgumentNullException(nameof(viewport)); Viewport = viewport; - _contacts = bindContacts ? state.ReactiveZoneContacts() : null; - _objectsViewport = bindObjects ? state.ReactiveObjectsViewport(viewport) : null; - _renderSplatsViewport = bindRenderSplats ? state.ReactiveRenderSplatsViewport(viewport) : null; + _contacts = bindContacts ? state.ZoneContacts.Reactive() : null; + _objectsViewport = bindObjects ? state.ObjectsViewport(viewport).Reactive() : null; + _renderSplatsViewport = bindRenderSplats ? state.RenderSplatsViewport(viewport).Reactive() : null; } public AetheriaRuntimeRtsViewportBounds Viewport { get; } diff --git a/Assets/Scripts/Gameplay/AetheriaUnityRuntimeClientProvider.cs b/Assets/Scripts/Gameplay/AetheriaUnityRuntimeClientProvider.cs index f96da17c3..19264fea3 100644 --- a/Assets/Scripts/Gameplay/AetheriaUnityRuntimeClientProvider.cs +++ b/Assets/Scripts/Gameplay/AetheriaUnityRuntimeClientProvider.cs @@ -77,104 +77,6 @@ public static class AetheriaUnityRuntimeClientProvider return RuntimeClient(stateBoot, runtimeId).State; } - public static AetheriaRuntimeCurrentDockingDocument CurrentDockingState(string runtimeId = "") - { - return RuntimeState(runtimeId).CurrentDockingState(); - } - - public static AetheriaRuntimeCurrentDockingDocument CurrentDockingState( - AetheriaRuntimeStateBootReport stateBoot, - string runtimeId = "") - { - return RuntimeState(stateBoot, runtimeId).CurrentDockingState(); - } - - public static AetheriaRuntimeCatalogSnapshot CurrentCatalog(string runtimeId = "") - { - return RuntimeState(runtimeId).CurrentCatalog(); - } - - public static AetheriaRuntimeCatalogSnapshot CurrentCatalog( - AetheriaRuntimeStateBootReport stateBoot, - string runtimeId = "") - { - return RuntimeState(stateBoot, runtimeId).CurrentCatalog(); - } - - public static AetheriaRuntimePlayerSettingsDocument CurrentPlayerSettings(string runtimeId = "") - { - return RuntimeState(runtimeId).CurrentPlayerSettings(); - } - - public static AetheriaRuntimePlayerSettingsDocument CurrentPlayerSettings( - AetheriaRuntimeStateBootReport stateBoot, - string runtimeId = "") - { - return RuntimeState(stateBoot, runtimeId).CurrentPlayerSettings(); - } - - public static AetheriaRuntimeCurrentEntityDocument CurrentEntityState(string runtimeId = "") - { - return RuntimeState(runtimeId).CurrentEntityState(); - } - - public static AetheriaRuntimeCurrentZoneDocument CurrentZoneState(string runtimeId = "") - { - return RuntimeState(runtimeId).CurrentZoneState(); - } - - public static AetheriaRuntimeZoneContactsDocument CurrentZoneContacts(string runtimeId = "") - { - return RuntimeState(runtimeId).CurrentZoneContacts(); - } - - public static CultMeshReactiveDocument ReactiveZoneContacts( - string runtimeId = "") - { - return RuntimeState(runtimeId).ReactiveZoneContacts(); - } - - public static AetheriaRuntimeStationRefitDocument CurrentStationRefit(string runtimeId = "") - { - return RuntimeState(runtimeId).CurrentStationRefit(); - } - - public static AetheriaRuntimeSectorMapDocument CurrentSectorMap(string runtimeId = "") - { - return RuntimeState(runtimeId).CurrentSectorMap(); - } - - public static AetheriaRuntimeSectorMapDocument CurrentSectorMap( - AetheriaRuntimeStateBootReport stateBoot, - string runtimeId = "") - { - return RuntimeState(stateBoot, runtimeId).CurrentSectorMap(); - } - - public static AetheriaRuntimeZoneDetailsDocument CurrentZoneDetails(int zoneIndex, string runtimeId = "") - { - return RuntimeState(runtimeId).CurrentZoneDetails(zoneIndex); - } - - public static AetheriaRuntimeInventoryDocument CurrentInventory(int entityIndex, string runtimeId = "") - { - return RuntimeState(runtimeId).CurrentInventory(entityIndex); - } - - public static CultMeshReactiveDocument ReactiveObjectsViewport( - AetheriaRuntimeRtsViewportBounds viewport, - string runtimeId = "") - { - return RuntimeState(runtimeId).ReactiveObjectsViewport(viewport); - } - - public static CultMeshReactiveDocument ReactiveRenderSplatsViewport( - AetheriaRuntimeRtsViewportBounds viewport, - string runtimeId = "") - { - return RuntimeState(runtimeId).ReactiveRenderSplatsViewport(viewport); - } - public static AetheriaUnityRtsViewportDocuments MapViewportDocuments( AetheriaRuntimeRtsViewportBounds viewport, string runtimeId = "") @@ -196,18 +98,6 @@ public static class AetheriaUnityRuntimeClientProvider return AetheriaUnityRtsViewportDocuments.OpenRenderSplats(RuntimeState(runtimeId), viewport); } - public static AetheriaRuntimeDaemonFrameDocument CurrentDaemonFrame(string runtimeId = "") - { - return RuntimeState(runtimeId).CurrentDaemonFrame(); - } - - public static AetheriaRuntimeDaemonFrameDocument CurrentDaemonFrame( - AetheriaRuntimeStateBootReport stateBoot, - string runtimeId = "") - { - return RuntimeState(stateBoot, runtimeId).CurrentDaemonFrame(); - } - public static AetheriaUnityDaemonRenderDocuments DaemonRenderDocuments( AetheriaRuntimeStateBootReport stateBoot, string runtimeId = "") @@ -215,13 +105,6 @@ public static class AetheriaUnityRuntimeClientProvider return AetheriaUnityDaemonRenderDocuments.Open(RuntimeState(stateBoot, runtimeId)); } - public static AetheriaRuntimeVerseHostSettingsDocument CurrentVerseHostSettings( - AetheriaRuntimeStateBootReport stateBoot, - string runtimeId = "") - { - return RuntimeState(stateBoot, runtimeId).CurrentVerseHostSettings(); - } - public static AetheriaControl Control(string runtimeId = "") { return RuntimeClient(runtimeId).Control; @@ -277,7 +160,7 @@ public static class AetheriaUnityRuntimeClientProvider { try { - var stored = CurrentPlayerSettings(); + var stored = RuntimeState().PlayerSettings.Latest(); if (stored == null) return; diff --git a/Assets/Scripts/Rendering.meta b/Assets/Scripts/Rendering.meta new file mode 100644 index 000000000..6cef0019d --- /dev/null +++ b/Assets/Scripts/Rendering.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: b3c453d425710cb47bfcde9c9acf3b15 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Rendering/AetheriaStardustRendererFeature.cs b/Assets/Scripts/Rendering/AetheriaStardustRendererFeature.cs new file mode 100644 index 000000000..65b8675e9 --- /dev/null +++ b/Assets/Scripts/Rendering/AetheriaStardustRendererFeature.cs @@ -0,0 +1,59 @@ +using UnityEngine; +using UnityEngine.Rendering; +using UnityEngine.Rendering.RenderGraphModule; +using UnityEngine.Rendering.Universal; + +public sealed class AetheriaStardustRendererFeature : ScriptableRendererFeature +{ + private StardustPass _pass; + + public override void Create() + { + _pass = new StardustPass + { + renderPassEvent = RenderPassEvent.BeforeRenderingPostProcessing + }; + } + + public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData) + { + if (_pass == null) + Create(); + + renderer.EnqueuePass(_pass); + } + + private sealed class StardustPass : ScriptableRenderPass + { + private sealed class PassData + { + internal Camera Camera; + internal TextureHandle ColorTarget; + internal TextureHandle DepthTarget; + } + + public override void RecordRenderGraph(RenderGraph renderGraph, ContextContainer frameData) + { + var cameraData = frameData.Get(); + var resourceData = frameData.Get(); + var colorTarget = resourceData.activeColorTexture; + if (!colorTarget.IsValid()) + return; + + using var builder = renderGraph.AddUnsafePass("Aetheria Stardust", out var passData); + passData.Camera = cameraData.camera; + passData.ColorTarget = colorTarget; + passData.DepthTarget = resourceData.activeDepthTexture; + + builder.UseTexture(passData.ColorTarget, AccessFlags.ReadWrite); + if (passData.DepthTarget.IsValid()) + builder.UseTexture(passData.DepthTarget, AccessFlags.Read); + + builder.AllowPassCulling(false); + builder.SetRenderFunc(static (PassData data, UnsafeGraphContext context) => + { + Stardust.RenderForCamera(context.cmd, data.Camera, data.ColorTarget, data.DepthTarget); + }); + } + } +} diff --git a/Assets/Scripts/Rendering/AetheriaStardustRendererFeature.cs.meta b/Assets/Scripts/Rendering/AetheriaStardustRendererFeature.cs.meta new file mode 100644 index 000000000..813079808 --- /dev/null +++ b/Assets/Scripts/Rendering/AetheriaStardustRendererFeature.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 8ed96b0a823f44f9afc6c62a94ddc98d diff --git a/Assets/Scripts/Rendering/AetheriaVolumeCloudRendererFeature.cs b/Assets/Scripts/Rendering/AetheriaVolumeCloudRendererFeature.cs new file mode 100644 index 000000000..189b43737 --- /dev/null +++ b/Assets/Scripts/Rendering/AetheriaVolumeCloudRendererFeature.cs @@ -0,0 +1,65 @@ +using UnityEngine; +using UnityEngine.Rendering; +using UnityEngine.Rendering.RenderGraphModule; +using UnityEngine.Rendering.Universal; + +public sealed class AetheriaVolumeCloudRendererFeature : ScriptableRendererFeature +{ + private VolumeCloudPass _pass; + + public override void Create() + { + _pass = new VolumeCloudPass + { + renderPassEvent = RenderPassEvent.BeforeRenderingPostProcessing + }; + _pass.ConfigureInput(ScriptableRenderPassInput.Depth); + } + + public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData) + { + if (_pass == null) + Create(); + + renderer.EnqueuePass(_pass); + } + + private sealed class VolumeCloudPass : ScriptableRenderPass + { + private sealed class PassData + { + internal Camera Camera; + internal VolumeCloudRenderer Renderer; + internal TextureHandle ColorTarget; + internal TextureHandle DepthTarget; + } + + public override void RecordRenderGraph(RenderGraph renderGraph, ContextContainer frameData) + { + var cameraData = frameData.Get(); + if (!VolumeCloudRenderer.TryGetRenderer(cameraData.camera, out var renderer)) + return; + + var resourceData = frameData.Get(); + var colorTarget = resourceData.activeColorTexture; + if (!colorTarget.IsValid()) + return; + + using var builder = renderGraph.AddUnsafePass("Aetheria Volume Clouds", out var passData); + passData.Camera = cameraData.camera; + passData.Renderer = renderer; + passData.ColorTarget = colorTarget; + passData.DepthTarget = resourceData.activeDepthTexture; + + builder.UseTexture(passData.ColorTarget, AccessFlags.ReadWrite); + if (passData.DepthTarget.IsValid()) + builder.UseTexture(passData.DepthTarget, AccessFlags.Read); + + builder.AllowPassCulling(false); + builder.SetRenderFunc(static (PassData data, UnsafeGraphContext context) => + { + data.Renderer.RenderClouds(context.cmd, data.Camera, data.ColorTarget, data.DepthTarget); + }); + } + } +} diff --git a/Assets/Scripts/Rendering/AetheriaVolumeCloudRendererFeature.cs.meta b/Assets/Scripts/Rendering/AetheriaVolumeCloudRendererFeature.cs.meta new file mode 100644 index 000000000..17903f27e --- /dev/null +++ b/Assets/Scripts/Rendering/AetheriaVolumeCloudRendererFeature.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 3a9355da1f1b4fd489be0de05ebd165b diff --git a/Assets/Scripts/Rendering/AetheriaVolumeSamplingRendererFeature.cs b/Assets/Scripts/Rendering/AetheriaVolumeSamplingRendererFeature.cs new file mode 100644 index 000000000..305168494 --- /dev/null +++ b/Assets/Scripts/Rendering/AetheriaVolumeSamplingRendererFeature.cs @@ -0,0 +1,44 @@ +using UnityEngine.Rendering; +using UnityEngine.Rendering.RenderGraphModule; +using UnityEngine.Rendering.Universal; + +public sealed class AetheriaVolumeSamplingRendererFeature : ScriptableRendererFeature +{ + private VolumeSamplingPass _pass; + + public override void Create() + { + _pass = new VolumeSamplingPass + { + renderPassEvent = RenderPassEvent.BeforeRendering + }; + } + + public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData) + { + if (_pass == null) + Create(); + + renderer.EnqueuePass(_pass); + } + + private sealed class VolumeSamplingPass : ScriptableRenderPass + { + private sealed class PassData + { + internal UnityEngine.Camera Camera; + } + + public override void RecordRenderGraph(RenderGraph renderGraph, ContextContainer frameData) + { + using var builder = renderGraph.AddUnsafePass("Aetheria Volume Sampling", out var passData); + var cameraData = frameData.Get(); + passData.Camera = cameraData.camera; + builder.AllowPassCulling(false); + builder.SetRenderFunc(static (PassData data, UnsafeGraphContext context) => + { + VolumeSampling.RenderForCamera(data.Camera); + }); + } + } +} diff --git a/Assets/Scripts/Rendering/AetheriaVolumeSamplingRendererFeature.cs.meta b/Assets/Scripts/Rendering/AetheriaVolumeSamplingRendererFeature.cs.meta new file mode 100644 index 000000000..2874bc4d4 --- /dev/null +++ b/Assets/Scripts/Rendering/AetheriaVolumeSamplingRendererFeature.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 24eee20c9f0bf4148804bc31f1be1482 \ No newline at end of file diff --git a/Assets/Scripts/ServerShared/Entity.cs b/Assets/Scripts/ServerShared/Entity.cs index c5914fbe2..2fcf45e14 100644 --- a/Assets/Scripts/ServerShared/Entity.cs +++ b/Assets/Scripts/ServerShared/Entity.cs @@ -43,6 +43,7 @@ public abstract class Entity public Faction Faction; public EquippableItem Hull; public EquippedItem EquippedHull; + public string DaemonRecordKey = ""; public int DaemonEntityIndex = -1; public float3 Position; diff --git a/Assets/Scripts/Tests/DaemonRuntimeDocumentTests.cs b/Assets/Scripts/Tests/DaemonRuntimeDocumentTests.cs index ba4ed3ef9..9731c0a2e 100644 --- a/Assets/Scripts/Tests/DaemonRuntimeDocumentTests.cs +++ b/Assets/Scripts/Tests/DaemonRuntimeDocumentTests.cs @@ -188,11 +188,11 @@ public class DaemonRuntimeDocumentTests MaxX = 100, MaxY = 100 }; - var currentCatalog = client.State.CurrentCatalog(); - var currentPlayerSettings = client.State.CurrentPlayerSettings(); - var currentFrame = client.State.CurrentDaemonFrame(); - var currentEntityState = client.State.CurrentEntityState(); - var currentObjectsViewport = client.State.CurrentObjectsViewport(viewport); + var currentCatalog = client.State.Catalog.Latest(); + var currentPlayerSettings = client.State.PlayerSettings.Latest(); + var currentFrame = client.State.DaemonFrame.Latest(); + var currentEntityState = client.State.CurrentEntity.Latest(); + var currentObjectsViewport = client.State.ObjectsViewport(viewport).Latest(); var objectsViewport = ReadLatest(client.State.ObjectsViewport(viewport)); var zoneDetails = ReadLatest(client.State.ZoneDetails(0)); var inventory = ReadLatest(client.State.Inventory(0)); @@ -206,35 +206,35 @@ public class DaemonRuntimeDocumentTests .ReactiveAsync() .GetAwaiter() .GetResult(); - using var zoneRenderReactive = client.State.ReactiveZoneRender(); + using var zoneRenderReactive = client.State.ZoneRender.Reactive(); var observedAuthoritativeFrame = ReadLatest(client.State.DaemonFrame); - using var catalogReactive = client.State.ReactiveCatalogSnapshot(); - using var daemonFrameReactive = client.State.ReactiveDaemonFrame(); - using var daemonSoaViewReactive = client.State.ReactiveDaemonSoaView(); + using var catalogReactive = client.State.Catalog.Reactive(); + using var daemonFrameReactive = client.State.DaemonFrame.Reactive(); + using var daemonSoaViewReactive = client.State.DaemonSoaView.Reactive(); Assert.IsTrue(AetheriaRuntimeDaemonRenderView.TryCreateCurrent( daemonFrameReactive, daemonSoaViewReactive, zoneRenderReactive, out var observed)); - using var loadoutTemplatesReactive = client.State.ReactiveLoadoutTemplates(); - using var sectorMapReactive = client.State.ReactiveSectorMap(); - using var playerSettingsReactive = client.State.ReactivePlayerSettingsDocument(); - using var verseHostSettingsReactive = client.State.ReactiveVerseHostSettings(); - using var zoneContactsReactive = client.State.ReactiveZoneContacts(); - using var currentZoneReactive = client.State.ReactiveCurrentZone(); - using var currentEntityDocumentReactive = client.State.ReactiveCurrentEntity(); - using var currentDockingReactive = client.State.ReactiveCurrentDocking(); - using var stationRefitReactive = client.State.ReactiveStationRefit(); - using var zoneDetailsReactive = client.State.ReactiveZoneDetails(0); - using var selectedObjectReactive = client.State.ReactiveSelectedObject(0); - using var inventoryReactive = client.State.ReactiveInventory(0); - using var mapViewportReactive = client.State.ReactiveRtsViewport(viewport); - using var objectsViewportReactive = client.State.ReactiveObjectsViewport(viewport); - using var gravityViewportReactive = client.State.ReactiveGravityViewport(viewport); - using var renderSplatsViewportReactive = client.State.ReactiveRenderSplatsViewport(viewport); - using var playerHudCatalog = client.State.ReactiveCatalogSnapshot(); - using var playerHudSettings = client.State.ReactivePlayerSettingsDocument(); - using var playerHudEntity = client.State.ReactiveCurrentEntity(); + using var loadoutTemplatesReactive = client.State.LoadoutTemplates.Reactive(); + using var sectorMapReactive = client.State.SectorMap.Reactive(); + using var playerSettingsReactive = client.State.PlayerSettings.Reactive(); + using var verseHostSettingsReactive = client.State.VerseHostSettings.Reactive(); + using var zoneContactsReactive = client.State.ZoneContacts.Reactive(); + using var currentZoneReactive = client.State.CurrentZone.Reactive(); + using var currentEntityDocumentReactive = client.State.CurrentEntity.Reactive(); + using var currentDockingReactive = client.State.CurrentDocking.Reactive(); + using var stationRefitReactive = client.State.StationRefit.Reactive(); + using var zoneDetailsReactive = client.State.ZoneDetails(0).Reactive(); + using var selectedObjectReactive = client.State.SelectedObject(0).Reactive(); + using var inventoryReactive = client.State.Inventory(0).Reactive(); + using var mapViewportReactive = client.State.RtsViewport(viewport).Reactive(); + using var objectsViewportReactive = client.State.ObjectsViewport(viewport).Reactive(); + using var gravityViewportReactive = client.State.GravityViewport(viewport).Reactive(); + using var renderSplatsViewportReactive = client.State.RenderSplatsViewport(viewport).Reactive(); + using var playerHudCatalog = client.State.Catalog.Reactive(); + using var playerHudSettings = client.State.PlayerSettings.Reactive(); + using var playerHudEntity = client.State.CurrentEntity.Reactive(); var gameSurface = ReadLatest(client.State.GameSurface); var gameTuiSurface = ReadLatest(client.State.GameTuiSurface); var editorSurface = ReadLatest(client.State.EditorSurface); @@ -247,7 +247,7 @@ public class DaemonRuntimeDocumentTests var stationRefit = ReadLatest(client.State.StationRefit); Assert.AreEqual(currentEntity.EntityKey, currentEntityDocumentReactive.Current.EntityKey); Assert.AreEqual(currentDocking.CurrentEntityKey, currentDockingReactive.Current.CurrentEntityKey); - Assert.AreEqual(stationRefit.StationEntityKey, stationRefitReactive.Current.StationEntityKey); + Assert.AreEqual(stationRefit.CurrentEntityKey, stationRefitReactive.Current.CurrentEntityKey); using var reactiveGameSurface = client.State.GameSurface.Reactive(); using var reactiveGameTuiSurface = client.State.GameTuiSurface.Reactive(); using var reactiveEditorSurface = client.State.EditorSurface.Reactive(); @@ -268,7 +268,7 @@ public class DaemonRuntimeDocumentTests Assert.AreEqual(AetheriaRuntimeDaemonEditorSurfaceBuilder.SurfaceId, reactiveEditorSurface.Current.Surface.Id); Assert.AreEqual(AetheriaRuntimeDaemonEditorSurfaceBuilder.TuiSurfaceId, reactiveEditorTuiSurface.Current.Surface.Id); Assert.AreEqual(currentDocking.CurrentEntityKey, currentDockingReactive.Current.CurrentEntityKey); - Assert.AreEqual(stationRefit.StationEntityKey, stationRefitReactive.Current.StationEntityKey); + Assert.AreEqual(stationRefit.CurrentEntityKey, stationRefitReactive.Current.CurrentEntityKey); Assert.IsNotNull(ReadLatest(client.State.PlayerSettings)); Assert.IsNotNull(ReadLatest(client.State.VerseHostSettings)); Assert.IsNotNull(ReadLatest(client.State.ZoneContacts)); @@ -301,7 +301,7 @@ public class DaemonRuntimeDocumentTests Assert.AreSame(catalog, catalogReactive.Current); Assert.AreEqual(AetheriaRuntimeDaemonSchemas.Frame, daemonFrameReactive.Current.Schema); Assert.AreEqual(AetheriaRuntimeDaemonSchemas.SoaView, daemonSoaViewReactive.Current.Schema); - Assert.AreEqual(AetheriaRuntimeLoadoutTemplatesDocument.SchemaId, loadoutTemplatesReactive.Current.Schema); + Assert.AreEqual(typeof(AetheriaRuntimeLoadoutTemplatesDocument), client.State.LoadoutTemplates.DocumentType); Assert.AreEqual(AetheriaRuntimeDaemonSchemas.SectorMap, sectorMapReactive.Current.Schema); Assert.AreEqual(AetheriaRuntimePlayerSettingsDocument.SchemaId, playerSettingsReactive.Current.Schema); Assert.AreEqual(AetheriaRuntimeVerseHostSettingsDocument.SchemaId, verseHostSettings.Schema); @@ -395,19 +395,19 @@ public class DaemonRuntimeDocumentTests .GetResult(); var aetheria = verse.Aetheria(); - var currentEntity = ReadLatest(aetheria.Document()); + var currentEntity = ReadLatest(aetheria.CurrentEntity); var latestFrameDocument = verse.Document( AetheriaRuntimeVerseRecordKeys.DaemonFrameLatest); using var latestFrameReactive = latestFrameDocument.Reactive(); - Assert.AreEqual("aetheria.current.entity", aetheria.Current.Entity.DocumentId); + Assert.AreEqual("aetheria.current.entity", aetheria.CurrentEntity.DocumentId); Assert.AreEqual("zone.0.entity.0", currentEntity.EntityKey); Assert.AreEqual("Player", currentEntity.Entity?.DisplayName); Assert.AreEqual(AetheriaRuntimeVerseRecordKeys.DaemonFrameLatest.ToString(), latestFrameDocument.DocumentId); Assert.AreEqual(AetheriaRuntimeDaemonSchemas.Frame, latestFrameReactive.Current.Schema); Assert.AreEqual(8, latestFrameReactive.Current.FrameId); Assert.IsTrue(latestFrameDocument.CanSet); - Assert.IsTrue(aetheria.Current.Entity.Sources.Any(source => + Assert.IsTrue(aetheria.CurrentEntity.Sources.Any(source => source.SourceId == AetheriaRuntimeVerseRecordKeys.DaemonFrameLatest.ToString())); } @@ -599,16 +599,16 @@ public class DaemonRuntimeDocumentTests var unityEditorSurfaceState = ReadLatest(client.State.EditorSurface); var unityEditorTuiSurfaceState = ReadLatest(client.State.EditorTuiSurface); var surfaceResolver = client.State.CreateEveSurfaceCultMeshStateRefResolver(); - var unityGameSurface = AetheriaRuntimeEveSurfaceAdapter.ToEveSurfaceDocument( + var unityGameSurface = AetheriaRuntimeSurfaceDocuments.ToEveSurfaceDocument( unityGameSurfaceState, surfaceResolver); - var unityGameTuiSurface = AetheriaRuntimeEveSurfaceAdapter.ToEveSurfaceDocument( + var unityGameTuiSurface = AetheriaRuntimeSurfaceDocuments.ToEveSurfaceDocument( unityGameTuiSurfaceState, surfaceResolver); - var unityEditorSurface = AetheriaRuntimeEveSurfaceAdapter.ToEveSurfaceDocument( + var unityEditorSurface = AetheriaRuntimeSurfaceDocuments.ToEveSurfaceDocument( unityEditorSurfaceState, surfaceResolver); - var unityEditorTuiSurface = AetheriaRuntimeEveSurfaceAdapter.ToEveSurfaceDocument( + var unityEditorTuiSurface = AetheriaRuntimeSurfaceDocuments.ToEveSurfaceDocument( unityEditorTuiSurfaceState, surfaceResolver); @@ -628,7 +628,7 @@ public class DaemonRuntimeDocumentTests command.Command == "aetheria.daemon.commands.TransferCargoItem")); Assert.AreEqual(AetheriaRuntimeDaemonEditorSurfaceBuilder.TuiSurfaceId, unityEditorTuiSurface.Surface.Id); Assert.AreEqual("editor.daemon", unityEditorTuiSurface.ProviderKind); - var genericGameSurface = AetheriaRuntimeEveSurfaceAdapter.ToEveSurfaceDocument( + var genericGameSurface = AetheriaRuntimeSurfaceDocuments.ToEveSurfaceDocument( ReadLatest(client.State.GameSurface), surfaceResolver); Assert.AreEqual(AetheriaRuntimeDaemonGameSurfaceBuilder.SurfaceId, genericGameSurface.Surface.Id); @@ -651,18 +651,18 @@ public class DaemonRuntimeDocumentTests "value", AetheriaRuntimeDaemonStateRefs.CurrentTargetName, AetheriaRuntimeDaemonSchemas.Frame)); - var genericGameTuiSurface = AetheriaRuntimeEveSurfaceAdapter.ToEveSurfaceDocument( + var genericGameTuiSurface = AetheriaRuntimeSurfaceDocuments.ToEveSurfaceDocument( ReadLatest(client.State.GameTuiSurface), surfaceResolver); Assert.AreEqual(AetheriaRuntimeDaemonGameSurfaceBuilder.TuiSurfaceId, genericGameTuiSurface.Surface.Id); - var genericEditorTuiSurface = AetheriaRuntimeEveSurfaceAdapter.ToEveSurfaceDocument( + var genericEditorTuiSurface = AetheriaRuntimeSurfaceDocuments.ToEveSurfaceDocument( ReadLatest(client.State.EditorTuiSurface), surfaceResolver); Assert.AreEqual(AetheriaRuntimeDaemonEditorSurfaceBuilder.TuiSurfaceId, genericEditorTuiSurface.Surface.Id); } [Test] - public void EveSurfaceAdapterResolvesDaemonStateRefsBeforeLowering() + public void RuntimeSurfaceDocumentsResolveDaemonStateRefsBeforeLowering() { var surface = new EveSurfaceDocument( "surface-state", @@ -690,7 +690,7 @@ public class DaemonRuntimeDocumentTests var resolver = CultMesh.StateRefResolver( "aetheria.tests.refs", (stateRef, _context) => stateRef == AetheriaRuntimeDaemonStateRefs.CurrentTargetName ? "Live Target" : ""); - var resolved = AetheriaRuntimeEveSurfaceAdapter.ResolveStateRefs(surface, resolver); + var resolved = AetheriaRuntimeSurfaceDocuments.ResolveStateRefs(surface, resolver); Assert.IsTrue(ContainsEveSurfaceMetric(resolved.Surface.Root, "Target", "Live Target")); Assert.IsTrue(ContainsEveSurfaceProp( @@ -700,7 +700,7 @@ public class DaemonRuntimeDocumentTests } [Test] - public void EveSurfaceAdapterResolvesTypedPointerPropsBeforeLowering() + public void RuntimeSurfaceDocumentsResolveTypedPointerPropsBeforeLowering() { var surface = new EveSurfaceDocument( "surface-state", @@ -729,7 +729,7 @@ public class DaemonRuntimeDocumentTests var resolver = CultMesh.StateRefResolver( "aetheria.tests.refs", (stateRef, _context) => stateRef == AetheriaRuntimeDaemonStateRefs.CurrentTargetName ? "Live Target" : ""); - var resolved = AetheriaRuntimeEveSurfaceAdapter.ResolveStateRefs(surface, resolver); + var resolved = AetheriaRuntimeSurfaceDocuments.ResolveStateRefs(surface, resolver); Assert.IsTrue(ContainsEveSurfaceProp(resolved.Surface.Root, "description", "Live Target")); Assert.IsTrue(ContainsEveSurfaceProp(resolved.Surface.Root, "descriptionRef", AetheriaRuntimeDaemonStateRefs.CurrentTargetName)); @@ -2207,9 +2207,9 @@ public class DaemonRuntimeDocumentTests .OpenAsync(statePath, "unity-observer-test", pullOnOpen: true) .GetAwaiter() .GetResult(); - using var observedFrame = client.State.ReactiveDaemonFrame(); - using var observedSoaView = client.State.ReactiveDaemonSoaView(); - using var observedZoneRender = client.State.ReactiveZoneRender(); + using var observedFrame = client.State.DaemonFrame.Reactive(); + using var observedSoaView = client.State.DaemonSoaView.Reactive(); + using var observedZoneRender = client.State.ZoneRender.Reactive(); Assert.IsTrue(AetheriaRuntimeDaemonRenderView.TryCreateCurrent( observedFrame, observedSoaView, @@ -2265,9 +2265,9 @@ public class DaemonRuntimeDocumentTests .OpenAsync(statePath, "unity-reactive-observer-test", pullOnOpen: true) .GetAwaiter() .GetResult(); - using var observedFrame = client.State.ReactiveDaemonFrame(); - using var observedSoaView = client.State.ReactiveDaemonSoaView(); - using var observedZoneRender = client.State.ReactiveZoneRender(); + using var observedFrame = client.State.DaemonFrame.Reactive(); + using var observedSoaView = client.State.DaemonSoaView.Reactive(); + using var observedZoneRender = client.State.ZoneRender.Reactive(); var observed = AetheriaRuntimeDaemonRenderView.TryCreateCurrent( observedFrame, observedSoaView, @@ -2312,9 +2312,9 @@ public class DaemonRuntimeDocumentTests .OpenAsync(statePath, "unity-frame-only-observer-test", pullOnOpen: true) .GetAwaiter() .GetResult(); - using var observedFrame = client.State.ReactiveDaemonFrame(); - using var observedSoaView = client.State.ReactiveDaemonSoaView(); - using var observedZoneRender = client.State.ReactiveZoneRender(); + using var observedFrame = client.State.DaemonFrame.Reactive(); + using var observedSoaView = client.State.DaemonSoaView.Reactive(); + using var observedZoneRender = client.State.ZoneRender.Reactive(); Assert.IsTrue(AetheriaRuntimeDaemonRenderView.TryCreateCurrent( observedFrame, observedSoaView, @@ -2375,7 +2375,7 @@ public class DaemonRuntimeDocumentTests } [Test] - public void CommandClientSendsCommandAgainstDaemonFrame + public void CommandClientSendsCommandAgainstDaemonFrame() { var statePath = Path.Combine( Path.GetTempPath(), diff --git a/Assets/Scripts/Tests/StarbridgePlayerSeatDocumentTests.cs b/Assets/Scripts/Tests/StarbridgePlayerSeatDocumentTests.cs index 1fd779c9a..080cd8ffe 100644 --- a/Assets/Scripts/Tests/StarbridgePlayerSeatDocumentTests.cs +++ b/Assets/Scripts/Tests/StarbridgePlayerSeatDocumentTests.cs @@ -82,12 +82,12 @@ public class StarbridgePlayerSeatDocumentTests var handle = client.Document( AetheriaRuntimeVerseRecordKeys.StarbridgePlayerSeat(seat.SeatId)); using var reactive = handle.Reactive(); - using var aetheriaSeat = client.State.ReactiveStarbridgePlayerSeat(seat.SeatId); + using var aetheriaSeat = client.Aetheria().StarbridgePlayerSeat(seat.SeatId).Reactive(); Assert.AreEqual("seat-support", handle.Latest().SeatId); Assert.AreEqual( "seat-support", - ReadLatest(client.State.StarbridgePlayerSeat(seat.SeatId)).SeatId); + ReadLatest(client.Aetheria().StarbridgePlayerSeat(seat.SeatId)).SeatId); Assert.AreEqual("item:cockpit:support", reactive.Current.CockpitItemKey); Assert.AreEqual("item:cockpit:support", aetheriaSeat.Current.CockpitItemKey); Assert.AreEqual("entity:pod:support", reactive.Current.EscapePodEntityKey); diff --git a/Assets/Scripts/Tests/Tests.asmdef b/Assets/Scripts/Tests/Tests.asmdef index ef8a1ec79..f07f01bdd 100644 --- a/Assets/Scripts/Tests/Tests.asmdef +++ b/Assets/Scripts/Tests/Tests.asmdef @@ -19,6 +19,29 @@ "allowUnsafeCode": false, "overrideReferences": true, "precompiledReferences": [ + "ConcurrentCollections.dll", + "GameCult.Caching.MessagePack.dll", + "GameCult.Caching.dll", + "GameCult.Logging.dll", + "GameCult.Mesh.dll", + "GameCult.Networking.dll", + "Isopoh.Cryptography.Argon2.dll", + "Isopoh.Cryptography.Blake2b.dll", + "Isopoh.Cryptography.SecureArray.dll", + "LiteNetLib.dll", + "Microsoft.Bcl.AsyncInterfaces.dll", + "Microsoft.Bcl.TimeProvider.dll", + "Microsoft.NET.StringTools.dll", + "MessagePack.Annotations.dll", + "MessagePack.CultMesh.dll", + "R3.dll", + "System.Collections.Immutable.dll", + "System.ComponentModel.Annotations.dll", + "System.IO.Pipelines.dll", + "System.Text.Encodings.Web.dll", + "System.Text.Json.dll", + "System.Threading.Channels.dll", + "System.Threading.Tasks.Extensions.dll", "nunit.framework.dll" ], "autoReferenced": false, diff --git a/Assets/Scripts/UI/HUD/SchematicDisplay.cs b/Assets/Scripts/UI/HUD/SchematicDisplay.cs index 333d1e225..f2e1fbb99 100644 --- a/Assets/Scripts/UI/HUD/SchematicDisplay.cs +++ b/Assets/Scripts/UI/HUD/SchematicDisplay.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Globalization; using System.Linq; using GameCult.Aetheria.State.Verse; +using GameCult.Mesh; using TMPro; using UnityEngine; using UnityEngine.UI; @@ -69,6 +70,7 @@ public class SchematicDisplay : MonoBehaviour private SchematicDisplayItem[] _schematicItems; private AetherDrive _aetherDrive; private AetheriaRuntimeDaemonRenderSettings? _renderSettings; + private AetheriaClientState _runtimeState; private bool _enemy; private Entity _player; @@ -92,6 +94,15 @@ public class SchematicDisplay : MonoBehaviour _renderSettings = renderSettings; } + private AetheriaClientState RuntimeState + { + get + { + _runtimeState ??= AetheriaUnityRuntimeClientProvider.RuntimeState("unity-schematic-display"); + return _runtimeState; + } + } + public void ShowShip(Entity entity, Entity player = null) { _enemy = player != null; @@ -318,7 +329,7 @@ public class SchematicDisplay : MonoBehaviour { try { - return AetheriaUnityRuntimeClientProvider.CurrentCatalog("unity-schematic-display"); + return RuntimeState.Catalog.Latest(); } catch (Exception ex) { @@ -332,7 +343,7 @@ public class SchematicDisplay : MonoBehaviour { try { - return AetheriaUnityRuntimeClientProvider.CurrentPlayerSettings("unity-schematic-display"); + return RuntimeState.PlayerSettings.Latest(); } catch (Exception ex) { @@ -346,9 +357,7 @@ public class SchematicDisplay : MonoBehaviour { try { - return AetheriaUnityRuntimeClientProvider - .CurrentEntityState("unity-schematic-display") - ?.Hud ?? new AetheriaRuntimeCurrentEntityHudStatus(); + return RuntimeState.CurrentEntity.Latest()?.Hud ?? new AetheriaRuntimeCurrentEntityHudStatus(); } catch (Exception ex) { diff --git a/Assets/Scripts/UI/InputScreen/InputDisplayLayout.cs b/Assets/Scripts/UI/InputScreen/InputDisplayLayout.cs index ee3277370..ff6357690 100644 --- a/Assets/Scripts/UI/InputScreen/InputDisplayLayout.cs +++ b/Assets/Scripts/UI/InputScreen/InputDisplayLayout.cs @@ -4,6 +4,7 @@ using System.Linq; using GameCult.Aetheria.EveRuntime; using GameCult.Aetheria.State.Verse; using GameCult.Eve.Surface; +using GameCult.Mesh; using UnityEngine; using UnityEngine.InputSystem; using UnityEngine.InputSystem.Utilities; @@ -18,6 +19,7 @@ public class InputDisplayLayout : MonoBehaviour private string _captureActionName = ""; private int _captureBindingIndex = -1; private string _captureBindingLabel = ""; + private AetheriaClientState _runtimeState; private readonly AetheriaEveUnitySurfaceChrome _surfaceChrome = new AetheriaEveUnitySurfaceChrome { RootAlignItems = Align.Center, @@ -368,8 +370,10 @@ public class InputDisplayLayout : MonoBehaviour { try { - return AetheriaUnityRuntimeClientProvider - .CurrentPlayerSettings(RequireLocalStateBoot(), "unity-input-screen"); + _runtimeState ??= AetheriaUnityRuntimeClientProvider.RuntimeState( + RequireLocalStateBoot(), + "unity-input-screen"); + return _runtimeState.PlayerSettings.Latest(); } catch (Exception ex) { diff --git a/Assets/Scripts/UI/MainMenu.cs b/Assets/Scripts/UI/MainMenu.cs index 02f66faa6..1ab6b5bdc 100644 --- a/Assets/Scripts/UI/MainMenu.cs +++ b/Assets/Scripts/UI/MainMenu.cs @@ -1,13 +1,16 @@ using System; +using System.Collections.Generic; using GameCult.Aetheria.EveRuntime; using GameCult.Aetheria.State.Verse; using GameCult.Eve.Surface; +using GameCult.Mesh; using TMPro; using UniRx; using UnityEngine; using UnityEngine.SceneManagement; using UnityEngine.UI; using UnityEngine.UIElements; +using UiButton = UnityEngine.UIElements.Button; public class MainMenu : MonoBehaviour { @@ -15,37 +18,46 @@ public class MainMenu : MonoBehaviour public GameSettings Settings; public ConfirmationDialog Dialog; public bool InGame; + public float FadeTime = 0.5f; + public float FadeDistance = 512f; + public float FadeAlphaExponent = 2f; + public float FadePositionExponent = 2f; + public float HoverSpacing = 8f; + public float HoverAnimationDuration = 0.16f; - private UIDocument _menuSurfaceDocument; + private UIDocument _currentMenuSurfaceDocument; + private UIDocument _nextMenuSurfaceDocument; private Func _canOpenRuntimeInputScreen; private Action _openRuntimeInputScreen; + private bool _fadeFromRight; + private bool _fading; + private float _fadeLerp; + private readonly List _hoverButtons = new List(); private readonly AetheriaEveUnitySurfaceChrome _menuSurfaceChrome = new AetheriaEveUnitySurfaceChrome { - RootAlignItems = Align.Center, - RootJustifyContent = Justify.Center, - RootPaddingLeft = 24f, + UseShell = false, + RootAlignItems = Align.FlexStart, + RootJustifyContent = Justify.FlexStart, + RootPaddingLeft = 96f, + RootPaddingTop = 92f, RootPaddingRight = 24f, - RootPaddingTop = 24f, RootPaddingBottom = 24f, - RootBackgroundColor = new Color(0f, 0f, 0f, 0.6f), - Width = 560f, - MinWidth = 0f, - MaxWidth = 560f, - PaddingLeft = 20f, - PaddingRight = 20f, - PaddingTop = 20f, - PaddingBottom = 20f, - BorderRadius = 0f, - BorderWidth = 0f, - BackgroundColor = new Color(0.08f, 0.1f, 0.14f, 0.96f) + RootBackgroundColor = new Color(0f, 0f, 0f, 0f), + RootPickingMode = PickingMode.Position }; void Start() { - ShowMain(); + ShowMain(false); } - private void ShowMain() + void Update() + { + UpdateMenuFade(); + UpdateHoverButtons(); + } + + private void ShowMain(bool animateFromRight = true) { var stateBoot = CurrentStateBoot(); var sectorMap = ResolveSectorMap(stateBoot); @@ -60,7 +72,8 @@ public class MainMenu : MonoBehaviour CanOpenRuntimeInputScreen(), InGame, DateTime.UtcNow.ToString("O")), - HandleMainSurfaceCommand); + HandleMainSurfaceCommand, + animateFromRight); } private void HandleMainSurfaceCommand(EveSurfaceCommandRequest request) @@ -77,7 +90,7 @@ public class MainMenu : MonoBehaviour if (ResolveSectorMap(CurrentStateBoot()) == null) { Debug.LogWarning("Main-menu Continue requested without a typed Aetheria sector-map projection."); - ShowMain(); + ShowMain(false); return; } @@ -87,7 +100,7 @@ public class MainMenu : MonoBehaviour StartNewGame(); return; case AetheriaRuntimeMainMenuCommandKind.ShowSettings: - ShowSettings(); + ShowSettings(true); return; case AetheriaRuntimeMainMenuCommandKind.Quit: Application.Quit(); @@ -102,7 +115,7 @@ public class MainMenu : MonoBehaviour { if (!TryStartDaemonObservedGame("Starting Daemon Run")) { - ShowMain(); + ShowMain(false); } } @@ -145,7 +158,9 @@ public class MainMenu : MonoBehaviour try { return AetheriaUnityRuntimeClientProvider - .CurrentSectorMap(stateBoot, "unity-main-menu"); + .RuntimeState(stateBoot, "unity-main-menu") + .SectorMap + .Latest(); } catch (Exception ex) { @@ -162,7 +177,9 @@ public class MainMenu : MonoBehaviour try { return AetheriaUnityRuntimeClientProvider - .CurrentPlayerSettings(stateBoot, "unity-main-menu"); + .RuntimeState(stateBoot, "unity-main-menu") + .PlayerSettings + .Latest(); } catch (Exception ex) { @@ -179,7 +196,9 @@ public class MainMenu : MonoBehaviour try { return AetheriaUnityRuntimeClientProvider - .CurrentVerseHostSettings(stateBoot, "unity-main-menu"); + .RuntimeState(stateBoot, "unity-main-menu") + .VerseHostSettings + .Latest(); } catch (Exception ex) { @@ -192,18 +211,19 @@ public class MainMenu : MonoBehaviour { if (!TryStartDaemonObservedGame("Continuing Daemon Run")) { - ShowMain(); + ShowMain(false); } } - private void ShowSettings() + private void ShowSettings(bool animateFromRight = true) { RenderMenuSurface( AetheriaRuntimeMainMenuSurfaceBuilder.BuildSettings(DateTime.UtcNow.ToString("O")), - HandleSettingsSurfaceCommand); + HandleSettingsSurfaceCommand, + animateFromRight); } - private void ShowInputSettings() + private void ShowInputSettings(bool animateFromRight = true) { var stateBoot = CurrentStateBoot(); RenderMenuSurface( @@ -213,19 +233,21 @@ public class MainMenu : MonoBehaviour CanOpenRuntimeInputScreen(), InGame, DateTime.UtcNow.ToString("O")), - HandleInputSettingsSurfaceCommand); + HandleInputSettingsSurfaceCommand, + animateFromRight); } - private void ShowPlayerSettingsSurface() + private void ShowPlayerSettingsSurface(bool animateFromRight = true) { RenderMenuSurface( AetheriaRuntimeMainMenuSurfaceBuilder.BuildPlayerSettingsShell( ResolvePlayerSettings(CurrentStateBoot()), DateTime.UtcNow.ToString("O")), - HandlePlayerSettingsSurfaceCommand); + HandlePlayerSettingsSurfaceCommand, + animateFromRight); } - private void ShowVerseSettingsSurface() + private void ShowVerseSettingsSurface(bool animateFromRight = true) { var stateBoot = CurrentStateBoot(); RenderMenuSurface( @@ -234,20 +256,377 @@ public class MainMenu : MonoBehaviour stateBoot, ResolveVerseHostSettings(stateBoot), DateTime.UtcNow.ToString("O"))), - HandleVerseSettingsSurfaceCommand); + HandleVerseSettingsSurfaceCommand, + animateFromRight); } private void RenderMenuSurface( AetheriaRuntimeSurfaceDocument document, - Action commandHandler) + Action commandHandler, + bool animateFromRight) { - _menuSurfaceDocument = AetheriaEveUnitySurfaceHost.RenderRuntime( + var initialRender = _currentMenuSurfaceDocument == null; + var targetDocument = initialRender ? _currentMenuSurfaceDocument : _nextMenuSurfaceDocument; + targetDocument = AetheriaEveUnitySurfaceHost.RenderRuntime( transform, - _menuSurfaceDocument, - "Aetheria Menu Surface", + targetDocument, + initialRender ? "Aetheria Menu Surface" : "Aetheria Menu Surface Transition", document, commandHandler, - _menuSurfaceChrome); + _menuSurfaceChrome, + sortingOrder: initialRender ? 1000 : 1001); + + ApplyMenuStyles(targetDocument.rootVisualElement, IsRootMainMenu(document)); + SetDocumentTransition(targetDocument, initialRender ? 0f : (animateFromRight ? FadeDistance : -FadeDistance), initialRender ? 1f : 0f); + + if (initialRender) + { + _currentMenuSurfaceDocument = targetDocument; + return; + } + + _nextMenuSurfaceDocument = targetDocument; + _fadeFromRight = animateFromRight; + _fadeLerp = 0f; + _fading = true; + } + + private static bool IsRootMainMenu(AetheriaRuntimeSurfaceDocument document) + { + return string.Equals( + document?.Surface?.Id, + AetheriaRuntimeMainMenuCommands.RootSurfaceId, + StringComparison.Ordinal); + } + + private void UpdateMenuFade() + { + if (!_fading) + return; + + _fadeLerp += Time.unscaledDeltaTime / Mathf.Max(0.01f, FadeTime); + var t = Mathf.Clamp01(_fadeLerp); + var outgoing = Mathf.Pow(t, FadePositionExponent); + var incoming = Mathf.Pow(1f - t, FadePositionExponent); + var direction = _fadeFromRight ? 1f : -1f; + + SetDocumentTransition( + _currentMenuSurfaceDocument, + -direction * FadeDistance * outgoing, + Mathf.Pow(1f - t, FadeAlphaExponent)); + SetDocumentTransition( + _nextMenuSurfaceDocument, + direction * FadeDistance * incoming, + Mathf.Pow(t, FadeAlphaExponent)); + + if (_fadeLerp <= 1f) + return; + + _fading = false; + AetheriaEveUnitySurfaceHost.Hide(_currentMenuSurfaceDocument); + var previous = _currentMenuSurfaceDocument; + _currentMenuSurfaceDocument = _nextMenuSurfaceDocument; + _nextMenuSurfaceDocument = previous; + SetDocumentTransition(_currentMenuSurfaceDocument, 0f, 1f); + } + + private static void SetDocumentTransition(UIDocument document, float x, float opacity) + { + if (document?.rootVisualElement == null) + return; + + document.rootVisualElement.style.translate = new Translate(new Length(x, LengthUnit.Pixel), 0f, 0f); + document.rootVisualElement.style.opacity = opacity; + } + + private void UpdateHoverButtons() + { + for (var i = _hoverButtons.Count - 1; i >= 0; i--) + { + var hover = _hoverButtons[i]; + if (hover.Button == null) + { + _hoverButtons.RemoveAt(i); + continue; + } + + var direction = hover.Hovering && hover.Button.enabledInHierarchy ? 1f : -1f; + hover.Lerp = Mathf.Clamp01(hover.Lerp + Time.unscaledDeltaTime / Mathf.Max(0.01f, HoverAnimationDuration) * direction); + hover.Button.style.letterSpacing = HoverSpacing * hover.Lerp; + } + } + + private void ApplyMenuStyles(VisualElement root, bool rootMainMenu) + { + if (root == null) + return; + + StyleMenuTree(root, rootMainMenu); + + var surface = rootMainMenu + ? FindElement(root, $"{AetheriaRuntimeMainMenuCommands.RootSurfaceId}.root") + : FindFirstKind(root, "surface"); + if (surface != null) + { + surface.style.flexGrow = 0f; + surface.style.width = rootMainMenu ? 680f : 560f; + surface.style.alignItems = Align.FlexStart; + surface.pickingMode = PickingMode.Position; + } + + var title = FindElement(root, $"{AetheriaRuntimeMainMenuCommands.RootSurfaceId}.title") as Label; + if (title != null) + { + title.style.fontSize = 82f; + title.style.color = new Color(0.82f, 0.95f, 1f, 0.96f); + title.style.unityFontStyleAndWeight = FontStyle.Normal; + title.style.unityTextAlign = TextAnchor.MiddleLeft; + title.style.marginLeft = 0f; + title.style.marginBottom = -12f; + title.style.width = 660f; + } + + var subtitle = FindElement(root, $"{AetheriaRuntimeMainMenuCommands.RootSurfaceId}.subtitle") as Label; + if (subtitle != null) + { + subtitle.style.fontSize = 38f; + subtitle.style.color = new Color(0.82f, 0.95f, 1f, 0.92f); + subtitle.style.unityFontStyleAndWeight = FontStyle.Normal; + subtitle.style.unityTextAlign = TextAnchor.MiddleRight; + subtitle.style.marginTop = 0f; + subtitle.style.marginBottom = 36f; + subtitle.style.width = 660f; + } + + RegisterHoverButtons(root); + } + + private void StyleMenuTree(VisualElement element, bool rootMainMenu) + { + if (element == null) + return; + + element.pickingMode = PickingMode.Position; + + if (element is UiButton button) + StyleMenuButton(button, rootMainMenu); + else if (element is TextField textField) + StyleMenuTextField(textField, rootMainMenu); + else if (element is Label label) + StyleMenuLabel(label, rootMainMenu); + else + StyleMenuContainer(element, rootMainMenu); + + foreach (var child in element.Children()) + StyleMenuTree(child, rootMainMenu); + } + + private static void StyleMenuContainer(VisualElement element, bool rootMainMenu) + { + element.style.backgroundColor = new Color(0f, 0f, 0f, 0f); + element.style.borderLeftWidth = 0f; + element.style.borderRightWidth = 0f; + element.style.borderTopWidth = 0f; + element.style.borderBottomWidth = 0f; + element.style.marginLeft = 0f; + element.style.marginTop = 0f; + element.style.marginBottom = 0f; + + if (element.ClassListContains("eve-row")) + { + element.style.flexDirection = FlexDirection.Row; + element.style.flexWrap = Wrap.Wrap; + element.style.alignItems = Align.Center; + element.style.marginTop = 2f; + element.style.marginBottom = 6f; + return; + } + + if (element.ClassListContains("eve-card")) + { + element.style.flexDirection = FlexDirection.Column; + element.style.alignItems = Align.FlexStart; + element.style.width = rootMainMenu ? 680f : 540f; + element.style.marginBottom = 18f; + element.style.paddingTop = 0f; + element.style.paddingBottom = 2f; + return; + } + + if (element.ClassListContains("eve-metric") || element.ClassListContains("eve-field")) + { + element.style.flexDirection = FlexDirection.Row; + element.style.alignItems = Align.Center; + element.style.width = rootMainMenu ? 320f : 520f; + element.style.marginBottom = 5f; + return; + } + + if (element.ClassListContains("eve-kind-control-text")) + { + element.style.flexDirection = FlexDirection.Row; + element.style.alignItems = Align.Center; + element.style.width = rootMainMenu ? 520f : 520f; + element.style.marginBottom = rootMainMenu ? 6f : 10f; + return; + } + + if (element.ClassListContains("eve-surface-root") || element.ClassListContains("eve-kind-surface")) + { + element.style.flexDirection = FlexDirection.Column; + element.style.alignItems = Align.FlexStart; + element.style.width = rootMainMenu ? 680f : 560f; + return; + } + + element.style.flexDirection = FlexDirection.Column; + element.style.alignItems = Align.FlexStart; + } + + private static void StyleMenuLabel(Label label, bool rootMainMenu) + { + label.style.color = new Color(0.86f, 0.98f, 1f, 0.94f); + label.style.unityFontStyleAndWeight = FontStyle.Normal; + label.style.unityTextAlign = TextAnchor.MiddleLeft; + label.style.whiteSpace = WhiteSpace.Normal; + label.style.marginLeft = 0f; + label.style.marginTop = 0f; + label.style.marginBottom = 0f; + + if (label.ClassListContains("eve-title")) + { + label.style.fontSize = rootMainMenu ? 24f : 24f; + label.style.unityFontStyleAndWeight = FontStyle.Normal; + label.style.marginTop = rootMainMenu ? 0f : 2f; + label.style.marginBottom = rootMainMenu ? 0f : 10f; + label.style.maxWidth = rootMainMenu ? 620f : 520f; + return; + } + + if (label.ClassListContains("eve-muted")) + { + label.style.fontSize = rootMainMenu ? 18f : 15f; + label.style.color = new Color(0.78f, 0.92f, 0.98f, 0.84f); + label.style.width = rootMainMenu ? 120f : 190f; + label.style.marginRight = 12f; + return; + } + + if (label.ClassListContains("eve-value")) + { + label.style.fontSize = rootMainMenu ? 20f : 16f; + label.style.unityFontStyleAndWeight = FontStyle.Normal; + label.style.maxWidth = rootMainMenu ? 360f : 300f; + return; + } + + label.style.fontSize = rootMainMenu ? 24f : 16f; + label.style.maxWidth = rootMainMenu ? 620f : 520f; + label.style.marginBottom = rootMainMenu ? 0f : 10f; + } + + private static void StyleMenuTextField(TextField field, bool rootMainMenu) + { + field.style.width = rootMainMenu ? 360f : 520f; + field.style.height = rootMainMenu ? 30f : 28f; + field.style.marginLeft = 0f; + field.style.marginTop = 0f; + field.style.marginBottom = rootMainMenu ? 4f : 8f; + field.style.color = new Color(0.86f, 0.98f, 1f, 0.96f); + field.style.fontSize = rootMainMenu ? 18f : 15f; + field.pickingMode = PickingMode.Position; + + field.labelElement.style.minWidth = rootMainMenu ? 100f : 190f; + field.labelElement.style.width = rootMainMenu ? 100f : 190f; + field.labelElement.style.color = new Color(0.78f, 0.92f, 0.98f, 0.84f); + } + + private static void StyleMenuButton(UiButton button, bool rootMainMenu) + { + button.style.backgroundColor = new Color(0f, 0f, 0f, 0f); + button.style.borderLeftWidth = 0f; + button.style.borderRightWidth = 0f; + button.style.borderTopWidth = 0f; + button.style.borderBottomWidth = 0f; + button.style.color = new Color(0.86f, 0.98f, 1f, 0.96f); + button.style.fontSize = rootMainMenu ? 24f : 16f; + button.style.unityFontStyleAndWeight = FontStyle.Normal; + button.style.unityTextAlign = TextAnchor.MiddleLeft; + button.style.width = rootMainMenu ? 220f : 230f; + button.style.height = rootMainMenu ? 32f : 24f; + button.style.marginLeft = 0f; + button.style.marginTop = 0f; + button.style.marginRight = rootMainMenu ? 0f : 14f; + button.style.marginBottom = rootMainMenu ? 0f : 4f; + button.style.paddingLeft = 0f; + button.style.paddingRight = 0f; + button.style.paddingTop = 0f; + button.style.paddingBottom = 0f; + button.style.letterSpacing = 0f; + button.focusable = true; + button.pickingMode = PickingMode.Position; + } + + private void RegisterHoverButtons(VisualElement root) + { + foreach (var button in FindButtons(root)) + { + var hover = new MenuHoverButton(button); + _hoverButtons.Add(hover); + button.RegisterCallback(_ => hover.Hovering = true); + button.RegisterCallback(_ => hover.Hovering = false); + } + } + + private static IEnumerable FindButtons(VisualElement root) + { + if (root == null) + yield break; + + if (root is UiButton button) + yield return button; + + foreach (var child in root.Children()) + { + foreach (var nested in FindButtons(child)) + yield return nested; + } + } + + private static VisualElement FindFirstKind(VisualElement root, string kind) + { + if (root == null) + return null; + + if (root.ClassListContains($"eve-kind-{kind}")) + return root; + + foreach (var child in root.Children()) + { + var match = FindFirstKind(child, kind); + if (match != null) + return match; + } + + return null; + } + + private static VisualElement FindElement(VisualElement root, string name) + { + if (root == null) + return null; + + if (string.Equals(root.name, name, StringComparison.Ordinal)) + return root; + + foreach (var child in root.Children()) + { + var match = FindElement(child, name); + if (match != null) + return match; + } + + return null; } private void HandleSettingsSurfaceCommand(EveSurfaceCommandRequest request) @@ -261,16 +640,16 @@ public class MainMenu : MonoBehaviour switch (command.Kind) { case AetheriaRuntimeMainMenuCommandKind.ShowPlayerSettings: - ShowPlayerSettingsSurface(); + ShowPlayerSettingsSurface(true); return; case AetheriaRuntimeMainMenuCommandKind.ShowVerseSettings: - ShowVerseSettingsSurface(); + ShowVerseSettingsSurface(true); return; case AetheriaRuntimeMainMenuCommandKind.ShowInputSettings: - ShowInputSettings(); + ShowInputSettings(true); return; case AetheriaRuntimeMainMenuCommandKind.BackToMain: - ShowMain(); + ShowMain(false); return; default: Debug.LogWarning($"Unhandled settings menu command kind: {command.Kind}"); @@ -289,12 +668,12 @@ public class MainMenu : MonoBehaviour switch (command.Kind) { case AetheriaRuntimeMainMenuCommandKind.BackToSettings: - ShowSettings(); + ShowSettings(false); return; case AetheriaRuntimeMainMenuCommandKind.OpenRuntimeInputScreen: if (!TryOpenRuntimeInputScreen()) { - ShowInputSettings(); + ShowInputSettings(false); } return; @@ -315,11 +694,11 @@ public class MainMenu : MonoBehaviour switch (command.Kind) { case AetheriaRuntimeMainMenuCommandKind.BackToSettings: - ShowSettings(); + ShowSettings(false); return; case AetheriaRuntimeMainMenuCommandKind.PlayerSettingsCommand: SendKnownAetheriaEveCommand(request, "player-settings"); - ShowPlayerSettingsSurface(); + ShowPlayerSettingsSurface(false); return; default: Debug.LogWarning($"Unhandled player-settings command kind: {command.Kind}"); @@ -338,15 +717,15 @@ public class MainMenu : MonoBehaviour switch (command.Kind) { case AetheriaRuntimeMainMenuCommandKind.BackToSettings: - ShowSettings(); + ShowSettings(false); return; case AetheriaRuntimeMainMenuCommandKind.ClientTargetCommand: RequestClientTargetCommand(request); - ShowVerseSettingsSurface(); + ShowVerseSettingsSurface(false); return; case AetheriaRuntimeMainMenuCommandKind.VerseHostCommand: SendKnownAetheriaEveCommand(request, "Verse-host"); - ShowVerseSettingsSurface(); + ShowVerseSettingsSurface(false); return; default: Debug.LogWarning($"Unhandled verse-settings command kind: {command.Kind}"); @@ -356,10 +735,8 @@ public class MainMenu : MonoBehaviour private void HideMenuSurface() { - if (_menuSurfaceDocument == null) - return; - - AetheriaEveUnitySurfaceHost.Hide(_menuSurfaceDocument); + AetheriaEveUnitySurfaceHost.Hide(_currentMenuSurfaceDocument); + AetheriaEveUnitySurfaceHost.Hide(_nextMenuSurfaceDocument); } public void SetRuntimeInputScreenShell(Func canOpenRuntimeInputScreen, Action openRuntimeInputScreen) @@ -446,10 +823,28 @@ public class MainMenu : MonoBehaviour private void OnDestroy() { - if (_menuSurfaceDocument != null) + if (_currentMenuSurfaceDocument != null) { - Destroy(_menuSurfaceDocument.gameObject); - _menuSurfaceDocument = null; + Destroy(_currentMenuSurfaceDocument.gameObject); + _currentMenuSurfaceDocument = null; + } + + if (_nextMenuSurfaceDocument != null) + { + Destroy(_nextMenuSurfaceDocument.gameObject); + _nextMenuSurfaceDocument = null; } } + + private sealed class MenuHoverButton + { + public MenuHoverButton(UiButton button) + { + Button = button; + } + + public UiButton Button { get; } + public bool Hovering { get; set; } + public float Lerp { get; set; } + } } diff --git a/Assets/Scripts/UI/Menu/InventoryMenu.cs b/Assets/Scripts/UI/Menu/InventoryMenu.cs index 1967048f4..e59d43f31 100644 --- a/Assets/Scripts/UI/Menu/InventoryMenu.cs +++ b/Assets/Scripts/UI/Menu/InventoryMenu.cs @@ -9,6 +9,7 @@ using System.Linq; using GameCult.Aetheria.EveRuntime; using GameCult.Aetheria.State.Verse; using GameCult.Eve.Surface; +using GameCult.Mesh; using UnityEngine; using UniRx; using UniRx.Triggers; @@ -21,17 +22,14 @@ public class InventoryMenu : MonoBehaviour public ConfirmationDialog Dialog; // public ClickCatcher Background; - private int2 _selectedPosition; - private InventoryPanel _selectedPanel; - private ItemInstance _selectedItem; private EquippedItem _selectedEquippedItem; - private int2[] _selectedCells; private AetheriaRuntimeCurrentEntityDocument _shipSettingsCurrentEntity; private UIDocument _shipSettingsSurfaceDocument; private UIDocument _cargoItemDetailsSurfaceDocument; private UIDocument _equippedItemDetailsSurfaceDocument; private AetheriaUnityActionBarPresentation _actionBarPresentation; - private AetheriaUnityObservedEntityIndex _observedEntityIndex; + private AetheriaUnityPresentationEntityIndex _presentationEntityIndex; + private AetheriaClientState _runtimeState; private readonly AetheriaEveUnitySurfaceChrome _shipSettingsSurfaceChrome = PanelChrome(360f, 420f); private readonly AetheriaEveUnitySurfaceChrome _cargoItemDetailsSurfaceChrome = PanelChrome(420f, 520f); private readonly AetheriaEveUnitySurfaceChrome _equippedItemDetailsSurfaceChrome = PanelChrome(460f, 560f); @@ -55,10 +53,12 @@ public class InventoryMenu : MonoBehaviour _actionBarPresentation = actionBarPresentation; } - public void SetObservedEntityIndex(AetheriaUnityObservedEntityIndex observedEntityIndex) + public void SetPresentationEntityIndex(AetheriaUnityPresentationEntityIndex presentationEntityIndex) { - _observedEntityIndex = observedEntityIndex; + _presentationEntityIndex = presentationEntityIndex; } + + private AetheriaClientState RuntimeState => _runtimeState ??= AetheriaUnityRuntimeClientProvider.RuntimeState("unity-inventory-menu"); // private int2 _dragCellOffset; // private ItemRotation _originalRotation; // //private Shape _previousFakeOccupancy; @@ -108,74 +108,6 @@ public class InventoryMenu : MonoBehaviour { foreach (var panel in InventoryPanels) { - panel.OnClickAsObservable().Subscribe(e => - { - if (e.data is InventoryCargoEventData cargoEvent) - { - var item = cargoEvent.CargoBay.Occupancy[cargoEvent.Position.x, cargoEvent.Position.y]; - if(item!=null) - { - if (e.clickCount == 2) - { - HideCargoItemDetailsSurface(); - HideEquippedItemDetailsSurface(); - ClearSelectedItemSelection(); - var otherPanel = panel == InventoryPanels[0] ? InventoryPanels[1] : InventoryPanels[0]; - RequestCargoItemTransfer(cargoEvent.CargoBay, otherPanel, item); - // TODO: SFX: Equip - // else - // TODO: SFX: Fail - } - else - { - HideCurrentShipSettingsSurface(); - HideCargoItemDetailsSurface(); - HideEquippedItemDetailsSurface(); - ClearSelectedItemSelection(); - _selectedPanel = panel; - _selectedPosition = cargoEvent.CargoBay.Cargo[item]; - _selectedItem = item; - _selectedCells = GetSelectedCells(item, _selectedPosition); - ApplySelectedCellHighlight(); - RenderCargoItemDetailsSurface(item); - // TODO: SFX: Success - } - } - } - else if (e.data is InventoryEntityEventData entityEvent) - { - var item = entityEvent.Entity.GearOccupancy[entityEvent.Position.x, entityEvent.Position.y]; - if (item != null) - { - if (e.clickCount == 2) - { - HideCargoItemDetailsSurface(); - HideEquippedItemDetailsSurface(); - ClearSelectedItemSelection(); - var otherPanel = panel == InventoryPanels[0] ? InventoryPanels[1] : InventoryPanels[0]; - RequestEquippedItemTransfer(entityEvent.Entity, item, otherPanel); - // TODO: SFX: Unequip - // else - // TODO: SFX: Fail - } - else - { - HideCurrentShipSettingsSurface(); - HideCargoItemDetailsSurface(); - ClearSelectedItemSelection(); - _selectedPanel = panel; - _selectedPosition = item.Position; - _selectedItem = item.EquippableItem; - _selectedEquippedItem = item; - _selectedCells = GetSelectedCells(item.EquippableItem, _selectedPosition); - ApplySelectedCellHighlight(); - RenderEquippedItemDetailsSurface(item); - // TODO: SFX: Success - } - } - } - }); - panel.OnBackgroundClick.Subscribe(data => { if (!TryReadCurrentEntity(out var currentEntity)) return; @@ -499,122 +431,9 @@ public class InventoryMenu : MonoBehaviour item.Entity.WeaponGroups[groupIndex].items.Contains(item); } - private void RequestCargoItemTransfer(EquippedCargoBay origin, InventoryPanel destination, ItemInstance item) - { - if (destination.DisplayedEntity != null && item is EquippableItem equippableItem) - { - RequestCargoItemEquip(origin, destination.DisplayedEntity, equippableItem); - return; - } - - if (destination.DisplayedCargo != null) - RequestCargoItemTransfer(origin, destination.DisplayedCargo, item); - } - private void ClearSelectedItemSelection() { - ClearSelectedCellHighlight(); - _selectedPanel = null; - _selectedItem = null; _selectedEquippedItem = null; - _selectedCells = null; - _selectedPosition = default; - } - - private void RequestEquippedItemTransfer(Entity origin, EquippedItem item, InventoryPanel destination) - { - if (destination.DisplayedCargo != null) - RequestEquippedItemStore(item, destination.DisplayedCargo); - } - - private void RequestCargoItemTransfer( - EquippedCargoBay origin, - EquippedCargoBay destination, - ItemInstance item) - { - if (!TryResolveCargoBay(origin, out var originEntityKey, out var originCargoIndex) || - !TryResolveCargoBay(destination, out var destinationEntityKey, out var destinationCargoIndex) || - item == null || - string.IsNullOrWhiteSpace(item.ItemKey) || - !origin.Cargo.TryGetValue(item, out var originPosition) || - !TryValidateTypedCargoSlot(originEntityKey, originCargoIndex, item, originPosition, true)) - { - return; - } - - var quantity = item is SimpleCommodity commodity ? commodity.Quantity : 1; - if (quantity <= 0) - return; - - TrySubmitOperation( - operations => operations.TransferCargoItem( - originEntityKey, - originCargoIndex, - destinationEntityKey, - destinationCargoIndex, - item.ItemKey, - quantity, - originPosition.x, - originPosition.y, - default, - default, - false), - "cargo transfer"); - } - - private void RequestCargoItemEquip( - EquippedCargoBay origin, - Entity destination, - EquippableItem item) - { - if (!TryResolveCargoBay(origin, out var originEntityKey, out var originCargoIndex) || - !TryResolveEntityRecordKey(destination, out var destinationEntityKey) || - item == null || - string.IsNullOrWhiteSpace(item.ItemKey) || - !origin.Cargo.TryGetValue(item, out var originPosition) || - !TryValidateTypedCargoSlot(originEntityKey, originCargoIndex, item, originPosition, true)) - { - return; - } - - TrySubmitOperation( - operations => operations.EquipItem( - "cargo", - originEntityKey, - originCargoIndex, - destinationEntityKey, - item.ItemKey, - originPosition.x, - originPosition.y, - default, - default, - false), - "cargo equip"); - } - - private void RequestEquippedItemStore( - EquippedItem item, - EquippedCargoBay destination) - { - if (!TryResolveEquippedItem(item, out var originEntityKey, out var equipmentIndex) || - !TryResolveCargoBay(destination, out var destinationEntityKey, out var destinationCargoIndex) || - item?.EquippableItem == null || - !TryValidateTypedEquipmentSlot(originEntityKey, equipmentIndex, item)) - { - return; - } - - TrySubmitOperation( - operations => operations.StoreItem( - originEntityKey, - equipmentIndex, - destinationEntityKey, - destinationCargoIndex, - item.EquippableItem.ItemKey, - default, - default, - false), - "equipment store"); } private void RequestEntityShutdownPerformance(string targetEntityKey, float shutdownPerformance) @@ -678,17 +497,17 @@ public class InventoryMenu : MonoBehaviour "weapon-group membership"); } - private bool TryResolveEntityRecordKey(Entity entity, out string recordKey) + private bool TryGetRecordKeyForPresentationEntity(Entity entity, out string recordKey) { recordKey = ""; - return _observedEntityIndex != null && - _observedEntityIndex.TryResolveEntityRecordKey(entity, out recordKey); + return _presentationEntityIndex != null && + _presentationEntityIndex.TryGetRecordKeyForPresentationEntity(entity, out recordKey); } private bool IsCurrentEntity(Entity entity) { if (entity == null || - !TryResolveEntityRecordKey(entity, out var entityKey) || + !TryGetRecordKeyForPresentationEntity(entity, out var entityKey) || string.IsNullOrWhiteSpace(entityKey) || !TryResolveCurrentEntityKey(out var currentEntityKey)) { @@ -732,9 +551,9 @@ public class InventoryMenu : MonoBehaviour { currentEntity = null; var currentEntityKey = CurrentEntitySnapshot()?.EntityKey ?? ""; - return _observedEntityIndex != null && + return _presentationEntityIndex != null && !string.IsNullOrWhiteSpace(currentEntityKey) && - _observedEntityIndex.TryResolveEntityByRecordKey(currentEntityKey, out currentEntity); + _presentationEntityIndex.TryGetPresentationEntityByRecordKey(currentEntityKey, out currentEntity); } private bool TryResolveCurrentDockingBay(out EquippedDockingBay dockingBay) @@ -744,8 +563,8 @@ public class InventoryMenu : MonoBehaviour if (refit?.IsDocked != true || refit.DockingBayIndex < 0 || string.IsNullOrWhiteSpace(refit.DockParentEntityKey) || - _observedEntityIndex == null || - !_observedEntityIndex.TryResolveDockingBayByRecordKey( + _presentationEntityIndex == null || + !_presentationEntityIndex.TryGetPresentationDockingBayByRecordKey( refit.DockParentEntityKey, refit.DockingBayIndex, out dockingBay)) @@ -756,18 +575,13 @@ public class InventoryMenu : MonoBehaviour return dockingBay != null; } - private bool TryResolveCargoBay(EquippedCargoBay cargoBay, out string entityKey, out int cargoIndex) - { - return TryResolveEquippedItem(cargoBay, out entityKey, out cargoIndex); - } - private bool TryResolveEquippedItem(EquippedItem item, out string entityKey, out int equipmentIndex) { entityKey = ""; equipmentIndex = -1; var entity = item?.Entity; if (entity?.Equipment == null || - !TryResolveEntityRecordKey(entity, out entityKey)) + !TryGetRecordKeyForPresentationEntity(entity, out entityKey)) { return false; } @@ -776,97 +590,6 @@ public class InventoryMenu : MonoBehaviour return equipmentIndex >= 0; } - private bool TryResolveTypedInventoryRows( - string entityKey, - out IReadOnlyList equipment, - out IReadOnlyList cargo) - { - equipment = Array.Empty(); - cargo = Array.Empty(); - if (string.IsNullOrWhiteSpace(entityKey)) - return false; - - try - { - var current = CurrentEntitySnapshot(); - if (current != null && string.Equals(current.EntityKey, entityKey, StringComparison.Ordinal)) - { - equipment = current.Equipment ?? Array.Empty(); - cargo = current.Cargo ?? Array.Empty(); - return true; - } - - var refit = StationRefitSnapshot(); - var entityIndex = -1; - if (refit != null) - { - if (string.Equals(refit.DockParentEntityKey, entityKey, StringComparison.Ordinal)) - entityIndex = refit.DockParentEntityIndex; - else - entityIndex = (refit.AvailableEntities ?? Array.Empty()) - .FirstOrDefault(option => string.Equals(option.EntityKey, entityKey, StringComparison.Ordinal)) - ?.EntityIndex ?? -1; - } - - if (entityIndex < 0) - return false; - - var inventory = InventorySnapshot(entityIndex); - if (inventory == null || !string.Equals(inventory.EntityKey, entityKey, StringComparison.Ordinal)) - return false; - - equipment = inventory.Equipment ?? Array.Empty(); - cargo = inventory.Cargo ?? Array.Empty(); - return true; - } - catch (Exception ex) - { - Debug.LogWarning($"Failed to validate Aetheria typed inventory document for {entityKey}: {ex.Message}"); - return false; - } - } - - private bool TryValidateTypedCargoSlot( - string entityKey, - int cargoIndex, - ItemInstance item, - int2 originPosition, - bool hasOriginPosition) - { - if (item == null || - string.IsNullOrWhiteSpace(item.ItemKey) || - !TryResolveTypedInventoryRows(entityKey, out _, out var cargo)) - { - return false; - } - - return cargo.Any(row => - string.Equals(row.Source, "cargo", StringComparison.Ordinal) && - row.SourceIndex == cargoIndex && - string.Equals(row.ItemKey, item.ItemKey, StringComparison.Ordinal) && - (!hasOriginPosition || (row.X == originPosition.x && row.Y == originPosition.y))); - } - - private bool TryValidateTypedEquipmentSlot( - string entityKey, - int equipmentIndex, - EquippedItem item) - { - if (item?.EquippableItem == null || - string.IsNullOrWhiteSpace(item.EquippableItem.ItemKey) || - !TryResolveTypedInventoryRows(entityKey, out var equipment, out _)) - { - return false; - } - - return equipment.Any(row => - string.Equals(row.Source, "equipment", StringComparison.Ordinal) && - row.SourceIndex == equipmentIndex && - row.X == item.Position.x && - row.Y == item.Position.y && - string.Equals(row.ItemKey, item.EquippableItem.ItemKey, StringComparison.Ordinal)); - } - private bool TrySubmitOperation( Action submit, string label) @@ -890,7 +613,7 @@ public class InventoryMenu : MonoBehaviour { try { - return AetheriaUnityRuntimeClientProvider.CurrentEntityState("unity-inventory-menu"); + return RuntimeState.CurrentEntity.Latest(); } catch (Exception ex) { @@ -903,7 +626,7 @@ public class InventoryMenu : MonoBehaviour { try { - return AetheriaUnityRuntimeClientProvider.CurrentStationRefit("unity-inventory-menu"); + return RuntimeState.StationRefit.Latest(); } catch (Exception ex) { @@ -912,19 +635,6 @@ public class InventoryMenu : MonoBehaviour } } - private AetheriaRuntimeInventoryDocument InventorySnapshot(int entityIndex) - { - try - { - return AetheriaUnityRuntimeClientProvider.CurrentInventory(entityIndex, "unity-inventory-menu"); - } - catch (Exception ex) - { - Debug.LogWarning($"Failed to read Aetheria typed inventory document for entity {entityIndex}: {ex.Message}"); - return null; - } - } - void Update() { // if (Keyboard.current.qKey.wasPressedThisFrame && _dragItem != null) @@ -937,41 +647,6 @@ public class InventoryMenu : MonoBehaviour // } } - private void ClearSelectedCellHighlight() - { - if (_selectedPanel == null || _selectedCells == null) return; - - foreach (var v in _selectedCells) - { - if (_selectedPanel.CellInstances.ContainsKey(v)) - _selectedPanel.CellInstances[v].Icon.color = _selectedPanel.GetColor(v); - } - } - - private void ApplySelectedCellHighlight() - { - if (_selectedPanel == null || _selectedCells == null) return; - - foreach (var v in _selectedCells) - { - if (_selectedPanel.CellInstances.ContainsKey(v)) - _selectedPanel.CellInstances[v].Icon.color = _selectedPanel.GetColor(v, true); - } - } - - private int2[] GetSelectedCells(ItemInstance item, int2 position) - { - var typedItem = FindTypedInventoryItem(item); - if (typedItem != null && typedItem.ShapeCells.Count > 0) - { - return typedItem.ShapeCells - .Select(cell => RotateTypedShapeCell(cell, typedItem, item.Rotation) + position) - .ToArray(); - } - - return Array.Empty(); - } - private AetheriaRuntimeCatalogItem FindTypedInventoryItem(ItemInstance item) { return CatalogSnapshot()?.FindItem(item, x => x.ItemKey); @@ -981,7 +656,7 @@ public class InventoryMenu : MonoBehaviour { try { - return AetheriaUnityRuntimeClientProvider.CurrentCatalog("unity-inventory-menu"); + return RuntimeState.Catalog.Latest(); } catch (Exception ex) { @@ -994,7 +669,7 @@ public class InventoryMenu : MonoBehaviour { try { - return AetheriaUnityRuntimeClientProvider.CurrentPlayerSettings("unity-inventory-menu"); + return RuntimeState.PlayerSettings.Latest(); } catch (Exception ex) { @@ -1042,20 +717,6 @@ public class InventoryMenu : MonoBehaviour return $"{FormatValue(value - 273.15f)} C"; } - private static int2 RotateTypedShapeCell( - AetheriaRuntimeShapeCell cell, - AetheriaRuntimeCatalogItem item, - ItemRotation rotation) - { - return rotation switch - { - ItemRotation.Clockwise => new int2(cell.Y, item.ShapeWidth - 1 - cell.X), - ItemRotation.Reversed => new int2(item.ShapeWidth - 1 - cell.X, item.ShapeHeight - 1 - cell.Y), - ItemRotation.CounterClockwise => new int2(item.ShapeHeight - 1 - cell.Y, cell.X), - _ => new int2(cell.X, cell.Y) - }; - } - private void OnDestroy() { if (_shipSettingsSurfaceDocument != null) diff --git a/Assets/Scripts/UI/Menu/InventoryPanel.cs b/Assets/Scripts/UI/Menu/InventoryPanel.cs index a7e57720a..fd1d0ff13 100644 --- a/Assets/Scripts/UI/Menu/InventoryPanel.cs +++ b/Assets/Scripts/UI/Menu/InventoryPanel.cs @@ -3,25 +3,19 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ using System; -using System.Collections; using System.Collections.Generic; -using System.Globalization; using System.Linq; using GameCult.Aetheria.EveRuntime; using GameCult.Aetheria.State.Verse; using GameCult.Eve.Surface; +using GameCult.Mesh; using TMPro; using UniRx; -using UniRx.Triggers; -using UnityEngine; -using UnityEngine.UI; -using UnityEngine.EventSystems; -using UnityEngine.Experimental.Rendering; using Unity.Mathematics; -using UnityEngine.Serialization; +using UnityEngine; +using UnityEngine.EventSystems; +using UnityEngine.UI; using UIE = UnityEngine.UIElements; -using static Unity.Mathematics.math; -using int2 = Unity.Mathematics.int2; public class InventoryPanel : MonoBehaviour, IPointerClickHandler { @@ -30,81 +24,41 @@ public class InventoryPanel : MonoBehaviour, IPointerClickHandler public bool Flip; public GameSettings Settings; public TextMeshProUGUI Title; - public TextMeshProUGUI MinTempLabel; - public TextMeshProUGUI MaxTempLabel; - public GameObject TemperatureRange; public Button Dropdown; public Button EditName; public Button Current; public Button Thermal; - public GridLayoutGroup Grid; - public Sprite[] NodeBackgroundTextures; - public Sprite[] NodeTextures; - public Sprite[] ThermalTextures; - public float ThermalToggleRegionSize = .2f; - public Prototype NodePrototype; - public RawImage TemperatureDisplay; - public Texture2D TemperatureColor; - public ExponentialLerp TemperatureColorCurve; - public ExponentialLerp TemperatureAlphaCurve; - public bool FitToContent; - public float CellHitPulseTime; public Color ToggleEnabledColor; public Color ToggleDisabledColor; - public Color CellBackgroundColor = new Color(0, 0, 0, .75f); - public float MinTempRange = 1; - public float DoubleClickTime = .5f; - public float HitDamageThreshold = 1; - - // Subject _onBeginDrag; - // Subject _onDrag; - // Subject _onEndDrag; - Subject<(InventoryEventData data, int clickCount)> _onClick; - // Subject _onPointerEnter; - // Subject _onPointerExit; private Entity _displayedEntity; private EquippedCargoBay _displayedCargo; private string _displayedEntityKey = ""; private string _displayedCargoEntityKey = ""; private int _displayedCargoIndex = -1; - private Shape _displayedHullShape; - private Shape _displayedHullInterior; - private Texture2D _temperatureTexture; - private RectTransform _firstRect; + private bool _thermal; + private bool _hud; private AetheriaUnityDragSession _dragSession; - private AetheriaUnityObservedEntityIndex _observedEntityIndex; - - public ItemInstance FakeItem; - public Shape FakeOccupancy; - public Shape IgnoreOccupancy; - public List EmptyCells = new List(); - - public void SetDragSession(AetheriaUnityDragSession dragSession) + private AetheriaUnityPresentationEntityIndex _presentationEntityIndex; + private AetheriaClientState _runtimeState; + private AetheriaRuntimeSurfaceDocument _inventoryPanelSurface; + private UIE.UIDocument _inventoryPanelSurfaceDocument; + private AetheriaRuntimeSurfaceDocument _dropdownSurface; + private UIE.UIDocument _dropdownSurfaceDocument; + private readonly AetheriaEveUnitySurfaceChrome _inventoryPanelSurfaceChrome = new AetheriaEveUnitySurfaceChrome { - _dragSession = dragSession; - } + RootAlignItems = UIE.Align.FlexStart, + RootJustifyContent = UIE.Justify.FlexStart, + RootPaddingTop = 0f, + Width = 520f, + MinWidth = 0f, + MaxWidth = 720f, + PaddingLeft = 16f, + PaddingRight = 16f, + PaddingTop = 14f, + PaddingBottom = 14f + }; - public void SetObservedEntityIndex(AetheriaUnityObservedEntityIndex observedEntityIndex) - { - _observedEntityIndex = observedEntityIndex; - } - - private AetheriaUnityDragSession DragSession => _dragSession ??= new AetheriaUnityDragSession(); - public Dictionary CellInstances = new Dictionary(); - - private List _subscriptions = new List(); - private Dictionary _cellAnimationSequence = new Dictionary(); - private int _hitSequence = 0; - private bool _thermal = false; - private int _clickCount; - private InventoryCell _clickCell; - private float _clickTime; - private AetheriaRuntimeStationRefitEntityOption[] _dropdownStationRefitEntities = - Array.Empty(); - private AetheriaRuntimeStationLoadoutRestoreOption[] _dropdownStationRefitLoadouts = - Array.Empty(); - private AetheriaRuntimeInventoryDropdownSurfaceModel _dropdownSurfaceModel; private readonly AetheriaEveUnitySurfaceChrome _dropdownSurfaceChrome = new AetheriaEveUnitySurfaceChrome { RootAlignItems = UIE.Align.FlexStart, @@ -118,96 +72,192 @@ public class InventoryPanel : MonoBehaviour, IPointerClickHandler PaddingTop = 18f, PaddingBottom = 18f }; - private UIE.UIDocument _dropdownSurfaceDocument; - private bool _hud; - - private int2[] _offsets = { - int2(0, 1), - int2(1, 0), - int2(0, -1), - int2(-1, 0), - int2(1, 1), - int2(1, -1), - int2(-1, -1), - int2(-1, 1) - }; + public readonly Dictionary CellInstances = new Dictionary(); + public Subject OnBackgroundClick = new Subject(); - public InventoryPanelTarget Target => + public InventoryPanelTarget Target => _displayedEntity != null ? InventoryPanelTarget.Equipment : _displayedCargo != null ? InventoryPanelTarget.Cargo : InventoryPanelTarget.None; - public Entity DisplayedEntity => _displayedEntity; - public EquippedCargoBay DisplayedCargo => _displayedCargo; - private void Start() + private AetheriaUnityDragSession DragSession => _dragSession ??= new AetheriaUnityDragSession(); + + private AetheriaClientState RuntimeState => + _runtimeState ??= AetheriaUnityRuntimeClientProvider.RuntimeState("unity-inventory"); + + public void SetDragSession(AetheriaUnityDragSession dragSession) { + _dragSession = dragSession; + } + + public void SetPresentationEntityIndex(AetheriaUnityPresentationEntityIndex presentationEntityIndex) + { + _presentationEntityIndex = presentationEntityIndex; + } + + private void Awake() + { + if (Dropdown) + Dropdown.onClick.AddListener(RenderDropdownSurface); if (Thermal) - { Thermal.onClick.AddListener(() => { - HideDropdownSurface(); - Display(_displayedEntity, false, !_thermal); + if (_displayedEntity != null) + Display(_displayedEntity, _hud, !_thermal); }); - } - if (Current) - { Current.onClick.AddListener(() => { - HideDropdownSurface(); - if (!IsCurrentEntity(_displayedEntity)) - { - if(_displayedEntity is Ship ship) - { - RequestDockedCurrentShip(ship); - } - else - { - Dialog.Clear(); - Dialog.Title.text = "Can't select entity, you can only pilot a ship!"; - Dialog.Show(); - Dialog.MoveToCursor(); - } - } + if (_displayedEntity is Ship ship && !IsCurrentEntity(_displayedEntity)) + RequestDockedCurrentShip(ship); }); + if (EditName) + EditName.onClick.AddListener(() => ShowRenameDialog(_displayedEntity)); + } + + public void Clear() + { + HideDropdownSurface(); + HideInventoryPanelSurface(); + _displayedEntity = null; + _displayedCargo = null; + _displayedEntityKey = ""; + _displayedCargoEntityKey = ""; + _displayedCargoIndex = -1; + _thermal = false; + _hud = false; + CellInstances.Clear(); + if (Title) + Title.text = "None"; + } + + public void Display(Entity entity, bool hud = false, bool thermal = false) + { + _displayedEntity = entity; + _displayedCargo = null; + _displayedCargoEntityKey = ""; + _displayedCargoIndex = -1; + _displayedEntityKey = TryGetRecordKeyForPresentationEntity(entity, out var entityKey) + ? entityKey + : ""; + _hud = hud; + _thermal = thermal; + if (Title) + Title.text = entity?.Name ?? ""; + RenderInventoryPanelSurface(); + } + + public void Display(EquippedCargoBay cargo) + { + _displayedEntity = null; + _displayedEntityKey = ""; + _displayedCargo = cargo; + _displayedCargoEntityKey = ""; + _displayedCargoIndex = -1; + _thermal = false; + _hud = false; + if (TryResolveCargoBay(cargo, out var entityKey, out var cargoIndex)) + { + _displayedCargoEntityKey = entityKey; + _displayedCargoIndex = cargoIndex; + } + if (Title) + Title.text = cargo?.Name ?? "Cargo"; + RenderInventoryPanelSurface(); + } + + public void RefreshCells() + { + RenderInventoryPanelSurface(); + } + + public void RefreshCells(IEnumerable cells) + { + RenderInventoryPanelSurface(); + } + + public Color GetColor(int2 position, bool selected = false) + { + return selected ? ToggleEnabledColor : Color.white; + } + + public void OnPointerClick(PointerEventData eventData) + { + OnBackgroundClick.OnNext(eventData); + } + + private void RenderInventoryPanelSurface() + { + _inventoryPanelSurface = ResolveInventoryPanelSurface(); + if (_inventoryPanelSurface == null) + { + HideInventoryPanelSurface(); + return; } - if (EditName) - { - EditName.onClick.AddListener(() => - { - HideDropdownSurface(); - Dialog.Clear(); - var entityName = _displayedEntity.Name; - Dialog.AddField("Name", () => entityName, s => entityName = s); - Dialog.Show(() => - { - RequestEntityName(_displayedEntity, entityName); - }); - Dialog.MoveToCursor(); - }); - } - - if(Dropdown) - { - Dropdown.onClick.AddListener(() => - { - RenderDropdownSurface(); - }); - } + _inventoryPanelSurfaceDocument = AetheriaEveUnitySurfaceHost.RenderRuntime( + transform, + _inventoryPanelSurfaceDocument, + "Aetheria Inventory Panel Surface", + _inventoryPanelSurface, + HandleInventoryPanelSurfaceCommand, + _inventoryPanelSurfaceChrome, + embeddedDocumentResolver: ResolveEmbeddedInventorySurface, + sortingOrder: 990); } private void RenderDropdownSurface() { - _dropdownSurfaceModel = BuildDropdownSurfaceModel(); + _dropdownSurface = ResolveDropdownSurface(); + if (_dropdownSurface == null) + { + HideDropdownSurface(); + return; + } _dropdownSurfaceDocument = AetheriaEveUnitySurfaceHost.RenderRuntime( transform, _dropdownSurfaceDocument, "Aetheria Inventory Dropdown Surface", - _dropdownSurfaceModel.Document, + _dropdownSurface, HandleDropdownSurfaceCommand, - _dropdownSurfaceChrome); + _dropdownSurfaceChrome, + sortingOrder: 1000); + } + + private AetheriaRuntimeSurfaceDocument ResolveEmbeddedInventorySurface(EveEmbeddedDocumentSlot slot) + { + if (slot == null || + !string.Equals(slot.SlotId, AetheriaRuntimeInventoryPanelSurfaceBuilder.DropdownSlotId, StringComparison.Ordinal)) + { + return null; + } + + return ResolveDropdownSurface(); + } + + private void HandleInventoryPanelSurfaceCommand(EveSurfaceCommandRequest request) + { + switch (request?.Command ?? "") + { + case AetheriaRuntimeInventoryPanelSurfaceBuilder.OpenNavigation: + RenderDropdownSurface(); + return; + case AetheriaRuntimeInventoryPanelSurfaceBuilder.ToggleThermal: + if (_displayedEntity != null) + Display(_displayedEntity, _hud, !_thermal); + return; + case AetheriaRuntimeInventoryPanelSurfaceBuilder.SetCurrent: + if (_displayedEntity is Ship ship && !IsCurrentEntity(_displayedEntity)) + RequestDockedCurrentShip(ship); + return; + case AetheriaRuntimeInventoryPanelSurfaceBuilder.EditName: + ShowRenameDialog(_displayedEntity); + return; + default: + Debug.LogWarning($"Unknown inventory panel command: {request?.Command}"); + return; + } } private void HandleDropdownSurfaceCommand(EveSurfaceCommandRequest request) @@ -225,7 +275,7 @@ public class InventoryPanel : MonoBehaviour, IPointerClickHandler } if (command.Kind == AetheriaRuntimeInventoryDropdownCommandKind.Select && - _dropdownSurfaceModel?.TryResolve(command.Command, out var selection) == true) + TryResolveDropdownSelection(request, command.Command, out var selection)) { ExecuteDropdownSelection(selection); HideDropdownSurface(); @@ -241,13 +291,11 @@ public class InventoryPanel : MonoBehaviour, IPointerClickHandler { case AetheriaRuntimeInventoryDropdownSelectionKind.EntityEquipment: case AetheriaRuntimeInventoryDropdownSelectionKind.Entity: - if (TryResolveStationRefitEntity(selection.EntityKey, selection.EntityIndex, out var entity)) - { + if (TryResolveStationRefitEntity(selection.EntityKey, out var entity)) Display(entity); - } return; case AetheriaRuntimeInventoryDropdownSelectionKind.EntityBay: - if (TryResolveStationRefitEntity(selection.EntityKey, selection.EntityIndex, out entity) && + if (TryResolveStationRefitEntity(selection.EntityKey, out entity) && selection.BayIndex >= 0 && selection.BayIndex < entity.CargoBays.Count) { @@ -256,775 +304,235 @@ public class InventoryPanel : MonoBehaviour, IPointerClickHandler return; case AetheriaRuntimeInventoryDropdownSelectionKind.DockingBay: if (TryResolveCurrentDockingBay(out var dockingBay)) - { Display(dockingBay); - } return; case AetheriaRuntimeInventoryDropdownSelectionKind.SaveLoadout: - if (_displayedEntity != null) - { - RequestLoadoutTemplateSave(_displayedEntity); - } + if (!string.IsNullOrWhiteSpace(_displayedEntityKey)) + RequestLoadoutTemplateSave(_displayedEntityKey); return; case AetheriaRuntimeInventoryDropdownSelectionKind.Loadout: - if (selection.TemplateIndex >= 0 && selection.TemplateIndex < _dropdownStationRefitLoadouts.Length) - { - RequestRuntimeLoadoutRestore(_dropdownStationRefitLoadouts[selection.TemplateIndex]); - } + if (TryResolveStationRefitLoadout(selection.TemplateIndex, out var loadout)) + RequestRuntimeLoadoutRestore(loadout); return; } } - private void HideDropdownSurface() + private static bool TryResolveDropdownSelection( + EveSurfaceCommandRequest request, + string command, + out AetheriaRuntimeInventoryDropdownSelection selection) { - if (_dropdownSurfaceDocument == null) - return; + selection = default; + if (request?.Payload == null) + return false; - AetheriaEveUnitySurfaceHost.Hide(_dropdownSurfaceDocument); - } - - private AetheriaRuntimeInventoryDropdownSurfaceModel BuildDropdownSurfaceModel() - { - var stationRefit = StationRefitSnapshot(); - _dropdownStationRefitEntities = (stationRefit?.AvailableEntities ?? Array.Empty()) - .ToArray(); - _dropdownStationRefitLoadouts = (stationRefit?.LoadoutRestoreOptions ?? Array.Empty()) - .ToArray(); - var entityOptions = _dropdownStationRefitEntities - .Select((entity, entityIndex) => new AetheriaRuntimeInventoryDropdownEntityOption( - entityIndex, - entity.EntityKey, - string.IsNullOrWhiteSpace(entity.DisplayName) ? $"Entity {entity.EntityIndex}" : entity.DisplayName, - IsDisplayedEntityKey(entity.EntityKey), - Enumerable.Range(0, Math.Max(entity.CargoBayCount, 0)) - .Select(bayIndex => new AetheriaRuntimeInventoryDropdownBayOption( - bayIndex, - $"Bay {bayIndex + 1}", - IsDisplayedCargoBayKey(entity.EntityKey, bayIndex))) - .ToArray())) - .ToArray(); - var loadoutOptions = _dropdownStationRefitLoadouts - .Select((loadout, optionIndex) => new AetheriaRuntimeInventoryDropdownLoadoutOption( - optionIndex, - loadout.TemplateName, - loadout.CanRestore ? $"{loadout.Price:n0}" : "", - loadout.CanRestore)) - .ToArray(); - var hasDockingBay = TryResolveCurrentDockingBayRow(out var currentDockingBay); - - return AetheriaRuntimeInventoryDropdownSurfaceBuilder.Build( - Title?.text ?? "None", - entityOptions, - hasDockingBay, - hasDockingBay - ? $"Docking Bay {currentDockingBay.DockingBayIndex + 1}" - : "Docking Bay", - hasDockingBay && - IsDisplayedCargoBayKey( - StationRefitSnapshot()?.DockParentEntityKey ?? "", - currentDockingBay.DockingBayIndex), - _displayedEntity != null, - loadoutOptions, - DateTime.UtcNow.ToString("O")); - } - -private void Update() - { - if (_displayedEntity != null && TemperatureDisplay) - { - var tempRange = _displayedEntity.MaxTemp - _displayedEntity.MinTemp; - var opacity = smoothstep(0, MinTempRange, tempRange); - if(MinTempLabel) - { - MinTempLabel.text = FormatTemperature(_displayedEntity.MinTemp); - MaxTempLabel.text = FormatTemperature(_displayedEntity.MaxTemp); - } - for(var x = 0; x < _temperatureTexture.width; x++) - { - for (var y = 0; y < _temperatureTexture.height; y++) - { - if (_displayedHullShape[int2(x-1, y-1)]) - { - var temp = (_displayedEntity.Temperature[x - 1, y - 1] - _displayedEntity.MinTemp) / (_displayedEntity.MaxTemp-_displayedEntity.MinTemp); - var color = TemperatureColor.GetPixelBilinear(TemperatureColorCurve.Evaluate(temp), 0); - color.a = TemperatureAlphaCurve.Evaluate(temp) * opacity; - _temperatureTexture.SetPixel(x,y,color); - } - else - _temperatureTexture.SetPixel(x,y,new Color(0,0,0,0)); - } - } - _temperatureTexture.Apply(); - TemperatureDisplay.rectTransform.anchoredPosition = _firstRect.anchoredPosition - Vector2.one * Grid.cellSize * 1.5f; - } - } - - private void OnDisable() - { - HideDropdownSurface(); - Clear(); - } - - - public void Clear() - { - HideDropdownSurface(); - _displayedCargo = null; - _displayedEntity = null; - _displayedEntityKey = ""; - _displayedCargoEntityKey = ""; - _displayedCargoIndex = -1; - _displayedHullShape = null; - _displayedHullInterior = null; - - foreach(var empty in EmptyCells) - Destroy(empty); - EmptyCells.Clear(); - - foreach(var node in CellInstances.Values) - node.GetComponent().ReturnToPool(); - CellInstances.Clear(); - - foreach(var s in _subscriptions) - s.Dispose(); - - _subscriptions.Clear(); - - if (Current) Current.gameObject.SetActive(false); - if (Thermal) Thermal.gameObject.SetActive(false); - if (TemperatureRange) TemperatureRange.SetActive(false); - if(TemperatureDisplay) TemperatureDisplay.gameObject.SetActive(false); - if (EditName) EditName.gameObject.SetActive(false); - - if(Title) - Title.text = "None"; - } - - public void Display(Entity entity, bool hud = false, bool thermal = false) - { - Clear(); - - _subscriptions.Add(entity.Equipment.ObserveAdd().Subscribe(_ => RefreshCells())); - _subscriptions.Add(entity.Equipment.ObserveRemove().Subscribe(_ => RefreshCells())); - - _thermal = thermal; - _hud = hud; - - _displayedEntity = entity; - _displayedCargo = null; - _displayedCargoEntityKey = ""; - _displayedCargoIndex = -1; - _displayedEntityKey = TryResolveEntityRecordKey(entity, out var displayedEntityKey) - ? displayedEntityKey - : ""; - _firstRect = null; - - if (TemperatureRange) - TemperatureRange.SetActive(true); - - if(Title) - Title.text = entity.Name; - - if (EditName) - { - EditName.gameObject.SetActive(true); - } - - if (!TryResolveTypedHullGeometry(entity.Hull, out _displayedHullShape, out _displayedHullInterior)) - { - Debug.LogError($"Cannot display inventory for {entity.Name}: missing typed hull geometry."); - return; - } - - if (FitToContent) - { - var gridRect = Grid.GetComponent(); - var rect = gridRect.rect; - Grid.cellSize = Vector2.one * (int) min(rect.width / (_displayedHullShape.Width + 1), rect.height / (_displayedHullShape.Height + 1)); - } - - if(TemperatureDisplay) - { - _temperatureTexture = new Texture2D( - _displayedHullShape.Width + 2, - _displayedHullShape.Height + 2, - TextureFormat.RGBA32, - false, - false); - TemperatureDisplay.gameObject.SetActive(true); - TemperatureDisplay.texture = _temperatureTexture; - var tempRect = TemperatureDisplay.rectTransform; - tempRect.sizeDelta = Grid.cellSize * new Vector2(_displayedHullShape.Width + 2, _displayedHullShape.Height + 2); - } - - if (Current) - { - Current.gameObject.SetActive(true); - Current.targetGraphic.color = - IsCurrentEntity(entity) - ? ToggleEnabledColor - : ToggleDisabledColor; - } - if (Thermal) - { - Thermal.gameObject.SetActive(true); - Thermal.targetGraphic.color = thermal ? ToggleEnabledColor : ToggleDisabledColor; - } - - Grid.constraintCount = _displayedHullShape.Width; - foreach (var v in _displayedHullShape.AllCoordinates) - { - if (!_displayedHullShape[v]) - { - var empty = new GameObject("Empty Node", typeof(RectTransform)); - empty.transform.SetParent(Grid.transform); - if (!_firstRect) - _firstRect = empty.GetComponent(); - EmptyCells.Add(empty); - } - else - { - var cell = NodePrototype.Instantiate(); - cell.Background.color = CellBackgroundColor; - if (!_firstRect) - _firstRect = cell.GetComponent(); - if (!thermal) - { - if(cell.PointerClickTrigger) - { - cell.PointerClickTrigger.OnPointerClickAsObservable() - .Subscribe(data => - { - if (cell != _clickCell || Time.time - _clickTime > DoubleClickTime) - _clickCount = 0; - _clickCell = cell; - _clickTime = Time.time; - _clickCount++; - _onClick?.OnNext((new InventoryEntityEventData(data, v, entity), _clickCount)); - }); - cell.BeginDragTrigger.OnBeginDragAsObservable() - .Subscribe(data => - { - //Debug.Log("Entity Drag Start"); - var item = entity.GearOccupancy[v.x, v.y]; - if (item != null) - { - var originalOccupancy = _displayedHullShape.Inset(GetItemShape(item.EquippableItem), item.Position, item.EquippableItem.Rotation); - _dragCells = originalOccupancy.Coordinates - .Select(v1 => Instantiate(CellInstances[v1], DragParent, true).transform).ToArray(); - foreach(var dragCell in _dragCells) - { - DestroyImmediate(dragCell.GetComponent()); - foreach (var component in dragCell.GetComponentsInChildren()) - component.enabled = false; - foreach (var img in dragCell.GetComponentsInChildren()) - img.color = new Color(img.color.r, img.color.g, img.color.b, img.color.a * .5f); - dragCell.GetComponentInChildren().raycastTarget = false; - } - _dragOffsets = _dragCells.Select(x => (Vector2) x.position - data.position).ToArray(); - IgnoreOccupancy = originalOccupancy; - RefreshCells(); - _originalRotation = item.EquippableItem.Rotation; - DragSession.Begin(new EquippedItemDragObject(item, entity, item.Position - v)); - //AkSoundEngine.PostEvent("Pickup", gameObject); - // TODO: SFX: Pickup Item - } - }); - cell.DragTrigger.OnDragAsObservable() - .Subscribe(data => - { - for (var i = 0; i < _dragCells.Length; i++) - _dragCells[i].position = new Vector3( - data.position.x + _dragOffsets[i].x, - data.position.y + _dragOffsets[i].y, - _dragCells[i].position.z); - }); - cell.EndDragTrigger.OnEndDragAsObservable() - .Subscribe(data => - { - //Debug.Log("Entity Drag End"); - IgnoreOccupancy = null; - var hadDragTarget = DragSession.End(); - if (!hadDragTarget) - entity.GearOccupancy[v.x, v.y].EquippableItem.Rotation = _originalRotation; - foreach(var dragObject in _dragCells) - Destroy(dragObject.gameObject); - _dragCells = null; - RefreshCells(); - }); - cell.PointerEnterTrigger.OnPointerEnterAsObservable() - .Subscribe(data => - { - //Debug.Log("Entity Pointer Enter"); - if (!DragSession.TryGetDraggedItem(out var itemDragObject)) return; - var item = itemDragObject.Item; - if (!(item is EquippableItem equippableItem)) return; - var placementPosition = v + itemDragObject.OriginCellOffset; - //foreach (var cell in _dragCells) cell.gameObject.SetActive(false); - FakeItem = item; - FakeOccupancy = _displayedHullShape.Inset(GetItemShape(equippableItem), placementPosition, item.Rotation); - RefreshCells(); - DragSession.RegisterTarget(drag => - { - //Debug.Log("Entity Drag Callback"); - FakeOccupancy = null; - RequestDraggedItemToEntity(drag, entity, placementPosition); - // TODO: SFX: Equip - }); - }); - cell.PointerExitTrigger.OnPointerExitAsObservable() - .Subscribe(data => - { - if (!DragSession.TryGetDraggedItem(out _)) return; - FakeItem = null; - FakeOccupancy = null; - RefreshCells(); - DragSession.UnregisterTarget(); - }); - } - } - else - { - cell.PointerClickTrigger.OnPointerClickAsObservable().Subscribe(data => - { - var rect = cell.GetComponent(); - var point = Rect.PointToNormalized(rect.rect, rect.InverseTransformPoint(data.position)); -// Debug.Log($"Clicked at pos {data.position}, normalized {point}"); - if (_displayedHullShape[int2(v.x - 1, v.y)] && point.x < ThermalToggleRegionSize) - { - RequestHullConductivityToggle(entity, int2(v.x - 1, v.y), 0); - } - if (_displayedHullShape[int2(v.x + 1, v.y)] && point.x > 1 - ThermalToggleRegionSize) - { - RequestHullConductivityToggle(entity, int2(v.x, v.y), 0); - } - if (_displayedHullShape[int2(v.x, v.y - 1)] && point.y < ThermalToggleRegionSize) - { - RequestHullConductivityToggle(entity, int2(v.x, v.y - 1), 1); - } - if (_displayedHullShape[int2(v.x, v.y + 1)] && point.y > 1 - ThermalToggleRegionSize) - { - RequestHullConductivityToggle(entity, int2(v.x, v.y), 1); - } - }); - } - CellInstances.Add(v, cell); - } - } - RefreshCells(CellInstances.Keys); - - _subscriptions.Add(entity.ArmorDamage.Subscribe(hit => - { - var hitCells = new[] { hit.pos }; - StartCoroutine(Pulse(hitCells, HitType.Armor, _hitSequence++)); - RefreshCells(hitCells); - })); - - _subscriptions.Add(entity.ItemDamage.Where(hit=>hit.damage > HitDamageThreshold).Subscribe(hit => - { - var hitCells = hit.item.InsetShape.Coordinates; - if(gameObject.activeInHierarchy) - StartCoroutine(Pulse(hitCells, HitType.Armor, _hitSequence++)); - RefreshCells(hitCells); - })); - } - - private IEnumerator Pulse(int2[] cells, HitType hitType, int sequence) - { - foreach (var cell in cells) - { - if(_cellAnimationSequence.ContainsKey(cell)) - _cellAnimationSequence[cell] = max(_cellAnimationSequence[cell], sequence); - else - _cellAnimationSequence[cell] = sequence; - } - var hitColor = hitType switch - { - HitType.Armor => Settings.ArmorHitColor, - HitType.Hardpoint => Settings.HardpointHitColor, - HitType.Gear => Settings.GearHitColor, - _ => Color.white - }; - float startTime = Time.time; - while (Time.time - startTime < CellHitPulseTime) - { - var lerp = (Time.time - startTime) / CellHitPulseTime; - foreach(var cell in cells) - if(_cellAnimationSequence[cell] == sequence) - if(CellInstances.ContainsKey(cell)) - CellInstances[cell].Background.color = Color.Lerp(hitColor, CellBackgroundColor, lerp); - yield return null; - } - foreach (var cell in cells) - if(CellInstances.ContainsKey(cell)) - CellInstances[cell].Background.color = CellBackgroundColor; - } - - public void Display(EquippedCargoBay cargo) - { - Clear(); - - _subscriptions.Add(cargo.Cargo.ObserveAdd().Subscribe(_ => RefreshCells())); - _subscriptions.Add(cargo.Cargo.ObserveRemove().Subscribe(_ => RefreshCells())); - - _displayedCargo = cargo; - _displayedEntity = null; - _displayedEntityKey = ""; - if (TryResolveCargoBay(cargo, out var displayedCargoEntityKey, out var displayedCargoIndex)) - { - _displayedCargoEntityKey = displayedCargoEntityKey; - _displayedCargoIndex = displayedCargoIndex; - } - else - { - _displayedCargoEntityKey = ""; - _displayedCargoIndex = -1; - } - - if(Title) - Title.text = cargo.Name; - - // FakeOccupancy = new Shape(cargo.InteriorShape.Width, cargo.InteriorShape.Height); - // IgnoreOccupancy = new Shape(cargo.InteriorShape.Width, cargo.InteriorShape.Height); - Grid.constraintCount = cargo.InteriorShape.Width; - foreach (var v in cargo.InteriorShape.AllCoordinates) - { - if (!cargo.InteriorShape[v]) - { - var empty = new GameObject("Empty Node", typeof(RectTransform)); - empty.transform.SetParent(Grid.transform); - EmptyCells.Add(empty); - } - else - { - var cell = NodePrototype.Instantiate(); - cell.PointerClickTrigger.OnPointerClickAsObservable() - .Subscribe(data => - { - if (cell != _clickCell || Time.time - _clickTime > DoubleClickTime) - _clickCount = 0; - _clickCell = cell; - _clickTime = Time.time; - _clickCount++; - _onClick?.OnNext((new InventoryCargoEventData(data, v, cargo), _clickCount)); - }); - cell.BeginDragTrigger.OnBeginDragAsObservable() - .Subscribe(data => - { - //Debug.Log("Inventory Drag Start"); - var item = cargo.Occupancy[v.x, v.y]; - if (item != null) - { - var itemPosition = cargo.Cargo[item]; - var originalOccupancy = cargo.InteriorShape.Inset(GetItemShape(item), itemPosition, item.Rotation); - _dragCells = originalOccupancy.Coordinates - .Select(v1 => Instantiate(CellInstances[v1], DragParent, true).transform).ToArray(); - foreach(var dragCell in _dragCells) - { - DestroyImmediate(dragCell.GetComponent()); - foreach (var component in dragCell.GetComponentsInChildren()) - component.enabled = false; - foreach (var img in dragCell.GetComponentsInChildren()) - img.color = new Color(img.color.r, img.color.g, img.color.b, img.color.a * .5f); - dragCell.GetComponentInChildren().raycastTarget = false; - } - _dragOffsets = _dragCells.Select(x => (Vector2) x.position - data.position).ToArray(); - IgnoreOccupancy = originalOccupancy; - RefreshCells(); - _originalRotation = item.Rotation; - DragSession.Begin(new ItemInstanceDragObject(item, cargo, cargo.Cargo[item] - v)); - // TODO: SFX: Pickup - } - }); - cell.DragTrigger.OnDragAsObservable() - .Subscribe(data => - { - for (var i = 0; i < _dragCells.Length; i++) - _dragCells[i].position = new Vector3( - data.position.x + _dragOffsets[i].x, - data.position.y + _dragOffsets[i].y, - _dragCells[i].position.z); - }); - cell.EndDragTrigger.OnEndDragAsObservable() - .Subscribe(data => - { - //Debug.Log("Inventory Drag End"); - IgnoreOccupancy = null; - DragSession.End(); - foreach(var dragObject in _dragCells) - Destroy(dragObject.gameObject); - _dragCells = null; - RefreshCells(); - }); - cell.PointerEnterTrigger.OnPointerEnterAsObservable() - .Subscribe(data => - { - //Debug.Log("Inventory Pointer Enter"); - if (!DragSession.TryGetDraggedItem(out var itemDragObject)) return; - var item = itemDragObject.Item; - var placementPosition = v + itemDragObject.OriginCellOffset; - //foreach (var cell in _dragCells) cell.gameObject.SetActive(false); - FakeItem = item; - FakeOccupancy = cargo.InteriorShape.Inset(GetItemShape(item), placementPosition, item.Rotation); - RefreshCells(); - DragSession.RegisterTarget(drag => - { - //Debug.Log("Inventory Drag Callback"); - FakeOccupancy = null; - RequestDraggedItemToCargo(drag, cargo, placementPosition); - // TODO: SFX: Drop - }); - }); - cell.PointerExitTrigger.OnPointerExitAsObservable() - .Subscribe(data => - { - if (!DragSession.TryGetDraggedItem(out _)) return; - FakeItem = null; - FakeOccupancy = null; - RefreshCells(); - DragSession.UnregisterTarget(); - }); - - CellInstances.Add(v, cell); - } - } - RefreshCells(); - } - - public void RefreshCells() - { - RefreshCells(CellInstances.Keys); - } - - public void RefreshCells(IEnumerable cells) - { - if (_displayedEntity != null) - { - foreach (var v in cells) - { - if(!CellInstances.ContainsKey(v)) continue; - - var spriteIndex = 0; - var interior = _displayedHullInterior[v]; - var item = FakeOccupancy?[v]??false ? FakeItem : IgnoreOccupancy?[v]??false ? null : _displayedEntity.GearOccupancy[v.x, v.y]?.EquippableItem; - var hardpoint = _displayedEntity.Hardpoints[v.x, v.y]; - - bool HardpointMatch(int2 offset) - { - var v2 = v + offset * (Flip ? -1 : 1); - return !( - _displayedHullShape[v2] && - _displayedEntity.Hardpoints[v2.x, v2.y] == hardpoint && - (FakeOccupancy?[v2]??false ? FakeItem : IgnoreOccupancy?[v2]??false ? null : _displayedEntity.GearOccupancy[v2.x, v2.y]?.EquippableItem) == item - ); - } - - bool NoHardpointMatch(int2 offset) - { - var v2 = v + offset * (Flip ? -1 : 1); - return !( - _displayedHullShape[v2] && ( - !interior && !_displayedHullInterior[v2] && _displayedEntity.Hardpoints[v2.x, v2.y] == null || - interior && item != null && - (FakeOccupancy?[v2]??false ? FakeItem : IgnoreOccupancy?[v2]??false ? null : _displayedEntity.GearOccupancy[v2.x, v2.y]?.EquippableItem) == item - ) - ); - } - - if (hardpoint != null) - { - for(int i = 0; i < 8; i++) - if (HardpointMatch(_offsets[i])) - spriteIndex += 1 << i; - } - else - { - for(int i = 0; i < 8; i++) - if (NoHardpointMatch(_offsets[i])) - spriteIndex += 1 << i; - } - - var bgSpriteIndex = spriteIndex; - - if (item != null) - spriteIndex += 1 << 8; - - if (_thermal) - { - bool ThermalMatch(int2 offset) - { - if (!_displayedHullShape[v + offset]) return false; - var i = (offset.x, offset.y); - return i switch - { - (1, 0) => _displayedEntity.HullConductivity[v.x, v.y].x, - (-1, 0) => _displayedEntity.HullConductivity[v.x-1, v.y].x, - (0, 1) => _displayedEntity.HullConductivity[v.x, v.y].y, - (0, -1) => _displayedEntity.HullConductivity[v.x, v.y-1].y, - _ => false - }; - } - spriteIndex = 0; - for(int i = 0; i < 4; i++) - if (ThermalMatch(_offsets[i])) - spriteIndex += 1 << i; - } - - CellInstances[v].Background.sprite = NodeBackgroundTextures[bgSpriteIndex]; - CellInstances[v].Icon.sprite = _thermal ? ThermalTextures[spriteIndex] : NodeTextures[spriteIndex]; - CellInstances[v].Icon.color = GetColor(v); - } - } - else if(_displayedCargo != null) - { - foreach (var v in cells) - { - if(!CellInstances.ContainsKey(v)) continue; - - var spriteIndex = 0; - var item = FakeOccupancy?[v]??false ? FakeItem : IgnoreOccupancy?[v]??false ? null : _displayedCargo.Occupancy[v.x, v.y]; - - bool ItemMatch(int2 offset) - { - var v2 = v + offset; - return !_displayedCargo.InteriorShape[v2] || item == null || - (FakeOccupancy?[v2]??false ? FakeItem : IgnoreOccupancy?[v2]??false ? null : _displayedCargo.Occupancy[v2.x, v2.y]) != item; - } - - for(int i = 0; i < 8; i++) - if (ItemMatch(_offsets[i])) - spriteIndex += 1 << i; - - var bgSpriteIndex = spriteIndex; - - if (item != null) - spriteIndex += 1 << 8; - - CellInstances[v].Background.sprite = NodeBackgroundTextures[bgSpriteIndex]; - CellInstances[v].Icon.sprite = NodeTextures[spriteIndex]; - CellInstances[v].Icon.color = GetColor(v); - } - } - } - - public Color GetColor(int2 position, bool highlight = false) - { - if (_displayedEntity != null) - { - var interior = _displayedHullInterior[position]; - var hardpoint = _displayedEntity.Hardpoints[position.x, position.y]; - var item = (FakeOccupancy?[position] ?? false ? FakeItem : - IgnoreOccupancy?[position] ?? false ? null : _displayedEntity.GearOccupancy[position.x, position.y]?.EquippableItem) as EquippableItem; - - if (_hud) - { - if (_displayedEntity.Armor[position.x, position.y] > .01f) - return Settings.ArmorGradient.Evaluate(_displayedEntity.Armor[position.x, position.y] / _displayedEntity.MaxArmor[position.x, position.y]); - - if (item != null) return Settings.DurabilityGradient.Evaluate(item.Durability / GetMaxDurability(item)); - return float3(.25f).ToColor(); - } - - if (hardpoint == null) - { - if(!interior) - return Color.white; - - if (item == null) - { - return float3(.25f).ToColor(); - } - - if(highlight) - return Color.white; - - return float3(.5f).ToColor(); - } - - var tint = hardpoint.TintColor; - - if (!highlight || item == null) - tint *= .7071f; - - return tint.ToColor(); - } - else - { - var item = FakeOccupancy?[position] ?? false ? FakeItem : IgnoreOccupancy?[position] ?? false ? null : _displayedCargo.Occupancy[position.x, position.y]; - - if (item == null) - return Color.white * .25f; - - var c = float3(1); - if (FindTypedInventoryItem(item)?.TryGetHardpointType(out HardpointType typedHardpoint) == true) - c = HardpointData.GetColor(typedHardpoint).ToUnityFloat3(); - - if(!highlight) - c *= .7071f; - - return c.ToColor(); - } - } - - private float GetMaxDurability(ItemInstance item) - { - var typedItem = FindTypedInventoryItem(item); - if (typedItem != null && typedItem.Durability > 0) - return (float)typedItem.Durability; - - return item is EquippableItem equippable ? Math.Max(equippable.Durability, 1f) : 1f; - } - - private Shape GetItemShape(ItemInstance item) - { - var typedItem = FindTypedInventoryItem(item); - if (typedItem != null && typedItem.ShapeCells.Count > 0) - return ToShape(typedItem.ShapeWidth, typedItem.ShapeHeight, typedItem.ShapeCells); - - return new Shape(1, 1); - } - - private bool TryResolveTypedHullGeometry(ItemInstance hull, out Shape hullShape, out Shape interiorShape) - { - hullShape = null; - interiorShape = null; - var typedHull = FindTypedInventoryItem(hull); - if (typedHull == null || - typedHull.ShapeCells.Count == 0 || - typedHull.InteriorShapeCells.Count == 0 || - typedHull.ShapeWidth <= 0 || - typedHull.ShapeHeight <= 0 || - typedHull.InteriorShapeWidth <= 0 || - typedHull.InteriorShapeHeight <= 0) + var selectionKindText = PayloadValue(request, "selectionKind"); + if (!Enum.TryParse(selectionKindText, out AetheriaRuntimeInventoryDropdownSelectionKind selectionKind) || + selectionKind == AetheriaRuntimeInventoryDropdownSelectionKind.Unknown) { return false; } - hullShape = ToShape(typedHull.ShapeWidth, typedHull.ShapeHeight, typedHull.ShapeCells); - interiorShape = ToShape(typedHull.InteriorShapeWidth, typedHull.InteriorShapeHeight, typedHull.InteriorShapeCells); + selection = new AetheriaRuntimeInventoryDropdownSelection( + selectionKind, + command, + PayloadValue(request, "entityKey"), + ParsePayloadInt(request, "entityIndex"), + ParsePayloadInt(request, "bayIndex"), + ParsePayloadInt(request, "templateIndex")); return true; } - private static Shape ToShape(int width, int height, IReadOnlyList cells) + private static string PayloadValue(EveSurfaceCommandRequest request, string key) { - var shape = new Shape(Math.Max(width, 1), Math.Max(height, 1)); - foreach (var cell in cells) - { - if (cell.X >= 0 && cell.Y >= 0 && cell.X < shape.Width && cell.Y < shape.Height) - shape[int2(cell.X, cell.Y)] = true; - } - - return shape; + return request?.Payload?.GetString(key, "") ?? ""; } - private AetheriaRuntimeCatalogItem FindTypedInventoryItem(ItemInstance item) + private static int ParsePayloadInt(EveSurfaceCommandRequest request, string key) { - return CatalogSnapshot()?.FindItem(item, x => x.ItemKey); + return request?.Payload?.GetInt32(key, -1) ?? -1; + } + + private void HideDropdownSurface() + { + if (_dropdownSurfaceDocument != null) + AetheriaEveUnitySurfaceHost.Hide(_dropdownSurfaceDocument); + } + + private void HideInventoryPanelSurface() + { + if (_inventoryPanelSurfaceDocument != null) + AetheriaEveUnitySurfaceHost.Hide(_inventoryPanelSurfaceDocument); + } + + private void ShowRenameDialog(Entity entity) + { + if (entity == null || Dialog == null) + return; + + Dialog.Clear(); + var entityName = entity.Name; + Dialog.AddField("Name", () => entityName, s => entityName = s); + Dialog.Show(() => RequestEntityName(entity, entityName)); + Dialog.MoveToCursor(); + } + + private AetheriaRuntimeSurfaceDocument ResolveInventoryPanelSurface() + { + try + { + return RuntimeState.InventoryPanelSurface(BuildInventoryPanelSurfaceRequest()).Latest(); + } + catch (Exception ex) + { + Debug.LogWarning($"Failed to read Aetheria inventory panel surface from local Verse state: {ex.Message}"); + return null; + } + } + + private AetheriaRuntimeSurfaceDocument ResolveDropdownSurface() + { + try + { + return RuntimeState.InventoryDropdownSurface(BuildDropdownSurfaceRequest()).Latest(); + } + catch (Exception ex) + { + Debug.LogWarning($"Failed to read Aetheria inventory dropdown surface from local Verse state: {ex.Message}"); + return null; + } + } + + private AetheriaRuntimeInventoryPanelSurfaceRequest BuildInventoryPanelSurfaceRequest() + { + var request = new AetheriaRuntimeInventoryPanelSurfaceRequest + { + ViewTitle = Title?.text ?? "", + DisplayedEntityKey = _displayedEntityKey, + DisplayedCargoEntityKey = _displayedCargoEntityKey, + DisplayedCargoIndex = _displayedCargoIndex, + ThermalView = _thermal, + HudView = _hud + }; + + if (_displayedEntity != null) + request.DisplayedEntityIndex = _displayedEntity.DaemonEntityIndex; + if (_displayedCargo != null && TryResolveDisplayedCargoEntityIndex(out var displayedCargoEntityIndex)) + request.DisplayedCargoEntityIndex = displayedCargoEntityIndex; + + if (DragSession.TryGetDraggedItem(out var itemDragObject)) + { + request.HasDragSession = true; + request.DragItemKey = itemDragObject.Item?.ItemKey ?? ""; + request.DragOriginOffsetX = itemDragObject.OriginCellOffset.x; + request.DragOriginOffsetY = itemDragObject.OriginCellOffset.y; + request.DragRotation = itemDragObject.Item?.Rotation.ToString() ?? ""; + } + + return request; + } + + private AetheriaRuntimeInventoryDropdownSurfaceRequest BuildDropdownSurfaceRequest() + { + return new AetheriaRuntimeInventoryDropdownSurfaceRequest + { + CurrentView = Title?.text ?? "", + DisplayedEntityKey = _displayedEntityKey, + DisplayedCargoEntityKey = _displayedCargoEntityKey, + DisplayedCargoIndex = _displayedCargoIndex, + CanSaveLoadout = !string.IsNullOrWhiteSpace(_displayedEntityKey) + }; + } + + private bool TryResolveDisplayedCargoEntityIndex(out int entityIndex) + { + entityIndex = -1; + if (string.IsNullOrWhiteSpace(_displayedCargoEntityKey)) + return false; + + var refit = StationRefitSnapshot(); + if (refit == null) + return false; + + if (string.Equals(refit.DockParentEntityKey, _displayedCargoEntityKey, StringComparison.Ordinal)) + entityIndex = refit.DockParentEntityIndex; + else + entityIndex = (refit.AvailableEntities ?? Array.Empty()) + .FirstOrDefault(option => string.Equals(option?.EntityKey ?? "", _displayedCargoEntityKey, StringComparison.Ordinal)) + ?.EntityIndex ?? -1; + + return entityIndex >= 0; + } + + private bool TryGetRecordKeyForPresentationEntity(Entity entity, out string recordKey) + { + recordKey = ""; + return entity != null && + _presentationEntityIndex != null && + _presentationEntityIndex.TryGetRecordKeyForPresentationEntity(entity, out recordKey); + } + + private bool IsCurrentEntity(Entity entity) + { + return entity != null && + TryGetRecordKeyForPresentationEntity(entity, out var entityKey) && + string.Equals(entityKey, CurrentEntitySnapshot()?.EntityKey ?? "", StringComparison.Ordinal); + } + + private bool TryResolveStationRefitEntity(string entityKey, out Entity entity) + { + entity = null; + return _presentationEntityIndex != null && + !string.IsNullOrWhiteSpace(entityKey) && + _presentationEntityIndex.TryGetPresentationEntityByRecordKey(entityKey, out entity); + } + + private bool TryResolveCurrentDockingBay(out EquippedDockingBay dockingBay) + { + dockingBay = null; + var refit = StationRefitSnapshot(); + return refit?.IsDocked == true && + refit.DockingBayIndex >= 0 && + !string.IsNullOrWhiteSpace(refit.DockParentEntityKey) && + _presentationEntityIndex != null && + _presentationEntityIndex.TryGetPresentationDockingBayByRecordKey( + refit.DockParentEntityKey, + refit.DockingBayIndex, + out dockingBay) && + dockingBay != null; + } + + private bool TryResolveStationRefitLoadout( + int templateIndex, + out AetheriaRuntimeStationLoadoutRestoreOption loadout) + { + loadout = null; + if (templateIndex < 0) + return false; + + loadout = (StationRefitSnapshot()?.LoadoutRestoreOptions ?? Array.Empty()) + .FirstOrDefault(option => option?.TemplateIndex == templateIndex); + return loadout != null; + } + + private bool TryResolveCargoBay(EquippedCargoBay cargoBay, out string entityKey, out int cargoIndex) + { + entityKey = ""; + cargoIndex = -1; + var entity = cargoBay?.Entity; + if (entity?.CargoBays == null || + !TryGetRecordKeyForPresentationEntity(entity, out entityKey)) + { + return false; + } + + cargoIndex = entity.CargoBays.IndexOf(cargoBay); + return cargoIndex >= 0; } private void RequestDockedCurrentShip(Ship ship) { - if (ship == null || !TryResolveEntityRecordKey(ship, out var targetEntityKey)) + if (ship == null || !TryGetRecordKeyForPresentationEntity(ship, out var targetEntityKey)) return; TrySubmitOperation( @@ -1034,18 +542,17 @@ private void Update() private void RequestEntityName(Entity entity, string name) { - if (entity == null || !TryResolveEntityRecordKey(entity, out var targetEntityKey)) + if (entity == null || !TryGetRecordKeyForPresentationEntity(entity, out var targetEntityKey)) return; TrySubmitOperation( operations => operations.SetEntityName(targetEntityKey, name ?? ""), - "entity name"); + "entity rename"); } private void RequestRuntimeLoadoutRestore(AetheriaRuntimeStationLoadoutRestoreOption loadout) { if (loadout == null || - !loadout.CanRestore || string.IsNullOrWhiteSpace(loadout.TargetEntityKey) || string.IsNullOrWhiteSpace(loadout.TemplateName)) { @@ -1057,373 +564,28 @@ private void Update() "loadout restore"); } - private void RequestLoadoutTemplateSave(Entity entity) + private void RequestLoadoutTemplateSave(string targetEntityKey) { - if (entity == null || !TryResolveEntityRecordKey(entity, out var targetEntityKey)) + if (string.IsNullOrWhiteSpace(targetEntityKey)) return; try { - var loadout = CreateLoadoutTemplate(targetEntityKey); - if (loadout?.RootEntity == null || string.IsNullOrWhiteSpace(loadout.RootEntity.Hull?.ItemKey ?? "")) - return; - - var submitted = AetheriaUnityRuntimeClientProvider + var loadout = RuntimeState.DaemonFrame.Latest().Run.CreateLoadoutTemplate(targetEntityKey ?? ""); + AetheriaUnityRuntimeClientProvider .Ui("unity-inventory") .SaveLoadoutTemplateAsync(loadout, "unity-inventory") + .ConfigureAwait(false) .GetAwaiter() .GetResult(); - - Debug.Log($"Submitted Aetheria loadout template save Eve operation: {submitted.OperationId}"); } catch (Exception ex) { - Debug.LogError($"Failed to send Aetheria loadout template save Eve command: {ex}"); + Debug.LogWarning($"Failed to save Aetheria loadout template: {ex.Message}"); } } - private void RequestHullConductivityToggle(Entity entity, int2 position, int axis) - { - if (entity == null || !TryResolveEntityRecordKey(entity, out var targetEntityKey)) - return; - - TrySubmitOperation( - operations => operations.ToggleHullConductivity(targetEntityKey, position.x, position.y, axis), - "hull conductivity"); - } - - private bool TryResolveEntityRecordKey(Entity entity, out string recordKey) - { - recordKey = ""; - return _observedEntityIndex != null && - _observedEntityIndex.TryResolveEntityRecordKey(entity, out recordKey); - } - - private bool IsCurrentEntity(Entity entity) - { - if (entity == null || - !TryResolveEntityRecordKey(entity, out var entityKey) || - string.IsNullOrWhiteSpace(entityKey) || - !TryResolveCurrentEntityKey(out var currentEntityKey)) - { - return false; - } - - return string.Equals(entityKey, currentEntityKey, StringComparison.Ordinal); - } - - private bool TryResolveCurrentEntityKey(out string currentEntityKey) - { - currentEntityKey = CurrentEntitySnapshot()?.EntityKey ?? ""; - return !string.IsNullOrWhiteSpace(currentEntityKey); - } - - private bool TryResolveCurrentDockingBayRow(out AetheriaRuntimeStationDockingBayRow dockingBay) - { - dockingBay = null; - var refit = StationRefitSnapshot(); - if (refit?.IsDocked != true || refit.DockingBayIndex < 0) - return false; - - dockingBay = (refit.DockingBays ?? Array.Empty()) - .FirstOrDefault(row => row != null && row.DockingBayIndex == refit.DockingBayIndex); - return dockingBay != null; - } - - private bool TryResolveCurrentDockingBay(out EquippedDockingBay dockingBay) - { - dockingBay = null; - var refit = StationRefitSnapshot(); - if (refit?.IsDocked != true || - refit.DockingBayIndex < 0 || - string.IsNullOrWhiteSpace(refit.DockParentEntityKey) || - _observedEntityIndex == null || - !_observedEntityIndex.TryResolveDockingBayByRecordKey( - refit.DockParentEntityKey, - refit.DockingBayIndex, - out dockingBay)) - { - return false; - } - - return dockingBay != null; - } - - private bool TryResolveStationRefitEntity(string entityKey, int optionIndex, out Entity entity) - { - entity = null; - if (string.IsNullOrWhiteSpace(entityKey) && - optionIndex >= 0 && - optionIndex < (_dropdownStationRefitEntities?.Length ?? 0)) - { - entityKey = _dropdownStationRefitEntities[optionIndex]?.EntityKey ?? ""; - } - - return TryResolveObservedAvailableEntityByKey(entityKey, out entity); - } - - private bool TryResolveObservedAvailableEntityByKey(string entityKey, out Entity entity) - { - entity = null; - if (_observedEntityIndex == null || string.IsNullOrWhiteSpace(entityKey)) - return false; - - return _observedEntityIndex.TryResolveEntityByRecordKey(entityKey, out entity); - } - - private bool IsDisplayedEntityKey(string entityKey) - { - return _displayedEntity != null && - !string.IsNullOrWhiteSpace(_displayedEntityKey) && - string.Equals(_displayedEntityKey, entityKey, StringComparison.Ordinal); - } - - private bool IsDisplayedCargoBayKey(string entityKey, int cargoBayIndex) - { - return _displayedCargo != null && - !string.IsNullOrWhiteSpace(_displayedCargoEntityKey) && - string.Equals(_displayedCargoEntityKey, entityKey, StringComparison.Ordinal) && - _displayedCargoIndex == cargoBayIndex; - } - - private bool TryResolveCargoBay(EquippedCargoBay cargoBay, out string entityKey, out int cargoIndex) - { - return TryResolveEquippedItem(cargoBay, out entityKey, out cargoIndex); - } - - private bool TryResolveEquippedItem(EquippedItem item, out string entityKey, out int equipmentIndex) - { - entityKey = ""; - equipmentIndex = -1; - var entity = item?.Entity; - if (entity?.Equipment == null || - !TryResolveEntityRecordKey(entity, out entityKey)) - { - return false; - } - - equipmentIndex = entity.Equipment.IndexOf(item); - return equipmentIndex >= 0; - } - - private bool TryResolveTypedInventoryRows( - string entityKey, - out IReadOnlyList equipment, - out IReadOnlyList cargo) - { - equipment = Array.Empty(); - cargo = Array.Empty(); - if (string.IsNullOrWhiteSpace(entityKey)) - return false; - - try - { - var current = CurrentEntitySnapshot(); - if (current != null && string.Equals(current.EntityKey, entityKey, StringComparison.Ordinal)) - { - equipment = current.Equipment ?? Array.Empty(); - cargo = current.Cargo ?? Array.Empty(); - return true; - } - - var refit = StationRefitSnapshot(); - var entityIndex = -1; - if (refit != null) - { - if (string.Equals(refit.DockParentEntityKey, entityKey, StringComparison.Ordinal)) - entityIndex = refit.DockParentEntityIndex; - else - entityIndex = (refit.AvailableEntities ?? Array.Empty()) - .FirstOrDefault(option => string.Equals(option.EntityKey, entityKey, StringComparison.Ordinal)) - ?.EntityIndex ?? -1; - } - - if (entityIndex < 0) - return false; - - var inventory = InventorySnapshot(entityIndex); - if (inventory == null || !string.Equals(inventory.EntityKey, entityKey, StringComparison.Ordinal)) - return false; - - equipment = inventory.Equipment ?? Array.Empty(); - cargo = inventory.Cargo ?? Array.Empty(); - return true; - } - catch (Exception ex) - { - Debug.LogWarning($"Failed to validate Aetheria typed inventory document for {entityKey}: {ex.Message}"); - return false; - } - } - - private bool TryValidateTypedCargoSlot( - string entityKey, - int cargoIndex, - ItemInstance item, - int2 originPosition, - bool hasOriginPosition) - { - if (item == null || - string.IsNullOrWhiteSpace(item.ItemKey) || - !TryResolveTypedInventoryRows(entityKey, out _, out var cargo)) - { - return false; - } - - return cargo.Any(row => - string.Equals(row.Source, "cargo", StringComparison.Ordinal) && - row.SourceIndex == cargoIndex && - string.Equals(row.ItemKey, item.ItemKey, StringComparison.Ordinal) && - (!hasOriginPosition || (row.X == originPosition.x && row.Y == originPosition.y))); - } - - private bool TryValidateTypedEquipmentSlot( - string entityKey, - int equipmentIndex, - EquippedItem item) - { - if (item?.EquippableItem == null || - string.IsNullOrWhiteSpace(item.EquippableItem.ItemKey) || - !TryResolveTypedInventoryRows(entityKey, out var equipment, out _)) - { - return false; - } - - return equipment.Any(row => - string.Equals(row.Source, "equipment", StringComparison.Ordinal) && - row.SourceIndex == equipmentIndex && - row.X == item.Position.x && - row.Y == item.Position.y && - string.Equals(row.ItemKey, item.EquippableItem.ItemKey, StringComparison.Ordinal)); - } - - private void RequestCargoItemTransfer( - EquippedCargoBay origin, - EquippedCargoBay destination, - ItemInstance item, - int2 destinationPosition, - bool hasDestinationPosition) - { - if (!TryResolveCargoBay(origin, out var originEntityKey, out var originCargoIndex) || - !TryResolveCargoBay(destination, out var destinationEntityKey, out var destinationCargoIndex) || - item == null || - string.IsNullOrWhiteSpace(item.ItemKey) || - !origin.Cargo.TryGetValue(item, out var originPosition) || - !TryValidateTypedCargoSlot(originEntityKey, originCargoIndex, item, originPosition, true)) - { - return; - } - - var quantity = item is SimpleCommodity commodity ? commodity.Quantity : 1; - if (quantity <= 0) - return; - - TrySubmitOperation( - operations => operations.TransferCargoItem( - originEntityKey, - originCargoIndex, - destinationEntityKey, - destinationCargoIndex, - item.ItemKey, - quantity, - originPosition.x, - originPosition.y, - destinationPosition.x, - destinationPosition.y, - hasDestinationPosition), - "cargo transfer"); - } - - private void RequestCargoItemEquip( - EquippedCargoBay origin, - Entity destination, - EquippableItem item, - int2 destinationPosition, - bool hasDestinationPosition) - { - if (!TryResolveCargoBay(origin, out var originEntityKey, out var originCargoIndex) || - !TryResolveEntityRecordKey(destination, out var destinationEntityKey) || - item == null || - string.IsNullOrWhiteSpace(item.ItemKey) || - !origin.Cargo.TryGetValue(item, out var originPosition) || - !TryValidateTypedCargoSlot(originEntityKey, originCargoIndex, item, originPosition, true)) - { - return; - } - - TrySubmitOperation( - operations => operations.EquipItem( - "cargo", - originEntityKey, - originCargoIndex, - destinationEntityKey, - item.ItemKey, - originPosition.x, - originPosition.y, - destinationPosition.x, - destinationPosition.y, - hasDestinationPosition), - "cargo equip"); - } - - private void RequestEquippedItemStore( - EquippedItem item, - EquippedCargoBay destination, - int2 destinationPosition, - bool hasDestinationPosition) - { - if (!TryResolveEquippedItem(item, out var originEntityKey, out var equipmentIndex) || - !TryResolveCargoBay(destination, out var destinationEntityKey, out var destinationCargoIndex) || - item?.EquippableItem == null || - !TryValidateTypedEquipmentSlot(originEntityKey, equipmentIndex, item)) - { - return; - } - - TrySubmitOperation( - operations => operations.StoreItem( - originEntityKey, - equipmentIndex, - destinationEntityKey, - destinationCargoIndex, - item.EquippableItem.ItemKey, - destinationPosition.x, - destinationPosition.y, - hasDestinationPosition), - "equipment store"); - } - - private void RequestEquippedItemEquip( - EquippedItem item, - Entity destination, - int2 destinationPosition) - { - if (!TryResolveEquippedItem(item, out var originEntityKey, out var equipmentIndex) || - !TryResolveEntityRecordKey(destination, out var destinationEntityKey) || - item?.EquippableItem == null || - !TryValidateTypedEquipmentSlot(originEntityKey, equipmentIndex, item)) - { - return; - } - - TrySubmitOperation( - operations => operations.EquipItem( - "equipment", - originEntityKey, - equipmentIndex, - destinationEntityKey, - item.EquippableItem.ItemKey, - item.Position.x, - item.Position.y, - destinationPosition.x, - destinationPosition.y, - true), - "equipment equip"); - } - - private bool TrySubmitOperation( - Action submit, - string label) + private bool TrySubmitOperation(Action submit, string label) { if (submit == null) return false; @@ -1444,7 +606,7 @@ private void Update() { try { - return AetheriaUnityRuntimeClientProvider.CurrentEntityState("unity-inventory"); + return RuntimeState.CurrentEntity.Latest(); } catch (Exception ex) { @@ -1457,7 +619,7 @@ private void Update() { try { - return AetheriaUnityRuntimeClientProvider.CurrentStationRefit("unity-inventory"); + return RuntimeState.StationRefit.Latest(); } catch (Exception ex) { @@ -1466,200 +628,25 @@ private void Update() } } - private AetheriaRuntimeLoadoutTemplateCommit CreateLoadoutTemplate(string targetEntityKey) - { - var frame = ResolveLoadoutFrame(); - return (frame?.Run ?? new AetheriaRuntimeRunCheckpointCommit()) - .CreateLoadoutTemplate(targetEntityKey ?? ""); - } - - private AetheriaRuntimeDaemonFrameDocument ResolveLoadoutFrame() - { - try - { - return AetheriaUnityRuntimeClientProvider.CurrentDaemonFrame("unity-inventory"); - } - catch (Exception ex) - { - Debug.LogWarning($"Failed to read Aetheria daemon frame for loadout template save: {ex.Message}"); - return null; - } - } - - private AetheriaRuntimeInventoryDocument InventorySnapshot(int entityIndex) - { - try - { - return AetheriaUnityRuntimeClientProvider.CurrentInventory(entityIndex, "unity-inventory"); - } - catch (Exception ex) - { - Debug.LogWarning($"Failed to read Aetheria typed inventory document for entity {entityIndex}: {ex.Message}"); - return null; - } - } - - private AetheriaRuntimeCatalogSnapshot CatalogSnapshot() - { - try - { - return AetheriaUnityRuntimeClientProvider.CurrentCatalog("unity-inventory"); - } - catch (Exception ex) - { - Debug.LogWarning($"Failed to read Aetheria runtime catalog for inventory panel: {ex.Message}"); - return null; - } - } - - private AetheriaRuntimePlayerSettingsDocument PlayerSettingsSnapshot() - { - try - { - return AetheriaUnityRuntimeClientProvider.CurrentPlayerSettings("unity-inventory"); - } - catch (Exception ex) - { - Debug.LogWarning($"Failed to read Aetheria player settings for inventory panel: {ex.Message}"); - return null; - } - } - - private string FormatValue(float value) - { - var settings = PlayerSettingsSnapshot(); - var significantDigits = settings?.SignificantDigits ?? 3; - var magnitude = value == 0.0f ? 0 : (int)Math.Floor(Math.Log10(Math.Abs(value))) + 1; - var digits = significantDigits - magnitude; - if (digits < 0) - digits = 0; - - var formatted = value.ToString($"N{digits}", CultureInfo.CurrentCulture); - var separator = CultureInfo.CurrentCulture.NumberFormat.NumberDecimalSeparator; - var decimalSeparator = Convert.ToChar(separator); - return formatted.Contains(separator) - ? formatted.TrimEnd('0').TrimEnd(decimalSeparator) - : formatted; - } - - private string FormatTemperature(float value) - { - var unit = PlayerSettingsSnapshot()?.TemperatureUnit ?? nameof(TemperatureUnit.Celsius); - if (string.Equals(unit, nameof(TemperatureUnit.Kelvin), StringComparison.OrdinalIgnoreCase)) - return $"{FormatValue(value)} K"; - if (string.Equals(unit, nameof(TemperatureUnit.Fahrenheit), StringComparison.OrdinalIgnoreCase)) - return $"{FormatValue(value * (9f / 5) - 459.67f)} F"; - - return $"{FormatValue(value - 273.15f)} C"; - } - - private void RequestDraggedItemToEntity(DragObject drag, Entity destination, int2 destinationPosition) - { - switch (drag) - { - case EquippedItemDragObject equippedItemDragObject: - RequestEquippedItemEquip( - equippedItemDragObject.EquippedItem, - destination, - destinationPosition); - return; - case ItemInstanceDragObject itemInstanceDragObject when itemInstanceDragObject.Item is EquippableItem equippableItem: - RequestCargoItemEquip( - itemInstanceDragObject.OriginInventory, - destination, - equippableItem, - destinationPosition, - true); - return; - } - } - - private void RequestDraggedItemToCargo(DragObject drag, EquippedCargoBay destination, int2 destinationPosition) - { - switch (drag) - { - case EquippedItemDragObject equippedItemDragObject: - RequestEquippedItemStore( - equippedItemDragObject.EquippedItem, - destination, - destinationPosition, - true); - return; - case ItemInstanceDragObject itemInstanceDragObject: - RequestCargoItemTransfer( - itemInstanceDragObject.OriginInventory, - destination, - itemInstanceDragObject.Item, - destinationPosition, - true); - return; - } - } - private void OnDestroy() { + if (_inventoryPanelSurfaceDocument != null) + { + AetheriaEveUnitySurfaceHost.DestroyDocument(_inventoryPanelSurfaceDocument); + _inventoryPanelSurfaceDocument = null; + } + if (_dropdownSurfaceDocument != null) { AetheriaEveUnitySurfaceHost.DestroyDocument(_dropdownSurfaceDocument); _dropdownSurfaceDocument = null; } } - - public Subject OnBackgroundClick = new Subject(); - private Transform[] _dragCells; - private Vector2[] _dragOffsets; - private ItemRotation _originalRotation; - - public Subject<(InventoryEventData data, int clickCount)> OnClickAsObservable() => _onClick ?? (_onClick = new Subject<(InventoryEventData data, int clickCount)>()); - // public UniRx.IObservable OnBeginDragAsObservable() => _onBeginDrag ?? (_onBeginDrag = new Subject()); - // public UniRx.IObservable OnDragAsObservable() => _onDrag ?? (_onDrag = new Subject()); - // public UniRx.IObservable OnEndDragAsObservable() => _onEndDrag ?? (_onEndDrag = new Subject()); - // public UniRx.IObservable OnPointerEnterAsObservable() => _onPointerEnter ?? (_onPointerEnter = new Subject()); - // public UniRx.IObservable OnPointerExitAsObservable() => _onPointerExit ?? (_onPointerExit = new Subject()); - - public void OnPointerClick(PointerEventData eventData) - { - OnBackgroundClick.OnNext(eventData); - } -} - -public abstract class InventoryEventData -{ - protected InventoryEventData(PointerEventData pointerEventData, int2 position) - { - PointerEventData = pointerEventData; - Position = position; - } - - public PointerEventData PointerEventData { get; } - public int2 Position { get; } -} - -public class InventoryEntityEventData : InventoryEventData -{ - public InventoryEntityEventData(PointerEventData pointerEventData, int2 position, Entity entity) : - base(pointerEventData, position) - { - Entity = entity; - } - - public Entity Entity { get; } -} - -public class InventoryCargoEventData : InventoryEventData -{ - public InventoryCargoEventData(PointerEventData pointerEventData, int2 position, EquippedCargoBay cargoBay) : - base(pointerEventData, position) - { - CargoBay = cargoBay; - } - - public EquippedCargoBay CargoBay { get; } } public enum InventoryPanelTarget { None, - Cargo, - Equipment + Equipment, + Cargo } diff --git a/Assets/Scripts/UI/Menu/LocalMenu.cs b/Assets/Scripts/UI/Menu/LocalMenu.cs index 2eafd5d23..2d516b01c 100644 --- a/Assets/Scripts/UI/Menu/LocalMenu.cs +++ b/Assets/Scripts/UI/Menu/LocalMenu.cs @@ -5,6 +5,7 @@ using System.Linq; using GameCult.Aetheria.EveRuntime; using GameCult.Aetheria.State.Verse; using GameCult.Eve.Surface; +using GameCult.Mesh; using Ink.Runtime; using TMPro; using UniRx; @@ -20,16 +21,17 @@ public class LocalMenu : MonoBehaviour public ChoicePrefab ChoicePrefab; private string _currentPath; - private LocationStory _currentLocation; + private LocationStory _currentLocalInkLocation; private Story _activeStory; private readonly List _activeChoices = new List(); private UIDocument _surfaceDocument; private readonly AetheriaEveUnitySurfaceChrome _surfaceChrome = new AetheriaEveUnitySurfaceChrome(); - private AetheriaUnityObservedEntityIndex _observedEntityIndex; + private AetheriaUnityPresentationEntityIndex _presentationEntityIndex; + private AetheriaClientState _runtimeState; - public void SetObservedEntityIndex(AetheriaUnityObservedEntityIndex observedEntityIndex) + public void SetPresentationEntityIndex(AetheriaUnityPresentationEntityIndex presentationEntityIndex) { - _observedEntityIndex = observedEntityIndex; + _presentationEntityIndex = presentationEntityIndex; } private sealed class ActiveStoryChoice @@ -48,13 +50,13 @@ public class LocalMenu : MonoBehaviour private void OnEnable() { - if (!TryResolveDockedLocalStory(out _currentLocation)) + if (!TryResolveDockedLocalInkStory(out _currentLocalInkLocation)) { HideStorySurface(); return; } - _activeStory = _currentLocation.Story; + _activeStory = _currentLocalInkLocation.Story; Continue(); } @@ -84,7 +86,7 @@ public class LocalMenu : MonoBehaviour else if (!_activeStory.canContinue) { // There's no choices, but we also can't continue; indicates we hit an END - if (_activeStory == _currentLocation.Story) + if (_activeStory == _currentLocalInkLocation.Story) { // END inside location-based story thread, restart the story _activeStory.ResetState(); @@ -93,7 +95,7 @@ public class LocalMenu : MonoBehaviour else { // END inside quest content, switch back to location thread and present choices - _activeStory = _currentLocation.Story; + _activeStory = _currentLocalInkLocation.Story; Continue(); } } @@ -103,10 +105,10 @@ public class LocalMenu : MonoBehaviour void PresentCurrentChoices() { - Debug.Log($"Current Path: \"{_currentPath}\" in {(_activeStory == _currentLocation.Story ? "Location Story" : "Quest Story")}"); - if(!string.IsNullOrEmpty(_currentPath) && _currentLocation.KnotQuests.ContainsKey(_currentPath)) + Debug.Log($"Current Path: \"{_currentPath}\" in {(_activeStory == _currentLocalInkLocation.Story ? "Location Story" : "Quest Story")}"); + if(!string.IsNullOrEmpty(_currentPath) && _currentLocalInkLocation.KnotQuests.ContainsKey(_currentPath)) { - foreach (var quest in _currentLocation.KnotQuests[_currentPath]) + foreach (var quest in _currentLocalInkLocation.KnotQuests[_currentPath]) { if (quest.Story == _activeStory) continue; // Don't repeat choices for active injected branch @@ -188,23 +190,26 @@ public class LocalMenu : MonoBehaviour private string ResolveLocationLabel() { - if (!string.IsNullOrWhiteSpace(_currentLocation?.Name)) - return _currentLocation.Name; - if (!string.IsNullOrWhiteSpace(_currentLocation?.FileName)) - return _currentLocation.FileName; + if (!string.IsNullOrWhiteSpace(_currentLocalInkLocation?.Name)) + return _currentLocalInkLocation.Name; + if (!string.IsNullOrWhiteSpace(_currentLocalInkLocation?.FileName)) + return _currentLocalInkLocation.FileName; return "Local"; } - private bool TryResolveDockedLocalStory(out LocationStory story) + private bool TryResolveDockedLocalInkStory(out LocationStory story) { story = null; var docking = CurrentDockingSnapshot(); - if (_observedEntityIndex == null || + // Ink story execution is still a Unity-local island. The docking identity + // comes from managed typed state; the remaining object lookup should go + // away when local story state becomes a daemon-owned CultMesh document. + if (_presentationEntityIndex == null || docking == null || !docking.IsDocked || string.IsNullOrWhiteSpace(docking.DockParentEntityKey) || docking.DockingBayIndex < 0 || - !_observedEntityIndex.TryResolveDockingBayByRecordKey( + !_presentationEntityIndex.TryGetPresentationDockingBayByRecordKey( docking.DockParentEntityKey, docking.DockingBayIndex, out var dockingBay) || @@ -217,11 +222,12 @@ public class LocalMenu : MonoBehaviour return true; } - private static AetheriaRuntimeCurrentDockingDocument CurrentDockingSnapshot() + private AetheriaRuntimeCurrentDockingDocument CurrentDockingSnapshot() { try { - return AetheriaUnityRuntimeClientProvider.CurrentDockingState("unity-local-menu"); + _runtimeState ??= AetheriaUnityRuntimeClientProvider.RuntimeState("unity-local-menu"); + return _runtimeState.CurrentDocking.Latest(); } catch (Exception ex) { diff --git a/Assets/Scripts/UI/Menu/MapRenderer.cs b/Assets/Scripts/UI/Menu/MapRenderer.cs index 1b0fbf2e3..2d81b890c 100644 --- a/Assets/Scripts/UI/Menu/MapRenderer.cs +++ b/Assets/Scripts/UI/Menu/MapRenderer.cs @@ -7,6 +7,7 @@ using System.Collections; using System.Collections.Generic; using System.Linq; using GameCult.Aetheria.State.Verse; +using GameCult.Mesh; using TMPro; using UnityEngine; using Unity.Mathematics; @@ -33,6 +34,7 @@ public class MapRenderer : MonoBehaviour public RawImage RtsIconPrototype; public float Scale; public float2 Position; + private AetheriaClientState _runtimeState; public float IconSize = 1f/128; private RectTransform _rect; @@ -307,9 +309,8 @@ public class MapRenderer : MonoBehaviour { try { - return AetheriaUnityRuntimeClientProvider - .CurrentPlayerSettings("unity-map-renderer") - ?.ShowAsteroidsInMinimap ?? false; + _runtimeState ??= AetheriaUnityRuntimeClientProvider.RuntimeState("unity-map-renderer"); + return _runtimeState.PlayerSettings.Latest()?.ShowAsteroidsInMinimap ?? false; } catch (Exception ex) { diff --git a/Assets/Scripts/UI/Menu/MenuPanel.cs b/Assets/Scripts/UI/Menu/MenuPanel.cs index e552d9554..19ef14271 100644 --- a/Assets/Scripts/UI/Menu/MenuPanel.cs +++ b/Assets/Scripts/UI/Menu/MenuPanel.cs @@ -8,6 +8,7 @@ using System.Linq; using GameCult.Aetheria.EveRuntime; using GameCult.Aetheria.State.Verse; using GameCult.Eve.Surface; +using GameCult.Mesh; using UnityEngine; using UnityEngine.UIElements; @@ -34,6 +35,7 @@ public class MenuPanel : MonoBehaviour private MenuTabBinding _current; private UIDocument _tabSurfaceDocument; private readonly AetheriaEveUnitySurfaceChrome _tabSurfaceChrome = new AetheriaEveUnitySurfaceChrome(); + private AetheriaClientState _runtimeState; public MenuTab CurrentTab { get; private set; } @@ -157,12 +159,13 @@ public class MenuPanel : MonoBehaviour .ToArray(); } - private static bool TryResolveCurrentDocking(out AetheriaRuntimeCurrentDockingDocument docking) + private bool TryResolveCurrentDocking(out AetheriaRuntimeCurrentDockingDocument docking) { docking = null; try { - docking = AetheriaUnityRuntimeClientProvider.CurrentDockingState("unity-runtime-menu-tabs"); + _runtimeState ??= AetheriaUnityRuntimeClientProvider.RuntimeState("unity-runtime-menu-tabs"); + docking = _runtimeState.CurrentDocking.Latest(); return docking != null; } catch (Exception ex) diff --git a/Assets/Scripts/UI/Menu/SectorMap.cs b/Assets/Scripts/UI/Menu/SectorMap.cs index b08673c38..68283999b 100644 --- a/Assets/Scripts/UI/Menu/SectorMap.cs +++ b/Assets/Scripts/UI/Menu/SectorMap.cs @@ -3,6 +3,7 @@ using System.Collections; using System.Collections.Generic; using System.Linq; using GameCult.Aetheria.State.Verse; +using GameCult.Mesh; using TMPro; using UniRx; using UnityEngine; @@ -33,6 +34,7 @@ public class SectorMap : MonoBehaviour public Texture2D HomeIcon; public Prototype LegendPrototype; public float IconDistance = 1; + private AetheriaClientState _runtimeState; public float IconBackgroundSize = 3; public float LabelDistance = .4f; public float LinkWidth; @@ -219,8 +221,8 @@ public class SectorMap : MonoBehaviour private void EnsureSectorMapLoaded() { - _sectorMap = AetheriaUnityRuntimeClientProvider - .CurrentSectorMap("unity-sector-map"); + _runtimeState ??= AetheriaUnityRuntimeClientProvider.RuntimeState("unity-sector-map"); + _sectorMap = _runtimeState.SectorMap.Latest(); if (_sectorMapLoaded) return; diff --git a/Assets/Scripts/UI/Menu/SectorRenderer.cs b/Assets/Scripts/UI/Menu/SectorRenderer.cs index 37f683645..e687c62a8 100644 --- a/Assets/Scripts/UI/Menu/SectorRenderer.cs +++ b/Assets/Scripts/UI/Menu/SectorRenderer.cs @@ -1,11 +1,10 @@ using System; using System.Collections; using System.Collections.Generic; -using System.Globalization; -using System.Linq; using GameCult.Aetheria.EveRuntime; using GameCult.Aetheria.State.Verse; using GameCult.Eve.Surface; +using GameCult.Mesh; using UniRx; using UnityEngine; using UnityEngine.EventSystems; @@ -45,6 +44,7 @@ public class SectorRenderer : MonoBehaviour, IBeginDragHandler, IDragHandler, IS private float _sectorBackgroundDepth; private float _sectorCameraDepth; private UIDocument _zoneDetailsSurfaceDocument; + private AetheriaClientState _runtimeState; private readonly AetheriaEveUnitySurfaceChrome _zoneDetailsSurfaceChrome = new AetheriaEveUnitySurfaceChrome { RootAlignItems = Align.FlexEnd, @@ -61,6 +61,8 @@ public class SectorRenderer : MonoBehaviour, IBeginDragHandler, IDragHandler, IS private float2 _position = float2(0.5f); private float _viewSize = .5f; + + private AetheriaClientState RuntimeState => _runtimeState ??= AetheriaUnityRuntimeClientProvider.RuntimeState("unity-sector-renderer"); void Start() { @@ -86,32 +88,15 @@ public class SectorRenderer : MonoBehaviour, IBeginDragHandler, IDragHandler, IS private void RenderZoneDetailsSurface(int zoneIndex) { - var sectorZone = ResolveSectorZone(zoneIndex); - var zoneDetails = ResolveZoneDetails(zoneIndex); - var ownerFactionIndex = sectorZone?.OwnerFactionIndex ?? -1; - var otherFactions = (sectorZone?.FactionIndices ?? Array.Empty()) - .Where(index => index >= 0 && index != ownerFactionIndex) - .Distinct() - .Select(FormatFaction) - .ToArray(); - var zoneFacts = AetheriaRuntimeZoneDetailsSurfaceBuilder.Facts( - zoneDetails, - ResolveHullType); + var surface = ResolveZoneDetailsSurface(zoneIndex); + if (surface == null) + return; _zoneDetailsSurfaceDocument = AetheriaEveUnitySurfaceHost.RenderRuntime( transform, _zoneDetailsSurfaceDocument, "Aetheria Sector Zone Details Surface", - AetheriaRuntimeZoneDetailsSurfaceBuilder.Build( - ResolveZoneName(sectorZone, zoneDetails), - ownerFactionIndex >= 0 ? FormatFaction(ownerFactionIndex) : "None", - FormatValue((float)zoneFacts.Mass), - FormatValue((float)zoneFacts.Radius), - otherFactions, - zoneFacts.Bodies, - zoneFacts.Entities, - zoneFacts.HasContents, - DateTime.UtcNow.ToString("O")), + surface, HandleZoneDetailsSurfaceCommand, _zoneDetailsSurfaceChrome); } @@ -141,105 +126,22 @@ public class SectorRenderer : MonoBehaviour, IBeginDragHandler, IDragHandler, IS AetheriaEveUnitySurfaceHost.Hide(_zoneDetailsSurfaceDocument); } - private string ResolveZoneName( - AetheriaRuntimeSectorMapZone sectorZone, - AetheriaRuntimeZoneDetailsDocument zoneDetails) - { - if (!string.IsNullOrWhiteSpace(zoneDetails?.ZoneName)) - return zoneDetails.ZoneName; - - if (!string.IsNullOrWhiteSpace(sectorZone?.Name)) - return sectorZone.Name; - - return sectorZone == null ? "Unknown" : $"Zone {sectorZone.ZoneIndex}"; - } - - private AetheriaRuntimeSectorMapZone ResolveSectorZone(int zoneIndex) + private AetheriaRuntimeSurfaceDocument ResolveZoneDetailsSurface(int zoneIndex) { if (zoneIndex < 0) return null; try { - var sectorMap = AetheriaUnityRuntimeClientProvider.CurrentSectorMap("unity-sector-renderer"); - return (sectorMap?.Zones ?? Array.Empty()) - .FirstOrDefault(zone => zone.ZoneIndex == zoneIndex); + return RuntimeState.ZoneDetailsSurface(zoneIndex).Latest(); } catch (Exception ex) { - Debug.LogWarning($"Failed to read Aetheria sector map zone from local Verse state: {ex.Message}"); + Debug.LogWarning($"Failed to read Aetheria sector zone details surface from local Verse state: {ex.Message}"); return null; } } - private static string FormatFaction(int factionIndex) - { - return factionIndex < 0 ? "None" : $"Faction {factionIndex}"; - } - - private AetheriaRuntimeZoneDetailsDocument ResolveZoneDetails(int zoneIndex) - { - if (zoneIndex < 0) - return null; - - try - { - return AetheriaUnityRuntimeClientProvider.CurrentZoneDetails(zoneIndex, "unity-sector-renderer"); - } - catch (Exception ex) - { - Debug.LogWarning($"Failed to read Aetheria sector zone details from local Verse state: {ex.Message}"); - return null; - } - } - - private string ResolveHullType(string hullItemKey) - { - var typedHull = ResolveCatalog()?.FindItem(hullItemKey ?? ""); - return typedHull?.HullType ?? ""; - } - - private AetheriaRuntimeCatalogSnapshot ResolveCatalog() - { - try - { - return AetheriaUnityRuntimeClientProvider.CurrentCatalog("unity-sector-renderer"); - } - catch (Exception ex) - { - Debug.LogWarning($"Failed to read Aetheria sector catalog from local Verse state: {ex.Message}"); - return null; - } - } - - private AetheriaRuntimePlayerSettingsDocument ResolvePlayerSettings() - { - try - { - return AetheriaUnityRuntimeClientProvider.CurrentPlayerSettings("unity-sector-renderer"); - } - catch (Exception ex) - { - Debug.LogWarning($"Failed to read Aetheria sector player settings from local Verse state: {ex.Message}"); - return null; - } - } - - private string FormatValue(float value) - { - var digits = ResolvePlayerSettings()?.SignificantDigits ?? 3; - var magnitude = value == 0f ? 0 : (int)Math.Floor(Math.Log10(Math.Abs(value))) + 1; - digits -= magnitude; - if (digits < 0) - digits = 0; - - var formatted = value.ToString($"N{digits}"); - var decimalSeparator = Convert.ToChar(CultureInfo.CurrentCulture.NumberFormat.NumberDecimalSeparator); - return formatted.Contains(CultureInfo.CurrentCulture.NumberFormat.NumberDecimalSeparator) - ? formatted.TrimEnd('0').TrimEnd(decimalSeparator) - : formatted; - } - private void OnEnable() { _init = true; @@ -259,7 +161,7 @@ public class SectorRenderer : MonoBehaviour, IBeginDragHandler, IDragHandler, IS { try { - return AetheriaUnityRuntimeClientProvider.CurrentZoneState("unity-sector-renderer"); + return RuntimeState.CurrentZone.Latest(); } catch (Exception ex) { @@ -299,7 +201,7 @@ public class SectorRenderer : MonoBehaviour, IBeginDragHandler, IDragHandler, IS { _outputTexture.Release(); } - _outputTexture = new RenderTexture(_size.x, _size.y, 0, RenderTextureFormat.Default); + _outputTexture = new RenderTexture(_size.x, _size.y, 16, RenderTextureFormat.Default); SectorCamera.targetTexture = _outputTexture; _outputImage.material.SetTexture("_DetailTex", _outputTexture); } diff --git a/Assets/Scripts/UI/Menu/TradeMenu.cs b/Assets/Scripts/UI/Menu/TradeMenu.cs index 54ef3b222..ef28ce958 100644 --- a/Assets/Scripts/UI/Menu/TradeMenu.cs +++ b/Assets/Scripts/UI/Menu/TradeMenu.cs @@ -6,6 +6,7 @@ using System.Text.RegularExpressions; using GameCult.Aetheria.EveRuntime; using GameCult.Aetheria.State.Verse; using GameCult.Eve.Surface; +using GameCult.Mesh; using TMPro; using UnityEngine; using UnityEngine.UI; @@ -28,6 +29,7 @@ public class TradeMenu : MonoBehaviour private string _targetCargoEntityKey = ""; private int _targetCargoIndex = -1; private string _targetCargoLabel = "Docking Bay"; + private AetheriaClientState _runtimeState; private (ItemFilter filter, HardpointType type) _hardpointFilter; private (ItemFilter filter, SimpleCommodityCategory type) _commodityFilter; private (ItemFilter filter, CompoundCommodityCategory type) _compoundCommodityFilter; @@ -46,6 +48,8 @@ public class TradeMenu : MonoBehaviour private AetheriaRuntimeTradeFilterSurfaceModel _filterSurfaceModel; private Action[] _rowActionCallbacks = Array.Empty(); private AetheriaRuntimeTradeRowActionSurfaceModel _rowActionSurfaceModel; + + private AetheriaClientState RuntimeState => _runtimeState ??= AetheriaUnityRuntimeClientProvider.RuntimeState("unity-trade"); public EquippedCargoBay Inventory { get; set; } @@ -504,7 +508,7 @@ public class TradeMenu : MonoBehaviour { try { - return AetheriaUnityRuntimeClientProvider.CurrentStationRefit("unity-trade"); + return RuntimeState.StationRefit.Latest(); } catch (Exception ex) { @@ -518,7 +522,7 @@ public class TradeMenu : MonoBehaviour docking = null; try { - docking = AetheriaUnityRuntimeClientProvider.CurrentDockingState("unity-trade"); + docking = RuntimeState.CurrentDocking.Latest(); return docking != null; } catch (Exception ex) @@ -577,7 +581,7 @@ public class TradeMenu : MonoBehaviour { try { - return AetheriaUnityRuntimeClientProvider.CurrentCatalog("unity-trade"); + return RuntimeState.Catalog.Latest(); } catch (Exception ex) { @@ -590,7 +594,7 @@ public class TradeMenu : MonoBehaviour { try { - return AetheriaUnityRuntimeClientProvider.CurrentPlayerSettings("unity-trade"); + return RuntimeState.PlayerSettings.Latest(); } catch (Exception ex) { diff --git a/Assets/Scripts/Zone Display/VolumeCloudRenderer.cs b/Assets/Scripts/Zone Display/VolumeCloudRenderer.cs index 1fa54980e..4804aafef 100644 --- a/Assets/Scripts/Zone Display/VolumeCloudRenderer.cs +++ b/Assets/Scripts/Zone Display/VolumeCloudRenderer.cs @@ -3,8 +3,10 @@ using System.Linq; using System.Collections; using System.Collections.Generic; using GameCult.Aetheria.State.Verse; +using GameCult.Mesh; using UnityEngine; using UnityEngine.Rendering; +using UnityEngine.Rendering.RenderGraphModule; /// /// Based on github.com/yangrc1234/VolumeCloud @@ -33,11 +35,11 @@ public class HaltonSequence /// /// Cloud renderer post processing. /// -[ImageEffectAllowedInSceneView] [ExecuteInEditMode,RequireComponent(typeof(Camera))] -[ImageEffectOpaque] public class VolumeCloudRenderer : EffectBase { + private static readonly Dictionary RenderersByCamera = new Dictionary(); + [Header("Render Settings")] [Range(0, 2)] public int downSample = 1; @@ -50,16 +52,73 @@ public class VolumeCloudRenderer : EffectBase private Matrix4x4 prevV; private Camera mcam; private HaltonSequence sequence = new HaltonSequence() { radix = 3 }; + private const int TemporalHistoryVersion = 4; + private int appliedTemporalHistoryVersion = -1; // The index of 4x4 pixels. private int frameIndex = 0; private bool firstFrame = true; + private AetheriaClientState _runtimeState; + private int _debugRenderCount; + private int _debugLastRenderFrame = -1; [SerializeField] private Shader cloudShader; + [SerializeField] + private Texture2D ditherTexture; + + public static bool TryGetRenderer(Camera camera, out VolumeCloudRenderer renderer) + { + renderer = null; + return camera != null + && RenderersByCamera.TryGetValue(camera, out renderer) + && renderer != null + && renderer.isActiveAndEnabled; + } + + public int DebugRenderCount => _debugRenderCount; + public int DebugLastRenderFrame => _debugLastRenderFrame; + public RenderTexture DebugUndersampleBuffer => undersampleBuffer; + public RenderTexture DebugHistoryBuffer => fullBuffer != null && fullBuffer.Length > 0 ? fullBuffer[fullBufferIndex] : null; + public RenderTexture DebugCurrentCloudBuffer => fullBuffer != null && fullBuffer.Length > 1 ? fullBuffer[fullBufferIndex ^ 1] : null; + public bool DebugFirstFrame => firstFrame; + + private void OnEnable() + { + var camera = GetComponent(); + if (camera != null) + RenderersByCamera[camera] = this; + ResetTemporalHistory(); + } + + private void OnDisable() + { + var camera = GetComponent(); + if (camera != null && RenderersByCamera.TryGetValue(camera, out var renderer) && renderer == this) + RenderersByCamera.Remove(camera); + } + void EnsureMaterial(bool force = false) { + if (cloudShader == null) + cloudShader = Shader.Find("Aetheria/CloudShader"); + if (mat == null || force) { mat = new Material(cloudShader); + ResetTemporalHistory(); + } + } + + private void ResetTemporalHistory() + { + firstFrame = true; + appliedTemporalHistoryVersion = TemporalHistoryVersion; + if (fullBuffer != null) + { + for (var i = 0; i < fullBuffer.Length; i++) + { + if (fullBuffer[i] != null) + fullBuffer[i].DiscardContents(); + } } } @@ -94,8 +153,8 @@ public class VolumeCloudRenderer : EffectBase { try { - return AetheriaUnityRuntimeClientProvider - .CurrentPlayerSettings("unity-volume-cloud-renderer"); + _runtimeState ??= AetheriaUnityRuntimeClientProvider.RuntimeState("unity-volume-cloud-renderer"); + return _runtimeState.PlayerSettings.Latest(); } catch (Exception ex) { @@ -105,31 +164,73 @@ public class VolumeCloudRenderer : EffectBase return null; } - [ImageEffectOpaque] - private void OnRenderImage(RenderTexture source, RenderTexture destination) { - if (cloudShader == null) { - Graphics.Blit(source, destination); - return; - } - + public void RenderClouds(UnsafeCommandBuffer cmd, Camera camera, TextureHandle colorTarget, TextureHandle depthTarget) + { mcam = GetComponent(); + if (camera != mcam || cloudShader == null) + return; + + _debugRenderCount++; + _debugLastRenderFrame = Time.frameCount; + var width = mcam.pixelWidth >> downSample; var height = mcam.pixelHeight >> downSample; + if (width <= 0 || height <= 0) + return; - this.EnsureMaterial(); + EnsureMaterial(); + EnsureDitherTexture(); + + if (appliedTemporalHistoryVersion != TemporalHistoryVersion) + ResetTemporalHistory(); EnsureArray(ref fullBuffer, 2); firstFrame |= EnsureRenderTarget(ref fullBuffer[0], width, height, RenderTextureFormat.ARGBFloat, FilterMode.Bilinear); firstFrame |= EnsureRenderTarget(ref fullBuffer[1], width, height, RenderTextureFormat.ARGBFloat, FilterMode.Bilinear); - firstFrame |= EnsureRenderTarget(ref undersampleBuffer, width , height, RenderTextureFormat.ARGBFloat, FilterMode.Bilinear); + firstFrame |= EnsureRenderTarget(ref undersampleBuffer, width, height, RenderTextureFormat.ARGBFloat, FilterMode.Bilinear); - frameIndex = (frameIndex + 1)% 16; + frameIndex = (frameIndex + 1) % 16; fullBufferIndex = (fullBufferIndex + 1) % 2; - /* Some code is from playdead TAA. */ + ApplyQualityKeywords(); - //1. Pass1, Render a undersampled buffer. The buffer is dithered using blue noise and halton sequence. - //If it's first frame, force a high quality sample to make the initial history buffer good enough. + var gpuProjection = GL.GetGPUProjectionMatrix(mcam.projectionMatrix, true); + mat.SetMatrix("_CamInvProj", (gpuProjection * mcam.worldToCameraMatrix).inverse); + mat.SetVector("_ProjectionExtents", mcam.GetProjectionExtents()); + mat.SetFloat("_RaymarchOffset", sequence.Get()); + mat.SetVector("_TexelSize", undersampleBuffer.texelSize); + mat.SetFloat("_ResetHistory", firstFrame ? 1.0f : 0.0f); + + mat.SetTexture("_UndersampleCloudTex", undersampleBuffer); + mat.SetMatrix("_PrevVP", gpuProjection * prevV); + + cmd.SetRenderTarget(undersampleBuffer); + cmd.SetViewport(new Rect(0, 0, width, height)); + cmd.ClearRenderTarget(false, true, Color.clear); + cmd.DrawProcedural(Matrix4x4.identity, mat, 0, MeshTopology.Triangles, 3, 1); + + mat.SetTexture("_MainTex", firstFrame ? undersampleBuffer : fullBuffer[fullBufferIndex]); + + cmd.SetRenderTarget(fullBuffer[fullBufferIndex ^ 1]); + cmd.SetViewport(new Rect(0, 0, width, height)); + cmd.ClearRenderTarget(false, true, Color.clear); + cmd.DrawProcedural(Matrix4x4.identity, mat, 1, MeshTopology.Triangles, 3, 1); + + mat.SetTexture("_CloudTex", fullBuffer[fullBufferIndex ^ 1]); + + if (depthTarget.IsValid()) + cmd.SetRenderTarget(colorTarget, depthTarget); + else + cmd.SetRenderTarget(colorTarget); + cmd.SetViewport(mcam.pixelRect); + cmd.DrawProcedural(Matrix4x4.identity, mat, 2, MeshTopology.Triangles, 3, 1); + + prevV = mcam.worldToCameraMatrix; + firstFrame = false; + } + + private void ApplyQualityKeywords() + { if (firstFrame || quality == Quality.Ultra) { mat.EnableKeyword("ULTRA_QUALITY"); mat.DisableKeyword("HIGH_QUALITY"); @@ -151,30 +252,22 @@ public class VolumeCloudRenderer : EffectBase mat.DisableKeyword("MEDIUM_QUALITY"); mat.EnableKeyword("LOW_QUALITY"); } - - mat.SetMatrix("_CamInvProj", (Camera.current.projectionMatrix * Camera.current.worldToCameraMatrix).inverse); - mat.SetVector("_ProjectionExtents", mcam.GetProjectionExtents()); - mat.SetFloat("_RaymarchOffset", sequence.Get()); - mat.SetVector("_TexelSize", undersampleBuffer.texelSize); - - Graphics.Blit(null, undersampleBuffer, mat, 0); - - //2. Pass 2, blend undersampled image with history buffer to new buffer. - mat.SetTexture("_UndersampleCloudTex", undersampleBuffer); - mat.SetMatrix("_PrevVP", GL.GetGPUProjectionMatrix(mcam.projectionMatrix,false) * prevV); - mat.SetVector("_ProjectionExtents", mcam.GetProjectionExtents()); - - if (firstFrame) { //Wait, is this the first frame? If it is, the history buffer is empty yet. It will cause glitch if we use it directly. Fill it using the undersample buffer. - Graphics.Blit(undersampleBuffer, fullBuffer[fullBufferIndex]); - } - Graphics.Blit(fullBuffer[fullBufferIndex], fullBuffer[fullBufferIndex ^ 1], mat, 1); - - //3. Pass3, Calculate lighting, blend final cloud image with final image. - mat.SetTexture("_CloudTex", fullBuffer[fullBufferIndex ^ 1]); - Graphics.Blit(source, destination, mat, 2); - - //4. Cleanup - prevV = mcam.worldToCameraMatrix; - firstFrame = false; } + + private void EnsureDitherTexture() + { + if (ditherTexture == null) + ditherTexture = Resources.Load("LDR_LLL1_0"); + + if (ditherTexture == null) + return; + + Shader.SetGlobalTexture("_DitheringTex", ditherTexture); + Shader.SetGlobalVector("_DitheringCoords", new Vector4( + (float)Math.Max(1, Screen.width) / ditherTexture.width, + (float)Math.Max(1, Screen.height) / ditherTexture.height, + 0, + 0)); + } + } diff --git a/Assets/Scripts/Zone Display/VolumeSampling.cs b/Assets/Scripts/Zone Display/VolumeSampling.cs index 6f39324b5..a0f8021e5 100644 --- a/Assets/Scripts/Zone Display/VolumeSampling.cs +++ b/Assets/Scripts/Zone Display/VolumeSampling.cs @@ -11,6 +11,7 @@ using UnityEngine.Rendering; using UnityEngine.Serialization; using static Unity.Mathematics.math; using static Unity.Mathematics.noise; +using System.Collections.Generic; /// /// Drives the volume render. @@ -19,6 +20,7 @@ using static Unity.Mathematics.noise; [RequireComponent( typeof( Camera ) )] public class VolumeSampling : MonoBehaviour { + private static readonly List ActiveSamplers = new List(); public Transform GridMesh; public Camera GridCamera; public GameSettings Settings; @@ -29,16 +31,58 @@ public class VolumeSampling : MonoBehaviour public RenderTexture NebulaTint; public AetheriaUnityRenderSplatViewportSource SplatViewportSource; public AetheriaRenderSplatLayerRenderer SplatLayerRenderer; + public AetheriaSceneRenderSplatSource SceneSplatSource; + + [SerializeField] + private bool useRuntimeRenderSplats; + + [SerializeField] + private bool renderSplatsEveryFrame; + + [SerializeField] + private float splatRenderIntervalSeconds = 0.25f; private MeshRenderer _gridMeshRenderer; private float _flowScroll; private Transform _gridTransform; + private Camera _ownerCamera; + private Vector2 _lastSplatCenter; + private Vector2 _lastSplatSize; + private Vector2 _publishedSplatCenter; + private Vector2 _publishedSplatSize; + private float _nextSplatRenderTime; + private bool _hasRenderedSplats; private GlobalKeyword _keywordFlowGlobal; private GlobalKeyword _keywordFlowSlope; private GlobalKeyword _keywordNoiseSlope; + public static void RenderForCamera(Camera camera) + { + if (camera == null) + return; + + for (var i = 0; i < ActiveSamplers.Count; i++) + { + var sampler = ActiveSamplers[i]; + if (sampler != null && sampler.TargetsCamera(camera)) + sampler.RenderSamplingPass(); + } + } + + private void OnEnable() + { + if (!ActiveSamplers.Contains(this)) + ActiveSamplers.Add(this); + } + + private void OnDisable() + { + ActiveSamplers.Remove(this); + } + private void Start() { + _ownerCamera = GetComponent(); _keywordFlowGlobal = GlobalKeyword.Create("FLOW_GLOBAL"); _keywordFlowSlope = GlobalKeyword.Create("FLOW_SLOPE"); _keywordNoiseSlope = GlobalKeyword.Create("NOISE_SLOPE"); @@ -53,12 +97,28 @@ public class VolumeSampling : MonoBehaviour { if(_gridTransform==null) _gridTransform = GridCamera?.transform; - // Shader needs to know the position and scale of cameras used to render input textures - if(GridCamera != null) - Shader.SetGlobalVector("_GridTransform", new Vector4(_gridTransform.position.x,_gridTransform.position.z,GridCamera.orthographicSize*2)); - RenderSplatTextures(); + _flowScroll += Settings.DefaultEnvironment.Flow.GlobalScrollSpeed * Time.deltaTime; + UpdateGridMeshBounds(); + } + private bool TargetsCamera(Camera camera) + { + _ownerCamera ??= GetComponent(); + return camera == _ownerCamera; + } + + private void RenderSamplingPass() + { + if (_gridTransform == null) + _gridTransform = GridCamera?.transform; + + RenderSplatTexturesIfNeeded(); + PublishSamplingGlobals(); + } + + private void UpdateGridMeshBounds() + { var environment = Settings.DefaultEnvironment; if (GridMesh != null) @@ -70,7 +130,19 @@ public class VolumeSampling : MonoBehaviour // GridMesh.position = new Vector3(_gridTransform.position.x, environment.Grid.Offset, _gridTransform.position.z); // GridMesh.localScale = new Vector3(GridCamera.orthographicSize, 1, GridCamera.orthographicSize); } - + } + + private void PublishSamplingGlobals() + { + var environment = Settings.DefaultEnvironment; + + ResolveSplatViewport(out var center, out var size); + _publishedSplatCenter = center; + _publishedSplatSize = size; + + // Shader sampling must use the same world-space viewport that produced the splat textures. + Shader.SetGlobalVector("_GridTransform", new Vector4(center.x, center.y, size.x)); + // Volumetric sampling parameters are used by several shaders Shader.SetGlobalTexture("_NebulaSurfaceHeight", NebulaSurfaceHeight); Shader.SetGlobalTexture("_NebulaPatchHeight", NebulaPatchHeight); @@ -105,7 +177,6 @@ public class VolumeSampling : MonoBehaviour Shader.SetGlobalFloat("_FlowAmplitude", environment.Flow.GlobalAmplitude); Shader.SetGlobalFloat("_FlowSlopeAmplitude", environment.Flow.SlopeAmplitude); Shader.SetGlobalFloat("_FlowSwirlAmplitude", environment.Flow.SwirlAmplitude); - _flowScroll += environment.Flow.GlobalScrollSpeed * Time.deltaTime; Shader.SetGlobalFloat("_FlowScroll", _flowScroll); Shader.SetGlobalFloat("_FlowPeriod", environment.Flow.Period); @@ -114,20 +185,33 @@ public class VolumeSampling : MonoBehaviour Shader.SetKeyword(_keywordNoiseSlope, environment.Noise.SlopeExponent != 0); } - private void RenderSplatTextures() + private void RenderSplatTexturesIfNeeded() { ResolveSplatRenderers(); - if (SplatViewportSource == null) + if (SplatLayerRenderer == null) return; - var size = GridCamera != null - ? new Vector2(GridCamera.orthographicSize * 2, GridCamera.orthographicSize * 2) - : new Vector2(1024, 1024); - var center = _gridTransform != null - ? new Vector2(_gridTransform.position.x, _gridTransform.position.z) - : Vector2.zero; - SplatViewportSource.SetViewport(center, size); - SplatViewportSource.RenderLatest(); + ResolveSplatViewport(out var center, out var size); + var document = ResolveSceneSplatDocument(center, size); + if (document == null && useRuntimeRenderSplats) + document = ResolveRuntimeSplatDocument(center, size); + if (document == null) + return; + + var viewportChanged = !_hasRenderedSplats || + !Approximately(center, _lastSplatCenter) || + !Approximately(size, _lastSplatSize); + var intervalElapsed = Time.unscaledTime >= _nextSplatRenderTime; + if (!renderSplatsEveryFrame && !viewportChanged && !intervalElapsed) + return; + + SplatLayerRenderer.Render(document, Mathf.RoundToInt(size.x), Mathf.RoundToInt(size.y)); + _lastSplatCenter = center; + _lastSplatSize = size; + _publishedSplatCenter = center; + _publishedSplatSize = size; + _nextSplatRenderTime = Time.unscaledTime + Mathf.Max(0.02f, splatRenderIntervalSeconds); + _hasRenderedSplats = true; if (SplatLayerRenderer == null) return; @@ -138,6 +222,52 @@ public class VolumeSampling : MonoBehaviour NebulaTint = ResolveLayerTexture(AetheriaRuntimeRenderSplatLayerKeys.FogTint, NebulaTint); } + private AetheriaRuntimeRenderSplatsViewportDocument ResolveSceneSplatDocument(Vector2 center, Vector2 size) + { + if (SceneSplatSource == null) + SceneSplatSource = GetComponent(); + return SceneSplatSource != null + ? SceneSplatSource.BuildDocument(BuildViewportBounds(center, size)) + : null; + } + + private AetheriaRuntimeRenderSplatsViewportDocument ResolveRuntimeSplatDocument(Vector2 center, Vector2 size) + { + if (SplatViewportSource == null) + return null; + + SplatViewportSource.SetViewport(center, size); + return SplatViewportSource.CurrentDocument; + } + + private void ResolveSplatViewport(out Vector2 center, out Vector2 size) + { + size = GridCamera != null + ? new Vector2(GridCamera.orthographicSize * 2, GridCamera.orthographicSize * 2) + : new Vector2(1024, 1024); + center = _gridTransform != null + ? new Vector2(_gridTransform.position.x, _gridTransform.position.z) + : Vector2.zero; + } + + private static AetheriaRuntimeRtsViewportBounds BuildViewportBounds(Vector2 center, Vector2 size) + { + var halfSize = size * 0.5f; + return new AetheriaRuntimeRtsViewportBounds + { + MinX = center.x - halfSize.x, + MinY = center.y - halfSize.y, + MaxX = center.x + halfSize.x, + MaxY = center.y + halfSize.y + }; + } + + private static bool Approximately(Vector2 lhs, Vector2 rhs) + { + return Mathf.Abs(lhs.x - rhs.x) < 0.001f && + Mathf.Abs(lhs.y - rhs.y) < 0.001f; + } + private RenderTexture ResolveLayerTexture(string layerKey, RenderTexture fallback) { return SplatLayerRenderer.TryGetTexture(layerKey, out var texture) && texture != null @@ -152,9 +282,13 @@ public class VolumeSampling : MonoBehaviour if (SplatLayerRenderer == null) SplatLayerRenderer = gameObject.AddComponent(); + if (!useRuntimeRenderSplats) + return; + if (SplatViewportSource == null) SplatViewportSource = GetComponent(); if (SplatViewportSource == null) SplatViewportSource = gameObject.AddComponent(); + SplatViewportSource.RenderInLateUpdate = false; } } diff --git a/Assets/Scripts/Zone Display/ZoneRenderer.cs b/Assets/Scripts/Zone Display/ZoneRenderer.cs index af449a8ed..be66b7a56 100644 --- a/Assets/Scripts/Zone Display/ZoneRenderer.cs +++ b/Assets/Scripts/Zone Display/ZoneRenderer.cs @@ -8,6 +8,7 @@ using System.Collections.Generic; using System.Linq; using Cinemachine; using GameCult.Aetheria.State.Verse; +using GameCult.Mesh; using UnityEngine; using UnityEngine.Serialization; using Object = UnityEngine.Object; @@ -29,6 +30,7 @@ public class ZoneRenderer : MonoBehaviour public CinemachineVirtualCamera SceneCamera; public Camera[] FogCameras; public Camera[] MinimapCameras; + private AetheriaClientState _runtimeState; public bool UseRenderSplatTextureLayers = true; public Material FogMaterial; public float FogFarFadeFraction = .125f; @@ -1199,8 +1201,8 @@ public class ZoneRenderer : MonoBehaviour { try { - return AetheriaUnityRuntimeClientProvider - .CurrentCatalog("unity-zone-renderer"); + _runtimeState ??= AetheriaUnityRuntimeClientProvider.RuntimeState("unity-zone-renderer"); + return _runtimeState.Catalog.Latest(); } catch (Exception ex) { diff --git a/Assets/Settings/Rendering/AetheriaMainMenuVolumeProfile.asset b/Assets/Settings/Rendering/AetheriaMainMenuVolumeProfile.asset new file mode 100644 index 000000000..32cf799b5 --- /dev/null +++ b/Assets/Settings/Rendering/AetheriaMainMenuVolumeProfile.asset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e8918e11e65658b7b0ab214a9501418e716cf59031366d879643946269705add +size 5024 diff --git a/Assets/Settings/Rendering/AetheriaMainMenuVolumeProfile.asset.meta b/Assets/Settings/Rendering/AetheriaMainMenuVolumeProfile.asset.meta new file mode 100644 index 000000000..1c6f814b1 --- /dev/null +++ b/Assets/Settings/Rendering/AetheriaMainMenuVolumeProfile.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 621d661451ce43259c2e712812fc2a5c +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Settings/Rendering/AetheriaUniversalRenderer.asset b/Assets/Settings/Rendering/AetheriaUniversalRenderer.asset index 4870b2512..5229892ec 100644 --- a/Assets/Settings/Rendering/AetheriaUniversalRenderer.asset +++ b/Assets/Settings/Rendering/AetheriaUniversalRenderer.asset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c2c5bb55c31c6c1db5b7f94033ee6e6763c8c307a3a37fa49ca2534aecff285c -size 1914 +oid sha256:fe00a44460a9814352bfbe05d761b50312d15ae1044982d105ca0897b2fd1ac6 +size 3988 diff --git a/Assets/Settings/Rendering/CustomPostProcessData.asset b/Assets/Settings/Rendering/CustomPostProcessData.asset new file mode 100644 index 000000000..40582a23c --- /dev/null +++ b/Assets/Settings/Rendering/CustomPostProcessData.asset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:567ea2b38ac84dd8dd2dc1e9640dd62bf60dfccb5be9a73972c84575ffb3e244 +size 5346 diff --git a/Assets/Settings/Rendering/CustomPostProcessData.asset.meta b/Assets/Settings/Rendering/CustomPostProcessData.asset.meta new file mode 100644 index 000000000..5e90b8f35 --- /dev/null +++ b/Assets/Settings/Rendering/CustomPostProcessData.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 5496e61d0bd534e4aae81b86f7e4d406 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Shaders/Brushes/PowerBrush.shader b/Assets/Shaders/Brushes/PowerBrush.shader index b332348d2..af9865c0e 100644 --- a/Assets/Shaders/Brushes/PowerBrush.shader +++ b/Assets/Shaders/Brushes/PowerBrush.shader @@ -1,68 +1,83 @@ -Shader "Brushes/Power Brush" { -Properties { - _Color ("Color", Color) = (1,1,1,1) - _Depth ("Depth", Float) = 0.5 - _Power("Power", Float) = 2 - _Cutoff("Cutoff", Range(0,1)) = 1 -} - -Category { - Tags { "Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent" "PreviewType"="Plane" } - Blend One One - ColorMask RGB - Cull Off Lighting Off ZWrite Off - - SubShader { - Pass { - - CGPROGRAM - #pragma vertex vert - #pragma fragment frag - #pragma target 2.0 - - #include "UnityCG.cginc" - - fixed4 _Color; - float _Depth; - half _Power; - half _Cutoff; - - struct appdata_t { - float4 vertex : POSITION; - fixed4 color : COLOR; - float2 texcoord : TEXCOORD0; - }; - - struct v2f { - float4 vertex : SV_POSITION; - fixed4 color : COLOR; - float2 texcoord : TEXCOORD0; - }; - - float4 _MainTex_ST; - - v2f vert (appdata_t v) - { - v2f o; - o.vertex = UnityObjectToClipPos(v.vertex); - o.color = v.color; - o.texcoord = v.texcoord; - return o; - } - - float powerPulse( float x, float power ) - { - x = saturate(abs(x))-.001; - return pow((x + 1.0f) * (1.0f - x), power); - } - - fixed4 frag (v2f i) : SV_Target - { - float dist = length(i.texcoord-float2(.5,.5))*2; - return _Depth * min(_Cutoff, powerPulse(dist,_Power)) * _Color * smoothstep(1, .95, dist); - } - ENDCG - } - } -} +Shader "Brushes/Power Brush" +{ + Properties + { + _Color ("Color", Color) = (1,1,1,1) + _Depth ("Depth", Float) = 0.5 + _Power ("Power", Float) = 2 + _Cutoff ("Cutoff", Range(0,1)) = 1 + } + + SubShader + { + Tags + { + "Queue" = "Transparent" + "IgnoreProjector" = "True" + "RenderType" = "Transparent" + "PreviewType" = "Plane" + "RenderPipeline" = "UniversalPipeline" + } + + Pass + { + Name "PowerBrush" + Tags { "LightMode" = "UniversalForward" } + + Blend One One + ColorMask RGB + Cull Off + ZWrite Off + + HLSLPROGRAM + #pragma vertex Vert + #pragma fragment Frag + #pragma target 2.0 + + #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl" + + CBUFFER_START(UnityPerMaterial) + half4 _Color; + float _Depth; + half _Power; + half _Cutoff; + CBUFFER_END + + struct Attributes + { + float4 positionOS : POSITION; + half4 color : COLOR; + float2 uv : TEXCOORD0; + }; + + struct Varyings + { + float4 positionCS : SV_POSITION; + half4 color : COLOR; + float2 uv : TEXCOORD0; + }; + + Varyings Vert(Attributes input) + { + Varyings output; + output.positionCS = TransformObjectToHClip(input.positionOS.xyz); + output.color = input.color; + output.uv = input.uv; + return output; + } + + float PowerPulse(float x, float power) + { + x = saturate(abs(x)) - 0.001; + return pow((x + 1.0) * (1.0 - x), power); + } + + half4 Frag(Varyings input) : SV_Target + { + float dist = length(input.uv - float2(0.5, 0.5)) * 2.0; + return _Depth * min(_Cutoff, PowerPulse(dist, _Power)) * _Color * smoothstep(1.0, 0.95, dist); + } + ENDHLSL + } + } } diff --git a/Assets/Shaders/Compute/Stardust/Stardust.cs b/Assets/Shaders/Compute/Stardust/Stardust.cs index d75b18f99..e70737cc2 100644 --- a/Assets/Shaders/Compute/Stardust/Stardust.cs +++ b/Assets/Shaders/Compute/Stardust/Stardust.cs @@ -3,10 +3,13 @@ using System.Collections; using System.Collections.Generic; using System.Linq; using UnityEngine.Rendering; +using UnityEngine.Rendering.RenderGraphModule; public class Stardust : MonoBehaviour { + private static readonly List Instances = new List(); + #region variables //public bool debugLog = false; //public float proxyPersistTime = 2; @@ -57,13 +60,52 @@ public class Stardust : MonoBehaviour private ComputeBuffer _quadPoints; private const int QUAD_STRIDE = 12; + private bool _buffersReady; #endregion + public static void RenderForCamera(UnsafeCommandBuffer cmd, Camera camera, TextureHandle colorTarget, TextureHandle depthTarget) + { + for (var i = 0; i < Instances.Count; i++) + { + var stardust = Instances[i]; + if (stardust == null || !stardust.isActiveAndEnabled || stardust.TargetCamera != camera) + continue; + + stardust.RenderStardust(cmd, colorTarget, depthTarget); + } + } + #region setup + private void OnEnable() + { + if (!Instances.Contains(this)) + Instances.Add(this); + } + + private void OnDisable() + { + Instances.Remove(this); + ReleaseBuffers(); + } + // Use this for initialization void Start() { + EnsureBuffers(); + } + + private void EnsureBuffers() + { + if (_buffersReady && (_particlesBuffer == null || _quadPoints == null)) + _buffersReady = false; + + if (_buffersReady) + return; + + if (ParticleCalculation == null || ParticleMaterial == null) + return; + //Find compute kernel _updateParticlesKernel = ParticleCalculation.FindKernel("UpdateParticles"); @@ -92,6 +134,8 @@ public class Stardust : MonoBehaviour new Vector3(-.5f, -.5f), new Vector3(-.5f, .5f) }); + + _buffersReady = true; } #endregion @@ -99,6 +143,18 @@ public class Stardust : MonoBehaviour // Update is called once per frame void Update() { + EnsureBuffers(); + if (!_buffersReady || GravityCamera == null) + return; + + if (_particlesBuffer == null) + { + _buffersReady = false; + EnsureBuffers(); + if (!_buffersReady) + return; + } + //Bind resources to compute shader ParticleCalculation.SetBuffer(_updateParticlesKernel, "particles", _particlesBuffer); @@ -121,10 +177,24 @@ public class Stardust : MonoBehaviour //Dispatch, launch threads on GPU int numberOfGroups = Mathf.CeilToInt((float)Span * Span / GROUP_SIZE); ParticleCalculation.Dispatch(_updateParticlesKernel, numberOfGroups, 1, 1); - + + } + + private void RenderStardust(UnsafeCommandBuffer cmd, TextureHandle colorTarget, TextureHandle depthTarget) + { + EnsureBuffers(); + if (!_buffersReady || ParticleMaterial == null || _particlesBuffer == null || _quadPoints == null) + return; + ParticleMaterial.SetBuffer("particles", _particlesBuffer); ParticleMaterial.SetBuffer("quadPoints", _quadPoints); - Graphics.DrawProcedural(ParticleMaterial, new Bounds(pos, new Vector3(Spacing * Span, 2048, Spacing * Span)), MeshTopology.Triangles, 6, Span * Span, TargetCamera, null, ShadowCastingMode.Off, false, 0); + + if (depthTarget.IsValid()) + cmd.SetRenderTarget(colorTarget, depthTarget); + else + cmd.SetRenderTarget(colorTarget); + + cmd.DrawProcedural(Matrix4x4.identity, ParticleMaterial, 0, MeshTopology.Triangles, 6, Span * Span); } #endregion @@ -151,14 +221,16 @@ public class Stardust : MonoBehaviour #region cleanup void OnDestroy() { -// m_emitterProxies.Release(); -// _temperature.Release(); - if(_particlesBuffer!=null) - { - _particlesBuffer.Release(); - _quadPoints.Release(); - } -// m_livingProxyIndices.Release(); + ReleaseBuffers(); + } + + private void ReleaseBuffers() + { + _particlesBuffer?.Release(); + _quadPoints?.Release(); + _particlesBuffer = null; + _quadPoints = null; + _buffersReady = false; } #endregion } diff --git a/Assets/Shaders/Compute/Stardust/Stardust.shader b/Assets/Shaders/Compute/Stardust/Stardust.shader index 86acf8c43..9a51fc368 100644 --- a/Assets/Shaders/Compute/Stardust/Stardust.shader +++ b/Assets/Shaders/Compute/Stardust/Stardust.shader @@ -1,169 +1,101 @@ -Shader "Aetheria/Stardust (Compute)" +Shader "Aetheria/Stardust (Compute)" { - Properties { - [HDR] _TintColor("Tint Color", Color) = (0.5,0.5,0.5,0.5) - _DistanceSizeExponent("Distance Size Exponent", Float) = 1 - _DistanceIntensityExponent("Distance Intensity Exponent", Float) = 1 - _EmissionGain("Emission Gain", Range(0, 1)) = 0.3 - _Power("Power", Float) = 2 - } + Properties + { + [HDR] _TintColor ("Tint Color", Color) = (0.5,0.5,0.5,0.5) + _DistanceSizeExponent ("Distance Size Exponent", Float) = 1 + _DistanceIntensityExponent ("Distance Intensity Exponent", Float) = 1 + _EmissionGain ("Emission Gain", Range(0, 1)) = 0.3 + _Power ("Power", Float) = 2 + _AlphaClip ("Alpha Clip", Range(0, 1)) = 0.05 + } - SubShader { - Tags { "Queue"="AlphaTest" "RenderType"="TransparentCutout" "IgnoreProjector"="True" } - Pass { - - CGPROGRAM - #pragma vertex particle_vertex - #pragma fragment frag - - #pragma target 5.0 - #include "UnityCG.cginc" - #include "Assets/Shaders/Dither Functions.cginc" - - struct Particle - { - float3 position; - float3 color; - float size; - }; - - StructuredBuffer particles; - StructuredBuffer quadPoints; - - float4 _TintColor; - float _EmissionGain; - float _Power; - float _DistanceSizeExponent; - float _DistanceIntensityExponent; - - struct v2f - { - float4 pos : SV_POSITION; - float2 uv : TEXCOORD0; - float4 screenPos : TEXCOORD1; - float4 color : COLOR; - }; - - //Vertex shader with no inputs - //Uses the system values SV_VertexID and SV_InstanceID to read from compute buffers - v2f particle_vertex(uint id : SV_VertexID, uint inst : SV_InstanceID) - { - v2f o; - - //Only transform world pos by view matrix - //To Create a billboarding effect - float3 worldPosition = particles[inst].position; - float dist = length(worldPosition - _WorldSpaceCameraPos); - - float4 cameraSpacePosition = mul(UNITY_MATRIX_V, float4(worldPosition, 1.0f)); - //cameraSpacePosition.w /= dist; - - float3 quadPoint = float3(quadPoints[id].xy, 0.0f) * particles[inst].size * pow(dist, _DistanceSizeExponent) / pow(100, _DistanceSizeExponent); - o.pos = mul(UNITY_MATRIX_P, cameraSpacePosition + float4(quadPoint, 0.0f)); - - //Shift coordinates for uvs - o.uv = quadPoints[id] + 0.5f; - - //transfer color of particle and global tint to vertex - o.color = float4(particles[inst].color * _TintColor.rgb, 100 * pow(100, _DistanceIntensityExponent) / pow(dist, _DistanceIntensityExponent)); - o.screenPos = ComputeScreenPos(o.pos); - - return o; - } - - float powerPulse( float x, float power ) - { - x = saturate(abs(x)); - return pow((x + 1.0f) * (1.0f - x), power); - } - - float4 frag(v2f i) : COLOR - { - float emission = powerPulse(length(i.uv-float2(.5,.5))*2,_Power); - float2 screenUV = i.screenPos.xy / i.screenPos.w; - ditherClip(screenUV, emission * i.color.a); - return i.color * emission * (exp(_EmissionGain * 5.0f)); - } - ENDCG - } - Pass + SubShader + { + Tags { - Name "ShadowCaster" - Tags { "LightMode" = "ShadowCaster" } - - CGPROGRAM - #pragma vertex particle_vertex - #pragma fragment frag - + "Queue" = "Transparent" + "RenderType" = "Transparent" + "IgnoreProjector" = "True" + "RenderPipeline" = "UniversalPipeline" + } + + Pass + { + Name "Stardust" + Tags { "LightMode" = "UniversalForward" } + + Blend One One + Cull Off + ZWrite Off + + HLSLPROGRAM + #pragma vertex Vert + #pragma fragment Frag #pragma target 5.0 - #include "UnityCG.cginc" - #include "Assets/Shaders/Dither Functions.cginc" - + + #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl" + struct Particle { float3 position; float3 color; float size; }; - + StructuredBuffer particles; StructuredBuffer quadPoints; - - float4 _TintColor; - float _EmissionGain; - float _Power; - float _DistanceSizeExponent; - float _DistanceIntensityExponent; - - struct v2f + + CBUFFER_START(UnityPerMaterial) + float4 _TintColor; + float _EmissionGain; + float _Power; + float _DistanceSizeExponent; + float _DistanceIntensityExponent; + float _AlphaClip; + CBUFFER_END + + struct Varyings { - float4 pos : SV_POSITION; + float4 positionCS : SV_POSITION; float2 uv : TEXCOORD0; - float4 screenPos : TEXCOORD1; float4 color : COLOR; }; - - //Vertex shader with no inputs - //Uses the system values SV_VertexID and SV_InstanceID to read from compute buffers - v2f particle_vertex(uint id : SV_VertexID, uint inst : SV_InstanceID) + + Varyings Vert(uint vertexId : SV_VertexID, uint instanceId : SV_InstanceID) { - v2f o; - - //Only transform world pos by view matrix - //To Create a billboarding effect - float3 worldPosition = particles[inst].position; - float dist = length(worldPosition - _WorldSpaceCameraPos); - - float4 cameraSpacePosition = mul(UNITY_MATRIX_V, float4(worldPosition, 1.0f)); - //cameraSpacePosition.w /= dist; - - float3 quadPoint = float3(quadPoints[id].xy, 0.0f) * particles[inst].size * pow(dist, _DistanceSizeExponent) / pow(100, _DistanceSizeExponent); - o.pos = mul(UNITY_MATRIX_P, cameraSpacePosition + float4(quadPoint, 0.0f)); - - //Shift coordinates for uvs - o.uv = quadPoints[id] + 0.5f; - - //transfer color of particle and global tint to vertex - o.color = float4(particles[inst].color * _TintColor.rgb, 100 * pow(100, _DistanceIntensityExponent) / pow(dist, _DistanceIntensityExponent)); - o.screenPos = ComputeScreenPos(o.pos); - - return o; + Varyings output; + + float3 worldPosition = particles[instanceId].position; + float dist = max(length(worldPosition - _WorldSpaceCameraPos.xyz), 0.001); + float4 viewPosition = mul(UNITY_MATRIX_V, float4(worldPosition, 1.0)); + float3 quadPoint = float3(quadPoints[vertexId].xy, 0.0) + * particles[instanceId].size + * pow(dist, _DistanceSizeExponent) + / pow(100.0, _DistanceSizeExponent); + + output.positionCS = mul(UNITY_MATRIX_P, viewPosition + float4(quadPoint, 0.0)); + output.uv = quadPoints[vertexId].xy + 0.5; + output.color = float4( + particles[instanceId].color * _TintColor.rgb, + 100.0 * pow(100.0, _DistanceIntensityExponent) / pow(dist, _DistanceIntensityExponent)); + + return output; } - - float powerPulse( float x, float power ) - { - x = saturate(abs(x)); - return pow((x + 1.0f) * (1.0f - x), power); - } - - float4 frag(v2f i) : COLOR + + float PowerPulse(float x, float power) { - float emission = powerPulse(length(i.uv-float2(.5,.5))*2,_Power); - float2 screenUV = i.screenPos.xy / i.screenPos.w; - ditherClip(screenUV, emission * i.color.a); - SHADOW_CASTER_FRAGMENT(i) + x = saturate(abs(x)); + return pow((x + 1.0) * (1.0 - x), power); } - ENDCG + + float4 Frag(Varyings input) : SV_Target + { + float emission = PowerPulse(length(input.uv - float2(0.5, 0.5)) * 2.0, _Power); + clip(emission * input.color.a - _AlphaClip); + return float4(input.color.rgb * emission * exp(_EmissionGain * 5.0), 1.0); + } + ENDHLSL } - } -} \ No newline at end of file + } +} diff --git a/Assets/Shaders/CustomDoF/AetheriaFogBlur.cs b/Assets/Shaders/CustomDoF/AetheriaFogBlur.cs new file mode 100644 index 000000000..ec2b8f8a2 --- /dev/null +++ b/Assets/Shaders/CustomDoF/AetheriaFogBlur.cs @@ -0,0 +1,34 @@ +using System; +using UnityEngine; +using UnityEngine.Rendering; +using UnityEngine.Rendering.Universal; + +namespace Aetheria.Rendering.PostProcessing +{ + [Serializable, VolumeComponentMenu("Aetheria/Fog Blur")] + [SupportedOnRenderPipeline(typeof(UniversalRenderPipelineAsset))] + public sealed class AetheriaFogBlur : VolumeComponent, IPostProcessComponent + { + [Tooltip("Texture whose red channel defines the fog blur mask.")] + public TextureParameter blurMask = new TextureParameter(null, TextureDimension.Tex2D); + + [Min(0.1f), Tooltip("Distance to the point of focus.")] + public MinFloatParameter focusDistance = new MinFloatParameter(10f, 0.1f); + + [Tooltip("Ratio of aperture. Smaller values produce a shallower blur.")] + public ClampedFloatParameter aperture = new ClampedFloatParameter(5.6f, 0.05f, 32f); + + [Tooltip("Distance between lens and film. Larger values produce a shallower blur.")] + public ClampedFloatParameter focalLength = new ClampedFloatParameter(50f, 1f, 300f); + + [Tooltip("Maximum fog blur radius.")] + public ClampedIntParameter kernelSize = new ClampedIntParameter((int)AetheriaFogBlurKernelSize.Medium, 0, 3); + + public bool IsActive() + { + return active && SystemInfo.graphicsShaderLevel >= 35; + } + + public bool IsTileCompatible() => false; + } +} diff --git a/Assets/Shaders/CustomDoF/AetheriaFogBlur.cs.meta b/Assets/Shaders/CustomDoF/AetheriaFogBlur.cs.meta new file mode 100644 index 000000000..38264b170 --- /dev/null +++ b/Assets/Shaders/CustomDoF/AetheriaFogBlur.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 3c8aa33374e94c7cb0f35af09e54c70c +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Shaders/CustomDoF/AetheriaFogBlurRendererFeature.cs b/Assets/Shaders/CustomDoF/AetheriaFogBlurRendererFeature.cs new file mode 100644 index 000000000..c6072892d --- /dev/null +++ b/Assets/Shaders/CustomDoF/AetheriaFogBlurRendererFeature.cs @@ -0,0 +1,279 @@ +using System; +using UnityEngine; +using UnityEngine.Experimental.Rendering; +using UnityEngine.Rendering; +using UnityEngine.Rendering.RenderGraphModule; +using UnityEngine.Rendering.Universal; + +namespace Aetheria.Rendering.PostProcessing +{ + public sealed class AetheriaFogBlurRendererFeature : ScriptableRendererFeature + { + [SerializeField] + private Shader shader; + + private FogBlurPass _pass; + + public override void Create() + { + if (shader == null) + shader = Shader.Find("Aetheria/PostProcessing/FogBlur"); + + _pass = new FogBlurPass(shader) + { + renderPassEvent = RenderPassEvent.BeforeRenderingPostProcessing + }; + _pass.ConfigureInput(ScriptableRenderPassInput.Depth); + } + + protected override void Dispose(bool disposing) + { + _pass?.Dispose(); + _pass = null; + } + + public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData) + { + if (_pass == null) + Create(); + + renderer.EnqueuePass(_pass); + } + + private sealed class FogBlurPass : ScriptableRenderPass, IDisposable + { + private enum Pass + { + CoCCalculation, + CoCTemporalFilter, + DownsampleAndPrefilter, + BokehSmallKernel, + BokehMediumKernel, + BokehLargeKernel, + BokehVeryLargeKernel, + PostFilter, + Combine + } + + private sealed class PassData + { + internal Material Material; + internal Texture BlurMask; + internal bool BlurMaskIsPackedCloud; + internal TextureHandle Source; + internal TextureHandle Destination; + internal TextureHandle CoC; + internal TextureHandle DoF; + internal TextureHandle Temp; + internal float Distance; + internal float LensCoeff; + internal float MaxCoC; + internal float RcpMaxCoC; + internal float RcpAspect; + internal int KernelPass; + internal int Width; + internal int Height; + internal int HalfWidth; + internal int HalfHeight; + } + + private const float FilmHeight = 0.024f; + + private readonly ProfilingSampler _profilingSampler = new ProfilingSampler("Aetheria Fog Blur"); + private Material _material; + + public FogBlurPass(Shader shader) + { + requiresIntermediateTexture = true; + + if (shader != null && shader.isSupported) + _material = CoreUtils.CreateEngineMaterial(shader); + } + + public void Dispose() + { + CoreUtils.Destroy(_material); + _material = null; + } + + public override void RecordRenderGraph(RenderGraph renderGraph, ContextContainer frameData) + { + if (_material == null) + return; + + var fogBlur = VolumeManager.instance.stack.GetComponent(); + if (fogBlur == null || !fogBlur.IsActive()) + return; + + var resourceData = frameData.Get(); + var cameraData = frameData.Get(); + if (resourceData.isActiveTargetBackBuffer) + return; + + var source = resourceData.activeColorTexture; + if (!source.IsValid()) + return; + + var blurMask = fogBlur.blurMask.value; + var blurMaskIsPackedCloud = false; + if (blurMask == null && + VolumeCloudRenderer.TryGetRenderer(cameraData.camera, out var cloudRenderer)) + { + blurMask = cloudRenderer.DebugCurrentCloudBuffer; + blurMaskIsPackedCloud = blurMask != null; + } + + if (blurMask == null) + return; + + var sourceDesc = source.GetDescriptor(renderGraph); + var width = sourceDesc.width; + var height = sourceDesc.height; + if (width <= 0 || height <= 0) + return; + + var halfWidth = Mathf.Max(1, width / 2); + var halfHeight = Mathf.Max(1, height / 2); + + var destinationDesc = MakeColorDescriptor(sourceDesc, width, height, sourceDesc.colorFormat, "_AetheriaFogBlurColor"); + var cocDesc = MakeColorDescriptor(sourceDesc, width, height, GraphicsFormat.R8_UNorm, "_AetheriaFogBlurCoC"); + var dofDesc = MakeColorDescriptor(sourceDesc, halfWidth, halfHeight, sourceDesc.colorFormat, "_AetheriaFogBlurDoF"); + var tempDesc = MakeColorDescriptor(sourceDesc, halfWidth, halfHeight, sourceDesc.colorFormat, "_AetheriaFogBlurTemp"); + + var destination = renderGraph.CreateTexture(destinationDesc); + var coc = renderGraph.CreateTexture(cocDesc); + var dof = renderGraph.CreateTexture(dofDesc); + var temp = renderGraph.CreateTexture(tempDesc); + + using (var builder = renderGraph.AddUnsafePass("Aetheria Fog Blur", out var passData, _profilingSampler)) + { + var scaledFilmHeight = FilmHeight * (height / 1080f); + var focalLengthMeters = fogBlur.focalLength.value / 1000f; + var focusDistance = Mathf.Max(fogBlur.focusDistance.value, focalLengthMeters); + var aspect = width / (float)height; + var coeff = focalLengthMeters * focalLengthMeters / + (fogBlur.aperture.value * (focusDistance - focalLengthMeters) * scaledFilmHeight * 2f); + var maxCoC = CalculateMaxCoCRadius((AetheriaFogBlurKernelSize)fogBlur.kernelSize.value, height); + + passData.Material = _material; + passData.BlurMask = blurMask; + passData.BlurMaskIsPackedCloud = blurMaskIsPackedCloud; + passData.Source = source; + passData.Destination = destination; + passData.CoC = coc; + passData.DoF = dof; + passData.Temp = temp; + passData.Distance = focusDistance; + passData.LensCoeff = coeff; + passData.MaxCoC = maxCoC; + passData.RcpMaxCoC = 1f / maxCoC; + passData.RcpAspect = 1f / aspect; + passData.KernelPass = (int)Pass.BokehSmallKernel + fogBlur.kernelSize.value; + passData.Width = width; + passData.Height = height; + passData.HalfWidth = halfWidth; + passData.HalfHeight = halfHeight; + + builder.UseTexture(source, AccessFlags.Read); + builder.UseTexture(destination, AccessFlags.Write); + builder.UseTexture(coc, AccessFlags.ReadWrite); + builder.UseTexture(dof, AccessFlags.ReadWrite); + builder.UseTexture(temp, AccessFlags.ReadWrite); + builder.SetRenderFunc(static (PassData data, UnsafeGraphContext context) => + { + Execute(data, CommandBufferHelpers.GetNativeCommandBuffer(context.cmd)); + }); + } + + resourceData.cameraColor = destination; + } + + private static TextureDesc MakeColorDescriptor( + TextureDesc source, + int width, + int height, + GraphicsFormat format, + string name) + { + return new TextureDesc(source) + { + width = width, + height = height, + format = format, + name = name, + clearBuffer = true, + clearColor = Color.clear, + depthBufferBits = DepthBits.None, + msaaSamples = MSAASamples.None, + useMipMap = false, + autoGenerateMips = false, + anisoLevel = 0, + filterMode = FilterMode.Bilinear, + discardBuffer = false + }; + } + + private static float CalculateMaxCoCRadius(AetheriaFogBlurKernelSize kernelSize, int screenHeight) + { + var radiusInPixels = (float)kernelSize * 4f + 6f; + return Mathf.Min(0.05f, radiusInPixels / screenHeight); + } + + private static void Execute(PassData data, CommandBuffer cmd) + { + var material = data.Material; + material.SetFloat(ShaderIds.Distance, data.Distance); + material.SetFloat(ShaderIds.LensCoeff, data.LensCoeff); + material.SetFloat(ShaderIds.MaxCoC, data.MaxCoC); + material.SetFloat(ShaderIds.RcpMaxCoC, data.RcpMaxCoC); + material.SetFloat(ShaderIds.RcpAspect, data.RcpAspect); + material.SetTexture(ShaderIds.DoFBlurTex, data.BlurMask); + material.SetFloat(ShaderIds.DoFBlurTexPacked, data.BlurMaskIsPackedCloud ? 1f : 0f); + + SetMainTex(cmd, data.Source, data.Width, data.Height); + Blitter.BlitCameraTexture(cmd, data.Source, data.CoC, material, (int)Pass.CoCCalculation); + + SetMainTex(cmd, data.Source, data.Width, data.Height); + material.SetTexture(ShaderIds.CoCTex, data.CoC); + Blitter.BlitCameraTexture(cmd, data.Source, data.DoF, material, (int)Pass.DownsampleAndPrefilter); + + SetMainTex(cmd, data.DoF, data.HalfWidth, data.HalfHeight); + Blitter.BlitCameraTexture(cmd, data.DoF, data.Temp, material, data.KernelPass); + + SetMainTex(cmd, data.Temp, data.HalfWidth, data.HalfHeight); + Blitter.BlitCameraTexture(cmd, data.Temp, data.DoF, material, (int)Pass.PostFilter); + + SetMainTex(cmd, data.Source, data.Width, data.Height); + material.SetTexture(ShaderIds.DepthOfFieldTex, data.DoF); + material.SetVector(ShaderIds.DepthOfFieldTexTexelSize, TexelSize(data.HalfWidth, data.HalfHeight)); + material.SetTexture(ShaderIds.CoCTex, data.CoC); + Blitter.BlitCameraTexture(cmd, data.Source, data.Destination, material, (int)Pass.Combine); + } + + private static void SetMainTex(CommandBuffer cmd, TextureHandle texture, int width, int height) + { + cmd.SetGlobalVector(ShaderIds.MainTexTexelSize, TexelSize(width, height)); + } + + private static Vector4 TexelSize(int width, int height) + { + return new Vector4(1f / width, 1f / height, width, height); + } + + private static class ShaderIds + { + internal static readonly int MainTexTexelSize = Shader.PropertyToID("_MainTex_TexelSize"); + internal static readonly int DoFBlurTex = Shader.PropertyToID("_DoFBlurTex"); + internal static readonly int DoFBlurTexPacked = Shader.PropertyToID("_DoFBlurTexPacked"); + internal static readonly int DepthOfFieldTex = Shader.PropertyToID("_DepthOfFieldTex"); + internal static readonly int DepthOfFieldTexTexelSize = Shader.PropertyToID("_DepthOfFieldTex_TexelSize"); + internal static readonly int Distance = Shader.PropertyToID("_Distance"); + internal static readonly int LensCoeff = Shader.PropertyToID("_LensCoeff"); + internal static readonly int MaxCoC = Shader.PropertyToID("_MaxCoC"); + internal static readonly int RcpMaxCoC = Shader.PropertyToID("_RcpMaxCoC"); + internal static readonly int RcpAspect = Shader.PropertyToID("_RcpAspect"); + internal static readonly int CoCTex = Shader.PropertyToID("_CoCTex"); + } + } + } +} diff --git a/Assets/Shaders/CustomDoF/AetheriaFogBlurRendererFeature.cs.meta b/Assets/Shaders/CustomDoF/AetheriaFogBlurRendererFeature.cs.meta new file mode 100644 index 000000000..0d71f27ed --- /dev/null +++ b/Assets/Shaders/CustomDoF/AetheriaFogBlurRendererFeature.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 4820e39ec20f45218b98a21714063e6a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Shaders/CustomDoF/AetheriaFogBlurUrp.hlsl b/Assets/Shaders/CustomDoF/AetheriaFogBlurUrp.hlsl new file mode 100644 index 000000000..68ff83de5 --- /dev/null +++ b/Assets/Shaders/CustomDoF/AetheriaFogBlurUrp.hlsl @@ -0,0 +1,146 @@ +#ifndef AETHERIA_FOG_BLUR_URP_INCLUDED +#define AETHERIA_FOG_BLUR_URP_INCLUDED + +#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl" +#include "Packages/com.unity.render-pipelines.core/ShaderLibrary/DynamicScalingClamping.hlsl" +#include "Packages/com.unity.render-pipelines.universal/Shaders/PostProcessing/Common.hlsl" +#include "DiskKernels.hlsl" +#include "Assets/Shaders/PackFloat.cginc" + +TEXTURE2D(_DoFBlurTex); +float _DoFBlurTexPacked; + +TEXTURE2D_X(_CoCTex); +TEXTURE2D_X(_DepthOfFieldTex); + +float4 _MainTex_TexelSize; +float4 _DepthOfFieldTex_TexelSize; +float _Distance; +float _LensCoeff; +float _MaxCoC; +float _RcpAspect; + +half4 FragCoC(Varyings input) : SV_Target +{ + UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(input); + float2 uv = UnityStereoTransformScreenSpaceTex(input.texcoord); + + half4 blur = SAMPLE_TEXTURE2D(_DoFBlurTex, sampler_LinearClamp, uv); + if (_DoFBlurTexPacked > 0.5) + { + float distance; + float density; + unpack(blur.a, distance, density); + + float farDepth = max(distance - _Distance, 0.0); + float farCoC = saturate(farDepth * max(_LensCoeff, 0.0025)); + float fogGate = smoothstep(0.02, 0.25, density); + return 0.5 + farCoC * fogGate * 0.5; + } + + return saturate(blur.r); +} + +half4 FragTempFilter(Varyings input) : SV_Target +{ + UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(input); + return SAMPLE_TEXTURE2D_X(_BlitTexture, sampler_LinearClamp, UnityStereoTransformScreenSpaceTex(input.texcoord)); +} + +half4 FragPrefilter(Varyings input) : SV_Target +{ + UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(input); + float2 uv = UnityStereoTransformScreenSpaceTex(input.texcoord); + float3 duv = _MainTex_TexelSize.xyx * float3(0.5, 0.5, -0.5); + + float2 uv0 = uv - duv.xy; + float2 uv1 = uv - duv.zy; + float2 uv2 = uv + duv.zy; + float2 uv3 = uv + duv.xy; + + half3 c0 = SAMPLE_TEXTURE2D_X(_BlitTexture, sampler_LinearClamp, ClampUVForBilinear(uv0, _MainTex_TexelSize.xy)).rgb; + half3 c1 = SAMPLE_TEXTURE2D_X(_BlitTexture, sampler_LinearClamp, ClampUVForBilinear(uv1, _MainTex_TexelSize.xy)).rgb; + half3 c2 = SAMPLE_TEXTURE2D_X(_BlitTexture, sampler_LinearClamp, ClampUVForBilinear(uv2, _MainTex_TexelSize.xy)).rgb; + half3 c3 = SAMPLE_TEXTURE2D_X(_BlitTexture, sampler_LinearClamp, ClampUVForBilinear(uv3, _MainTex_TexelSize.xy)).rgb; + + half coc0 = SAMPLE_TEXTURE2D_X(_CoCTex, sampler_LinearClamp, ClampUVForBilinear(uv0, _MainTex_TexelSize.xy)).r * 2.0 - 1.0; + half coc1 = SAMPLE_TEXTURE2D_X(_CoCTex, sampler_LinearClamp, ClampUVForBilinear(uv1, _MainTex_TexelSize.xy)).r * 2.0 - 1.0; + half coc2 = SAMPLE_TEXTURE2D_X(_CoCTex, sampler_LinearClamp, ClampUVForBilinear(uv2, _MainTex_TexelSize.xy)).r * 2.0 - 1.0; + half coc3 = SAMPLE_TEXTURE2D_X(_CoCTex, sampler_LinearClamp, ClampUVForBilinear(uv3, _MainTex_TexelSize.xy)).r * 2.0 - 1.0; + + half3 avg = (c0 + c1 + c2 + c3) * 0.25; + half cocMin = min(coc0, Min3(coc1, coc2, coc3)); + half cocMax = max(coc0, Max3(coc1, coc2, coc3)); + half coc = (-cocMin > cocMax ? cocMin : cocMax) * _MaxCoC; + + avg *= smoothstep(0.0, _MainTex_TexelSize.y * 2.0, abs(coc)); + return half4(avg, coc); +} + +half4 FragBlur(Varyings input) : SV_Target +{ + UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(input); + float2 uv = UnityStereoTransformScreenSpaceTex(input.texcoord); + half4 center = SAMPLE_TEXTURE2D_X(_BlitTexture, sampler_LinearClamp, ClampUVForBilinear(uv, _MainTex_TexelSize.xy)); + + half4 acc = 0.0; + UNITY_LOOP + for (int si = 0; si < kSampleCount; si++) + { + float2 disp = kDiskKernel[si] * _MaxCoC; + float dist = length(disp); + float2 sampleUv = uv + float2(disp.x * _RcpAspect, disp.y); + half4 sampleColor = SAMPLE_TEXTURE2D_X(_BlitTexture, sampler_LinearClamp, ClampUVForBilinear(sampleUv, _MainTex_TexelSize.xy)); + + half farCoC = max(min(center.a, sampleColor.a), 0.0); + half nearCoC = max(-sampleColor.a, 0.0); + half margin = _MainTex_TexelSize.y * 2.0; + half weight = max( + saturate((farCoC - dist + margin) / margin), + saturate((nearCoC - dist + margin) / margin)); + + acc += half4(sampleColor.rgb, 1.0) * weight; + } + + acc.rgb /= max(acc.a, 1e-4); + acc.a = saturate(acc.a / max((half)kSampleCount, 1.0h)); + return acc; +} + +half4 FragPostBlur(Varyings input) : SV_Target +{ + UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(input); + float2 uv = UnityStereoTransformScreenSpaceTex(input.texcoord); + float4 duv = _MainTex_TexelSize.xyxy * float4(0.5, 0.5, -0.5, 0.0); + + half4 acc = 0.0; + acc += SAMPLE_TEXTURE2D_X(_BlitTexture, sampler_LinearClamp, ClampUVForBilinear(uv - duv.xy, _MainTex_TexelSize.xy)); + acc += SAMPLE_TEXTURE2D_X(_BlitTexture, sampler_LinearClamp, ClampUVForBilinear(uv - duv.zy, _MainTex_TexelSize.xy)); + acc += SAMPLE_TEXTURE2D_X(_BlitTexture, sampler_LinearClamp, ClampUVForBilinear(uv + duv.zy, _MainTex_TexelSize.xy)); + acc += SAMPLE_TEXTURE2D_X(_BlitTexture, sampler_LinearClamp, ClampUVForBilinear(uv + duv.xy, _MainTex_TexelSize.xy)); + return acc * 0.25; +} + +half4 FragCombine(Varyings input) : SV_Target +{ + UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(input); + float2 uv = UnityStereoTransformScreenSpaceTex(input.texcoord); + + half4 color = SAMPLE_TEXTURE2D_X(_BlitTexture, sampler_LinearClamp, ClampUVForBilinear(uv, _MainTex_TexelSize.xy)); + half4 dof = SAMPLE_TEXTURE2D_X(_DepthOfFieldTex, sampler_LinearClamp, ClampUVForBilinear(uv, _DepthOfFieldTex_TexelSize.xy)); + half coc = SAMPLE_TEXTURE2D_X(_CoCTex, sampler_LinearClamp, ClampUVForBilinear(uv, _MainTex_TexelSize.xy)).r; + coc = (coc - 0.5) * 2.0 * _MaxCoC; + + half farAlpha = smoothstep(_MainTex_TexelSize.y * 2.0, _MainTex_TexelSize.y * 4.0, coc); + return lerp(color, half4(dof.rgb, color.a), farAlpha); +} + +half4 FragDebugOverlay(Varyings input) : SV_Target +{ + UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(input); + float2 uv = UnityStereoTransformScreenSpaceTex(input.texcoord); + half coc = SAMPLE_TEXTURE2D_X(_CoCTex, sampler_LinearClamp, ClampUVForBilinear(uv, _MainTex_TexelSize.xy)).r; + return half4(coc, coc, coc, 1.0); +} + +#endif diff --git a/Assets/Shaders/CustomDoF/AetheriaFogBlurUrp.hlsl.meta b/Assets/Shaders/CustomDoF/AetheriaFogBlurUrp.hlsl.meta new file mode 100644 index 000000000..55bfddfbe --- /dev/null +++ b/Assets/Shaders/CustomDoF/AetheriaFogBlurUrp.hlsl.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 825f9bd701ee6b94498ac02d28fc8a3b +ShaderIncludeImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Shaders/CustomDoF/DepthOfField.cs b/Assets/Shaders/CustomDoF/DepthOfField.cs index 5905fba5a..a01ee12bf 100644 --- a/Assets/Shaders/CustomDoF/DepthOfField.cs +++ b/Assets/Shaders/CustomDoF/DepthOfField.cs @@ -1,271 +1,12 @@ using System; -using UnityEngine; -using UnityEngine.Rendering; -using UnityEngine.Rendering.PostProcessing; namespace Aetheria.Rendering.PostProcessing { - /// - /// Convolution kernel size for the Aetheria fog blur effect. - /// public enum AetheriaFogBlurKernelSize { - /// - /// Small filter. - /// Small, - - /// - /// Medium filter. - /// Medium, - - /// - /// Large filter. - /// Large, - - /// - /// Very large filter. - /// VeryLarge } - - /// - /// A volume parameter holding a value. - /// - [Serializable] - public sealed class AetheriaFogBlurKernelSizeParameter : ParameterOverride {} - - /// - /// This class holds settings for Aetheria's custom fog blur effect. - /// - [Serializable] - [PostProcess(typeof(AetheriaFogBlurRenderer), PostProcessEvent.BeforeStack, "Aetheria/Fog Blur", false)] - public sealed class AetheriaFogBlur : PostProcessEffectSettings - { - /// - /// The distance to the point of focus. - /// - [UnityEngine.Rendering.PostProcessing.Min(0.1f), Tooltip("Distance to the point of focus.")] - public UnityEngine.Rendering.PostProcessing.FloatParameter focusDistance = new UnityEngine.Rendering.PostProcessing.FloatParameter { value = 10f }; - - /// - /// The ratio of the aperture (known as f-stop or f-number). The smaller the value is, the - /// shallower the depth of field is. - /// - [Range(0.05f, 32f), Tooltip("Ratio of aperture (known as f-stop or f-number). The smaller the value is, the shallower the depth of field is.")] - public UnityEngine.Rendering.PostProcessing.FloatParameter aperture = new UnityEngine.Rendering.PostProcessing.FloatParameter { value = 5.6f }; - - /// - /// The distance between the lens and the film. The larger the value is, the shallower the - /// depth of field is. - /// - [Range(1f, 300f), Tooltip("Distance between the lens and the film. The larger the value is, the shallower the depth of field is.")] - public UnityEngine.Rendering.PostProcessing.FloatParameter focalLength = new UnityEngine.Rendering.PostProcessing.FloatParameter { value = 50f }; - - /// - /// The convolution kernel size of the bokeh filter, which determines the maximum radius of - /// bokeh. It also affects the performance (the larger the kernel is, the longer the GPU - /// time is required). - /// - [DisplayName("Max Blur Size"), Tooltip("Convolution kernel size of the bokeh filter, which determines the maximum radius of bokeh. It also affects performances (the larger the kernel is, the longer the GPU time is required).")] - public AetheriaFogBlurKernelSizeParameter kernelSize = new AetheriaFogBlurKernelSizeParameter { value = AetheriaFogBlurKernelSize.Medium }; - - /// - /// Returns true if the effect is currently enabled and supported. - /// - /// The current post-processing render context - /// true if the effect is currently enabled and supported - public override bool IsEnabledAndSupported(PostProcessRenderContext context) - { - return enabled.value - && SystemInfo.graphicsShaderLevel >= 35; - } - } - - [UnityEngine.Scripting.Preserve] - // TODO: Doesn't play nice with alpha propagation, see if it can be fixed without killing performances - internal sealed class AetheriaFogBlurRenderer : PostProcessEffectRenderer - { - enum Pass - { - CoCCalculation, - CoCTemporalFilter, - DownsampleAndPrefilter, - BokehSmallKernel, - BokehMediumKernel, - BokehLargeKernel, - BokehVeryLargeKernel, - PostFilter, - Combine, - DebugOverlay - } - - // Ping-pong between two history textures as we can't read & write the same target in the - // same pass - const int k_NumEyes = 2; - const int k_NumCoCHistoryTextures = 2; - readonly RenderTexture[][] m_CoCHistoryTextures = new RenderTexture[k_NumEyes][]; - int[] m_HistoryPingPong = new int[k_NumEyes]; - - // Height of the 35mm full-frame format (36mm x 24mm) - // TODO: Should be set by a physical camera - const float k_FilmHeight = 0.024f; - - public AetheriaFogBlurRenderer() - { - for (int eye = 0; eye < k_NumEyes; eye++) - { - m_CoCHistoryTextures[eye] = new RenderTexture[k_NumCoCHistoryTextures]; - m_HistoryPingPong[eye] = 0; - } - } - - public override DepthTextureMode GetCameraFlags() - { - return DepthTextureMode.Depth; - } - - RenderTextureFormat SelectFormat(RenderTextureFormat primary, RenderTextureFormat secondary) - { - if (SystemInfo.SupportsRenderTextureFormat(primary)) - return primary; - - if (SystemInfo.SupportsRenderTextureFormat(secondary)) - return secondary; - - return RenderTextureFormat.Default; - } - - float CalculateMaxCoCRadius(int screenHeight) - { - // Estimate the allowable maximum radius of CoC from the kernel - // size (the equation below was empirically derived). - float radiusInPixels = (float)settings.kernelSize.value * 4f + 6f; - - // Applying a 5% limit to the CoC radius to keep the size of - // TileMax/NeighborMax small enough. - return Mathf.Min(0.05f, radiusInPixels / screenHeight); - } - - RenderTexture CheckHistory(int eye, int id, PostProcessRenderContext context, RenderTextureFormat format) - { - var rt = m_CoCHistoryTextures[eye][id]; - - if (m_ResetHistory || rt == null || !rt.IsCreated() || rt.width != context.width || rt.height != context.height) - { - RenderTexture.ReleaseTemporary(rt); - - rt = context.GetScreenSpaceTemporaryRT(0, format, RenderTextureReadWrite.Linear); - rt.name = "CoC History, Eye: " + eye + ", ID: " + id; - rt.filterMode = FilterMode.Bilinear; - rt.Create(); - m_CoCHistoryTextures[eye][id] = rt; - } - - return rt; - } - - public override void Render(PostProcessRenderContext context) - { - // The coc is stored in alpha so we need a 4 channels target. Note that using ARGB32 - // will result in a very weak near-blur. - var colorFormat = context.camera.allowHDR ? RenderTextureFormat.ARGBHalf : RenderTextureFormat.ARGB32; - var cocFormat = SelectFormat(RenderTextureFormat.R8, RenderTextureFormat.RHalf); - - // Material setup - float scaledFilmHeight = k_FilmHeight * (context.height / 1080f); - var f = settings.focalLength.value / 1000f; - var s1 = Mathf.Max(settings.focusDistance.value, f); - var aspect = (float)context.screenWidth / (float)context.screenHeight; - var coeff = f * f / (settings.aperture.value * (s1 - f) * scaledFilmHeight * 2f); - var maxCoC = CalculateMaxCoCRadius(context.screenHeight); - - var sheet = context.propertySheets.Get(Shader.Find("Aetheria/PostProcessing/FogBlur")); - sheet.properties.Clear(); - sheet.properties.SetFloat(Distance, s1); - sheet.properties.SetFloat(LensCoeff, coeff); - sheet.properties.SetFloat(MaxCoC, maxCoC); - sheet.properties.SetFloat(RcpMaxCoC, 1f / maxCoC); - sheet.properties.SetFloat(RcpAspect, 1f / aspect); - - var cmd = context.command; - cmd.BeginSample("DepthOfField"); - - // CoC calculation pass - context.GetScreenSpaceTemporaryRT(cmd, CoCTex, 0, cocFormat, RenderTextureReadWrite.Linear); - cmd.BlitFullscreenTriangle(BuiltinRenderTextureType.None, CoCTex, sheet, (int)Pass.CoCCalculation); - - // CoC temporal filter pass when TAA is enabled - if (context.IsTemporalAntialiasingActive()) - { - float motionBlending = context.temporalAntialiasing.motionBlending; - float blend = m_ResetHistory ? 0f : motionBlending; // Handles first frame blending - var jitter = context.temporalAntialiasing.jitter; - - sheet.properties.SetVector(TaaParams, new Vector3(jitter.x, jitter.y, blend)); - - int pp = m_HistoryPingPong[context.xrActiveEye]; - var historyRead = CheckHistory(context.xrActiveEye, ++pp % 2, context, cocFormat); - var historyWrite = CheckHistory(context.xrActiveEye, ++pp % 2, context, cocFormat); - m_HistoryPingPong[context.xrActiveEye] = ++pp % 2; - - cmd.BlitFullscreenTriangle(historyRead, historyWrite, sheet, (int)Pass.CoCTemporalFilter); - cmd.ReleaseTemporaryRT(CoCTex); - cmd.SetGlobalTexture(CoCTex, historyWrite); - } - - // Downsampling and prefiltering pass - context.GetScreenSpaceTemporaryRT(cmd, DepthOfFieldTex, 0, colorFormat, RenderTextureReadWrite.Default, FilterMode.Bilinear, context.width / 2, context.height / 2); - cmd.BlitFullscreenTriangle(context.source, DepthOfFieldTex, sheet, (int)Pass.DownsampleAndPrefilter); - - // Bokeh simulation pass - context.GetScreenSpaceTemporaryRT(cmd, DepthOfFieldTemp, 0, colorFormat, RenderTextureReadWrite.Default, FilterMode.Bilinear, context.width / 2, context.height / 2); - cmd.BlitFullscreenTriangle(DepthOfFieldTex, DepthOfFieldTemp, sheet, (int)Pass.BokehSmallKernel + (int)settings.kernelSize.value); - - // Postfilter pass - cmd.BlitFullscreenTriangle(DepthOfFieldTemp, DepthOfFieldTex, sheet, (int)Pass.PostFilter); - cmd.ReleaseTemporaryRT(DepthOfFieldTemp); - - // Debug overlay pass - if (context.IsDebugOverlayEnabled(UnityEngine.Rendering.PostProcessing.DebugOverlay.DepthOfField)) - context.PushDebugOverlay(cmd, context.source, sheet, (int)Pass.DebugOverlay); - - // Combine pass - cmd.BlitFullscreenTriangle(context.source, context.destination, sheet, (int)Pass.Combine); - cmd.ReleaseTemporaryRT(DepthOfFieldTex); - - if (!context.IsTemporalAntialiasingActive()) - cmd.ReleaseTemporaryRT(CoCTex); - - cmd.EndSample("DepthOfField"); - - m_ResetHistory = false; - } - - public override void Release() - { - for (int eye = 0; eye < k_NumEyes; eye++) - { - for (int i = 0; i < m_CoCHistoryTextures[eye].Length; i++) - { - RenderTexture.ReleaseTemporary(m_CoCHistoryTextures[eye][i]); - m_CoCHistoryTextures[eye][i] = null; - } - m_HistoryPingPong[eye] = 0; - } - - ResetHistory(); - } - static readonly int DepthOfFieldTemp = Shader.PropertyToID("_DepthOfFieldTemp"); - static readonly int DepthOfFieldTex = Shader.PropertyToID("_DepthOfFieldTex"); - static readonly int Distance = Shader.PropertyToID("_Distance"); - static readonly int LensCoeff = Shader.PropertyToID("_LensCoeff"); - static readonly int MaxCoC = Shader.PropertyToID("_MaxCoC"); - static readonly int RcpMaxCoC = Shader.PropertyToID("_RcpMaxCoC"); - static readonly int RcpAspect = Shader.PropertyToID("_RcpAspect"); - static readonly int CoCTex = Shader.PropertyToID("_CoCTex"); - static readonly int TaaParams = Shader.PropertyToID("_TaaParams"); - } } diff --git a/Assets/Shaders/CustomDoF/DepthOfField.hlsl b/Assets/Shaders/CustomDoF/DepthOfField.hlsl index 40b2a8f3d..fa55722d9 100644 --- a/Assets/Shaders/CustomDoF/DepthOfField.hlsl +++ b/Assets/Shaders/CustomDoF/DepthOfField.hlsl @@ -4,11 +4,13 @@ #include "StdLib.hlsl" #include "Colors.hlsl" #include "DiskKernels.hlsl" +#include "Assets/Shaders/PackFloat.cginc" -TEXTURE2D_SAMPLER2D(_MainTex, sampler_MainTex); float4 _MainTex_TexelSize; +TEXTURE2D_SAMPLER2D(_BlitTexture, sampler_BlitTexture); TEXTURE2D_SAMPLER2D(_DoFBlurTex, sampler_DoFBlurTex); +float _DoFBlurTexPacked; TEXTURE2D_SAMPLER2D(_CameraDepthTexture, sampler_CameraDepthTexture); TEXTURE2D_SAMPLER2D(_CameraMotionVectorsTexture, sampler_CameraMotionVectorsTexture); @@ -28,7 +30,16 @@ half3 _TaaParams; // Jitter.x, Jitter.y, Blending // CoC calculation half4 FragCoC(VaryingsDefault i) : SV_Target { - return SAMPLE_TEXTURE2D(_DoFBlurTex, sampler_DoFBlurTex, i.texcoordStereo).r; + half4 blur = SAMPLE_TEXTURE2D(_DoFBlurTex, sampler_DoFBlurTex, i.texcoordStereo); + if (_DoFBlurTexPacked > 0.5) + { + float distance; + float density; + unpack(blur.a, distance, density); + return saturate(density); + } + + return blur.r; } // Temporal filter @@ -66,7 +77,7 @@ half4 FragTempFilter(VaryingsDefault i) : SV_Target // Sample the history buffer with the motion vector at the closest point float2 motion = SAMPLE_TEXTURE2D(_CameraMotionVectorsTexture, sampler_CameraMotionVectorsTexture, UnityStereoTransformScreenSpaceTex(i.texcoord + closest.xy)).xy; - half cocHis = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, UnityStereoTransformScreenSpaceTex(i.texcoord - motion)).r; + half cocHis = SAMPLE_TEXTURE2D(_BlitTexture, sampler_BlitTexture, UnityStereoTransformScreenSpaceTex(i.texcoord - motion)).r; // Neighborhood clamping half cocMin = closest.z; @@ -83,9 +94,9 @@ half4 FragPrefilter(VaryingsDefault i) : SV_Target #if UNITY_GATHER_SUPPORTED // Sample source colors - half4 c_r = GATHER_RED_TEXTURE2D(_MainTex, sampler_MainTex, i.texcoordStereo); - half4 c_g = GATHER_GREEN_TEXTURE2D(_MainTex, sampler_MainTex, i.texcoordStereo); - half4 c_b = GATHER_BLUE_TEXTURE2D(_MainTex, sampler_MainTex, i.texcoordStereo); + half4 c_r = GATHER_RED_TEXTURE2D(_BlitTexture, sampler_BlitTexture, i.texcoordStereo); + half4 c_g = GATHER_GREEN_TEXTURE2D(_BlitTexture, sampler_BlitTexture, i.texcoordStereo); + half4 c_b = GATHER_BLUE_TEXTURE2D(_BlitTexture, sampler_BlitTexture, i.texcoordStereo); half3 c0 = half3(c_r.x, c_g.x, c_b.x); half3 c1 = half3(c_r.y, c_g.y, c_b.y); @@ -108,10 +119,10 @@ half4 FragPrefilter(VaryingsDefault i) : SV_Target float2 uv3 = UnityStereoTransformScreenSpaceTex(i.texcoord + duv.xy); // Sample source colors - half3 c0 = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, uv0).rgb; - half3 c1 = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, uv1).rgb; - half3 c2 = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, uv2).rgb; - half3 c3 = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, uv3).rgb; + half3 c0 = SAMPLE_TEXTURE2D(_BlitTexture, sampler_BlitTexture, uv0).rgb; + half3 c1 = SAMPLE_TEXTURE2D(_BlitTexture, sampler_BlitTexture, uv1).rgb; + half3 c2 = SAMPLE_TEXTURE2D(_BlitTexture, sampler_BlitTexture, uv2).rgb; + half3 c3 = SAMPLE_TEXTURE2D(_BlitTexture, sampler_BlitTexture, uv3).rgb; // Sample CoCs half coc0 = SAMPLE_TEXTURE2D(_CoCTex, sampler_CoCTex, uv0).r * 2.0 - 1.0; @@ -149,7 +160,7 @@ half4 FragPrefilter(VaryingsDefault i) : SV_Target // Bokeh filter with disk-shaped kernels half4 FragBlur(VaryingsDefault i) : SV_Target { - half4 samp0 = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, i.texcoordStereo); + half4 samp0 = SAMPLE_TEXTURE2D(_BlitTexture, sampler_BlitTexture, i.texcoordStereo); half4 bgAcc = 0.0; // Background: far field bokeh half4 fgAcc = 0.0; // Foreground: near field bokeh @@ -161,7 +172,7 @@ half4 FragBlur(VaryingsDefault i) : SV_Target float dist = length(disp); float2 duv = float2(disp.x * _RcpAspect, disp.y); - half4 samp = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, UnityStereoTransformScreenSpaceTex(i.texcoord + duv)); + half4 samp = SAMPLE_TEXTURE2D(_BlitTexture, sampler_BlitTexture, UnityStereoTransformScreenSpaceTex(i.texcoord + duv)); // BG: Compare CoC of the current sample and the center sample // and select smaller one. @@ -206,10 +217,10 @@ half4 FragPostBlur(VaryingsDefault i) : SV_Target // 9 tap tent filter with 4 bilinear samples const float4 duv = _MainTex_TexelSize.xyxy * float4(0.5, 0.5, -0.5, 0); half4 acc; - acc = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, UnityStereoTransformScreenSpaceTex(i.texcoord - duv.xy)); - acc += SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, UnityStereoTransformScreenSpaceTex(i.texcoord - duv.zy)); - acc += SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, UnityStereoTransformScreenSpaceTex(i.texcoord + duv.zy)); - acc += SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, UnityStereoTransformScreenSpaceTex(i.texcoord + duv.xy)); + acc = SAMPLE_TEXTURE2D(_BlitTexture, sampler_BlitTexture, UnityStereoTransformScreenSpaceTex(i.texcoord - duv.xy)); + acc += SAMPLE_TEXTURE2D(_BlitTexture, sampler_BlitTexture, UnityStereoTransformScreenSpaceTex(i.texcoord - duv.zy)); + acc += SAMPLE_TEXTURE2D(_BlitTexture, sampler_BlitTexture, UnityStereoTransformScreenSpaceTex(i.texcoord + duv.zy)); + acc += SAMPLE_TEXTURE2D(_BlitTexture, sampler_BlitTexture, UnityStereoTransformScreenSpaceTex(i.texcoord + duv.xy)); return acc / 4.0; } @@ -223,7 +234,7 @@ half4 FragCombine(VaryingsDefault i) : SV_Target // Convert CoC to far field alpha value. float ffa = smoothstep(_MainTex_TexelSize.y * 2.0, _MainTex_TexelSize.y * 4.0, coc); - half4 color = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, i.texcoordStereo); + half4 color = SAMPLE_TEXTURE2D(_BlitTexture, sampler_BlitTexture, i.texcoordStereo); #if defined(UNITY_COLORSPACE_GAMMA) color = SRGBToLinear(color); @@ -244,7 +255,7 @@ half4 FragCombine(VaryingsDefault i) : SV_Target // Debug overlay half4 FragDebugOverlay(VaryingsDefault i) : SV_Target { - half3 color = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, i.texcoordStereo).rgb; + half3 color = SAMPLE_TEXTURE2D(_BlitTexture, sampler_BlitTexture, i.texcoordStereo).rgb; // Calculate the radiuses of CoC. half4 src = SAMPLE_TEXTURE2D(_DepthOfFieldTex, sampler_DepthOfFieldTex, i.texcoordStereo); diff --git a/Assets/Shaders/CustomDoF/DepthOfField.shader b/Assets/Shaders/CustomDoF/DepthOfField.shader index 469370458..a1cc1f54b 100644 --- a/Assets/Shaders/CustomDoF/DepthOfField.shader +++ b/Assets/Shaders/CustomDoF/DepthOfField.shader @@ -13,9 +13,9 @@ Shader "Aetheria/PostProcessing/FogBlur" HLSLPROGRAM #pragma target 3.5 - #pragma vertex VertDefault + #pragma vertex Vert #pragma fragment FragCoC - #include "DepthOfField.hlsl" + #include "AetheriaFogBlurUrp.hlsl" ENDHLSL } @@ -25,9 +25,9 @@ Shader "Aetheria/PostProcessing/FogBlur" HLSLPROGRAM #pragma target 5.0 - #pragma vertex VertDefault + #pragma vertex Vert #pragma fragment FragTempFilter - #include "DepthOfField.hlsl" + #include "AetheriaFogBlurUrp.hlsl" ENDHLSL } @@ -37,9 +37,9 @@ Shader "Aetheria/PostProcessing/FogBlur" HLSLPROGRAM #pragma target 5.0 - #pragma vertex VertDefault + #pragma vertex Vert #pragma fragment FragPrefilter - #include "DepthOfField.hlsl" + #include "AetheriaFogBlurUrp.hlsl" ENDHLSL } @@ -49,10 +49,10 @@ Shader "Aetheria/PostProcessing/FogBlur" HLSLPROGRAM #pragma target 3.5 - #pragma vertex VertDefault + #pragma vertex Vert #pragma fragment FragBlur #define KERNEL_SMALL - #include "DepthOfField.hlsl" + #include "AetheriaFogBlurUrp.hlsl" ENDHLSL } @@ -62,10 +62,10 @@ Shader "Aetheria/PostProcessing/FogBlur" HLSLPROGRAM #pragma target 3.5 - #pragma vertex VertDefault + #pragma vertex Vert #pragma fragment FragBlur #define KERNEL_MEDIUM - #include "DepthOfField.hlsl" + #include "AetheriaFogBlurUrp.hlsl" ENDHLSL } @@ -75,10 +75,10 @@ Shader "Aetheria/PostProcessing/FogBlur" HLSLPROGRAM #pragma target 3.5 - #pragma vertex VertDefault + #pragma vertex Vert #pragma fragment FragBlur #define KERNEL_LARGE - #include "DepthOfField.hlsl" + #include "AetheriaFogBlurUrp.hlsl" ENDHLSL } @@ -88,10 +88,10 @@ Shader "Aetheria/PostProcessing/FogBlur" HLSLPROGRAM #pragma target 3.5 - #pragma vertex VertDefault + #pragma vertex Vert #pragma fragment FragBlur #define KERNEL_VERYLARGE - #include "DepthOfField.hlsl" + #include "AetheriaFogBlurUrp.hlsl" ENDHLSL } @@ -101,9 +101,9 @@ Shader "Aetheria/PostProcessing/FogBlur" HLSLPROGRAM #pragma target 3.5 - #pragma vertex VertDefault + #pragma vertex Vert #pragma fragment FragPostBlur - #include "DepthOfField.hlsl" + #include "AetheriaFogBlurUrp.hlsl" ENDHLSL } @@ -113,9 +113,9 @@ Shader "Aetheria/PostProcessing/FogBlur" HLSLPROGRAM #pragma target 3.5 - #pragma vertex VertDefault + #pragma vertex Vert #pragma fragment FragCombine - #include "DepthOfField.hlsl" + #include "AetheriaFogBlurUrp.hlsl" ENDHLSL } @@ -125,9 +125,9 @@ Shader "Aetheria/PostProcessing/FogBlur" HLSLPROGRAM #pragma target 3.5 - #pragma vertex VertDefault + #pragma vertex Vert #pragma fragment FragDebugOverlay - #include "DepthOfField.hlsl" + #include "AetheriaFogBlurUrp.hlsl" ENDHLSL } } @@ -144,9 +144,9 @@ Shader "Aetheria/PostProcessing/FogBlur" HLSLPROGRAM #pragma target 3.5 - #pragma vertex VertDefault + #pragma vertex Vert #pragma fragment FragCoC - #include "DepthOfField.hlsl" + #include "AetheriaFogBlurUrp.hlsl" ENDHLSL } @@ -156,9 +156,9 @@ Shader "Aetheria/PostProcessing/FogBlur" HLSLPROGRAM #pragma target 3.5 - #pragma vertex VertDefault + #pragma vertex Vert #pragma fragment FragTempFilter - #include "DepthOfField.hlsl" + #include "AetheriaFogBlurUrp.hlsl" ENDHLSL } @@ -168,9 +168,9 @@ Shader "Aetheria/PostProcessing/FogBlur" HLSLPROGRAM #pragma target 3.5 - #pragma vertex VertDefault + #pragma vertex Vert #pragma fragment FragPrefilter - #include "DepthOfField.hlsl" + #include "AetheriaFogBlurUrp.hlsl" ENDHLSL } @@ -180,10 +180,10 @@ Shader "Aetheria/PostProcessing/FogBlur" HLSLPROGRAM #pragma target 3.5 - #pragma vertex VertDefault + #pragma vertex Vert #pragma fragment FragBlur #define KERNEL_SMALL - #include "DepthOfField.hlsl" + #include "AetheriaFogBlurUrp.hlsl" ENDHLSL } @@ -193,10 +193,10 @@ Shader "Aetheria/PostProcessing/FogBlur" HLSLPROGRAM #pragma target 3.5 - #pragma vertex VertDefault + #pragma vertex Vert #pragma fragment FragBlur #define KERNEL_MEDIUM - #include "DepthOfField.hlsl" + #include "AetheriaFogBlurUrp.hlsl" ENDHLSL } @@ -206,10 +206,10 @@ Shader "Aetheria/PostProcessing/FogBlur" HLSLPROGRAM #pragma target 3.5 - #pragma vertex VertDefault + #pragma vertex Vert #pragma fragment FragBlur #define KERNEL_LARGE - #include "DepthOfField.hlsl" + #include "AetheriaFogBlurUrp.hlsl" ENDHLSL } @@ -219,10 +219,10 @@ Shader "Aetheria/PostProcessing/FogBlur" HLSLPROGRAM #pragma target 3.5 - #pragma vertex VertDefault + #pragma vertex Vert #pragma fragment FragBlur #define KERNEL_VERYLARGE - #include "DepthOfField.hlsl" + #include "AetheriaFogBlurUrp.hlsl" ENDHLSL } @@ -232,9 +232,9 @@ Shader "Aetheria/PostProcessing/FogBlur" HLSLPROGRAM #pragma target 3.5 - #pragma vertex VertDefault + #pragma vertex Vert #pragma fragment FragPostBlur - #include "DepthOfField.hlsl" + #include "AetheriaFogBlurUrp.hlsl" ENDHLSL } @@ -244,9 +244,9 @@ Shader "Aetheria/PostProcessing/FogBlur" HLSLPROGRAM #pragma target 3.5 - #pragma vertex VertDefault + #pragma vertex Vert #pragma fragment FragCombine - #include "DepthOfField.hlsl" + #include "AetheriaFogBlurUrp.hlsl" ENDHLSL } @@ -256,9 +256,9 @@ Shader "Aetheria/PostProcessing/FogBlur" HLSLPROGRAM #pragma target 3.5 - #pragma vertex VertDefault + #pragma vertex Vert #pragma fragment FragDebugOverlay - #include "DepthOfField.hlsl" + #include "AetheriaFogBlurUrp.hlsl" ENDHLSL } } diff --git a/Assets/Shaders/CustomDoF/StdLib.hlsl b/Assets/Shaders/CustomDoF/StdLib.hlsl index 224dc7263..cabdb0926 100644 --- a/Assets/Shaders/CustomDoF/StdLib.hlsl +++ b/Assets/Shaders/CustomDoF/StdLib.hlsl @@ -260,11 +260,6 @@ float2 TransformTriangleVertexToUV(float2 vertex) // ----------------------------------------------------------------------------- // Default vertex shaders -struct AttributesDefault -{ - float3 vertex : POSITION; -}; - struct VaryingsDefault { float4 vertex : SV_POSITION; @@ -279,15 +274,19 @@ struct VaryingsDefault float _DepthSlice; #endif -VaryingsDefault VertDefault(AttributesDefault v) +float2 FullScreenTriangleUV(uint vertexID) { - VaryingsDefault o; - o.vertex = float4(v.vertex.xy, 0.0, 1.0); - o.texcoord = TransformTriangleVertexToUV(v.vertex.xy); + return float2((vertexID << 1) & 2, vertexID & 2); +} -#if UNITY_UV_STARTS_AT_TOP - o.texcoord = o.texcoord * float2(1.0, -1.0) + float2(0.0, 1.0); -#endif +float4 _BlitScaleBias; // xy: scale, zw: translate + +VaryingsDefault VertDefault(uint vertexID : SV_VertexID) +{ + float2 uv = FullScreenTriangleUV(vertexID); + VaryingsDefault o; + o.vertex = float4(uv * 2.0 - 1.0, 0.0, 1.0); + o.texcoord = uv * _BlitScaleBias.xy + _BlitScaleBias.zw; o.texcoordStereo = TransformStereoScreenSpaceTex(o.texcoord, 1.0); @@ -300,16 +299,17 @@ float4 _UVTransform; // xy: scale, wz: translate float4 _PosScaleOffset; // xy: scale, wz: offset #endif -VaryingsDefault VertUVTransform(AttributesDefault v) +VaryingsDefault VertUVTransform(uint vertexID : SV_VertexID) { + float2 uv = FullScreenTriangleUV(vertexID); VaryingsDefault o; #if STEREO_DOUBLEWIDE_TARGET - o.vertex = float4(v.vertex.xy * _PosScaleOffset.xy + _PosScaleOffset.zw, 0.0, 1.0); + o.vertex = float4((uv * 2.0 - 1.0) * _PosScaleOffset.xy + _PosScaleOffset.zw, 0.0, 1.0); #else - o.vertex = float4(v.vertex.xy, 0.0, 1.0); + o.vertex = float4(uv * 2.0 - 1.0, 0.0, 1.0); #endif - o.texcoord = TransformTriangleVertexToUV(v.vertex.xy) * _UVTransform.xy + _UVTransform.zw; + o.texcoord = uv * _UVTransform.xy + _UVTransform.zw; o.texcoordStereo = TransformStereoScreenSpaceTex(o.texcoord, 1.0); #if STEREO_INSTANCING_ENABLED o.stereoTargetEyeIndex = (uint)_DepthSlice; diff --git a/Assets/Shaders/Raymarching/CloudShader.shader b/Assets/Shaders/Raymarching/CloudShader.shader index e87575b89..c402636c7 100644 --- a/Assets/Shaders/Raymarching/CloudShader.shader +++ b/Assets/Shaders/Raymarching/CloudShader.shader @@ -36,6 +36,7 @@ Shader "Aetheria/CloudShader" #include "UnityCG.cginc" #include "Assets/Shaders/PackFloat.cginc" sampler2D _CameraDepthTexture; + float4 _CameraDepthTexture_TexelSize; float _RaymarchOffset; //raymarch offset by halton sequence, [0,1] float4 _ProjectionExtents; sampler2D _DitheringTex; @@ -44,25 +45,25 @@ Shader "Aetheria/CloudShader" float _ExtinctionCoefficient; - struct appdata - { - float4 vertex : POSITION; - float2 uv : TEXCOORD0; - }; - struct Interpolator { float4 vertex : SV_POSITION; float4 screenPos : TEXCOORD0; float2 vsray : TEXCOORD1; }; - Interpolator vert (appdata v) + float2 FullScreenTriangleUV(uint vertexID) { + return float2((vertexID << 1) & 2, vertexID & 2); + } + + Interpolator vert (uint vertexID : SV_VertexID) + { + float2 uv = FullScreenTriangleUV(vertexID); + float2 rayUv = float2(uv.x, 1.0 - uv.y); Interpolator o; - o.vertex = UnityObjectToClipPos(v.vertex); - v.vertex.z = 0.5; + o.vertex = float4(uv * 2.0 - 1.0, 0.0, 1.0); o.screenPos = ComputeScreenPos(o.vertex); - o.vsray = (2.0 * v.uv - 1.0) * _ProjectionExtents.xy + _ProjectionExtents.zw; + o.vsray = (2.0 * rayUv - 1.0) * _ProjectionExtents.xy + _ProjectionExtents.zw; return o; } @@ -135,14 +136,16 @@ Shader "Aetheria/CloudShader" float3 DepthToWorld(float2 uv, float depth) { - float z = (1-depth) * 2.0 - 1.0; + #if UNITY_REVERSED_Z + float z = depth; + #else + float z = lerp(UNITY_NEAR_CLIP_VALUE, 1.0, depth); + #endif float4 clipSpacePosition = float4(uv * 2.0 - 1.0, z, 1.0); - float4 viewSpacePosition = mul(_CamInvProj,clipSpacePosition); - viewSpacePosition /= viewSpacePosition.w; - - float4 worldSpacePosition = mul(unity_ObjectToWorld,viewSpacePosition); + float4 worldSpacePosition = mul(_CamInvProj, clipSpacePosition); + worldSpacePosition /= worldSpacePosition.w; return worldSpacePosition.xyz; } @@ -157,18 +160,15 @@ Shader "Aetheria/CloudShader" float3 vspos = float3(i.vsray, 1.0); float4 worldPos = mul(unity_CameraToWorld,float4(vspos,1.0)); worldPos /= worldPos.w; - float4 screenPos = UNITY_PROJ_COORD( i.screenPos ); - float depthSample = tex2Dproj( _CameraDepthTexture, screenPos ).r; - float3 worldDepth = DepthToWorld(screenPos, depthSample); - float raymarchEnd = length(worldDepth-worldPos.xyz); + float2 screenUV = i.screenPos.xy / i.screenPos.w; + float depthSample = tex2D(_CameraDepthTexture, screenUV).r; + float raymarchEnd = LinearEyeDepth(depthSample) * length(vspos) / max(abs(vspos.z), 0.0001); float raymarchStart = _ProjectionParams.y; //float sceneDepth = Linear01Depth(depthSample); //bool occluded = GetRaymarchEndFromSceneDepth(sceneDepth, raymarchEnd); float3 viewDir = normalize(worldPos.xyz - _WorldSpaceCameraPos); - float2 screenUV = i.screenPos.xy / i.screenPos.w; - //float blue = tex2D(_DitheringTex, screenPos * _DitheringCoords.xy + _DitheringCoords.zw).r; float dither = tex2D(_DitheringTex, screenUV * _DitheringCoords.xy).r; float offset = -fmod(_RaymarchOffset + dither, 1.0f); //final offset combined. The value will be multiplied by sample step in GetDensity. @@ -177,7 +177,6 @@ Shader "Aetheria/CloudShader" float distance; //TODO: sceneDepth here is distance in camera z-axis, but the parameter should be radial distance. float density = GetDensity(_WorldSpaceCameraPos, viewDir, raymarchEnd, offset, /*out*/intensity, /*out*/distance); - if(depthSample > .99) density = 1; return float4(intensity, pack(distance, density)); } @@ -201,17 +200,12 @@ Shader "Aetheria/CloudShader" float4 _UndersampleCloudTex_TexelSize; float4x4 _PrevVP; //View projection matrix of last frame. Used to temporal reprojection. + float _ResetHistory; //These values are needed for doing extra raymarch when out of bound. sampler2D _CameraDepthTexture; float4 _ProjectionExtents; - struct appdata - { - float4 vertex : POSITION; - float2 uv : TEXCOORD0; - }; - struct v2f { float4 vertex : SV_POSITION; @@ -220,12 +214,19 @@ Shader "Aetheria/CloudShader" float4 screenPos : TEXCOORD2; }; - v2f vert(appdata v) + float2 FullScreenTriangleUV(uint vertexID) { + return float2((vertexID << 1) & 2, vertexID & 2); + } + + v2f vert(uint vertexID : SV_VertexID) + { + float2 uv = FullScreenTriangleUV(vertexID); + float2 rayUv = float2(uv.x, 1.0 - uv.y); v2f o; - o.vertex = UnityObjectToClipPos(v.vertex); - o.uv = v.uv; - o.vsray = (2.0 * v.uv - 1.0) * _ProjectionExtents.xy + _ProjectionExtents.zw; + o.vertex = float4(uv * 2.0 - 1.0, 0.0, 1.0); + o.uv = uv; + o.vsray = (2.0 * rayUv - 1.0) * _ProjectionExtents.xy + _ProjectionExtents.zw; o.screenPos = ComputeScreenPos(o.vertex); return o; } @@ -264,52 +265,6 @@ Shader "Aetheria/CloudShader" // float4 worldPos = mul(unity_CameraToWorld, float4(vspos, 1.0f)); // worldPos /= worldPos.w; float4 raymarchResult = tex2D(_UndersampleCloudTex, i.uv); - float distance; - float density; - float density2; - unpack(raymarchResult.a, distance, density); - distance *= _ProjectionParams.z; - //float intensity = raymarchResult.x; - - { //Do temporal reprojection and clip things. - half outOfBound; - float2 prevUV = PrevUV(mul(unity_CameraToWorld, float4(normalize(vspos) * distance, 1.0)), outOfBound); //find uv in history buffer. - - float4 prevSample = tex2D(_MainTex, prevUV); - float2 xoffset = float2(_UndersampleCloudTex_TexelSize.x, 0.0f); - float2 yoffset = float2(0.0f, _UndersampleCloudTex_TexelSize.y); - - float4 m1 = 0.0f, m2 = 0.0f; - //The loop below calculates mean and variance used to calculate AABB. - [unroll] - for (int x = -1; x <= 1; x ++) { - [unroll] - for (int y = -1; y <= 1; y ++ ) { - float4 val; - if (x == 0 && y == 0) { - val = float4(raymarchResult.rgb, distance); - } - else { - val = tex2Dlod(_UndersampleCloudTex, float4(i.uv + xoffset * x + yoffset * y, 0.0, 0.0)); - float distance2; - unpack(val.a, distance2, density2); - val = float4(val.rgb, distance2); - } - m1 += val; - m2 += val * val; - } - } - //Code from https://zhuanlan.zhihu.com/p/64993622. - float gamma = 0.5f; - float4 mu = m1 / 9; - float4 sigma = sqrt(abs(m2 / 9 - mu * mu)); - float4 minc = mu - gamma * sigma; - float4 maxc = mu + gamma * sigma; - prevSample = ClipAABB(minc, maxc, prevSample); - - //Blend - raymarchResult = lerp(float4(prevSample.rgb, density), float4(raymarchResult.rgb, density2), max(0.05f, outOfBound)); - } return raymarchResult; } ENDCG @@ -318,47 +273,46 @@ Shader "Aetheria/CloudShader" //Pass3, Blend final cloud image with final image. Pass{ Cull Off ZWrite Off ZTest Always + Blend One OneMinusSrcAlpha CGPROGRAM #pragma target 5.0 #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" + #include "Assets/Shaders/PackFloat.cginc" - sampler2D _MainTex; //Final image without cloud. sampler2D _CloudTex; //The full resolution cloud tex we generated. sampler2D _CameraDepthTexture; float4 _ProjectionExtents; - struct appdata - { - float4 vertex : POSITION; - float2 uv : TEXCOORD0; - }; - struct v2f { float2 uv : TEXCOORD0; float4 vertex : SV_POSITION; }; - v2f vert(appdata v) + float2 FullScreenTriangleUV(uint vertexID) { + return float2((vertexID << 1) & 2, vertexID & 2); + } + + v2f vert(uint vertexID : SV_VertexID) + { + float2 uv = FullScreenTriangleUV(vertexID); v2f o; - o.vertex = UnityObjectToClipPos(v.vertex); - o.uv = v.uv; + o.vertex = float4(uv * 2.0 - 1.0, 0.0, 1.0); + o.uv = uv; return o; } half4 frag(v2f i) : SV_Target { - //float3 vspos = float3(i.vsray, 1.0); - //float4 worldPos = mul(unity_CameraToWorld,float4(vspos,1.0)); - - half4 mcol = tex2D(_MainTex,i.uv); float4 currSample = tex2D(_CloudTex, i.uv); - - return half4(mcol.rgb * (1 - currSample.a) + currSample.rgb, 1); + float distance; + float density; + unpack(currSample.a, distance, density); + return half4(currSample.rgb, saturate(density)); } ENDCG } diff --git a/Assets/Shaders/Raymarching/VolumeSun.shader b/Assets/Shaders/Raymarching/VolumeSun.shader index 9e47b83d2..65c80b143 100644 --- a/Assets/Shaders/Raymarching/VolumeSun.shader +++ b/Assets/Shaders/Raymarching/VolumeSun.shader @@ -1,150 +1,181 @@ -// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)' - Shader "Aetheria/Volume Sun" { - Properties - { - _ColorRamp ("Color Ramp", 2D) = "white" {} - _Offset ("Offset", CUBE) = "gray" {} - _Albedo ("Albedo", CUBE) = "black" {} - _RayStepSize("Ray Step Size", Float) = .01 - _FirstOffsetDistance("First Offset Distance", Float) = .01 - _FirstOffsetDepthExponent("First Offset Depth Exponent", Float) = 1 - _SecondOffsetDistance("Second Offset Distance", Float) = .01 - _SecondOffsetDepthExponent("Second Offset Depth Exponent", Float) = 1 - _DensityDepthExponent("Density Depth Exponent", Float) = 1 - _DensityAlbedoExponent("Density Albedo Exponent", Float) = 1 - _NoiseFrequency("Noise Frequency", Float) = 1 - _NoiseAmplitude("Noise Amplitude", Float) = 1 - _NoiseSpeed("Noise Speed", Float) = 1 - _Emission("Emission", Float) = 1 - _Alpha("Alpha Multiplier", Float) = 1 - _Glossiness("Gloss", Range(0,1)) = 1 - _Metallic("Metallic", Range(0,1)) = 1 - //_EdgeTransparency("Edge Transparency", Float) = 1 - } - - SubShader - { - Tags { "RenderType"="Opaque" } - LOD 200 + Properties + { + _ColorRamp ("Color Ramp", 2D) = "white" {} + _Offset ("Offset", CUBE) = "gray" {} + _Albedo ("Albedo", CUBE) = "black" {} + _RayStepSize ("Ray Step Size", Float) = 0.01 + _FirstOffsetDistance ("First Offset Distance", Float) = 0.01 + _FirstOffsetDepthExponent ("First Offset Depth Exponent", Float) = 1 + _SecondOffsetDistance ("Second Offset Distance", Float) = 0.01 + _SecondOffsetDepthExponent ("Second Offset Depth Exponent", Float) = 1 + _DensityDepthExponent ("Density Depth Exponent", Float) = 1 + _DensityAlbedoExponent ("Density Albedo Exponent", Float) = 1 + _NoiseFrequency ("Noise Frequency", Float) = 1 + _NoiseAmplitude ("Noise Amplitude", Float) = 1 + _NoiseSpeed ("Noise Speed", Float) = 1 + _Emission ("Emission", Float) = 1 + _Alpha ("Alpha Multiplier", Float) = 1 + _Glossiness ("Gloss", Range(0,1)) = 1 + _Metallic ("Metallic", Range(0,1)) = 1 + } - CGPROGRAM + SubShader + { + Tags + { + "Queue" = "Geometry" + "RenderType" = "Opaque" + "RenderPipeline" = "UniversalPipeline" + } - #pragma surface surf Standard vertex:vert NoLighting noambient noforwardadd - #pragma target 3.0 - #include "UnityPBSLighting.cginc" - #include "Assets/Shaders/Dither Functions.cginc" + Pass + { + Name "VolumeSun" + Tags { "LightMode" = "UniversalForward" } - sampler2D _ColorRamp; - samplerCUBE _Offset; - samplerCUBE _Albedo; - - struct Input { - float4 screenPos; - float3 worldNormal; - float3 viewDir; - }; + Cull Back + ZWrite On - half _Glossiness; - half _Metallic; - fixed4 _Color; + HLSLPROGRAM + #pragma vertex Vert + #pragma fragment Frag + #pragma target 3.5 - float _RayStepSize; - float _FirstOffsetDistance; - float _SecondOffsetDistance; - float _FirstOffsetDepthExponent; - float _SecondOffsetDepthExponent; - float _LimbDarkening; - float _DensityDepthExponent; - float _DensityAlbedoExponent; - float _Alpha; - float _Emission; + #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl" - float4x4 _AlbedoRotation; - float4x4 _FirstOffsetDomainRotation; - float4x4 _FirstOffsetRotation; - float4x4 _SecondOffsetDomainRotation; - float4x4 _SecondOffsetRotation; + TEXTURE2D(_ColorRamp); + SAMPLER(sampler_ColorRamp); + TEXTURECUBE(_Offset); + SAMPLER(sampler_Offset); + TEXTURECUBE(_Albedo); + SAMPLER(sampler_Albedo); - float4 _LightingDirection; - half _NoiseFrequency; - half _NoiseAmplitude; - half _NoiseSpeed; + CBUFFER_START(UnityPerMaterial) + float _RayStepSize; + float _FirstOffsetDistance; + float _SecondOffsetDistance; + float _FirstOffsetDepthExponent; + float _SecondOffsetDepthExponent; + float _LimbDarkening; + float _DensityDepthExponent; + float _DensityAlbedoExponent; + float _Alpha; + float _Emission; + float4x4 _AlbedoRotation; + float4x4 _FirstOffsetDomainRotation; + float4x4 _FirstOffsetRotation; + float4x4 _SecondOffsetDomainRotation; + float4x4 _SecondOffsetRotation; + float4 _LightingDirection; + half _NoiseFrequency; + half _NoiseAmplitude; + half _NoiseSpeed; + CBUFFER_END - void vert (inout appdata_full v, out Input o) { - UNITY_INITIALIZE_OUTPUT(Input,o); - } + struct Attributes + { + float4 positionOS : POSITION; + float3 normalOS : NORMAL; + }; - float tri(in float x){return abs(frac(x)-.5);} - float3 tri3(in float3 p){return float3( tri(p.z+tri(p.y*1.)), tri(p.z+tri(p.x*1.)), tri(p.y+tri(p.x*1.)));} + struct Varyings + { + float4 positionCS : SV_POSITION; + float3 normalWS : TEXCOORD0; + float3 viewDirWS : TEXCOORD1; + }; - float triNoise3d(in float3 p, in float spd) - { - float z=1.4; - float rz = 0.; - float3 bp = p; - for (float i=0.; i<=2.; i++ ) - { - float3 dg = tri3(bp*2.); - p += (dg+_Time.y*spd); + Varyings Vert(Attributes input) + { + Varyings output; + float3 positionWS = TransformObjectToWorld(input.positionOS.xyz); + output.positionCS = TransformWorldToHClip(positionWS); + output.normalWS = normalize(TransformObjectToWorldNormal(input.normalOS)); + output.viewDirWS = normalize(GetWorldSpaceViewDir(positionWS)); + return output; + } - bp *= 1.8; - z *= 1.5; - p *= 1.2; - - rz+= (tri(p.z+tri(p.x+tri(p.y))))/z; - bp += 0.14; - } - return rz; - } - - void surf (Input IN, inout SurfaceOutputStandard o) - { - float rim = saturate(dot(IN.viewDir,IN.worldNormal)); - float2 screenPos = IN.screenPos.xy / IN.screenPos.w; - - float3 rayPos = IN.worldNormal; - float3 rayStep = normalize(IN.viewDir)*_RayStepSize*(2-rim); - half dither = frac(tex2D(_DitheringTex, (screenPos + .5) * _DitheringCoords.xy).r + _FrameNumber * 1.61803398875); - //float dither = tex2D(_DitheringTex, (screenPos + .5) * _DitheringCoords.xy + _DitheringCoords.zw).r * 2; - rayPos += rayStep * dither; - - float3 accum = 0; - float alphaAccum = 0; - for(int i=0;i<32;i++){ - float elevation = length(rayPos); - float depth = 1 - elevation; - float turbulence = 1.25 - abs(rayPos.y) * .5; + float Tri(float x) + { + return abs(frac(x) - 0.5); + } - float3 firstSamplePosition = mul((float3x3)_FirstOffsetDomainRotation, rayPos); - float3 offset = mul((float3x3)_FirstOffsetRotation, - normalize(texCUBElod(_Offset, float4(firstSamplePosition, i/8)).rgb - float3(.5,.5,.5))) * - _FirstOffsetDistance * (pow(elevation,_FirstOffsetDepthExponent)) * turbulence; + float3 Tri3(float3 p) + { + return float3( + Tri(p.z + Tri(p.y)), + Tri(p.z + Tri(p.x)), + Tri(p.y + Tri(p.x))); + } - float3 secondSamplePosition = mul((float3x3)_SecondOffsetDomainRotation, rayPos + offset); - float3 offset2 = mul((float3x3)_SecondOffsetRotation, - normalize(texCUBElod(_Offset, float4(secondSamplePosition, i/16)).rgb - float3(.5,.5,.5))) * - _SecondOffsetDistance * (pow(elevation,_SecondOffsetDepthExponent)) * turbulence; + float TriNoise3d(float3 p, float speed) + { + float z = 1.4; + float result = 0.0; + float3 bp = p; - float albedo = texCUBElod (_Albedo, float4(mul((float3x3)_AlbedoRotation,normalize(firstSamplePosition + offset2)) ,i/8)).x; - float noise = max(1 - (triNoise3d(secondSamplePosition * _NoiseFrequency, _NoiseSpeed)) * _NoiseAmplitude, 0.01); + UNITY_UNROLL + for (int i = 0; i <= 2; i++) + { + float3 dg = Tri3(bp * 2.0); + p += dg + _Time.y * speed; + bp *= 1.8; + z *= 1.5; + p *= 1.2; + result += Tri(p.z + Tri(p.x + Tri(p.y))) / z; + bp += 0.14; + } - float density = pow(max(depth,0),_DensityDepthExponent) * pow(albedo,_DensityAlbedoExponent); - alphaAccum += density; - accum += tex2D(_ColorRamp, albedo.xx * noise) * density * noise; - - rayPos += rayStep; - } - ditherClip(screenPos, alphaAccum * _Alpha); + return result; + } - o.Smoothness = _Glossiness; - o.Albedo = 0; - o.Emission = accum*_Emission; - o.Metallic = _Metallic; - o.Alpha = 1; - } - ENDCG - } - FallBack "Standard" + float4 Frag(Varyings input) : SV_Target + { + float rim = saturate(dot(input.viewDirWS, input.normalWS)); + float3 rayPos = input.normalWS; + float3 rayStep = normalize(input.viewDirWS) * _RayStepSize * (2.0 - rim); + float3 accum = 0.0; + float alphaAccum = 0.0; + + UNITY_LOOP + for (int i = 0; i < 32; i++) + { + float elevation = length(rayPos); + float depth = 1.0 - elevation; + float turbulence = 1.25 - abs(rayPos.y) * 0.5; + + float3 firstSamplePosition = mul((float3x3)_FirstOffsetDomainRotation, rayPos); + float3 offset = mul( + (float3x3)_FirstOffsetRotation, + normalize(SAMPLE_TEXTURECUBE_LOD(_Offset, sampler_Offset, firstSamplePosition, i / 8.0).rgb - float3(0.5, 0.5, 0.5))) + * _FirstOffsetDistance + * pow(max(elevation, 0.0001), _FirstOffsetDepthExponent) + * turbulence; + + float3 secondSamplePosition = mul((float3x3)_SecondOffsetDomainRotation, rayPos + offset); + float3 offset2 = mul( + (float3x3)_SecondOffsetRotation, + normalize(SAMPLE_TEXTURECUBE_LOD(_Offset, sampler_Offset, secondSamplePosition, i / 16.0).rgb - float3(0.5, 0.5, 0.5))) + * _SecondOffsetDistance + * pow(max(elevation, 0.0001), _SecondOffsetDepthExponent) + * turbulence; + + float3 albedoDirection = normalize(mul((float3x3)_AlbedoRotation, firstSamplePosition + offset2)); + float albedo = SAMPLE_TEXTURECUBE_LOD(_Albedo, sampler_Albedo, albedoDirection, i / 8.0).r; + float noise = max(1.0 - TriNoise3d(secondSamplePosition * _NoiseFrequency, _NoiseSpeed) * _NoiseAmplitude, 0.01); + float density = pow(max(depth, 0.0), _DensityDepthExponent) * pow(max(albedo, 0.0), _DensityAlbedoExponent); + + alphaAccum += density; + accum += SAMPLE_TEXTURE2D(_ColorRamp, sampler_ColorRamp, albedo.xx * noise).rgb * density * noise; + rayPos += rayStep; + } + + clip(alphaAccum * _Alpha - 0.01); + return float4(accum * _Emission, 1.0); + } + ENDHLSL + } + } } diff --git a/GameCult.Aetheria.State.Unity.csproj b/GameCult.Aetheria.State.Unity.csproj index 3752c7865..143da1e2b 100644 --- a/GameCult.Aetheria.State.Unity.csproj +++ b/GameCult.Aetheria.State.Unity.csproj @@ -1,4 +1,4 @@ - + latest @@ -53,6 +53,7 @@ + @@ -86,12 +87,12 @@ - + @@ -103,6 +104,7 @@ + diff --git a/Packages/manifest.json b/Packages/manifest.json index 09418fd95..03816862e 100644 --- a/Packages/manifest.json +++ b/Packages/manifest.json @@ -1,19 +1,17 @@ { "dependencies": { + "com.gamecult.brokkr": "file:../../Brokkr/surfaces/unity/Packages/com.gamecult.brokkr/", "com.unity.2d.sprite": "1.0.0", "com.unity.2d.tilemap": "1.0.0", "com.unity.ai.navigation": "2.0.12", "com.unity.burst": "1.8.29", "com.unity.cinemachine": "2.10.7", - "com.gamecult.brokkr": "file:../../Brokkr/surfaces/unity/Packages/com.gamecult.brokkr/", + "com.unity.entities.graphics": "1.4.20", "com.unity.ide.rider": "3.0.39", "com.unity.ide.visualstudio": "2.0.27", "com.unity.inputsystem": "1.19.0", - "com.unity.entities.graphics": "1.4.20", - "org.gamecult.cultmath": "file:../../CultMath", "com.unity.mathematics": "1.3.3", "com.unity.multiplayer.center": "1.0.1", - "com.unity.postprocessing": "3.5.4", "com.unity.recorder": "5.1.5", "com.unity.render-pipelines.universal": "17.2.0", "com.unity.test-framework": "1.6.0", @@ -22,6 +20,7 @@ "com.unity.xr.legacyinputhelpers": "3.0.1", "org.gamecult.aetheria.eve-runtime": "file:org.gamecult.aetheria.eve-runtime", "org.gamecult.aetheria.state": "file:org.gamecult.aetheria.state", + "org.gamecult.cultmath": "file:../../CultMath", "org.gamecult.eve.surface": "file:../../Eve/packages/org.gamecult.eve.surface", "org.gamecult.eve.unity-uitoolkit": "file:../../Eve/packages/org.gamecult.eve.unity-uitoolkit", "com.unity.modules.accessibility": "1.0.0", diff --git a/Packages/org.gamecult.aetheria.eve-runtime/README.md b/Packages/org.gamecult.aetheria.eve-runtime/README.md index 5f87aac37..f538bf562 100644 --- a/Packages/org.gamecult.aetheria.eve-runtime/README.md +++ b/Packages/org.gamecult.aetheria.eve-runtime/README.md @@ -3,7 +3,7 @@ This package mounts provider-owned Eve surfaces from Aetheria's typed CultCache state into Unity UI Toolkit. -`AetheriaEveSurfacePresenter` is a projection bridge: +`AetheriaEveSurfacePresenter` is a typed surface document lowerer: - it resolves the active client target from `GameData/aetheria-client.cc`; - it reads `gamecult.eve.surface.v1` from the selected typed state source, which @@ -24,6 +24,20 @@ still works as a legacy fallback), or disable the automatic mount with `--aetheria-disable-eve-runtime-bootstrap`. Batchmode disables the bootstrap so compile and smoke gates do not accidentally create renderer state. +Nested CultUI regions are expressed as `embeddedDocuments` / `surface.slot` +entries on the retained surface component. Aetheria mirrors Eve's +`EveEmbeddedDocumentSlot` as `AetheriaRuntimeEmbeddedDocumentSlot`, and the +Unity host resolves those child documents through the same managed CultMesh +state handles as the parent surface. Use this for daemon-owned composite UI +such as inventory panels with dropdown child surfaces; do not rebuild the child +state as a Unity-only model, projector, or drag/drop workaround. + +Discovery starts from Eve's `web/fixtures/cultui-embedded-surface.json` fixture +and `docs/parity-testing-harness.md` runtime matrix. Keep Unity covered by the +Aetheria state verifier and `verify-stage7d-unity-parity.ps1`; keep the shared +CultUI contract covered by Eve's browser DSL test, parity harness, and Flutter +parity smoke. + Commands are surfaced as `gamecult.eve.command.v1` requests. The presenter does not accept or apply them locally; provider acceptance still belongs to the CultMesh command bridge. Renderer-local command effects would be the wrong diff --git a/Packages/org.gamecult.aetheria.eve-runtime/Runtime/AetheriaEveSurfacePresenter.cs b/Packages/org.gamecult.aetheria.eve-runtime/Runtime/AetheriaEveSurfacePresenter.cs index 13c985e8e..b75f3136a 100644 --- a/Packages/org.gamecult.aetheria.eve-runtime/Runtime/AetheriaEveSurfacePresenter.cs +++ b/Packages/org.gamecult.aetheria.eve-runtime/Runtime/AetheriaEveSurfacePresenter.cs @@ -132,9 +132,9 @@ namespace GameCult.Aetheria.EveRuntime var surface = ReadDaemonSurfaceState(stateBoot); return surface == null ? null - : AetheriaRuntimeEveSurfaceAdapter.ToEveSurfaceDocument( + : AetheriaRuntimeSurfaceDocuments.ToEveSurfaceDocument( surface, - AetheriaUnityRuntimeClientProvider.EveSurfaceStateRefResolver( + AetheriaEveRuntimeUnityHooks.TryCreateStateRefResolver( stateBoot, "unity-eve-surface-presenter")); } @@ -170,9 +170,10 @@ namespace GameCult.Aetheria.EveRuntime private CultMeshReactiveDocument? CreateReactiveDaemonSurfaceState( AetheriaRuntimeStateBootReport stateBoot) { - return AetheriaUnityRuntimeClientProvider - .RuntimeState(stateBoot, "unity-eve-surface-presenter") - .ReactiveEveSurface(surfaceId); + return AetheriaEveRuntimeUnityHooks + .RequireRuntimeState(stateBoot, "unity-eve-surface-presenter") + .EveSurfaceDocument(surfaceId) + ?.Reactive(); } private UIDocument ResolveDocument() @@ -184,9 +185,7 @@ namespace GameCult.Aetheria.EveRuntime private AetheriaRuntimeStateBootReport ResolveStateBoot() { - return AetheriaRuntimeStateBoot.Inspect( - AetheriaUnityRuntimePaths.GameDataDirectory, - stateFilePathOverride); + return AetheriaEveRuntimeUnityHooks.RequireStateBoot(stateFilePathOverride); } private static VisualElement BuildError(string message) @@ -217,7 +216,7 @@ namespace GameCult.Aetheria.EveRuntime surface, request => EmitCommand(stateBoot, request), RootOnlyChrome, - AetheriaUnityRuntimeClientProvider.EveSurfaceStateRefResolver( + AetheriaEveRuntimeUnityHooks.TryCreateStateRefResolver( stateBoot, "unity-eve-surface-presenter")); _mountedStatePath = statePath; @@ -231,7 +230,7 @@ namespace GameCult.Aetheria.EveRuntime var clientId = string.IsNullOrWhiteSpace(request.ClientId) ? "unity-eve-surface-presenter" : request.ClientId; - var control = AetheriaUnityRuntimeClientProvider.Control( + var control = AetheriaEveRuntimeUnityHooks.RequireControl( stateBoot, clientId); if (control.TrySubmitSurfaceCommand(request, out var daemonEnvelope)) @@ -243,8 +242,8 @@ namespace GameCult.Aetheria.EveRuntime try { - var envelope = AetheriaUnityRuntimeClientProvider - .Ui(stateBoot, "unity-eve-surface-presenter") + var envelope = AetheriaEveRuntimeUnityHooks + .RequireUi(stateBoot, "unity-eve-surface-presenter") .SurfaceCommandAsync( request, string.IsNullOrWhiteSpace(request.ClientId) ? "unity-uitoolkit" : request.ClientId) diff --git a/Packages/org.gamecult.aetheria.eve-runtime/Runtime/AetheriaEveUnitySurfaceHost.cs b/Packages/org.gamecult.aetheria.eve-runtime/Runtime/AetheriaEveUnitySurfaceHost.cs index b98d4046c..1fbd449b3 100644 --- a/Packages/org.gamecult.aetheria.eve-runtime/Runtime/AetheriaEveUnitySurfaceHost.cs +++ b/Packages/org.gamecult.aetheria.eve-runtime/Runtime/AetheriaEveUnitySurfaceHost.cs @@ -41,6 +41,8 @@ namespace GameCult.Aetheria.EveRuntime public static class AetheriaEveUnitySurfaceHost { + private static PanelSettings _runtimePanelSettings; + public static UIDocument Render( Transform owner, UIDocument document, @@ -49,6 +51,7 @@ namespace GameCult.Aetheria.EveRuntime Action commandHandler, AetheriaEveUnitySurfaceChrome chrome, CultMeshStateRefResolver stateRefResolver = null, + Func embeddedDocumentResolver = null, int sortingOrder = 1000) { if (owner == null) @@ -68,8 +71,8 @@ namespace GameCult.Aetheria.EveRuntime ContainsStateRefs(surface) ? CreateDefaultStateRefResolver() : null); - surface = AetheriaRuntimeEveSurfaceAdapter.ResolveStateRefs(surface, effectiveStateRefResolver); - var lowerer = new EveUiToolkitSurfaceLowerer(); + surface = AetheriaRuntimeSurfaceDocuments.ResolveStateRefs(surface, effectiveStateRefResolver); + var lowerer = new EveUiToolkitSurfaceLowerer(new EveUiToolkitSurfaceOptions(embeddedDocumentResolver)); if (chrome.UseShell) { var shell = CreateShell(chrome); @@ -81,6 +84,8 @@ namespace GameCult.Aetheria.EveRuntime root.Add(lowerer.Lower(surface, commandHandler)); } + Debug.Log( + $"Rendered Eve surface '{surface.Surface.Id}' into UIDocument '{document.name}' with panel '{document.panelSettings?.name ?? "none"}'."); return document; } @@ -92,6 +97,7 @@ namespace GameCult.Aetheria.EveRuntime Action commandHandler, AetheriaEveUnitySurfaceChrome chrome, CultMeshStateRefResolver stateRefResolver = null, + Func embeddedDocumentResolver = null, int sortingOrder = 1000) { if (surface == null) @@ -101,10 +107,17 @@ namespace GameCult.Aetheria.EveRuntime owner, document, hostName, - AetheriaRuntimeEveSurfaceAdapter.ToEveSurfaceDocument(surface), + AetheriaRuntimeSurfaceDocuments.ToEveSurfaceDocument(surface), commandHandler, chrome, stateRefResolver, + embeddedDocumentResolver == null + ? null + : slot => + { + var nested = embeddedDocumentResolver(slot); + return nested == null ? null : AetheriaRuntimeSurfaceDocuments.ToEveSurfaceDocument(nested); + }, sortingOrder); } @@ -132,17 +145,39 @@ namespace GameCult.Aetheria.EveRuntime int sortingOrder) { if (document != null) + { + if (document.panelSettings == null) + document.panelSettings = ResolveRuntimePanelSettings(); return document; + } var host = new GameObject(string.IsNullOrWhiteSpace(hostName) ? "Aetheria Eve Surface" : hostName); + host.SetActive(false); host.transform.SetParent(owner, false); host.layer = owner.gameObject.layer; document = host.AddComponent(); document.sortingOrder = sortingOrder; - host.SetActive(false); + document.panelSettings = ResolveRuntimePanelSettings(); return document; } + private static PanelSettings ResolveRuntimePanelSettings() + { + if (_runtimePanelSettings != null) + return _runtimePanelSettings; + + _runtimePanelSettings = ScriptableObject.CreateInstance(); + _runtimePanelSettings.name = "Aetheria Runtime Eve Panel Settings"; + _runtimePanelSettings.scaleMode = PanelScaleMode.ScaleWithScreenSize; + _runtimePanelSettings.referenceResolution = new Vector2Int(1920, 1080); + _runtimePanelSettings.screenMatchMode = PanelScreenMatchMode.MatchWidthOrHeight; + _runtimePanelSettings.match = 0.5f; + _runtimePanelSettings.themeStyleSheet = Resources.Load("UnityDefaultRuntimeTheme"); + if (_runtimePanelSettings.themeStyleSheet == null) + Debug.LogWarning("Aetheria runtime Eve panel could not load UnityDefaultRuntimeTheme.tss."); + return _runtimePanelSettings; + } + private static void ConfigureRoot(VisualElement root, AetheriaEveUnitySurfaceChrome chrome) { root.Clear(); @@ -222,14 +257,78 @@ namespace GameCult.Aetheria.EveRuntime private static CultMeshStateRefResolver CreateDefaultStateRefResolver() { - var stateBoot = AetheriaRuntimeStateBoot.Inspect(AetheriaUnityRuntimePaths.GameDataDirectory, ""); - if (!stateBoot.SupportsLocalStateFileRead || !stateBoot.StateFileExists) - return null; - - return AetheriaUnityRuntimeClientProvider.EveSurfaceStateRefResolver( - stateBoot, - "unity-eve-surface-host"); + return AetheriaEveRuntimeUnityHooks.TryCreateDefaultStateRefResolver(); } } + + public static class AetheriaEveRuntimeUnityHooks + { + public static Func ResolveStateBoot { get; set; } + + public static Func RuntimeState { get; set; } + + public static Func Control { get; set; } + + public static Func Ui { get; set; } + + public static Func StateRefResolver { get; set; } + + public static AetheriaRuntimeStateBootReport RequireStateBoot(string stateFilePathOverride) + { + if (ResolveStateBoot == null) + throw new InvalidOperationException("Aetheria Eve runtime hooks are not installed."); + + return ResolveStateBoot(stateFilePathOverride ?? ""); + } + + public static AetheriaClientState RequireRuntimeState( + AetheriaRuntimeStateBootReport stateBoot, + string runtimeId) + { + if (RuntimeState == null) + throw new InvalidOperationException("Aetheria Eve runtime state hook is not installed."); + + return RuntimeState(stateBoot, runtimeId ?? ""); + } + + public static AetheriaControl RequireControl( + AetheriaRuntimeStateBootReport stateBoot, + string runtimeId) + { + if (Control == null) + throw new InvalidOperationException("Aetheria Eve runtime control hook is not installed."); + + return Control(stateBoot, runtimeId ?? ""); + } + + public static AetheriaUi RequireUi( + AetheriaRuntimeStateBootReport stateBoot, + string runtimeId) + { + if (Ui == null) + throw new InvalidOperationException("Aetheria Eve runtime UI hook is not installed."); + + return Ui(stateBoot, runtimeId ?? ""); + } + + public static CultMeshStateRefResolver TryCreateStateRefResolver( + AetheriaRuntimeStateBootReport stateBoot, + string runtimeId) + { + return StateRefResolver?.Invoke(stateBoot, runtimeId ?? ""); + } + + public static CultMeshStateRefResolver TryCreateDefaultStateRefResolver() + { + if (ResolveStateBoot == null || StateRefResolver == null) + return null; + + var stateBoot = ResolveStateBoot(""); + if (!stateBoot.SupportsLocalStateFileRead || !stateBoot.StateFileExists) + return null; + + return StateRefResolver(stateBoot, "unity-eve-surface-host"); + } + } } diff --git a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaClient.cs b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaClient.cs index 7f302e822..967db3d41 100644 --- a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaClient.cs +++ b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaClient.cs @@ -180,7 +180,7 @@ namespace GameCult.Aetheria.State.Verse ThrowIfDisposed(); try { - _daemonFrame ??= State.ReactiveDaemonFrame(); + _daemonFrame ??= State.DaemonFrame.Reactive(); return _daemonFrame.Current; } catch diff --git a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaClientState.cs b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaClientState.cs index 51fe9e95a..fffe3305d 100644 --- a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaClientState.cs +++ b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaClientState.cs @@ -20,6 +20,9 @@ namespace GameCult.Aetheria.State.Verse private readonly Func> _gravityViewport; private readonly Func> _renderSplatsViewport; private readonly Func> _zoneDetails; + private readonly Func> _zoneDetailsSurface; + private readonly Func> _inventoryPanelSurface; + private readonly Func> _inventoryDropdownSurface; private readonly Func> _selectedObject; private readonly Func> _inventory; private readonly Func> _starbridgePlayerSeat; @@ -55,6 +58,9 @@ namespace GameCult.Aetheria.State.Verse Func> gravityViewport, Func> renderSplatsViewport, Func> zoneDetails, + Func> zoneDetailsSurface, + Func> inventoryPanelSurface, + Func> inventoryDropdownSurface, Func> selectedObject, Func> inventory, CultMeshDocumentHandle starbridgeScenario, @@ -88,6 +94,9 @@ namespace GameCult.Aetheria.State.Verse _gravityViewport = gravityViewport ?? throw new ArgumentNullException(nameof(gravityViewport)); _renderSplatsViewport = renderSplatsViewport ?? throw new ArgumentNullException(nameof(renderSplatsViewport)); _zoneDetails = zoneDetails ?? throw new ArgumentNullException(nameof(zoneDetails)); + _zoneDetailsSurface = zoneDetailsSurface ?? throw new ArgumentNullException(nameof(zoneDetailsSurface)); + _inventoryPanelSurface = inventoryPanelSurface ?? throw new ArgumentNullException(nameof(inventoryPanelSurface)); + _inventoryDropdownSurface = inventoryDropdownSurface ?? throw new ArgumentNullException(nameof(inventoryDropdownSurface)); _selectedObject = selectedObject ?? throw new ArgumentNullException(nameof(selectedObject)); _inventory = inventory ?? throw new ArgumentNullException(nameof(inventory)); StarbridgeScenario = starbridgeScenario ?? throw new ArgumentNullException(nameof(starbridgeScenario)); @@ -98,60 +107,20 @@ namespace GameCult.Aetheria.State.Verse public CultMeshDocumentHandle ProviderAdvertisement { get; } - public CultMeshReactiveDocument ReactiveProviderAdvertisement() - { - return ProviderAdvertisement.Reactive(); - } - public CultMeshDocumentHandle Health { get; } - public CultMeshReactiveDocument ReactiveHealth() - { - return Health.Reactive(); - } - public CultMeshDocumentHandle CommandBoundary { get; } - public CultMeshReactiveDocument ReactiveCommandBoundary() - { - return CommandBoundary.Reactive(); - } - public CultMeshDocumentHandle AuthorityPolicy { get; } - public CultMeshReactiveDocument ReactiveAuthorityPolicy() - { - return AuthorityPolicy.Reactive(); - } - public CultMeshDocumentHandle DaemonFrame { get; } - public AetheriaRuntimeDaemonFrameDocument CurrentDaemonFrame() - { - return DaemonFrame.Latest(); - } - public CultMeshDocumentHandle DaemonSoaView { get; } - public AetheriaRuntimeDaemonSoaViewDocument CurrentDaemonSoaView() - { - return DaemonSoaView.Latest(); - } - public CultMeshDocumentHandle Catalog { get; } - public AetheriaRuntimeCatalogSnapshot CurrentCatalog() - { - return Catalog.Latest(); - } - public CultMeshDocumentHandle LoadoutTemplates { get; } - public AetheriaRuntimeLoadoutTemplatesDocument CurrentLoadoutTemplates() - { - return LoadoutTemplates.Latest(); - } - public CultMeshDocumentHandle GameSurface { get; } public CultMeshDocumentHandle GameTuiSurface { get; } @@ -162,168 +131,28 @@ namespace GameCult.Aetheria.State.Verse public CultMeshDocumentHandle PlayerSettings { get; } - public AetheriaRuntimePlayerSettingsDocument CurrentPlayerSettings() - { - return PlayerSettings.Latest(); - } - public CultMeshDocumentHandle VerseHostSettings { get; } - public AetheriaRuntimeVerseHostSettingsDocument CurrentVerseHostSettings() - { - return VerseHostSettings.Latest(); - } - public CultMeshDocumentHandle CurrentZone { get; } - public AetheriaRuntimeCurrentZoneDocument CurrentZoneState() - { - return CurrentZone.Latest(); - } - public CultMeshDocumentHandle CurrentEntity { get; } - public AetheriaRuntimeCurrentEntityDocument CurrentEntityState() - { - return CurrentEntity.Latest(); - } - public CultMeshDocumentHandle CurrentDocking { get; } - public AetheriaRuntimeCurrentDockingDocument CurrentDockingState() - { - return CurrentDocking.Latest(); - } - public CultMeshDocumentHandle StarbridgeScenario { get; } - public AetheriaRuntimeStarbridgeScenarioDocument CurrentStarbridgeScenario() - { - return StarbridgeScenario.Latest(); - } - - public CultMeshReactiveDocument ReactiveStarbridgeScenario() - { - return StarbridgeScenario.Reactive(); - } - public CultMeshDocumentHandle StarbridgeSession { get; } - public AetheriaRuntimeStarbridgeSessionDocument CurrentStarbridgeSession() - { - return StarbridgeSession.Latest(); - } - - public CultMeshReactiveDocument ReactiveStarbridgeSession() - { - return StarbridgeSession.Reactive(); - } - public CultMeshDocumentHandle StarbridgeSummary { get; } - public AetheriaRuntimeStarbridgeSessionSummaryDocument CurrentStarbridgeSummary() - { - return StarbridgeSummary.Latest(); - } - - public CultMeshReactiveDocument ReactiveStarbridgeSummary() - { - return StarbridgeSummary.Reactive(); - } - public CultMeshDocumentHandle ZoneContacts { get; } - public AetheriaRuntimeZoneContactsDocument CurrentZoneContacts() - { - return ZoneContacts.Latest(); - } - public CultMeshDocumentHandle StationRefit { get; } - public AetheriaRuntimeStationRefitDocument CurrentStationRefit() - { - return StationRefit.Latest(); - } - public CultMeshDocumentHandle SectorMap { get; } - public AetheriaRuntimeSectorMapDocument CurrentSectorMap() - { - return SectorMap.Latest(); - } - public CultMeshDocumentHandle ZoneRender { get; } - public AetheriaRuntimeZoneRenderDocument CurrentZoneRender() - { - return ZoneRender.Latest(); - } - - public CultMeshReactiveDocument ReactiveDaemonFrame() - { - return DaemonFrame.Reactive(); - } - - public CultMeshReactiveDocument ReactiveDaemonSoaView() - { - return DaemonSoaView.Reactive(); - } - - public CultMeshReactiveDocument ReactiveZoneRender() - { - return ZoneRender.Reactive(); - } - - public CultMeshReactiveDocument ReactiveLoadoutTemplates() - { - return LoadoutTemplates.Reactive(); - } - - public CultMeshReactiveDocument ReactiveSectorMap() - { - return SectorMap.Reactive(); - } - - public CultMeshReactiveDocument ReactiveCurrentZone() - { - return CurrentZone.Reactive(); - } - - public CultMeshReactiveDocument ReactiveZoneContacts() - { - return ZoneContacts.Reactive(); - } - - public CultMeshReactiveDocument ReactiveVerseHostSettings() - { - return VerseHostSettings.Reactive(); - } - - public CultMeshReactiveDocument ReactiveCurrentDocking() - { - return CurrentDocking.Reactive(); - } - - public CultMeshReactiveDocument ReactiveCurrentEntity() - { - return CurrentEntity.Reactive(); - } - - public CultMeshReactiveDocument ReactiveStationRefit() - { - return StationRefit.Reactive(); - } - - public CultMeshReactiveDocument ReactiveCatalogSnapshot() - { - return Catalog.Reactive(); - } - - public CultMeshReactiveDocument ReactivePlayerSettingsDocument() - { - return PlayerSettings.Reactive(); - } - public static bool TryResolveEveSurface( string? surfaceId, out AetheriaClientEveSurface surface) @@ -372,80 +201,30 @@ namespace GameCult.Aetheria.State.Verse }; } - public CultMeshReactiveDocument? ReactiveEveSurface( - string? surfaceId) - { - return EveSurfaceDocument(surfaceId)?.Reactive(); - } - public CultMeshDocumentHandle RtsViewport( AetheriaRuntimeRtsViewportBounds viewport) { return _mapViewport(viewport ?? new AetheriaRuntimeRtsViewportBounds()); } - public AetheriaRuntimeRtsViewportDocument CurrentRtsViewport(AetheriaRuntimeRtsViewportBounds viewport) - { - return RtsViewport(viewport).Latest(); - } - - public CultMeshReactiveDocument ReactiveRtsViewport( - AetheriaRuntimeRtsViewportBounds viewport) - { - return RtsViewport(viewport).Reactive(); - } - public CultMeshDocumentHandle ObjectsViewport( AetheriaRuntimeRtsViewportBounds viewport) { return _objectsViewport(viewport ?? new AetheriaRuntimeRtsViewportBounds()); } - public AetheriaRuntimeObjectsViewportDocument CurrentObjectsViewport(AetheriaRuntimeRtsViewportBounds viewport) - { - return ObjectsViewport(viewport).Latest(); - } - - public CultMeshReactiveDocument ReactiveObjectsViewport( - AetheriaRuntimeRtsViewportBounds viewport) - { - return ObjectsViewport(viewport).Reactive(); - } - public CultMeshDocumentHandle GravityViewport( AetheriaRuntimeRtsViewportBounds viewport) { return _gravityViewport(viewport ?? new AetheriaRuntimeRtsViewportBounds()); } - public AetheriaRuntimeGravityViewportDocument CurrentGravityViewport(AetheriaRuntimeRtsViewportBounds viewport) - { - return GravityViewport(viewport).Latest(); - } - - public CultMeshReactiveDocument ReactiveGravityViewport( - AetheriaRuntimeRtsViewportBounds viewport) - { - return GravityViewport(viewport).Reactive(); - } - public CultMeshDocumentHandle RenderSplatsViewport( AetheriaRuntimeRtsViewportBounds viewport) { return _renderSplatsViewport(viewport ?? new AetheriaRuntimeRtsViewportBounds()); } - public AetheriaRuntimeRenderSplatsViewportDocument CurrentRenderSplatsViewport(AetheriaRuntimeRtsViewportBounds viewport) - { - return RenderSplatsViewport(viewport).Latest(); - } - - public CultMeshReactiveDocument ReactiveRenderSplatsViewport( - AetheriaRuntimeRtsViewportBounds viewport) - { - return RenderSplatsViewport(viewport).Reactive(); - } - public CultMeshDocumentHandle ZoneDetails(int zoneIndex) { if (zoneIndex < 0) @@ -454,14 +233,24 @@ namespace GameCult.Aetheria.State.Verse return _zoneDetails(zoneIndex); } - public AetheriaRuntimeZoneDetailsDocument CurrentZoneDetails(int zoneIndex) + public CultMeshDocumentHandle ZoneDetailsSurface(int zoneIndex) { - return ZoneDetails(zoneIndex).Latest(); + if (zoneIndex < 0) + throw new ArgumentOutOfRangeException(nameof(zoneIndex), zoneIndex, "Aetheria zone index must be non-negative."); + + return _zoneDetailsSurface(zoneIndex); } - public CultMeshReactiveDocument ReactiveZoneDetails(int zoneIndex) + public CultMeshDocumentHandle InventoryPanelSurface( + AetheriaRuntimeInventoryPanelSurfaceRequest request) { - return ZoneDetails(zoneIndex).Reactive(); + return _inventoryPanelSurface(request ?? new AetheriaRuntimeInventoryPanelSurfaceRequest()); + } + + public CultMeshDocumentHandle InventoryDropdownSurface( + AetheriaRuntimeInventoryDropdownSurfaceRequest request) + { + return _inventoryDropdownSurface(request ?? new AetheriaRuntimeInventoryDropdownSurfaceRequest()); } public CultMeshDocumentHandle SelectedObject(int zoneIndex) @@ -472,16 +261,6 @@ namespace GameCult.Aetheria.State.Verse return _selectedObject(zoneIndex); } - public AetheriaRuntimeSelectedObjectDocument CurrentSelectedObject(int zoneIndex) - { - return SelectedObject(zoneIndex).Latest(); - } - - public CultMeshReactiveDocument ReactiveSelectedObject(int zoneIndex) - { - return SelectedObject(zoneIndex).Reactive(); - } - public CultMeshDocumentHandle Inventory(int entityIndex) { if (entityIndex < 0) @@ -490,16 +269,6 @@ namespace GameCult.Aetheria.State.Verse return _inventory(entityIndex); } - public AetheriaRuntimeInventoryDocument CurrentInventory(int entityIndex) - { - return Inventory(entityIndex).Latest(); - } - - public CultMeshReactiveDocument ReactiveInventory(int entityIndex) - { - return Inventory(entityIndex).Reactive(); - } - public CultMeshDocumentHandle StarbridgePlayerSeat(string seatId) { if (string.IsNullOrWhiteSpace(seatId)) @@ -508,23 +277,12 @@ namespace GameCult.Aetheria.State.Verse return _starbridgePlayerSeat(seatId); } - public AetheriaRuntimeStarbridgePlayerSeatDocument CurrentStarbridgePlayerSeat(string seatId) - { - return StarbridgePlayerSeat(seatId).Latest(); - } - - public CultMeshReactiveDocument ReactiveStarbridgePlayerSeat( - string seatId) - { - return StarbridgePlayerSeat(seatId).Reactive(); - } - public CultMeshStateRefResolver CreateEveSurfaceCultMeshStateRefResolver() { - _eveStateRefFrame ??= ReactiveDaemonFrame(); - _eveStateRefHealth ??= ReactiveHealth(); - _eveStateRefCommandBoundary ??= ReactiveCommandBoundary(); - _eveStateRefCatalog ??= ReactiveCatalogSnapshot(); + _eveStateRefFrame ??= DaemonFrame.Reactive(); + _eveStateRefHealth ??= Health.Reactive(); + _eveStateRefCommandBoundary ??= CommandBoundary.Reactive(); + _eveStateRefCatalog ??= Catalog.Reactive(); return AetheriaRuntimeStateRefResolver.CreateEveSurfaceCultMeshStateRefResolver( () => _eveStateRefFrame.Current, diff --git a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeAssets.cs b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeAssets.cs index 81069d93e..cee8c6694 100644 --- a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeAssets.cs +++ b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeAssets.cs @@ -21,6 +21,8 @@ namespace GameCult.Aetheria.State.Verse Add(entries, MapIcon("body.planet", "Planet", "Sprites/Map/planet")); Add(entries, MapIcon("body.sun", "Sun", "Sprites/Map/sun")); Add(entries, MapIcon("body.asteroid", "Asteroid", "Sprites/Map/asteroid")); + foreach (var inventoryAsset in InventoryUiAssets()) + Add(entries, inventoryAsset); foreach (var item in catalog?.Items ?? Array.Empty()) { @@ -84,6 +86,49 @@ namespace GameCult.Aetheria.State.Verse "image/*"); } + public static AetheriaRuntimeAssetRef InventoryCellBackgroundAtlas() + { + return Texture( + "inventory.cell.background_atlas", + "Sprites/Flat UI/Nodes/Nodes8BG"); + } + + public static AetheriaRuntimeAssetRef InventoryCellForegroundAtlas() + { + return Texture( + "inventory.cell.foreground_atlas", + "Sprites/Flat UI/Nodes/Nodes8"); + } + + public static AetheriaRuntimeAssetRef InventoryThermalLayerAtlas() + { + return Texture( + "inventory.cell.thermal_layer_atlas", + "Sprites/Flat UI/pipes"); + } + + public static IReadOnlyList InventoryUiAssets() + { + return new[] + { + new AetheriaRuntimeAssetManifestEntry + { + Ref = InventoryCellBackgroundAtlas(), + Tags = new[] { "inventory", "cell", "background", "atlas" } + }, + new AetheriaRuntimeAssetManifestEntry + { + Ref = InventoryCellForegroundAtlas(), + Tags = new[] { "inventory", "cell", "foreground", "atlas" } + }, + new AetheriaRuntimeAssetManifestEntry + { + Ref = InventoryThermalLayerAtlas(), + Tags = new[] { "inventory", "thermal", "layer", "atlas" } + } + }; + } + private static AetheriaRuntimeAssetManifestEntry MapIcon( string key, string label, @@ -106,6 +151,16 @@ namespace GameCult.Aetheria.State.Verse "image/*"); } + private static AetheriaRuntimeAssetRef Texture(string key, string resourcePath) + { + return AetheriaRuntimeAssetRef.FromKey( + key, + AetheriaRuntimeAssetKinds.Texture, + $"resources://{NormalizeResourcePath(resourcePath)}", + AetheriaRuntimeAssetTransports.Resources, + "image/*"); + } + private static string NormalizeResourcePath(string path) { path ??= ""; diff --git a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeCultCacheDocumentStore.cs b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeCultCacheDocumentStore.cs index e9fa48039..850c42e2a 100644 --- a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeCultCacheDocumentStore.cs +++ b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeCultCacheDocumentStore.cs @@ -113,7 +113,7 @@ namespace GameCult.Aetheria.State.Verse new System.Collections.Generic.Dictionary(StringComparer.Ordinal), Array.Empty()); - writer.WriteArrayHeader(5); + writer.WriteArrayHeader(6); writer.Write(component.Id ?? ""); writer.Write(component.Kind ?? ""); writer.WriteMapHeader(component.Props?.Count ?? 0); @@ -141,6 +141,19 @@ namespace GameCult.Aetheria.State.Verse writer.Write(record.RouteKind); writer.Write(record.RouteDescription); } + + writer.WriteArrayHeader(component.EmbeddedDocuments?.Count ?? 0); + foreach (var slot in component.EmbeddedDocuments ?? Array.Empty()) + { + var route = slot.RouteHint ?? CultMeshRouteHint.Automatic; + writer.WriteArrayHeader(6); + writer.Write(slot.SlotId ?? ""); + writer.Write(slot.DocumentId ?? ""); + writer.Write(slot.SchemaId ?? ""); + writer.Write(slot.PresentationKind ?? ""); + writer.Write(route.Kind.ToString()); + writer.Write(route.Description ?? ""); + } } private static void WriteSurfaceCommands( @@ -241,12 +254,57 @@ namespace GameCult.Aetheria.State.Verse var props = ReadFieldStringMap(ref reader, componentFields, 2); var children = ReadFieldSurfaceComponents(ref reader, componentFields, 3); var stateBindings = ReadFieldSurfaceStateBindings(ref reader, componentFields, 4); - for (var field = 5; field < componentFields; field++) + var embeddedDocuments = ReadFieldEmbeddedDocumentSlots(ref reader, componentFields, 5); + for (var field = 6; field < componentFields; field++) { reader.Skip(); } - return new AetheriaRuntimeSurfaceComponent(id, kind, props, children, stateBindings); + return new AetheriaRuntimeSurfaceComponent(id, kind, props, children, stateBindings, embeddedDocuments); + } + + private static System.Collections.Generic.IReadOnlyList ReadFieldEmbeddedDocumentSlots( + ref MessagePackReader reader, + int fields, + int index) + { + if (index >= fields || reader.NextMessagePackType == MessagePackType.Nil) + { + if (index < fields) + reader.ReadNil(); + + return Array.Empty(); + } + + var count = reader.ReadArrayHeader(); + var slots = new AetheriaRuntimeEmbeddedDocumentSlot[count]; + for (var item = 0; item < count; item++) + { + var slotFields = reader.ReadArrayHeader(); + var slotId = ReadFieldString(ref reader, slotFields, 0, ""); + var documentId = ReadFieldString(ref reader, slotFields, 1, ""); + var schemaId = ReadFieldString(ref reader, slotFields, 2, ""); + var presentationKind = ReadFieldString(ref reader, slotFields, 3, ""); + var routeKind = ReadFieldString(ref reader, slotFields, 4, nameof(CultMeshLocalityKind.Automatic)); + var routeDescription = ReadFieldString(ref reader, slotFields, 5, ""); + for (var field = 6; field < slotFields; field++) + { + reader.Skip(); + } + + slots[item] = new AetheriaRuntimeEmbeddedDocumentSlot( + slotId, + documentId, + schemaId, + presentationKind, + new CultMeshRouteHint( + Enum.TryParse(routeKind, true, out var locality) + ? locality + : CultMeshLocalityKind.Automatic, + routeDescription)); + } + + return slots; } private static System.Collections.Generic.IReadOnlyList ReadFieldSurfaceStateBindings( diff --git a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeEveSurfaceAdapter.cs.meta b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeEveSurfaceAdapter.cs.meta deleted file mode 100644 index d1c9956ba..000000000 --- a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeEveSurfaceAdapter.cs.meta +++ /dev/null @@ -1,2 +0,0 @@ -fileFormatVersion: 2 -guid: 949c806607f976641b113e355f3fbaa0 \ No newline at end of file diff --git a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeInventoryDropdownSurfaceBuilder.cs b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeInventoryDropdownSurfaceBuilder.cs index 18ac9bc7b..c47f58d05 100644 --- a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeInventoryDropdownSurfaceBuilder.cs +++ b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeInventoryDropdownSurfaceBuilder.cs @@ -1,157 +1,11 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.Linq; using GameCult.Eve.Surface; namespace GameCult.Aetheria.State.Verse { - public sealed class AetheriaRuntimeInventoryDropdownOption - { - public AetheriaRuntimeInventoryDropdownOption(string id, string label, string command) - { - Id = id ?? ""; - Label = label ?? ""; - Command = command ?? ""; - } - - public string Id { get; } - public string Label { get; } - public string Command { get; } - public bool IsCommand => !string.IsNullOrWhiteSpace(Command); - } - - public sealed class AetheriaRuntimeInventoryDropdownGroup - { - public AetheriaRuntimeInventoryDropdownGroup( - string id, - string title, - IReadOnlyList options) - { - Id = id ?? ""; - Title = title ?? ""; - Options = options ?? Array.Empty(); - } - - public string Id { get; } - public string Title { get; } - public IReadOnlyList Options { get; } - } - - public sealed class AetheriaRuntimeInventoryDropdownEntityOption - { - public AetheriaRuntimeInventoryDropdownEntityOption( - int entityIndex, - string entityKey, - string name, - bool isDisplayed, - IReadOnlyList bays) - { - EntityIndex = entityIndex; - EntityKey = entityKey ?? ""; - Name = name ?? ""; - IsDisplayed = isDisplayed; - Bays = bays ?? Array.Empty(); - } - - public int EntityIndex { get; } - public string EntityKey { get; } - public string Name { get; } - public bool IsDisplayed { get; } - public IReadOnlyList Bays { get; } - } - - public sealed class AetheriaRuntimeInventoryDropdownBayOption - { - public AetheriaRuntimeInventoryDropdownBayOption(int bayIndex, string label, bool isDisplayed) - { - BayIndex = bayIndex; - Label = label ?? ""; - IsDisplayed = isDisplayed; - } - - public int BayIndex { get; } - public string Label { get; } - public bool IsDisplayed { get; } - } - - public sealed class AetheriaRuntimeInventoryDropdownLoadoutOption - { - public AetheriaRuntimeInventoryDropdownLoadoutOption( - int templateIndex, - string name, - string priceLabel, - bool canRestore) - { - TemplateIndex = templateIndex; - Name = name ?? ""; - PriceLabel = priceLabel ?? ""; - CanRestore = canRestore; - } - - public int TemplateIndex { get; } - public string Name { get; } - public string PriceLabel { get; } - public bool CanRestore { get; } - } - - public enum AetheriaRuntimeInventoryDropdownSelectionKind - { - Unknown = 0, - EntityEquipment = 1, - EntityBay = 2, - Entity = 3, - DockingBay = 4, - SaveLoadout = 5, - Loadout = 6 - } - - public readonly struct AetheriaRuntimeInventoryDropdownSelection - { - public AetheriaRuntimeInventoryDropdownSelection( - AetheriaRuntimeInventoryDropdownSelectionKind kind, - string command, - string entityKey = "", - int entityIndex = -1, - int bayIndex = -1, - int templateIndex = -1) - { - Kind = kind; - Command = command ?? ""; - EntityKey = entityKey ?? ""; - EntityIndex = entityIndex; - BayIndex = bayIndex; - TemplateIndex = templateIndex; - } - - public AetheriaRuntimeInventoryDropdownSelectionKind Kind { get; } - public string Command { get; } - public string EntityKey { get; } - public int EntityIndex { get; } - public int BayIndex { get; } - public int TemplateIndex { get; } - } - - public sealed class AetheriaRuntimeInventoryDropdownSurfaceModel - { - public AetheriaRuntimeInventoryDropdownSurfaceModel( - AetheriaRuntimeSurfaceDocument document, - IReadOnlyDictionary selections) - { - Document = document ?? throw new ArgumentNullException(nameof(document)); - Selections = selections ?? new Dictionary(StringComparer.Ordinal); - } - - public AetheriaRuntimeSurfaceDocument Document { get; } - public IReadOnlyDictionary Selections { get; } - - public bool TryResolve( - string command, - out AetheriaRuntimeInventoryDropdownSelection selection) - { - return Selections.TryGetValue(command ?? "", out selection); - } - } - public static class AetheriaRuntimeInventoryDropdownSurfaceBuilder { public const string SurfaceId = "aetheria.inventory.panel.dropdown"; @@ -179,7 +33,7 @@ namespace GameCult.Aetheria.State.Verse return $"{SurfaceId}.loadout_{templateIndex}"; } - public static AetheriaRuntimeInventoryDropdownSurfaceModel Build( + public static AetheriaRuntimeSurfaceDocument Build( string currentView, IEnumerable entities, bool hasDockingBay, @@ -190,7 +44,6 @@ namespace GameCult.Aetheria.State.Verse string updatedAtUtc) { var groups = new List(); - var selections = new Dictionary(StringComparer.Ordinal); foreach (var entity in entities ?? Array.Empty()) { @@ -205,12 +58,12 @@ namespace GameCult.Aetheria.State.Verse options.Add(new AetheriaRuntimeInventoryDropdownOption( $"{SurfaceId}.entity_{entity.EntityIndex}.equipment", "Equipment", - equipmentCommand)); - selections[equipmentCommand] = new AetheriaRuntimeInventoryDropdownSelection( - AetheriaRuntimeInventoryDropdownSelectionKind.EntityEquipment, equipmentCommand, - entityKey: entity.EntityKey, - entityIndex: entity.EntityIndex); + new AetheriaRuntimeInventoryDropdownSelection( + AetheriaRuntimeInventoryDropdownSelectionKind.EntityEquipment, + equipmentCommand, + entityKey: entity.EntityKey, + entityIndex: entity.EntityIndex))); } foreach (var bay in entity.Bays) @@ -222,13 +75,13 @@ namespace GameCult.Aetheria.State.Verse options.Add(new AetheriaRuntimeInventoryDropdownOption( $"{SurfaceId}.entity_{entity.EntityIndex}.bay_{bay.BayIndex}", string.IsNullOrWhiteSpace(bay.Label) ? $"Bay {bay.BayIndex + 1}" : bay.Label, - bayCommand)); - selections[bayCommand] = new AetheriaRuntimeInventoryDropdownSelection( - AetheriaRuntimeInventoryDropdownSelectionKind.EntityBay, bayCommand, - entityKey: entity.EntityKey, - entityIndex: entity.EntityIndex, - bayIndex: bay.BayIndex); + new AetheriaRuntimeInventoryDropdownSelection( + AetheriaRuntimeInventoryDropdownSelectionKind.EntityBay, + bayCommand, + entityKey: entity.EntityKey, + entityIndex: entity.EntityIndex, + bayIndex: bay.BayIndex))); } if (entity.Bays.Count == 0 && !entity.IsDisplayed) @@ -237,12 +90,12 @@ namespace GameCult.Aetheria.State.Verse options.Add(new AetheriaRuntimeInventoryDropdownOption( $"{SurfaceId}.entity_{entity.EntityIndex}.select", entity.Name, - entityCommand)); - selections[entityCommand] = new AetheriaRuntimeInventoryDropdownSelection( - AetheriaRuntimeInventoryDropdownSelectionKind.Entity, entityCommand, - entityKey: entity.EntityKey, - entityIndex: entity.EntityIndex); + new AetheriaRuntimeInventoryDropdownSelection( + AetheriaRuntimeInventoryDropdownSelectionKind.Entity, + entityCommand, + entityKey: entity.EntityKey, + entityIndex: entity.EntityIndex))); } if (options.Count > 0) @@ -264,11 +117,11 @@ namespace GameCult.Aetheria.State.Verse new AetheriaRuntimeInventoryDropdownOption( $"{SurfaceId}.dockingBay.select", dockingBayLabel, - DockingBay) + DockingBay, + new AetheriaRuntimeInventoryDropdownSelection( + AetheriaRuntimeInventoryDropdownSelectionKind.DockingBay, + DockingBay)) })); - selections[DockingBay] = new AetheriaRuntimeInventoryDropdownSelection( - AetheriaRuntimeInventoryDropdownSelectionKind.DockingBay, - DockingBay); } var loadoutOptions = new List(); @@ -277,10 +130,10 @@ namespace GameCult.Aetheria.State.Verse loadoutOptions.Add(new AetheriaRuntimeInventoryDropdownOption( $"{SurfaceId}.loadouts.save", "Save Loadout", - SaveLoadout)); - selections[SaveLoadout] = new AetheriaRuntimeInventoryDropdownSelection( - AetheriaRuntimeInventoryDropdownSelectionKind.SaveLoadout, - SaveLoadout); + SaveLoadout, + new AetheriaRuntimeInventoryDropdownSelection( + AetheriaRuntimeInventoryDropdownSelectionKind.SaveLoadout, + SaveLoadout))); } foreach (var loadout in loadouts ?? Array.Empty()) @@ -295,15 +148,13 @@ namespace GameCult.Aetheria.State.Verse loadoutOptions.Add(new AetheriaRuntimeInventoryDropdownOption( $"{SurfaceId}.loadouts.{(loadout.CanRestore ? "restore" : "locked")}_{loadout.TemplateIndex}", $"{loadout.Name} - {priceSuffix}{(loadout.CanRestore ? "" : " (unavailable)")}", - loadout.CanRestore ? loadoutCommand : "")); - - if (loadout.CanRestore) - { - selections[loadoutCommand] = new AetheriaRuntimeInventoryDropdownSelection( + loadout.CanRestore ? loadoutCommand : "", + loadout.CanRestore + ? new AetheriaRuntimeInventoryDropdownSelection( AetheriaRuntimeInventoryDropdownSelectionKind.Loadout, loadoutCommand, - templateIndex: loadout.TemplateIndex); - } + templateIndex: loadout.TemplateIndex) + : default)); } if (loadoutOptions.Count > 0) @@ -314,9 +165,53 @@ namespace GameCult.Aetheria.State.Verse loadoutOptions)); } - return new AetheriaRuntimeInventoryDropdownSurfaceModel( - Build(currentView, groups, updatedAtUtc), - selections); + return Build(currentView, groups, updatedAtUtc); + } + + public static AetheriaRuntimeSurfaceDocument BuildFromDocuments( + AetheriaRuntimeStationRefitDocument stationRefit, + AetheriaRuntimeInventoryDropdownSurfaceRequest request, + string updatedAtUtc) + { + request ??= new AetheriaRuntimeInventoryDropdownSurfaceRequest(); + stationRefit ??= new AetheriaRuntimeStationRefitDocument(); + var entities = (stationRefit.AvailableEntities ?? Array.Empty()) + .Select((entity, entityIndex) => new AetheriaRuntimeInventoryDropdownEntityOption( + entityIndex, + entity.EntityKey, + string.IsNullOrWhiteSpace(entity.DisplayName) ? $"Entity {entity.EntityIndex}" : entity.DisplayName, + IsDisplayedEntity(request, entity.EntityKey), + Enumerable.Range(0, Math.Max(entity.CargoBayCount, 0)) + .Select(bayIndex => new AetheriaRuntimeInventoryDropdownBayOption( + bayIndex, + $"Bay {bayIndex + 1}", + IsDisplayedCargoBay(request, entity.EntityKey, bayIndex))) + .ToArray())) + .ToArray(); + var loadouts = (stationRefit.LoadoutRestoreOptions ?? Array.Empty()) + .Select((loadout, optionIndex) => new AetheriaRuntimeInventoryDropdownLoadoutOption( + optionIndex, + loadout.TemplateName, + loadout.CanRestore ? $"{loadout.Price:n0}" : "", + loadout.CanRestore)) + .ToArray(); + var currentDockingBay = (stationRefit.DockingBays ?? Array.Empty()) + .FirstOrDefault(row => row != null && row.DockingBayIndex == stationRefit.DockingBayIndex); + var hasDockingBay = currentDockingBay != null && stationRefit.IsDocked; + + return Build( + request.CurrentView, + entities, + hasDockingBay, + hasDockingBay ? $"Docking Bay {currentDockingBay.DockingBayIndex + 1}" : "Docking Bay", + hasDockingBay && + IsDisplayedCargoBay( + request, + stationRefit.DockParentEntityKey, + currentDockingBay.DockingBayIndex), + request.CanSaveLoadout, + loadouts, + updatedAtUtc); } private static AetheriaRuntimeSurfaceDocument Build( @@ -386,7 +281,7 @@ namespace GameCult.Aetheria.State.Verse private static AetheriaRuntimeSurfaceComponent ToOptionComponent(AetheriaRuntimeInventoryDropdownOption option) { if (option.IsCommand) - return Button(option.Id, option.Label, option.Command); + return Button(option.Id, option.Label, option.Command, option.Selection); return Text(option.Id, option.Label); } @@ -401,6 +296,27 @@ namespace GameCult.Aetheria.State.Verse .ToArray()).Trim('-'); } + private static bool IsDisplayedEntity( + AetheriaRuntimeInventoryDropdownSurfaceRequest request, + string entityKey) + { + return request != null && + !string.IsNullOrWhiteSpace(request.DisplayedEntityKey) && + string.Equals(request.DisplayedEntityKey, entityKey ?? "", StringComparison.Ordinal); + } + + private static bool IsDisplayedCargoBay( + AetheriaRuntimeInventoryDropdownSurfaceRequest request, + string entityKey, + int cargoBayIndex) + { + return request != null && + cargoBayIndex >= 0 && + request.DisplayedCargoIndex == cargoBayIndex && + !string.IsNullOrWhiteSpace(request.DisplayedCargoEntityKey) && + string.Equals(request.DisplayedCargoEntityKey, entityKey ?? "", StringComparison.Ordinal); + } + private static AetheriaRuntimeSurfaceComponent Card( string id, string title, @@ -419,9 +335,28 @@ namespace GameCult.Aetheria.State.Verse return Node(id, "text", new[] { ("value", value ?? "") }); } - private static AetheriaRuntimeSurfaceComponent Button(string id, string label, string command) + private static AetheriaRuntimeSurfaceComponent Button( + string id, + string label, + string command, + AetheriaRuntimeInventoryDropdownSelection selection = default) { - return Node(id, "control.button", new[] { ("label", label ?? ""), ("command", command ?? "") }); + var props = new List<(string Key, string Value)> + { + ("label", label ?? ""), + ("command", command ?? "") + }; + + if (selection.Kind != AetheriaRuntimeInventoryDropdownSelectionKind.Unknown) + { + props.Add(("selectionKind", selection.Kind.ToString())); + props.Add(("entityKey", selection.EntityKey ?? "")); + props.Add(("entityIndex", selection.EntityIndex.ToString(CultureInfo.InvariantCulture))); + props.Add(("bayIndex", selection.BayIndex.ToString(CultureInfo.InvariantCulture))); + props.Add(("templateIndex", selection.TemplateIndex.ToString(CultureInfo.InvariantCulture))); + } + + return Node(id, "control.button", props); } private static AetheriaRuntimeSurfaceComponent ButtonRow( diff --git a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeInventoryDropdownSurfaceDocuments.cs b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeInventoryDropdownSurfaceDocuments.cs new file mode 100644 index 000000000..3269144bc --- /dev/null +++ b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeInventoryDropdownSurfaceDocuments.cs @@ -0,0 +1,166 @@ +using System; +using System.Collections.Generic; +using GameCult.Caching; +using MessagePack; + +namespace GameCult.Aetheria.State.Verse +{ + public sealed class AetheriaRuntimeInventoryDropdownOption + { + public AetheriaRuntimeInventoryDropdownOption( + string id, + string label, + string command, + AetheriaRuntimeInventoryDropdownSelection selection = default) + { + Id = id ?? ""; + Label = label ?? ""; + Command = command ?? ""; + Selection = selection; + } + + public string Id { get; } + public string Label { get; } + public string Command { get; } + public AetheriaRuntimeInventoryDropdownSelection Selection { get; } + public bool IsCommand => !string.IsNullOrWhiteSpace(Command); + } + + public sealed class AetheriaRuntimeInventoryDropdownGroup + { + public AetheriaRuntimeInventoryDropdownGroup( + string id, + string title, + IReadOnlyList options) + { + Id = id ?? ""; + Title = title ?? ""; + Options = options ?? Array.Empty(); + } + + public string Id { get; } + public string Title { get; } + public IReadOnlyList Options { get; } + } + + public sealed class AetheriaRuntimeInventoryDropdownEntityOption + { + public AetheriaRuntimeInventoryDropdownEntityOption( + int entityIndex, + string entityKey, + string name, + bool isDisplayed, + IReadOnlyList bays) + { + EntityIndex = entityIndex; + EntityKey = entityKey ?? ""; + Name = name ?? ""; + IsDisplayed = isDisplayed; + Bays = bays ?? Array.Empty(); + } + + public int EntityIndex { get; } + public string EntityKey { get; } + public string Name { get; } + public bool IsDisplayed { get; } + public IReadOnlyList Bays { get; } + } + + public sealed class AetheriaRuntimeInventoryDropdownBayOption + { + public AetheriaRuntimeInventoryDropdownBayOption(int bayIndex, string label, bool isDisplayed) + { + BayIndex = bayIndex; + Label = label ?? ""; + IsDisplayed = isDisplayed; + } + + public int BayIndex { get; } + public string Label { get; } + public bool IsDisplayed { get; } + } + + public sealed class AetheriaRuntimeInventoryDropdownLoadoutOption + { + public AetheriaRuntimeInventoryDropdownLoadoutOption( + int templateIndex, + string name, + string priceLabel, + bool canRestore) + { + TemplateIndex = templateIndex; + Name = name ?? ""; + PriceLabel = priceLabel ?? ""; + CanRestore = canRestore; + } + + public int TemplateIndex { get; } + public string Name { get; } + public string PriceLabel { get; } + public bool CanRestore { get; } + } + + public sealed class AetheriaRuntimeInventoryDropdownSurfaceRequest + { + public string CurrentView { get; set; } = ""; + + public string DisplayedEntityKey { get; set; } = ""; + + public string DisplayedCargoEntityKey { get; set; } = ""; + + public int DisplayedCargoIndex { get; set; } = -1; + + public bool CanSaveLoadout { get; set; } + } + + public enum AetheriaRuntimeInventoryDropdownSelectionKind + { + Unknown = 0, + EntityEquipment = 1, + EntityBay = 2, + Entity = 3, + DockingBay = 4, + SaveLoadout = 5, + Loadout = 6 + } + + [MessagePackObject] + public readonly struct AetheriaRuntimeInventoryDropdownSelection + { + [SerializationConstructor] + public AetheriaRuntimeInventoryDropdownSelection( + AetheriaRuntimeInventoryDropdownSelectionKind kind, + string command, + string entityKey = "", + int entityIndex = -1, + int bayIndex = -1, + int templateIndex = -1) + { + Kind = kind; + Command = command ?? ""; + EntityKey = entityKey ?? ""; + EntityIndex = entityIndex; + BayIndex = bayIndex; + TemplateIndex = templateIndex; + } + + [Key(0)] + public AetheriaRuntimeInventoryDropdownSelectionKind Kind { get; } + + [Key(1)] + public string Command { get; } + + [Key(2)] + public string EntityKey { get; } + + [Key(3)] + public int EntityIndex { get; } + + [Key(4)] + public int BayIndex { get; } + + [Key(5)] + public int TemplateIndex { get; } + } + +} diff --git a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeInventoryDropdownSurfaceDocuments.cs.meta b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeInventoryDropdownSurfaceDocuments.cs.meta new file mode 100644 index 000000000..04d15f515 --- /dev/null +++ b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeInventoryDropdownSurfaceDocuments.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 2aa286ddcdb331c48a61dcd15cc93985 \ No newline at end of file diff --git a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeInventoryPanelSurfaceBuilder.cs b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeInventoryPanelSurfaceBuilder.cs new file mode 100644 index 000000000..0652b9ead --- /dev/null +++ b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeInventoryPanelSurfaceBuilder.cs @@ -0,0 +1,387 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; + +namespace GameCult.Aetheria.State.Verse +{ + public sealed class AetheriaRuntimeInventoryPanelSurfaceRequest + { + public string ViewTitle { get; set; } = ""; + + public string DisplayedEntityKey { get; set; } = ""; + + public int DisplayedEntityIndex { get; set; } = -1; + + public string DisplayedCargoEntityKey { get; set; } = ""; + + public int DisplayedCargoEntityIndex { get; set; } = -1; + + public int DisplayedCargoIndex { get; set; } = -1; + + public bool ThermalView { get; set; } + + public bool HudView { get; set; } + + public bool HasDragSession { get; set; } + + public string DragItemKey { get; set; } = ""; + + public string DragSourceKind { get; set; } = ""; + + public string DragSourceEntityKey { get; set; } = ""; + + public int DragSourceIndex { get; set; } = -1; + + public int DragOriginOffsetX { get; set; } + + public int DragOriginOffsetY { get; set; } + + public string DragRotation { get; set; } = ""; + + public int HoverCellX { get; set; } = -1; + + public int HoverCellY { get; set; } = -1; + } + + public static class AetheriaRuntimeInventoryPanelSurfaceBuilder + { + public const string SurfaceId = "aetheria.inventory.panel"; + public const string ToggleThermal = "aetheria.inventory.panel.toggle_thermal"; + public const string SetCurrent = "aetheria.inventory.panel.set_current"; + public const string EditName = "aetheria.inventory.panel.edit_name"; + public const string OpenNavigation = "aetheria.inventory.panel.open_navigation"; + public const string DropdownSlotId = "aetheria.inventory.panel.dropdown.slot"; + + public static AetheriaRuntimeSurfaceDocument BuildFromDocuments( + AetheriaRuntimeCurrentEntityDocument currentEntity, + AetheriaRuntimeStationRefitDocument stationRefit, + AetheriaRuntimeInventoryDocument displayedInventory, + AetheriaRuntimeCatalogSnapshot catalog, + AetheriaRuntimePlayerSettingsDocument playerSettings, + AetheriaRuntimeInventoryPanelSurfaceRequest request, + string dropdownSurfaceDocumentId, + string updatedAtUtc, + long version = 1) + { + request ??= new AetheriaRuntimeInventoryPanelSurfaceRequest(); + var viewTitle = ResolveViewTitle(request, displayedInventory); + var isEquipmentView = !string.IsNullOrWhiteSpace(request.DisplayedEntityKey); + var isCargoView = !string.IsNullOrWhiteSpace(request.DisplayedCargoEntityKey) && + request.DisplayedCargoIndex >= 0; + var items = ResolveItems(displayedInventory, request, isEquipmentView, isCargoView); + var children = new List + { + Card( + $"{SurfaceId}.summary", + viewTitle, + Metric($"{SurfaceId}.summary.mode", "Mode", request.ThermalView ? "Thermal" : "Inventory"), + Metric($"{SurfaceId}.summary.entity", "Entity", ResolveEntityName(stationRefit, request)), + Metric($"{SurfaceId}.summary.current", "Current Ship", IsCurrentEntity(currentEntity, request) ? "Yes" : "No"), + Metric($"{SurfaceId}.summary.drag", "Drag", request.HasDragSession ? "Active" : "None")) + }; + + children.Add(ButtonRow( + $"{SurfaceId}.actions", + Button($"{SurfaceId}.actions.navigate", "Navigate", OpenNavigation), + Button($"{SurfaceId}.actions.thermal", request.ThermalView ? "Inventory" : "Thermal", ToggleThermal), + Button($"{SurfaceId}.actions.current", "Set Current", SetCurrent), + Button($"{SurfaceId}.actions.rename", "Rename", EditName))); + + children.Add(DragSession($"{SurfaceId}.drag", request)); + children.Add(EmbeddedDropdownSlot($"{SurfaceId}.dropdown", dropdownSurfaceDocumentId)); + children.Add(Grid($"{SurfaceId}.grid", items, catalog, playerSettings)); + + return new AetheriaRuntimeSurfaceDocument( + providerId: "aetheria", + providerKind: "inventory.panel", + title: viewTitle, + version: version, + updatedAtUtc: updatedAtUtc ?? "", + surface: new AetheriaRuntimeSurfaceTree( + SurfaceId, + Node( + $"{SurfaceId}.root", + "surface", + Array.Empty<(string Key, string Value)>(), + children.ToArray()), + Array.Empty()), + commands: new[] + { + new AetheriaRuntimeSurfaceCommandTemplate(OpenNavigation, "Navigate", AetheriaRuntimeSurfaceCommandTemplate.CultMeshTransport), + new AetheriaRuntimeSurfaceCommandTemplate(ToggleThermal, request.ThermalView ? "Inventory" : "Thermal", AetheriaRuntimeSurfaceCommandTemplate.CultMeshTransport), + new AetheriaRuntimeSurfaceCommandTemplate(SetCurrent, "Set Current", AetheriaRuntimeSurfaceCommandTemplate.CultMeshTransport), + new AetheriaRuntimeSurfaceCommandTemplate(EditName, "Rename", AetheriaRuntimeSurfaceCommandTemplate.CultMeshTransport) + }); + } + + private static IReadOnlyList ResolveItems( + AetheriaRuntimeInventoryDocument displayedInventory, + AetheriaRuntimeInventoryPanelSurfaceRequest request, + bool isEquipmentView, + bool isCargoView) + { + if (displayedInventory == null) + return Array.Empty(); + + if (isEquipmentView) + return displayedInventory.Equipment ?? Array.Empty(); + + if (isCargoView) + { + return (displayedInventory.Cargo ?? Array.Empty()) + .Where(item => item != null && item.SourceIndex == request.DisplayedCargoIndex) + .ToArray(); + } + + return Array.Empty(); + } + + private static AetheriaRuntimeSurfaceComponent Grid( + string id, + IReadOnlyList items, + AetheriaRuntimeCatalogSnapshot catalog, + AetheriaRuntimePlayerSettingsDocument playerSettings) + { + var props = new List<(string Key, string Value)> + { + ("count", "0"), + ("cellSpriteMode", "bitmask8"), + ("cellSpriteColumns", "16"), + ("cellSpriteRows", "16") + }; + AddAssetProps(props, "backgroundAtlas", AetheriaRuntimeAssets.InventoryCellBackgroundAtlas()); + AddAssetProps(props, "foregroundAtlas", AetheriaRuntimeAssets.InventoryCellForegroundAtlas()); + AddAssetProps(props, "thermalLayer", AetheriaRuntimeAssets.InventoryThermalLayerAtlas()); + + var cells = (items ?? Array.Empty()) + .Where(item => item != null) + .OrderBy(item => item.SourceIndex) + .ThenBy(item => item.X) + .ThenBy(item => item.Y) + .Select(item => ItemCell(id, item, catalog, playerSettings)) + .ToArray(); + props[0] = ("count", cells.Length.ToString(CultureInfo.InvariantCulture)); + return Node( + id, + "inventory.grid", + props, + cells); + } + + private static AetheriaRuntimeSurfaceComponent DragSession( + string id, + AetheriaRuntimeInventoryPanelSurfaceRequest request) + { + return Node( + id, + "inventory.drag_session", + new[] + { + ("active", request.HasDragSession ? "true" : "false"), + ("itemKey", request.DragItemKey ?? ""), + ("sourceKind", request.DragSourceKind ?? ""), + ("sourceEntityKey", request.DragSourceEntityKey ?? ""), + ("sourceIndex", request.DragSourceIndex.ToString(CultureInfo.InvariantCulture)), + ("originOffsetX", request.DragOriginOffsetX.ToString(CultureInfo.InvariantCulture)), + ("originOffsetY", request.DragOriginOffsetY.ToString(CultureInfo.InvariantCulture)), + ("rotation", request.DragRotation ?? ""), + ("hoverCellX", request.HoverCellX.ToString(CultureInfo.InvariantCulture)), + ("hoverCellY", request.HoverCellY.ToString(CultureInfo.InvariantCulture)) + }); + } + + private static AetheriaRuntimeSurfaceComponent EmbeddedDropdownSlot( + string id, + string dropdownSurfaceDocumentId) + { + return Node( + id, + "surface.slot", + new[] + { + ("slotId", DropdownSlotId), + ("documentId", dropdownSurfaceDocumentId ?? ""), + ("schemaId", "gamecult.aetheria.runtime_surface.v1"), + ("presentationKind", "inventory.dropdown") + }, + Array.Empty(), + new[] + { + new AetheriaRuntimeEmbeddedDocumentSlot( + DropdownSlotId, + dropdownSurfaceDocumentId ?? "", + "gamecult.aetheria.runtime_surface.v1", + "inventory.dropdown") + }); + } + + private static AetheriaRuntimeSurfaceComponent ItemCell( + string gridId, + AetheriaRuntimeRtsInventoryItem item, + AetheriaRuntimeCatalogSnapshot catalog, + AetheriaRuntimePlayerSettingsDocument playerSettings) + { + var typedItem = catalog?.FindItem(item.ItemKey ?? ""); + var props = new List<(string Key, string Value)> + { + ("itemKey", item.ItemKey ?? ""), + ("label", string.IsNullOrWhiteSpace(typedItem?.Name) ? item.ItemKey ?? "" : typedItem.Name), + ("source", item.Source ?? ""), + ("sourceIndex", item.SourceIndex.ToString(CultureInfo.InvariantCulture)), + ("x", item.X.ToString(CultureInfo.InvariantCulture)), + ("y", item.Y.ToString(CultureInfo.InvariantCulture)), + ("quantity", item.Quantity.ToString(CultureInfo.InvariantCulture)), + ("quality", FormatValue(item.Quality, playerSettings)), + ("durability", FormatValue(item.Durability, playerSettings)) + }; + AddAssetProps(props, "icon", ResolveItemIconAsset(item, typedItem)); + return Node( + $"{gridId}.item.{SafeId(item.ItemKey)}.{item.SourceIndex}.{item.X}.{item.Y}", + "inventory.item", + props); + } + + private static AetheriaRuntimeAssetRef ResolveItemIconAsset( + AetheriaRuntimeRtsInventoryItem item, + AetheriaRuntimeCatalogItem typedItem) + { + if (!string.IsNullOrWhiteSpace(item?.IconAsset?.AssetKey)) + return item.IconAsset; + + return AetheriaRuntimeAssets.AssetRefFromCatalogIcon( + typedItem?.ActionBarIcon, + $"item.{item?.ItemKey ?? ""}.icon"); + } + + private static void AddAssetProps( + List<(string Key, string Value)> props, + string prefix, + AetheriaRuntimeAssetRef asset) + { + if (props == null || string.IsNullOrWhiteSpace(prefix) || asset == null) + return; + + props.Add(($"{prefix}AssetKey", asset.AssetKey ?? "")); + props.Add(($"{prefix}AssetKind", asset.Kind ?? "")); + props.Add(($"{prefix}AssetUri", asset.Uri ?? "")); + props.Add(($"{prefix}AssetTransport", asset.Transport ?? "")); + props.Add(($"{prefix}AssetMimeType", asset.MimeType ?? "")); + props.Add(($"{prefix}AssetContentHash", asset.ContentHash ?? "")); + } + + private static string ResolveViewTitle( + AetheriaRuntimeInventoryPanelSurfaceRequest request, + AetheriaRuntimeInventoryDocument displayedInventory) + { + if (!string.IsNullOrWhiteSpace(request?.ViewTitle)) + return request.ViewTitle; + + if (!string.IsNullOrWhiteSpace(displayedInventory?.EntityKey)) + return displayedInventory.EntityKey; + + return "Inventory"; + } + + private static string ResolveEntityName( + AetheriaRuntimeStationRefitDocument stationRefit, + AetheriaRuntimeInventoryPanelSurfaceRequest request) + { + var entityKey = string.IsNullOrWhiteSpace(request?.DisplayedEntityKey) + ? request?.DisplayedCargoEntityKey ?? "" + : request.DisplayedEntityKey; + var entity = (stationRefit?.AvailableEntities ?? Array.Empty()) + .FirstOrDefault(option => string.Equals(option?.EntityKey ?? "", entityKey, StringComparison.Ordinal)); + return string.IsNullOrWhiteSpace(entity?.DisplayName) ? entityKey : entity.DisplayName; + } + + private static bool IsCurrentEntity( + AetheriaRuntimeCurrentEntityDocument currentEntity, + AetheriaRuntimeInventoryPanelSurfaceRequest request) + { + return currentEntity != null && + !string.IsNullOrWhiteSpace(request?.DisplayedEntityKey) && + string.Equals(currentEntity.EntityKey ?? "", request.DisplayedEntityKey, StringComparison.Ordinal); + } + + private static string FormatValue( + double value, + AetheriaRuntimePlayerSettingsDocument playerSettings) + { + var digits = playerSettings?.SignificantDigits ?? 3; + var magnitude = value == 0 ? 0 : (int)Math.Floor(Math.Log10(Math.Abs(value))) + 1; + digits -= magnitude; + if (digits < 0) + digits = 0; + + var formatted = value.ToString($"N{digits}", CultureInfo.CurrentCulture); + var separator = CultureInfo.CurrentCulture.NumberFormat.NumberDecimalSeparator; + return formatted.Contains(separator) + ? formatted.TrimEnd('0').TrimEnd(Convert.ToChar(separator)) + : formatted; + } + + private static AetheriaRuntimeSurfaceComponent Card( + string id, + string title, + params AetheriaRuntimeSurfaceComponent[] children) + { + return Node(id, "card", new[] { ("title", title ?? "") }, children); + } + + private static AetheriaRuntimeSurfaceComponent Metric(string id, string label, string value) + { + return Node(id, "metric", new[] { ("label", label ?? ""), ("value", value ?? "") }); + } + + private static AetheriaRuntimeSurfaceComponent Button(string id, string label, string command) + { + return Node(id, "control.button", new[] { ("label", label ?? ""), ("command", command ?? "") }); + } + + private static AetheriaRuntimeSurfaceComponent ButtonRow( + string id, + params AetheriaRuntimeSurfaceComponent[] children) + { + return Node(id, "row", Array.Empty<(string Key, string Value)>(), children); + } + + private static AetheriaRuntimeSurfaceComponent Node( + string id, + string kind, + IEnumerable<(string Key, string Value)> props, + params AetheriaRuntimeSurfaceComponent[] children) + { + return Node(id, kind, props, children, Array.Empty()); + } + + private static AetheriaRuntimeSurfaceComponent Node( + string id, + string kind, + IEnumerable<(string Key, string Value)> props, + IReadOnlyList children, + IReadOnlyList embeddedDocuments) + { + return new AetheriaRuntimeSurfaceComponent( + id ?? "", + kind ?? "", + (props ?? Array.Empty<(string Key, string Value)>()) + .ToDictionary(prop => prop.Key, prop => prop.Value ?? "", StringComparer.Ordinal), + children ?? Array.Empty(), + AetheriaRuntimeSurfaceStateBindings.FromProps( + (props ?? Array.Empty<(string Key, string Value)>()) + .ToDictionary(prop => prop.Key, prop => prop.Value ?? "", StringComparer.Ordinal)), + embeddedDocuments ?? Array.Empty()); + } + + private static string SafeId(string value) + { + if (string.IsNullOrWhiteSpace(value)) + return "empty"; + + return new string(value + .Select(character => char.IsLetterOrDigit(character) ? char.ToLowerInvariant(character) : '-') + .ToArray()).Trim('-'); + } + } +} diff --git a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeInventoryPanelSurfaceBuilder.cs.meta b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeInventoryPanelSurfaceBuilder.cs.meta new file mode 100644 index 000000000..619a00f6b --- /dev/null +++ b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeInventoryPanelSurfaceBuilder.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 004c7f1c9c55dc240a18bacfd9af2caf \ No newline at end of file diff --git a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeMainMenuSurfaceBuilder.cs b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeMainMenuSurfaceBuilder.cs index 4aba923d5..03c234786 100644 --- a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeMainMenuSurfaceBuilder.cs +++ b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeMainMenuSurfaceBuilder.cs @@ -149,64 +149,16 @@ namespace GameCult.Aetheria.State.Verse { var commands = new List(); var actionButtons = new List(); - var cardChildren = new List(); - targetKind = string.IsNullOrWhiteSpace(targetKind) ? "unknown" : targetKind; - targetSource = string.IsNullOrWhiteSpace(targetSource) ? "unknown" : targetSource; - verseVisibility = string.IsNullOrWhiteSpace(verseVisibility) ? "unknown" : verseVisibility; - var verseMeshAddress = string.IsNullOrWhiteSpace(verseCultMeshAddress) ? "unpublished" : verseCultMeshAddress; - var verseLabel = VerseLabel(verseTitle, verseId); if (!inGame) { - if (!hasAuthoritativeDaemonFrame) - { - cardChildren.Add(Text( - $"{AetheriaRuntimeMainMenuCommands.RootSurfaceId}.continue.note", - $"No authoritative daemon frame is available in {verseLabel} yet. Start or connect a daemon before opening the observer scene.")); - } - else - { - commands.Add(Command(AetheriaRuntimeMainMenuCommands.ContinueRun, "Continue")); - actionButtons.Add(Button( - $"{AetheriaRuntimeMainMenuCommands.RootSurfaceId}.continue", - "Continue", - AetheriaRuntimeMainMenuCommands.ContinueRun)); - cardChildren.Add(Metric( - $"{AetheriaRuntimeMainMenuCommands.RootSurfaceId}.continue.run", - "Daemon Run", - daemonRunId)); - cardChildren.Add(Metric( - $"{AetheriaRuntimeMainMenuCommands.RootSurfaceId}.continue.frame", - "Daemon Frame", - daemonFrameId.ToString())); - } + commands.Add(Command(AetheriaRuntimeMainMenuCommands.ContinueRun, "Continue")); + actionButtons.Add(Button( + $"{AetheriaRuntimeMainMenuCommands.RootSurfaceId}.continue", + "Continue", + AetheriaRuntimeMainMenuCommands.ContinueRun)); } - cardChildren.Add(Metric( - $"{AetheriaRuntimeMainMenuCommands.RootSurfaceId}.target.title", - "Client Target", - targetLabel)); - cardChildren.Add(Metric( - $"{AetheriaRuntimeMainMenuCommands.RootSurfaceId}.target.transport", - "Transport", - targetKind)); - cardChildren.Add(Metric( - $"{AetheriaRuntimeMainMenuCommands.RootSurfaceId}.target.source", - "Target Source", - targetSource)); - cardChildren.Add(Metric( - $"{AetheriaRuntimeMainMenuCommands.RootSurfaceId}.verse.title", - "Verse", - verseLabel)); - cardChildren.Add(Metric( - $"{AetheriaRuntimeMainMenuCommands.RootSurfaceId}.verse.visibility", - "Visibility", - verseVisibility)); - cardChildren.Add(Metric( - $"{AetheriaRuntimeMainMenuCommands.RootSurfaceId}.verse.mesh", - "CultMesh", - verseMeshAddress)); - commands.Add(Command(AetheriaRuntimeMainMenuCommands.NewGame, "New Game")); commands.Add(Command(AetheriaRuntimeMainMenuCommands.ShowSettings, "Settings")); commands.Add(Command(AetheriaRuntimeMainMenuCommands.Quit, "Quit")); @@ -215,21 +167,15 @@ namespace GameCult.Aetheria.State.Verse actionButtons.Add(Button($"{AetheriaRuntimeMainMenuCommands.RootSurfaceId}.settings", "Settings", AetheriaRuntimeMainMenuCommands.ShowSettings)); actionButtons.Add(Button($"{AetheriaRuntimeMainMenuCommands.RootSurfaceId}.quit", "Quit", AetheriaRuntimeMainMenuCommands.Quit)); - cardChildren.Add(Text( - $"{AetheriaRuntimeMainMenuCommands.RootSurfaceId}.note", - $"The client lowers this shell through Eve. The client target chooses which Verse it follows; game truth belongs to the daemon serving {verseLabel}.")); - cardChildren.Add(ButtonColumn($"{AetheriaRuntimeMainMenuCommands.RootSurfaceId}.actions", actionButtons.ToArray())); - return BuildSurfaceDocument( AetheriaRuntimeMainMenuCommands.RootSurfaceId, "Aetheria Terminus", updatedAtUtc, version, commands, - Card( - $"{AetheriaRuntimeMainMenuCommands.RootSurfaceId}.card", - "Aetheria Terminus", - cardChildren.ToArray())); + Text($"{AetheriaRuntimeMainMenuCommands.RootSurfaceId}.title", "AETHERIA", "text.title"), + Text($"{AetheriaRuntimeMainMenuCommands.RootSurfaceId}.subtitle", "TERMINUS", "text.subtitle"), + ButtonColumn($"{AetheriaRuntimeMainMenuCommands.RootSurfaceId}.actions", actionButtons.ToArray())); } public static AetheriaRuntimeSurfaceDocument BuildSettings( @@ -248,18 +194,13 @@ namespace GameCult.Aetheria.State.Verse Command(AetheriaRuntimeMainMenuCommands.ShowInputSettings, "Input"), Command(AetheriaRuntimeMainMenuCommands.BackToMain, "Back") }, - Card( - "aetheria.mainMenu.settings.card", - "Settings", - Text( - "aetheria.mainMenu.settings.note", - "Player settings, client target selection, and input rebinding all lower through typed Eve surfaces. Audio still has no typed owner."), - ButtonRow( - "aetheria.mainMenu.settings.actions", - Button("aetheria.mainMenu.settings.playerSettings", "Player Settings", AetheriaRuntimeMainMenuCommands.ShowPlayerSettings), - Button("aetheria.mainMenu.settings.verse", "Verse", AetheriaRuntimeMainMenuCommands.ShowVerseSettings), - Button("aetheria.mainMenu.settings.input", "Input", AetheriaRuntimeMainMenuCommands.ShowInputSettings), - Button("aetheria.mainMenu.settings.back", "Back", AetheriaRuntimeMainMenuCommands.BackToMain)))); + Text("aetheria.mainMenu.settings.title", "SETTINGS", "text.title"), + ButtonColumn( + "aetheria.mainMenu.settings.actions", + Button("aetheria.mainMenu.settings.playerSettings", "Player Settings", AetheriaRuntimeMainMenuCommands.ShowPlayerSettings), + Button("aetheria.mainMenu.settings.verse", "Verse", AetheriaRuntimeMainMenuCommands.ShowVerseSettings), + Button("aetheria.mainMenu.settings.input", "Input", AetheriaRuntimeMainMenuCommands.ShowInputSettings), + Button("aetheria.mainMenu.settings.back", "Back", AetheriaRuntimeMainMenuCommands.BackToMain))); } private static AetheriaRuntimeSurfaceDocument BuildInputSettings( @@ -275,8 +216,9 @@ namespace GameCult.Aetheria.State.Verse Command(AetheriaRuntimeMainMenuCommands.BackToSettings, "Back") }; - var cardChildren = new List + var children = new List { + Text("aetheria.mainMenu.input.title", "INPUT", "text.title"), Metric( "aetheria.mainMenu.input.bindingOverrides", "Binding Overrides", @@ -290,19 +232,19 @@ namespace GameCult.Aetheria.State.Verse if (canOpenRuntimeInputScreen) { commands.Insert(0, Command(AetheriaRuntimeMainMenuCommands.OpenRuntimeInputScreen, "Open Remap Screen")); - cardChildren.Add(Text( + children.Add(Text( "aetheria.mainMenu.input.note", "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.")); } else if (inGame) { - cardChildren.Add(Text( + children.Add(Text( "aetheria.mainMenu.input.note", "The runtime Eve input screen should own rebinding here, but this scene has no active input surface to hand off to.")); } else { - cardChildren.Add(Text( + children.Add(Text( "aetheria.mainMenu.input.note", "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.")); } @@ -317,7 +259,7 @@ namespace GameCult.Aetheria.State.Verse } buttons.Add(Button("aetheria.mainMenu.input.back", "Back", AetheriaRuntimeMainMenuCommands.BackToSettings)); - cardChildren.Add(ButtonRow("aetheria.mainMenu.input.actions", buttons.ToArray())); + children.Add(ButtonColumn("aetheria.mainMenu.input.actions", buttons.ToArray())); return BuildSurfaceDocument( AetheriaRuntimeMainMenuCommands.InputSettingsSurfaceId, @@ -325,10 +267,7 @@ namespace GameCult.Aetheria.State.Verse updatedAtUtc, version, commands, - Card( - "aetheria.mainMenu.input.card", - "Input Settings", - cardChildren.ToArray())); + children.ToArray()); } public static AetheriaRuntimeSurfaceDocument BuildVerseSettingsShell( @@ -420,7 +359,12 @@ namespace GameCult.Aetheria.State.Verse private static AetheriaRuntimeSurfaceComponent Text(string id, string value) { - return Node(id, "text", new[] { ("value", value ?? "") }); + return Text(id, value, "text"); + } + + private static AetheriaRuntimeSurfaceComponent Text(string id, string value, string kind) + { + return Node(id, kind, new[] { ("value", value ?? "") }); } private static AetheriaRuntimeSurfaceComponent Button(string id, string label, string command) diff --git a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimePlayerSettingsSurfaceBuilder.cs b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimePlayerSettingsSurfaceBuilder.cs index 23e6487aa..c52815919 100644 --- a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimePlayerSettingsSurfaceBuilder.cs +++ b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimePlayerSettingsSurfaceBuilder.cs @@ -5,96 +5,6 @@ using GameCult.Mesh; namespace GameCult.Aetheria.State.Verse { - public sealed class AetheriaRuntimeSurfaceDocument - { - public AetheriaRuntimeSurfaceDocument( - string providerId, - string providerKind, - string title, - long version, - string updatedAtUtc, - AetheriaRuntimeSurfaceTree surface, - IReadOnlyList commands) - { - ProviderId = providerId ?? ""; - ProviderKind = providerKind ?? ""; - Title = title ?? ""; - Version = version; - UpdatedAtUtc = updatedAtUtc ?? ""; - Surface = surface ?? throw new ArgumentNullException(nameof(surface)); - Commands = commands ?? Array.Empty(); - } - - public string ProviderId { get; } - - public string ProviderKind { get; } - - public string Title { get; } - - public long Version { get; } - - public string UpdatedAtUtc { get; } - - public AetheriaRuntimeSurfaceTree Surface { get; } - - public IReadOnlyList Commands { get; } - } - - public sealed class AetheriaRuntimeSurfaceTree - { - public AetheriaRuntimeSurfaceTree( - string id, - AetheriaRuntimeSurfaceComponent root, - IReadOnlyList styles) - { - Id = id ?? ""; - Root = root ?? throw new ArgumentNullException(nameof(root)); - Styles = styles ?? Array.Empty(); - } - - public string Id { get; } - - public AetheriaRuntimeSurfaceComponent Root { get; } - - public IReadOnlyList Styles { get; } - } - - public sealed class AetheriaRuntimeSurfaceComponent - { - public AetheriaRuntimeSurfaceComponent( - string id, - string kind, - IReadOnlyDictionary props, - IReadOnlyList children) - : this(id, kind, props, children, AetheriaRuntimeSurfaceStateBindings.FromProps(props)) - { - } - - public AetheriaRuntimeSurfaceComponent( - string id, - string kind, - IReadOnlyDictionary props, - IReadOnlyList children, - IReadOnlyList stateBindings) - { - Id = id ?? ""; - Kind = kind ?? ""; - Props = props ?? new Dictionary(StringComparer.Ordinal); - Children = children ?? Array.Empty(); - StateBindings = stateBindings ?? Array.Empty(); - } - - public string Id { get; } - - public string Kind { get; } - - public IReadOnlyDictionary Props { get; } - - public IReadOnlyList Children { get; } - - public IReadOnlyList StateBindings { get; } - } - public static class AetheriaRuntimeSurfaceStateRefs { public const string Source = "stateRef"; @@ -226,46 +136,6 @@ namespace GameCult.Aetheria.State.Verse } } - public sealed class AetheriaRuntimeSurfaceStyleToken - { - public AetheriaRuntimeSurfaceStyleToken(string name, string value) - { - Name = name ?? ""; - Value = value ?? ""; - } - - public string Name { get; } - - public string Value { get; } - } - - public sealed class AetheriaRuntimeSurfaceCommandTemplate - { - public const string CultMeshTransport = "cultmesh"; - - public AetheriaRuntimeSurfaceCommandTemplate(string command, string label, string transport) - : this(new CultMeshOperationBindingDescriptor( - command, - label, - "", - new CultMeshRouteHint(CultMeshLocalityKind.Automatic, transport))) - { - } - - public AetheriaRuntimeSurfaceCommandTemplate(CultMeshOperationBindingDescriptor operation) - { - Operation = operation ?? throw new ArgumentNullException(nameof(operation)); - } - - public CultMeshOperationBindingDescriptor Operation { get; } - - public string Command => Operation.OperationId; - - public string Label => Operation.Label; - - public string Transport => Operation.RouteHint.Description ?? ""; - } - public static class AetheriaRuntimePlayerSettingsSurfaceBuilder { public static AetheriaRuntimeSurfaceDocument Build( diff --git a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeRtsDocuments.cs b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeRtsDocuments.cs index edc495446..a50d62e88 100644 --- a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeRtsDocuments.cs +++ b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeRtsDocuments.cs @@ -491,7 +491,7 @@ namespace GameCult.Aetheria.State.Verse .Where(entity => entity != null && entity.TargetEntityIndex >= 0 && entityMap.ContainsKey(entity.TargetEntityIndex)) - .Select(entity => ProjectZoneTargetRow(entity, entityMap[entity.TargetEntityIndex])) + .Select(entity => ToZoneTargetRow(entity, entityMap[entity.TargetEntityIndex])) .ToArray(), Contacts = entities .Where(entity => entity != null) @@ -524,14 +524,14 @@ namespace GameCult.Aetheria.State.Verse : StationRefitEntities(context, parent, currentEntityIndex); var stationStock = parent == null ? Array.Empty() - : ProjectStationStock(parent); + : StationStock(parent); var dockingBays = parent == null ? Array.Empty() : ProjectStationDockingBays(context, parent, currentEntityIndex); var cargoTargets = parent == null ? Array.Empty() : ProjectStationCargoTargets(parentKey, dockingBayIndex, dockingBays, availableEntities); - stationStock = ProjectStationStockTradeFacts( + stationStock = StationStockTradeFacts( stationStock, availableEntities, cargoTargets, @@ -563,7 +563,7 @@ namespace GameCult.Aetheria.State.Verse }; } - private static AetheriaRuntimeZoneTargetRow ProjectZoneTargetRow( + private static AetheriaRuntimeZoneTargetRow ToZoneTargetRow( AetheriaRuntimeEntitySnapshotCommit observer, AetheriaRuntimeEntitySnapshotCommit target) { @@ -1078,14 +1078,14 @@ namespace GameCult.Aetheria.State.Verse entity.CargoContents?.Count ?? 0), DockingBayIndex = dockingBayIndex, HullItemKey = entity.HullItemKey ?? "", - CargoItems = ProjectStationStock(entity) + CargoItems = StationStock(entity) }); } return options.ToArray(); } - private static IReadOnlyList ProjectStationStock( + private static IReadOnlyList StationStock( AetheriaRuntimeEntitySnapshotCommit dockParent) { var stock = new List(); @@ -1155,7 +1155,7 @@ namespace GameCult.Aetheria.State.Verse OccupiedHullItemKey = assignedEntity?.HullItemKey ?? "", OccupiedByCurrentEntity = assignedEntityIndex >= 0 && assignedEntityIndex == currentEntityIndex, CargoItems = dockingBayIndex < contents.Count - ? ProjectStationStock(contents[dockingBayIndex], dockingBayIndex) + ? StationStock(contents[dockingBayIndex], dockingBayIndex) : Array.Empty() }); } @@ -1163,7 +1163,7 @@ namespace GameCult.Aetheria.State.Verse return rows.ToArray(); } - private static IReadOnlyList ProjectStationStock( + private static IReadOnlyList StationStock( AetheriaRuntimeCargoBayLoadoutCommit cargoBay, int cargoBayIndex) { @@ -1221,7 +1221,7 @@ namespace GameCult.Aetheria.State.Verse return options.ToArray(); } - private static IReadOnlyList ProjectStationStockTradeFacts( + private static IReadOnlyList StationStockTradeFacts( IReadOnlyList stationStock, IReadOnlyList availableEntities, IReadOnlyList cargoTargets, @@ -1232,7 +1232,7 @@ namespace GameCult.Aetheria.State.Verse .Select(stock => { var itemKey = stock?.ItemKey ?? ""; - var price = ProjectStationStockPrice(stock, catalog); + var price = StationStockPrice(stock, catalog); return new AetheriaRuntimeStationStockItem { ItemKey = itemKey, @@ -1250,7 +1250,7 @@ namespace GameCult.Aetheria.State.Verse .ToArray(); } - private static int ProjectStationStockPrice( + private static int StationStockPrice( AetheriaRuntimeStationStockItem? stock, AetheriaRuntimeCatalogSnapshot? catalog) { diff --git a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeEveSurfaceAdapter.cs b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeSurfaceDocuments.cs similarity index 62% rename from Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeEveSurfaceAdapter.cs rename to Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeSurfaceDocuments.cs index 3ce0e58ab..a4f498763 100644 --- a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeEveSurfaceAdapter.cs +++ b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeSurfaceDocuments.cs @@ -1,15 +1,238 @@ using System; using System.Collections.Generic; using System.Linq; +using GameCult.Caching; using GameCult.Eve.Surface; using GameCult.Mesh; +using MessagePack; using EveSurfaceState = global::Aetheria.State.Documents.EveSurfaceState; #nullable enable namespace GameCult.Aetheria.State.Verse { - public static class AetheriaRuntimeEveSurfaceAdapter + [CultDocument("gamecult.aetheria.runtime_surface", "gamecult.aetheria.runtime_surface.v1")] + [MessagePackObject] + public sealed class AetheriaRuntimeSurfaceDocument + { + [SerializationConstructor] + public AetheriaRuntimeSurfaceDocument( + string providerId, + string providerKind, + string title, + long version, + string updatedAtUtc, + AetheriaRuntimeSurfaceTree surface, + IReadOnlyList commands) + { + ProviderId = providerId ?? ""; + ProviderKind = providerKind ?? ""; + Title = title ?? ""; + Version = version; + UpdatedAtUtc = updatedAtUtc ?? ""; + Surface = surface ?? throw new ArgumentNullException(nameof(surface)); + Commands = commands ?? Array.Empty(); + } + + [Key(0)] + public string ProviderId { get; } + + [Key(1)] + public string ProviderKind { get; } + + [Key(2)] + public string Title { get; } + + [Key(3)] + public long Version { get; } + + [Key(4)] + public string UpdatedAtUtc { get; } + + [Key(5)] + public AetheriaRuntimeSurfaceTree Surface { get; } + + [Key(6)] + public IReadOnlyList Commands { get; } + } + + [MessagePackObject] + public sealed class AetheriaRuntimeSurfaceTree + { + [SerializationConstructor] + public AetheriaRuntimeSurfaceTree( + string id, + AetheriaRuntimeSurfaceComponent root, + IReadOnlyList styles) + { + Id = id ?? ""; + Root = root ?? throw new ArgumentNullException(nameof(root)); + Styles = styles ?? Array.Empty(); + } + + [Key(0)] + public string Id { get; } + + [Key(1)] + public AetheriaRuntimeSurfaceComponent Root { get; } + + [Key(2)] + public IReadOnlyList Styles { get; } + } + + [MessagePackObject] + public sealed class AetheriaRuntimeSurfaceComponent + { + public AetheriaRuntimeSurfaceComponent( + string id, + string kind, + IReadOnlyDictionary props, + IReadOnlyList children) + : this(id, kind, props, children, AetheriaRuntimeSurfaceStateBindings.FromProps(props)) + { + } + + public AetheriaRuntimeSurfaceComponent( + string id, + string kind, + IReadOnlyDictionary props, + IReadOnlyList children, + IReadOnlyList stateBindings) + : this(id, kind, props, children, stateBindings, Array.Empty()) + { + } + + [SerializationConstructor] + public AetheriaRuntimeSurfaceComponent( + string id, + string kind, + IReadOnlyDictionary props, + IReadOnlyList children, + IReadOnlyList stateBindings, + IReadOnlyList embeddedDocuments) + { + Id = id ?? ""; + Kind = kind ?? ""; + Props = props ?? new Dictionary(StringComparer.Ordinal); + Children = children ?? Array.Empty(); + StateBindings = stateBindings ?? Array.Empty(); + EmbeddedDocuments = embeddedDocuments ?? Array.Empty(); + } + + [Key(0)] + public string Id { get; } + + [Key(1)] + public string Kind { get; } + + [Key(2)] + public IReadOnlyDictionary Props { get; } + + [Key(3)] + public IReadOnlyList Children { get; } + + [Key(4)] + public IReadOnlyList StateBindings { get; } + + [Key(5)] + public IReadOnlyList EmbeddedDocuments { get; } + } + + [MessagePackObject] + public sealed class AetheriaRuntimeEmbeddedDocumentSlot + { + [SerializationConstructor] + public AetheriaRuntimeEmbeddedDocumentSlot( + string slotId, + string documentId, + string schemaId, + string presentationKind, + CultMeshRouteHint routeHint) + { + SlotId = slotId ?? ""; + DocumentId = documentId ?? ""; + SchemaId = schemaId ?? ""; + PresentationKind = presentationKind ?? ""; + RouteHint = routeHint ?? CultMeshRouteHint.Automatic; + } + + public AetheriaRuntimeEmbeddedDocumentSlot( + string slotId, + string documentId, + string schemaId, + string presentationKind) + : this(slotId, documentId, schemaId, presentationKind, CultMeshRouteHint.Automatic) + { + } + + [Key(0)] + public string SlotId { get; } + + [Key(1)] + public string DocumentId { get; } + + [Key(2)] + public string SchemaId { get; } + + [Key(3)] + public string PresentationKind { get; } + + [Key(4)] + public CultMeshRouteHint RouteHint { get; } + } + + [MessagePackObject] + public sealed class AetheriaRuntimeSurfaceStyleToken + { + [SerializationConstructor] + public AetheriaRuntimeSurfaceStyleToken(string name, string value) + { + Name = name ?? ""; + Value = value ?? ""; + } + + [Key(0)] + public string Name { get; } + + [Key(1)] + public string Value { get; } + } + + [MessagePackObject] + public sealed class AetheriaRuntimeSurfaceCommandTemplate + { + public const string CultMeshTransport = "cultmesh"; + + public AetheriaRuntimeSurfaceCommandTemplate(string command, string label, string transport) + : this(CultMesh.OperationBindingRecord( + command, + label, + "", + nameof(CultMeshLocalityKind.Automatic), + transport).ToBinding()) + { + } + + [SerializationConstructor] + public AetheriaRuntimeSurfaceCommandTemplate(CultMeshOperationBindingDescriptor operation) + { + Operation = operation ?? throw new ArgumentNullException(nameof(operation)); + } + + [Key(0)] + public CultMeshOperationBindingDescriptor Operation { get; } + + [IgnoreMember] + public string Command => Operation.OperationId; + + [IgnoreMember] + public string Label => Operation.Label; + + [IgnoreMember] + public string Transport => Operation.RouteHint.Description ?? ""; + } + + public static class AetheriaRuntimeSurfaceDocuments { public static EveSurfaceState ToEveSurfaceState(AetheriaRuntimeSurfaceDocument document) { @@ -69,10 +292,10 @@ namespace GameCult.Aetheria.State.Verse state.Surface.Id, ToEveSurfaceComponent(state.Surface.Root), state.Surface.Styles - .Select(style => new GameCult.Eve.Surface.EveStyleToken(style.Name, style.Value)) + .Select(style => new EveStyleToken(style.Name, style.Value)) .ToArray()), state.Commands - .Select(command => new GameCult.Eve.Surface.EveCommandTemplate( + .Select(command => new EveCommandTemplate( ToCultMeshOperationBinding(command))) .ToArray()); @@ -161,7 +384,8 @@ namespace GameCult.Aetheria.State.Verse component.Kind, props, component.Children.Select(ToEveSurfaceComponent).ToArray(), - component.StateBindings.Select(ToCultMeshStateBinding).ToArray()); + component.StateBindings.Select(ToCultMeshStateBinding).ToArray(), + component.EmbeddedDocuments.Select(ToEveEmbeddedDocumentSlot).ToArray()); } private static global::Aetheria.State.Documents.EveSurface ToEveSurfaceState( @@ -228,6 +452,16 @@ namespace GameCult.Aetheria.State.Verse stateBindings.Select(ToCultMeshStateBinding).ToArray()); } + private static EveEmbeddedDocumentSlot ToEveEmbeddedDocumentSlot(AetheriaRuntimeEmbeddedDocumentSlot slot) + { + return new EveEmbeddedDocumentSlot( + slot.SlotId, + slot.DocumentId, + slot.SchemaId, + slot.PresentationKind, + slot.RouteHint); + } + private static CultMeshStateBindingDescriptor ToCultMeshStateBinding( CultMeshStateBindingDescriptor binding) { @@ -275,7 +509,8 @@ namespace GameCult.Aetheria.State.Verse component.Kind, props, ResolveStateRefs(component.Children, resolveStateRef), - component.StateBindings); + component.StateBindings, + component.EmbeddedDocuments); } private static IReadOnlyList ResolveStateRefs( diff --git a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeSurfaceDocuments.cs.meta b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeSurfaceDocuments.cs.meta new file mode 100644 index 000000000..760268579 --- /dev/null +++ b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeSurfaceDocuments.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 59e70d5ec3b3d984f8577fa2b61a17c2 \ No newline at end of file diff --git a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeVerseClient.cs b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeVerseClient.cs index ba07ca077..db97193b4 100644 --- a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeVerseClient.cs +++ b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeVerseClient.cs @@ -121,6 +121,7 @@ namespace GameCult.Aetheria.State.Verse typeof(AetheriaRuntimeStarbridgePlayerSeatDocument), typeof(AetheriaRuntimePlayerSettingsDocument), typeof(AetheriaRuntimeVerseHostSettingsDocument), + typeof(AetheriaRuntimeSurfaceDocument), typeof(AetheriaRuntimeDaemonCommandDocument), typeof(AetheriaRuntimeEveCommandDocument), typeof(EveSurfaceState) @@ -147,6 +148,7 @@ namespace GameCult.Aetheria.State.Verse private CultMeshReactiveDocument? _managedDaemonFrame; private CultMeshReactiveDocument? _managedCatalog; private CultMeshReactiveDocument? _managedLoadoutTemplates; + private CultMeshReactiveDocument? _managedPlayerSettings; private CultMeshReactiveDocument? _managedStarbridgeScenario; private CultMeshReactiveDocument? _managedStarbridgeSession; private bool _disposed; @@ -356,6 +358,7 @@ namespace GameCult.Aetheria.State.Verse _managedDaemonFrame?.Dispose(); _managedCatalog?.Dispose(); _managedLoadoutTemplates?.Dispose(); + _managedPlayerSettings?.Dispose(); _managedStarbridgeScenario?.Dispose(); _managedStarbridgeSession?.Dispose(); _node.Dispose(); @@ -383,6 +386,11 @@ namespace GameCult.Aetheria.State.Verse () => Task.FromResult(BootstrapLoadoutTemplatesDocument()), AetheriaRuntimeLoadoutTemplatesDocument.SchemaId, CatalogBootstrapSource("catalog:aetheria.loadout_templates")); + var playerSettingsDocument = BootstrapCatalogDocument( + "aetheria.settings.player", + () => Task.FromResult(BootstrapPlayerSettingsDocument()), + AetheriaRuntimePlayerSettingsDocument.SchemaId, + CatalogBootstrapSource("catalog:aetheria.player_settings")); var starbridgeScenarioDocument = Document( AetheriaRuntimeVerseRecordKeys.StarbridgeScenarioLatest); var starbridgeSessionDocument = Document( @@ -392,6 +400,7 @@ namespace GameCult.Aetheria.State.Verse CultMeshReactiveDocument? managedDaemonFrame = null; CultMeshReactiveDocument? managedCatalog = null; CultMeshReactiveDocument? managedLoadoutTemplates = null; + CultMeshReactiveDocument? managedPlayerSettings = null; CultMeshReactiveDocument? managedStarbridgeScenario = null; CultMeshReactiveDocument? managedStarbridgeSession = null; @@ -417,11 +426,7 @@ namespace GameCult.Aetheria.State.Verse AetheriaRuntimeVerseRecordKeys.DaemonEditorTuiSurface), catalogDocument, loadoutTemplatesDocument, - BootstrapCatalogDocument( - "aetheria.settings.player", - () => Task.FromResult(BootstrapPlayerSettingsDocument()), - AetheriaRuntimePlayerSettingsDocument.SchemaId, - CatalogBootstrapSource("catalog:aetheria.player_settings")), + playerSettingsDocument, BootstrapCatalogDocument( "aetheria.settings.verse_host", () => Task.FromResult(BootstrapVerseHostSettingsDocument()), @@ -477,6 +482,25 @@ namespace GameCult.Aetheria.State.Verse IndexedDocumentId("aetheria.zone.details", zoneIndex), frame => Task.FromResult(AetheriaRuntimeRtsDocuments.ZoneDetails(frame, zoneIndex)), AetheriaRuntimeDaemonSchemas.ZoneDetails), + zoneIndex => ManagedFrameDocument( + IndexedDocumentId("aetheria.zone.details.surface", zoneIndex), + frame => ZoneDetailsSurfaceAsync(frame, zoneIndex), + AetheriaRuntimeZoneDetailsSurfaceBuilder.SurfaceId, + CultMesh.ProjectionSource("catalog:aetheria.runtime"), + CultMesh.ProjectionSource("catalog:aetheria.player_settings")), + request => ManagedFrameDocument( + InventoryPanelSurfaceDocumentId(request), + frame => InventoryPanelSurfaceAsync(frame, request), + AetheriaRuntimeInventoryPanelSurfaceBuilder.SurfaceId, + CultMesh.ProjectionSource("catalog:aetheria.runtime"), + CultMesh.ProjectionSource("catalog:aetheria.player_settings"), + CultMesh.ProjectionSource("loadout-templates:aetheria.runtime")), + request => ManagedFrameDocument( + InventoryDropdownSurfaceDocumentId(request), + frame => InventoryDropdownSurfaceAsync(frame, request), + AetheriaRuntimeInventoryDropdownSurfaceBuilder.SurfaceId, + CultMesh.ProjectionSource("catalog:aetheria.runtime"), + CultMesh.ProjectionSource("loadout-templates:aetheria.runtime")), entityIndex => ManagedFrameDocument( IndexedDocumentId("aetheria.object.selected", entityIndex), frame => Task.FromResult(AetheriaRuntimeRtsDocuments.SelectedObject(frame, entityIndex)), @@ -497,14 +521,16 @@ namespace GameCult.Aetheria.State.Verse seatId => Document( AetheriaRuntimeVerseRecordKeys.StarbridgePlayerSeat(seatId))); - managedDaemonFrame = state.ReactiveDaemonFrame(); - managedCatalog = state.ReactiveCatalogSnapshot(); - managedLoadoutTemplates = state.ReactiveLoadoutTemplates(); - managedStarbridgeScenario = state.ReactiveStarbridgeScenario(); - managedStarbridgeSession = state.ReactiveStarbridgeSession(); + managedDaemonFrame = state.DaemonFrame.Reactive(); + managedCatalog = state.Catalog.Reactive(); + managedLoadoutTemplates = state.LoadoutTemplates.Reactive(); + managedPlayerSettings = state.PlayerSettings.Reactive(); + managedStarbridgeScenario = state.StarbridgeScenario.Reactive(); + managedStarbridgeSession = state.StarbridgeSession.Reactive(); _managedDaemonFrame = managedDaemonFrame; _managedCatalog = managedCatalog; _managedLoadoutTemplates = managedLoadoutTemplates; + _managedPlayerSettings = managedPlayerSettings; _managedStarbridgeScenario = managedStarbridgeScenario; _managedStarbridgeSession = managedStarbridgeSession; return state; @@ -557,6 +583,12 @@ namespace GameCult.Aetheria.State.Verse ?? throw new InvalidOperationException("Aetheria Verse client has no loadout templates document yet."); } + AetheriaRuntimePlayerSettingsDocument RequireManagedPlayerSettings() + { + return managedPlayerSettings?.Current + ?? throw new InvalidOperationException("Aetheria Verse client has no player settings document yet."); + } + CultMeshDocumentHandle BootstrapCatalogDocument( string documentId, Func> bootstrap, @@ -599,6 +631,57 @@ namespace GameCult.Aetheria.State.Verse RequireManagedCatalog())); } + Task ZoneDetailsSurfaceAsync( + AetheriaRuntimeDaemonFrameDocument frame, + int zoneIndex) + { + return Task.FromResult(AetheriaRuntimeZoneDetailsSurfaceBuilder.BuildFromDocuments( + AetheriaRuntimeRtsDocuments.ZoneDetails(frame, zoneIndex), + AetheriaRuntimeRtsDocuments.SectorMap(frame), + RequireManagedCatalog(), + RequireManagedPlayerSettings(), + DateTime.UtcNow.ToString("O", CultureInfo.InvariantCulture))); + } + + Task InventoryPanelSurfaceAsync( + AetheriaRuntimeDaemonFrameDocument frame, + AetheriaRuntimeInventoryPanelSurfaceRequest request) + { + request ??= new AetheriaRuntimeInventoryPanelSurfaceRequest(); + var entityIndex = request.DisplayedEntityIndex >= 0 + ? request.DisplayedEntityIndex + : request.DisplayedCargoEntityIndex; + var inventory = entityIndex < 0 + ? new AetheriaRuntimeInventoryDocument() + : AetheriaRuntimeRtsDocuments.Inventory(frame, entityIndex); + + return Task.FromResult(AetheriaRuntimeInventoryPanelSurfaceBuilder.BuildFromDocuments( + AetheriaRuntimeRtsDocuments.CurrentEntity(frame), + AetheriaRuntimeRtsDocuments.StationRefit( + frame, + RequireManagedLoadoutTemplates().Templates, + RequireManagedCatalog()), + inventory, + RequireManagedCatalog(), + RequireManagedPlayerSettings(), + request, + InventoryDropdownSurfaceDocumentId(ToDropdownRequest(request)), + DateTime.UtcNow.ToString("O", CultureInfo.InvariantCulture))); + } + + Task InventoryDropdownSurfaceAsync( + AetheriaRuntimeDaemonFrameDocument frame, + AetheriaRuntimeInventoryDropdownSurfaceRequest request) + { + return Task.FromResult(AetheriaRuntimeInventoryDropdownSurfaceBuilder.BuildFromDocuments( + AetheriaRuntimeRtsDocuments.StationRefit( + frame, + RequireManagedLoadoutTemplates().Templates, + RequireManagedCatalog()), + request, + DateTime.UtcNow.ToString("O", CultureInfo.InvariantCulture))); + } + static string IndexedDocumentId(string prefix, int index) { return $"{prefix}.{index.ToString(CultureInfo.InvariantCulture)}"; @@ -616,6 +699,46 @@ namespace GameCult.Aetheria.State.Verse ViewportToken(normalized.MaxY)); } + static string InventoryPanelSurfaceDocumentId(AetheriaRuntimeInventoryPanelSurfaceRequest? request) + { + request ??= new AetheriaRuntimeInventoryPanelSurfaceRequest(); + return string.Join( + ".", + "aetheria.inventory.panel.surface", + AetheriaRuntimeVerseRecordKeys.StableToken(request.DisplayedEntityKey), + request.DisplayedEntityIndex.ToString(CultureInfo.InvariantCulture), + AetheriaRuntimeVerseRecordKeys.StableToken(request.DisplayedCargoEntityKey), + request.DisplayedCargoEntityIndex.ToString(CultureInfo.InvariantCulture), + request.DisplayedCargoIndex.ToString(CultureInfo.InvariantCulture), + request.ThermalView ? "thermal" : "inventory"); + } + + static string InventoryDropdownSurfaceDocumentId(AetheriaRuntimeInventoryDropdownSurfaceRequest? request) + { + request ??= new AetheriaRuntimeInventoryDropdownSurfaceRequest(); + return string.Join( + ".", + "aetheria.inventory.dropdown.surface", + AetheriaRuntimeVerseRecordKeys.StableToken(request.DisplayedEntityKey), + AetheriaRuntimeVerseRecordKeys.StableToken(request.DisplayedCargoEntityKey), + request.DisplayedCargoIndex.ToString(CultureInfo.InvariantCulture), + request.CanSaveLoadout ? "save" : "readonly"); + } + + static AetheriaRuntimeInventoryDropdownSurfaceRequest ToDropdownRequest( + AetheriaRuntimeInventoryPanelSurfaceRequest? request) + { + request ??= new AetheriaRuntimeInventoryPanelSurfaceRequest(); + return new AetheriaRuntimeInventoryDropdownSurfaceRequest + { + CurrentView = request.ViewTitle ?? "", + DisplayedEntityKey = request.DisplayedEntityKey ?? "", + DisplayedCargoEntityKey = request.DisplayedCargoEntityKey ?? "", + DisplayedCargoIndex = request.DisplayedCargoIndex, + CanSaveLoadout = !string.IsNullOrWhiteSpace(request.DisplayedEntityKey) + }; + } + static string ViewportToken(double value) { return value diff --git a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeZoneDetailsSurfaceBuilder.cs b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeZoneDetailsSurfaceBuilder.cs index 1ef3fe868..a53235b24 100644 --- a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeZoneDetailsSurfaceBuilder.cs +++ b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeZoneDetailsSurfaceBuilder.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.Linq; using GameCult.Eve.Surface; @@ -200,6 +201,36 @@ namespace GameCult.Aetheria.State.Verse }); } + public static AetheriaRuntimeSurfaceDocument BuildFromDocuments( + AetheriaRuntimeZoneDetailsDocument zoneDetails, + AetheriaRuntimeSectorMapDocument sectorMap, + AetheriaRuntimeCatalogSnapshot catalog, + AetheriaRuntimePlayerSettingsDocument playerSettings, + string updatedAtUtc, + long version = 1) + { + var sectorZone = ResolveSectorZone(sectorMap, zoneDetails?.ZoneIndex ?? -1); + var ownerFactionIndex = sectorZone?.OwnerFactionIndex ?? -1; + var otherFactions = (sectorZone?.FactionIndices ?? Array.Empty()) + .Where(index => index >= 0 && index != ownerFactionIndex) + .Distinct() + .Select(FormatFaction) + .ToArray(); + var facts = Facts(zoneDetails, hullItemKey => ResolveHullType(catalog, hullItemKey)); + + return Build( + ResolveZoneName(sectorZone, zoneDetails), + ownerFactionIndex >= 0 ? FormatFaction(ownerFactionIndex) : "None", + FormatValue(facts.Mass, playerSettings), + FormatValue(facts.Radius, playerSettings), + otherFactions, + facts.Bodies, + facts.Entities, + facts.HasContents, + updatedAtUtc, + version); + } + private static IReadOnlyList BodyFacts( AetheriaRuntimeZoneSnapshotCommit zone) { @@ -281,6 +312,60 @@ namespace GameCult.Aetheria.State.Verse { return entity != null && string.Equals(entity.HullType ?? "", hullType, StringComparison.Ordinal); } + + private static AetheriaRuntimeSectorMapZone ResolveSectorZone( + AetheriaRuntimeSectorMapDocument sectorMap, + int zoneIndex) + { + if (zoneIndex < 0) + return null; + + return (sectorMap?.Zones ?? Array.Empty()) + .FirstOrDefault(zone => zone.ZoneIndex == zoneIndex); + } + + private static string ResolveZoneName( + AetheriaRuntimeSectorMapZone sectorZone, + AetheriaRuntimeZoneDetailsDocument zoneDetails) + { + if (!string.IsNullOrWhiteSpace(zoneDetails?.ZoneName)) + return zoneDetails.ZoneName; + + if (!string.IsNullOrWhiteSpace(sectorZone?.Name)) + return sectorZone.Name; + + return sectorZone == null ? "Unknown" : $"Zone {sectorZone.ZoneIndex}"; + } + + private static string ResolveHullType( + AetheriaRuntimeCatalogSnapshot catalog, + string hullItemKey) + { + var typedHull = catalog?.FindItem(hullItemKey ?? ""); + return typedHull?.HullType ?? ""; + } + + private static string FormatFaction(int factionIndex) + { + return factionIndex < 0 ? "None" : $"Faction {factionIndex}"; + } + + private static string FormatValue( + double value, + AetheriaRuntimePlayerSettingsDocument playerSettings) + { + var digits = playerSettings?.SignificantDigits ?? 3; + var magnitude = value == 0 ? 0 : (int)Math.Floor(Math.Log10(Math.Abs(value))) + 1; + digits -= magnitude; + if (digits < 0) + digits = 0; + + var formatted = value.ToString($"N{digits}", CultureInfo.CurrentCulture); + var decimalSeparator = Convert.ToChar(CultureInfo.CurrentCulture.NumberFormat.NumberDecimalSeparator); + return formatted.Contains(CultureInfo.CurrentCulture.NumberFormat.NumberDecimalSeparator) + ? formatted.TrimEnd('0').TrimEnd(decimalSeparator) + : formatted; + } } public enum AetheriaRuntimeZoneDetailsCommandKind diff --git a/Packages/packages-lock.json b/Packages/packages-lock.json index 2304b0e0b..661afc3f5 100644 --- a/Packages/packages-lock.json +++ b/Packages/packages-lock.json @@ -156,16 +156,6 @@ "dependencies": {}, "url": "https://packages.unity.com" }, - "com.unity.postprocessing": { - "version": "3.5.4", - "depth": 0, - "source": "registry", - "dependencies": { - "com.unity.modules.imgui": "1.0.0", - "com.unity.modules.physics": "1.0.0" - }, - "url": "https://packages.unity.com" - }, "com.unity.profiling.core": { "version": "1.0.3", "depth": 2, diff --git a/ProjectSettings/EditorSettings.asset b/ProjectSettings/EditorSettings.asset index 6c3863143..4f9bf396c 100644 --- a/ProjectSettings/EditorSettings.asset +++ b/ProjectSettings/EditorSettings.asset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2d78aa3fe7486c837d14708b3d75c7a9fb99c8f7f1d07b2d32d2a0fca7e8f141 -size 1541 +oid sha256:c26af4370b8ae1a589758cf88ac8807485eee1e152e2258c32d7cff847923f99 +size 1638 diff --git a/ProjectSettings/EntitiesClientSettings.asset b/ProjectSettings/EntitiesClientSettings.asset new file mode 100644 index 000000000..db73d76f8 --- /dev/null +++ b/ProjectSettings/EntitiesClientSettings.asset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:440747cc2795b64122061910cb9b2f1a20d563f53145898c4243056376475282 +size 505 diff --git a/ProjectSettings/GraphicsSettings.asset b/ProjectSettings/GraphicsSettings.asset index 0874f9a26..e636f0782 100644 --- a/ProjectSettings/GraphicsSettings.asset +++ b/ProjectSettings/GraphicsSettings.asset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f49b079fb613c3ae8807d0966f63cd88cf47a7d39e346bc3516df5a5a849963b +oid sha256:2c623a9a0390e7316b2f679b535c9b0913d0f97d31b87c2c6d24de77b4898c5d size 2935 diff --git a/ProjectSettings/URPProjectSettings.asset b/ProjectSettings/URPProjectSettings.asset new file mode 100644 index 000000000..601674cdd --- /dev/null +++ b/ProjectSettings/URPProjectSettings.asset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:68d75e5e1502dd7a4c18b7306430916b5ee8dadfdb0fbb588893fe38eaa2c056 +size 557 diff --git a/docs/cultmesh-feature-implementation-guide.md b/docs/cultmesh-feature-implementation-guide.md index 531ad4a80..2b328520d 100644 --- a/docs/cultmesh-feature-implementation-guide.md +++ b/docs/cultmesh-feature-implementation-guide.md @@ -12,7 +12,7 @@ The rule is blunt: ```text small UI-only value: derive it inline from already accessible typed state -named UI-only projection: define the projected document, then read it +named UI-only derived view: define the typed derived document, then read it simulation feature: define one canonical typed document, let daemon authority mutate it, consume the same document everywhere hot rendering/physics: use SoA, not object-graph UI documents ``` @@ -45,12 +45,30 @@ A second document is justified only when it earns a distinct job: - viewport/windowed selection; - SoA/native render or physics layout; - lossy presentation summaries; -- UI surface documents; +- UI surface documents, including nested CultUI `embeddedDocuments` slots when + a parent surface reserves layout for a separately synced child document; - compatibility mirrors with a named removal stage. Projection is not the normal way to make state client-visible. Projection is an exception for a different shape. +Nested UI does not loosen this rule. If a daemon-owned panel needs an embedded +dropdown, item inspector, scene insert, or drag/drop target surface, the parent +surface carries a `surface.slot` with `embeddedDocuments`, and each child is +resolved as its own managed CultMesh document. The renderer lowers the retained +tree and wires commands; it does not invent a Unity-only state model for the +child surface. + +Discovery and parity live with the shared Eve contract, not in an Aetheria-only +helper. Any CultUI feature that affects nested surfaces must update the +canonical fixture at `E:\Projects\Eve\web\fixtures\cultui-embedded-surface.json` +or an equivalent versioned fixture, and it must be discoverable from +`E:\Projects\Eve\docs\parity-testing-harness.md`. The active runtime checks are +the browser DSL test, Eve parity harness, Flutter parity smoke, Unity build, and +Aetheria verifier scripts. Android/Kotlin and Fensalir must remain listed in +the matrix with explicit pending/unsupported status until they become active +GUI CultUI renderers. Do not let an untested target disappear into politeness. + ## The Ergonomic Bar UI-only state should take one or two steps. @@ -63,11 +81,11 @@ var currentDocking = client.State.Reactive typed client facade + -> typed client handles -> typed command document -> local Verse node -> daemon command gate -> simulation -> typed daemon frame/fact documents -> local Verse replica - -> typed projection or native SoA view + -> managed typed document/query or native SoA view -> Unity/Electron rendering and UI ``` @@ -79,7 +79,7 @@ typed Verse state." Durable typed state and shared runtime contract. This is the center of the new world. It owns document types, schema registration, state mapping, Eve command bridges, provider advertisement, player settings, loadout templates, trade -policy, Verse target state, and several projection helpers. +policy, Verse target state, and managed derived document/query helpers. `Aetheria.State.Daemon` @@ -91,8 +91,8 @@ daemon frame/SoA/health/provider/surface records. The Unity-embeddable runtime state package. Unity, tests, daemon, and Electron binding generation all depend on this contract. This package contains the -shared C# client facade (`AetheriaClient`, `AetheriaControl`, `AetheriaUi`), -daemon command documents, RTS/current-entity projections, Starbridge documents, +shared C# client handles (`AetheriaClient`, `AetheriaControl`, `AetheriaUi`), +daemon command documents, RTS/current-entity documents, Starbridge documents, authority policy, SoA documents, Eve surface builders, and runtime catalog snapshots. @@ -106,7 +106,7 @@ daemon contract fills in. `Aetheria.Rts.Web` Electron RTS client. It is intentionally thin: browser UI plus Electron -CultMesh/Verse access, local projection reads, and typed operation helpers. +CultMesh/Verse access, generated query/document reads, and typed operation helpers. Generated TypeScript bindings live in `Aetheria.Rts.Web/Electron/aetheria-rts-generated-bindings.ts` and are derived from C# `[Key]` declarations. @@ -218,7 +218,7 @@ Command application is in: Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeDaemonOperations.cs ``` -### Client Facade +### Client Handles Use `AetheriaClient` first: @@ -232,9 +232,9 @@ documents, Starbridge seat handles, typed controls through `AetheriaControl`, and typed UI surface helpers through `AetheriaUi`. If a Unity panel or Electron surface needs a fact, prefer the generic managed -document call first. Add a named facade only when it carries semantic identity, +document call first. Add a named handle only when it carries semantic identity, parameters, operation policy, or a distinct derived/native shape. Avoid handing -callers raw daemon frames unless the caller is a transitional projection layer. +callers raw daemon frames unless the caller is an explicitly fenced transition. ### Unity Shell @@ -292,11 +292,11 @@ Add or inspect a daemon command: - `Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeDaemonOperations.cs` - `Aetheria.State.Verify/Program.cs` -Add a client projection: +Add a managed derived document/query: -- `Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeRtsProjection.cs` +- `Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeRtsDocuments.cs` - `Packages/org.gamecult.aetheria.state/Runtime/AetheriaClient.cs` -- `Aetheria.Rts.Web/Electron/aetheria-rts-local-projection.ts` +- `Aetheria.Rts.Web/Electron/aetheria-rts-local-documents.ts` - `Aetheria.Rts.Web/Electron/aetheria-rts-generated-bindings.ts` - `Aetheria.State.Verify/Program.cs` @@ -306,7 +306,6 @@ Work on Unity thin-client migration: - `Assets/Scripts/Gameplay/AetheriaUnityGameplayBootShell.cs` - `Assets/Scripts/Gameplay/AetheriaUnityGameplayInputShell.cs` - `Assets/Scripts/Gameplay/AetheriaUnityObservedFrameApplier.cs` -- `Assets/Scripts/Gameplay/AetheriaUnityObservedFacadeProjector.cs` - `Assets/Scripts/Gameplay/AetheriaUnityCurrentEntityBinder.cs` - `Assets/Scripts/Gameplay/AetheriaUnityTargetPresentation.cs` - `Assets/Scripts/UI/Menu/*.cs` @@ -316,7 +315,7 @@ Work on Unity thin-client migration: Work on Eve/CultUI surfaces: - `Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeEveSurfaceState.cs` -- `Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeEveSurfaceAdapter.cs` +- `Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeSurfaceDocuments.cs` - `Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeEveCommandClient.cs` - `Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntime*SurfaceBuilder.cs` - `Packages/org.gamecult.aetheria.eve-runtime/Runtime/AetheriaEveSurfacePresenter.cs` @@ -330,7 +329,7 @@ Work on RTS/Electron: - `Aetheria.Rts.Web/Electron/preload.cjs` - `Aetheria.Rts.Web/Electron/aetheria-cultmesh.ts` - `Aetheria.Rts.Web/Electron/aetheria-rts-bindings.ts` -- `Aetheria.Rts.Web/Electron/aetheria-rts-local-projection.ts` +- `Aetheria.Rts.Web/Electron/aetheria-rts-local-documents.ts` - `Aetheria.Rts.Web/scripts/generate-rts-bindings.mjs` Work on native/SoA render path: @@ -360,7 +359,7 @@ Work on Ymir/physics: fact. If yes, promote it into `Aetheria.State` and read it through `AetheriaClient`. -3. If a fact is needed every frame, prefer a typed projection or SoA/native view. +3. If a fact is needed every frame, prefer a typed query/document or SoA/native view. Do not make Unity crawl a whole daemon frame in a hot path unless the code is explicitly transitional and fenced by the verifier. @@ -373,7 +372,7 @@ Work on Ymir/physics: enemy AI, movement rules, trade rules, station rules, or combat rules. 6. Keep Unity honest. - Unity may adapt old facade objects for presentation until Stage 8 finishes, + Unity may adapt old object graphs for presentation until Stage 8 finishes, but new state truth belongs in typed documents. 7. Do not preserve legacy shape for its own sake.