From 7006a6b0dd4038f86ece5c89397a3c573290ce8a Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Mon, 10 Aug 2026 14:57:50 +0100 Subject: [PATCH] Restore the stable legacy Unity project --- .gitignore | 44 - .voidbot/state/aetheria.cc | Bin 190733 -> 8361 bytes AGENTS.md | 81 - Aetheria RTS.lnk | Bin 1163 -> 0 bytes Aetheria.Rts.Web/Client/app.ts | 4 - .../Electron/aetheria-cultmesh.ts | 797 - .../Electron/aetheria-rts-bindings.ts | 58 - .../aetheria-rts-generated-bindings.ts | 2596 -- .../Electron/aetheria-rts-local-documents.ts | 495 - Aetheria.Rts.Web/Electron/main.ts | 432 - Aetheria.Rts.Web/package-lock.json | 305 - Aetheria.Rts.Web/package.json | 33 - .../scripts/generate-rts-bindings.mjs | 1641 -- .../scripts/verify-stage7b-rts-client.ps1 | 576 - .../scripts/verify-stage7c-electron-rts.ps1 | 136 - .../scripts/verify-stage7c-local-runtime.ps1 | 195 - .../verify-stage7d-remote-cultmesh.ps1 | 145 - Aetheria.Rts.Web/tsconfig.electron.json | 19 - Aetheria.Rts.Web/tsconfig.json | 14 - Aetheria.Rts.Web/wwwroot/index.html | 21 - Aetheria.Rts.Web/wwwroot/styles.css | 90 - .../Aetheria.State.AuthoritySmoke.csproj | 13 - Aetheria.State.AuthoritySmoke/Program.cs | 2026 -- .../Aetheria.State.Daemon.Smoke.csproj | 17 - Aetheria.State.Daemon.Smoke/Program.cs | 9834 ------- .../Aetheria.State.Daemon.csproj | 19 - .../AetheriaDaemonGeneratedEntityFactory.cs | 72 - .../AetheriaDaemonHangarCoordinator.cs | 196 - .../AetheriaDaemonLoadoutGenerator.cs | 448 - .../AetheriaDaemonNativeCatalog.cs | 232 - .../AetheriaDaemonRegularRunWriter.cs | 134 - .../AetheriaDaemonRegularTopologyGenerator.cs | 258 - .../AetheriaDaemonRunFactory.cs | 39 - .../AetheriaDaemonTutorialRunWriter.cs | 134 - ...AetheriaDaemonTutorialTopologyGenerator.cs | 725 - ...AetheriaDaemonTutorialWorldMaterializer.cs | 276 - .../AetheriaDaemonZoneBodyGenerator.cs | 508 - .../AetheriaDaemonZoneGenerator.cs | 595 - .../AetheriaYmirPersistence.cs | 11 - .../AetheriaYmirPersistenceCoordinator.cs | 398 - .../AetheriaYmirPersistenceDocuments.cs | 43 - .../AetheriaYmirWorldPhysics.cs | 630 - Aetheria.State.Daemon/Program.cs | 4360 ---- .../Properties/AssemblyInfo.cs | 3 - .../Aetheria.State.Freeze.csproj | 15 - Aetheria.State.Freeze/Program.cs | 1374 - .../Aetheria.State.Import.csproj | 18 - Aetheria.State.Import/Program.cs | 1560 -- .../Aetheria.State.Replica.csproj | 14 - Aetheria.State.Replica/Program.cs | 151 - .../Aetheria.State.Smoke.csproj | 17 - Aetheria.State.Smoke/Program.cs | 1460 -- .../Aetheria.State.Unity.Smoke.csproj | 14 - Aetheria.State.Unity.Smoke/Program.cs | 281 - .../Aetheria.State.Unity.csproj | 13 - .../AetheriaRuntimeCatalogClient.cs | 43 - .../Aetheria.State.Verify.csproj | 18 - .../AetheriaCatalogEnumProbeTypes.cs | 38 - Aetheria.State.Verify/Program.cs | 21377 ---------------- .../Aetheria.State.Dependencies.props | 7 - .../Aetheria.State.Dependencies.targets | 20 - Aetheria.State/Aetheria.State.csproj | 29 - Aetheria.State/AetheriaCatalogKeys.cs | 31 - Aetheria.State/AetheriaDocumentRegistry.cs | 83 - Aetheria.State/AetheriaEveCommandBridge.cs | 478 - Aetheria.State/AetheriaEveSurfaceDocuments.cs | 379 - Aetheria.State/AetheriaHangar.cs | 434 - Aetheria.State/AetheriaRuntimeStateMapper.cs | 215 - Aetheria.State/AetheriaStateNode.cs | 462 - Aetheria.State/AetheriaStatePaths.cs | 16 - .../AetheriaVerseCatalogDocuments.cs | 54 - Aetheria.State/AetheriaVerseDiscoveryHost.cs | 67 - .../AetheriaVerseHostSettingsNormalizer.cs | 44 - Aetheria.State/AetheriaVerseReplica.cs | 311 - .../Documents/AetheriaCorporation.cs | 79 - .../AetheriaEveCommandAcceptanceStatus.cs | 75 - .../Documents/AetheriaGameSessionState.cs | 26 - .../Documents/AetheriaItemDefinition.cs | 331 - .../AetheriaLegacyCatalogQuarantine.cs | 51 - .../Documents/AetheriaMainMenuState.cs | 26 - .../Documents/AetheriaMigrationLedger.cs | 61 - Aetheria.State/Documents/AetheriaNameFile.cs | 31 - .../Documents/AetheriaPlayerProfile.cs | 25 - .../Documents/AetheriaRuntimeSession.cs | 26 - .../AetheriaRuntimeStateDocuments.cs | 1136 - .../Documents/AetheriaTradeValuePolicy.cs | 36 - .../Documents/AetheriaVerseHostSettings.cs | 21 - .../Documents/AetheriaWorldState.cs | 27 - .../Migration/LegacyMigrationBoundary.cs | 21 - Aetheria.State/README.md | 463 - .../docs/client-runtime-conformance.md | 75 - .../docs/cultmesh-cross-runtime-primitives.md | 407 - ...ltmesh-ergonomics-staged-migration-plan.md | 641 - .../docs/daemon-code-map-for-rust-port.md | 570 - .../docs/rust-rebuild-freeze-checklist.md | 106 - .../docs/stage-7-client-surface-inventory.md | 298 - ...-thin-client-staged-implementation-plan.md | 1311 - .../verse-authority-implementation-plan.md | 1980 -- Aetheria.State/docs/verse-authority-policy.md | 221 - Aetheria.State/docs/verse-daemon-shape.md | 65 - .../scripts/verify-stage7d-unity-parity.ps1 | 843 - Aetheria.Unity/.gitignore | 9 - Aetheria.Unity/Assets/Aetheria.unity | 170 - Aetheria.Unity/Assets/AetheriaUnityClient.cs | 83 - .../Assets/AetheriaUnityClient.cs.meta | 2 - .../AetheriaUnityThermalPresentationSink.cs | 54 - ...theriaUnityThermalPresentationSink.cs.meta | 2 - .../Editor/AetheriaDaemonDevelopmentWindow.cs | 644 - .../AetheriaDaemonDevelopmentWindow.cs.meta | 2 - .../Assets/Editor/AetheriaUnityBuild.cs | 63 - .../Assets/Editor/AetheriaUnityBuild.cs.meta | 2 - Aetheria.Unity/Assets/Settings.meta | 8 - .../AetheriaUniversalRenderPipeline.asset | 3 - ...AetheriaUniversalRenderPipeline.asset.meta | 8 - .../Settings/AetheriaUniversalRenderer.asset | 3 - .../AetheriaUniversalRenderer.asset.meta | 8 - Aetheria.Unity/Packages/manifest.json | 16 - Aetheria.Unity/Packages/packages-lock.json | 246 - .../ProjectSettings/AudioManager.asset | 3 - .../ProjectSettings/ClusterInputManager.asset | 3 - .../ProjectSettings/DynamicsManager.asset | 3 - .../ProjectSettings/EditorBuildSettings.asset | 3 - .../ProjectSettings/EditorSettings.asset | 3 - .../ProjectSettings/GraphicsSettings.asset | 3 - .../ProjectSettings/InputManager.asset | 3 - .../ProjectSettings/MemorySettings.asset | 3 - .../ProjectSettings/MultiplayerManager.asset | 3 - .../ProjectSettings/NavMeshAreas.asset | 3 - .../ProjectSettings/Physics2DSettings.asset | 3 - .../ProjectSettings/PresetManager.asset | 3 - .../ProjectSettings/ProjectSettings.asset | 3 - .../ProjectSettings/ProjectVersion.txt | 2 - .../ProjectSettings/QualitySettings.asset | 3 - .../SceneTemplateSettings.json | 121 - .../ProjectSettings/TagManager.asset | 3 - .../ProjectSettings/TimeManager.asset | 3 - .../UnityConnectSettings.asset | 3 - .../ProjectSettings/VFXManager.asset | 3 - .../VersionControlSettings.asset | 3 - Aetheria.Unity/README.md | 22 - Aetheria.sln | 77 - .../AetheriaEntityAuthoringV1.blend | 3 - .../ambientCG.meta => Allegorithmic.meta} | 2 +- .../Plugins.meta} | 2 +- Assets/Allegorithmic/Plugins/EULA.txt | 468 + .../Plugins/EULA.txt.meta} | 2 +- .../Plugins/Substance.EditorHelper.meta} | 2 +- .../Substance.EditorHelper/Scripter.cs | 87 + .../Substance.EditorHelper/Scripter.cs.meta} | 2 +- .../Substance.EditorHelper.asmdef | 3 + .../Substance.EditorHelper.asmdef.meta | 7 + .../Plugins/Substance.Engine.bundle.meta | 33 + .../Substance.Engine.bundle/Contents.meta | 8 + .../Contents/Info.plist | 44 + .../Contents/Info.plist.meta | 2 +- .../Contents/MacOS.meta | 8 + .../Contents/MacOS/Substance.Engine | 3 + .../Contents/MacOS/Substance.Engine.meta | 7 + .../Plugins/Substance.Engine.dll | 3 + .../Plugins/Substance.Engine.dll.meta | 27 + .../Allegorithmic/Plugins/Substance.Game.dll | 3 + .../Plugins/Substance.Game.dll.meta | 33 + .../Plugins/Substance.Platform.meta | 8 + .../Substance.Platform/NativeFunctions.cs | 1159 + .../NativeFunctions.cs.meta} | 2 +- .../Substance.Platform.asmdef | 3 + .../Substance.Platform.asmdef.meta | 7 + Assets/Allegorithmic/Plugins/Substance.meta | 8 + .../Plugins/Substance/Arm64.meta | 8 + .../Arm64/libSubstance.Engine.Mobile.so | Bin 0 -> 2136832 bytes .../Arm64/libSubstance.Engine.Mobile.so.meta | 96 + .../Substance/Arm64/libalgcompression.a | 3 + .../Substance/Arm64/libalgcompression.a.meta | 96 + .../Substance/Arm64/libpfxlinkercommon.a | 3 + .../Substance/Arm64/libpfxlinkercommon.a.meta | 96 + .../Substance/Arm64/libsubstance_framework.a | 3 + .../Arm64/libsubstance_framework.a.meta | 96 + .../Substance/Arm64/libsubstance_linker.a | 3 + .../Arm64/libsubstance_linker.a.meta | 96 + .../Substance/Arm64/libsubstance_neon_blend.a | 3 + .../Arm64/libsubstance_neon_blend.a.meta | 96 + .../Plugins/Substance/Arm64/libtinyxml.a | 3 + .../Plugins/Substance/Arm64/libtinyxml.a.meta | 96 + .../Plugins/Substance/Armv7.meta | 8 + .../Armv7/libSubstance.Engine.Mobile.so | Bin 0 -> 1849504 bytes .../Armv7/libSubstance.Engine.Mobile.so.meta | 93 + .../Substance/Armv7/libalgcompression.a | 3 + .../Substance/Armv7/libalgcompression.a.meta | 102 + .../Substance/Armv7/libpfxlinkercommon.a | 3 + .../Substance/Armv7/libpfxlinkercommon.a.meta | 102 + .../Substance/Armv7/libsubstance_framework.a | 3 + .../Armv7/libsubstance_framework.a.meta | 93 + .../Substance/Armv7/libsubstance_linker.a | 3 + .../Armv7/libsubstance_linker.a.meta | 93 + .../Substance/Armv7/libsubstance_neon_blend.a | 3 + .../Armv7/libsubstance_neon_blend.a.meta | 93 + .../Plugins/Substance/Armv7/libtinyxml.a | 3 + .../Plugins/Substance/Armv7/libtinyxml.a.meta | 93 + .../Plugins/Substance/Substance Engine.meta | 8 + .../Substance/Substance Engine}/Editor.meta | 2 +- .../Substance Engine/Editor/MainObject.png | 3 + .../Editor/MainObject.png.meta} | 66 +- .../Editor/Substance.Editor.dll | 3 + .../Editor/Substance.Editor.dll.meta | 33 + .../Substance Engine/Editor/Substance.png | 3 + .../Editor/Substance.png.meta} | 70 +- .../Editor/SubstanceGraph-32.png | 3 + .../Editor/SubstanceGraph-32.png.meta} | 72 +- .../Editor/SubstanceGraph.png | 3 + .../Editor/SubstanceGraph.png.meta | 88 + .../Plugins/Substance/Substance Source.meta | 8 + .../Substance/Substance Source/Editor.meta | 8 + .../Substance Source/Editor/Images.meta | 8 + .../Editor/Images/AssetDownload.png | 3 + .../Editor/Images/AssetDownload.png.meta | 88 + .../Editor/Images/AvailableDownloads.png | 3 + .../Editor/Images/AvailableDownloads.png.meta | 88 + .../Editor/Images/DetailExitButton.png | 3 + .../Editor/Images/DetailExitButton.png.meta | 88 + .../Editor/Images/LoginButton.png | 3 + .../Editor/Images/LoginButton.png.meta | 88 + .../Editor/Images/LogoutButton.png | 3 + .../Editor/Images/LogoutButton.png.meta | 88 + .../Editor/Images/MagnifyingGlass.png | 3 + .../Editor/Images/MagnifyingGlass.png.meta | 88 + .../Editor/Images/MyAssetsButton.png | 3 + .../Editor/Images/MyAssetsButton.png.meta | 88 + .../Editor/Images/Source.Icon.png | 3 + .../Editor/Images/Source.Icon.png.meta | 88 + .../Editor/Images/SubstanceSourceLoading.png | 3 + .../Images/SubstanceSourceLoading.png.meta | 88 + .../Editor/Images/download.png | 3 + .../Editor/Images/download.png.meta | 88 + .../Substance Source/Editor/Images/free.png | 3 + .../Editor/Images/free.png.meta | 88 + .../Editor/Images/leftarrow.png | 3 + .../Editor/Images/leftarrow.png.meta | 88 + .../Editor/Images/library.png | 3 + .../Editor/Images/library.png.meta | 88 + .../Editor/Images/logo-substance.png | 3 + .../Editor/Images/logo-substance.png.meta | 88 + .../Editor/Images/logo-white.png | 3 + .../Editor/Images/logo-white.png.meta | 88 + .../Substance Source/Editor/Images/new.png | 3 + .../Editor/Images/new.png.meta | 88 + .../Editor/Images/rightarrow.png | 3 + .../Editor/Images/rightarrow.png.meta | 88 + .../Editor/Images/spinner.png | 3 + .../Editor/Images/spinner.png.meta | 88 + .../Editor/SourceInterfacex64.dll | 3 + .../Editor/SourceInterfacex64.dll.meta | 27 + .../Editor/SourceUnityLib.dll | 3 + .../Editor/SourceUnityLib.dll.meta | 33 + .../Editor/libSourceInterfacex64.dylib | 3 + .../Editor/libSourceInterfacex64.dylib.meta | 7 + .../Allegorithmic/Plugins/Substance/iOS.meta | 8 + .../Substance/iOS/libSubstance.Engine.a | 3 + .../Substance/iOS/libSubstance.Engine.a.meta | 102 + .../Plugins/Substance/iOS/libalgcompression.a | 3 + .../Substance/iOS/libalgcompression.a.meta | 91 + .../Substance/iOS/libpfxlinkercommon.a | 3 + .../Substance/iOS/libpfxlinkercommon.a.meta | 91 + .../Substance/iOS/libsubstance_framework.a | 3 + .../iOS/libsubstance_framework.a.meta | 91 + .../Substance/iOS/libsubstance_linker.a | 3 + .../Substance/iOS/libsubstance_linker.a.meta | 91 + .../Substance/iOS/libsubstance_neon_blend.a | 3 + .../iOS/libsubstance_neon_blend.a.meta | 91 + .../Plugins/Substance/iOS/libtinyxml.a | 3 + .../Plugins/Substance/iOS/libtinyxml.a.meta | 91 + .../Plugins/engine_build_info.txt | 4 + .../Plugins/engine_build_info.txt.meta} | 4 +- .../Plugins/libSubstance.Engine.so | Bin 0 -> 1666024 bytes .../Plugins/libSubstance.Engine.so.meta | 27 + .../Plugins/libsubstance_sse2_blend.bundle | 3 + .../libsubstance_sse2_blend.bundle.meta | 32 + .../Plugins/libsubstance_sse2_blend.so | Bin 0 -> 875112 bytes .../Plugins/libsubstance_sse2_blend.so.meta | 27 + .../Plugins/substance_sse2_blend.dll | 3 + .../Plugins/substance_sse2_blend.dll.meta | 27 + Assets/DefaultVolumeProfile.asset | 3 - Assets/DefaultVolumeProfile.asset.meta | 8 - .../AetheriaCultUiCompositionSurfaceWindow.cs | 393 - .../Editor/AetheriaEditorPlayModeTrigger.cs | 93 - Assets/Editor/AetheriaGravityFogVerifier.cs | 41 - .../Editor/AetheriaGravityFogVerifier.cs.meta | 2 - Assets/Editor/AetheriaRenderProbe.cs | 363 - .../AetheriaStardustContinuityVerifier.cs | 240 - ...AetheriaStardustContinuityVerifier.cs.meta | 2 - Assets/Editor/AetheriaUrpBootstrap.cs | 295 - Assets/Editor/AetheriaUrpBootstrap.cs.meta | 2 - Assets/Editor/EveAssetBundleBuilder.cs | 748 - Assets/Editor/EveAssetBundleBuilder.cs.meta | 2 - .../Editor/EveEnvironmentProfileMigrator.cs | 49 - .../EveEnvironmentProfileMigrator.cs.meta | 2 - Assets/Editor/EveThermalProfileMigrator.cs | 78 - .../Editor/EveThermalProfileMigrator.cs.meta | 2 - Assets/Materials/Aluminium brushed.mat | 21 +- Assets/Materials/Black.mat | 24 +- Assets/Materials/Car Paint Magenta.mat | 22 +- Assets/Materials/Car Paint Red.mat | 36 +- Assets/Materials/Car Paint White Small.mat | 23 +- Assets/Materials/Car Paint White.mat | 22 +- Assets/Materials/Cockpit.mat | 25 +- Assets/Materials/Radiators 1.mat | 31 +- Assets/Materials/Radiators.mat | 23 +- Assets/Materials/Steel brushed.mat | 37 +- Assets/Materials/Steel parkerized.mat | 23 +- Assets/Materials/Steel.mat | 27 +- Assets/Materials/Substance.meta | 8 + Assets/Materials/Substance/Metal.meta | 8 + .../Substance/Metal/Pickup Base.sbsar | 3 + .../Substance/Metal/Pickup Base.sbsar.meta | 382 + .../Substance/Metal/Pickup Trim.sbsar | 3 + .../Substance/Metal/Pickup Trim.sbsar.meta | 381 + .../Substance/Metal/galvanized_001.sbsar | 3 + .../Substance/Metal/galvanized_001.sbsar.meta | 369 + .../Substance/Metal/iron_damaged.sbsar | 3 + .../Substance/Metal/iron_damaged.sbsar.meta | 293 + .../Substance/Metal/iron_rugged.sbsar | 3 + .../Substance/Metal/iron_rugged.sbsar.meta | 293 + .../Materials/Substance/Metal/metal_002.sbsar | 3 + .../Substance/Metal/metal_002.sbsar.meta | 372 + .../Materials/Substance/Metal/metal_003.sbsar | 3 + .../Substance/Metal/metal_003.sbsar.meta | 372 + .../Materials/Substance/Metal/metal_006.sbsar | 3 + .../Substance/Metal/metal_006.sbsar.meta | 369 + .../Substance/Metal/metal_aluminium.sbsar | 3 + .../Metal/metal_aluminium.sbsar.meta | 373 + .../Metal/metal_aluminium_brushed.sbsar | 3 + .../Metal/metal_aluminium_brushed.sbsar.meta | 373 + .../Substance/Metal/metal_brass.sbsar | 3 + .../Substance/Metal/metal_brass.sbsar.meta | 375 + .../Substance/Metal/metal_car_paint 1.sbsar | 3 + .../Metal/metal_car_paint 1.sbsar.meta | 379 + .../Metal/metal_car_paint Magenta.sbsar | 3 + .../Metal/metal_car_paint Magenta.sbsar.meta | 381 + .../Substance/Metal/metal_car_paint red.sbsar | 3 + .../Metal/metal_car_paint red.sbsar.meta | 381 + .../Metal/metal_car_paint white.sbsar | 3 + .../Metal/metal_car_paint white.sbsar.meta | 382 + .../Substance/Metal/metal_copper.sbsar | 3 + .../Substance/Metal/metal_copper.sbsar.meta | 369 + .../Metal/metal_corrugated_002.sbsar | 3 + .../Metal/metal_corrugated_002.sbsar.meta | 369 + .../Substance/Metal/metal_floor_002.sbsar | 3 + .../Metal/metal_floor_002.sbsar.meta | 369 + .../Substance/Metal/metal_floor_004.sbsar | 3 + .../Metal/metal_floor_004.sbsar.meta | 369 + .../Substance/Metal/metal_floor_006.sbsar | 3 + .../Metal/metal_floor_006.sbsar.meta | 369 + .../Substance/Metal/metal_gold.sbsar | 3 + .../Substance/Metal/metal_gold.sbsar.meta | 373 + .../Substance/Metal/metal_nickel.sbsar | 3 + .../Substance/Metal/metal_nickel.sbsar.meta | 369 + .../Substance/Metal/metal_painted_steel.sbsar | 3 + .../Metal/metal_painted_steel.sbsar.meta | 376 + .../Substance/Metal/metal_panels_001.sbsar | 3 + .../Metal/metal_panels_001.sbsar.meta | 369 + .../Substance/Metal/metal_panels_002.sbsar | 3 + .../Metal/metal_panels_002.sbsar.meta | 369 + .../Substance/Metal/metal_plate_001.sbsar | 3 + .../Metal/metal_plate_001.sbsar.meta | 369 + .../Substance/Metal/metal_plate_004.sbsar | 3 + .../Metal/metal_plate_004.sbsar.meta | 369 + .../Substance/Metal/metal_plate_011.sbsar | 3 + .../Metal/metal_plate_011.sbsar.meta | 369 + .../Substance/Metal/metal_platinum.sbsar | 3 + .../Substance/Metal/metal_platinum.sbsar.meta | 372 + .../Substance/Metal/metal_roll_up.sbsar | 3 + .../Substance/Metal/metal_roll_up.sbsar.meta | 369 + .../Substance/Metal/metal_silver.sbsar | 3 + .../Substance/Metal/metal_silver.sbsar.meta | 369 + .../Substance/Metal/metal_steel.sbsar | 3 + .../Substance/Metal/metal_steel.sbsar.meta | 375 + .../Substance/Metal/metal_steel_brushed.sbsar | 3 + .../Metal/metal_steel_brushed.sbsar.meta | 370 + .../Substance/Metal/metal_steel_bumped.sbsar | 3 + .../Metal/metal_steel_bumped.sbsar.meta | 369 + .../Metal/metal_steel_parkerized.sbsar | 3 + .../Metal/metal_steel_parkerized.sbsar.meta | 369 + .../Substance/Metal/metal_titanium.sbsar | 3 + .../Substance/Metal/metal_titanium.sbsar.meta | 369 + .../Substance/Metal/metal_used.sbsar | 3 + .../Substance/Metal/metal_used.sbsar.meta | 371 + .../Substance/Metal/metal_weapon.sbsar | 3 + .../Substance/Metal/metal_weapon.sbsar.meta | 373 + .../Substance/Metal/metal_woven.sbsar | 3 + .../Substance/Metal/metal_woven.sbsar.meta | 369 + .../Substance/Metal/snatched_metal.sbsar | 3 + .../Substance/Metal/snatched_metal.sbsar.meta | 311 + Assets/Materials/Substance/marble_002.sbsar | 3 + .../Materials/Substance/marble_002.sbsar.meta | 296 + .../Scripts/CelestialBodyGenerator.cs | 40 +- .../Celestial Body/Scripts/MeshBaker.cs | 48 + .../Celestial Body/Scripts/MeshBaker.cs.meta} | 2 +- .../Routing/Spline/Bundling/TimeMeasurer.cs | 4 +- ...urceDisabled.asmdef => MessagePack.asmdef} | 10 +- ...ed.asmdef.meta => MessagePack.asmdef.meta} | 0 .../TypelessContractlessStandardResolver.cs | 8 +- ...m.Runtime.CompilerServices.Unsafe.dll.meta | 4 +- Assets/Prefabs/RPG/Effects/Mine.prefab | 88 - Assets/Prefabs/RPG/Effects/Mine.prefab.meta | 7 - Assets/Prefabs/UI/Context Menu Option.prefab | 406 + .../UI/Context Menu Option.prefab.meta | 7 + Assets/Prefabs/UI/Context Menu.prefab | 279 + Assets/Prefabs/UI/Context Menu.prefab.meta | 7 + Assets/Prefabs/UI/Dropdown Menu.prefab | 283 + Assets/Prefabs/UI/Dropdown Menu.prefab.meta | 7 + .../Prefabs/UI/Input Display Container.prefab | 1 + Assets/Prefabs/UI/Inventory.prefab | 1 + Assets/Prefabs/UI/Main Menu Canvas.prefab | 1103 + .../Fancy/Weapon Group.prefab | 397 + .../Fancy/Weapon Group.prefab.meta | 7 + .../Fancy/Weapon Groups.prefab | 330 + .../Fancy/Weapon Groups.prefab.meta | 7 + .../UI/Properties Panel/Properties.prefab | 831 + .../Properties Panel/Properties.prefab.meta | 7 + .../Properties Panel/Property List 1.prefab | 529 + .../Property List 1.prefab.meta | 7 + .../UI/Properties Panel/Property List.prefab | 529 + .../Property List.prefab.meta | 7 + .../Fog Patch Displacement.renderTexture | 2 +- Assets/Resources/Fog Patch.renderTexture | 2 +- .../Fog Surface Displacement.renderTexture | 2 +- Assets/Resources/Fog Tint.renderTexture | 2 +- Assets/Resources/Influence.renderTexture | 2 +- Assets/Resources/Minimap.renderTexture | 2 +- Assets/Resources/MinimapGravity.renderTexture | 2 +- .../Resources/MinimapInfluence.renderTexture | 2 +- .../Prefabs/Weapons/Mine Launcher.prefab | 336 + .../Prefabs/Weapons/Mine Launcher.prefab.meta | 7 + Assets/Resources/Settings.asset | 4 +- Assets/Resources/Slime Gravity.renderTexture | 2 +- Assets/Resources/UnityDefaultRuntimeTheme.tss | 2 - .../UnityDefaultRuntimeTheme.tss.meta | 12 - Assets/Resources/ansi104.json | 214 + Assets/Resources/ansi104.json.meta | 7 + Assets/Samples/Localization.meta | 8 + .../Samples/Localization/0.11.1-preview.meta | 8 + .../0.11.1-preview/Google Sheets.meta | 8 + .../0.11.1-preview/Google Sheets/.sample.json | 5 + .../0.11.1-preview/Google Sheets/Editor.meta | 8 + .../Editor/CustomColumnDataExample.cs | 53 + .../Editor/CustomColumnDataExample.cs.meta} | 2 +- .../Editor/GoogleSheetsExamples.cs | 245 + .../Editor/GoogleSheetsExamples.cs.meta | 11 + .../Google Sheets/Editor/SmartStringColumn.cs | 33 + .../Editor/SmartStringColumn.cs.meta | 11 + Assets/Scenes/ARPG.unity | 1725 +- .../Postprocessing Profile Main Menu.asset | 4 +- Assets/Scenes/FieldShieldTest.unity | 414 +- Assets/Scenes/Main Menu.unity | 489 +- Assets/Scripts/AetheriaInput.cs | 3 +- .../{UnitySupport.meta => CultCache.meta} | 0 Assets/Scripts/CultCache/Editor.meta | 8 + .../CultCache/Editor/AetheriaDatabaseView.cs | 46 + .../Editor/AetheriaDatabaseView.cs.meta | 3 + .../Scripts/CultCache/Editor/BaseInspector.cs | 31 + .../CultCache/Editor/BaseInspector.cs.meta | 3 + .../CultCache/Editor/DatabaseInspector.cs | 293 + .../Editor/DatabaseInspector.cs.meta | 11 + .../Scripts/CultCache/Editor/DatabaseView.cs | 363 + .../CultCache/Editor/DatabaseView.cs.meta | 11 + .../CultCache/Editor/EditorDispatch.cs | 81 + .../CultCache/Editor/EditorDispatch.cs.meta | 11 + .../Scripts/CultCache/Editor/Inspectors.meta | 8 + .../Editor/Inspectors/AetheriaInspectors.cs | 234 + .../Inspectors/AetheriaInspectors.cs.meta | 3 + .../Inspectors/AnimationCurveInspector.cs | 58 + .../AnimationCurveInspector.cs.meta | 3 + .../Inspectors/DatabaseLinkInspector.cs | 169 + .../Inspectors/DatabaseLinkInspector.cs.meta | 3 + .../Editor/Inspectors/DictionaryInspector.cs | 211 + .../Inspectors/DictionaryInspector.cs.meta | 3 + .../Editor/Inspectors/EnumValuesInspector.cs | 59 + .../Inspectors/EnumValuesInspector.cs.meta | 3 + .../Editor/Inspectors/FloatInspector.cs | 54 + .../Editor/Inspectors/FloatInspector.cs.meta | 3 + .../Editor/Inspectors/IntInspector.cs | 40 + .../Editor/Inspectors/IntInspector.cs.meta | 3 + .../Editor/Inspectors/ListInspector.cs | 132 + .../Editor/Inspectors/ListInspector.cs.meta | 3 + .../Editor/Inspectors/MathematicsInspector.cs | 144 + .../Inspectors/MathematicsInspector.cs.meta | 11 + .../Editor/Inspectors/PrimitiveInspector.cs | 40 + .../Inspectors/PrimitiveInspector.cs.meta | 3 + .../Editor/Inspectors/TypeInspector.cs | 23 + .../Editor/Inspectors/TypeInspector.cs.meta | 3 + .../Editor/Inspectors/UnityObjectInspector.cs | 47 + .../Inspectors/UnityObjectInspector.cs.meta | 3 + .../UnityExtensions.cs | 12 +- .../UnityExtensions.cs.meta | 0 .../{UnitySupport => CultCache}/XKCDColors.cs | 0 .../XKCDColors.cs.meta | 0 Assets/Scripts/CultClient.cs | 167 + Assets/Scripts/CultClient.cs.meta | 11 + .../Scripts/Editor/GameplaySettingsEditor.cs | 6 +- Assets/Scripts/Editor/NameTools.cs | 235 +- Assets/Scripts/Editor/SerializerTestWindow.cs | 118 + .../Editor/SerializerTestWindow.cs.meta | 11 + Assets/Scripts/Gameplay/ActionBarSlot.cs | 231 +- Assets/Scripts/Gameplay/ActionGameManager.cs | 1475 +- .../AetheriaDaemonIndirectRenderer.cs | 266 - .../AetheriaDaemonIndirectRenderer.cs.meta | 2 - .../Gameplay/AetheriaDaemonObserver.cs | 168 - .../Gameplay/AetheriaDaemonObserver.cs.meta | 2 - .../AetheriaDaemonRenderAssetCatalog.cs | 118 - .../AetheriaDaemonRenderAssetCatalog.cs.meta | 2 - .../Gameplay/AetheriaDaemonRenderBuffer.cs | 292 - .../AetheriaDaemonRenderBuffer.cs.meta | 2 - .../AetheriaDaemonRenderGroupBatch.cs | 175 - .../AetheriaDaemonRenderGroupBatch.cs.meta | 2 - .../AetheriaDaemonRenderGroupMatrixJob.cs | 86 - ...AetheriaDaemonRenderGroupMatrixJob.cs.meta | 2 - .../Gameplay/AetheriaDaemonRenderMatrixJob.cs | 43 - .../AetheriaDaemonRenderMatrixJob.cs.meta | 2 - .../AetheriaDaemonRenderNativeView.cs | 248 - .../AetheriaDaemonRenderNativeView.cs.meta | 2 - .../Gameplay/AetheriaDaemonSoaMemoryMap.cs | 220 - .../AetheriaDaemonSoaMemoryMap.cs.meta | 2 - .../AetheriaRenderSplatLayerRenderer.cs | 95 - .../Gameplay/AetheriaRenderSplatRasterizer.cs | 6 - .../AetheriaRenderSplatRasterizer.cs.meta | 11 - .../Gameplay/AetheriaUnityActionBarBinding.cs | 13 - .../AetheriaUnityActionBarBinding.cs.meta | 2 - .../AetheriaUnityActionBarPresentation.cs | 273 - ...AetheriaUnityActionBarPresentation.cs.meta | 2 - .../Gameplay/AetheriaUnityCockpitHudShell.cs | 40 - .../AetheriaUnityCockpitHudShell.cs.meta | 2 - .../AetheriaUnityCurrentEntityBinder.cs | 87 - .../AetheriaUnityCurrentEntityBinder.cs.meta | 2 - .../AetheriaUnityCurrentEntityPresentation.cs | 228 - ...eriaUnityCurrentEntityPresentation.cs.meta | 2 - .../AetheriaUnityDaemonRenderDocuments.cs | 44 - ...AetheriaUnityDaemonRenderDocuments.cs.meta | 2 - .../Gameplay/AetheriaUnityDragObjects.cs | 45 - .../Gameplay/AetheriaUnityDragObjects.cs.meta | 2 - .../Gameplay/AetheriaUnityDragSession.cs | 37 - .../Gameplay/AetheriaUnityDragSession.cs.meta | 2 - ...etheriaUnityEntityBlueprintMaterializer.cs | 161 - ...iaUnityEntityBlueprintMaterializer.cs.meta | 2 - .../AetheriaUnityGameViewportDocuments.cs | 102 - ...AetheriaUnityGameViewportDocuments.cs.meta | 2 - .../AetheriaUnityGameplayBootShell.cs | 141 - .../AetheriaUnityGameplayBootShell.cs.meta | 2 - .../AetheriaUnityGameplayInputShell.cs | 218 - .../AetheriaUnityGameplayInputShell.cs.meta | 2 - .../AetheriaUnityGameplayLoopShell.cs | 35 - .../AetheriaUnityGameplayLoopShell.cs.meta | 2 - .../AetheriaUnityGameplaySceneWiring.cs | 165 - .../AetheriaUnityGameplaySceneWiring.cs.meta | 11 - .../AetheriaUnityLoadoutItemFactory.cs | 63 - .../AetheriaUnityLoadoutItemFactory.cs.meta | 2 - .../Gameplay/AetheriaUnityMenuShell.cs | 129 - .../Gameplay/AetheriaUnityMenuShell.cs.meta | 2 - .../AetheriaUnityObservedEntityRestorer.cs | 346 - ...etheriaUnityObservedEntityRestorer.cs.meta | 2 - .../AetheriaUnityObservedFrameApplier.cs | 159 - .../AetheriaUnityObservedFrameApplier.cs.meta | 2 - ...AetheriaUnityObservedZoneContextFactory.cs | 198 - ...riaUnityObservedZoneContextFactory.cs.meta | 2 - .../AetheriaUnityPilotCommandSender.cs | 122 - .../AetheriaUnityPilotCommandSender.cs.meta | 2 - .../AetheriaUnityPilotFrameController.cs | 75 - .../AetheriaUnityPilotFrameController.cs.meta | 2 - .../AetheriaUnityPilotOperationController.cs | 114 - ...heriaUnityPilotOperationController.cs.meta | 2 - .../AetheriaUnityPresentationEntityIndex.cs | 109 - ...theriaUnityPresentationEntityIndex.cs.meta | 2 - .../AetheriaUnityRenderSplatViewportSource.cs | 168 - ...eriaUnityRenderSplatViewportSource.cs.meta | 11 - .../AetheriaUnityRuntimeClientProvider.cs | 242 - ...AetheriaUnityRuntimeClientProvider.cs.meta | 2 - .../Gameplay/AetheriaUnityRuntimePaths.cs | 20 - .../AetheriaUnityRuntimePaths.cs.meta | 2 - .../AetheriaUnityTargetPresentation.cs | 296 - .../AetheriaUnityTargetPresentation.cs.meta | 2 - .../AetheriaUnityWeaponPresentationPolicy.cs | 36 - ...heriaUnityWeaponPresentationPolicy.cs.meta | 11 - Assets/Scripts/Gameplay/ArticulationPoint.cs | 26 +- Assets/Scripts/Gameplay/EntityInstance.cs | 302 +- Assets/Scripts/Gameplay/FieldDriver.cs | 185 +- Assets/Scripts/Gameplay/GameSettings.cs | 3 +- Assets/Scripts/Gameplay/GridObject.cs | 26 +- Assets/Scripts/Gameplay/HullCollider.cs | 106 +- Assets/Scripts/Gameplay/ItemPickup.cs | 17 +- Assets/Scripts/Gameplay/Physics.meta | 8 - .../Physics/AetheriaYmirPhysicsBridge.cs | 109 - .../Physics/AetheriaYmirPhysicsBridge.cs.meta | 2 - Assets/Scripts/Gameplay/ShieldManager.cs | 76 +- Assets/Scripts/Gameplay/ShieldTest.cs | 15 +- Assets/Scripts/Gameplay/ShipInstance.cs | 35 +- Assets/Scripts/Gameplay/TimedDestroy.cs | 19 + Assets/Scripts/Gameplay/TimedDestroy.cs.meta | 3 + Assets/Scripts/Gameplay/TractorBeam.cs | 17 +- .../Scripts/Gameplay/Weapons/ConstantLaser.cs | 111 + .../Gameplay/Weapons/ConstantLaser.cs.meta | 11 + .../Gameplay/Weapons/ConstantLaserManager.cs | 34 + .../Weapons/ConstantLaserManager.cs.meta | 11 + .../Gameplay/Weapons/ConstantLightning.cs | 87 + .../Weapons/ConstantLightning.cs.meta | 11 + .../Weapons/ConstantLightningManager.cs | 32 + .../Weapons/ConstantLightningManager.cs.meta | 3 + .../Weapons/ConstantParticleWeapon.cs | 86 + .../Weapons/ConstantParticleWeapon.cs.meta | 3 + .../Weapons/ConstantParticleWeaponManager.cs | 33 + .../ConstantParticleWeaponManager.cs.meta | 3 + .../Weapons/ConstantWeaponEffectManager.cs | 9 + .../ConstantWeaponEffectManager.cs.meta | 11 + .../Gameplay/Weapons/GuidedProjectile.cs | 226 + .../Gameplay/Weapons/GuidedProjectile.cs.meta | 11 + .../Weapons/GuidedProjectileManager.cs | 65 + .../Weapons/GuidedProjectileManager.cs.meta | 11 + .../Scripts/Gameplay/Weapons/HitscanEffect.cs | 94 + .../Gameplay/Weapons/HitscanEffect.cs.meta | 11 + .../Gameplay/Weapons/HitscanManager.cs | 26 + .../Gameplay/Weapons/HitscanManager.cs.meta | 3 + .../Weapons/InstantWeaponEffectManager.cs | 8 + .../InstantWeaponEffectManager.cs.meta | 11 + Assets/Scripts/Gameplay/Weapons/Laser.cs | 72 + Assets/Scripts/Gameplay/Weapons/Laser.cs.meta | 11 + .../Scripts/Gameplay/Weapons/LaserManager.cs | 25 + .../Gameplay/Weapons/LaserManager.cs.meta | 11 + Assets/Scripts/Gameplay/Weapons/Lightning.cs | 80 + .../Gameplay/Weapons/Lightning.cs.meta | 11 + .../Gameplay/Weapons/LightningGunManager.cs | 24 + .../Weapons/LightningGunManager.cs.meta | 11 + Assets/Scripts/Gameplay/Weapons/Mine.cs | 111 + Assets/Scripts/Gameplay/Weapons/Mine.cs.meta | 3 + .../Scripts/Gameplay/Weapons/MineManager.cs | 34 + .../Gameplay/Weapons/MineManager.cs.meta | 3 + .../Gameplay/Weapons/ParticleChargeEffect.cs | 25 +- Assets/Scripts/Gameplay/Weapons/Projectile.cs | 125 + .../Gameplay/Weapons/Projectile.cs.meta | 11 + .../Gameplay/Weapons/ProjectileManager.cs | 38 + .../Weapons/ProjectileManager.cs.meta | 11 + Assets/Scripts/Gravity.cs | 5 +- Assets/Scripts/InitialSettings.cs | 23 + Assets/Scripts/InitialSettings.cs.meta | 11 + .../AetheriaStardustRendererFeature.cs | 59 - .../AetheriaStardustRendererFeature.cs.meta | 2 - .../AetheriaVolumeCloudRendererFeature.cs | 65 - ...AetheriaVolumeCloudRendererFeature.cs.meta | 2 - .../AetheriaVolumeSamplingRendererFeature.cs | 44 - ...heriaVolumeSamplingRendererFeature.cs.meta | 2 - .../Scripts/ServerShared/ActionCollection.cs | 32 + .../ServerShared/ActionCollection.cs.meta | 11 + .../ServerShared/Aetheria.Shared.Unity.asmdef | 8 +- Assets/Scripts/ServerShared/AetheriaMath.cs | 148 +- .../AetheriaRuntimeItemReference.cs | 30 - Assets/Scripts/ServerShared/Agents/Agent.cs | 20 +- .../ServerShared/Agents/States/Combat.cs | 38 +- .../Agents/States/MatchVelocity.cs | 17 +- .../ServerShared/Agents/States/MoveTo.cs | 17 +- .../ServerShared/Agents/Tasks/AgentTask.cs | 30 +- .../ServerShared/Agents/Tasks/HaulingTask.cs | 22 +- .../ServerShared/Agents/Tasks/Mining.cs | 20 +- .../ServerShared/Agents/Tasks/PatrolOrbits.cs | 8 +- .../Agents/Tasks/StationTowing.cs | 22 +- .../ServerShared/Agents/Tasks/Survey.cs | 18 +- Assets/Scripts/ServerShared/Attributes.cs | 27 + .../Scripts/ServerShared/Attributes.cs.meta | 11 + .../ServerShared/Behaviors/AetherDrive.cs | 210 +- .../ServerShared/Behaviors/AutoWeapon.cs | 23 +- .../ServerShared/Behaviors/Behaviors.cs | 281 +- .../ServerShared/Behaviors/Capacitor.cs | 56 +- .../ServerShared/Behaviors/ChargedWeapon.cs | 176 +- .../Scripts/ServerShared/Behaviors/Cockpit.cs | 35 +- .../ServerShared/Behaviors/ConstantWeapon.cs | 86 +- .../ServerShared/Behaviors/Cooldown.cs | 42 +- .../ServerShared/Behaviors/EnergyDraw.cs | 44 +- Assets/Scripts/ServerShared/Behaviors/Heat.cs | 45 +- .../ServerShared/Behaviors/HeatStorage.cs | 35 +- .../ServerShared/Behaviors/InstantWeapon.cs | 122 +- .../ServerShared/Behaviors/ItemUsage.cs | 41 +- .../ServerShared/Behaviors/Launcher.cs | 61 + .../ServerShared/Behaviors/Launcher.cs.meta | 3 + .../ServerShared/Behaviors/LockWeapon.cs | 116 +- .../ServerShared/Behaviors/MiningTool.cs | 93 +- .../ServerShared/Behaviors/Radiator.cs | 115 +- .../Scripts/ServerShared/Behaviors/Reactor.cs | 87 +- .../ServerShared/Behaviors/Reflector.cs | 43 +- .../ServerShared/Behaviors/ResourceScanner.cs | 130 +- .../Scripts/ServerShared/Behaviors/Sensor.cs | 141 +- .../Scripts/ServerShared/Behaviors/Shield.cs | 54 +- .../ServerShared/Behaviors/StatModifier.cs | 183 +- .../Scripts/ServerShared/Behaviors/Switch.cs | 28 +- .../ServerShared/Behaviors/Thermotoggle.cs | 51 +- .../ServerShared/Behaviors/Thruster.cs | 106 +- .../Scripts/ServerShared/Behaviors/Trigger.cs | 30 +- .../Behaviors/TurretController.cs | 66 +- .../Behaviors/VelocityConversion.cs | 39 +- .../ServerShared/Behaviors/VelocityLimit.cs | 52 +- .../ServerShared/Behaviors/Visibility.cs | 43 +- .../Scripts/ServerShared/Behaviors/Weapon.cs | 251 +- Assets/Scripts/ServerShared/Behaviors/Wear.cs | 37 +- Assets/Scripts/ServerShared/BezierCurve.cs | 47 +- Assets/Scripts/ServerShared/ColorMath.cs | 39 +- Assets/Scripts/ServerShared/Corporations.cs | 77 +- Assets/Scripts/ServerShared/CultCache.meta | 8 + .../ServerShared/CultCache/Attributes.cs | 146 + .../ServerShared/CultCache/Attributes.cs.meta | 11 + .../CultCache/CollectionExtensions.cs | 76 + .../CultCache/CollectionExtensions.cs.meta | 11 + .../ServerShared/CultCache/CultCache.cs | 574 + .../ServerShared/CultCache/CultCache.cs.meta | 11 + .../ServerShared/CultCache/DatabaseEntry.cs | 91 + .../DatabaseEntry.cs.meta} | 0 .../CultCache/ReflectionExtensions.cs | 120 + .../CultCache/ReflectionExtensions.cs.meta | 3 + .../CultCache/RethinkConnection.cs | 34 + .../CultCache/RethinkConnection.cs.meta | 11 + .../ServerShared/CultCache/Serialization.meta | 8 + .../CultCache/Serialization/JsonConverters.cs | 169 + .../Serialization/JsonConverters.cs.meta | 11 + .../Serialization/JsonKnownTypes.meta | 8 + .../JsonKnownTypes/AttributesManager.cs | 16 + .../JsonKnownTypes/AttributesManager.cs.meta | 11 + .../JsonKnownTypes/Exceptions.meta | 8 + .../Exceptions/JsonKnownTypesException.cs | 15 + .../JsonKnownTypesException.cs.meta | 11 + .../JsonDiscriminatorAttribute.cs | 18 + .../JsonDiscriminatorAttribute.cs.meta | 11 + .../JsonDiscriminatorSettings.cs | 8 + .../JsonDiscriminatorSettings.cs.meta | 11 + .../JsonKnownThisTypeAttribute.cs | 17 + .../JsonKnownThisTypeAttribute.cs.meta | 11 + .../JsonKnownTypes/JsonKnownTypeAttribute.cs | 22 + .../JsonKnownTypeAttribute.cs.meta | 11 + .../JsonKnownTypes/JsonKnownTypes.csproj | 22 + .../JsonKnownTypes/JsonKnownTypes.csproj.meta | 7 + .../JsonKnownTypesContractResolver.cs | 16 + .../JsonKnownTypesContractResolver.cs.meta | 11 + .../JsonKnownTypes/JsonKnownTypesConverter.cs | 57 + .../JsonKnownTypesConverter.cs.meta | 11 + .../JsonKnownTypesDictionaryManager.cs | 64 + .../JsonKnownTypesDictionaryManager.cs.meta | 11 + .../JsonKnownTypes/JsonKnownTypesSettings.cs | 12 + .../JsonKnownTypesSettings.cs.meta | 11 + .../JsonKnownTypesSettingsManager.cs | 60 + .../JsonKnownTypesSettingsManager.cs.meta | 11 + .../Serialization/JsonKnownTypes/Utils.meta | 8 + .../JsonKnownTypes/Utils/Mapper.cs | 15 + .../JsonKnownTypes/Utils/Mapper.cs.meta | 11 + .../CultCache/Serialization/MathFormatters.cs | 226 + .../Serialization/MathFormatters.cs.meta | 11 + .../CultCache/Serialization/MathResolver.cs | 80 + .../Serialization/MathResolver.cs.meta | 11 + .../Serialization/RegisterResolver.cs | 42 + .../Serialization/RegisterResolver.cs.meta | 11 + .../Serialization/TypeFormatterResolver.cs | 66 + .../TypeFormatterResolver.cs.meta | 11 + Assets/Scripts/ServerShared/Entity.cs | 1363 +- ...EntityConstructionBlueprintMaterializer.cs | 204 - .../Scripts/ServerShared/EntitySerializer.cs | 248 + ...lizer.cs.meta => EntitySerializer.cs.meta} | 0 Assets/Scripts/ServerShared/EntitySettings.cs | 14 +- Assets/Scripts/ServerShared/EnumDictionary.cs | 8 +- .../EnumerableSelectionExtensions.cs | 54 - .../EnumerableSelectionExtensions.cs.meta | 11 - Assets/Scripts/ServerShared/Environment.cs | 173 +- .../Scripts/ServerShared/ExponentialCurve.cs | 27 +- Assets/Scripts/ServerShared/Extensions.cs | 57 +- Assets/Scripts/ServerShared/Galaxy.cs | 502 +- Assets/Scripts/ServerShared/GlobalData.cs | 125 + .../Scripts/ServerShared/GlobalData.cs.meta | 11 + Assets/Scripts/ServerShared/ItemData.cs | 650 + Assets/Scripts/ServerShared/ItemData.cs.meta | 3 + Assets/Scripts/ServerShared/ItemInstance.cs | 49 +- Assets/Scripts/ServerShared/ItemManager.cs | 420 +- .../Scripts/ServerShared/LoadoutGenerator.cs | 335 +- .../ServerShared/MarkovNameGenerator.cs | 6 +- Assets/Scripts/ServerShared/Messages.cs | 84 + Assets/Scripts/ServerShared/Messages.cs.meta | 11 + .../Scripts/ServerShared/NIH/LiteNetLib.meta | 8 + .../NIH/LiteNetLib/BaseChannel.cs | 31 + .../NIH/LiteNetLib/BaseChannel.cs.meta | 11 + .../NIH/LiteNetLib/ConnectionRequest.cs | 146 + .../NIH/LiteNetLib/ConnectionRequest.cs.meta | 11 + .../NIH/LiteNetLib/INetEventListener.cs | 222 + .../NIH/LiteNetLib/INetEventListener.cs.meta | 11 + .../ServerShared/NIH/LiteNetLib/Layers.meta | 8 + .../NIH/LiteNetLib/Layers/Crc32cLayer.cs | 36 + .../NIH/LiteNetLib/Layers/Crc32cLayer.cs.meta | 11 + .../NIH/LiteNetLib/Layers/PacketLayerBase.cs | 15 + .../LiteNetLib/Layers/PacketLayerBase.cs.meta | 11 + .../NIH/LiteNetLib/Layers/XorEncryptLayer.cs | 59 + .../LiteNetLib/Layers/XorEncryptLayer.cs.meta | 11 + .../NIH/LiteNetLib/LiteNetLib.csproj | 30 + .../NIH/LiteNetLib/LiteNetLib.csproj.meta | 7 + .../NIH/LiteNetLib/NatPunchModule.cs | 239 + .../NIH/LiteNetLib/NatPunchModule.cs.meta | 11 + .../NIH/LiteNetLib/NetConstants.cs | 72 + .../NIH/LiteNetLib/NetConstants.cs.meta | 11 + .../ServerShared/NIH/LiteNetLib/NetDebug.cs | 78 + .../NIH/LiteNetLib/NetDebug.cs.meta | 11 + .../NIH/LiteNetLib/NetExceptions.cs | 33 + .../NIH/LiteNetLib/NetExceptions.cs.meta | 11 + .../ServerShared/NIH/LiteNetLib/NetManager.cs | 1588 ++ .../NIH/LiteNetLib/NetManager.cs.meta | 11 + .../ServerShared/NIH/LiteNetLib/NetPacket.cs | 243 + .../NIH/LiteNetLib/NetPacket.cs.meta | 11 + .../NIH/LiteNetLib/NetPacketPool.cs | 86 + .../NIH/LiteNetLib/NetPacketPool.cs.meta | 11 + .../ServerShared/NIH/LiteNetLib/NetPeer.cs | 1131 + .../NIH/LiteNetLib/NetPeer.cs.meta | 11 + .../ServerShared/NIH/LiteNetLib/NetSocket.cs | 339 + .../NIH/LiteNetLib/NetSocket.cs.meta | 11 + .../NIH/LiteNetLib/NetStatistics.cs | 39 + .../NIH/LiteNetLib/NetStatistics.cs.meta | 11 + .../ServerShared/NIH/LiteNetLib/NetUtils.cs | 197 + .../NIH/LiteNetLib/NetUtils.cs.meta | 11 + .../NIH/LiteNetLib/ReliableChannel.cs | 310 + .../NIH/LiteNetLib/ReliableChannel.cs.meta | 11 + .../NIH/LiteNetLib/SequencedChannel.cs | 93 + .../NIH/LiteNetLib/SequencedChannel.cs.meta | 11 + .../ServerShared/NIH/LiteNetLib/Utils.meta | 8 + .../NIH/LiteNetLib/Utils/CRC32C.cs | 102 + .../NIH/LiteNetLib/Utils/CRC32C.cs.meta | 11 + .../NIH/LiteNetLib/Utils/FastBitConverter.cs | 118 + .../LiteNetLib/Utils/FastBitConverter.cs.meta | 11 + .../NIH/LiteNetLib/Utils/INetSerializable.cs | 8 + .../LiteNetLib/Utils/INetSerializable.cs.meta | 11 + .../NIH/LiteNetLib/Utils/NetDataReader.cs | 707 + .../LiteNetLib/Utils/NetDataReader.cs.meta | 11 + .../NIH/LiteNetLib/Utils/NetDataWriter.cs | 415 + .../LiteNetLib/Utils/NetDataWriter.cs.meta | 11 + .../LiteNetLib/Utils/NetPacketProcessor.cs | 314 + .../Utils/NetPacketProcessor.cs.meta | 11 + .../NIH/LiteNetLib/Utils/NetSerializer.cs | 601 + .../LiteNetLib/Utils/NetSerializer.cs.meta | 11 + .../NIH/LiteNetLib/Utils/NtpPacket.cs | 428 + .../NIH/LiteNetLib/Utils/NtpPacket.cs.meta | 11 + .../NIH/LiteNetLib/Utils/NtpRequest.cs | 142 + .../NIH/LiteNetLib/Utils/NtpRequest.cs.meta | 11 + .../ServerShared/NIH/MIConvexHull/Cell2.cs | 32 +- .../Scripts/ServerShared/NIH/RethinkDb.meta | 8 + .../NIH/RethinkDb/RethinkDB.asmdef | 3 + .../NIH/RethinkDb/RethinkDB.asmdef.meta | 7 + .../NIH/RethinkDb/RethinkDb.Driver.Linq.meta | 8 + .../RethinkDb.Driver.Linq/Attributes.meta | 8 + .../Attributes/PrimaryIndexAttribute.cs | 9 + .../Attributes/PrimaryIndexAttribute.cs.meta | 11 + .../Attributes/SecondaryIndexAttribute.cs | 8 + .../SecondaryIndexAttribute.cs.meta | 11 + .../RethinkDb.Driver.Linq/LinqExtensions.cs | 26 + .../LinqExtensions.cs.meta | 11 + .../MemberNameResolver.cs | 49 + .../MemberNameResolver.cs.meta | 11 + .../RethinkDb.Driver.Linq.csproj | 42 + .../RethinkDb.Driver.Linq.csproj.meta | 7 + .../RethinkDb.Driver.Linq.ruleset | 6 + .../RethinkDb.Driver.Linq.ruleset.meta | 7 + .../RethinkDb.Driver.Linq/RethinkDbGroup.cs | 23 + .../RethinkDbGroup.cs.meta | 11 + .../RethinkDbQueryModelVisitor.cs | 190 + .../RethinkDbQueryModelVisitor.cs.meta | 11 + .../RethinkQueryExecutor.cs | 132 + .../RethinkQueryExecutor.cs.meta | 11 + .../RethinkDb.Driver.Linq/RethinkQueryable.cs | 22 + .../RethinkQueryable.cs.meta | 11 + .../SelectionProjector.cs | 26 + .../SelectionProjector.cs.meta | 11 + .../WhereClauseParsers.meta | 8 + .../WhereClauseParsers/BaseIndexParser.cs | 40 + .../BaseIndexParser.cs.meta | 11 + .../WhereClauseParsers/DefaultParser.cs | 32 + .../WhereClauseParsers/DefaultParser.cs.meta | 11 + .../WhereClauseParsers/ExprHelper.cs | 60 + .../WhereClauseParsers/ExprHelper.cs.meta | 11 + .../WhereClauseParsers/ExpressionVisitor.cs | 111 + .../ExpressionVisitor.cs.meta | 11 + .../WhereClauseParsers/GroupItemsParser.cs | 49 + .../GroupItemsParser.cs.meta | 11 + .../WhereClauseParsers/IWhereClauseParser.cs | 13 + .../IWhereClauseParser.cs.meta | 11 + .../WhereClauseParsers/PrimaryIndexParser.cs | 18 + .../PrimaryIndexParser.cs.meta | 11 + .../SecondaryIndexParser.cs | 26 + .../SecondaryIndexParser.cs.meta | 11 + .../WhereClauseParsers/SubQueryVisitor.meta | 8 + .../SubQueryVisitor/AllSubQueryVisitor.cs | 19 + .../AllSubQueryVisitor.cs.meta | 11 + .../SubQueryVisitor/AnySubQueryVisitor.cs | 11 + .../AnySubQueryVisitor.cs.meta | 11 + .../BaseFilterableSubQueryVisitor.cs | 23 + .../BaseFilterableSubQueryVisitor.cs.meta | 11 + .../SubQueryVisitor/BaseSubQueryVisitor.cs | 33 + .../BaseSubQueryVisitor.cs.meta | 11 + .../ContainsSubQueryVisitor.cs | 22 + .../ContainsSubQueryVisitor.cs.meta | 11 + .../SubQueryVisitor/CountSubQueryVisitor.cs | 11 + .../CountSubQueryVisitor.cs.meta | 11 + .../SubQueryVisitor/FirstSubQueryVisitor.cs | 24 + .../FirstSubQueryVisitor.cs.meta | 11 + .../SubQueryVisitor/ISubQueryVisitor.cs | 11 + .../SubQueryVisitor/ISubQueryVisitor.cs.meta | 11 + .../NIH/RethinkDb/RethinkDb.Driver.meta | 8 + .../NIH/RethinkDb/RethinkDb.Driver/Ast.meta | 8 + .../RethinkDb.Driver/Ast/ExtensionHelper.cs | 31 + .../Ast/ExtensionHelper.cs.meta | 11 + .../RethinkDb/RethinkDb.Driver/Ast/Poco.cs | 45 + .../RethinkDb.Driver/Ast/Poco.cs.meta | 11 + .../RethinkDb.Driver/Ast/PropertyHelper.cs | 515 + .../Ast/PropertyHelper.cs.meta | 11 + .../RethinkDb/RethinkDb.Driver/Ast/Query.cs | 80 + .../RethinkDb.Driver/Ast/Query.cs.meta | 11 + .../RethinkDb/RethinkDb.Driver/Ast/ReqlAst.cs | 450 + .../RethinkDb.Driver/Ast/ReqlAst.cs.meta | 11 + .../RethinkDb.Driver/Ast/ReqlExpr.cs | 194 + .../RethinkDb.Driver/Ast/ReqlExpr.cs.meta | 11 + .../RethinkDb/RethinkDb.Driver/Ast/ReqlRaw.cs | 116 + .../RethinkDb.Driver/Ast/ReqlRaw.cs.meta | 11 + .../RethinkDb.Driver/Ast/TopLevel.cs | 52 + .../RethinkDb.Driver/Ast/TopLevel.cs.meta | 11 + .../RethinkDb/RethinkDb.Driver/Ast/Util.cs | 142 + .../RethinkDb.Driver/Ast/Util.cs.meta | 11 + .../RethinkDb.Driver/ErrorBuilder.cs | 95 + .../RethinkDb.Driver/ErrorBuilder.cs.meta | 11 + .../RethinkDb/RethinkDb.Driver/Extras.meta | 8 + .../RethinkDb.Driver/Extras/Dao.meta | 8 + .../RethinkDb.Driver/Extras/Dao/Document.cs | 31 + .../Extras/Dao/Document.cs.meta | 11 + .../RethinkDb.Driver/Extras/Dao/IDao.cs | 84 + .../RethinkDb.Driver/Extras/Dao/IDao.cs.meta | 11 + .../RethinkDb.Driver/Extras/Dao/RethinkDao.cs | 209 + .../Extras/Dao/RethinkDao.cs.meta | 11 + .../RethinkDb/RethinkDb.Driver/Generated.meta | 8 + .../RethinkDb.Driver/Generated/Ast.meta | 8 + .../RethinkDb.Driver/Generated/Ast/Add.cs | 101 + .../Generated/Ast/Add.cs.meta | 11 + .../RethinkDb.Driver/Generated/Ast/And.cs | 104 + .../Generated/Ast/And.cs.meta | 11 + .../RethinkDb.Driver/Generated/Ast/Append.cs | 95 + .../Generated/Ast/Append.cs.meta | 11 + .../RethinkDb.Driver/Generated/Ast/April.cs | 77 + .../Generated/Ast/April.cs.meta | 11 + .../RethinkDb.Driver/Generated/Ast/Args.cs | 107 + .../Generated/Ast/Args.cs.meta | 11 + .../RethinkDb.Driver/Generated/Ast/Asc.cs | 77 + .../Generated/Ast/Asc.cs.meta | 11 + .../RethinkDb.Driver/Generated/Ast/August.cs | 77 + .../Generated/Ast/August.cs.meta | 11 + .../RethinkDb.Driver/Generated/Ast/Avg.cs | 110 + .../Generated/Ast/Avg.cs.meta | 11 + .../RethinkDb.Driver/Generated/Ast/Between.cs | 138 + .../Generated/Ast/Between.cs.meta | 11 + .../RethinkDb.Driver/Generated/Ast/Binary.cs | 113 + .../Generated/Ast/Binary.cs.meta | 11 + .../RethinkDb.Driver/Generated/Ast/Bracket.cs | 95 + .../Generated/Ast/Bracket.cs.meta | 11 + .../RethinkDb.Driver/Generated/Ast/Branch.cs | 107 + .../Generated/Ast/Branch.cs.meta | 11 + .../RethinkDb.Driver/Generated/Ast/Ceil.cs | 104 + .../Generated/Ast/Ceil.cs.meta | 11 + .../Generated/Ast/ChangeAt.cs | 95 + .../Generated/Ast/ChangeAt.cs.meta | 11 + .../RethinkDb.Driver/Generated/Ast/Changes.cs | 201 + .../Generated/Ast/Changes.cs.meta | 11 + .../RethinkDb.Driver/Generated/Ast/Circle.cs | 153 + .../Generated/Ast/Circle.cs.meta | 11 + .../Generated/Ast/CoerceTo.cs | 101 + .../Generated/Ast/CoerceTo.cs.meta | 11 + .../Generated/Ast/ConcatMap.cs | 101 + .../Generated/Ast/ConcatMap.cs.meta | 11 + .../RethinkDb.Driver/Generated/Ast/Config.cs | 95 + .../Generated/Ast/Config.cs.meta | 11 + .../Generated/Ast/Contains.cs | 104 + .../Generated/Ast/Contains.cs.meta | 11 + .../RethinkDb.Driver/Generated/Ast/Count.cs | 95 + .../Generated/Ast/Count.cs.meta | 11 + .../RethinkDb.Driver/Generated/Ast/Date.cs | 101 + .../Generated/Ast/Date.cs.meta | 11 + .../RethinkDb.Driver/Generated/Ast/Datum.cs | 93 + .../Generated/Ast/Datum.cs.meta | 11 + .../RethinkDb.Driver/Generated/Ast/Day.cs | 101 + .../Generated/Ast/Day.cs.meta | 11 + .../Generated/Ast/DayOfWeek.cs | 95 + .../Generated/Ast/DayOfWeek.cs.meta | 11 + .../Generated/Ast/DayOfYear.cs | 101 + .../Generated/Ast/DayOfYear.cs.meta | 11 + .../RethinkDb.Driver/Generated/Ast/Db.cs | 302 + .../RethinkDb.Driver/Generated/Ast/Db.cs.meta | 11 + .../Generated/Ast/DbCreate.cs | 137 + .../Generated/Ast/DbCreate.cs.meta | 11 + .../RethinkDb.Driver/Generated/Ast/DbDrop.cs | 137 + .../Generated/Ast/DbDrop.cs.meta | 11 + .../RethinkDb.Driver/Generated/Ast/DbList.cs | 95 + .../Generated/Ast/DbList.cs.meta | 11 + .../Generated/Ast/December.cs | 77 + .../Generated/Ast/December.cs.meta | 11 + .../RethinkDb.Driver/Generated/Ast/Default.cs | 116 + .../Generated/Ast/Default.cs.meta | 11 + .../RethinkDb.Driver/Generated/Ast/Delete.cs | 144 + .../Generated/Ast/Delete.cs.meta | 11 + .../Generated/Ast/DeleteAt.cs | 101 + .../Generated/Ast/DeleteAt.cs.meta | 11 + .../RethinkDb.Driver/Generated/Ast/Desc.cs | 77 + .../Generated/Ast/Desc.cs.meta | 11 + .../Generated/Ast/Difference.cs | 101 + .../Generated/Ast/Difference.cs.meta | 11 + .../Generated/Ast/Distance.cs | 147 + .../Generated/Ast/Distance.cs.meta | 11 + .../Generated/Ast/Distinct.cs | 138 + .../Generated/Ast/Distinct.cs.meta | 11 + .../RethinkDb.Driver/Generated/Ast/Div.cs | 95 + .../Generated/Ast/Div.cs.meta | 11 + .../Generated/Ast/Downcase.cs | 107 + .../Generated/Ast/Downcase.cs.meta | 11 + .../RethinkDb.Driver/Generated/Ast/During.cs | 144 + .../Generated/Ast/During.cs.meta | 11 + .../Generated/Ast/EpochTime.cs | 98 + .../Generated/Ast/EpochTime.cs.meta | 11 + .../RethinkDb.Driver/Generated/Ast/Eq.cs | 95 + .../RethinkDb.Driver/Generated/Ast/Eq.cs.meta | 11 + .../RethinkDb.Driver/Generated/Ast/EqJoin.cs | 126 + .../Generated/Ast/EqJoin.cs.meta | 11 + .../RethinkDb.Driver/Generated/Ast/Error.cs | 107 + .../Generated/Ast/Error.cs.meta | 11 + .../Generated/Ast/February.cs | 77 + .../Generated/Ast/February.cs.meta | 11 + .../RethinkDb.Driver/Generated/Ast/Fill.cs | 131 + .../Generated/Ast/Fill.cs.meta | 11 + .../RethinkDb.Driver/Generated/Ast/Filter.cs | 135 + .../Generated/Ast/Filter.cs.meta | 11 + .../RethinkDb.Driver/Generated/Ast/Floor.cs | 104 + .../Generated/Ast/Floor.cs.meta | 11 + .../RethinkDb.Driver/Generated/Ast/Fold.cs | 144 + .../Generated/Ast/Fold.cs.meta | 11 + .../RethinkDb.Driver/Generated/Ast/ForEach.cs | 101 + .../Generated/Ast/ForEach.cs.meta | 11 + .../RethinkDb.Driver/Generated/Ast/Friday.cs | 77 + .../Generated/Ast/Friday.cs.meta | 11 + .../RethinkDb.Driver/Generated/Ast/Func.cs | 278 + .../Generated/Ast/Func.cs.meta | 11 + .../RethinkDb.Driver/Generated/Ast/Funcall.cs | 114 + .../Generated/Ast/Funcall.cs.meta | 11 + .../RethinkDb.Driver/Generated/Ast/Ge.cs | 95 + .../RethinkDb.Driver/Generated/Ast/Ge.cs.meta | 11 + .../RethinkDb.Driver/Generated/Ast/Geojson.cs | 119 + .../Generated/Ast/Geojson.cs.meta | 11 + .../RethinkDb.Driver/Generated/Ast/Get.cs | 95 + .../Generated/Ast/Get.cs.meta | 11 + .../RethinkDb.Driver/Generated/Ast/GetAll.cs | 132 + .../Generated/Ast/GetAll.cs.meta | 11 + .../Generated/Ast/GetField.cs | 98 + .../Generated/Ast/GetField.cs.meta | 11 + .../Generated/Ast/GetIntersecting.cs | 135 + .../Generated/Ast/GetIntersecting.cs.meta | 11 + .../Generated/Ast/GetNearest.cs | 153 + .../Generated/Ast/GetNearest.cs.meta | 11 + .../RethinkDb.Driver/Generated/Ast/Grant.cs | 125 + .../Generated/Ast/Grant.cs.meta | 11 + .../RethinkDb.Driver/Generated/Ast/Group.cs | 192 + .../Generated/Ast/Group.cs.meta | 11 + .../RethinkDb.Driver/Generated/Ast/Gt.cs | 95 + .../RethinkDb.Driver/Generated/Ast/Gt.cs.meta | 11 + .../Generated/Ast/HasFields.cs | 95 + .../Generated/Ast/HasFields.cs.meta | 11 + .../RethinkDb.Driver/Generated/Ast/Hours.cs | 101 + .../Generated/Ast/Hours.cs.meta | 11 + .../RethinkDb.Driver/Generated/Ast/Http.cs | 189 + .../Generated/Ast/Http.cs.meta | 11 + .../Generated/Ast/InTimezone.cs | 95 + .../Generated/Ast/InTimezone.cs.meta | 11 + .../Generated/Ast/Includes.cs | 107 + .../Generated/Ast/Includes.cs.meta | 11 + .../Generated/Ast/IndexCreate.cs | 135 + .../Generated/Ast/IndexCreate.cs.meta | 11 + .../Generated/Ast/IndexDrop.cs | 95 + .../Generated/Ast/IndexDrop.cs.meta | 11 + .../Generated/Ast/IndexList.cs | 95 + .../Generated/Ast/IndexList.cs.meta | 11 + .../Generated/Ast/IndexRename.cs | 132 + .../Generated/Ast/IndexRename.cs.meta | 11 + .../Generated/Ast/IndexStatus.cs | 98 + .../Generated/Ast/IndexStatus.cs.meta | 11 + .../Generated/Ast/IndexWait.cs | 98 + .../Generated/Ast/IndexWait.cs.meta | 11 + .../RethinkDb.Driver/Generated/Ast/Info.cs | 95 + .../Generated/Ast/Info.cs.meta | 11 + .../Generated/Ast/InnerJoin.cs | 101 + .../Generated/Ast/InnerJoin.cs.meta | 11 + .../RethinkDb.Driver/Generated/Ast/Insert.cs | 162 + .../Generated/Ast/Insert.cs.meta | 11 + .../Generated/Ast/InsertAt.cs | 95 + .../Generated/Ast/InsertAt.cs.meta | 11 + .../Generated/Ast/Intersects.cs | 107 + .../Generated/Ast/Intersects.cs.meta | 11 + .../RethinkDb.Driver/Generated/Ast/IsEmpty.cs | 95 + .../Generated/Ast/IsEmpty.cs.meta | 11 + .../RethinkDb.Driver/Generated/Ast/Iso8601.cs | 146 + .../Generated/Ast/Iso8601.cs.meta | 11 + .../RethinkDb.Driver/Generated/Ast/January.cs | 77 + .../Generated/Ast/January.cs.meta | 11 + .../Generated/Ast/Javascript.cs | 114 + .../Generated/Ast/Javascript.cs.meta | 11 + .../RethinkDb.Driver/Generated/Ast/Json.cs | 95 + .../Generated/Ast/Json.cs.meta | 11 + .../RethinkDb.Driver/Generated/Ast/July.cs | 77 + .../Generated/Ast/July.cs.meta | 11 + .../RethinkDb.Driver/Generated/Ast/June.cs | 77 + .../Generated/Ast/June.cs.meta | 11 + .../RethinkDb.Driver/Generated/Ast/Keys.cs | 107 + .../Generated/Ast/Keys.cs.meta | 11 + .../RethinkDb.Driver/Generated/Ast/Le.cs | 95 + .../RethinkDb.Driver/Generated/Ast/Le.cs.meta | 11 + .../RethinkDb.Driver/Generated/Ast/Limit.cs | 95 + .../Generated/Ast/Limit.cs.meta | 11 + .../RethinkDb.Driver/Generated/Ast/Line.cs | 116 + .../Generated/Ast/Line.cs.meta | 11 + .../RethinkDb.Driver/Generated/Ast/Literal.cs | 125 + .../Generated/Ast/Literal.cs.meta | 11 + .../RethinkDb.Driver/Generated/Ast/Lt.cs | 95 + .../RethinkDb.Driver/Generated/Ast/Lt.cs.meta | 11 + .../Generated/Ast/MakeArray.cs | 77 + .../Generated/Ast/MakeArray.cs.meta | 11 + .../RethinkDb.Driver/Generated/Ast/MakeObj.cs | 102 + .../Generated/Ast/MakeObj.cs.meta | 11 + .../RethinkDb.Driver/Generated/Ast/Map.cs | 107 + .../Generated/Ast/Map.cs.meta | 11 + .../RethinkDb.Driver/Generated/Ast/March.cs | 77 + .../Generated/Ast/March.cs.meta | 11 + .../RethinkDb.Driver/Generated/Ast/Match.cs | 125 + .../Generated/Ast/Match.cs.meta | 11 + .../RethinkDb.Driver/Generated/Ast/Max.cs | 132 + .../Generated/Ast/Max.cs.meta | 11 + .../RethinkDb.Driver/Generated/Ast/Maxval.cs | 77 + .../Generated/Ast/Maxval.cs.meta | 11 + .../RethinkDb.Driver/Generated/Ast/May.cs | 77 + .../Generated/Ast/May.cs.meta | 11 + .../RethinkDb.Driver/Generated/Ast/Merge.cs | 104 + .../Generated/Ast/Merge.cs.meta | 11 + .../RethinkDb.Driver/Generated/Ast/Min.cs | 132 + .../Generated/Ast/Min.cs.meta | 11 + .../RethinkDb.Driver/Generated/Ast/Minutes.cs | 101 + .../Generated/Ast/Minutes.cs.meta | 11 + .../RethinkDb.Driver/Generated/Ast/Minval.cs | 77 + .../Generated/Ast/Minval.cs.meta | 11 + .../RethinkDb.Driver/Generated/Ast/Mod.cs | 95 + .../Generated/Ast/Mod.cs.meta | 11 + .../RethinkDb.Driver/Generated/Ast/Monday.cs | 77 + .../Generated/Ast/Monday.cs.meta | 11 + .../RethinkDb.Driver/Generated/Ast/Month.cs | 101 + .../Generated/Ast/Month.cs.meta | 11 + .../RethinkDb.Driver/Generated/Ast/Mul.cs | 95 + .../Generated/Ast/Mul.cs.meta | 11 + .../RethinkDb.Driver/Generated/Ast/Ne.cs | 95 + .../RethinkDb.Driver/Generated/Ast/Ne.cs.meta | 11 + .../RethinkDb.Driver/Generated/Ast/Not.cs | 101 + .../Generated/Ast/Not.cs.meta | 11 + .../Generated/Ast/November.cs | 77 + .../Generated/Ast/November.cs.meta | 11 + .../RethinkDb.Driver/Generated/Ast/Now.cs | 104 + .../Generated/Ast/Now.cs.meta | 11 + .../RethinkDb.Driver/Generated/Ast/Nth.cs | 98 + .../Generated/Ast/Nth.cs.meta | 11 + .../RethinkDb.Driver/Generated/Ast/October.cs | 77 + .../Generated/Ast/October.cs.meta | 11 + .../Generated/Ast/OffsetsOf.cs | 95 + .../Generated/Ast/OffsetsOf.cs.meta | 11 + .../RethinkDb.Driver/Generated/Ast/Or.cs | 104 + .../RethinkDb.Driver/Generated/Ast/Or.cs.meta | 11 + .../RethinkDb.Driver/Generated/Ast/OrderBy.cs | 138 + .../Generated/Ast/OrderBy.cs.meta | 11 + .../Generated/Ast/OuterJoin.cs | 104 + .../Generated/Ast/OuterJoin.cs.meta | 11 + .../RethinkDb.Driver/Generated/Ast/Pluck.cs | 101 + .../Generated/Ast/Pluck.cs.meta | 11 + .../RethinkDb.Driver/Generated/Ast/Point.cs | 107 + .../Generated/Ast/Point.cs.meta | 11 + .../RethinkDb.Driver/Generated/Ast/Polygon.cs | 131 + .../Generated/Ast/Polygon.cs.meta | 11 + .../Generated/Ast/PolygonSub.cs | 131 + .../Generated/Ast/PolygonSub.cs.meta | 11 + .../RethinkDb.Driver/Generated/Ast/Prepend.cs | 95 + .../Generated/Ast/Prepend.cs.meta | 11 + .../RethinkDb.Driver/Generated/Ast/Random.cs | 132 + .../Generated/Ast/Random.cs.meta | 11 + .../RethinkDb.Driver/Generated/Ast/Range.cs | 101 + .../Generated/Ast/Range.cs.meta | 11 + .../Generated/Ast/Rebalance.cs | 95 + .../Generated/Ast/Rebalance.cs.meta | 11 + .../Generated/Ast/Reconfigure.cs | 156 + .../Generated/Ast/Reconfigure.cs.meta | 11 + .../RethinkDb.Driver/Generated/Ast/Reduce.cs | 110 + .../Generated/Ast/Reduce.cs.meta | 11 + .../RethinkDb.Driver/Generated/Ast/Replace.cs | 168 + .../Generated/Ast/Replace.cs.meta | 11 + .../Generated/Ast/ReqlExpr.cs | 8562 +++++++ .../Generated/Ast/ReqlExpr.cs.meta | 11 + .../Generated/Ast/ReqlObject.cs | 110 + .../Generated/Ast/ReqlObject.cs.meta | 11 + .../RethinkDb.Driver/Generated/Ast/Round.cs | 104 + .../Generated/Ast/Round.cs.meta | 11 + .../RethinkDb.Driver/Generated/Ast/Sample.cs | 95 + .../Generated/Ast/Sample.cs.meta | 11 + .../Generated/Ast/Saturday.cs | 77 + .../Generated/Ast/Saturday.cs.meta | 11 + .../RethinkDb.Driver/Generated/Ast/Seconds.cs | 101 + .../Generated/Ast/Seconds.cs.meta | 11 + .../Generated/Ast/September.cs | 77 + .../Generated/Ast/September.cs.meta | 11 + .../Generated/Ast/SetDifference.cs | 98 + .../Generated/Ast/SetDifference.cs.meta | 11 + .../Generated/Ast/SetInsert.cs | 95 + .../Generated/Ast/SetInsert.cs.meta | 11 + .../Generated/Ast/SetIntersection.cs | 98 + .../Generated/Ast/SetIntersection.cs.meta | 11 + .../Generated/Ast/SetUnion.cs | 95 + .../Generated/Ast/SetUnion.cs.meta | 11 + .../RethinkDb.Driver/Generated/Ast/Skip.cs | 95 + .../Generated/Ast/Skip.cs.meta | 11 + .../RethinkDb.Driver/Generated/Ast/Slice.cs | 135 + .../Generated/Ast/Slice.cs.meta | 11 + .../Generated/Ast/SpliceAt.cs | 95 + .../Generated/Ast/SpliceAt.cs.meta | 11 + .../RethinkDb.Driver/Generated/Ast/Split.cs | 119 + .../Generated/Ast/Split.cs.meta | 11 + .../RethinkDb.Driver/Generated/Ast/Status.cs | 95 + .../Generated/Ast/Status.cs.meta | 11 + .../RethinkDb.Driver/Generated/Ast/Sub.cs | 95 + .../Generated/Ast/Sub.cs.meta | 11 + .../RethinkDb.Driver/Generated/Ast/Sum.cs | 110 + .../Generated/Ast/Sum.cs.meta | 11 + .../RethinkDb.Driver/Generated/Ast/Sunday.cs | 77 + .../Generated/Ast/Sunday.cs.meta | 11 + .../RethinkDb.Driver/Generated/Ast/Sync.cs | 107 + .../Generated/Ast/Sync.cs.meta | 11 + .../RethinkDb.Driver/Generated/Ast/Table.cs | 567 + .../Generated/Ast/Table.cs.meta | 11 + .../Generated/Ast/TableCreate.cs | 231 + .../Generated/Ast/TableCreate.cs.meta | 11 + .../Generated/Ast/TableDrop.cs | 173 + .../Generated/Ast/TableDrop.cs.meta | 11 + .../Generated/Ast/TableList.cs | 95 + .../Generated/Ast/TableList.cs.meta | 11 + .../Generated/Ast/Thursday.cs | 77 + .../Generated/Ast/Thursday.cs.meta | 11 + .../RethinkDb.Driver/Generated/Ast/Time.cs | 95 + .../Generated/Ast/Time.cs.meta | 11 + .../Generated/Ast/TimeOfDay.cs | 101 + .../Generated/Ast/TimeOfDay.cs.meta | 11 + .../Generated/Ast/Timezone.cs | 101 + .../Generated/Ast/Timezone.cs.meta | 11 + .../Generated/Ast/ToEpochTime.cs | 95 + .../Generated/Ast/ToEpochTime.cs.meta | 11 + .../Generated/Ast/ToGeojson.cs | 110 + .../Generated/Ast/ToGeojson.cs.meta | 11 + .../Generated/Ast/ToIso8601.cs | 101 + .../Generated/Ast/ToIso8601.cs.meta | 11 + .../Generated/Ast/ToJsonString.cs | 101 + .../Generated/Ast/ToJsonString.cs.meta | 11 + .../RethinkDb.Driver/Generated/Ast/Tuesday.cs | 77 + .../Generated/Ast/Tuesday.cs.meta | 11 + .../RethinkDb.Driver/Generated/Ast/TypeOf.cs | 101 + .../Generated/Ast/TypeOf.cs.meta | 11 + .../RethinkDb.Driver/Generated/Ast/Ungroup.cs | 116 + .../Generated/Ast/Ungroup.cs.meta | 11 + .../RethinkDb.Driver/Generated/Ast/Union.cs | 132 + .../Generated/Ast/Union.cs.meta | 11 + .../RethinkDb.Driver/Generated/Ast/Upcase.cs | 107 + .../Generated/Ast/Upcase.cs.meta | 11 + .../RethinkDb.Driver/Generated/Ast/Update.cs | 147 + .../Generated/Ast/Update.cs.meta | 11 + .../RethinkDb.Driver/Generated/Ast/Uuid.cs | 101 + .../Generated/Ast/Uuid.cs.meta | 11 + .../RethinkDb.Driver/Generated/Ast/Values.cs | 107 + .../Generated/Ast/Values.cs.meta | 11 + .../RethinkDb.Driver/Generated/Ast/Var.cs | 77 + .../Generated/Ast/Var.cs.meta | 11 + .../RethinkDb.Driver/Generated/Ast/Wait.cs | 141 + .../Generated/Ast/Wait.cs.meta | 11 + .../Generated/Ast/Wednesday.cs | 77 + .../Generated/Ast/Wednesday.cs.meta | 11 + .../Generated/Ast/WithFields.cs | 134 + .../Generated/Ast/WithFields.cs.meta | 11 + .../RethinkDb.Driver/Generated/Ast/Without.cs | 101 + .../Generated/Ast/Without.cs.meta | 11 + .../RethinkDb.Driver/Generated/Ast/Year.cs | 101 + .../Generated/Ast/Year.cs.meta | 11 + .../RethinkDb.Driver/Generated/Ast/Zip.cs | 98 + .../Generated/Ast/Zip.cs.meta | 11 + .../RethinkDb.Driver/Generated/Model.meta | 8 + .../Generated/Model/AuthType.cs | 21 + .../Generated/Model/AuthType.cs.meta | 11 + .../RethinkDb.Driver/Generated/Model/Bound.cs | 21 + .../Generated/Model/Bound.cs.meta | 11 + .../Generated/Model/Conflict.cs | 22 + .../Generated/Model/Conflict.cs.meta | 11 + .../Generated/Model/Durability.cs | 21 + .../Generated/Model/Durability.cs.meta | 11 + .../Generated/Model/EmergencyRepair.cs | 21 + .../Generated/Model/EmergencyRepair.cs.meta | 11 + .../Generated/Model/Format.cs | 21 + .../Generated/Model/Format.cs.meta | 11 + .../Generated/Model/GeoSystem.cs | 21 + .../Generated/Model/GeoSystem.cs.meta | 11 + .../Generated/Model/HttpMethod.cs | 25 + .../Generated/Model/HttpMethod.cs.meta | 11 + .../Generated/Model/IdentifierFormat.cs | 21 + .../Generated/Model/IdentifierFormat.cs.meta | 11 + .../Generated/Model/ReadMode.cs | 22 + .../Generated/Model/ReadMode.cs.meta | 11 + .../Generated/Model/ReqlFunction0.cs | 19 + .../Generated/Model/ReqlFunction0.cs.meta | 11 + .../Generated/Model/ReqlFunction1.cs | 19 + .../Generated/Model/ReqlFunction1.cs.meta | 11 + .../Generated/Model/ReqlFunction2.cs | 19 + .../Generated/Model/ReqlFunction2.cs.meta | 11 + .../Generated/Model/ReqlFunction3.cs | 19 + .../Generated/Model/ReqlFunction3.cs.meta | 11 + .../Generated/Model/ReqlFunction4.cs | 19 + .../Generated/Model/ReqlFunction4.cs.meta | 11 + .../Generated/Model/ResultFormat.cs | 24 + .../Generated/Model/ResultFormat.cs.meta | 11 + .../Generated/Model/Status.cs | 23 + .../Generated/Model/Status.cs.meta | 11 + .../Generated/Model/TopLevel.cs | 5804 +++++ .../Generated/Model/TopLevel.cs.meta | 11 + .../RethinkDb.Driver/Generated/Model/Unit.cs | 24 + .../Generated/Model/Unit.cs.meta | 11 + .../RethinkDb.Driver/Generated/Proto.meta | 8 + .../Generated/Proto/DatumType.cs | 23 + .../Generated/Proto/DatumType.cs.meta | 11 + .../Generated/Proto/ErrorType.cs | 24 + .../Generated/Proto/ErrorType.cs.meta | 11 + .../Generated/Proto/FrameType.cs | 18 + .../Generated/Proto/FrameType.cs.meta | 11 + .../Generated/Proto/Protocol.cs | 18 + .../Generated/Proto/Protocol.cs.meta | 11 + .../Generated/Proto/QueryType.cs | 21 + .../Generated/Proto/QueryType.cs.meta | 11 + .../Generated/Proto/ResponseNote.cs | 38 + .../Generated/Proto/ResponseNote.cs.meta | 11 + .../Generated/Proto/ResponseType.cs | 39 + .../Generated/Proto/ResponseType.cs.meta | 11 + .../Generated/Proto/TermType.cs | 194 + .../Generated/Proto/TermType.cs.meta | 11 + .../Generated/Proto/Version.cs | 21 + .../Generated/Proto/Version.cs.meta | 11 + .../Generated/ReqlAuthError.cs | 35 + .../Generated/ReqlAuthError.cs.meta | 11 + .../Generated/ReqlAvailabilityError.cs | 35 + .../Generated/ReqlAvailabilityError.cs.meta | 11 + .../Generated/ReqlClientError.cs | 35 + .../Generated/ReqlClientError.cs.meta | 11 + .../Generated/ReqlCompileError.cs | 35 + .../Generated/ReqlCompileError.cs.meta | 11 + .../Generated/ReqlDriverCompileError.cs | 35 + .../Generated/ReqlDriverCompileError.cs.meta | 11 + .../Generated/ReqlDriverError.cs | 35 + .../Generated/ReqlDriverError.cs.meta | 11 + .../RethinkDb.Driver/Generated/ReqlError.cs | 37 + .../Generated/ReqlError.cs.meta | 11 + .../Generated/ReqlInternalError.cs | 35 + .../Generated/ReqlInternalError.cs.meta | 11 + .../Generated/ReqlNonExistenceError.cs | 35 + .../Generated/ReqlNonExistenceError.cs.meta | 11 + .../Generated/ReqlOpFailedError.cs | 35 + .../Generated/ReqlOpFailedError.cs.meta | 11 + .../Generated/ReqlOpIndeterminateError.cs | 35 + .../ReqlOpIndeterminateError.cs.meta | 11 + .../Generated/ReqlPermissionError.cs | 35 + .../Generated/ReqlPermissionError.cs.meta | 11 + .../Generated/ReqlQueryLogicError.cs | 35 + .../Generated/ReqlQueryLogicError.cs.meta | 11 + .../Generated/ReqlResourceLimitError.cs | 35 + .../Generated/ReqlResourceLimitError.cs.meta | 11 + .../Generated/ReqlRuntimeError.cs | 35 + .../Generated/ReqlRuntimeError.cs.meta | 11 + .../Generated/ReqlServerCompileError.cs | 35 + .../Generated/ReqlServerCompileError.cs.meta | 11 + .../Generated/ReqlUserError.cs | 35 + .../Generated/ReqlUserError.cs.meta | 11 + .../NIH/RethinkDb/RethinkDb.Driver/Log.cs | 116 + .../RethinkDb/RethinkDb.Driver/Log.cs.meta | 11 + .../NIH/RethinkDb/RethinkDb.Driver/Model.meta | 8 + .../RethinkDb.Driver/Model/Arguments.cs | 77 + .../RethinkDb.Driver/Model/Arguments.cs.meta | 11 + .../RethinkDb.Driver/Model/Backtrace.cs | 25 + .../RethinkDb.Driver/Model/Backtrace.cs.meta | 11 + .../RethinkDb.Driver/Model/Change[T].cs | 107 + .../RethinkDb.Driver/Model/Change[T].cs.meta | 11 + .../Model/ExtensionsForResult.cs | 153 + .../Model/ExtensionsForResult.cs.meta | 11 + .../RethinkDb.Driver/Model/GroupedResult.cs | 69 + .../Model/GroupedResult.cs.meta | 11 + .../RethinkDb.Driver/Model/MapObject.cs | 49 + .../RethinkDb.Driver/Model/MapObject.cs.meta | 11 + .../RethinkDb.Driver/Model/OptArgs.cs | 101 + .../RethinkDb.Driver/Model/OptArgs.cs.meta | 11 + .../RethinkDb.Driver/Model/PathSpec.cs | 41 + .../RethinkDb.Driver/Model/PathSpec.cs.meta | 11 + .../RethinkDb.Driver/Model/Profile.cs | 25 + .../RethinkDb.Driver/Model/Profile.cs.meta | 11 + .../RethinkDb.Driver/Model/ReqlLambda.cs | 9 + .../RethinkDb.Driver/Model/ReqlLambda.cs.meta | 11 + .../RethinkDb.Driver/Model/Result.cs | 102 + .../RethinkDb.Driver/Model/Result.cs.meta | 11 + .../RethinkDb.Driver/Model/Server.cs | 37 + .../RethinkDb.Driver/Model/Server.cs.meta | 11 + .../NIH/RethinkDb/RethinkDb.Driver/Net.meta | 8 + .../RethinkDb/RethinkDb.Driver/Net/Awaiter.cs | 12 + .../RethinkDb.Driver/Net/Awaiter.cs.meta | 11 + .../RethinkDb.Driver/Net/Clustering.meta | 8 + .../Net/Clustering/ConnectionPool.cs | 587 + .../Net/Clustering/ConnectionPool.cs.meta | 11 + .../Net/Clustering/EpsilonGreedy.cs | 416 + .../Net/Clustering/EpsilonGreedy.cs.meta | 11 + .../Net/Clustering/EpsilonValueCalculator.cs | 144 + .../Clustering/EpsilonValueCalculator.cs.meta | 11 + .../Net/Clustering/ExceptionIs.cs | 13 + .../Net/Clustering/ExceptionIs.cs.meta | 11 + .../Net/Clustering/HostEntry.cs | 65 + .../Net/Clustering/HostEntry.cs.meta | 11 + .../Net/Clustering/HostPool.cs | 82 + .../Net/Clustering/HostPool.cs.meta | 11 + .../Net/Clustering/IPoolingStrategy.cs | 12 + .../Net/Clustering/IPoolingStrategy.cs.meta | 11 + .../Net/Clustering/RoundRobinHostPool.cs | 158 + .../Net/Clustering/RoundRobinHostPool.cs.meta | 11 + .../RethinkDb.Driver/Net/Clustering/Seed.cs | 30 + .../Net/Clustering/Seed.cs.meta | 11 + .../RethinkDb.Driver/Net/Clustering/Server.cs | 28 + .../Net/Clustering/Server.cs.meta | 11 + .../RethinkDb.Driver/Net/Connection.cs | 726 + .../RethinkDb.Driver/Net/Connection.cs.meta | 11 + .../RethinkDb.Driver/Net/Converter.cs | 174 + .../RethinkDb.Driver/Net/Converter.cs.meta | 11 + .../RethinkDb/RethinkDb.Driver/Net/Crypto.cs | 114 + .../RethinkDb.Driver/Net/Crypto.cs.meta | 11 + .../RethinkDb/RethinkDb.Driver/Net/Cursor.cs | 340 + .../RethinkDb.Driver/Net/Cursor.cs.meta | 11 + .../RethinkDb.Driver/Net/FormatOptions.cs | 52 + .../Net/FormatOptions.cs.meta | 11 + .../RethinkDb.Driver/Net/Handshake.cs | 305 + .../RethinkDb.Driver/Net/Handshake.cs.meta | 11 + .../RethinkDb.Driver/Net/IConnection.cs | 45 + .../RethinkDb.Driver/Net/IConnection.cs.meta | 11 + .../Net/IConnectionBuilder.cs | 20 + .../Net/IConnectionBuilder.cs.meta | 11 + .../RethinkDb/RethinkDb.Driver/Net/ICursor.cs | 10 + .../RethinkDb.Driver/Net/ICursor.cs.meta | 11 + .../RethinkDb.Driver/Net/IncrementalSHA256.cs | 71 + .../Net/IncrementalSHA256.cs.meta | 11 + .../RethinkDb.Driver/Net/JsonConverters.meta | 8 + .../ExtensionsForJsonConverters.cs | 25 + .../ExtensionsForJsonConverters.cs.meta | 11 + .../Net/JsonConverters/ReqlBinaryConverter.cs | 61 + .../ReqlBinaryConverter.cs.meta | 11 + .../JsonConverters/ReqlDateTimeConverter.cs | 143 + .../ReqlDateTimeConverter.cs.meta | 11 + .../Net/JsonConverters/ReqlExprConverter.cs | 33 + .../JsonConverters/ReqlExprConverter.cs.meta | 11 + .../JsonConverters/ReqlGroupingConverter.cs | 97 + .../ReqlGroupingConverter.cs.meta | 11 + .../RethinkDb.Driver/Net/Response.cs | 138 + .../RethinkDb.Driver/Net/Response.cs.meta | 11 + .../RethinkDb.Driver/Net/ScramAttributes.cs | 153 + .../Net/ScramAttributes.cs.meta | 11 + .../RethinkDb.Driver/Net/SecureRandom.cs | 20 + .../RethinkDb.Driver/Net/SecureRandom.cs.meta | 11 + .../RethinkDb.Driver/Net/SocketWrapper.cs | 396 + .../Net/SocketWrapper.cs.meta | 11 + .../RethinkDb.Driver/Net/SslContext.cs | 43 + .../RethinkDb.Driver/Net/SslContext.cs.meta | 11 + .../RethinkDb.Driver/ReqlAssertFailure.cs | 21 + .../ReqlAssertFailure.cs.meta | 11 + .../RethinkDb/RethinkDb.Driver/RethinkDB.cs | 35 + .../RethinkDb.Driver/RethinkDB.cs.meta | 11 + .../RethinkDb.Driver/RethinkDBConstants.cs | 44 + .../RethinkDBConstants.cs.meta | 11 + .../RethinkDb.Driver/RethinkDb.Driver.csproj | 43 + .../RethinkDb.Driver.csproj.meta | 7 + .../RethinkDb.Driver/RethinkDb.Driver.ruleset | 6 + .../RethinkDb.Driver.ruleset.meta | 7 + .../NIH/RethinkDb/RethinkDb.Driver/Utils.meta | 8 + .../RethinkDb.Driver/Utils/CancellableTask.cs | 38 + .../Utils/CancellableTask.cs.meta | 11 + .../Utils/ExtensionsForEvent.cs | 26 + .../Utils/ExtensionsForEvent.cs.meta | 11 + .../Utils/ExtensionsForString.cs | 23 + .../Utils/ExtensionsForString.cs.meta | 11 + .../Utils/ExtensionsForTcpClient.cs | 16 + .../Utils/ExtensionsForTcpClient.cs.meta | 11 + .../Utils/ExtensionsForType.cs | 28 + .../Utils/ExtensionsForType.cs.meta | 11 + .../RethinkDb.Driver/Utils/QueryHelper.cs | 32 + .../Utils/QueryHelper.cs.meta | 11 + .../RethinkDb.Driver/Utils/StringHelper.cs | 15 + .../Utils/StringHelper.cs.meta | 11 + .../RethinkDb.Driver/Utils/TaskHelper.cs | 72 + .../RethinkDb.Driver/Utils/TaskHelper.cs.meta | 11 + .../ServerShared/Narrative/StoryProcessor.cs | 24 +- .../ServerShared/Narrative/ZoneConstraints.cs | 6 +- .../ServerShared/Narrative/ZoneSelectors.cs | 5 +- Assets/Scripts/ServerShared/Noise1D.cs | 4 +- Assets/Scripts/ServerShared/OrbitalEntity.cs | 16 +- Assets/Scripts/ServerShared/PlayerData.cs | 29 + .../Scripts/ServerShared/PlayerData.cs.meta | 3 + Assets/Scripts/ServerShared/PlayerSettings.cs | 80 + ...ettings.cs.meta => PlayerSettings.cs.meta} | 0 Assets/Scripts/ServerShared/Rect.cs | 6 +- .../Scripts/ServerShared/RuntimeGeometry.cs | 308 - .../ServerShared/RuntimeGeometry.cs.meta | 3 - .../ServerShared/RuntimePlayerSettings.cs | 103 - .../Scripts/ServerShared/SampleElimination.cs | 23 +- Assets/Scripts/ServerShared/SavedGame.cs | 132 + Assets/Scripts/ServerShared/SavedGame.cs.meta | 3 + Assets/Scripts/ServerShared/Settings.cs | 49 +- Assets/Scripts/ServerShared/Ship.cs | 150 +- Assets/Scripts/ServerShared/StatRecipe.cs | 496 - .../Scripts/ServerShared/StatRecipe.cs.meta | 2 - Assets/Scripts/ServerShared/Stats.meta | 8 - Assets/Scripts/ServerShared/Wormhole.cs | 7 + Assets/Scripts/ServerShared/Wormhole.cs.meta | 3 + Assets/Scripts/ServerShared/WwiseMetadata.cs | 6 + .../Scripts/ServerShared/WwiseSoundBinding.cs | 13 + .../ServerShared/YmirPhysicsContracts.cs | 845 - .../ServerShared/YmirPhysicsContracts.cs.meta | 2 - Assets/Scripts/ServerShared/Zone.cs | 478 +- .../ServerShared/ZoneConstructionData.cs | 117 - Assets/Scripts/ServerShared/ZoneData.cs | 212 + ...structionData.cs.meta => ZoneData.cs.meta} | 0 Assets/Scripts/ServerShared/ZoneGenerator.cs | 324 +- Assets/Scripts/StrategyGameManager.csbak | 1249 + Assets/Scripts/StrategyGameManager.csbak.meta | 7 + .../AetheriaRuntimeAssetBundleLayoutTests.cs | 41 - ...heriaRuntimeAssetBundleLayoutTests.cs.meta | 2 - .../Scripts/Tests/CultMathPrimitiveTests.cs | 74 - .../Tests/CultMathPrimitiveTests.cs.meta | 2 - .../Tests/DaemonRuntimeDocumentTests.cs | 6937 ----- .../Tests/DaemonRuntimeDocumentTests.cs.meta | 2 - .../StarbridgePlayerSeatDocumentTests.cs | 103 - .../StarbridgePlayerSeatDocumentTests.cs.meta | 11 - .../Tests/StatRecipeSurfaceCommandTests.cs | 108 - .../StatRecipeSurfaceCommandTests.cs.meta | 2 - Assets/Scripts/Tests/StatRecipeTests.cs | 254 - Assets/Scripts/Tests/StatRecipeTests.cs.meta | 2 - Assets/Scripts/Tests/Tests.asmdef | 36 +- Assets/Scripts/Tests/YmirPhysicsQueryTests.cs | 482 - .../Tests/YmirPhysicsQueryTests.cs.meta | 2 - Assets/Scripts/UI/ClickRaycaster.cs | 20 +- Assets/Scripts/UI/ClickableCollider.cs | 74 +- Assets/Scripts/UI/ConfirmationDialog.cs | 160 +- Assets/Scripts/UI/ConsoleController.cs | 124 + Assets/Scripts/UI/ConsoleController.cs.meta | 11 + Assets/Scripts/UI/ConsoleView.cs | 149 + Assets/Scripts/UI/ConsoleView.cs.meta | 11 + Assets/Scripts/UI/ContextMenu.cs | 142 + Assets/Scripts/UI/ContextMenu.cs.meta | 11 + Assets/Scripts/UI/ContextMenuOption.cs | 17 + Assets/Scripts/UI/ContextMenuOption.cs.meta | 11 + Assets/Scripts/UI/DropdownMenu.cs | 88 + Assets/Scripts/UI/DropdownMenu.cs.meta | 11 + Assets/Scripts/UI/FieldTester.cs | 112 + Assets/Scripts/UI/FieldTester.cs.meta | 3 + Assets/Scripts/UI/HUD/SchematicDisplay.cs | 262 +- Assets/Scripts/UI/HUD/SchematicListElement.cs | 56 +- .../UI/InputScreen/InputDisplayLayout.cs | 992 +- Assets/Scripts/UI/InputScreen/InputLayout.cs | 111 +- Assets/Scripts/UI/MainMenu.cs | 964 +- Assets/Scripts/UI/Menu/InventoryMenu.cs | 791 +- Assets/Scripts/UI/Menu/InventoryPanel.cs | 1411 +- Assets/Scripts/UI/Menu/LocalMenu.cs | 201 +- Assets/Scripts/UI/Menu/MapRenderer.cs | 308 +- Assets/Scripts/UI/Menu/MenuPanel.cs | 295 +- Assets/Scripts/UI/Menu/MenuTabButton.cs | 19 + Assets/Scripts/UI/Menu/MenuTabButton.cs.meta | 11 + Assets/Scripts/UI/Menu/SectorMap.cs | 585 +- Assets/Scripts/UI/Menu/SectorRenderer.cs | 198 +- Assets/Scripts/UI/Menu/TradeMenu.cs | 1394 +- Assets/Scripts/UI/Menu/TradeMenuDebug.cs | 472 + Assets/Scripts/UI/Menu/TradeMenuDebug.cs.meta | 3 + .../Scripts/UI/Menu/WeaponGroupAssignment.cs | 65 + .../UI/Menu/WeaponGroupAssignment.cs.meta | 11 + Assets/Scripts/UI/Menu/WeaponGroupElement.cs | 15 + .../UI/Menu/WeaponGroupElement.cs.meta | 11 + .../UI/Properties Panel/PropertiesList.cs | 89 + .../Properties Panel/PropertiesList.cs.meta | 11 + .../UI/Properties Panel/PropertiesPanel.cs | 661 + .../Properties Panel/PropertiesPanel.cs.meta | 31 + Assets/Scripts/UnityExtensions.cs | 3 +- Assets/Scripts/UnityFormatters.cs | 149 + Assets/Scripts/UnityFormatters.cs.meta | 11 + Assets/Scripts/Zone Display/MapView.cs | 39 +- Assets/Scripts/Zone Display/SnapToGrid.cs | 26 +- .../Zone Display/VolumeCloudRenderer.cs | 201 +- Assets/Scripts/Zone Display/VolumeSampling.cs | 178 +- Assets/Scripts/Zone Display/ZoneRenderer.cs | 1182 +- Assets/Settings.meta | 8 - Assets/Settings/Rendering.meta | 8 - .../AetheriaMainMenuVolumeProfile.asset | 3 - .../AetheriaMainMenuVolumeProfile.asset.meta | 8 - .../Settings/Rendering/AetheriaURPAsset.asset | 3 - .../Rendering/AetheriaURPAsset.asset.meta | 8 - .../Rendering/AetheriaUniversalRenderer.asset | 3 - .../AetheriaUniversalRenderer.asset.meta | 8 - .../Rendering/CustomPostProcessData.asset | 3 - Assets/Shaders/AetheriaDaemonIndirect.shader | 123 - .../AetheriaDaemonIndirect.shader.meta | 9 - Assets/Shaders/Brushes/PowerBrush.shader | 149 +- Assets/Shaders/Compute/Lightning.meta | 8 + .../Shaders/Compute/Lightning/Lightning 1.mat | 85 + .../Compute/Lightning/Lightning 1.mat.meta} | 4 +- .../Compute/Lightning/Lightning.compute | 139 + .../Compute/Lightning/Lightning.compute.meta | 9 + .../Shaders/Compute/Lightning/Lightning.mat | 85 + .../Compute/Lightning/Lightning.mat.meta | 8 + .../Compute/Lightning/Lightning.shader | 150 + .../Compute/Lightning/Lightning.shader.meta | 3 + .../Compute/Lightning/LightningCompute.cs | 357 + .../Lightning/LightningCompute.cs.meta | 11 + .../Compute/Lightning/LightningVertex.cginc | 20 + .../Lightning/LightningVertex.cginc.meta | 3 + Assets/Shaders/Compute/Slime/Slime.cs | 87 +- .../Shaders/Compute/Stardust/Stardust.compute | 3 +- Assets/Shaders/Compute/Stardust/Stardust.cs | 92 +- .../Shaders/Compute/Stardust/Stardust.shader | 238 +- Assets/Shaders/CustomDoF/AetheriaFogBlur.cs | 34 - .../Shaders/CustomDoF/AetheriaFogBlur.cs.meta | 11 - .../AetheriaFogBlurRendererFeature.cs | 279 - .../AetheriaFogBlurRendererFeature.cs.meta | 11 - .../Shaders/CustomDoF/AetheriaFogBlurUrp.hlsl | 146 - Assets/Shaders/CustomDoF/DepthOfField.cs | 260 +- Assets/Shaders/CustomDoF/DepthOfField.hlsl | 47 +- Assets/Shaders/CustomDoF/DepthOfField.shader | 80 +- Assets/Shaders/CustomDoF/StdLib.hlsl | 32 +- Assets/Shaders/Raymarching/CloudShader.shader | 192 +- Assets/Shaders/Raymarching/VolumeSun.shader | 293 +- Assets/Shaders/Volumetric.cginc | 24 +- .../ThirdParty/ambientCG/Metal012.meta | 8 - .../ThirdParty/ambientCG/Metal012/LICENSE.md | 14 - .../Metal012/Metal012_1K-JPG_Color.jpg | 3 - .../Metal012/Metal012_1K-JPG_Metalness.jpg | 3 - .../Metal012/Metal012_1K-JPG_NormalGL.jpg | 3 - ...niversalRenderPipelineGlobalSettings.asset | 3 - ...salRenderPipelineGlobalSettings.asset.meta | 8 - Assets/csc.rsp | 2 +- CultMath.csproj | 535 - Economy.Server/Economy.Server.csproj | 21 + Economy.Server/Program.cs | 234 + Economy.Server/Server.cs | 240 + Economy.Server/appsettings.json | 18 + Economy.Shared/Economy.Shared.csproj | 20 + .../Unity.Mathematics/Noise/LICENSE | 20 + Economy.Shared/Unity.Mathematics/Noise/README | 16 + .../Unity.Mathematics/Noise/cellular2D.cs | 58 + .../Unity.Mathematics/Noise/cellular2x2.cs | 46 + .../Unity.Mathematics/Noise/cellular2x2x2.cs | 65 + .../Unity.Mathematics/Noise/cellular3D.cs | 169 + .../Unity.Mathematics/Noise/classicnoise2D.cs | 102 + .../Unity.Mathematics/Noise/classicnoise3D.cs | 161 + .../Unity.Mathematics/Noise/classicnoise4D.cs | 291 + .../Unity.Mathematics/Noise/common.cs | 50 + .../Unity.Mathematics/Noise/noise2D.cs | 68 + .../Unity.Mathematics/Noise/noise3D.cs | 93 + .../Unity.Mathematics/Noise/noise3Dgrad.cs | 102 + .../Unity.Mathematics/Noise/noise4D.cs | 99 + .../Unity.Mathematics/Noise/psrdnoise2D.cs | 439 + .../Unity.Mathematics/PropertyAttributes.cs | 6 + .../Unity.Mathematics.asmdef | 4 + Economy.Shared/Unity.Mathematics/bool2.gen.cs | 501 + .../Unity.Mathematics/bool2x2.gen.cs | 209 + .../Unity.Mathematics/bool2x3.gen.cs | 216 + .../Unity.Mathematics/bool2x4.gen.cs | 223 + Economy.Shared/Unity.Mathematics/bool3.gen.cs | 1270 + .../Unity.Mathematics/bool3x2.gen.cs | 212 + .../Unity.Mathematics/bool3x3.gen.cs | 219 + .../Unity.Mathematics/bool3x4.gen.cs | 226 + Economy.Shared/Unity.Mathematics/bool4.gen.cs | 3187 +++ .../Unity.Mathematics/bool4x2.gen.cs | 215 + .../Unity.Mathematics/bool4x3.gen.cs | 222 + .../Unity.Mathematics/bool4x4.gen.cs | 229 + .../Unity.Mathematics/double2.gen.cs | 760 + .../Unity.Mathematics/double2x2.gen.cs | 468 + .../Unity.Mathematics/double2x3.gen.cs | 454 + .../Unity.Mathematics/double2x4.gen.cs | 469 + .../Unity.Mathematics/double3.gen.cs | 1538 ++ .../Unity.Mathematics/double3x2.gen.cs | 442 + .../Unity.Mathematics/double3x3.gen.cs | 494 + .../Unity.Mathematics/double3x4.gen.cs | 489 + .../Unity.Mathematics/double4.gen.cs | 3464 +++ .../Unity.Mathematics/double4x2.gen.cs | 445 + .../Unity.Mathematics/double4x3.gen.cs | 460 + .../Unity.Mathematics/double4x4.gen.cs | 596 + .../Unity.Mathematics/float2.gen.cs | 760 + .../Unity.Mathematics/float2x2.gen.cs | 468 + .../Unity.Mathematics/float2x3.gen.cs | 454 + .../Unity.Mathematics/float2x4.gen.cs | 469 + .../Unity.Mathematics/float3.gen.cs | 1538 ++ .../Unity.Mathematics/float3x2.gen.cs | 442 + .../Unity.Mathematics/float3x3.gen.cs | 494 + .../Unity.Mathematics/float3x4.gen.cs | 489 + .../Unity.Mathematics/float4.gen.cs | 3464 +++ .../Unity.Mathematics/float4x2.gen.cs | 445 + .../Unity.Mathematics/float4x3.gen.cs | 460 + .../Unity.Mathematics/float4x4.gen.cs | 596 + Economy.Shared/Unity.Mathematics/half.cs | 110 + Economy.Shared/Unity.Mathematics/half2.gen.cs | 471 + Economy.Shared/Unity.Mathematics/half3.gen.cs | 1239 + Economy.Shared/Unity.Mathematics/half4.gen.cs | 3155 +++ Economy.Shared/Unity.Mathematics/int2.gen.cs | 780 + .../Unity.Mathematics/int2x2.gen.cs | 506 + .../Unity.Mathematics/int2x3.gen.cs | 506 + .../Unity.Mathematics/int2x4.gen.cs | 521 + Economy.Shared/Unity.Mathematics/int3.gen.cs | 1556 ++ .../Unity.Mathematics/int3x2.gen.cs | 494 + .../Unity.Mathematics/int3x3.gen.cs | 527 + .../Unity.Mathematics/int3x4.gen.cs | 524 + Economy.Shared/Unity.Mathematics/int4.gen.cs | 3480 +++ .../Unity.Mathematics/int4x2.gen.cs | 497 + .../Unity.Mathematics/int4x3.gen.cs | 512 + .../Unity.Mathematics/int4x4.gen.cs | 546 + Economy.Shared/Unity.Mathematics/math.cs | 3470 +++ Economy.Shared/Unity.Mathematics/matrix.cs | 901 + .../Unity.Mathematics/matrix.gen.cs | 1821 ++ .../Unity.Mathematics/quaternion.cs | 610 + Economy.Shared/Unity.Mathematics/random.cs | 567 + .../Unity.Mathematics/rigid_transform.cs | 339 + Economy.Shared/Unity.Mathematics/uint2.gen.cs | 780 + .../Unity.Mathematics/uint2x2.gen.cs | 494 + .../Unity.Mathematics/uint2x3.gen.cs | 506 + .../Unity.Mathematics/uint2x4.gen.cs | 521 + Economy.Shared/Unity.Mathematics/uint3.gen.cs | 1556 ++ .../Unity.Mathematics/uint3x2.gen.cs | 494 + .../Unity.Mathematics/uint3x3.gen.cs | 512 + .../Unity.Mathematics/uint3x4.gen.cs | 524 + Economy.Shared/Unity.Mathematics/uint4.gen.cs | 3480 +++ .../Unity.Mathematics/uint4x2.gen.cs | 497 + .../Unity.Mathematics/uint4x3.gen.cs | 512 + .../Unity.Mathematics/uint4x4.gen.cs | 530 + .../netstandard2.0/Economy.Shared.deps.json | 1207 + .../Debug/netstandard2.0/Economy.Shared.dll | 3 + .../Economy.Shared.AssemblyInfo.cs | 22 + .../Economy.Shared.AssemblyInfoInputs.cache | 1 + .../Economy.Shared.assets.cache | Bin 0 -> 26223 bytes ...Economy.Shared.csproj.FileListAbsolute.txt | 8 + ...onomy.Shared.csprojAssemblyReference.cache | Bin 0 -> 4013 bytes .../Debug/netstandard2.0/Economy.Shared.dll | 3 + .../obj/Economy.Shared.csproj.nuget.cache | 5 + .../Economy.Shared.csproj.nuget.dgspec.json | 87 + .../obj/Economy.Shared.csproj.nuget.g.props | 21 + .../obj/Economy.Shared.csproj.nuget.g.targets | 9 + Economy.Shared/obj/project.assets.json | 4107 +++ Economy.Shared/obj/project.packagespec.json | 74 + GameCult.Aetheria.EveRuntime.csproj | 968 - GameCult.Aetheria.State.Unity.csproj | 591 - GameCult.Brokkr.Editor.csproj | 954 - GameCult.Brokkr.csproj | 926 - GameCult.Eve.Surface.csproj | 446 - GameCult.Eve.UnityScene.csproj | 964 - GameCult.Eve.UnityUIToolkit.csproj | 954 - GameData/KeyboardLayouts/ansi104.json | 1 + GameData/KeyboardLayouts/ansi104.msgpack | 3 + GameData/Legacy/AetherDB.2021-03-05.msgpack | 3 - GameData/Legacy/AetherDB.2021-04-14.msgpack | 3 - GameData/aetheria-client.cc | 1 - GameData/aetheria-hermodr-simple.cc | Bin 36474 -> 0 bytes GameData/aetheria-simple.cc | Bin 68637 -> 0 bytes GameData/aetheria-world.cc | Bin 12800966 -> 0 bytes .../aetheria-world.cc.authority.policy.cc | Bin 548 -> 0 bytes GameData/cultui-debug-surface.cultui | 116 - MessagePack.SourceDisabled.csproj | 64 - Packages/manifest.json | 38 +- .../README.md | 57 - .../README.md.meta | 7 - .../Runtime.meta | 8 - .../AetheriaEveFileSurfacePresenter.cs | 348 - .../AetheriaEveFileSurfacePresenter.cs.meta | 11 - .../Runtime/AetheriaEveRuntimeBootstrap.cs | 73 - .../AetheriaEveRuntimeBootstrap.cs.meta | 2 - .../AetheriaEveRuntimeUnityClientCache.cs | 99 - ...AetheriaEveRuntimeUnityClientCache.cs.meta | 2 - .../AetheriaEveRuntimeUnityHookInstaller.cs | 28 - ...theriaEveRuntimeUnityHookInstaller.cs.meta | 2 - .../Runtime/AetheriaEveSurfacePresenter.cs | 290 - .../AetheriaEveSurfacePresenter.cs.meta | 2 - .../AetheriaEveUnitySceneProviderBridge.cs | 464 - ...etheriaEveUnitySceneProviderBridge.cs.meta | 2 - .../AetheriaEveUnitySceneProviderComponent.cs | 161 - ...eriaEveUnitySceneProviderComponent.cs.meta | 2 - .../Runtime/AetheriaEveUnitySurfaceHost.cs | 411 - .../AetheriaEveUnitySurfaceHost.cs.meta | 2 - .../GameCult.Aetheria.EveRuntime.asmdef | 19 - .../GameCult.Aetheria.EveRuntime.asmdef.meta | 7 - .../package.json | 20 - .../package.json.meta | 7 - .../org.gamecult.aetheria.state/Runtime.meta | 8 - .../Runtime/AetheriaClient.cs | 229 - .../Runtime/AetheriaClient.cs.meta | 2 - .../Runtime/AetheriaClientState.cs | 344 - .../Runtime/AetheriaClientState.cs.meta | 11 - .../Runtime/AetheriaControl.cs | 244 - .../Runtime/AetheriaControl.cs.meta | 11 - .../Runtime/AetheriaRuntimeAgentTasks.cs | 1304 - .../Runtime/AetheriaRuntimeAgentTasks.cs.meta | 2 - .../Runtime/AetheriaRuntimeAssemblyInfo.cs | 5 - .../AetheriaRuntimeAssemblyInfo.cs.meta | 2 - .../Runtime/AetheriaRuntimeAssetDocuments.cs | 119 - .../AetheriaRuntimeAssetDocuments.cs.meta | 11 - .../Runtime/AetheriaRuntimeAssets.cs | 815 - .../Runtime/AetheriaRuntimeAssets.cs.meta | 11 - .../Runtime/AetheriaRuntimeBehaviorKinds.cs | 41 - .../AetheriaRuntimeBehaviorKinds.cs.meta | 11 - .../AetheriaRuntimeBehaviorMetadata.cs | 190 - .../AetheriaRuntimeBehaviorMetadata.cs.meta | 11 - .../AetheriaRuntimeBehaviorSimulation.cs | 495 - .../AetheriaRuntimeBehaviorSimulation.cs.meta | 2 - .../AetheriaRuntimeBehaviorStateProjector.cs | 133 - ...heriaRuntimeBehaviorStateProjector.cs.meta | 2 - .../AetheriaRuntimeCargoCapacityQueries.cs | 82 - ...etheriaRuntimeCargoCapacityQueries.cs.meta | 2 - ...iaRuntimeCargoItemDetailsSurfaceBuilder.cs | 734 - ...timeCargoItemDetailsSurfaceBuilder.cs.meta | 2 - .../AetheriaRuntimeCargoTransactions.cs | 68 - .../AetheriaRuntimeCargoTransactions.cs.meta | 2 - .../Runtime/AetheriaRuntimeCatalogCommands.cs | 13 - .../AetheriaRuntimeCatalogCommands.cs.meta | 2 - .../Runtime/AetheriaRuntimeCatalogSnapshot.cs | 1455 -- .../AetheriaRuntimeCatalogSnapshot.cs.meta | 2 - .../Runtime/AetheriaRuntimeCatalogStore.cs | 2070 -- .../AetheriaRuntimeCatalogStore.cs.meta | 2 - .../AetheriaRuntimeClientTargetCommands.cs | 218 - ...etheriaRuntimeClientTargetCommands.cs.meta | 2 - .../AetheriaRuntimeClientTargetStore.cs | 181 - .../AetheriaRuntimeClientTargetStore.cs.meta | 2 - ...theriaRuntimeClientTargetSurfaceBuilder.cs | 365 - ...aRuntimeClientTargetSurfaceBuilder.cs.meta | 2 - .../AetheriaRuntimeCommittedFactImporter.cs | 185 - ...theriaRuntimeCommittedFactImporter.cs.meta | 2 - .../AetheriaRuntimeConsumableSimulation.cs | 442 - ...etheriaRuntimeConsumableSimulation.cs.meta | 2 - .../AetheriaRuntimeCultCacheDocumentStore.cs | 526 - ...heriaRuntimeCultCacheDocumentStore.cs.meta | 2 - .../Runtime/AetheriaRuntimeDaemonDocuments.cs | 1070 - .../AetheriaRuntimeDaemonDocuments.cs.meta | 2 - ...theriaRuntimeDaemonEditorSurfaceBuilder.cs | 214 - ...aRuntimeDaemonEditorSurfaceBuilder.cs.meta | 2 - ...AetheriaRuntimeDaemonGameSurfaceBuilder.cs | 2128 -- ...riaRuntimeDaemonGameSurfaceBuilder.cs.meta | 2 - .../AetheriaRuntimeDaemonIntentState.cs | 92 - .../AetheriaRuntimeDaemonIntentState.cs.meta | 2 - .../AetheriaRuntimeDaemonItemStatQueries.cs | 366 - ...theriaRuntimeDaemonItemStatQueries.cs.meta | 2 - .../AetheriaRuntimeDaemonObservationCursor.cs | 83 - ...eriaRuntimeDaemonObservationCursor.cs.meta | 2 - .../AetheriaRuntimeDaemonOperationClient.cs | 600 - ...theriaRuntimeDaemonOperationClient.cs.meta | 2 - .../AetheriaRuntimeDaemonOperations.cs | 2483 -- .../AetheriaRuntimeDaemonOperations.cs.meta | 2 - .../AetheriaRuntimeDaemonOperationsClient.cs | 645 - ...heriaRuntimeDaemonOperationsClient.cs.meta | 2 - .../AetheriaRuntimeDaemonRenderQueries.cs | 2093 -- ...AetheriaRuntimeDaemonRenderQueries.cs.meta | 2 - .../AetheriaRuntimeDaemonRenderView.cs | 60 - .../AetheriaRuntimeDaemonRenderView.cs.meta | 2 - .../AetheriaRuntimeDaemonSimulation.cs | 2855 --- .../AetheriaRuntimeDaemonSimulation.cs.meta | 2 - ...AetheriaRuntimeDaemonSimulationSettings.cs | 303 - ...riaRuntimeDaemonSimulationSettings.cs.meta | 2 - .../AetheriaRuntimeDaemonSoaDocuments.cs | 298 - .../AetheriaRuntimeDaemonSoaDocuments.cs.meta | 2 - .../AetheriaRuntimeDaemonSoaFramePublisher.cs | 953 - ...eriaRuntimeDaemonSoaFramePublisher.cs.meta | 2 - .../AetheriaRuntimeDaemonSoaViewIndex.cs | 462 - .../AetheriaRuntimeDaemonSoaViewIndex.cs.meta | 2 - .../Runtime/AetheriaRuntimeDaemonStateRefs.cs | 32 - .../AetheriaRuntimeDaemonStateRefs.cs.meta | 2 - ...heriaRuntimeDaemonSurfaceCommandCatalog.cs | 99 - ...RuntimeDaemonSurfaceCommandCatalog.cs.meta | 2 - .../AetheriaRuntimeDaemonSurfaceCommands.cs | 26 - ...theriaRuntimeDaemonSurfaceCommands.cs.meta | 2 - .../AetheriaRuntimeDaemonTickRunner.cs | 517 - .../AetheriaRuntimeDaemonTickRunner.cs.meta | 2 - .../AetheriaRuntimeDaemonTradeItemQueries.cs | 279 - ...heriaRuntimeDaemonTradeItemQueries.cs.meta | 2 - .../AetheriaRuntimeEnergySimulation.cs | 198 - .../AetheriaRuntimeEnergySimulation.cs.meta | 2 - .../AetheriaRuntimeEquippedBehaviorQueries.cs | 195 - ...eriaRuntimeEquippedBehaviorQueries.cs.meta | 2 - ...untimeEquippedItemDetailsSurfaceBuilder.cs | 892 - ...eEquippedItemDetailsSurfaceBuilder.cs.meta | 2 - .../AetheriaRuntimeEveCommandClient.cs | 843 - .../AetheriaRuntimeEveCommandClient.cs.meta | 2 - .../AetheriaRuntimeEveCommandDocument.cs | 251 - .../AetheriaRuntimeEveCommandDocument.cs.meta | 2 - .../AetheriaRuntimeEveEntitySoaProjection.cs | 93 - ...heriaRuntimeEveEntitySoaProjection.cs.meta | 2 - .../AetheriaRuntimeEveSurfaceCatalog.cs | 250 - .../AetheriaRuntimeEveSurfaceCatalog.cs.meta | 2 - .../AetheriaRuntimeFlightSimulation.cs | 592 - .../AetheriaRuntimeFlightSimulation.cs.meta | 2 - .../Runtime/AetheriaRuntimeGameDocuments.cs | 1983 -- .../AetheriaRuntimeGameDocuments.cs.meta | 2 - .../Runtime/AetheriaRuntimeGameEvents.cs | 22 - .../Runtime/AetheriaRuntimeGameEvents.cs.meta | 2 - .../AetheriaRuntimeGameViewportDocuments.cs | 1567 -- ...theriaRuntimeGameViewportDocuments.cs.meta | 2 - .../Runtime/AetheriaRuntimeHangarDocuments.cs | 114 - .../AetheriaRuntimeHangarDocuments.cs.meta | 2 - .../AetheriaRuntimeHangarSurfaceBuilder.cs | 194 - ...etheriaRuntimeHangarSurfaceBuilder.cs.meta | 2 - ...AetheriaRuntimeInputCapabilityDocuments.cs | 545 - ...riaRuntimeInputCapabilityDocuments.cs.meta | 2 - .../AetheriaRuntimeInputSettingsCommands.cs | 23 - ...theriaRuntimeInputSettingsCommands.cs.meta | 2 - ...heriaRuntimeInputSettingsSurfaceBuilder.cs | 538 - ...RuntimeInputSettingsSurfaceBuilder.cs.meta | 2 - ...aRuntimeInventoryDropdownSurfaceBuilder.cs | 441 - ...imeInventoryDropdownSurfaceBuilder.cs.meta | 2 - ...untimeInventoryDropdownSurfaceDocuments.cs | 166 - ...eInventoryDropdownSurfaceDocuments.cs.meta | 2 - ...eriaRuntimeInventoryPanelSurfaceBuilder.cs | 559 - ...untimeInventoryPanelSurfaceBuilder.cs.meta | 2 - .../Runtime/AetheriaRuntimeItemCategories.cs | 14 - .../AetheriaRuntimeItemCategories.cs.meta | 2 - .../AetheriaRuntimeLoadoutTemplateCommands.cs | 13 - ...eriaRuntimeLoadoutTemplateCommands.cs.meta | 2 - ...AetheriaRuntimeLocalStorySurfaceBuilder.cs | 193 - ...riaRuntimeLocalStorySurfaceBuilder.cs.meta | 2 - .../AetheriaRuntimeMainMenuSurfaceBuilder.cs | 741 - ...heriaRuntimeMainMenuSurfaceBuilder.cs.meta | 2 - .../AetheriaRuntimeMiningSimulation.cs | 110 - .../AetheriaRuntimeMiningSimulation.cs.meta | 2 - .../AetheriaRuntimeOperationsCommands.cs | 13 - .../AetheriaRuntimeOperationsCommands.cs.meta | 2 - .../Runtime/AetheriaRuntimeOrbitQueries.cs | 82 - .../AetheriaRuntimeOrbitQueries.cs.meta | 11 - .../Runtime/AetheriaRuntimeOrbitSimulation.cs | 49 - .../AetheriaRuntimeOrbitSimulation.cs.meta | 11 - .../AetheriaRuntimePickupTransactions.cs | 53 - .../AetheriaRuntimePickupTransactions.cs.meta | 2 - .../AetheriaRuntimePlayerSettingsCommands.cs | 25 - ...heriaRuntimePlayerSettingsCommands.cs.meta | 2 - ...eriaRuntimePlayerSettingsSurfaceBuilder.cs | 199 - ...untimePlayerSettingsSurfaceBuilder.cs.meta | 2 - .../AetheriaRuntimeRefitTransactions.cs | 828 - .../AetheriaRuntimeRefitTransactions.cs.meta | 2 - .../AetheriaRuntimeRenderSplatDocuments.cs | 49 - ...etheriaRuntimeRenderSplatDocuments.cs.meta | 2 - .../AetheriaRuntimeSectorMapSurfaceBuilder.cs | 383 - ...eriaRuntimeSectorMapSurfaceBuilder.cs.meta | 11 - .../AetheriaRuntimeSensorSimulation.cs | 365 - .../AetheriaRuntimeSensorSimulation.cs.meta | 11 - .../AetheriaRuntimeSettingsDocuments.cs | 180 - .../AetheriaRuntimeSettingsDocuments.cs.meta | 2 - ...theriaRuntimeShipSettingsSurfaceBuilder.cs | 197 - ...aRuntimeShipSettingsSurfaceBuilder.cs.meta | 2 - .../Runtime/AetheriaRuntimeShotReceipts.cs | 26 - .../AetheriaRuntimeShotReceipts.cs.meta | 2 - .../AetheriaRuntimeSnapshotDocuments.cs | 1605 -- .../AetheriaRuntimeSnapshotDocuments.cs.meta | 2 - .../AetheriaRuntimeStarbridgeDocuments.cs | 321 - ...AetheriaRuntimeStarbridgeDocuments.cs.meta | 2 - ...riaRuntimeStarbridgePlayerSeatDocuments.cs | 156 - ...ntimeStarbridgePlayerSeatDocuments.cs.meta | 2 - .../AetheriaRuntimeStatRecipeCommands.cs | 40 - .../AetheriaRuntimeStatRecipeCommands.cs.meta | 2 - ...AetheriaRuntimeStatRecipeSurfaceBuilder.cs | 915 - ...riaRuntimeStatRecipeSurfaceBuilder.cs.meta | 2 - .../Runtime/AetheriaRuntimeStatRecipes.cs | 363 - .../AetheriaRuntimeStatRecipes.cs.meta | 2 - .../Runtime/AetheriaRuntimeStateBoot.cs | 165 - .../Runtime/AetheriaRuntimeStateBoot.cs.meta | 2 - .../Runtime/AetheriaRuntimeStateBoundary.cs | 124 - .../AetheriaRuntimeStateBoundary.cs.meta | 2 - .../AetheriaRuntimeStateRefResolver.cs | 310 - .../AetheriaRuntimeStateRefResolver.cs.meta | 2 - .../AetheriaRuntimeSurfaceDocuments.cs | 631 - .../AetheriaRuntimeSurfaceDocuments.cs.meta | 2 - .../AetheriaRuntimeSurveySimulation.cs | 51 - .../AetheriaRuntimeSurveySimulation.cs.meta | 2 - ...AetheriaRuntimeThermalMedicalSimulation.cs | 90 - ...riaRuntimeThermalMedicalSimulation.cs.meta | 2 - .../AetheriaRuntimeThermalSimulation.cs | 411 - .../AetheriaRuntimeThermalSimulation.cs.meta | 2 - .../AetheriaRuntimeTractorMechanics.cs | 16 - .../AetheriaRuntimeTractorMechanics.cs.meta | 2 - ...RuntimeTradeCargoSelectorSurfaceBuilder.cs | 326 - ...meTradeCargoSelectorSurfaceBuilder.cs.meta | 2 - ...iaRuntimeTradeInteractionSurfaceBuilder.cs | 581 - ...timeTradeInteractionSurfaceBuilder.cs.meta | 2 - ...iaRuntimeTradeItemDetailsSurfaceBuilder.cs | 477 - ...timeTradeItemDetailsSurfaceBuilder.cs.meta | 2 - ...iaRuntimeTradeValuePolicySurfaceBuilder.cs | 231 - ...timeTradeValuePolicySurfaceBuilder.cs.meta | 2 - ...theriaRuntimeTurretControllerSimulation.cs | 133 - ...aRuntimeTurretControllerSimulation.cs.meta | 11 - .../AetheriaRuntimeVerseAuthorityPolicy.cs | 464 - ...etheriaRuntimeVerseAuthorityPolicy.cs.meta | 2 - .../Runtime/AetheriaRuntimeVerseClient.cs | 1133 - .../AetheriaRuntimeVerseClient.cs.meta | 2 - .../Runtime/AetheriaRuntimeVerseDiscovery.cs | 91 - .../AetheriaRuntimeVerseDiscovery.cs.meta | 2 - .../AetheriaRuntimeVerseHostCommands.cs | 15 - .../AetheriaRuntimeVerseHostCommands.cs.meta | 2 - .../AetheriaRuntimeVerseReplicaBridge.cs | 272 - .../AetheriaRuntimeVerseReplicaBridge.cs.meta | 2 - .../AetheriaRuntimeVisibilitySimulation.cs | 145 - ...etheriaRuntimeVisibilitySimulation.cs.meta | 11 - .../Runtime/AetheriaRuntimeWorldPhysics.cs | 88 - .../AetheriaRuntimeWorldPhysics.cs.meta | 2 - ...etheriaRuntimeZoneDetailsSurfaceBuilder.cs | 407 - ...iaRuntimeZoneDetailsSurfaceBuilder.cs.meta | 2 - .../Runtime/AetheriaState.cs | 204 - .../Runtime/AetheriaState.cs.meta | 2 - .../Runtime/AetheriaUi.cs | 50 - .../Runtime/AetheriaUi.cs.meta | 11 - .../GameCult.Aetheria.State.Unity.asmdef | 46 - .../GameCult.Aetheria.State.Unity.asmdef.meta | 7 - .../org.gamecult.aetheria.state/package.json | 18 - .../package.json.meta | 7 - Packages/packages-lock.json | 277 +- ProjectSettings/EditorSettings.asset | 4 +- ProjectSettings/EntitiesClientSettings.asset | 3 - ProjectSettings/GraphicsSettings.asset | 4 +- ProjectSettings/ProjectSettings.asset | 4 +- ProjectSettings/ProjectVersion.txt | 4 +- ProjectSettings/ProjectVersionPrevious.txt | 4 +- ProjectSettings/QualitySettings.asset | 4 +- ProjectSettings/ShaderGraphSettings.asset | 3 - ProjectSettings/URPProjectSettings.asset | 3 - README.md | 72 +- RethinkDB Queries.txt | 33 + UpgradeLog.htm | Bin 0 -> 46900 bytes UserSettings/EditorUserSettings.asset | 3 + .../Layouts/CurrentMaximizeLayout.dwlt | 2313 ++ UserSettings/Layouts/default-2021.dwlt | 1081 + UserSettings/Layouts/default-6000.dwlt | 1390 + UserSettings/Search.settings | 1 + conformance/eve/README.md | 6 - conformance/eve/aetheria-world-scenario.json | 94 - .../aetheria-world-surface.conformance.json | 26 - conformance/eve/aetheria-world-surface.json | 230 - .../eve/aetheria.provider-advertisement.json | 226 - ...a.unity-playable-world-asset-manifest.json | 21 - conformance/eve/provider-pack.json | 17 - csc.rsp | 1 - docs/aetheria-current-codebase-model.md | 655 - docs/aetheria-perfect-machine-map.md | 3611 +-- docs/aetheria-verse-client-contract.md | 147 - docs/blender-entity-authoring-v1.md | 343 - docs/cockpit-doctrine-combat.md | 245 - docs/cultmesh-feature-implementation-guide.md | 511 - docs/deterministic-combat-resolution.md | 305 - docs/developer-navigation.md | 533 - docs/game-modes-and-progression.md | 240 - docs/hangar-launcher.md | 82 - docs/original-game-combat-spec.md | 138 - docs/original-game-migration-matrix.md | 172 - docs/original-game-presentation-spec.md | 139 - docs/original-game-rendering-spec.md | 160 - docs/original-game-simulation-spec.md | 200 - docs/original-game-specification.md | 485 - docs/original-game-world-economy-spec.md | 134 - docs/renderless-aetheria-architecture.md | 426 - docs/unity-daemon-development.md | 98 - scripts/run-aetheria-unity.ps1 | 94 - scripts/start-aetheria-daemon-editor.ps1 | 58 - scripts/verify-aetheria-daemon.ps1 | 52 - scripts/verify-aetheria-unity-client.ps1 | 137 - scripts/verify-eve-conformance-pack.mjs | 51 - .../Aetheria.TransportProbe.csproj | 14 - tools/Aetheria.TransportProbe/Program.cs | 40 - .../blender/create_aetheria_entity_fixture.py | 481 - tools/blender/validate_aetheria_entity.py | 204 - tools/clean-ignored-worktree.ps1 | 181 - 2070 files changed, 180272 insertions(+), 169085 deletions(-) delete mode 100644 AGENTS.md delete mode 100644 Aetheria RTS.lnk delete mode 100644 Aetheria.Rts.Web/Client/app.ts delete mode 100644 Aetheria.Rts.Web/Electron/aetheria-cultmesh.ts delete mode 100644 Aetheria.Rts.Web/Electron/aetheria-rts-bindings.ts delete mode 100644 Aetheria.Rts.Web/Electron/aetheria-rts-generated-bindings.ts delete mode 100644 Aetheria.Rts.Web/Electron/aetheria-rts-local-documents.ts delete mode 100644 Aetheria.Rts.Web/Electron/main.ts delete mode 100644 Aetheria.Rts.Web/package-lock.json delete mode 100644 Aetheria.Rts.Web/package.json delete mode 100644 Aetheria.Rts.Web/scripts/generate-rts-bindings.mjs delete mode 100644 Aetheria.Rts.Web/scripts/verify-stage7b-rts-client.ps1 delete mode 100644 Aetheria.Rts.Web/scripts/verify-stage7c-electron-rts.ps1 delete mode 100644 Aetheria.Rts.Web/scripts/verify-stage7c-local-runtime.ps1 delete mode 100644 Aetheria.Rts.Web/scripts/verify-stage7d-remote-cultmesh.ps1 delete mode 100644 Aetheria.Rts.Web/tsconfig.electron.json delete mode 100644 Aetheria.Rts.Web/tsconfig.json delete mode 100644 Aetheria.Rts.Web/wwwroot/index.html delete mode 100644 Aetheria.Rts.Web/wwwroot/styles.css delete mode 100644 Aetheria.State.AuthoritySmoke/Aetheria.State.AuthoritySmoke.csproj delete mode 100644 Aetheria.State.AuthoritySmoke/Program.cs delete mode 100644 Aetheria.State.Daemon.Smoke/Aetheria.State.Daemon.Smoke.csproj delete mode 100644 Aetheria.State.Daemon.Smoke/Program.cs delete mode 100644 Aetheria.State.Daemon/Aetheria.State.Daemon.csproj delete mode 100644 Aetheria.State.Daemon/AetheriaDaemonGeneratedEntityFactory.cs delete mode 100644 Aetheria.State.Daemon/AetheriaDaemonHangarCoordinator.cs delete mode 100644 Aetheria.State.Daemon/AetheriaDaemonLoadoutGenerator.cs delete mode 100644 Aetheria.State.Daemon/AetheriaDaemonNativeCatalog.cs delete mode 100644 Aetheria.State.Daemon/AetheriaDaemonRegularRunWriter.cs delete mode 100644 Aetheria.State.Daemon/AetheriaDaemonRegularTopologyGenerator.cs delete mode 100644 Aetheria.State.Daemon/AetheriaDaemonRunFactory.cs delete mode 100644 Aetheria.State.Daemon/AetheriaDaemonTutorialRunWriter.cs delete mode 100644 Aetheria.State.Daemon/AetheriaDaemonTutorialTopologyGenerator.cs delete mode 100644 Aetheria.State.Daemon/AetheriaDaemonTutorialWorldMaterializer.cs delete mode 100644 Aetheria.State.Daemon/AetheriaDaemonZoneBodyGenerator.cs delete mode 100644 Aetheria.State.Daemon/AetheriaDaemonZoneGenerator.cs delete mode 100644 Aetheria.State.Daemon/AetheriaYmirPersistence.cs delete mode 100644 Aetheria.State.Daemon/AetheriaYmirPersistenceCoordinator.cs delete mode 100644 Aetheria.State.Daemon/AetheriaYmirPersistenceDocuments.cs delete mode 100644 Aetheria.State.Daemon/AetheriaYmirWorldPhysics.cs delete mode 100644 Aetheria.State.Daemon/Program.cs delete mode 100644 Aetheria.State.Daemon/Properties/AssemblyInfo.cs delete mode 100644 Aetheria.State.Freeze/Aetheria.State.Freeze.csproj delete mode 100644 Aetheria.State.Freeze/Program.cs delete mode 100644 Aetheria.State.Import/Aetheria.State.Import.csproj delete mode 100644 Aetheria.State.Import/Program.cs delete mode 100644 Aetheria.State.Replica/Aetheria.State.Replica.csproj delete mode 100644 Aetheria.State.Replica/Program.cs delete mode 100644 Aetheria.State.Smoke/Aetheria.State.Smoke.csproj delete mode 100644 Aetheria.State.Smoke/Program.cs delete mode 100644 Aetheria.State.Unity.Smoke/Aetheria.State.Unity.Smoke.csproj delete mode 100644 Aetheria.State.Unity.Smoke/Program.cs delete mode 100644 Aetheria.State.Unity/Aetheria.State.Unity.csproj delete mode 100644 Aetheria.State.Unity/AetheriaRuntimeCatalogClient.cs delete mode 100644 Aetheria.State.Verify/Aetheria.State.Verify.csproj delete mode 100644 Aetheria.State.Verify/AetheriaCatalogEnumProbeTypes.cs delete mode 100644 Aetheria.State.Verify/Program.cs delete mode 100644 Aetheria.State/Aetheria.State.Dependencies.props delete mode 100644 Aetheria.State/Aetheria.State.Dependencies.targets delete mode 100644 Aetheria.State/Aetheria.State.csproj delete mode 100644 Aetheria.State/AetheriaCatalogKeys.cs delete mode 100644 Aetheria.State/AetheriaDocumentRegistry.cs delete mode 100644 Aetheria.State/AetheriaEveCommandBridge.cs delete mode 100644 Aetheria.State/AetheriaEveSurfaceDocuments.cs delete mode 100644 Aetheria.State/AetheriaHangar.cs delete mode 100644 Aetheria.State/AetheriaRuntimeStateMapper.cs delete mode 100644 Aetheria.State/AetheriaStateNode.cs delete mode 100644 Aetheria.State/AetheriaStatePaths.cs delete mode 100644 Aetheria.State/AetheriaVerseCatalogDocuments.cs delete mode 100644 Aetheria.State/AetheriaVerseDiscoveryHost.cs delete mode 100644 Aetheria.State/AetheriaVerseHostSettingsNormalizer.cs delete mode 100644 Aetheria.State/AetheriaVerseReplica.cs delete mode 100644 Aetheria.State/Documents/AetheriaCorporation.cs delete mode 100644 Aetheria.State/Documents/AetheriaEveCommandAcceptanceStatus.cs delete mode 100644 Aetheria.State/Documents/AetheriaGameSessionState.cs delete mode 100644 Aetheria.State/Documents/AetheriaItemDefinition.cs delete mode 100644 Aetheria.State/Documents/AetheriaLegacyCatalogQuarantine.cs delete mode 100644 Aetheria.State/Documents/AetheriaMainMenuState.cs delete mode 100644 Aetheria.State/Documents/AetheriaMigrationLedger.cs delete mode 100644 Aetheria.State/Documents/AetheriaNameFile.cs delete mode 100644 Aetheria.State/Documents/AetheriaPlayerProfile.cs delete mode 100644 Aetheria.State/Documents/AetheriaRuntimeSession.cs delete mode 100644 Aetheria.State/Documents/AetheriaRuntimeStateDocuments.cs delete mode 100644 Aetheria.State/Documents/AetheriaTradeValuePolicy.cs delete mode 100644 Aetheria.State/Documents/AetheriaVerseHostSettings.cs delete mode 100644 Aetheria.State/Documents/AetheriaWorldState.cs delete mode 100644 Aetheria.State/Migration/LegacyMigrationBoundary.cs delete mode 100644 Aetheria.State/README.md delete mode 100644 Aetheria.State/docs/client-runtime-conformance.md delete mode 100644 Aetheria.State/docs/cultmesh-cross-runtime-primitives.md delete mode 100644 Aetheria.State/docs/cultmesh-ergonomics-staged-migration-plan.md delete mode 100644 Aetheria.State/docs/daemon-code-map-for-rust-port.md delete mode 100644 Aetheria.State/docs/rust-rebuild-freeze-checklist.md delete mode 100644 Aetheria.State/docs/stage-7-client-surface-inventory.md delete mode 100644 Aetheria.State/docs/stage-7-thin-client-staged-implementation-plan.md delete mode 100644 Aetheria.State/docs/verse-authority-implementation-plan.md delete mode 100644 Aetheria.State/docs/verse-authority-policy.md delete mode 100644 Aetheria.State/docs/verse-daemon-shape.md delete mode 100644 Aetheria.State/scripts/verify-stage7d-unity-parity.ps1 delete mode 100644 Aetheria.Unity/.gitignore delete mode 100644 Aetheria.Unity/Assets/Aetheria.unity delete mode 100644 Aetheria.Unity/Assets/AetheriaUnityClient.cs delete mode 100644 Aetheria.Unity/Assets/AetheriaUnityClient.cs.meta delete mode 100644 Aetheria.Unity/Assets/AetheriaUnityThermalPresentationSink.cs delete mode 100644 Aetheria.Unity/Assets/AetheriaUnityThermalPresentationSink.cs.meta delete mode 100644 Aetheria.Unity/Assets/Editor/AetheriaDaemonDevelopmentWindow.cs delete mode 100644 Aetheria.Unity/Assets/Editor/AetheriaDaemonDevelopmentWindow.cs.meta delete mode 100644 Aetheria.Unity/Assets/Editor/AetheriaUnityBuild.cs delete mode 100644 Aetheria.Unity/Assets/Editor/AetheriaUnityBuild.cs.meta delete mode 100644 Aetheria.Unity/Assets/Settings.meta delete mode 100644 Aetheria.Unity/Assets/Settings/AetheriaUniversalRenderPipeline.asset delete mode 100644 Aetheria.Unity/Assets/Settings/AetheriaUniversalRenderPipeline.asset.meta delete mode 100644 Aetheria.Unity/Assets/Settings/AetheriaUniversalRenderer.asset delete mode 100644 Aetheria.Unity/Assets/Settings/AetheriaUniversalRenderer.asset.meta delete mode 100644 Aetheria.Unity/Packages/manifest.json delete mode 100644 Aetheria.Unity/Packages/packages-lock.json delete mode 100644 Aetheria.Unity/ProjectSettings/AudioManager.asset delete mode 100644 Aetheria.Unity/ProjectSettings/ClusterInputManager.asset delete mode 100644 Aetheria.Unity/ProjectSettings/DynamicsManager.asset delete mode 100644 Aetheria.Unity/ProjectSettings/EditorBuildSettings.asset delete mode 100644 Aetheria.Unity/ProjectSettings/EditorSettings.asset delete mode 100644 Aetheria.Unity/ProjectSettings/GraphicsSettings.asset delete mode 100644 Aetheria.Unity/ProjectSettings/InputManager.asset delete mode 100644 Aetheria.Unity/ProjectSettings/MemorySettings.asset delete mode 100644 Aetheria.Unity/ProjectSettings/MultiplayerManager.asset delete mode 100644 Aetheria.Unity/ProjectSettings/NavMeshAreas.asset delete mode 100644 Aetheria.Unity/ProjectSettings/Physics2DSettings.asset delete mode 100644 Aetheria.Unity/ProjectSettings/PresetManager.asset delete mode 100644 Aetheria.Unity/ProjectSettings/ProjectSettings.asset delete mode 100644 Aetheria.Unity/ProjectSettings/ProjectVersion.txt delete mode 100644 Aetheria.Unity/ProjectSettings/QualitySettings.asset delete mode 100644 Aetheria.Unity/ProjectSettings/SceneTemplateSettings.json delete mode 100644 Aetheria.Unity/ProjectSettings/TagManager.asset delete mode 100644 Aetheria.Unity/ProjectSettings/TimeManager.asset delete mode 100644 Aetheria.Unity/ProjectSettings/UnityConnectSettings.asset delete mode 100644 Aetheria.Unity/ProjectSettings/VFXManager.asset delete mode 100644 Aetheria.Unity/ProjectSettings/VersionControlSettings.asset delete mode 100644 Aetheria.Unity/README.md delete mode 100644 Aetheria.sln delete mode 100644 Asset Sources/Conformance/AetheriaEntityAuthoringV1.blend rename Assets/{Textures/ThirdParty/ambientCG.meta => Allegorithmic.meta} (77%) rename Assets/{Scripts/Rendering.meta => Allegorithmic/Plugins.meta} (77%) create mode 100644 Assets/Allegorithmic/Plugins/EULA.txt rename Assets/{Textures/ThirdParty/ambientCG/Metal012/LICENSE.md.meta => Allegorithmic/Plugins/EULA.txt.meta} (75%) rename Assets/{Textures/ThirdParty.meta => Allegorithmic/Plugins/Substance.EditorHelper.meta} (77%) create mode 100644 Assets/Allegorithmic/Plugins/Substance.EditorHelper/Scripter.cs rename Assets/{Editor/AetheriaRenderProbe.cs.meta => Allegorithmic/Plugins/Substance.EditorHelper/Scripter.cs.meta} (83%) create mode 100644 Assets/Allegorithmic/Plugins/Substance.EditorHelper/Substance.EditorHelper.asmdef create mode 100644 Assets/Allegorithmic/Plugins/Substance.EditorHelper/Substance.EditorHelper.asmdef.meta create mode 100644 Assets/Allegorithmic/Plugins/Substance.Engine.bundle.meta create mode 100644 Assets/Allegorithmic/Plugins/Substance.Engine.bundle/Contents.meta create mode 100644 Assets/Allegorithmic/Plugins/Substance.Engine.bundle/Contents/Info.plist rename Aetheria.Unity/Assets/Aetheria.unity.meta => Assets/Allegorithmic/Plugins/Substance.Engine.bundle/Contents/Info.plist.meta (74%) create mode 100644 Assets/Allegorithmic/Plugins/Substance.Engine.bundle/Contents/MacOS.meta create mode 100644 Assets/Allegorithmic/Plugins/Substance.Engine.bundle/Contents/MacOS/Substance.Engine create mode 100644 Assets/Allegorithmic/Plugins/Substance.Engine.bundle/Contents/MacOS/Substance.Engine.meta create mode 100644 Assets/Allegorithmic/Plugins/Substance.Engine.dll create mode 100644 Assets/Allegorithmic/Plugins/Substance.Engine.dll.meta create mode 100644 Assets/Allegorithmic/Plugins/Substance.Game.dll create mode 100644 Assets/Allegorithmic/Plugins/Substance.Game.dll.meta create mode 100644 Assets/Allegorithmic/Plugins/Substance.Platform.meta create mode 100644 Assets/Allegorithmic/Plugins/Substance.Platform/NativeFunctions.cs rename Assets/{Scripts/Gameplay/AetheriaRenderSplatLayerRenderer.cs.meta => Allegorithmic/Plugins/Substance.Platform/NativeFunctions.cs.meta} (83%) create mode 100644 Assets/Allegorithmic/Plugins/Substance.Platform/Substance.Platform.asmdef create mode 100644 Assets/Allegorithmic/Plugins/Substance.Platform/Substance.Platform.asmdef.meta create mode 100644 Assets/Allegorithmic/Plugins/Substance.meta create mode 100644 Assets/Allegorithmic/Plugins/Substance/Arm64.meta create mode 100644 Assets/Allegorithmic/Plugins/Substance/Arm64/libSubstance.Engine.Mobile.so create mode 100644 Assets/Allegorithmic/Plugins/Substance/Arm64/libSubstance.Engine.Mobile.so.meta create mode 100644 Assets/Allegorithmic/Plugins/Substance/Arm64/libalgcompression.a create mode 100644 Assets/Allegorithmic/Plugins/Substance/Arm64/libalgcompression.a.meta create mode 100644 Assets/Allegorithmic/Plugins/Substance/Arm64/libpfxlinkercommon.a create mode 100644 Assets/Allegorithmic/Plugins/Substance/Arm64/libpfxlinkercommon.a.meta create mode 100644 Assets/Allegorithmic/Plugins/Substance/Arm64/libsubstance_framework.a create mode 100644 Assets/Allegorithmic/Plugins/Substance/Arm64/libsubstance_framework.a.meta create mode 100644 Assets/Allegorithmic/Plugins/Substance/Arm64/libsubstance_linker.a create mode 100644 Assets/Allegorithmic/Plugins/Substance/Arm64/libsubstance_linker.a.meta create mode 100644 Assets/Allegorithmic/Plugins/Substance/Arm64/libsubstance_neon_blend.a create mode 100644 Assets/Allegorithmic/Plugins/Substance/Arm64/libsubstance_neon_blend.a.meta create mode 100644 Assets/Allegorithmic/Plugins/Substance/Arm64/libtinyxml.a create mode 100644 Assets/Allegorithmic/Plugins/Substance/Arm64/libtinyxml.a.meta create mode 100644 Assets/Allegorithmic/Plugins/Substance/Armv7.meta create mode 100644 Assets/Allegorithmic/Plugins/Substance/Armv7/libSubstance.Engine.Mobile.so create mode 100644 Assets/Allegorithmic/Plugins/Substance/Armv7/libSubstance.Engine.Mobile.so.meta create mode 100644 Assets/Allegorithmic/Plugins/Substance/Armv7/libalgcompression.a create mode 100644 Assets/Allegorithmic/Plugins/Substance/Armv7/libalgcompression.a.meta create mode 100644 Assets/Allegorithmic/Plugins/Substance/Armv7/libpfxlinkercommon.a create mode 100644 Assets/Allegorithmic/Plugins/Substance/Armv7/libpfxlinkercommon.a.meta create mode 100644 Assets/Allegorithmic/Plugins/Substance/Armv7/libsubstance_framework.a create mode 100644 Assets/Allegorithmic/Plugins/Substance/Armv7/libsubstance_framework.a.meta create mode 100644 Assets/Allegorithmic/Plugins/Substance/Armv7/libsubstance_linker.a create mode 100644 Assets/Allegorithmic/Plugins/Substance/Armv7/libsubstance_linker.a.meta create mode 100644 Assets/Allegorithmic/Plugins/Substance/Armv7/libsubstance_neon_blend.a create mode 100644 Assets/Allegorithmic/Plugins/Substance/Armv7/libsubstance_neon_blend.a.meta create mode 100644 Assets/Allegorithmic/Plugins/Substance/Armv7/libtinyxml.a create mode 100644 Assets/Allegorithmic/Plugins/Substance/Armv7/libtinyxml.a.meta create mode 100644 Assets/Allegorithmic/Plugins/Substance/Substance Engine.meta rename {Aetheria.Unity/Assets => Assets/Allegorithmic/Plugins/Substance/Substance Engine}/Editor.meta (77%) create mode 100644 Assets/Allegorithmic/Plugins/Substance/Substance Engine/Editor/MainObject.png rename Assets/{Textures/ThirdParty/ambientCG/Metal012/Metal012_1K-JPG_Color.jpg.meta => Allegorithmic/Plugins/Substance/Substance Engine/Editor/MainObject.png.meta} (55%) create mode 100644 Assets/Allegorithmic/Plugins/Substance/Substance Engine/Editor/Substance.Editor.dll create mode 100644 Assets/Allegorithmic/Plugins/Substance/Substance Engine/Editor/Substance.Editor.dll.meta create mode 100644 Assets/Allegorithmic/Plugins/Substance/Substance Engine/Editor/Substance.png rename Assets/{Textures/ThirdParty/ambientCG/Metal012/Metal012_1K-JPG_Metalness.jpg.meta => Allegorithmic/Plugins/Substance/Substance Engine/Editor/Substance.png.meta} (54%) create mode 100644 Assets/Allegorithmic/Plugins/Substance/Substance Engine/Editor/SubstanceGraph-32.png rename Assets/{Textures/ThirdParty/ambientCG/Metal012/Metal012_1K-JPG_NormalGL.jpg.meta => Allegorithmic/Plugins/Substance/Substance Engine/Editor/SubstanceGraph-32.png.meta} (53%) create mode 100644 Assets/Allegorithmic/Plugins/Substance/Substance Engine/Editor/SubstanceGraph.png create mode 100644 Assets/Allegorithmic/Plugins/Substance/Substance Engine/Editor/SubstanceGraph.png.meta create mode 100644 Assets/Allegorithmic/Plugins/Substance/Substance Source.meta create mode 100644 Assets/Allegorithmic/Plugins/Substance/Substance Source/Editor.meta create mode 100644 Assets/Allegorithmic/Plugins/Substance/Substance Source/Editor/Images.meta create mode 100644 Assets/Allegorithmic/Plugins/Substance/Substance Source/Editor/Images/AssetDownload.png create mode 100644 Assets/Allegorithmic/Plugins/Substance/Substance Source/Editor/Images/AssetDownload.png.meta create mode 100644 Assets/Allegorithmic/Plugins/Substance/Substance Source/Editor/Images/AvailableDownloads.png create mode 100644 Assets/Allegorithmic/Plugins/Substance/Substance Source/Editor/Images/AvailableDownloads.png.meta create mode 100644 Assets/Allegorithmic/Plugins/Substance/Substance Source/Editor/Images/DetailExitButton.png create mode 100644 Assets/Allegorithmic/Plugins/Substance/Substance Source/Editor/Images/DetailExitButton.png.meta create mode 100644 Assets/Allegorithmic/Plugins/Substance/Substance Source/Editor/Images/LoginButton.png create mode 100644 Assets/Allegorithmic/Plugins/Substance/Substance Source/Editor/Images/LoginButton.png.meta create mode 100644 Assets/Allegorithmic/Plugins/Substance/Substance Source/Editor/Images/LogoutButton.png create mode 100644 Assets/Allegorithmic/Plugins/Substance/Substance Source/Editor/Images/LogoutButton.png.meta create mode 100644 Assets/Allegorithmic/Plugins/Substance/Substance Source/Editor/Images/MagnifyingGlass.png create mode 100644 Assets/Allegorithmic/Plugins/Substance/Substance Source/Editor/Images/MagnifyingGlass.png.meta create mode 100644 Assets/Allegorithmic/Plugins/Substance/Substance Source/Editor/Images/MyAssetsButton.png create mode 100644 Assets/Allegorithmic/Plugins/Substance/Substance Source/Editor/Images/MyAssetsButton.png.meta create mode 100644 Assets/Allegorithmic/Plugins/Substance/Substance Source/Editor/Images/Source.Icon.png create mode 100644 Assets/Allegorithmic/Plugins/Substance/Substance Source/Editor/Images/Source.Icon.png.meta create mode 100644 Assets/Allegorithmic/Plugins/Substance/Substance Source/Editor/Images/SubstanceSourceLoading.png create mode 100644 Assets/Allegorithmic/Plugins/Substance/Substance Source/Editor/Images/SubstanceSourceLoading.png.meta create mode 100644 Assets/Allegorithmic/Plugins/Substance/Substance Source/Editor/Images/download.png create mode 100644 Assets/Allegorithmic/Plugins/Substance/Substance Source/Editor/Images/download.png.meta create mode 100644 Assets/Allegorithmic/Plugins/Substance/Substance Source/Editor/Images/free.png create mode 100644 Assets/Allegorithmic/Plugins/Substance/Substance Source/Editor/Images/free.png.meta create mode 100644 Assets/Allegorithmic/Plugins/Substance/Substance Source/Editor/Images/leftarrow.png create mode 100644 Assets/Allegorithmic/Plugins/Substance/Substance Source/Editor/Images/leftarrow.png.meta create mode 100644 Assets/Allegorithmic/Plugins/Substance/Substance Source/Editor/Images/library.png create mode 100644 Assets/Allegorithmic/Plugins/Substance/Substance Source/Editor/Images/library.png.meta create mode 100644 Assets/Allegorithmic/Plugins/Substance/Substance Source/Editor/Images/logo-substance.png create mode 100644 Assets/Allegorithmic/Plugins/Substance/Substance Source/Editor/Images/logo-substance.png.meta create mode 100644 Assets/Allegorithmic/Plugins/Substance/Substance Source/Editor/Images/logo-white.png create mode 100644 Assets/Allegorithmic/Plugins/Substance/Substance Source/Editor/Images/logo-white.png.meta create mode 100644 Assets/Allegorithmic/Plugins/Substance/Substance Source/Editor/Images/new.png create mode 100644 Assets/Allegorithmic/Plugins/Substance/Substance Source/Editor/Images/new.png.meta create mode 100644 Assets/Allegorithmic/Plugins/Substance/Substance Source/Editor/Images/rightarrow.png create mode 100644 Assets/Allegorithmic/Plugins/Substance/Substance Source/Editor/Images/rightarrow.png.meta create mode 100644 Assets/Allegorithmic/Plugins/Substance/Substance Source/Editor/Images/spinner.png create mode 100644 Assets/Allegorithmic/Plugins/Substance/Substance Source/Editor/Images/spinner.png.meta create mode 100644 Assets/Allegorithmic/Plugins/Substance/Substance Source/Editor/SourceInterfacex64.dll create mode 100644 Assets/Allegorithmic/Plugins/Substance/Substance Source/Editor/SourceInterfacex64.dll.meta create mode 100644 Assets/Allegorithmic/Plugins/Substance/Substance Source/Editor/SourceUnityLib.dll create mode 100644 Assets/Allegorithmic/Plugins/Substance/Substance Source/Editor/SourceUnityLib.dll.meta create mode 100644 Assets/Allegorithmic/Plugins/Substance/Substance Source/Editor/libSourceInterfacex64.dylib create mode 100644 Assets/Allegorithmic/Plugins/Substance/Substance Source/Editor/libSourceInterfacex64.dylib.meta create mode 100644 Assets/Allegorithmic/Plugins/Substance/iOS.meta create mode 100644 Assets/Allegorithmic/Plugins/Substance/iOS/libSubstance.Engine.a create mode 100644 Assets/Allegorithmic/Plugins/Substance/iOS/libSubstance.Engine.a.meta create mode 100644 Assets/Allegorithmic/Plugins/Substance/iOS/libalgcompression.a create mode 100644 Assets/Allegorithmic/Plugins/Substance/iOS/libalgcompression.a.meta create mode 100644 Assets/Allegorithmic/Plugins/Substance/iOS/libpfxlinkercommon.a create mode 100644 Assets/Allegorithmic/Plugins/Substance/iOS/libpfxlinkercommon.a.meta create mode 100644 Assets/Allegorithmic/Plugins/Substance/iOS/libsubstance_framework.a create mode 100644 Assets/Allegorithmic/Plugins/Substance/iOS/libsubstance_framework.a.meta create mode 100644 Assets/Allegorithmic/Plugins/Substance/iOS/libsubstance_linker.a create mode 100644 Assets/Allegorithmic/Plugins/Substance/iOS/libsubstance_linker.a.meta create mode 100644 Assets/Allegorithmic/Plugins/Substance/iOS/libsubstance_neon_blend.a create mode 100644 Assets/Allegorithmic/Plugins/Substance/iOS/libsubstance_neon_blend.a.meta create mode 100644 Assets/Allegorithmic/Plugins/Substance/iOS/libtinyxml.a create mode 100644 Assets/Allegorithmic/Plugins/Substance/iOS/libtinyxml.a.meta create mode 100644 Assets/Allegorithmic/Plugins/engine_build_info.txt rename Assets/{Shaders/CustomDoF/AetheriaFogBlurUrp.hlsl.meta => Allegorithmic/Plugins/engine_build_info.txt.meta} (61%) create mode 100644 Assets/Allegorithmic/Plugins/libSubstance.Engine.so create mode 100644 Assets/Allegorithmic/Plugins/libSubstance.Engine.so.meta create mode 100644 Assets/Allegorithmic/Plugins/libsubstance_sse2_blend.bundle create mode 100644 Assets/Allegorithmic/Plugins/libsubstance_sse2_blend.bundle.meta create mode 100644 Assets/Allegorithmic/Plugins/libsubstance_sse2_blend.so create mode 100644 Assets/Allegorithmic/Plugins/libsubstance_sse2_blend.so.meta create mode 100644 Assets/Allegorithmic/Plugins/substance_sse2_blend.dll create mode 100644 Assets/Allegorithmic/Plugins/substance_sse2_blend.dll.meta delete mode 100644 Assets/DefaultVolumeProfile.asset delete mode 100644 Assets/DefaultVolumeProfile.asset.meta delete mode 100644 Assets/Editor/AetheriaCultUiCompositionSurfaceWindow.cs delete mode 100644 Assets/Editor/AetheriaEditorPlayModeTrigger.cs delete mode 100644 Assets/Editor/AetheriaGravityFogVerifier.cs delete mode 100644 Assets/Editor/AetheriaGravityFogVerifier.cs.meta delete mode 100644 Assets/Editor/AetheriaRenderProbe.cs delete mode 100644 Assets/Editor/AetheriaStardustContinuityVerifier.cs delete mode 100644 Assets/Editor/AetheriaStardustContinuityVerifier.cs.meta delete mode 100644 Assets/Editor/AetheriaUrpBootstrap.cs delete mode 100644 Assets/Editor/AetheriaUrpBootstrap.cs.meta delete mode 100644 Assets/Editor/EveAssetBundleBuilder.cs delete mode 100644 Assets/Editor/EveAssetBundleBuilder.cs.meta delete mode 100644 Assets/Editor/EveEnvironmentProfileMigrator.cs delete mode 100644 Assets/Editor/EveEnvironmentProfileMigrator.cs.meta delete mode 100644 Assets/Editor/EveThermalProfileMigrator.cs delete mode 100644 Assets/Editor/EveThermalProfileMigrator.cs.meta create mode 100644 Assets/Materials/Substance.meta create mode 100644 Assets/Materials/Substance/Metal.meta create mode 100644 Assets/Materials/Substance/Metal/Pickup Base.sbsar create mode 100644 Assets/Materials/Substance/Metal/Pickup Base.sbsar.meta create mode 100644 Assets/Materials/Substance/Metal/Pickup Trim.sbsar create mode 100644 Assets/Materials/Substance/Metal/Pickup Trim.sbsar.meta create mode 100644 Assets/Materials/Substance/Metal/galvanized_001.sbsar create mode 100644 Assets/Materials/Substance/Metal/galvanized_001.sbsar.meta create mode 100644 Assets/Materials/Substance/Metal/iron_damaged.sbsar create mode 100644 Assets/Materials/Substance/Metal/iron_damaged.sbsar.meta create mode 100644 Assets/Materials/Substance/Metal/iron_rugged.sbsar create mode 100644 Assets/Materials/Substance/Metal/iron_rugged.sbsar.meta create mode 100644 Assets/Materials/Substance/Metal/metal_002.sbsar create mode 100644 Assets/Materials/Substance/Metal/metal_002.sbsar.meta create mode 100644 Assets/Materials/Substance/Metal/metal_003.sbsar create mode 100644 Assets/Materials/Substance/Metal/metal_003.sbsar.meta create mode 100644 Assets/Materials/Substance/Metal/metal_006.sbsar create mode 100644 Assets/Materials/Substance/Metal/metal_006.sbsar.meta create mode 100644 Assets/Materials/Substance/Metal/metal_aluminium.sbsar create mode 100644 Assets/Materials/Substance/Metal/metal_aluminium.sbsar.meta create mode 100644 Assets/Materials/Substance/Metal/metal_aluminium_brushed.sbsar create mode 100644 Assets/Materials/Substance/Metal/metal_aluminium_brushed.sbsar.meta create mode 100644 Assets/Materials/Substance/Metal/metal_brass.sbsar create mode 100644 Assets/Materials/Substance/Metal/metal_brass.sbsar.meta create mode 100644 Assets/Materials/Substance/Metal/metal_car_paint 1.sbsar create mode 100644 Assets/Materials/Substance/Metal/metal_car_paint 1.sbsar.meta create mode 100644 Assets/Materials/Substance/Metal/metal_car_paint Magenta.sbsar create mode 100644 Assets/Materials/Substance/Metal/metal_car_paint Magenta.sbsar.meta create mode 100644 Assets/Materials/Substance/Metal/metal_car_paint red.sbsar create mode 100644 Assets/Materials/Substance/Metal/metal_car_paint red.sbsar.meta create mode 100644 Assets/Materials/Substance/Metal/metal_car_paint white.sbsar create mode 100644 Assets/Materials/Substance/Metal/metal_car_paint white.sbsar.meta create mode 100644 Assets/Materials/Substance/Metal/metal_copper.sbsar create mode 100644 Assets/Materials/Substance/Metal/metal_copper.sbsar.meta create mode 100644 Assets/Materials/Substance/Metal/metal_corrugated_002.sbsar create mode 100644 Assets/Materials/Substance/Metal/metal_corrugated_002.sbsar.meta create mode 100644 Assets/Materials/Substance/Metal/metal_floor_002.sbsar create mode 100644 Assets/Materials/Substance/Metal/metal_floor_002.sbsar.meta create mode 100644 Assets/Materials/Substance/Metal/metal_floor_004.sbsar create mode 100644 Assets/Materials/Substance/Metal/metal_floor_004.sbsar.meta create mode 100644 Assets/Materials/Substance/Metal/metal_floor_006.sbsar create mode 100644 Assets/Materials/Substance/Metal/metal_floor_006.sbsar.meta create mode 100644 Assets/Materials/Substance/Metal/metal_gold.sbsar create mode 100644 Assets/Materials/Substance/Metal/metal_gold.sbsar.meta create mode 100644 Assets/Materials/Substance/Metal/metal_nickel.sbsar create mode 100644 Assets/Materials/Substance/Metal/metal_nickel.sbsar.meta create mode 100644 Assets/Materials/Substance/Metal/metal_painted_steel.sbsar create mode 100644 Assets/Materials/Substance/Metal/metal_painted_steel.sbsar.meta create mode 100644 Assets/Materials/Substance/Metal/metal_panels_001.sbsar create mode 100644 Assets/Materials/Substance/Metal/metal_panels_001.sbsar.meta create mode 100644 Assets/Materials/Substance/Metal/metal_panels_002.sbsar create mode 100644 Assets/Materials/Substance/Metal/metal_panels_002.sbsar.meta create mode 100644 Assets/Materials/Substance/Metal/metal_plate_001.sbsar create mode 100644 Assets/Materials/Substance/Metal/metal_plate_001.sbsar.meta create mode 100644 Assets/Materials/Substance/Metal/metal_plate_004.sbsar create mode 100644 Assets/Materials/Substance/Metal/metal_plate_004.sbsar.meta create mode 100644 Assets/Materials/Substance/Metal/metal_plate_011.sbsar create mode 100644 Assets/Materials/Substance/Metal/metal_plate_011.sbsar.meta create mode 100644 Assets/Materials/Substance/Metal/metal_platinum.sbsar create mode 100644 Assets/Materials/Substance/Metal/metal_platinum.sbsar.meta create mode 100644 Assets/Materials/Substance/Metal/metal_roll_up.sbsar create mode 100644 Assets/Materials/Substance/Metal/metal_roll_up.sbsar.meta create mode 100644 Assets/Materials/Substance/Metal/metal_silver.sbsar create mode 100644 Assets/Materials/Substance/Metal/metal_silver.sbsar.meta create mode 100644 Assets/Materials/Substance/Metal/metal_steel.sbsar create mode 100644 Assets/Materials/Substance/Metal/metal_steel.sbsar.meta create mode 100644 Assets/Materials/Substance/Metal/metal_steel_brushed.sbsar create mode 100644 Assets/Materials/Substance/Metal/metal_steel_brushed.sbsar.meta create mode 100644 Assets/Materials/Substance/Metal/metal_steel_bumped.sbsar create mode 100644 Assets/Materials/Substance/Metal/metal_steel_bumped.sbsar.meta create mode 100644 Assets/Materials/Substance/Metal/metal_steel_parkerized.sbsar create mode 100644 Assets/Materials/Substance/Metal/metal_steel_parkerized.sbsar.meta create mode 100644 Assets/Materials/Substance/Metal/metal_titanium.sbsar create mode 100644 Assets/Materials/Substance/Metal/metal_titanium.sbsar.meta create mode 100644 Assets/Materials/Substance/Metal/metal_used.sbsar create mode 100644 Assets/Materials/Substance/Metal/metal_used.sbsar.meta create mode 100644 Assets/Materials/Substance/Metal/metal_weapon.sbsar create mode 100644 Assets/Materials/Substance/Metal/metal_weapon.sbsar.meta create mode 100644 Assets/Materials/Substance/Metal/metal_woven.sbsar create mode 100644 Assets/Materials/Substance/Metal/metal_woven.sbsar.meta create mode 100644 Assets/Materials/Substance/Metal/snatched_metal.sbsar create mode 100644 Assets/Materials/Substance/Metal/snatched_metal.sbsar.meta create mode 100644 Assets/Materials/Substance/marble_002.sbsar create mode 100644 Assets/Materials/Substance/marble_002.sbsar.meta create mode 100644 Assets/Plugins/Celestial Body/Scripts/MeshBaker.cs rename Assets/{Editor/AetheriaCultUiCompositionSurfaceWindow.cs.meta => Plugins/Celestial Body/Scripts/MeshBaker.cs.meta} (83%) rename Assets/Plugins/MessagePack/{MessagePack.SourceDisabled.asmdef => MessagePack.asmdef} (65%) rename Assets/Plugins/MessagePack/{MessagePack.SourceDisabled.asmdef.meta => MessagePack.asmdef.meta} (100%) delete mode 100644 Assets/Prefabs/RPG/Effects/Mine.prefab delete mode 100644 Assets/Prefabs/RPG/Effects/Mine.prefab.meta create mode 100644 Assets/Prefabs/UI/Context Menu Option.prefab create mode 100644 Assets/Prefabs/UI/Context Menu Option.prefab.meta create mode 100644 Assets/Prefabs/UI/Context Menu.prefab create mode 100644 Assets/Prefabs/UI/Context Menu.prefab.meta create mode 100644 Assets/Prefabs/UI/Dropdown Menu.prefab create mode 100644 Assets/Prefabs/UI/Dropdown Menu.prefab.meta create mode 100644 Assets/Prefabs/UI/Properties Panel/Fancy/Weapon Group.prefab create mode 100644 Assets/Prefabs/UI/Properties Panel/Fancy/Weapon Group.prefab.meta create mode 100644 Assets/Prefabs/UI/Properties Panel/Fancy/Weapon Groups.prefab create mode 100644 Assets/Prefabs/UI/Properties Panel/Fancy/Weapon Groups.prefab.meta create mode 100644 Assets/Prefabs/UI/Properties Panel/Properties.prefab create mode 100644 Assets/Prefabs/UI/Properties Panel/Properties.prefab.meta create mode 100644 Assets/Prefabs/UI/Properties Panel/Property List 1.prefab create mode 100644 Assets/Prefabs/UI/Properties Panel/Property List 1.prefab.meta create mode 100644 Assets/Prefabs/UI/Properties Panel/Property List.prefab create mode 100644 Assets/Prefabs/UI/Properties Panel/Property List.prefab.meta create mode 100644 Assets/Resources/Prefabs/Weapons/Mine Launcher.prefab create mode 100644 Assets/Resources/Prefabs/Weapons/Mine Launcher.prefab.meta delete mode 100644 Assets/Resources/UnityDefaultRuntimeTheme.tss delete mode 100644 Assets/Resources/UnityDefaultRuntimeTheme.tss.meta create mode 100644 Assets/Resources/ansi104.json create mode 100644 Assets/Resources/ansi104.json.meta create mode 100644 Assets/Samples/Localization.meta create mode 100644 Assets/Samples/Localization/0.11.1-preview.meta create mode 100644 Assets/Samples/Localization/0.11.1-preview/Google Sheets.meta create mode 100644 Assets/Samples/Localization/0.11.1-preview/Google Sheets/.sample.json create mode 100644 Assets/Samples/Localization/0.11.1-preview/Google Sheets/Editor.meta create mode 100644 Assets/Samples/Localization/0.11.1-preview/Google Sheets/Editor/CustomColumnDataExample.cs rename Assets/{Editor/AetheriaEditorPlayModeTrigger.cs.meta => Samples/Localization/0.11.1-preview/Google Sheets/Editor/CustomColumnDataExample.cs.meta} (83%) create mode 100644 Assets/Samples/Localization/0.11.1-preview/Google Sheets/Editor/GoogleSheetsExamples.cs create mode 100644 Assets/Samples/Localization/0.11.1-preview/Google Sheets/Editor/GoogleSheetsExamples.cs.meta create mode 100644 Assets/Samples/Localization/0.11.1-preview/Google Sheets/Editor/SmartStringColumn.cs create mode 100644 Assets/Samples/Localization/0.11.1-preview/Google Sheets/Editor/SmartStringColumn.cs.meta rename Assets/Scripts/{UnitySupport.meta => CultCache.meta} (100%) create mode 100644 Assets/Scripts/CultCache/Editor.meta create mode 100644 Assets/Scripts/CultCache/Editor/AetheriaDatabaseView.cs create mode 100644 Assets/Scripts/CultCache/Editor/AetheriaDatabaseView.cs.meta create mode 100644 Assets/Scripts/CultCache/Editor/BaseInspector.cs create mode 100644 Assets/Scripts/CultCache/Editor/BaseInspector.cs.meta create mode 100644 Assets/Scripts/CultCache/Editor/DatabaseInspector.cs create mode 100644 Assets/Scripts/CultCache/Editor/DatabaseInspector.cs.meta create mode 100644 Assets/Scripts/CultCache/Editor/DatabaseView.cs create mode 100644 Assets/Scripts/CultCache/Editor/DatabaseView.cs.meta create mode 100644 Assets/Scripts/CultCache/Editor/EditorDispatch.cs create mode 100644 Assets/Scripts/CultCache/Editor/EditorDispatch.cs.meta create mode 100644 Assets/Scripts/CultCache/Editor/Inspectors.meta create mode 100644 Assets/Scripts/CultCache/Editor/Inspectors/AetheriaInspectors.cs create mode 100644 Assets/Scripts/CultCache/Editor/Inspectors/AetheriaInspectors.cs.meta create mode 100644 Assets/Scripts/CultCache/Editor/Inspectors/AnimationCurveInspector.cs create mode 100644 Assets/Scripts/CultCache/Editor/Inspectors/AnimationCurveInspector.cs.meta create mode 100644 Assets/Scripts/CultCache/Editor/Inspectors/DatabaseLinkInspector.cs create mode 100644 Assets/Scripts/CultCache/Editor/Inspectors/DatabaseLinkInspector.cs.meta create mode 100644 Assets/Scripts/CultCache/Editor/Inspectors/DictionaryInspector.cs create mode 100644 Assets/Scripts/CultCache/Editor/Inspectors/DictionaryInspector.cs.meta create mode 100644 Assets/Scripts/CultCache/Editor/Inspectors/EnumValuesInspector.cs create mode 100644 Assets/Scripts/CultCache/Editor/Inspectors/EnumValuesInspector.cs.meta create mode 100644 Assets/Scripts/CultCache/Editor/Inspectors/FloatInspector.cs create mode 100644 Assets/Scripts/CultCache/Editor/Inspectors/FloatInspector.cs.meta create mode 100644 Assets/Scripts/CultCache/Editor/Inspectors/IntInspector.cs create mode 100644 Assets/Scripts/CultCache/Editor/Inspectors/IntInspector.cs.meta create mode 100644 Assets/Scripts/CultCache/Editor/Inspectors/ListInspector.cs create mode 100644 Assets/Scripts/CultCache/Editor/Inspectors/ListInspector.cs.meta create mode 100644 Assets/Scripts/CultCache/Editor/Inspectors/MathematicsInspector.cs create mode 100644 Assets/Scripts/CultCache/Editor/Inspectors/MathematicsInspector.cs.meta create mode 100644 Assets/Scripts/CultCache/Editor/Inspectors/PrimitiveInspector.cs create mode 100644 Assets/Scripts/CultCache/Editor/Inspectors/PrimitiveInspector.cs.meta create mode 100644 Assets/Scripts/CultCache/Editor/Inspectors/TypeInspector.cs create mode 100644 Assets/Scripts/CultCache/Editor/Inspectors/TypeInspector.cs.meta create mode 100644 Assets/Scripts/CultCache/Editor/Inspectors/UnityObjectInspector.cs create mode 100644 Assets/Scripts/CultCache/Editor/Inspectors/UnityObjectInspector.cs.meta rename Assets/Scripts/{UnitySupport => CultCache}/UnityExtensions.cs (74%) rename Assets/Scripts/{UnitySupport => CultCache}/UnityExtensions.cs.meta (100%) rename Assets/Scripts/{UnitySupport => CultCache}/XKCDColors.cs (100%) rename Assets/Scripts/{UnitySupport => CultCache}/XKCDColors.cs.meta (100%) create mode 100644 Assets/Scripts/CultClient.cs create mode 100644 Assets/Scripts/CultClient.cs.meta create mode 100644 Assets/Scripts/Editor/SerializerTestWindow.cs create mode 100644 Assets/Scripts/Editor/SerializerTestWindow.cs.meta delete mode 100644 Assets/Scripts/Gameplay/AetheriaDaemonIndirectRenderer.cs delete mode 100644 Assets/Scripts/Gameplay/AetheriaDaemonIndirectRenderer.cs.meta delete mode 100644 Assets/Scripts/Gameplay/AetheriaDaemonObserver.cs delete mode 100644 Assets/Scripts/Gameplay/AetheriaDaemonObserver.cs.meta delete mode 100644 Assets/Scripts/Gameplay/AetheriaDaemonRenderAssetCatalog.cs delete mode 100644 Assets/Scripts/Gameplay/AetheriaDaemonRenderAssetCatalog.cs.meta delete mode 100644 Assets/Scripts/Gameplay/AetheriaDaemonRenderBuffer.cs delete mode 100644 Assets/Scripts/Gameplay/AetheriaDaemonRenderBuffer.cs.meta delete mode 100644 Assets/Scripts/Gameplay/AetheriaDaemonRenderGroupBatch.cs delete mode 100644 Assets/Scripts/Gameplay/AetheriaDaemonRenderGroupBatch.cs.meta delete mode 100644 Assets/Scripts/Gameplay/AetheriaDaemonRenderGroupMatrixJob.cs delete mode 100644 Assets/Scripts/Gameplay/AetheriaDaemonRenderGroupMatrixJob.cs.meta delete mode 100644 Assets/Scripts/Gameplay/AetheriaDaemonRenderMatrixJob.cs delete mode 100644 Assets/Scripts/Gameplay/AetheriaDaemonRenderMatrixJob.cs.meta delete mode 100644 Assets/Scripts/Gameplay/AetheriaDaemonRenderNativeView.cs delete mode 100644 Assets/Scripts/Gameplay/AetheriaDaemonRenderNativeView.cs.meta delete mode 100644 Assets/Scripts/Gameplay/AetheriaDaemonSoaMemoryMap.cs delete mode 100644 Assets/Scripts/Gameplay/AetheriaDaemonSoaMemoryMap.cs.meta delete mode 100644 Assets/Scripts/Gameplay/AetheriaRenderSplatLayerRenderer.cs delete mode 100644 Assets/Scripts/Gameplay/AetheriaRenderSplatRasterizer.cs delete mode 100644 Assets/Scripts/Gameplay/AetheriaRenderSplatRasterizer.cs.meta delete mode 100644 Assets/Scripts/Gameplay/AetheriaUnityActionBarBinding.cs delete mode 100644 Assets/Scripts/Gameplay/AetheriaUnityActionBarBinding.cs.meta delete mode 100644 Assets/Scripts/Gameplay/AetheriaUnityActionBarPresentation.cs delete mode 100644 Assets/Scripts/Gameplay/AetheriaUnityActionBarPresentation.cs.meta delete mode 100644 Assets/Scripts/Gameplay/AetheriaUnityCockpitHudShell.cs delete mode 100644 Assets/Scripts/Gameplay/AetheriaUnityCockpitHudShell.cs.meta delete mode 100644 Assets/Scripts/Gameplay/AetheriaUnityCurrentEntityBinder.cs delete mode 100644 Assets/Scripts/Gameplay/AetheriaUnityCurrentEntityBinder.cs.meta delete mode 100644 Assets/Scripts/Gameplay/AetheriaUnityCurrentEntityPresentation.cs delete mode 100644 Assets/Scripts/Gameplay/AetheriaUnityCurrentEntityPresentation.cs.meta delete mode 100644 Assets/Scripts/Gameplay/AetheriaUnityDaemonRenderDocuments.cs delete mode 100644 Assets/Scripts/Gameplay/AetheriaUnityDaemonRenderDocuments.cs.meta delete mode 100644 Assets/Scripts/Gameplay/AetheriaUnityDragObjects.cs delete mode 100644 Assets/Scripts/Gameplay/AetheriaUnityDragObjects.cs.meta delete mode 100644 Assets/Scripts/Gameplay/AetheriaUnityDragSession.cs delete mode 100644 Assets/Scripts/Gameplay/AetheriaUnityDragSession.cs.meta delete mode 100644 Assets/Scripts/Gameplay/AetheriaUnityEntityBlueprintMaterializer.cs delete mode 100644 Assets/Scripts/Gameplay/AetheriaUnityEntityBlueprintMaterializer.cs.meta delete mode 100644 Assets/Scripts/Gameplay/AetheriaUnityGameViewportDocuments.cs delete mode 100644 Assets/Scripts/Gameplay/AetheriaUnityGameViewportDocuments.cs.meta delete mode 100644 Assets/Scripts/Gameplay/AetheriaUnityGameplayBootShell.cs delete mode 100644 Assets/Scripts/Gameplay/AetheriaUnityGameplayBootShell.cs.meta delete mode 100644 Assets/Scripts/Gameplay/AetheriaUnityGameplayInputShell.cs delete mode 100644 Assets/Scripts/Gameplay/AetheriaUnityGameplayInputShell.cs.meta delete mode 100644 Assets/Scripts/Gameplay/AetheriaUnityGameplayLoopShell.cs delete mode 100644 Assets/Scripts/Gameplay/AetheriaUnityGameplayLoopShell.cs.meta delete mode 100644 Assets/Scripts/Gameplay/AetheriaUnityGameplaySceneWiring.cs delete mode 100644 Assets/Scripts/Gameplay/AetheriaUnityGameplaySceneWiring.cs.meta delete mode 100644 Assets/Scripts/Gameplay/AetheriaUnityLoadoutItemFactory.cs delete mode 100644 Assets/Scripts/Gameplay/AetheriaUnityLoadoutItemFactory.cs.meta delete mode 100644 Assets/Scripts/Gameplay/AetheriaUnityMenuShell.cs delete mode 100644 Assets/Scripts/Gameplay/AetheriaUnityMenuShell.cs.meta delete mode 100644 Assets/Scripts/Gameplay/AetheriaUnityObservedEntityRestorer.cs delete mode 100644 Assets/Scripts/Gameplay/AetheriaUnityObservedEntityRestorer.cs.meta delete mode 100644 Assets/Scripts/Gameplay/AetheriaUnityObservedFrameApplier.cs delete mode 100644 Assets/Scripts/Gameplay/AetheriaUnityObservedFrameApplier.cs.meta delete mode 100644 Assets/Scripts/Gameplay/AetheriaUnityObservedZoneContextFactory.cs delete mode 100644 Assets/Scripts/Gameplay/AetheriaUnityObservedZoneContextFactory.cs.meta delete mode 100644 Assets/Scripts/Gameplay/AetheriaUnityPilotCommandSender.cs delete mode 100644 Assets/Scripts/Gameplay/AetheriaUnityPilotCommandSender.cs.meta delete mode 100644 Assets/Scripts/Gameplay/AetheriaUnityPilotFrameController.cs delete mode 100644 Assets/Scripts/Gameplay/AetheriaUnityPilotFrameController.cs.meta delete mode 100644 Assets/Scripts/Gameplay/AetheriaUnityPilotOperationController.cs delete mode 100644 Assets/Scripts/Gameplay/AetheriaUnityPilotOperationController.cs.meta delete mode 100644 Assets/Scripts/Gameplay/AetheriaUnityPresentationEntityIndex.cs delete mode 100644 Assets/Scripts/Gameplay/AetheriaUnityPresentationEntityIndex.cs.meta delete mode 100644 Assets/Scripts/Gameplay/AetheriaUnityRenderSplatViewportSource.cs delete mode 100644 Assets/Scripts/Gameplay/AetheriaUnityRenderSplatViewportSource.cs.meta delete mode 100644 Assets/Scripts/Gameplay/AetheriaUnityRuntimeClientProvider.cs delete mode 100644 Assets/Scripts/Gameplay/AetheriaUnityRuntimeClientProvider.cs.meta delete mode 100644 Assets/Scripts/Gameplay/AetheriaUnityRuntimePaths.cs delete mode 100644 Assets/Scripts/Gameplay/AetheriaUnityRuntimePaths.cs.meta delete mode 100644 Assets/Scripts/Gameplay/AetheriaUnityTargetPresentation.cs delete mode 100644 Assets/Scripts/Gameplay/AetheriaUnityTargetPresentation.cs.meta delete mode 100644 Assets/Scripts/Gameplay/AetheriaUnityWeaponPresentationPolicy.cs delete mode 100644 Assets/Scripts/Gameplay/AetheriaUnityWeaponPresentationPolicy.cs.meta delete mode 100644 Assets/Scripts/Gameplay/Physics.meta delete mode 100644 Assets/Scripts/Gameplay/Physics/AetheriaYmirPhysicsBridge.cs delete mode 100644 Assets/Scripts/Gameplay/Physics/AetheriaYmirPhysicsBridge.cs.meta create mode 100644 Assets/Scripts/Gameplay/TimedDestroy.cs create mode 100644 Assets/Scripts/Gameplay/TimedDestroy.cs.meta create mode 100644 Assets/Scripts/Gameplay/Weapons/ConstantLaser.cs create mode 100644 Assets/Scripts/Gameplay/Weapons/ConstantLaser.cs.meta create mode 100644 Assets/Scripts/Gameplay/Weapons/ConstantLaserManager.cs create mode 100644 Assets/Scripts/Gameplay/Weapons/ConstantLaserManager.cs.meta create mode 100644 Assets/Scripts/Gameplay/Weapons/ConstantLightning.cs create mode 100644 Assets/Scripts/Gameplay/Weapons/ConstantLightning.cs.meta create mode 100644 Assets/Scripts/Gameplay/Weapons/ConstantLightningManager.cs create mode 100644 Assets/Scripts/Gameplay/Weapons/ConstantLightningManager.cs.meta create mode 100644 Assets/Scripts/Gameplay/Weapons/ConstantParticleWeapon.cs create mode 100644 Assets/Scripts/Gameplay/Weapons/ConstantParticleWeapon.cs.meta create mode 100644 Assets/Scripts/Gameplay/Weapons/ConstantParticleWeaponManager.cs create mode 100644 Assets/Scripts/Gameplay/Weapons/ConstantParticleWeaponManager.cs.meta create mode 100644 Assets/Scripts/Gameplay/Weapons/ConstantWeaponEffectManager.cs create mode 100644 Assets/Scripts/Gameplay/Weapons/ConstantWeaponEffectManager.cs.meta create mode 100644 Assets/Scripts/Gameplay/Weapons/GuidedProjectile.cs create mode 100644 Assets/Scripts/Gameplay/Weapons/GuidedProjectile.cs.meta create mode 100644 Assets/Scripts/Gameplay/Weapons/GuidedProjectileManager.cs create mode 100644 Assets/Scripts/Gameplay/Weapons/GuidedProjectileManager.cs.meta create mode 100644 Assets/Scripts/Gameplay/Weapons/HitscanEffect.cs create mode 100644 Assets/Scripts/Gameplay/Weapons/HitscanEffect.cs.meta create mode 100644 Assets/Scripts/Gameplay/Weapons/HitscanManager.cs create mode 100644 Assets/Scripts/Gameplay/Weapons/HitscanManager.cs.meta create mode 100644 Assets/Scripts/Gameplay/Weapons/InstantWeaponEffectManager.cs create mode 100644 Assets/Scripts/Gameplay/Weapons/InstantWeaponEffectManager.cs.meta create mode 100644 Assets/Scripts/Gameplay/Weapons/Laser.cs create mode 100644 Assets/Scripts/Gameplay/Weapons/Laser.cs.meta create mode 100644 Assets/Scripts/Gameplay/Weapons/LaserManager.cs create mode 100644 Assets/Scripts/Gameplay/Weapons/LaserManager.cs.meta create mode 100644 Assets/Scripts/Gameplay/Weapons/Lightning.cs create mode 100644 Assets/Scripts/Gameplay/Weapons/Lightning.cs.meta create mode 100644 Assets/Scripts/Gameplay/Weapons/LightningGunManager.cs create mode 100644 Assets/Scripts/Gameplay/Weapons/LightningGunManager.cs.meta create mode 100644 Assets/Scripts/Gameplay/Weapons/Mine.cs create mode 100644 Assets/Scripts/Gameplay/Weapons/Mine.cs.meta create mode 100644 Assets/Scripts/Gameplay/Weapons/MineManager.cs create mode 100644 Assets/Scripts/Gameplay/Weapons/MineManager.cs.meta create mode 100644 Assets/Scripts/Gameplay/Weapons/Projectile.cs create mode 100644 Assets/Scripts/Gameplay/Weapons/Projectile.cs.meta create mode 100644 Assets/Scripts/Gameplay/Weapons/ProjectileManager.cs create mode 100644 Assets/Scripts/Gameplay/Weapons/ProjectileManager.cs.meta create mode 100644 Assets/Scripts/InitialSettings.cs create mode 100644 Assets/Scripts/InitialSettings.cs.meta delete mode 100644 Assets/Scripts/Rendering/AetheriaStardustRendererFeature.cs delete mode 100644 Assets/Scripts/Rendering/AetheriaStardustRendererFeature.cs.meta delete mode 100644 Assets/Scripts/Rendering/AetheriaVolumeCloudRendererFeature.cs delete mode 100644 Assets/Scripts/Rendering/AetheriaVolumeCloudRendererFeature.cs.meta delete mode 100644 Assets/Scripts/Rendering/AetheriaVolumeSamplingRendererFeature.cs delete mode 100644 Assets/Scripts/Rendering/AetheriaVolumeSamplingRendererFeature.cs.meta create mode 100644 Assets/Scripts/ServerShared/ActionCollection.cs create mode 100644 Assets/Scripts/ServerShared/ActionCollection.cs.meta delete mode 100644 Assets/Scripts/ServerShared/AetheriaRuntimeItemReference.cs create mode 100644 Assets/Scripts/ServerShared/Attributes.cs create mode 100644 Assets/Scripts/ServerShared/Attributes.cs.meta create mode 100644 Assets/Scripts/ServerShared/Behaviors/Launcher.cs create mode 100644 Assets/Scripts/ServerShared/Behaviors/Launcher.cs.meta create mode 100644 Assets/Scripts/ServerShared/CultCache.meta create mode 100644 Assets/Scripts/ServerShared/CultCache/Attributes.cs create mode 100644 Assets/Scripts/ServerShared/CultCache/Attributes.cs.meta create mode 100644 Assets/Scripts/ServerShared/CultCache/CollectionExtensions.cs create mode 100644 Assets/Scripts/ServerShared/CultCache/CollectionExtensions.cs.meta create mode 100644 Assets/Scripts/ServerShared/CultCache/CultCache.cs create mode 100644 Assets/Scripts/ServerShared/CultCache/CultCache.cs.meta create mode 100644 Assets/Scripts/ServerShared/CultCache/DatabaseEntry.cs rename Assets/Scripts/ServerShared/{AetheriaRuntimeItemReference.cs.meta => CultCache/DatabaseEntry.cs.meta} (100%) create mode 100644 Assets/Scripts/ServerShared/CultCache/ReflectionExtensions.cs create mode 100644 Assets/Scripts/ServerShared/CultCache/ReflectionExtensions.cs.meta create mode 100644 Assets/Scripts/ServerShared/CultCache/RethinkConnection.cs create mode 100644 Assets/Scripts/ServerShared/CultCache/RethinkConnection.cs.meta create mode 100644 Assets/Scripts/ServerShared/CultCache/Serialization.meta create mode 100644 Assets/Scripts/ServerShared/CultCache/Serialization/JsonConverters.cs create mode 100644 Assets/Scripts/ServerShared/CultCache/Serialization/JsonConverters.cs.meta create mode 100644 Assets/Scripts/ServerShared/CultCache/Serialization/JsonKnownTypes.meta create mode 100644 Assets/Scripts/ServerShared/CultCache/Serialization/JsonKnownTypes/AttributesManager.cs create mode 100644 Assets/Scripts/ServerShared/CultCache/Serialization/JsonKnownTypes/AttributesManager.cs.meta create mode 100644 Assets/Scripts/ServerShared/CultCache/Serialization/JsonKnownTypes/Exceptions.meta create mode 100644 Assets/Scripts/ServerShared/CultCache/Serialization/JsonKnownTypes/Exceptions/JsonKnownTypesException.cs create mode 100644 Assets/Scripts/ServerShared/CultCache/Serialization/JsonKnownTypes/Exceptions/JsonKnownTypesException.cs.meta create mode 100644 Assets/Scripts/ServerShared/CultCache/Serialization/JsonKnownTypes/JsonDiscriminatorAttribute.cs create mode 100644 Assets/Scripts/ServerShared/CultCache/Serialization/JsonKnownTypes/JsonDiscriminatorAttribute.cs.meta create mode 100644 Assets/Scripts/ServerShared/CultCache/Serialization/JsonKnownTypes/JsonDiscriminatorSettings.cs create mode 100644 Assets/Scripts/ServerShared/CultCache/Serialization/JsonKnownTypes/JsonDiscriminatorSettings.cs.meta create mode 100644 Assets/Scripts/ServerShared/CultCache/Serialization/JsonKnownTypes/JsonKnownThisTypeAttribute.cs create mode 100644 Assets/Scripts/ServerShared/CultCache/Serialization/JsonKnownTypes/JsonKnownThisTypeAttribute.cs.meta create mode 100644 Assets/Scripts/ServerShared/CultCache/Serialization/JsonKnownTypes/JsonKnownTypeAttribute.cs create mode 100644 Assets/Scripts/ServerShared/CultCache/Serialization/JsonKnownTypes/JsonKnownTypeAttribute.cs.meta create mode 100644 Assets/Scripts/ServerShared/CultCache/Serialization/JsonKnownTypes/JsonKnownTypes.csproj create mode 100644 Assets/Scripts/ServerShared/CultCache/Serialization/JsonKnownTypes/JsonKnownTypes.csproj.meta create mode 100644 Assets/Scripts/ServerShared/CultCache/Serialization/JsonKnownTypes/JsonKnownTypesContractResolver.cs create mode 100644 Assets/Scripts/ServerShared/CultCache/Serialization/JsonKnownTypes/JsonKnownTypesContractResolver.cs.meta create mode 100644 Assets/Scripts/ServerShared/CultCache/Serialization/JsonKnownTypes/JsonKnownTypesConverter.cs create mode 100644 Assets/Scripts/ServerShared/CultCache/Serialization/JsonKnownTypes/JsonKnownTypesConverter.cs.meta create mode 100644 Assets/Scripts/ServerShared/CultCache/Serialization/JsonKnownTypes/JsonKnownTypesDictionaryManager.cs create mode 100644 Assets/Scripts/ServerShared/CultCache/Serialization/JsonKnownTypes/JsonKnownTypesDictionaryManager.cs.meta create mode 100644 Assets/Scripts/ServerShared/CultCache/Serialization/JsonKnownTypes/JsonKnownTypesSettings.cs create mode 100644 Assets/Scripts/ServerShared/CultCache/Serialization/JsonKnownTypes/JsonKnownTypesSettings.cs.meta create mode 100644 Assets/Scripts/ServerShared/CultCache/Serialization/JsonKnownTypes/JsonKnownTypesSettingsManager.cs create mode 100644 Assets/Scripts/ServerShared/CultCache/Serialization/JsonKnownTypes/JsonKnownTypesSettingsManager.cs.meta create mode 100644 Assets/Scripts/ServerShared/CultCache/Serialization/JsonKnownTypes/Utils.meta create mode 100644 Assets/Scripts/ServerShared/CultCache/Serialization/JsonKnownTypes/Utils/Mapper.cs create mode 100644 Assets/Scripts/ServerShared/CultCache/Serialization/JsonKnownTypes/Utils/Mapper.cs.meta create mode 100644 Assets/Scripts/ServerShared/CultCache/Serialization/MathFormatters.cs create mode 100644 Assets/Scripts/ServerShared/CultCache/Serialization/MathFormatters.cs.meta create mode 100644 Assets/Scripts/ServerShared/CultCache/Serialization/MathResolver.cs create mode 100644 Assets/Scripts/ServerShared/CultCache/Serialization/MathResolver.cs.meta create mode 100644 Assets/Scripts/ServerShared/CultCache/Serialization/RegisterResolver.cs create mode 100644 Assets/Scripts/ServerShared/CultCache/Serialization/RegisterResolver.cs.meta create mode 100644 Assets/Scripts/ServerShared/CultCache/Serialization/TypeFormatterResolver.cs create mode 100644 Assets/Scripts/ServerShared/CultCache/Serialization/TypeFormatterResolver.cs.meta delete mode 100644 Assets/Scripts/ServerShared/EntityConstructionBlueprintMaterializer.cs create mode 100644 Assets/Scripts/ServerShared/EntitySerializer.cs rename Assets/Scripts/ServerShared/{EntityConstructionBlueprintMaterializer.cs.meta => EntitySerializer.cs.meta} (100%) delete mode 100644 Assets/Scripts/ServerShared/EnumerableSelectionExtensions.cs delete mode 100644 Assets/Scripts/ServerShared/EnumerableSelectionExtensions.cs.meta create mode 100644 Assets/Scripts/ServerShared/GlobalData.cs create mode 100644 Assets/Scripts/ServerShared/GlobalData.cs.meta create mode 100644 Assets/Scripts/ServerShared/ItemData.cs create mode 100644 Assets/Scripts/ServerShared/ItemData.cs.meta create mode 100644 Assets/Scripts/ServerShared/Messages.cs create mode 100644 Assets/Scripts/ServerShared/Messages.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/LiteNetLib.meta create mode 100644 Assets/Scripts/ServerShared/NIH/LiteNetLib/BaseChannel.cs create mode 100644 Assets/Scripts/ServerShared/NIH/LiteNetLib/BaseChannel.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/LiteNetLib/ConnectionRequest.cs create mode 100644 Assets/Scripts/ServerShared/NIH/LiteNetLib/ConnectionRequest.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/LiteNetLib/INetEventListener.cs create mode 100644 Assets/Scripts/ServerShared/NIH/LiteNetLib/INetEventListener.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/LiteNetLib/Layers.meta create mode 100644 Assets/Scripts/ServerShared/NIH/LiteNetLib/Layers/Crc32cLayer.cs create mode 100644 Assets/Scripts/ServerShared/NIH/LiteNetLib/Layers/Crc32cLayer.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/LiteNetLib/Layers/PacketLayerBase.cs create mode 100644 Assets/Scripts/ServerShared/NIH/LiteNetLib/Layers/PacketLayerBase.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/LiteNetLib/Layers/XorEncryptLayer.cs create mode 100644 Assets/Scripts/ServerShared/NIH/LiteNetLib/Layers/XorEncryptLayer.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/LiteNetLib/LiteNetLib.csproj create mode 100644 Assets/Scripts/ServerShared/NIH/LiteNetLib/LiteNetLib.csproj.meta create mode 100644 Assets/Scripts/ServerShared/NIH/LiteNetLib/NatPunchModule.cs create mode 100644 Assets/Scripts/ServerShared/NIH/LiteNetLib/NatPunchModule.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/LiteNetLib/NetConstants.cs create mode 100644 Assets/Scripts/ServerShared/NIH/LiteNetLib/NetConstants.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/LiteNetLib/NetDebug.cs create mode 100644 Assets/Scripts/ServerShared/NIH/LiteNetLib/NetDebug.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/LiteNetLib/NetExceptions.cs create mode 100644 Assets/Scripts/ServerShared/NIH/LiteNetLib/NetExceptions.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/LiteNetLib/NetManager.cs create mode 100644 Assets/Scripts/ServerShared/NIH/LiteNetLib/NetManager.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/LiteNetLib/NetPacket.cs create mode 100644 Assets/Scripts/ServerShared/NIH/LiteNetLib/NetPacket.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/LiteNetLib/NetPacketPool.cs create mode 100644 Assets/Scripts/ServerShared/NIH/LiteNetLib/NetPacketPool.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/LiteNetLib/NetPeer.cs create mode 100644 Assets/Scripts/ServerShared/NIH/LiteNetLib/NetPeer.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/LiteNetLib/NetSocket.cs create mode 100644 Assets/Scripts/ServerShared/NIH/LiteNetLib/NetSocket.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/LiteNetLib/NetStatistics.cs create mode 100644 Assets/Scripts/ServerShared/NIH/LiteNetLib/NetStatistics.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/LiteNetLib/NetUtils.cs create mode 100644 Assets/Scripts/ServerShared/NIH/LiteNetLib/NetUtils.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/LiteNetLib/ReliableChannel.cs create mode 100644 Assets/Scripts/ServerShared/NIH/LiteNetLib/ReliableChannel.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/LiteNetLib/SequencedChannel.cs create mode 100644 Assets/Scripts/ServerShared/NIH/LiteNetLib/SequencedChannel.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/LiteNetLib/Utils.meta create mode 100644 Assets/Scripts/ServerShared/NIH/LiteNetLib/Utils/CRC32C.cs create mode 100644 Assets/Scripts/ServerShared/NIH/LiteNetLib/Utils/CRC32C.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/LiteNetLib/Utils/FastBitConverter.cs create mode 100644 Assets/Scripts/ServerShared/NIH/LiteNetLib/Utils/FastBitConverter.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/LiteNetLib/Utils/INetSerializable.cs create mode 100644 Assets/Scripts/ServerShared/NIH/LiteNetLib/Utils/INetSerializable.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/LiteNetLib/Utils/NetDataReader.cs create mode 100644 Assets/Scripts/ServerShared/NIH/LiteNetLib/Utils/NetDataReader.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/LiteNetLib/Utils/NetDataWriter.cs create mode 100644 Assets/Scripts/ServerShared/NIH/LiteNetLib/Utils/NetDataWriter.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/LiteNetLib/Utils/NetPacketProcessor.cs create mode 100644 Assets/Scripts/ServerShared/NIH/LiteNetLib/Utils/NetPacketProcessor.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/LiteNetLib/Utils/NetSerializer.cs create mode 100644 Assets/Scripts/ServerShared/NIH/LiteNetLib/Utils/NetSerializer.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/LiteNetLib/Utils/NtpPacket.cs create mode 100644 Assets/Scripts/ServerShared/NIH/LiteNetLib/Utils/NtpPacket.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/LiteNetLib/Utils/NtpRequest.cs create mode 100644 Assets/Scripts/ServerShared/NIH/LiteNetLib/Utils/NtpRequest.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDB.asmdef create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDB.asmdef.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver.Linq.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver.Linq/Attributes.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver.Linq/Attributes/PrimaryIndexAttribute.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver.Linq/Attributes/PrimaryIndexAttribute.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver.Linq/Attributes/SecondaryIndexAttribute.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver.Linq/Attributes/SecondaryIndexAttribute.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver.Linq/LinqExtensions.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver.Linq/LinqExtensions.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver.Linq/MemberNameResolver.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver.Linq/MemberNameResolver.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver.Linq/RethinkDb.Driver.Linq.csproj create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver.Linq/RethinkDb.Driver.Linq.csproj.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver.Linq/RethinkDb.Driver.Linq.ruleset create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver.Linq/RethinkDb.Driver.Linq.ruleset.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver.Linq/RethinkDbGroup.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver.Linq/RethinkDbGroup.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver.Linq/RethinkDbQueryModelVisitor.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver.Linq/RethinkDbQueryModelVisitor.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver.Linq/RethinkQueryExecutor.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver.Linq/RethinkQueryExecutor.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver.Linq/RethinkQueryable.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver.Linq/RethinkQueryable.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver.Linq/SelectionProjector.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver.Linq/SelectionProjector.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver.Linq/WhereClauseParsers.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver.Linq/WhereClauseParsers/BaseIndexParser.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver.Linq/WhereClauseParsers/BaseIndexParser.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver.Linq/WhereClauseParsers/DefaultParser.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver.Linq/WhereClauseParsers/DefaultParser.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver.Linq/WhereClauseParsers/ExprHelper.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver.Linq/WhereClauseParsers/ExprHelper.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver.Linq/WhereClauseParsers/ExpressionVisitor.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver.Linq/WhereClauseParsers/ExpressionVisitor.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver.Linq/WhereClauseParsers/GroupItemsParser.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver.Linq/WhereClauseParsers/GroupItemsParser.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver.Linq/WhereClauseParsers/IWhereClauseParser.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver.Linq/WhereClauseParsers/IWhereClauseParser.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver.Linq/WhereClauseParsers/PrimaryIndexParser.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver.Linq/WhereClauseParsers/PrimaryIndexParser.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver.Linq/WhereClauseParsers/SecondaryIndexParser.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver.Linq/WhereClauseParsers/SecondaryIndexParser.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver.Linq/WhereClauseParsers/SubQueryVisitor.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver.Linq/WhereClauseParsers/SubQueryVisitor/AllSubQueryVisitor.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver.Linq/WhereClauseParsers/SubQueryVisitor/AllSubQueryVisitor.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver.Linq/WhereClauseParsers/SubQueryVisitor/AnySubQueryVisitor.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver.Linq/WhereClauseParsers/SubQueryVisitor/AnySubQueryVisitor.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver.Linq/WhereClauseParsers/SubQueryVisitor/BaseFilterableSubQueryVisitor.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver.Linq/WhereClauseParsers/SubQueryVisitor/BaseFilterableSubQueryVisitor.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver.Linq/WhereClauseParsers/SubQueryVisitor/BaseSubQueryVisitor.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver.Linq/WhereClauseParsers/SubQueryVisitor/BaseSubQueryVisitor.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver.Linq/WhereClauseParsers/SubQueryVisitor/ContainsSubQueryVisitor.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver.Linq/WhereClauseParsers/SubQueryVisitor/ContainsSubQueryVisitor.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver.Linq/WhereClauseParsers/SubQueryVisitor/CountSubQueryVisitor.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver.Linq/WhereClauseParsers/SubQueryVisitor/CountSubQueryVisitor.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver.Linq/WhereClauseParsers/SubQueryVisitor/FirstSubQueryVisitor.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver.Linq/WhereClauseParsers/SubQueryVisitor/FirstSubQueryVisitor.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver.Linq/WhereClauseParsers/SubQueryVisitor/ISubQueryVisitor.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver.Linq/WhereClauseParsers/SubQueryVisitor/ISubQueryVisitor.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Ast.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Ast/ExtensionHelper.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Ast/ExtensionHelper.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Ast/Poco.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Ast/Poco.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Ast/PropertyHelper.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Ast/PropertyHelper.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Ast/Query.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Ast/Query.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Ast/ReqlAst.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Ast/ReqlAst.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Ast/ReqlExpr.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Ast/ReqlExpr.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Ast/ReqlRaw.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Ast/ReqlRaw.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Ast/TopLevel.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Ast/TopLevel.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Ast/Util.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Ast/Util.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/ErrorBuilder.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/ErrorBuilder.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Extras.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Extras/Dao.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Extras/Dao/Document.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Extras/Dao/Document.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Extras/Dao/IDao.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Extras/Dao/IDao.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Extras/Dao/RethinkDao.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Extras/Dao/RethinkDao.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Add.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Add.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/And.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/And.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Append.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Append.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/April.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/April.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Args.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Args.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Asc.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Asc.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/August.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/August.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Avg.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Avg.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Between.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Between.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Binary.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Binary.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Bracket.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Bracket.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Branch.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Branch.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Ceil.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Ceil.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/ChangeAt.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/ChangeAt.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Changes.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Changes.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Circle.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Circle.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/CoerceTo.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/CoerceTo.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/ConcatMap.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/ConcatMap.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Config.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Config.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Contains.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Contains.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Count.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Count.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Date.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Date.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Datum.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Datum.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Day.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Day.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/DayOfWeek.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/DayOfWeek.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/DayOfYear.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/DayOfYear.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Db.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Db.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/DbCreate.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/DbCreate.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/DbDrop.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/DbDrop.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/DbList.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/DbList.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/December.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/December.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Default.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Default.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Delete.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Delete.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/DeleteAt.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/DeleteAt.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Desc.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Desc.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Difference.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Difference.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Distance.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Distance.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Distinct.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Distinct.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Div.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Div.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Downcase.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Downcase.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/During.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/During.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/EpochTime.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/EpochTime.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Eq.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Eq.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/EqJoin.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/EqJoin.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Error.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Error.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/February.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/February.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Fill.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Fill.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Filter.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Filter.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Floor.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Floor.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Fold.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Fold.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/ForEach.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/ForEach.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Friday.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Friday.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Func.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Func.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Funcall.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Funcall.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Ge.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Ge.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Geojson.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Geojson.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Get.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Get.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/GetAll.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/GetAll.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/GetField.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/GetField.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/GetIntersecting.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/GetIntersecting.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/GetNearest.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/GetNearest.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Grant.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Grant.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Group.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Group.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Gt.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Gt.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/HasFields.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/HasFields.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Hours.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Hours.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Http.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Http.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/InTimezone.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/InTimezone.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Includes.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Includes.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/IndexCreate.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/IndexCreate.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/IndexDrop.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/IndexDrop.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/IndexList.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/IndexList.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/IndexRename.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/IndexRename.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/IndexStatus.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/IndexStatus.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/IndexWait.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/IndexWait.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Info.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Info.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/InnerJoin.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/InnerJoin.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Insert.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Insert.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/InsertAt.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/InsertAt.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Intersects.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Intersects.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/IsEmpty.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/IsEmpty.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Iso8601.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Iso8601.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/January.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/January.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Javascript.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Javascript.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Json.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Json.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/July.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/July.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/June.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/June.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Keys.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Keys.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Le.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Le.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Limit.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Limit.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Line.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Line.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Literal.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Literal.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Lt.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Lt.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/MakeArray.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/MakeArray.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/MakeObj.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/MakeObj.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Map.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Map.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/March.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/March.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Match.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Match.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Max.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Max.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Maxval.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Maxval.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/May.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/May.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Merge.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Merge.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Min.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Min.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Minutes.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Minutes.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Minval.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Minval.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Mod.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Mod.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Monday.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Monday.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Month.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Month.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Mul.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Mul.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Ne.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Ne.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Not.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Not.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/November.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/November.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Now.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Now.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Nth.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Nth.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/October.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/October.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/OffsetsOf.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/OffsetsOf.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Or.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Or.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/OrderBy.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/OrderBy.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/OuterJoin.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/OuterJoin.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Pluck.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Pluck.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Point.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Point.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Polygon.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Polygon.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/PolygonSub.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/PolygonSub.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Prepend.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Prepend.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Random.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Random.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Range.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Range.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Rebalance.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Rebalance.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Reconfigure.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Reconfigure.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Reduce.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Reduce.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Replace.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Replace.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/ReqlExpr.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/ReqlExpr.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/ReqlObject.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/ReqlObject.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Round.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Round.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Sample.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Sample.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Saturday.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Saturday.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Seconds.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Seconds.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/September.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/September.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/SetDifference.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/SetDifference.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/SetInsert.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/SetInsert.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/SetIntersection.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/SetIntersection.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/SetUnion.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/SetUnion.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Skip.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Skip.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Slice.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Slice.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/SpliceAt.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/SpliceAt.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Split.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Split.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Status.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Status.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Sub.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Sub.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Sum.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Sum.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Sunday.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Sunday.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Sync.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Sync.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Table.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Table.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/TableCreate.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/TableCreate.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/TableDrop.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/TableDrop.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/TableList.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/TableList.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Thursday.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Thursday.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Time.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Time.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/TimeOfDay.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/TimeOfDay.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Timezone.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Timezone.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/ToEpochTime.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/ToEpochTime.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/ToGeojson.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/ToGeojson.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/ToIso8601.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/ToIso8601.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/ToJsonString.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/ToJsonString.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Tuesday.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Tuesday.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/TypeOf.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/TypeOf.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Ungroup.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Ungroup.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Union.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Union.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Upcase.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Upcase.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Update.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Update.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Uuid.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Uuid.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Values.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Values.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Var.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Var.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Wait.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Wait.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Wednesday.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Wednesday.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/WithFields.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/WithFields.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Without.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Without.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Year.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Year.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Zip.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Ast/Zip.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Model.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Model/AuthType.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Model/AuthType.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Model/Bound.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Model/Bound.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Model/Conflict.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Model/Conflict.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Model/Durability.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Model/Durability.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Model/EmergencyRepair.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Model/EmergencyRepair.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Model/Format.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Model/Format.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Model/GeoSystem.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Model/GeoSystem.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Model/HttpMethod.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Model/HttpMethod.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Model/IdentifierFormat.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Model/IdentifierFormat.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Model/ReadMode.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Model/ReadMode.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Model/ReqlFunction0.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Model/ReqlFunction0.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Model/ReqlFunction1.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Model/ReqlFunction1.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Model/ReqlFunction2.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Model/ReqlFunction2.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Model/ReqlFunction3.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Model/ReqlFunction3.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Model/ReqlFunction4.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Model/ReqlFunction4.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Model/ResultFormat.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Model/ResultFormat.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Model/Status.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Model/Status.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Model/TopLevel.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Model/TopLevel.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Model/Unit.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Model/Unit.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Proto.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Proto/DatumType.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Proto/DatumType.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Proto/ErrorType.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Proto/ErrorType.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Proto/FrameType.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Proto/FrameType.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Proto/Protocol.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Proto/Protocol.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Proto/QueryType.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Proto/QueryType.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Proto/ResponseNote.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Proto/ResponseNote.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Proto/ResponseType.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Proto/ResponseType.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Proto/TermType.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Proto/TermType.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Proto/Version.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/Proto/Version.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/ReqlAuthError.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/ReqlAuthError.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/ReqlAvailabilityError.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/ReqlAvailabilityError.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/ReqlClientError.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/ReqlClientError.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/ReqlCompileError.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/ReqlCompileError.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/ReqlDriverCompileError.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/ReqlDriverCompileError.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/ReqlDriverError.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/ReqlDriverError.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/ReqlError.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/ReqlError.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/ReqlInternalError.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/ReqlInternalError.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/ReqlNonExistenceError.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/ReqlNonExistenceError.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/ReqlOpFailedError.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/ReqlOpFailedError.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/ReqlOpIndeterminateError.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/ReqlOpIndeterminateError.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/ReqlPermissionError.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/ReqlPermissionError.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/ReqlQueryLogicError.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/ReqlQueryLogicError.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/ReqlResourceLimitError.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/ReqlResourceLimitError.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/ReqlRuntimeError.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/ReqlRuntimeError.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/ReqlServerCompileError.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/ReqlServerCompileError.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/ReqlUserError.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Generated/ReqlUserError.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Log.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Log.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Model.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Model/Arguments.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Model/Arguments.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Model/Backtrace.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Model/Backtrace.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Model/Change[T].cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Model/Change[T].cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Model/ExtensionsForResult.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Model/ExtensionsForResult.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Model/GroupedResult.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Model/GroupedResult.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Model/MapObject.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Model/MapObject.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Model/OptArgs.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Model/OptArgs.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Model/PathSpec.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Model/PathSpec.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Model/Profile.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Model/Profile.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Model/ReqlLambda.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Model/ReqlLambda.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Model/Result.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Model/Result.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Model/Server.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Model/Server.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Net.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Net/Awaiter.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Net/Awaiter.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Net/Clustering.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Net/Clustering/ConnectionPool.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Net/Clustering/ConnectionPool.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Net/Clustering/EpsilonGreedy.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Net/Clustering/EpsilonGreedy.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Net/Clustering/EpsilonValueCalculator.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Net/Clustering/EpsilonValueCalculator.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Net/Clustering/ExceptionIs.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Net/Clustering/ExceptionIs.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Net/Clustering/HostEntry.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Net/Clustering/HostEntry.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Net/Clustering/HostPool.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Net/Clustering/HostPool.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Net/Clustering/IPoolingStrategy.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Net/Clustering/IPoolingStrategy.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Net/Clustering/RoundRobinHostPool.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Net/Clustering/RoundRobinHostPool.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Net/Clustering/Seed.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Net/Clustering/Seed.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Net/Clustering/Server.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Net/Clustering/Server.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Net/Connection.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Net/Connection.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Net/Converter.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Net/Converter.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Net/Crypto.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Net/Crypto.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Net/Cursor.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Net/Cursor.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Net/FormatOptions.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Net/FormatOptions.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Net/Handshake.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Net/Handshake.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Net/IConnection.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Net/IConnection.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Net/IConnectionBuilder.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Net/IConnectionBuilder.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Net/ICursor.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Net/ICursor.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Net/IncrementalSHA256.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Net/IncrementalSHA256.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Net/JsonConverters.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Net/JsonConverters/ExtensionsForJsonConverters.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Net/JsonConverters/ExtensionsForJsonConverters.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Net/JsonConverters/ReqlBinaryConverter.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Net/JsonConverters/ReqlBinaryConverter.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Net/JsonConverters/ReqlDateTimeConverter.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Net/JsonConverters/ReqlDateTimeConverter.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Net/JsonConverters/ReqlExprConverter.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Net/JsonConverters/ReqlExprConverter.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Net/JsonConverters/ReqlGroupingConverter.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Net/JsonConverters/ReqlGroupingConverter.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Net/Response.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Net/Response.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Net/ScramAttributes.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Net/ScramAttributes.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Net/SecureRandom.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Net/SecureRandom.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Net/SocketWrapper.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Net/SocketWrapper.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Net/SslContext.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Net/SslContext.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/ReqlAssertFailure.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/ReqlAssertFailure.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/RethinkDB.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/RethinkDB.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/RethinkDBConstants.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/RethinkDBConstants.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/RethinkDb.Driver.csproj create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/RethinkDb.Driver.csproj.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/RethinkDb.Driver.ruleset create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/RethinkDb.Driver.ruleset.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Utils.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Utils/CancellableTask.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Utils/CancellableTask.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Utils/ExtensionsForEvent.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Utils/ExtensionsForEvent.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Utils/ExtensionsForString.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Utils/ExtensionsForString.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Utils/ExtensionsForTcpClient.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Utils/ExtensionsForTcpClient.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Utils/ExtensionsForType.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Utils/ExtensionsForType.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Utils/QueryHelper.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Utils/QueryHelper.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Utils/StringHelper.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Utils/StringHelper.cs.meta create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Utils/TaskHelper.cs create mode 100644 Assets/Scripts/ServerShared/NIH/RethinkDb/RethinkDb.Driver/Utils/TaskHelper.cs.meta create mode 100644 Assets/Scripts/ServerShared/PlayerData.cs create mode 100644 Assets/Scripts/ServerShared/PlayerData.cs.meta create mode 100644 Assets/Scripts/ServerShared/PlayerSettings.cs rename Assets/Scripts/ServerShared/{RuntimePlayerSettings.cs.meta => PlayerSettings.cs.meta} (100%) delete mode 100644 Assets/Scripts/ServerShared/RuntimeGeometry.cs delete mode 100644 Assets/Scripts/ServerShared/RuntimeGeometry.cs.meta delete mode 100644 Assets/Scripts/ServerShared/RuntimePlayerSettings.cs create mode 100644 Assets/Scripts/ServerShared/SavedGame.cs create mode 100644 Assets/Scripts/ServerShared/SavedGame.cs.meta delete mode 100644 Assets/Scripts/ServerShared/StatRecipe.cs delete mode 100644 Assets/Scripts/ServerShared/StatRecipe.cs.meta delete mode 100644 Assets/Scripts/ServerShared/Stats.meta create mode 100644 Assets/Scripts/ServerShared/Wormhole.cs create mode 100644 Assets/Scripts/ServerShared/Wormhole.cs.meta delete mode 100644 Assets/Scripts/ServerShared/YmirPhysicsContracts.cs delete mode 100644 Assets/Scripts/ServerShared/YmirPhysicsContracts.cs.meta delete mode 100644 Assets/Scripts/ServerShared/ZoneConstructionData.cs create mode 100644 Assets/Scripts/ServerShared/ZoneData.cs rename Assets/Scripts/ServerShared/{ZoneConstructionData.cs.meta => ZoneData.cs.meta} (100%) create mode 100644 Assets/Scripts/StrategyGameManager.csbak create mode 100644 Assets/Scripts/StrategyGameManager.csbak.meta delete mode 100644 Assets/Scripts/Tests/AetheriaRuntimeAssetBundleLayoutTests.cs delete mode 100644 Assets/Scripts/Tests/AetheriaRuntimeAssetBundleLayoutTests.cs.meta delete mode 100644 Assets/Scripts/Tests/CultMathPrimitiveTests.cs delete mode 100644 Assets/Scripts/Tests/CultMathPrimitiveTests.cs.meta delete mode 100644 Assets/Scripts/Tests/DaemonRuntimeDocumentTests.cs delete mode 100644 Assets/Scripts/Tests/DaemonRuntimeDocumentTests.cs.meta delete mode 100644 Assets/Scripts/Tests/StarbridgePlayerSeatDocumentTests.cs delete mode 100644 Assets/Scripts/Tests/StarbridgePlayerSeatDocumentTests.cs.meta delete mode 100644 Assets/Scripts/Tests/StatRecipeSurfaceCommandTests.cs delete mode 100644 Assets/Scripts/Tests/StatRecipeSurfaceCommandTests.cs.meta delete mode 100644 Assets/Scripts/Tests/StatRecipeTests.cs delete mode 100644 Assets/Scripts/Tests/StatRecipeTests.cs.meta delete mode 100644 Assets/Scripts/Tests/YmirPhysicsQueryTests.cs delete mode 100644 Assets/Scripts/Tests/YmirPhysicsQueryTests.cs.meta create mode 100644 Assets/Scripts/UI/ConsoleController.cs create mode 100644 Assets/Scripts/UI/ConsoleController.cs.meta create mode 100644 Assets/Scripts/UI/ConsoleView.cs create mode 100644 Assets/Scripts/UI/ConsoleView.cs.meta create mode 100644 Assets/Scripts/UI/ContextMenu.cs create mode 100644 Assets/Scripts/UI/ContextMenu.cs.meta create mode 100644 Assets/Scripts/UI/ContextMenuOption.cs create mode 100644 Assets/Scripts/UI/ContextMenuOption.cs.meta create mode 100644 Assets/Scripts/UI/DropdownMenu.cs create mode 100644 Assets/Scripts/UI/DropdownMenu.cs.meta create mode 100644 Assets/Scripts/UI/FieldTester.cs create mode 100644 Assets/Scripts/UI/FieldTester.cs.meta create mode 100644 Assets/Scripts/UI/Menu/MenuTabButton.cs create mode 100644 Assets/Scripts/UI/Menu/MenuTabButton.cs.meta create mode 100644 Assets/Scripts/UI/Menu/TradeMenuDebug.cs create mode 100644 Assets/Scripts/UI/Menu/TradeMenuDebug.cs.meta create mode 100644 Assets/Scripts/UI/Menu/WeaponGroupAssignment.cs create mode 100644 Assets/Scripts/UI/Menu/WeaponGroupAssignment.cs.meta create mode 100644 Assets/Scripts/UI/Menu/WeaponGroupElement.cs create mode 100644 Assets/Scripts/UI/Menu/WeaponGroupElement.cs.meta create mode 100644 Assets/Scripts/UI/Properties Panel/PropertiesList.cs create mode 100644 Assets/Scripts/UI/Properties Panel/PropertiesList.cs.meta create mode 100644 Assets/Scripts/UI/Properties Panel/PropertiesPanel.cs create mode 100644 Assets/Scripts/UI/Properties Panel/PropertiesPanel.cs.meta create mode 100644 Assets/Scripts/UnityFormatters.cs create mode 100644 Assets/Scripts/UnityFormatters.cs.meta delete mode 100644 Assets/Settings.meta delete mode 100644 Assets/Settings/Rendering.meta delete mode 100644 Assets/Settings/Rendering/AetheriaMainMenuVolumeProfile.asset delete mode 100644 Assets/Settings/Rendering/AetheriaMainMenuVolumeProfile.asset.meta delete mode 100644 Assets/Settings/Rendering/AetheriaURPAsset.asset delete mode 100644 Assets/Settings/Rendering/AetheriaURPAsset.asset.meta delete mode 100644 Assets/Settings/Rendering/AetheriaUniversalRenderer.asset delete mode 100644 Assets/Settings/Rendering/AetheriaUniversalRenderer.asset.meta delete mode 100644 Assets/Settings/Rendering/CustomPostProcessData.asset delete mode 100644 Assets/Shaders/AetheriaDaemonIndirect.shader delete mode 100644 Assets/Shaders/AetheriaDaemonIndirect.shader.meta create mode 100644 Assets/Shaders/Compute/Lightning.meta create mode 100644 Assets/Shaders/Compute/Lightning/Lightning 1.mat rename Assets/{Settings/Rendering/CustomPostProcessData.asset.meta => Shaders/Compute/Lightning/Lightning 1.mat.meta} (64%) create mode 100644 Assets/Shaders/Compute/Lightning/Lightning.compute create mode 100644 Assets/Shaders/Compute/Lightning/Lightning.compute.meta create mode 100644 Assets/Shaders/Compute/Lightning/Lightning.mat create mode 100644 Assets/Shaders/Compute/Lightning/Lightning.mat.meta create mode 100644 Assets/Shaders/Compute/Lightning/Lightning.shader create mode 100644 Assets/Shaders/Compute/Lightning/Lightning.shader.meta create mode 100644 Assets/Shaders/Compute/Lightning/LightningCompute.cs create mode 100644 Assets/Shaders/Compute/Lightning/LightningCompute.cs.meta create mode 100644 Assets/Shaders/Compute/Lightning/LightningVertex.cginc create mode 100644 Assets/Shaders/Compute/Lightning/LightningVertex.cginc.meta delete mode 100644 Assets/Shaders/CustomDoF/AetheriaFogBlur.cs delete mode 100644 Assets/Shaders/CustomDoF/AetheriaFogBlur.cs.meta delete mode 100644 Assets/Shaders/CustomDoF/AetheriaFogBlurRendererFeature.cs delete mode 100644 Assets/Shaders/CustomDoF/AetheriaFogBlurRendererFeature.cs.meta delete mode 100644 Assets/Shaders/CustomDoF/AetheriaFogBlurUrp.hlsl delete mode 100644 Assets/Textures/ThirdParty/ambientCG/Metal012.meta delete mode 100644 Assets/Textures/ThirdParty/ambientCG/Metal012/LICENSE.md delete mode 100644 Assets/Textures/ThirdParty/ambientCG/Metal012/Metal012_1K-JPG_Color.jpg delete mode 100644 Assets/Textures/ThirdParty/ambientCG/Metal012/Metal012_1K-JPG_Metalness.jpg delete mode 100644 Assets/Textures/ThirdParty/ambientCG/Metal012/Metal012_1K-JPG_NormalGL.jpg delete mode 100644 Assets/UniversalRenderPipelineGlobalSettings.asset delete mode 100644 Assets/UniversalRenderPipelineGlobalSettings.asset.meta delete mode 100644 CultMath.csproj create mode 100644 Economy.Server/Economy.Server.csproj create mode 100644 Economy.Server/Program.cs create mode 100644 Economy.Server/Server.cs create mode 100644 Economy.Server/appsettings.json create mode 100644 Economy.Shared/Economy.Shared.csproj create mode 100644 Economy.Shared/Unity.Mathematics/Noise/LICENSE create mode 100644 Economy.Shared/Unity.Mathematics/Noise/README create mode 100644 Economy.Shared/Unity.Mathematics/Noise/cellular2D.cs create mode 100644 Economy.Shared/Unity.Mathematics/Noise/cellular2x2.cs create mode 100644 Economy.Shared/Unity.Mathematics/Noise/cellular2x2x2.cs create mode 100644 Economy.Shared/Unity.Mathematics/Noise/cellular3D.cs create mode 100644 Economy.Shared/Unity.Mathematics/Noise/classicnoise2D.cs create mode 100644 Economy.Shared/Unity.Mathematics/Noise/classicnoise3D.cs create mode 100644 Economy.Shared/Unity.Mathematics/Noise/classicnoise4D.cs create mode 100644 Economy.Shared/Unity.Mathematics/Noise/common.cs create mode 100644 Economy.Shared/Unity.Mathematics/Noise/noise2D.cs create mode 100644 Economy.Shared/Unity.Mathematics/Noise/noise3D.cs create mode 100644 Economy.Shared/Unity.Mathematics/Noise/noise3Dgrad.cs create mode 100644 Economy.Shared/Unity.Mathematics/Noise/noise4D.cs create mode 100644 Economy.Shared/Unity.Mathematics/Noise/psrdnoise2D.cs create mode 100644 Economy.Shared/Unity.Mathematics/PropertyAttributes.cs create mode 100644 Economy.Shared/Unity.Mathematics/Unity.Mathematics.asmdef create mode 100644 Economy.Shared/Unity.Mathematics/bool2.gen.cs create mode 100644 Economy.Shared/Unity.Mathematics/bool2x2.gen.cs create mode 100644 Economy.Shared/Unity.Mathematics/bool2x3.gen.cs create mode 100644 Economy.Shared/Unity.Mathematics/bool2x4.gen.cs create mode 100644 Economy.Shared/Unity.Mathematics/bool3.gen.cs create mode 100644 Economy.Shared/Unity.Mathematics/bool3x2.gen.cs create mode 100644 Economy.Shared/Unity.Mathematics/bool3x3.gen.cs create mode 100644 Economy.Shared/Unity.Mathematics/bool3x4.gen.cs create mode 100644 Economy.Shared/Unity.Mathematics/bool4.gen.cs create mode 100644 Economy.Shared/Unity.Mathematics/bool4x2.gen.cs create mode 100644 Economy.Shared/Unity.Mathematics/bool4x3.gen.cs create mode 100644 Economy.Shared/Unity.Mathematics/bool4x4.gen.cs create mode 100644 Economy.Shared/Unity.Mathematics/double2.gen.cs create mode 100644 Economy.Shared/Unity.Mathematics/double2x2.gen.cs create mode 100644 Economy.Shared/Unity.Mathematics/double2x3.gen.cs create mode 100644 Economy.Shared/Unity.Mathematics/double2x4.gen.cs create mode 100644 Economy.Shared/Unity.Mathematics/double3.gen.cs create mode 100644 Economy.Shared/Unity.Mathematics/double3x2.gen.cs create mode 100644 Economy.Shared/Unity.Mathematics/double3x3.gen.cs create mode 100644 Economy.Shared/Unity.Mathematics/double3x4.gen.cs create mode 100644 Economy.Shared/Unity.Mathematics/double4.gen.cs create mode 100644 Economy.Shared/Unity.Mathematics/double4x2.gen.cs create mode 100644 Economy.Shared/Unity.Mathematics/double4x3.gen.cs create mode 100644 Economy.Shared/Unity.Mathematics/double4x4.gen.cs create mode 100644 Economy.Shared/Unity.Mathematics/float2.gen.cs create mode 100644 Economy.Shared/Unity.Mathematics/float2x2.gen.cs create mode 100644 Economy.Shared/Unity.Mathematics/float2x3.gen.cs create mode 100644 Economy.Shared/Unity.Mathematics/float2x4.gen.cs create mode 100644 Economy.Shared/Unity.Mathematics/float3.gen.cs create mode 100644 Economy.Shared/Unity.Mathematics/float3x2.gen.cs create mode 100644 Economy.Shared/Unity.Mathematics/float3x3.gen.cs create mode 100644 Economy.Shared/Unity.Mathematics/float3x4.gen.cs create mode 100644 Economy.Shared/Unity.Mathematics/float4.gen.cs create mode 100644 Economy.Shared/Unity.Mathematics/float4x2.gen.cs create mode 100644 Economy.Shared/Unity.Mathematics/float4x3.gen.cs create mode 100644 Economy.Shared/Unity.Mathematics/float4x4.gen.cs create mode 100644 Economy.Shared/Unity.Mathematics/half.cs create mode 100644 Economy.Shared/Unity.Mathematics/half2.gen.cs create mode 100644 Economy.Shared/Unity.Mathematics/half3.gen.cs create mode 100644 Economy.Shared/Unity.Mathematics/half4.gen.cs create mode 100644 Economy.Shared/Unity.Mathematics/int2.gen.cs create mode 100644 Economy.Shared/Unity.Mathematics/int2x2.gen.cs create mode 100644 Economy.Shared/Unity.Mathematics/int2x3.gen.cs create mode 100644 Economy.Shared/Unity.Mathematics/int2x4.gen.cs create mode 100644 Economy.Shared/Unity.Mathematics/int3.gen.cs create mode 100644 Economy.Shared/Unity.Mathematics/int3x2.gen.cs create mode 100644 Economy.Shared/Unity.Mathematics/int3x3.gen.cs create mode 100644 Economy.Shared/Unity.Mathematics/int3x4.gen.cs create mode 100644 Economy.Shared/Unity.Mathematics/int4.gen.cs create mode 100644 Economy.Shared/Unity.Mathematics/int4x2.gen.cs create mode 100644 Economy.Shared/Unity.Mathematics/int4x3.gen.cs create mode 100644 Economy.Shared/Unity.Mathematics/int4x4.gen.cs create mode 100644 Economy.Shared/Unity.Mathematics/math.cs create mode 100644 Economy.Shared/Unity.Mathematics/matrix.cs create mode 100644 Economy.Shared/Unity.Mathematics/matrix.gen.cs create mode 100644 Economy.Shared/Unity.Mathematics/quaternion.cs create mode 100644 Economy.Shared/Unity.Mathematics/random.cs create mode 100644 Economy.Shared/Unity.Mathematics/rigid_transform.cs create mode 100644 Economy.Shared/Unity.Mathematics/uint2.gen.cs create mode 100644 Economy.Shared/Unity.Mathematics/uint2x2.gen.cs create mode 100644 Economy.Shared/Unity.Mathematics/uint2x3.gen.cs create mode 100644 Economy.Shared/Unity.Mathematics/uint2x4.gen.cs create mode 100644 Economy.Shared/Unity.Mathematics/uint3.gen.cs create mode 100644 Economy.Shared/Unity.Mathematics/uint3x2.gen.cs create mode 100644 Economy.Shared/Unity.Mathematics/uint3x3.gen.cs create mode 100644 Economy.Shared/Unity.Mathematics/uint3x4.gen.cs create mode 100644 Economy.Shared/Unity.Mathematics/uint4.gen.cs create mode 100644 Economy.Shared/Unity.Mathematics/uint4x2.gen.cs create mode 100644 Economy.Shared/Unity.Mathematics/uint4x3.gen.cs create mode 100644 Economy.Shared/Unity.Mathematics/uint4x4.gen.cs create mode 100644 Economy.Shared/bin/Debug/netstandard2.0/Economy.Shared.deps.json create mode 100644 Economy.Shared/bin/Debug/netstandard2.0/Economy.Shared.dll create mode 100644 Economy.Shared/obj/Debug/netstandard2.0/Economy.Shared.AssemblyInfo.cs create mode 100644 Economy.Shared/obj/Debug/netstandard2.0/Economy.Shared.AssemblyInfoInputs.cache create mode 100644 Economy.Shared/obj/Debug/netstandard2.0/Economy.Shared.assets.cache create mode 100644 Economy.Shared/obj/Debug/netstandard2.0/Economy.Shared.csproj.FileListAbsolute.txt create mode 100644 Economy.Shared/obj/Debug/netstandard2.0/Economy.Shared.csprojAssemblyReference.cache create mode 100644 Economy.Shared/obj/Debug/netstandard2.0/Economy.Shared.dll create mode 100644 Economy.Shared/obj/Economy.Shared.csproj.nuget.cache create mode 100644 Economy.Shared/obj/Economy.Shared.csproj.nuget.dgspec.json create mode 100644 Economy.Shared/obj/Economy.Shared.csproj.nuget.g.props create mode 100644 Economy.Shared/obj/Economy.Shared.csproj.nuget.g.targets create mode 100644 Economy.Shared/obj/project.assets.json create mode 100644 Economy.Shared/obj/project.packagespec.json delete mode 100644 GameCult.Aetheria.EveRuntime.csproj delete mode 100644 GameCult.Aetheria.State.Unity.csproj delete mode 100644 GameCult.Brokkr.Editor.csproj delete mode 100644 GameCult.Brokkr.csproj delete mode 100644 GameCult.Eve.Surface.csproj delete mode 100644 GameCult.Eve.UnityScene.csproj delete mode 100644 GameCult.Eve.UnityUIToolkit.csproj create mode 100644 GameData/KeyboardLayouts/ansi104.json create mode 100644 GameData/KeyboardLayouts/ansi104.msgpack delete mode 100644 GameData/Legacy/AetherDB.2021-03-05.msgpack delete mode 100644 GameData/Legacy/AetherDB.2021-04-14.msgpack delete mode 100644 GameData/aetheria-client.cc delete mode 100644 GameData/aetheria-hermodr-simple.cc delete mode 100644 GameData/aetheria-simple.cc delete mode 100644 GameData/aetheria-world.cc delete mode 100644 GameData/aetheria-world.cc.authority.policy.cc delete mode 100644 GameData/cultui-debug-surface.cultui delete mode 100644 MessagePack.SourceDisabled.csproj delete mode 100644 Packages/org.gamecult.aetheria.eve-runtime/README.md delete mode 100644 Packages/org.gamecult.aetheria.eve-runtime/README.md.meta delete mode 100644 Packages/org.gamecult.aetheria.eve-runtime/Runtime.meta delete mode 100644 Packages/org.gamecult.aetheria.eve-runtime/Runtime/AetheriaEveFileSurfacePresenter.cs delete mode 100644 Packages/org.gamecult.aetheria.eve-runtime/Runtime/AetheriaEveFileSurfacePresenter.cs.meta delete mode 100644 Packages/org.gamecult.aetheria.eve-runtime/Runtime/AetheriaEveRuntimeBootstrap.cs delete mode 100644 Packages/org.gamecult.aetheria.eve-runtime/Runtime/AetheriaEveRuntimeBootstrap.cs.meta delete mode 100644 Packages/org.gamecult.aetheria.eve-runtime/Runtime/AetheriaEveRuntimeUnityClientCache.cs delete mode 100644 Packages/org.gamecult.aetheria.eve-runtime/Runtime/AetheriaEveRuntimeUnityClientCache.cs.meta delete mode 100644 Packages/org.gamecult.aetheria.eve-runtime/Runtime/AetheriaEveRuntimeUnityHookInstaller.cs delete mode 100644 Packages/org.gamecult.aetheria.eve-runtime/Runtime/AetheriaEveRuntimeUnityHookInstaller.cs.meta delete mode 100644 Packages/org.gamecult.aetheria.eve-runtime/Runtime/AetheriaEveSurfacePresenter.cs delete mode 100644 Packages/org.gamecult.aetheria.eve-runtime/Runtime/AetheriaEveSurfacePresenter.cs.meta delete mode 100644 Packages/org.gamecult.aetheria.eve-runtime/Runtime/AetheriaEveUnitySceneProviderBridge.cs delete mode 100644 Packages/org.gamecult.aetheria.eve-runtime/Runtime/AetheriaEveUnitySceneProviderBridge.cs.meta delete mode 100644 Packages/org.gamecult.aetheria.eve-runtime/Runtime/AetheriaEveUnitySceneProviderComponent.cs delete mode 100644 Packages/org.gamecult.aetheria.eve-runtime/Runtime/AetheriaEveUnitySceneProviderComponent.cs.meta delete mode 100644 Packages/org.gamecult.aetheria.eve-runtime/Runtime/AetheriaEveUnitySurfaceHost.cs delete mode 100644 Packages/org.gamecult.aetheria.eve-runtime/Runtime/AetheriaEveUnitySurfaceHost.cs.meta delete mode 100644 Packages/org.gamecult.aetheria.eve-runtime/Runtime/GameCult.Aetheria.EveRuntime.asmdef delete mode 100644 Packages/org.gamecult.aetheria.eve-runtime/Runtime/GameCult.Aetheria.EveRuntime.asmdef.meta delete mode 100644 Packages/org.gamecult.aetheria.eve-runtime/package.json delete mode 100644 Packages/org.gamecult.aetheria.eve-runtime/package.json.meta delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime.meta delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaClient.cs delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaClient.cs.meta delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaClientState.cs delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaClientState.cs.meta delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaControl.cs delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaControl.cs.meta delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeAgentTasks.cs delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeAgentTasks.cs.meta delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeAssemblyInfo.cs delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeAssemblyInfo.cs.meta delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeAssetDocuments.cs delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeAssetDocuments.cs.meta delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeAssets.cs delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeAssets.cs.meta delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeBehaviorKinds.cs delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeBehaviorKinds.cs.meta delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeBehaviorMetadata.cs delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeBehaviorMetadata.cs.meta delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeBehaviorSimulation.cs delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeBehaviorSimulation.cs.meta delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeBehaviorStateProjector.cs delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeBehaviorStateProjector.cs.meta delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeCargoCapacityQueries.cs delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeCargoCapacityQueries.cs.meta delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeCargoItemDetailsSurfaceBuilder.cs delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeCargoItemDetailsSurfaceBuilder.cs.meta delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeCargoTransactions.cs delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeCargoTransactions.cs.meta delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeCatalogCommands.cs delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeCatalogCommands.cs.meta delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeCatalogSnapshot.cs delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeCatalogSnapshot.cs.meta delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeCatalogStore.cs delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeCatalogStore.cs.meta delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeClientTargetCommands.cs delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeClientTargetCommands.cs.meta delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeClientTargetStore.cs delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeClientTargetStore.cs.meta delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeClientTargetSurfaceBuilder.cs delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeClientTargetSurfaceBuilder.cs.meta delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeCommittedFactImporter.cs delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeCommittedFactImporter.cs.meta delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeConsumableSimulation.cs delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeConsumableSimulation.cs.meta delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeCultCacheDocumentStore.cs delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeCultCacheDocumentStore.cs.meta delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeDaemonDocuments.cs delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeDaemonDocuments.cs.meta delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeDaemonEditorSurfaceBuilder.cs delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeDaemonEditorSurfaceBuilder.cs.meta delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeDaemonGameSurfaceBuilder.cs delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeDaemonGameSurfaceBuilder.cs.meta delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeDaemonIntentState.cs delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeDaemonIntentState.cs.meta delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeDaemonItemStatQueries.cs delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeDaemonItemStatQueries.cs.meta delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeDaemonObservationCursor.cs delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeDaemonObservationCursor.cs.meta delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeDaemonOperationClient.cs delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeDaemonOperationClient.cs.meta delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeDaemonOperations.cs delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeDaemonOperations.cs.meta delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeDaemonOperationsClient.cs delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeDaemonOperationsClient.cs.meta delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeDaemonRenderQueries.cs delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeDaemonRenderQueries.cs.meta delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeDaemonRenderView.cs delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeDaemonRenderView.cs.meta delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeDaemonSimulation.cs delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeDaemonSimulation.cs.meta delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeDaemonSimulationSettings.cs delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeDaemonSimulationSettings.cs.meta delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeDaemonSoaDocuments.cs delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeDaemonSoaDocuments.cs.meta delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeDaemonSoaFramePublisher.cs delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeDaemonSoaFramePublisher.cs.meta delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeDaemonSoaViewIndex.cs delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeDaemonSoaViewIndex.cs.meta delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeDaemonStateRefs.cs delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeDaemonStateRefs.cs.meta delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeDaemonSurfaceCommandCatalog.cs delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeDaemonSurfaceCommandCatalog.cs.meta delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeDaemonSurfaceCommands.cs delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeDaemonSurfaceCommands.cs.meta delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeDaemonTickRunner.cs delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeDaemonTickRunner.cs.meta delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeDaemonTradeItemQueries.cs delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeDaemonTradeItemQueries.cs.meta delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeEnergySimulation.cs delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeEnergySimulation.cs.meta delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeEquippedBehaviorQueries.cs delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeEquippedBehaviorQueries.cs.meta delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeEquippedItemDetailsSurfaceBuilder.cs delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeEquippedItemDetailsSurfaceBuilder.cs.meta delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeEveCommandClient.cs delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeEveCommandClient.cs.meta delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeEveCommandDocument.cs delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeEveCommandDocument.cs.meta delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeEveEntitySoaProjection.cs delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeEveEntitySoaProjection.cs.meta delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeEveSurfaceCatalog.cs delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeEveSurfaceCatalog.cs.meta delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeFlightSimulation.cs delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeFlightSimulation.cs.meta delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeGameDocuments.cs delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeGameDocuments.cs.meta delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeGameEvents.cs delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeGameEvents.cs.meta delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeGameViewportDocuments.cs delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeGameViewportDocuments.cs.meta delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeHangarDocuments.cs delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeHangarDocuments.cs.meta delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeHangarSurfaceBuilder.cs delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeHangarSurfaceBuilder.cs.meta delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeInputCapabilityDocuments.cs delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeInputCapabilityDocuments.cs.meta delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeInputSettingsCommands.cs delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeInputSettingsCommands.cs.meta delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeInputSettingsSurfaceBuilder.cs delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeInputSettingsSurfaceBuilder.cs.meta delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeInventoryDropdownSurfaceBuilder.cs delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeInventoryDropdownSurfaceBuilder.cs.meta delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeInventoryDropdownSurfaceDocuments.cs delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeInventoryDropdownSurfaceDocuments.cs.meta delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeInventoryPanelSurfaceBuilder.cs delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeInventoryPanelSurfaceBuilder.cs.meta delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeItemCategories.cs delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeItemCategories.cs.meta delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeLoadoutTemplateCommands.cs delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeLoadoutTemplateCommands.cs.meta delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeLocalStorySurfaceBuilder.cs delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeLocalStorySurfaceBuilder.cs.meta delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeMainMenuSurfaceBuilder.cs delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeMainMenuSurfaceBuilder.cs.meta delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeMiningSimulation.cs delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeMiningSimulation.cs.meta delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeOperationsCommands.cs delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeOperationsCommands.cs.meta delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeOrbitQueries.cs delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeOrbitQueries.cs.meta delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeOrbitSimulation.cs delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeOrbitSimulation.cs.meta delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimePickupTransactions.cs delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimePickupTransactions.cs.meta delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimePlayerSettingsCommands.cs delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimePlayerSettingsCommands.cs.meta delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimePlayerSettingsSurfaceBuilder.cs delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimePlayerSettingsSurfaceBuilder.cs.meta delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeRefitTransactions.cs delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeRefitTransactions.cs.meta delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeRenderSplatDocuments.cs delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeRenderSplatDocuments.cs.meta delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeSectorMapSurfaceBuilder.cs delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeSectorMapSurfaceBuilder.cs.meta delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeSensorSimulation.cs delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeSensorSimulation.cs.meta delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeSettingsDocuments.cs delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeSettingsDocuments.cs.meta delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeShipSettingsSurfaceBuilder.cs delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeShipSettingsSurfaceBuilder.cs.meta delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeShotReceipts.cs delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeShotReceipts.cs.meta delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeSnapshotDocuments.cs delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeSnapshotDocuments.cs.meta delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeStarbridgeDocuments.cs delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeStarbridgeDocuments.cs.meta delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeStarbridgePlayerSeatDocuments.cs delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeStarbridgePlayerSeatDocuments.cs.meta delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeStatRecipeCommands.cs delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeStatRecipeCommands.cs.meta delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeStatRecipeSurfaceBuilder.cs delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeStatRecipeSurfaceBuilder.cs.meta delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeStatRecipes.cs delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeStatRecipes.cs.meta delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeStateBoot.cs delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeStateBoot.cs.meta delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeStateBoundary.cs delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeStateBoundary.cs.meta delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeStateRefResolver.cs delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeStateRefResolver.cs.meta delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeSurfaceDocuments.cs delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeSurfaceDocuments.cs.meta delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeSurveySimulation.cs delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeSurveySimulation.cs.meta delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeThermalMedicalSimulation.cs delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeThermalMedicalSimulation.cs.meta delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeThermalSimulation.cs delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeThermalSimulation.cs.meta delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeTractorMechanics.cs delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeTractorMechanics.cs.meta delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeTradeCargoSelectorSurfaceBuilder.cs delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeTradeCargoSelectorSurfaceBuilder.cs.meta delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeTradeInteractionSurfaceBuilder.cs delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeTradeInteractionSurfaceBuilder.cs.meta delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeTradeItemDetailsSurfaceBuilder.cs delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeTradeItemDetailsSurfaceBuilder.cs.meta delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeTradeValuePolicySurfaceBuilder.cs delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeTradeValuePolicySurfaceBuilder.cs.meta delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeTurretControllerSimulation.cs delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeTurretControllerSimulation.cs.meta delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeVerseAuthorityPolicy.cs delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeVerseAuthorityPolicy.cs.meta delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeVerseClient.cs delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeVerseClient.cs.meta delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeVerseDiscovery.cs delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeVerseDiscovery.cs.meta delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeVerseHostCommands.cs delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeVerseHostCommands.cs.meta delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeVerseReplicaBridge.cs delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeVerseReplicaBridge.cs.meta delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeVisibilitySimulation.cs delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeVisibilitySimulation.cs.meta delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeWorldPhysics.cs delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeWorldPhysics.cs.meta delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeZoneDetailsSurfaceBuilder.cs delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeZoneDetailsSurfaceBuilder.cs.meta delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaState.cs delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaState.cs.meta delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaUi.cs delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/AetheriaUi.cs.meta delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/GameCult.Aetheria.State.Unity.asmdef delete mode 100644 Packages/org.gamecult.aetheria.state/Runtime/GameCult.Aetheria.State.Unity.asmdef.meta delete mode 100644 Packages/org.gamecult.aetheria.state/package.json delete mode 100644 Packages/org.gamecult.aetheria.state/package.json.meta delete mode 100644 ProjectSettings/EntitiesClientSettings.asset delete mode 100644 ProjectSettings/ShaderGraphSettings.asset delete mode 100644 ProjectSettings/URPProjectSettings.asset create mode 100644 RethinkDB Queries.txt create mode 100644 UpgradeLog.htm create mode 100644 UserSettings/EditorUserSettings.asset create mode 100644 UserSettings/Layouts/CurrentMaximizeLayout.dwlt create mode 100644 UserSettings/Layouts/default-2021.dwlt create mode 100644 UserSettings/Layouts/default-6000.dwlt create mode 100644 UserSettings/Search.settings delete mode 100644 conformance/eve/README.md delete mode 100644 conformance/eve/aetheria-world-scenario.json delete mode 100644 conformance/eve/aetheria-world-surface.conformance.json delete mode 100644 conformance/eve/aetheria-world-surface.json delete mode 100644 conformance/eve/aetheria.provider-advertisement.json delete mode 100644 conformance/eve/aetheria.unity-playable-world-asset-manifest.json delete mode 100644 conformance/eve/provider-pack.json delete mode 100644 csc.rsp delete mode 100644 docs/aetheria-current-codebase-model.md delete mode 100644 docs/aetheria-verse-client-contract.md delete mode 100644 docs/blender-entity-authoring-v1.md delete mode 100644 docs/cockpit-doctrine-combat.md delete mode 100644 docs/cultmesh-feature-implementation-guide.md delete mode 100644 docs/deterministic-combat-resolution.md delete mode 100644 docs/developer-navigation.md delete mode 100644 docs/game-modes-and-progression.md delete mode 100644 docs/hangar-launcher.md delete mode 100644 docs/original-game-combat-spec.md delete mode 100644 docs/original-game-migration-matrix.md delete mode 100644 docs/original-game-presentation-spec.md delete mode 100644 docs/original-game-rendering-spec.md delete mode 100644 docs/original-game-simulation-spec.md delete mode 100644 docs/original-game-specification.md delete mode 100644 docs/original-game-world-economy-spec.md delete mode 100644 docs/renderless-aetheria-architecture.md delete mode 100644 docs/unity-daemon-development.md delete mode 100644 scripts/run-aetheria-unity.ps1 delete mode 100644 scripts/start-aetheria-daemon-editor.ps1 delete mode 100644 scripts/verify-aetheria-daemon.ps1 delete mode 100644 scripts/verify-aetheria-unity-client.ps1 delete mode 100644 scripts/verify-eve-conformance-pack.mjs delete mode 100644 tools/Aetheria.TransportProbe/Aetheria.TransportProbe.csproj delete mode 100644 tools/Aetheria.TransportProbe/Program.cs delete mode 100644 tools/blender/create_aetheria_entity_fixture.py delete mode 100644 tools/blender/validate_aetheria_entity.py delete mode 100644 tools/clean-ignored-worktree.ps1 diff --git a/.gitignore b/.gitignore index 6c92ebc09..bead77e1d 100644 --- a/.gitignore +++ b/.gitignore @@ -32,10 +32,6 @@ # Visual Studio cache directory .vs/ -# Local agent/editor runtime state -.brokkr/ -.codex/ - # Gradle cache directory .gradle/ @@ -101,39 +97,6 @@ Economy.Server/bin/ Economy.Server/obj/ Economy.Shared/bin/ Economy.Shared/obj/ -Aetheria.State/bin/ -Aetheria.State/obj/ -Aetheria.State.Daemon/bin/ -Aetheria.State.Daemon/obj/ -Aetheria.State.Daemon.Smoke/bin/ -Aetheria.State.Daemon.Smoke/obj/ -Aetheria.State.Import/bin/ -Aetheria.State.Import/obj/ -Aetheria.State.Smoke/bin/ -Aetheria.State.Smoke/obj/ -Aetheria.State.Unity/bin/ -Aetheria.State.Unity/obj/ -Aetheria.State.Unity.Smoke/bin/ -Aetheria.State.Unity.Smoke/obj/ -Aetheria.State.Replica/bin/ -Aetheria.State.Replica/obj/ -Aetheria.State.Verify/bin/ -Aetheria.State.Verify/obj/ -Aetheria.Rts.Web/bin/ -Aetheria.Rts.Web/obj/ -Aetheria.Rts.Web/electron-dist/ -Aetheria.Rts.Web/logs/ -Aetheria.Rts.Web/node_modules/ -Aetheria.Rts.Web/runtime/ -Aetheria.Rts.Web/Aetheria.Rts.Web/logs/ -Aetheria.Rts.Web/*.log -Aetheria.Rts.Web/wwwroot/*.js -Aetheria.State.AuthoritySmoke/bin/ -Aetheria.State.AuthoritySmoke/obj/ -Aetheria.State.Freeze/bin/ -Aetheria.State.Freeze/obj/ -Aetheria.State.Daemon/*.log -*.client-quic-*.pfx # JetBrains Rider .idea folder .idea/ @@ -143,11 +106,4 @@ Aetheria.State.Daemon/*.log Packages/packages-lock.json GameData/PlayerSettings.msgpack -GameData/*.cultmesh/ -GameData/*.cc.records/ -GameData/*.cc.records.*/ -GameData/*.cc.eve.pending/ -GameData/*.cc.before-* -GameData/*.cc.daemon.*.cc -/UserSettings/ Vault/Aetheria/.obsidian/plugins/copilot/data.json diff --git a/.voidbot/state/aetheria.cc b/.voidbot/state/aetheria.cc index 4c7eea6b08df06ae7bf08dc1b6705ef0dba1ba6a..168f00ca4d27a3c5fc6c690ac9b5c7f7534e679b 100644 GIT binary patch delta 1166 zcmY*ZO=uNY6lUg$m};9qBL0V?b`yBMrS z1l_pkqEMN_tfW+0)PhjRq97veqKmdrN^#LZqq}z3){R?xW(+ZJH8bbjbI$jD=ezUi z@7o_tfBU6B+`8I4b-H!p^s$qT)>L!xwzspGTJyK&zxX!?a;++t+gdgdK6<VLSjQCQ(SuD?HzTj7(tm3&M)H;+cOyE>4AMS%7OHUz{(Ib46LY& z-1s_(l}=WlZj2FFSsa2+Qh_IZ2HG4Nsza=oFz!RKHs!&`bzloAp(|U;KzKikd0`G* zMjaKP1I0j@VV&R^$Sz7|{eX5-rY(UYu7j}xNYFVZLEqH@EX)-@y!6Iy{QCwN>o|r; zcv!(#^^(CI)pnCEAf2)d6V`%wZoV88h)xI?YL+RiF)X3ZDpqaf@VsH_PhJtpwOKlX zq(EgX3(?%KLod=XR&0X#j7qhHZ)k%okyi(8vOR}1c_Gf2OU7U&IneLLdtfwO!u?uh9gNGF|MTEUa`d6$36BQu{jrnPy|=Zk m#n?0d+K$a7`G2+D#ocHA^?{1xt+L{~ndkoFp~1w7#s2|`q?nih literal 190733 zcmc${*>hysc_&6q_|0#A@q@z=S4}m0AuibBIO6qJo|A&+ns21TtUKuc-cOnbX@9iUdDu)xhv~4}eE)dd z>UG;Unn&rwOZMGo`rSsFwTIo~5$^k^-@EBQCd2f2kZj|5gMM=@$wuk@=CG3t_7glX z862nmS~eID+i5atwnyD|vzPGSK|k5qzM8ZLowU`=(xln%B;8S#^waLaVQVn#_74(& z#kIjOO*(`2_z18zdGAuPkB?c3_w?}$K#pIMTmA0nq>*;Iqros44F)|tWGU(4?phN- zrO5$Sb=+&7B;BLqUK%~E+s}^E_NdwFrAgL38u#QaNpsjf?2homc$h8;h{wY;ONaLe z9hV0L{}Fypnpx5WBKqllfOLN_yxU6q?Zaf)BvNr@vyb18u@%G%z8ZAf$>1KKeruHW zxe@gbkB8lR%~5(|FiNvO`|-VIZ;XFGBdYK?v74+ z=?^}|1IF^$W^Wf5Pk-><__%|AJC{a(ySlu3wzhn>wz9gna(3<1>9ysR`uVfV-+q_@ z?Zqq3?ya0#TRy+Gdak~@vhwXpa{Yyn>k;SxfB?Qb?J-Dc=rsF(?)*uKWZxrAt%GK= zKl_}tcZn#%#;?nLfA++Xo;r1^8q%LBV;!ey`|!>%ZKvRcEWo+`$0ayF<|X~nj$QcV zfSo>lp&GEC3Sgtd!T8{Cbmu5N8VpYyuK)0rfAJsxuYXwr?Y-=9FdXfr!=r!qrw{e| ztd_VeQ z`Q;sM&EIr8pIrsdt&e-7hI8|~y=E)z1>y3OYhHFD&F;~o|0lofb%5GUv-;?*QF0%gIp{%5$gLnRh@auO z-x>_=f?u<4XPiQm43kdU?D(4?s2~TjCB7gz>K-UvCh5Iy2V9-jlh@Mpm=d51#viHt zBfO9XA{?*;@cW>Z4Y;19ImXTmL7+)C9_}|G2%GyONQKSzpg%Y|sqaFb;Nu|xX!eGH z<0NUN?ZFYQONhAZX?6&)3hqk|2id6p6B8Maer`*~j}(^~{_Xa9Nw<#+$Vts!?<7gz z>SiN+7cJ!bpi0(n_oMB1Rg1qlpgKsX=$n~j0sdYA(C;h-}H zj(lylS=dG2_N?DMK2AqjJ-HcgYI@x44jW1thUfrk3(5zYu@msw*aUdM@e$Mx9!>mH zoGX_5kVW9ukDQW!UEhEb zONWXS{L$UFEe=e+xeE%yX5YuciF)5=NPxRAA)wAdvs0*56fkj9f3FRoO4{hu-s;NQ zndP;W^Yztpr@mdK21=K_Gin}We-SL7_l7EHI<0p_z0> zu#m9(Liu2|W^aIP9^B7dX8wOJlD9_b(MF0L5xYTdZ8wJ$qO^u!DhcYrFUJE|AJLof z)bs$_Ch0@ULyVC1fd!gTz4V~jK1s~841Cx_7qrazXzrSLjl^}_y|&wAhyOQ$q~#0m}PA&7lNOrEd?jU z6W*Uf9;fXw?`w3@QM21i@X1sSQ4*VG$>4q;i$ClhR|(BOzoH^Jhn;q(%Vt%@0bZ!_ z`S7a3gASt|=S!!MEK-?mLqqo_@!iN`k@?%N{I?=m{*hL|TAlVuYcOgEg9OP|Z?}al zK50YCwISWoJ90q~7E3$0yVe`fXHY7B0}r|kO}nieX9Am2&}8S=i!W?-hoi%Wy}m}} zTa&Nqe*}hAo|Mn>;&B$<{NiQaNJ{}bAa5CGL7Zmo!FU9wYeSvsw%c|4*iUFl1Le6Q zC}6&<3KD!TfV`)G;A4Q|fAbO0Xgy{+pzyJmEcQ!9= zT;Ft!@sQu$fe;NZx#~gy?=WAC`tV*iy)PsUDLB)E1H}R!Tl8=IVRYHn{6hnxK7GqU z_{?3rJM_#7uFzt@&?M(3hWA#!4UcPz*2&k~X5*Sy*lj8wQ72D{#JpEFZ!6<3$ii?bY#j4vq z1oL+qH@04BxN2?5+a9_;4)1&ZjkMpP1LZmqhDJx!smS+XS~e4&pZi)uzfQiYT;OTi zC(2ym!jfa0z!C3L9XrZ(#h>x|Yu#3fwtf~i*7aOWXFJ#F;`N~r+IJCjNMOd8)^HD@ zPFP9T+|lr*n7=SlN%&&gc7P(tiS}JX|I-dlHf%Qvw>9_+@Ap0a^%@HFTYv4KH5mB>qD>!^9q zrHujALmU!Wxsw8I0^7T9?D&r`bJqlxHh>&=VWGgiuVp7ti$~6nv$t(c>;iBgEoLtD z))pOxV`yuE4n`B?61T*i8+UhXd%j+P0an5xprB2Oovr~SGHOcIwHQ1v#qiiW8)nHT z8mCg+h*3{~NdQ6#`6m4Vzb63~NJhg)MR_?JIp;PkC?!rNMDh44&|_F~LI<|w$un3mc-Xd!Nm;a>?d zFz_xN$?&AQd<1)tV=JY^Wr{&nzM`Cy46bh;_<~)**jt-y3joOAhFfR!%;0!d#g&B= z^~C`W0pkYlcTO#@eN(PbcBJ=^zJ(|ksFDUDHFP?nZKv8eYr>bRK_`PAMh!$HOi>yn zzper#Y-A-8p4p;h9ITLgD?uQJup~jSm7cG9!F7)#G_HwZ96n#BNsai&w3T1je2bnk zVhxdxt>!iZ8^Lo|m)qTU#y`xPtbnGzdo;M4&eMP|%2mmI#4dnoRkO4P+@+X`d)iF} z>MLp9;>TIkcfo=q+;S&1z*3}U0Zk2F#*arU%k^b_y>4~!b*7me-II#=6!qdYtQqp6 zu>nVc@o5$J4E=)I9LXnuDR$gf6xBsTfEtOIYA{YY==0qprEMGXVp%?>QnrR42F-@?_qWCgy`w&QFCF*E}0=j@2v z!nf1Fe11#unRXEx#xBJ6d$!>#lp5UW){ zY)a;W1PWQxeLEfQK_9#c<9Tp@`g_;(F1H#Rkk=zb`3L)OQNn`~a4=mKjXxGad9ezV z!nWk>CgyBi)LFfm9d*+EN_qKJH+C<>R^U}R^OiPa@mLqaGw@sr0k5?kV-8q7qV8!A zg`k&EXYL)-yUrczLKN(`U-_>^R{q-D%Qe1Ay+sL$=w`@DKhWsT9Y%M8skUM2fm@bX zXiDr=d5)Ucraaw90>Sp0V}uxCIf>(EJW|^{?j9q2aB_)xxi!R@Ip2U3U zgiZ(P%C0!6cnCx0dQ7-;;Q){WfD1Y`V!-rzoX48IyyY%%bdcsV!CUSiw(d=VaJEOqZ;5v%Ent?S#~ zR)O`esGunB+@}m$YLkviBf?sws*al@gwmkA5y3*d3?cjsViShzKHLcS(Lu$3f80g* zr{GdpfXWxy8Xq0IdGR%)jn=r^>jaNq&ASNo(~qoCGB3?WGwi87jYQy z)c~oSVP9=LNuP)iBkDT<7b)0y)j6N54fMPL2{ly$Q7xf`0KMtv5OQG1KljENn0E=A zOq+;*sfENejv34da77U+`qB6$J%Wrvw#X>r{2bmYVrL;hw_{YCs;|^fQ|Lw6G>7x z*?@d{cu?!Z#3zcs2#EI~bWD9lx;(JBnnwYX8eej2_0sTQ&|htsl-x15%Sw8j#wRoA@rB|& zPLBo2!>E&azZ9Ep*ShpOHBYeg2Q`(9K}XI2=$i{m7bh6&N6Y$0MAfIB3& zJeCN!+nFs_g}J5f-&|IgV3$b^|8ApYnaTodz+yo`0Hcg4Qc>b!8|+ZqIZrcnP%AhZ zi-G(knMssdTI4f!Hm!Sff#TDai-Ys7AtPlIC-lqQbB~ zr>-5T43!ZfFX%bJJSSAl{}~`>N}=#uTZ}Zo%p5VHxZUihz32=1JOh@i`Mq)}++5gP z9)kdt4UZ6*L2dx-$s{9kUWkc={FSGtoC-^@eH%_|!kQ_1==`vQC_Exs!8xzM&ZWY2 z*Wh)-?xJA`rw^tx)~FKW5E_jMz5QlOYy+7_>BIjsnPvtnKhAFf zO&lzrQW6qc2t*nNz!NfJs_s%M~?$bB|1`Y7s{ouT*lz+ODels za8>MH{G1`l!9e$?dasCVQAWSeF9IG>W5zNCgHSl4W~CDL3X?E3kuaGpu-k`ki=`mo zz)G-R=~Dsb-SKfP&f)*9{$k7Qj0Sj)#47BK44UCNDsv2e$_YLMa>HH+RvKzDy=Sh`b-C*JIeqk64 z`UtC9Zh!^1Qp%jbCfM)7MOPhkQco;w9v9wk zQ3;S55{IaE!rK|D7D*OrMRFJ^Osq0v8~ikY>cV#J;&!iP866H;#fd_h6NF337x76} z&jRw00u*>368iGD)pa#q;`7NK4OsY`r~yWqj$g&a5hpFkSL9-0earsA5!xT4@BspV z#f@s!;Zpd4zyq9e3ry#a(Fp4jOg|JE;=gzw!Uh=4B&?qlMF}*JJ!^^!aSAjrme=I` zzXUwIn#oRsRe_SSMk(;2#YV%%g6blrbNC$g0gH%G$B1H9+(jry5}|`_1M5M9dZxC+ z%_tY4s;ehfX>;6^XtGs5qU@*FJVtqsqLQ${byl4k9#L2T0&$@l93|Mf(bC47I?0X0Wv(k84U<4DihtV8N)H#jcO%VmT_f{Owo%8#P0gN-92B zNDJpu_C56~@`Yq%>8zbp{a)51!rtGL(&fv8&dF;jq7Y#5>j?e!5B8wJE>R=0@F?w# z_^X7Z?9ykij=P=b%py5m0=E!Ew|yujZ>AKVJP$JzS(}b!j*-M*fRi~!O>;t)ky!AZ zzWQ%Vwtq2+{UdmSArSkWU2Hn)Yo#nmq*aKRAA%aVV~oX$?T@-QI2wrZB20?1OiBqY z(avjp6~BcT!s0Rg0${f&?XV~>bd~A`m^xO>$_i0f)&`!qGRVy(MG(EJ1~kjE#gUuZ zr%F2mdv=Q|9Mw8$+Sk9$3%J%ifg|X)V>vf>jJnuyYI~`VusvCa289Gfts2a0f~lgQ zq$dd&FN2c7KU8JvEw`XmKyD32Y{b2U8bdb7;|wpA;IE>rO_8ma(KmvJ_eZ8u2f>pm zFXbDyilpVY0Qh5;dBY|LE8}q*j%=ccEhNwl}i;zI{-h*bZ`4&nzo5wXtvQB&X#Rc}m zHUQh%xHLkAK2#)<@>3Z8dy+jYiSTfnv@tmE={{77b)gxEUDznl)^L-{HDeo(2e-Xs zU3UNkHoZ(?O9BgUn4CADl6R~)M9YVB15H8-`K9*wYiKihVeciArC*-~DY+lGD!9jG z@_s(P2>Ynz%vI3Y8=s%|#yM&G+kBy0AsRZ)8n7D&Jr*!ly%=IbtvwYLL(67%@ti<- zJ#?y!j~zty6N?1~1%^{0FpbK-t3RWxZ-6p^aevTmkB_@3mdW5~A*n%kyEfS0hn`W( z7II9&E0z&J4(Sk9poTe2z)4gC$yFCs@Mh@C7GaznKPYeA_ByojAk=$n@kf(MA%0)q zvWNkSD-qA2tTLNee-^GUTw~aKP9TFWT&%g`N00(RJndYHS^-5fNqjI8^nwSL-_dxW z{8xX12A)YXy+V?78z^%-zNPYzt*=0ff6lk91j|sZY1}L~f8B5wm zvuG3|dIhfe#;|E+#gDAgYsSkYjgM+zVk&FEMrbvsqT;;%%o2X-CF^w6_dH}#^r#HR zRq*OfVO67~m#P@AuyS`9vc?`AxzRd`%7<`c@@Qw7pib$9jAA|}G*)Re12qveheL@v zP1}|8dv*budWY!nN<5ko+u(lHADrteALEKP)-cp3KwHJBcBcE=1d9*u+&5GSiM435w^ z_!&4vU-EtZeQU#7t{9ns+0Vxz7^Pk*4OJIYPdl)hrMD6w$yb%j2tP#pZL;y}4RaKj zxn=(*x|_?+auMPtNC#OVyGZ?XSt)xk;kio7qhYpQQa6RoHS}Kf=5~V9p5PuW3}c<5vIyNOAgwQ0#9ZD+g#$f{W;3 zT7H;R5H`Tj$DqZN2GS3Q1C+X-9JSEg7p2r?b+Id)crwwbuLvYiXLzq^PF7Q=<*EmH z;QS?QR`I4hktKhEWGk+a!Wp$qv}~KvD-B~AJg6W@STxG47M7rYi${#E=abfeqaENG z@h6}pLqQnXaXyUetXWd@HLQ@wxF$eb%=_gA7&P>k(0RV_IB zF+alnlE??E`06r>2NSWY>7;FKBV({mSeWsC8vw9jRBuYz#L8dw7GhTeSaT z=qo@4Ltl74XZh2bfvD6+y(_S>IY5Lh?N+u24>flo7`~Jx7vCVYG%TW#XbTvb*AkOFWP^g{)>mfIYuWO^K-^6@_vT{!%)Z4u=TH8Uk+^7%*Cr z8uCmMNlYfdd4pQ~qzsRKYXIT)q+SixIfV)>u;P(acp8vA*cf;hCPuU5RO6I^_#6mR zHNK>R6AAsm$%Ah(8u8s$?jsifH4WEjVbSfejD$C^viK2 zx0{}+Lr+z&<2|+$WOD=Mv!OUxH;GCajRvHnm6bDea^0Ck?qp>ZHl+9?_&}=yU(f{x zQZOsS_H7ya7Hv5EHr7J`iv^{;7=zxb-oAihv~wRcqJTw14S(*N?3!o|R8k*+Hv%9A z=NnJRi6DFwcESFl5tf9~DPe_oV1A@-Ui{pc6S4b1vu6y5v_JvT^JXxmSOi+LTBeEU zrhf^q&W% z;PnFvf~on(FS?|s;w4otpn5n622q_px3+q|zOr0B9OMkASuHQ)zxvAQWiQQnIv)=b zYDPW~L5Y|t3I#uh7S(!)kdgbOl=Asli$y$l_t992W9+_)f}8NjkeA8c&qE5INXZL@ zt`_io;sBV*^toDmDC0ot%xZkPAU8-WGx=S#Wmw3Fdw{$?jKZe?hmT2+=kST@d5vk_L@=lIm9$0 z%(Z;#+Ivtu`dEyq>K@l9=s$zohR7MKVk{2z87LbetQthS#jqYCnl53a_jQ3-aQ3l# zlvWPp$npTv$9kz>^|Aksr^1hp3Shn-)GB`BV5iy`z008ykX1cC>*b6v<@~yrxwBs0 zxQ<$QDbhh;zIl9zks^5pScBqZ?>vhzFX<>0>y;r7ca(CAo+*>O;srHJspp&mFXm(P zXa`J!CW*`B2z#M{p5WG&UPV`&mFZTniQU9%7Yv(LC!!=?0?gtud=$ zBrt>P4&Z%rRu7&L-NtE=(JRn3CqSw(inRiD@F%pA zDG>0*tlh6HRz9=0y1aJg)Y|fy`k6D!mBq@qWM%F28T4h;&z?W67SUgQ;)-w$bzLwN zei1$gG?A}<9;~Ic$Xfa=Tr50~+bis!{2~`vksdzdPhjAJL0QzPYPBm29E2>G z4s#t0zjTefbIF`*72pWEh&c}($VeX)PxpX)%ql8;gECI%E-RChY9=kVSCS5+*#46` z1`;v^!T57%w`)-=Bpj;=RIRB4qCNxTPkOV=sgas>j$yL^RrFkxZ-MRFqaI2cx4cqm zLL8eYiSE0e9<|`a!xmdg?!yW>WFIx=jfAhE>!DpCuZ3Pcxf84r7*#ryP=_Xk`IWyh zuqUvmoP{8o&Qoct7Ev9qmSK`vS{dcK8iqC;2m&!0@={;=bQz$*X0Wb}8W|RWybQq8 zIss)OLjpzy$Q19Wh(I5cwC-|y$mG^CPm2Oll0%HKEUB)pQvUzGsQm9o)B(E&s!9{O zttK=Hwk;UkiP+CVIXMw6A$yk|SMFoLH=er+Vdq02kNaUPeK4QAow_tz9f(uOY7~A0hqyM8ybjHxel#Ekjak&jG`^( z*;vG>)%!SjE1FC{R8A`ASz!%q2Sql!H?C|YtZylgJgbG-&x!E$@5F$4PMZi9+4L_lAfDs4Jev==F!u9^~}r%P+tDo0rAJ zhX3F1phgP0LDtYrN=)b~uRv5vMhHV8sz>jOtgosFC5&x!4xU>(bD@6j%$aHS_^Gus zXV*@jt)Dq(&4d5;=WY=49c8#j|JfrQalvlbwyCqd-`Jaf+cc%lb=elTtEG4nU*eQ8t;;RLi(z}jFi z4Q-ULjlJH8Hmj^V4iB|3pB&RElRn~?=6_i}&{J-)x^v*n zYCLHv_+93d(5vL&z~1cRSP7V`fQbPJPur=7Y!&MvWG3zZD3If99x=(Yl(R{!Tu7o? z@??ZdxJDR|69mn$zu?7T*BA@Oi(y@e!@#^2LpEYW3I-F*Z4hb}F=PYqz9y z9ZS-YH6^G+zAqs_WMrEt6tfgtCyY8^?PhQw{*OKD=F(swd%75>W0*+buNxaS7~lW{ zQ!jv5y{Aub=9{1l>(#2b^ax}EKS4+BTJIPlJTm8>UIk(SUARS%qb##xCoWqDL;Mg_ zg29O(p&=a$I!}yRhV=+-Rk;wqx`7gtumU+t#AiCoByMpMfNYkhI!TomMnUbkw@WQr zW}#Kd$qNW1C>^EXNb^lE9A2UfF4PY!vx|*%27gpDpmek1aN=*j(pGcl+LKfeBfLrR zj@6(j(uMRpOnn61bRE#jcsyJiB8U)dJVIay@T6pbQ5z;@L7i*>g#TtS-gO^0r|e<6 zQfFDL$}VFRiw@0DLXPF66fE^cc}R&re?^XkxEx2}juDcQtIwHS$3K@BCRQMK0d4!&6= z{xM(1WobdJ#?l4Y#uD8&GtF8+8Mmxs8|uE1hA{{smTn)1dW8A*P!0#my1OE($Dv85 zNNca}3ERX((;Q(eIHA$Z7X_^vy!?w?$a&n5w0Mi}DrP$4J;K*YZ@(&zQYlAIi!fZ_ z+c6y{J`hBor~`Tf>9ARlvWHc>(~%&cQL5~CX5$b zz>|A=>+A{!)FuzfjneBG$lscerF)&r5{Aua&r^P+4vO zL(H3i@Fi_QmE_;TILkW^i(LLJuhje|^1vIc-cy2&gbq31@cPUwX!s~fifWuK-{c%K zzXqxoW)Rgi^u}#7}js~YtLz|?Iq-^SC&s}p}5)E47#`3 z z-R|vA$(OwrT_dGVnldT&yciTv*2wCMH5e*lpu?Flq2M#H99VJ5bV()2_Y8+BLJ$GL%s&rL7)#+{O-`)WbgCkba2{>@!SW{5L*b0ujS3jA# zCniFb{(~gbNnjYH5-o6o1pnDu9bYYN4abP>GByyr&K$Qq8H+@NrVCm127)!H#@&_) zLoQk?dmTO0H8BROyzQ^Z6*3dKgYXMH8rZ0Mlu?e#E(Mz?D4Mxa_$BZ#L4nJ0lLLeB z|0E|nFuRhEMKBLUmAgb`i2mg(&5Zj@LtC_H+&K*$06Qd%H_-fsR;IyPp+wY_>!fzK z%{MOaawAFya-8?X@x>zimC?SWS?<>++-h)m2$aBwMuomdN3y%!V~8TgJ;ZiLg^I)t zL|%cyO$01gA)2%_bB~;?B}Mbgd^~Hf|W+{aNZ8*TP2vW3>~Y< zEh)#>BfMh73Q}`@XpqZX?TXfLu%xB(A1?#)28v*~gy??SUP3jd38Bq7J3`h&3WkyB zXV@;G!o5FcQ9EiiY_KcZMrYD9BVIIyY?%M$=5QB5@M(f|8=G$x!It_(o#oK0gp7TJ zod5(UVM1{A;JLHoKN4qw2Mvw&Dgpqfy>e><#}DyzUzi0p&14#Am&+Hjj$a7V_F5^R zFrUDyBom(e>1e7PWM2m68Y;Rr^_-Gv0Br?;qKiSBlo%Xk!LsS33*ismz#+t@vUQj3 zS8Qe8z`isEN!L+k1p~|mrSa1=h%)GnU5k~j%h1FUaqV*cfc_#%d_Y+)rrD7M1rE`+ z-ILaBL?iJZ3IL?Jf+Q=FMjpV^bV@@i%h`*9$vjPO7R}-drdJ_f7`J6?6=5bE1w77b z%Hat6F`lE7zhUzeDrg&{u`fDI^v9*-QT0Run5vRA$`a(Dcwocz=3Bd%p8rkNb%pP~ zWe`0*%W_xcFl{F{)>%%EdCq|yH9CVT!+wUU=;YG&Rv0!w>}U{+h6Wf@3t|n5`G;=J z24!bcdI!>ufP@PFI`N*}+!vikGSd4^)tW32_-GtdC}sl;Uc#jMEf9{WL4AJEqh=0G zx4&+LOQ|WO;e}u#b;Ofw4-Sx!$PL3BK2+{OiRV{pki(@$r6wpqd)Zor7wb9x7VW6e za~uSf%QS)!zNG6M+L8(I&|=8pid=CyyiYi#LxggO1)b(zPfY2^dE(Nz(;aX@P_c3( zW^($x?XtRaUv_MuY#Usr^&hNLNo!;*BMQYB>+=X)= zYlY6Vcex*VME~WdO*$-qGH;yfg!5BdY-o=}%hI&#(~N7o~dqaG!uD-WLuR)s>AHRy@L*td8> zFJ!x?_`lnFeF#9nElT$}N{#6&5%El*KQ^qDhGv0Dxl%+U%ZC}sF7fGd^zSJGAyzHj4H> zpV<@QBGVw+-1LLKx>P~=d~}xVdlWSp$$;l4nd|pxTM? zU}p%BdY5Bn*w#=ncrH(|n2h_7 zL434D7SB|-!}C1GO!|Y4w5bL-GX~N*o|R|dd}FR{%+lRV8F0$;Pvz~G!|_vZ$YYKduL!L(HyqqDKW zL$h$WN9d{PG;xL~XKFyF!xF{;&qO5s;e+PeETGF^i!+r;*CAn|d&w0FoCu7i4v!#3 zs4aaUDl1k&pc&X-)=S}vdBH(Al(n}w0DRgi8{rCo$+{G!O_7`=3M0}|R^>@Y{9W6S zELgtVj5}s<>bxc%h7%hPmXe(&G_L%$ivtt+4YeD^gj=b$=R04L*zsaMM_7^DIa-XV zC=60y?IZTs8_kGE@SlLk5SV{f1R9Vxo&-pXyLpha8o-jE6p(jj{H!KgPL6d=OnwUDJ30{otsD~A+im!<-RGR?Mb$WG-;}WK;sWEd8 zTegJ-tf~e>Uj}^{@FS2#%ImR@<*k`e(ER*0*4=d9R+qSw(g?wXr1D9j5L)oo!?qpU zoJF2>e|P!;z=QbVO9`T%Ak~cZ4G`-3`uqsJB_`V-fI)I7=wnHtNFa%R@)n+ig?z#> z%Ge!Otz?Ed{y

bu}{xOIOCTF_-5qjZdz@jLqJ2{X$< zwS_?Z{PRq6(D>+?3hkwE1+@!@xH)8HsQ9@WqS;fx-|1lTX$V5RBz*5x;8!2re zQTWwZq1L$|Oob^p*bA_Et{){%k0ZGakg{H$0Aw0bTtb-&4j&ci)nu0*Ds2(W!zq6L z{+vK)>k<~nioj$5oT)lyBn+)pKe?{rs$7Ft#>F>Gs?jBul8-D|Ia_O;prt>=5F`Hy z!T*7SB;9QW(aY5k6+0ISc;dR|)Iz-Cl$C=*0~tYXWoTf?uoD{Xym`S%H->Q`ti2(l z!pc~yo&$PWF6P`)Zw+GmCCut5Lfa*8!_f%k*9DLbdu!F$D-2S8{4u3Btjcb3OzB|Q z!i=!uj8X^(4jkZsWiuxlrpf_6yb=TTQZ=Z>jrM(8{G9OX3M-tuxb@ITlAeiGo82Vj z5DBmdz#RQi{+Sj5`8t;uSE@lRU^Os}+3bs2XP4a`tQ=fh#;>HxjEhS7v4ct=gEjR9 zhE>lrf{W4nZ&)-cIMr%bCgNIoGn9Ix#U+_fYBX_nmgfss{z&_X4x2OJwiSc2?gVVzxVHQ}ruIsk1SqNr<$wF%vp94?JTU{!fg zLlW(;8v_-J`0A)&0f+?yQ;JzbdTa*iIKrnXH__A4p_2+Qf}-8zT+zQyKQf(2f=peS2!LQvwE%c<0dqfG3I!b_v% zo{A`u|2q1L^-gxzNn>+=9~lIRAjOh)L5%FpIauKj{es5=PH$?w5mc?nsD^H-1UU*? zVNszXs%P5XozpOS0Zz$Z2;!tgg!q{f_b*RfC^%I)>r#t^@cfjJK(W$9RgqeIe1AS= z5r&7ItB2ePRFRt>;_FBgr)_+R#VX5agKw}a6UXMbfG@7I%}dfuGXKWfVc@1%0Vk_? zm;jp53sYHR7{J(+THJY=|6S%2U?#9zc7O@*Jhv4eY16Y#4yBkOlu!i;99UFrN@x;R zpcXWk>a;5OqF3V4>--Q=)4<%;$zca&r~Z|7_u)cJJCwlC{v07|l83tsp=?iWNczA) ze5xwv$YcZ_6Q?1{V64HskmltAsSiUtI_<>j-tgkQk>7sh4UN(LZl;c3vBAdTrOAwO zo9eG+!O_(5s9`KvXw26{O`_i{%=$G4c5))QxVw)({CHyY^{E(kW|r_Pb z-AIfXf+K_3F7#6qV|x1%8ZQIzGabr@hzE*HtOCU-z8}D8P$?el4$-7uF5lfLnCl_}t`M50rA2+p9_KnSp zVJ)|$KM>j1UT6=K!Go}Mp0ahyQP9}wVh?QTBXk03q7s*n&c~Z6V=f%k2APGerz=P=Vo+^}wLSV4M?kBWQ-3~L^KzqYt4bU6 z6r#%>g@I$>^S=uxNB7@baS;s>7-+E$0DQu%(170JD>4w}~2I^cQFyrr=F) z64ahf=n^|pR!^+(qA3e%NpX*Ekzw>~6-LDXnm7`i#t|Dq=HT_5KGCpe=!M4B16=K3 z3rNa<`1mx7Ux7%g>0#DR-mr1#)~Cyw9puKiqY&(ZJq{L@Au4FF)Z=~%8_Gdvvk)@W zf*UEd_ainy<~AbjcZrZkOTfr3;xxVV)ojpzu!nIXdxJ~EVe{kxs@>T``as)FuYigd zK@z)3IjvAje}E*bL?pggycEv35FBDW2nzruE|Sap=ciMRawB?b*a1761D}Xe9$f-r zd{Vv~BeG`VwNgekruA7&DAaxF3nt4UV~e6&@ZYqKLmY4F$Cz7s5J5^btAYzTxF2^~ zz&r11gawZ{Xy0R4{~U+6{6tl%(f89ORZW#Nsf&b=CQyU+Ms_r{7-GcYkQ`KN!lvr@ zm?He7^g*r0vgL@z%n3M3o16=T@jfh7fIoo9aJM76L*e`cySpD9=~E5R{|L@U=fXWW z1m1dLrz}7~Vgr^+-`1@$B?S8=11`fm<6Io}n=0yAtpN8EAfDqfFvcV-JMp1le)tz< zmwHlA=nDY<-vRhT?^2ZBp$6Yd?XTycB1_8XWALh+#`tP$J0a8;Rk6yw!THV8g;kF* zTEN*`Ys}Ua55K5yaL9rL-7zb8^=xhVY;EQA4AFA8aI_1Q{TR}gI$uOD5Brg^4e~^8 zG$wS3DFCAzbsOZaEsWg2NVxpPp@k}lX=KE6b=v^ovR)YPVWy^7dXEi!Ybx;2Q8AkJ z^r-lSl%fJf1h+;Is*a2Lx-uST4Xu?71I<5|kv+kFSM)luRYaMA>>-?{_G46^{o{K- z9UMvNp3DX3tXwBYRyH9-bRq!VWQ@q_*i;TFJ&`s)CV{iHWLaPT8{a@qB*G|_CQh9z zzp&ae+MBvxvK7(ul*rJIM_G?Fo5v`#iG7y=egsQeKPhp2gm?_y9cN=B^ z=W?@u_j?@U)`Y^w@DpkgcwTzYNkhAlI$VX!L(}lf7o5EHqPG7lEe3T6=!FvKyDPdt zqfmk>e9K1HS}({Cz2s* z2wDW0j$ym7*8FTzIKD8+Llx>(NGmOm%3D?*l)nf)EG55UF){YMi!oi-`d2HC_*CI# z)cMueFmI57lZ<6c*<$YtXBou71|2V?qrW6O9BSW}6>fLn@@76s6lBGCBRV!(X6WRV$csHp}G%{__J zj8ut!;>;IGjAX@$@a63hk&K$}Le9e?e$~I(-YmpSBbGT^50_Gr7nHjpgCLMh(hw5U zWLBQ`iVhf1QcxV^92W~*kFEQTfVFh43>&GSzuDYT@Y5IDkrDW3qbuqOmBKIZVM_^T zNyy3VTV`dv^pcmeXy4Wr2-3Un<<(*`X(_GpH*6#Y&uWjwmNGNKmT++f#ml`C2SPFG ze+GiTzP@(*7Dz0+eI3!Q+h~xW;cy#uK+WFmJv2+^2ZoN}RGq*8hd8datenLm&uXh7 zIP;!fh*(Xq7;YNaTOALj%6xl`iVmxR1#l8ees<+J7M5Q7BkVl>d-mPzrVeA-d`y!3#HJqyZWpWxCBi zO+UiQ>TfGLV`*7Kgc%#Zl|ICDHOSvIF$3-?o)ud2XkeL>g@;3BrO9OZ(xd%T58M6C zWfsN7+$c<1l*;YLhDf$lMSdBs=%=~RJ+i1>S&(pJAi}P~ zRqmBR)^F!R`wy`oyr<>82z|!-ZEvm$DZg9MWl5VEN5__}ESw&zF?>yv6` z%BPd!9HXJ0T~nr(M=wJIPCQlzDs;4U#g%yRX+>prXA)K!m2p`+AGe@Yi(yU=10XOc zlQ$85U`I3oCo>^sY=eh%Db+IfJYZ+eMU5fMJ)`lh0Rm~dL9uD5Xpwk$5$G?-4fZw) za%ksl7B@_|8*|yf>6@g55DB~d5%LNq0E7!0n`BZ{IJna2<@?x!Vh-V1;`F%(cSOn) zky%bB4>0=&)VJe_8N1fS;*RE`cvI<;+_17UOI6@$zAxzZttskI4iuKnVcaJjHpG6y zPz9e<5dz6f9&tjvMnry>hi{j8qL|8PDzE3F?2E}6(`0^vE_;$CXfQi@_2N_!V*yYX zF^G943=lKEvX-nIt(`vQ22mGu!F|jNqFq)z@ZRl;pYl$z$sa>vLtN$HE6k#YW}pYT zr)VQtJ+!vx^<>C{Yk8}812K3zhh7zk5+yV-oqS2VWz6lFeh{Th^$NUR9W{PkNyR)% z4@{Ufi--a@GlZ_=ouka&gTXj#nlGJ=+ig@98S&r44&rI`F>P(Y0He{Gg&*W!_s0k6 zsE+d^c);Uee^mP;W^x6h&He$}b?aij`;Gd8sOiEFi?R0sB(Jct6$~_FSCP5$SgD0i znB8qa#A9X}VrX1_$OE&Skx@Yk@K<+l-e}}q$TSbyDGESr{g?YxqP(as)1%RnBqmPQ zKuEv4k06G$Kj&W?R9UGtx_Z};uG5ETRj64{!17%X7W+66+?T|mLZI^KySmj+EES|_ znqY=4okL?kyd(#&BdCKz4iJ@$H`U#5TRyNYb1kX$q{0JMGbv5UkeN%WmJB0fafA-- z#3Z80i>Ts~fGsX^EGOe*oGXg0Xdm(j>H9#8pIl%&bq$p29@B%M(I4y$T;x2$bW7DmMdEsGq z|E?_FIxx<8RFc`Glp9i`SGG(qg5Oxk-{T1;O$=9tyvbdi-4pa;hcS1UFrig(Bp3{a z%z)Rdxle(s)gm5P*C_4QH5^5fjTj4|ZiGny!BzAk(N$~2&n&A%SfH80I9(A)kc2}S zQN)paC_sgfoJHqR2ihCN?0f{`-;WD0%FzXXQra+C()9g(?*{f#h#_|+Q24++Ce9>>nn3-~5frdA z#z&PnPM?pvrH#qWqZY!HL>!`9I5bMU8maV_6XR_=U%+;n3ISkSok(*$*c+apMH$Dm zh~bFec0}C~joYv{x^KEQBj9Uc*#MM71DlFvOsc{&pH0;~~bo!X!O{5XdRjm;Ex9 zUA$BnGEC}_K@5-=1!$tj%vw0}`SYHQohe>2?M0^8j5%lWqhSH+7{vmLx%jC@zTcf6 zN3Gw!?IdAwT-7~OzMdPCd~`;&k42us34o-4>BT$-@4**=S0s??(FvfV-{wMI)YIPP z;YI&tEXk9nyzlXYr^R=_4uVBJ{WH;FNP(MPEElT>j<)TKmYmd36#qOH{)~`tIo|N- zX%Md;y(hQTq-0GdF+ho^ptr5LYcRf*5!fx|8~9H19Ll8k+(1VP-mj?DWdVIYBXNh@xW^z!oW4B1zu<@a6l4*v%r&< zqqr|>sQt`%Vq_j3 zUXAhKv6cP+E}opR$ z7?H`W0PKT?`9K9z(_`!v0WdnZDkqBZlV^1ze{C0&${_w6B{-&?BgAyolD4a+tIxom zqypVAM6yGtvVY?}!u}b#!=eA3cf9&Hh+Xm2*g<{?W@e%x_~cp_iqo%p!@k64S|FfU z{{^?_vY~v55b}w0V!!2qH(PqyHArzoR2f9b-P&)IV!q zwtoUXSs3I^)cSQbPItbTBofFgQ2NhK(NAlGZ?PUn4ZZRtte=AGF(*p%DmAsOgMa?I z+!GJPvIC}pJZ%JS=`rLKi8d?C$K669BIrlPq82nQV3OWWswl!2Su&3WEcsx!||?X8?ab_2(r*Dsu1sXX)i?B4R3wdM0`r&sIC zE2qBw&Zv2i{q`$Ac-XXc{RlqY!!`!y9HDRZJ^mqm93My|O56HArWXUyYI=HX#X;tu zdqyYsP(JL53tBrKV{XqUQO6)h4Sb@#bEw?X-nq}idl8k<^}avW_zjMW{-g^D?`0p@ zrw{yXozdWZM)vh3AL0D={QV!xO(DkqiGkxsZhmH&+*)&tQI$A`@Z@tl=A~wZ{&>+x zk}UKz)k8j!1u~zyJ>^!!LnK5xX>Htn4*_K8GW_*Z_L;H3Uf*{`mlj4VHgn zT_+!Da|@=96P||$>EMV34Ik*UgrN5C7^Zx(}^_J^RRt)ob4$r{nY^f4J=NhY%`~FND;{egVnC zxd4=TxY+YwA1RjXKHvX{o&AY~Bm0@H-ZlWs2y0UnGHG^_7f0yk3!mrb-#%xJ$M`_v4(@Wm%; zm4we9puh&vZ81RgkHBEVldaC9Q)sOljzG|;tgX4?teJAK+w+1?m^CO% z5_9}R(?>Y?w|Vqql`?-Uzx+6p@c-X*JHMRrz<1?Pz+ZYG%*qqj?RCNWxwkAkdqQic6nV{;}wGvEQXMz(7?`HLPp{u$_kN$(d?-@6d!joBng1pCllu{wZ79VA# zDn`SfU)&!h!Ix3%5$#|=j?OhU^n;$K#j$oFs z#(VS&?(pP`K;*k6BA=i@UwS~4;83VuC@%aSsL71{`ZPG>uv_D@hux+Dxan6I?yG21 z_zd0#w$np^zO7o>Q=C8a=ZeT$08r{jnr61wXi`wZ9U()GY7+c=TtCt*bp-;i?V)#P zTW?9O>n$L~{AA^TASG|sd5C;V0{aKju2*>?a5K)WojJR9=6rp5_1rX1B)&vEk-rFj zNNghJGTFg`ej?FCGWnSqGTAK?XlEs)}?O= zae=~#k*NyivN1Y-ft2FBZJ1jtYO)3y~98v{H3T`p@ejrIsi z!kZ~Wk(xn>GksX#gm)4If+PM1y3E~AXN@WmkK~abE|CkoBOdF^1t?v@=|?7#b-2mk zm}YRZS0z^e`icsFCkcNwPSCrX9F5WPCFLO5q03%5G+K7SrndG_j(s;xkA1QPk4gr2 zm_LLA;$UM)^Z>dZhoVwdEs31PMNOUvi^MwWQB+HXrRUrm8yujDgQPe^{@>JV z!$E2}Ks@ z0f!F-1fe`cMFF#WuW>rN=j_JFLbqQ1I{vpRZ zLC6HVkR<}G!LYBQk2i}fR4T$( z4<>;}9<;z;P1Y8itiA>DN!fv@m?F7=1-`jO~|;ri~Ihh3cB<=%v-8M-7IF5tjxmPXKdz&JZ8 z4Uj+=S~h(tvTi*XKo*p+MPCd}E|UH>TPveQj1DoV6goomXp7|1>*2g2Tm(%rh?E4j z0)x3>9=#kKf}h4%!a;68lR`y2WL6aj5l_9CM*>mc+fNb05c~l=L0DL-9L{9xg_QRtB(=f#0J`Te?XtvQa*|qaBA`o-F00aOKg2RLxD8Zr z*OV3B1{oM2gw8jm#o9A8b#gADLeEU6p)DsWM|HFzWvu6t7j%$IT({F=Pv9@rfPI#{ zU-kkD)-`F@h5rW{+$fs=C%mF46i^V=8SIm}?6`Vm9wAPdso>6{;)NXQp+;p;us_0g zKpn`4r8lH>;+)dv=y(io62xs9ZMkX*0+SsQ-{6NGi`LFu%1yrtE#l!+7CDSX!tN^N z2nR+znOqYO*kp-`oIs`q;7S#M^F6IfWbbaFNNXU3Zh>8}+7ZO1wNNp!>ZQ~gp0=gr z4!Q$w(<{B^>3kKG6O<71C#Bd-bneDi%id8_xJeiE#A9g)CEH4rP5>Je$xJA}FSHU# z6A{X}=8*`_B#DxPN#_(LLXHiD6cATXD?LBcE|7XqKSfXSFLXt9gc7>@Cu>^IR5H%$ zV!K2mh)ku+J(hTLE&b^AJ%LzlbG-tr#Hx9SVC__HcEpe`N~WOya#JEXKtR|BAN;;S zs3Fu)g`STong!IxEh&`W z#ZoduyaDG^Y+9x*&#j$ash>W5eoC+amuNiU)cG@-w*0G)M1Z(;`t#s=tqCP+veJUP zYO}sC(w}lY`)p#9D)N|-5xnBBQFV#{gjjKA`HE3dqy4*lcIDLaxz#hv7rrzb_`gsR z5rCD)a-)JW!3{EZLH(ppRUWi1R6?jp6(S4pM~MhHr9H72?xV0N7ad|^GD)}=nbAcP zT1{YMpE~M3@E$ZQnRVlbBeB1gQ1ZH9>_|^W?}VmC6UGS3;#qzq8aFq4a2HE$S&{4U z=>L^((V{=z3~O@CWG$|vNfR4hZtK8ar+7UYWFyR5itHCNdqTHzq*j`(wtFBfIy*H)(|qzzMJ-L>~Hm`EFI(yKZI0wl1wrMTzbaKH4)wlNhGp zEgx?*YtL8D)wwa21j5M8-}hwK`Y1lb390c=5g`pwn;b}k(=B=6kyw`4 zkR$n422r}v?u$@c77hpyZ8hNt8!?H`@%?EJAFvk3hz3f;e24dq5^0*$z#y6gIK-27 zGbU5Yja5ePJL_)r!a8DR6c#YzPzB^G!w8#bejvPDZF+N`(86&Kjb<%4e{d)TqTIB# zk>cD6i|0B@3!4BsmfBPMGN`(imo?Bg6xv(Olr|`hjurnbDWFigf`qoj`vCdv9 z*eN`<;F_cv*EO-}Kd3S3&u*VTe`+sRIUke^>+vFk+vP)=+zGU%P&Ha_t)92dS>r?>qYwIAVq2wu? z}Q?{0!hS$sx(6CMq%*I!#p3BZ5`j?bG|6)_&TYeAbm6 z3+JN8Y=p2T2BszKspW`Qf0h+*g2v#CKxl)n(W8UBB>21AZy>*^Z_C5c7Op~42}ml3 zt138JvzkkOE z(woTp!w>=Pb?WW5Y|pzQHiCh>vy+;&9=1GikQn0#1o-CLiFGj2DV2xmQUq-M!PJlr#AEQ*zGDG zuW^nSY%`ym%gaNsXT?_P+SM9VP-Gin!)6-nD3 zey!>?;(S~Qr0ENoc>bdcx3s=oy%oAlqXO;%c|*GLS03|%%t;sy_nf>P3N_0~c}6bq zQ;x+3A-0(UzXRU(g+97U*i>#q zQKj>YIW1c}IAFJpL#q4w_cfhbKIN#OtqqnqfdwVG6;T*UJ`fYezG8~!-c8YH2&cdb zkx{Kk3^i=-s~I7)cFratyY`yCbEmH&*A4R|LswvmHg%Xd5lCdfK@ZYf>M2T_wpv9p zgk((R~i3+x;BZFsCQ@ZlCj zRzBmxaZv_?S{l?4WTOZ=xA8pCt~^;;&7-3Hvqb7fXe3hV`M^Rq z%}^}?KE~Vos05UHL61AxQB1|PdnRHr$@6GMMNfbL=CRcTsJ9cQ*osujdv7o3&KKTY z7v2vz-%(%H09j`h;M8;GUa^Y>A&?N#Ak8x=zyl&1q0n4GL4DI^3aQG*CI_?D$7qIDVd+V$IZJ(t4=yk$VGJ}sUq{xbqyB9u2~dpxH06M zExvpvDb3rgb82j7#jT3B2+AnXc}PzfB#X~wE*gG+cq!6IVr_bYk9$G^Wc5JUDY#b{ zU61S}5_6~rg05-GppVuN(ifsuV&+;}1#L|}FyOw7^UW$v=I`mIiLt4!W{`VQawL&m z4wyhifdocgn{#Y59cSoogfzsJHux=RQV<(KV8BEfr8nSRPm|B*w^53KxQ`13LM!YB z$->}?#|025A2m?;Oe0Oo=2T|?L6uYxb}D-n)vR|)SlRU$4p+It@AO>2u0fc<&z{KTlns(|c45>p#4SXISFz#S<;t$eRrBH4v@lB9 z|0~1snqC;yD72f$QsOF60%STlVM=3doE`xL^k6R`g$}o^G4AspN*gF<+dNabbVhK{ zSjB;34Wqv)hI&(f7Ctk^ig!{)fF)`fF@d3$H7M_3uJtIUtg@Fgv#h$;stt-bT$;O= z^iF5Fudng~abGP?Yz7s-zU;NYI(*rFQDo=8!JX=LkVZGuRpy2ZradvSC7Xg1AEqF1 zmlp-n4JLl6`bw)`Vy8oF-Ug%;143}}IILvYMdUW*vgW%UPIlUgH4Va7ikE??cyv|9 z0Y_2`tga$7bsiRo0L>jXL)-kS05qxcoIY{W%N+15e`ICOVlSIdHuD^>TVl;}gLps{ ztfB)DFSzQYp&eiN$4-HBubqjE#h{xnIXC9URH|1Bv#(Gdu0TL;!G_2YQNRoE|<|66|K_nxdGgV?B zHxtZi5;N&C(?kgIPXi01s_)y`S*}IYtdnjBubUAq)3oQ-JaV`YG}q!tdl{O^@X*t- zhwq9cfKdzuofR}tm?4dlAw+smCt0#(r|!VkWi<_od|V`>0V_G`<;|m3DiLm_bh`oe zxy=KlD?kI?{SUL13~7dnHx`G;q$?E5QOekAD8#oIvVc)D`ztz)StKpr zB7~vpBc`Cx;wD>3FPTco%OO*Y73jfsLabTI1&jmpTFv3tAAeDfBwOS{lvuNkw2YIw z^Gb_Ul2|C(ZyY`Y?UjW#-%(u+)nKYj`Far|C5#v@ln=^mqn4v;sT5hxd2(8_N6~ao z*F&q93Nf?JEy-ynJi|O1pVYjYVJwdNiz>C6Da|W{xT;5mZafL@ELc$xh=LPu+UhoZ#StfXi_VN;>2>L?) zx-=}p`)2*d=AK8~q?(7d`7DMDkv@S}qOp02zB|B-Ji=2MS`oH8nGWspOladJ06Ok; zCD&iv-FVF-IjG6JtD&7^6>mleE;d0U%&MW-K*IROGp~Ia`_5odH4+c(@df>*@gvWX zBh{rbb4xFTDqhyE=5D7=cPFK5=_2B2Wu!jN2~~xTsx#`N(|;jw`9y-ODT820imX9Y zcxauzmU{l^Oo_{%!1LUJ8@H!z)H_WCZiVaf@3!+sZ8aYVuO7B-kojIq@FhBVYN<$V zjmc2=4e-_#2v*<5%5cwxJ=u?(51rVN1wiXvGr6~_&H~QW>?M38;tL!ZWW@G#2~`Eh zpy*Q-d2tgiISu+N>Mcd4TCU&+L;%N-Srzb);^&gP|!>zm%Q_9GxRq^2&1-AXn={)3~F zx*|^6(y%&&_i@k(s(Y-zO6w~yI)f0S3euycK^Urdsi1=O9E~%PaXz|`n;z&ufXeO}x z)`k&oS(6tCm(KD;w4@T+Osqd|` zEMUsopH)MXS4eosbE)}}SPFoVh2^kj#IEu!U}UI;vwjfE$X&up!hIPDS;!KnP%olv z(B?2F7Ls1ZkH_2NlPL@~OGC_n&4r@TAjxiB%Nu4A@~)`|n!-9`!PIz>mZ7urGxgwR zdrMLbGV#m;4Fh?8k$#R&5W2>G7|@%tn^)=3%8u$F$N1HrGH(RqJXBnCkraZFb>f?E z&{-OL20|};t9IVBJrk4X{0508=Mfvm+89U&=!1Ta#1ghDxvjwem!Jm`RNX_{zx5(V z9WHsR@RnSMo?4V;I?>II{=t$DGYU$8SzN^3n4PQo75T9>{Sm|~~0D*$ol3Pm~ zQo_Ue(%b!G<%{Rw;_H6%w08rfq9pzOY7vs&OY2sMC00{$lh18)T9KMe*lC%lf%q@i zY&S9b32PjYElSs8i|uQ)`nl<^9Lq#S9MX(YuXnq8c4zU`&wF=jO=6O;^*I(6*P=KZ zEj+?ng}1$4{kEnTu3)9!Ki)7>52T0%1saq=hSTDbr|nMo9!@;i0lOtqY!QZB7_OFl z#)4_)>LHsn6^m$j6%>gbSO?a@pqcZm+Uc;dF)2j!Cv`99-oqA0Zjc#wi4NdU286WU zXy!wtx8}Ny_70;+u$iOJyo1fygDIy++eHXjJ@LZcOPVi5{0x=p7=JKw&lV>QZZvSH z6Dq9^2b|AxQ6xAD+1<}X!+~^j@{pq$Uf*_ajI*^exSBHWkE9X*Ju8`W^9{&+D3W4H zEu&6HEi^|IGQAj{fT0B57ln3+6HDOP<)QfU*NSDQMG-5F8DXcfQx}^}Sa=G0Z)p5Y zYNC0NC*;3ZRk<`!1KBgkqQ&JpsAyqgIVy{{rE>I`S!>QvCaDR71>)xx zSW7#1JR1}OXRlgR26)t0Yr(aQn)8ELu(|p94D!2%^@LO8w2fCWQq{n!^akP8PZV-n zBRs5Q*qjdPXSqPUBVo&7fOL09OH*n6a9U`eRzYikUSKd&2JS(Rs|^p8n6M0)bj){I zMvf{RPq~Xp<1GI*)Z+~Emf_QQHm(N2*pAk8@}3A@&hvye&qQ?x*uHxUPhg6gGlzjn z=??R~(6S_jAUsn*)Dtfz@s2J*_ReE&9;BhgWFJ*Jjvg-A0#Hb$@GKWpaXB{2T>e71 zOwMD@2bX%1J-4(&+%FjJsw^t~BS3-kolyqF05_({+SX13<9xXA3)PKPIvXKx$U2X$ zIkhtv_YP6(fDmZ%#+7e=As8x))TKY^q--5lS3JHb7jv8zZFVu~omNA|c!OhTWAJ3Q zACvOB7nKz;P7xB5WC(BCE?ko9EA0$w=R@1x zlZ7-8{C1Z5sz|WBer;jgH7}1(o?Ra@n%5zNIKe%)l<=2b_^BLtkHh+)08a2NA#{A# zw}&{cOQLU+DJk`QT{1mi07k<(hFz;CEW722vn0% z`kI|Nukj5Ea?Hh@y#nsg0S?OM8}nLFb!|r7@%G| zMc+mHE;hBF;UpAY;w2vqKKa(G<+sA^nA?!GhUN&^5KK9&Lw~_6)2`J5(!N=Ka*1db z*&8$eEK{_joq>~!xH9Ke)Az@od{YMRO+NS!Cm#H`dr|~_?sj|`1nm|#OpkQPNaN(X zE>EnhXs;1ra1cyRuk9WV?)!GRc{usd8xs#*5Lx~zf3#w0@&z#GcotdC?F-Rr0UiE| z_~EJ~)=T7BkZH=Z6#YJXDU{L9akWo{3nB5Owi#8D+M+}KCMDMv8K(agCH}uY3!tj7 zb5Y9vfybLAfgYvZ(Qyca5TZ2X{-y^u+*ULkisKP{i)!h9xE@Ac4lQWa^45e*y3M(6 ztsTk|u(a%{9nhku+xc7@DunCdOqWZvj0RmCs?-|+Rc36=tV_^stA=ysl>z++1|C&U z>)?CQHfHx=bCOq@*)CWpf8qIA+j4`cat?r&Ia*3D;3EX=tp2ySN40Q$=zfMB4;qtu zNF0;zpJp8FN}36A{pwd;2*0aV1 z7*1S`_E4=4X)W@gWVu7$qC{?Xyf`=5kOx9agjEe4&eST9A3RF9?kELEq<4}O@eBK6 zS%IMAyN$eL^(sUvz03ltatL$Et_JREeu2Xkqc3KX1t6<5$_tQ9-^4xsB<8rY?KVqy zWa~I_ZV5Id14HtKZflcXFswNLc6*Rv#tCAqm@F#?wa#+O3t(B=QA7_SGPr+lP>rzH z^hbe0E_bxaH!qeg;j!~Aty$Dr9=ll#^0bpVYo3b~!j{s3MQa1|M z#Nf(0K?rG|;ut`Mq%weB8{vrwBPhObxuoUmt}7oeTU81BI$)3UX&z$p z43@f@(SpEi25xBaD@+Fd?N@H8fLgrQ^&=QpGXGOZB!rZ%nY~l&ca_>gs4aFSi`VW4 zQIX@U_69L{29{(#zgBuET|DMQlP!x-R8O#3OIB9SNcl15HLa||pA5x?`UMLhr5MLI zZ@Hw;-OepmIbAMwU%bPz`hb*gK+|Iu1l*y?(kP}lg4HPDSWr|bf5-F%AjKHuVhNOj z9L$SVCf^ZQw*b}Ut-_UzQzEXzB3TGMJLDHLgL|4H(Go0)5Kyeht{YsX+T6gE#*X0r zT-+5Pcs{oUjR-stPlAmRV{iKE6egk&Dl`fg@xP)At)XtT)Ce3URs7X-8uWGlSbUqF zavM3=;v`hZ$zIZ`mQXT*m1H-zXddY=P}+P?B0Lsn5W|B~44 zB{xVVuRuaKylwk3&S>4msUw|T#)@%nApXzTJ^sSJ%U_7A%Sb!5*Ji-<;tkArhXw8h zSaS9^Liv*24uckJo^enrM4M>_ytFfwl4k%51<9f>lG6}co%vym~?k{41Vkblkq z>bn3cu5rqn8#!KSSZXv79hBKY)g{J3FT9U>2!{wO(9bQhuR^vn09}rEG&|yPFoo^3 zVsY4kh%do(unl|#2$OsPrKh6nijNS&CNhFs7+eIu4@biH?{2i<5+MB%Vx7}L`z25) zd#SaG5(mry;Nk1hjzMxtJs%RW;m>3vN8Q42ee|81oN1==#pIn-yv9#^ffYn8uf}{y zOt$D{#h<_P-Iq)Y81Ak;Cg_-I(?pw@hODy1S8{dQN{S>EX>4;VFwhZ40P%#CBlx8T z^({bM$`?lXH;w_jm*8s@bXxHNuEnY)?y7)~d>SM{@jm0okVw^}XMpt8MfgGo`G~xm zagh3S>U?w|9<)}N$D+oX%1CKADsr^p^Y;x(J?Xfln1r=vynIu+Rx@_{IvqwIRAAnP zWbx@m&?RqDeK}7@yrNLKjC1W+AzKy#??xqf_3{Dos!FTB1)Wjr<$zBd-Tu-xL_EA# z^%rR@;lspKJXo3yw>FWNz`Q1LBwf$?8TVK@Ugdgr0@4VTWpJaP=EM9Ah54d6Fp71h zIxZtBDKQf;I>&%#$QT{oTnxXWN6PTZml32$cO1{4T7%e%&sm(uvK+-Ztt5U@=IL1{ z1s}*WF5uYX{|52xFA?Gw_962QYkW^_h@Q}wW`KXqi9~AWtHHD>rl^TG2s54HFfpF zNkAx%!8xVu`BW5CJsX%pGL1n}m}RO5Nu6qspvH~;!%KycPYC+*z$A_hE?5Uj2tfrP z#YzkOZO|rw z7LF!nM?5~DF9XmRM%ixNeFDesL@%A!S2VKmvAEXkP&EjC0|3Ci;>oZ}6A{3}#qiWp z9=0T!Jiz6F*6x%ad$DMzhKqAr=X_Hs0#fJHaP&Mtbc@53aN%u>fx9J6 zvkVc~;;{i_?ObnLKV#%3leO|V%kKjkRE9)NrZt|=yx8ksfHcfipT=RR&1ou+!eL*g zCC#WSdg;HOKt^;-%Y19bN-3-|6mM2&)O_iKVO5PX=M>>v3z^-Tc|;TII^5?jWpO0j zFGK!7cQW*pE{2a~;=>|0I5vIM)%h01f%lQhh=i?M1gvK!6~y2!_VUAP-1Yc5k}XDg zu^Utp)wLMXEX)P8wV`Kw102hSqtxK-$2Q=j=YX|>$rV;EqH!^pkJ&)!7%M!@HC3a^ z_9_PAHTWWL72?;^9My)SQdYW@SYFP8mV)pCew>seORed}Jfgn8*b?5xo(sPfqgHAN zvVFvt7g1ISKn?3rP>6vL4N;MYSnQ@4;r1Chct32cefAJ`L8O7_IpdIG(XK-rgF8CR zKjby*;T3%I7YiD#&RVdULX`nISc!o=DfT$Gs$fhp1Zq8Rkn9{cUraP1+#g@B5v3(M zC7j;MmO~kmf{<8YFbKi$d^2nyQC3(s$Bb%#ykG>V7}~-7=Te({?#wZc^BkN#BpwWOhlti zN@qycAQ)5CaaD-WL%D_qIjCIg7R&uW+tZhA0UBBx)5#vw z<2Tv6?YvWaTe}+{6r-`X>qprEP3s4@>t;XUFXpPBDiJhByUri@R!U4IHEqwIz}V}~ z0){9}Y6?4{@9ePJZ4c=n(H9L%Zb04w)3(2-X6|o!XJ_o`PjpM|vDarlwjOtnmfsQo z2BFHpVm0+&jEU$jN*v&_vMd;JHrNi4NFbqiGiKhL$_&c(V%!-+v;edWyuKhIHWVvp zgl%Fwj|}$a0pk5Q+q#+X25kz@82m$>!C=H#j74XKC9HW9IX&q=R%Yp?`>3YL+jA(P zs0T1+e>5252d_^lZ*{S|_IB~(?-=u~DVQ$;Q4JIETeC_R!ShS_2n(r)smRK*U=BNH zXaez974a|}aV+s+hu}5Rn2%v zLIhDY8J3{_6R%c@2JQ;tuqbKi56JeUqhTWCAqJR%^uj_QtsUi|%v!#zYMQ(@A5uXP zngB6TxNMgeTR#oTHPBAc=BwF9rM5VN35M>@#pVf=;sL}Rk|rk&HhqF(&2bN=;o?SL zq%qux#PhbIuS3q?7Q|3J848_>v}kL6Obev)nG5)zihFB|FA^iFEY)f#g+b^-X+W44 zNd2|^A^*Fjqrz2lxOttv)lQE`^qYr%x`$auI6Evwqz1JfgJUl=Kff3WnFCmB`3P?H zaV8l2#$56d+pn6cBkn^|0vqg{S45SCA0;g}7v+Db02%xtD5{q8ag+H_ZKvk>)$E%R zPGnkCS~tM>wl+)ZLq}2Wg^gZXL4P`1|Va%^RU~ zP9AR3o%#uV?KJmrAf!)R%Fjhq{P=-@E34;QtIbx$>#uFt>4hCu$D@g!NRw&wnu*Mz#(EN#dn^?33?Y!EmxeOcRGJ38#BZUR_m4ayS?8PaC+2d zt&;8cGqCzYJkRSaTVovmzz87^b*(L*t=Y+k6C`*9=|E#BPR04(2ue|jEy=m^+8Jnn zUvItzN1%;U48Z)apnOnlDZZ3;cI@?1sRrl_;#EMr9S(PpHaSXjp4rDhm^^DC;Q!}6 zy$hwr{e!g89uG;0$Xh0`Z{gyBap>?s8IX48trIFVLkO?nB{(>cl(Y~EO)R*6-%4C< zVGGtJXq`#fNY-!7q@?+#MR)&ydvE(AS9a!yX+8vj4Ft$v;FM&IH8WyW^$WX6HYKgt zO>(ApNDjBVXQUlbBCETryKC89)vnt`ss+NZcfCOZ#P;l3@~+qZ5@}vIn#os(lJuu}sh3JSTimZd!s0~Y7MCR-9=f2PXey6s zsw4r=mh*}&=TZF)*xO{Io8A8027=UDA~?iv$L9(5Y_pWB)Q(Jae(loBlcy%8i>BlM zN{7T%GF~>nZ?4VtTUV3()ZFJ(V)V$TAD#zvCIU`=&ySMQx577<>?ZN!&y49~$mJjw zTn?!gt#m)sJvL+d)BDuJ8N<1SF$B! zQaFV#r2a;!$^wh?{oc-!XDGn2mwvxE^z9Sz!xWH)F!w0JF}XHMDrmM1)T`BZFv%B& z1OB^<{<($U#||fzWS?`g7s*UC$U#U*$VSd}Yn@RU>Y4Tu1gi_%o2E-mQHmId3$zQQ-s=E4!tkTGA-Hb(QPKf8c6fYZF8B<6G!$jNS&w zk&v?V+Ev=X;lmC12KQ=L5g^=at;iRgHMZs#^P}RM=W|CX862%3v@i~Tl^b$3r!(XXOAK(%?a4Q7Qzufs}<#VubiC~egPT}&I<1XhPDfB^=nS*uIa z&+AC~g{uNQ!qGM~49E?Z*rBy;z!|;mLDH!4W?4|(cL`vk> z90TT4hp$T{-{=)BRO$4GUe9yBo`E>d0uR*jIb9#4*RfONH2W3MEGMd?6$sp1{gw^v zHja`VH2w?E%dwj=M9&#F7noSM!QkG$@!+BSH%CdLzRNl`r7p5sn6f-8{7gz(c~nG~ zx-R;P;!RnWN(DfFjIgi4s##lRfvyQXA9iJn39&`OVtbTqZO)sG{xi+%NtPI@6V0}N z8>MA9kn3mmejH4?>6MAuxv81CiBpZ)$=O%m9_+3cFPUALoSmDPo|~R+%$%5d_4U2h zX7-Cu{pMYPmh}uh!P;-H!RV2~ulKfPkVE42x~HVDWAv_v%&AP<{qL`#nh7E3xoiu` z(a4wW@7$C3@X-2Q?U2bm%zo$-#_PI48tA>use&2V|?hxM>ZUp#fZ|hwK zoqJLR?3@3V{p=18x+KtKZ|WLv8%)X2+AP*^zQLEtZW{%%HxSTTZ+~~tdEX!J&G7MR zZ<4zQod@*Hu>UKCB_vb7Uf4l?eg}l>;>>_4?)tBgM|;mlUL8qjyqCaMd6S|$y?_U{Zjb?dl!5B_VMe6ks!`x~;iIRTwq0ETzz61nPba&qrP zS9>_(!F_7yQ(N7=A8!y?_PxYQri$bHPStqz8*<9u?sz-8AL_39g|?UX@q%e{u{*I7 z-8<+GAQG>BW8<55gzMmq?+)H`uEAVDv=rxoGA2>l-_Zm`c3r%SU9iq!***BnI7zU> z8oKNO7K*pvxKo{5`mVXeAV;uc**z~ksc%6M+_m$KmF~U`b-h_%$Isuv+QX*Yk+_hw z6MmaDTe8%b zJvmDfvPD7NsD>*c2NV zh9dBB5Iyu~tEh%kHv?2zl|?#03AX4*)HrYEMgh;hJPtRM(ni@3C}fr8K-JZrqQ zhq3QC2nT=Xf{n$tg}e&>(8VkYh42J;NqSx6ilH@aXwZ#FG}787wE??~C*W}ec+k5X z%MJ~}rloS2fx(<>g>sMxIKeDAP|s(-y@@~NR5sqzvkT`e3{$bg4&Hc?n`nmxoi(ZC z!KEzX4GRmdpVn$7ZFPDZ=#nOzUVI-KN1|5|VQzKN?tUP{Z&iZihzZNm#>pxGBN<)t zOWei7BJ3G^r`279bcRPL`?iK;A9Wlxc|S7vk`hUGD3-N zOgxsit{J82VI6rInIrJOAL&)9>q6%SXGw%d&A3oK1Qp0LFvo7tS!GM%) zqpVZ}J*BH}PULz?Y}bPrb~Un_t@KF@W`8|L--ReZ^x=`iV>H7%@RW0ut?jZ|@xiIOgg)Pu0KJI|lF@1uA=Q$3^FV&WD;C)<%xRGZ1SOSJ?qMx6! za~-UuT#e~23`A3AM5dHq%7&cix*bV3OSnQIKuJ*yaC%G(wH>n zL4^4R1-`!xk(so&=G!r8L~^#n%>jhkQR#e#j7%B?q!gE-gwTk#=wXzh@m0KQviB3$ zD@b9ek`B;UUx-0AgTGX2uC!jvMIr`UBlTCqyrH`f36Aqk1yrVd-n)cc{Tc`%5lurD z?G9cVnMtSykueoRo8p%TNE0~M!V^>F>=Ymc3R&| zuCQf&wsKj5@5K$EIxa_)@z%s!uxIj(4qUdqwY33ScdTI^W{zI+h}s%PEjp+8}w zc^9c}lIIwIDA)k25~8{32oWZ(EGAxpPRO+ZNZv%Kl)QBih0* zECYtD;Yigfq!5_f+}g(fLla1Xs4qH^V-zb?s5`qTZKn&?)>7IHerPQt`TYqDEO=HR z`q)v+2h1YCj;xV=uR^~DJLl5H7F+YcNg8%e>liTbfWnBWRcah+I=mJ`aR>cNU=rj` zr7ihTB?-f?kc;}T!Ya;Pz!(R_dQlV4c`}i~{P|Ms?NVv3;>n3Xc_`8JEAy=kgb-%l z4bn_N9K7Uk2e8R*r9}5tmGbXHowbj^Y=_xbq|Od#QQU)mWH`;Cc~dk-dfeT&VW-CC zRhorkhWS}*5t#+^k>V1yhSmzFO6E;cgbt;piPrULm8Hy4Y(ng(cExL&s~*^gq?2d5 zYPmFE(V(yabBc1`UAC!pzEZaj!0JXadVS-_4Pi~OH0&QJB z0ox(N2U4Y@k9kRRokAG6M8r@DU@DKzjWLyAa}>sS!hdK}6fPe^u^@yL3-O;9HP(l- zU}Tfyfmv*8z^5g{08Sk8h_X{Qhj}8y+dDkHASW*xB7o9KG(N&2x55Q6@y>XJX`IL8 z^7oqK&;+6j?KycqezHy)mhF(#RY7JVqGe$C#2-D59w zR_C(*nvjS8Zd_{bvGW(OK$HK*Kjq}IqK?e7#JFJ>QsQzC(qrEm4yLc8;7*cKI(xyQ zw?L-5Tk439d!SK%jNE|T-c>Lo(z64M`TEKhlB~tE(OQ=T38#tP4uKgPlI0E!ZO5tk zQ51Ko#KUDtWkQyBD*k~kyUJV~+RpTi!+Nhkqfpi@Gel2km727=2>Eb<#xw*i%nH4T zU^!=ubJZIywAhSRxzwsYp6)oC(;cJ0xwy!vi4l13!zUQQet^oGF@xVt?_hS1S7q=w zFCi&TOdQ&F$L#NDE$XMBru~r!Fw)nBS-1d1l(gc)7l#!dd_?NB$1D?2L0QZo*H3XY z$!;Uc1ePwNa5ndi!8TI&+#dA8VB?3Gmzj2!BtGsDcu92G z+}n;uJXfWGW8MochdLbTL-{Ok(EFI( zQzMg^SMbQ^Git`fUq|xpEYWk|-2958aJ`(!cJ;e7f{cNdYTeL}XJp?qOzZxDz zaUeW}%zv^@ryH#AK%@_K0SMEGhsnnL!8zE0Vo&SQnl5hnbiDyz3^4)ZO{GgrUYD_E zN?RO2KfZF?#j`o5vL+vMK^+%MAH`#J|tl6BVaL6 zb`yzCF!u$&H5yw2hmraigK8yDSNj}ZIlf1XGy@Xalneq!O9{bWgRbvj+S>&D|NeP$&^FZV^6DClMp#fRs z*nq069mKw)MyB5e?OeT+JjMaCpwo;=Nk(EqaYixKO{r05d0INrQv)%UJY8hS__k@} z@plyNkMD`O&F!wkH5B3T{BBO;P*=*-dBRM0Rt4%Z~7V+rE z^_of`leMC$q!-Xyl#-MSr(KdJpW{ztLF`H9k?Rn5dwaauvN>Eq=}BVZf#k-(3*3Rs zLpZJ^S@3t3n52-(3wYAVNmv;KTbi!mtLI7^H50h~Hk=3j-ueJmPzm{XCj}d|GnW_9 z9tO(7HHYR!SSLq}l_y0p?lDOW{9TobBRjSluNrbLMi0NZBCZ38j3-1*mc`wZe$ zFA?eY?I8UgUgk{(ndeHnZnBZRBr+t6OBU8q3cRoAe#cM;xrPa}+H@zR9xWW zmTCrbSF&AYxso1iOE%H>!BhXx?jM8ip3La4EPM)HLbvX4K(4`Bp0|VmN~15VeCe5I zuPI2pW+F{*944(^Rv$sHe=U*LHhopj2~;o>8?)NA%f9m5Gp>cFhXSDR((n&t$R`qc zDP2OYEe_uV>#Mr8t=xZuai&{HUPC9pYns+ZT)x@{elqe2dUOH>imOuxQi#Yb+Zk+b zGP8LvYj1CawY$s;8z=8>r8Dlbq%ewN`hUFU%OR<+Az_3;Tg`m)ggR&iYs3~-rNY`a z&eHrG_qmw%i$xyF6kWDcrijRTfR#vVgJq6FGNho%LnU3YvqwV14hKvfz5(&;dV3X} z_elluUV;irnvg75BAGvU>OU?Kv27q|)U#~E65Os8=%A;-#cUvGSQ8qn#S^f)-CG5N zoY!RTuxz^8YnHXgmSo|_ks^4ZW`yR@PDsWRkZZBUCQN5A?tT*!$C#rY2S}uOH1MC( z-f@505F1FFoSL+#Nfg50SB0=Z@LLSaElc4Lyx2(K_t}h9XU-ihMRl&V0d!KMg z%3|C+6%gQpyh1Zm;>&P_mI$U>E_b>)ffqiG;z*(%xtgwEV>}bQq~3EPxJF}HD~zBD z%VKhJZt@V_n!7}F>puv(HD66PTru{p6wifR@5IDNv%M2@V=Aa#7Ptwh@k0ytgN0tgOZJ5nMpfB$}5mvM?y%<4}y? zBG#E{4uKjFaN#q|9`BdFv}Ek~{VW&z+8K+@*`ocBU9@$Wg>fw!6|Sh0*Sl5=L-6FZ z0l%s1J-k*r5=6VyA%g+Rl4qizA5&`dc8Rc?;%kVcAz+Wd18MCTdyPVMIK(7{qag3I z!K&yq6w`Qd(g#ni_5%iVGSv)HEFCb7Y0eEq%NdJC0Y=1qcM0Rb0LNTiE-uYn)r4MgM66DhscB{X~y z1+w90G*FV!u&%G+zQjCtwK0=@<}IC#lb8t36-xk1wDZBwOO$iTeL+oZdu~sXu3R;w zq(+WL4J#e@U2x`b75dAC zNvcKtIZ;A_c!HAAJ=PM>l;w)(^T#O#atizDuF|CE>>FK9j1`5()}u4(U)cj;}z6mi#84aywRsDF;pC`S8v{zm_~Vk^kVgFv3aQ$Vo#9 zX@p>eJesig1q(1A-k#i+rDO&u9e%9@B>XITI9Cw=nm9f;eX21rdHlr0bXw+>UN(tq z9817aY0!8B0cg(YWeAhBy>&qp!k~Lt|cSL9gW8aULuB;HfVZ z1u5N!KmvR<0FG;{8lv0FX3TlVRii|~%cph2(W`W<#!%xiDG`8ARFYMX;Wu3{(o9LCctdI*B%_gN%cJzCsTc`M@(w6<>r{iQ?M$n6PbZph3Gy zlPO4NStpWvq-t7xzEVKYq4~}X1uw=_Vg;uwFb^OG>%796S?evR08gXuSmHc@Q#aM} z#;NkOe<)OQuBb0ZW+nRzmsYj?A75edr`fFGZH&g={=LXs3%U-nSOb5{@d1a~fGB;T zs1o5u=N1)jGQ^*B(G5X}p@e?yACX--k4ynvWuSY~G(XCNLBlpM_0UJ~S57$l! z%81cZ+oF1aF)ZQ?m?}$syDHblrUIXmkZ9e#u?$Z6q4>UKgLT~ z*G6m9UnkuEYhQ=kWf@6p7ty>)WU)50$qLRD@fQhd0vchz$4m;qK~Z#fLC-2te4_Ki zA{+zxvl6XP{=fJao0H%z#6@_3gkl$xZTq#(KW4S^~$V9jS{4yMj{otwp zD0mgL(+lJfSp?O9WTogTu&r@lzh;>yJ89qZXNNfXp(Mm>lW-QMczDdr?qiHZwIkNl zA%qBwz#kytiV3qwR7AE75-5Rv;lh!^Oi4RWfs4Lo>YK3EFi%96z5EivefhDGL&~N2 zo69SDf-w0k$3>AKX2M#dl@UtcAsD98xKAf*M0IbV*y zPS#A%VbxN;kzOR;Ry(Q^dSu34IDiD7az2Hm$dz=I;wMuoy(#3wF@DnilwVEpLBiMx zI6zJjcRR|yuzq8C6Y#l4FXnN%0Gxn$?~2P!C*^|ZEHoWXzAK1lyJS2M%d7CPR4>q& z0U`kV!LA?>XEMvh4q0STn+uc>;z<22zw)qt7jA^TO+FrzA``e;j-yTJRO8gd@#*6; zC#wO>NLR~!-)BP@F-}8{{9*d7>N1G1dthjE0XJ2Z#%J>FM~6Kzxwdk5I^DGan$zNr z2@rZNxLZ`>nEfvjpF;8|;m=vk=kK>|U=yr<1TFwv)jkpC+ZL^xRFDRrJFqdn3vruo z6a<`sAZgAd2!~q?9`%YTd&pICI3LouF$pfv3`TjJUs(?mBSc^1fwDsj@3txc3Mx;N zZs=7p14mwpuAPXo{K-UDvDFV-jf&`!jyK{AqTvG#U}w1yQ-;P6l!a|ohmf+f>Tf{u zL8E47T!<}uAQIGob2-GxB@r>7CNS)L`QWK5D>yGGUPi@yXv(9gMqBXWb&w(FgyE@Q zG?6T?Rq+;%^4vG0tg#6KGA0qB5mmZoW$)GYF;ZFPmPH%}6NJS#mC&eRl`~0`X3yRL zP_*t1bXuUF6_A2@vGH_-iNsJs1gVW~c>P|N){dl|3~#mmUJ5oK=UqC&lXl&K2=~Ys z$wiN};4TGaf7y~G{8*$p5@c#2tJPrmA+Rl9G3Ex}StcEojv!V!Jy-;Cy<#q%a*dCq zWU>h$FFi(h&Rz*0(NV`EYlHLxyU5HzkH!UFh$$g~al{0bmNB}j@(l;o`J($NGBYpH zW$`kY3+IV-aVy4^zO*F5L)Ym0;O93lHWK+{-DkZ&!txVas@=E>ElqmWkQ^d+BIg$$ z1QuMHzQ9LpfLJ8f*T{U1x|~@4JP~%)GS2*>QY|;kH@?v4)V1WId|4EMysQqp8P5~9 zE?wzs((@2(j?7xqJM?9AaJc(z#6swgj|WDb=wwFbALou$IL{ikfO?fuUrM26 zen+_|F>7eBTJ95EBk&+1w5)A+^GPsvg3c6%)P%xK}=mCvFk`0%6PB$hS$HUxL+sdlq)Z&|z zzk_^w`0BrG7(XGz+N(+4_8!+a2 zhbEy4hohf&3*%AtA|@5>U0pSMLcs%LFv!Ye6ep+H1p@23v@eqC_sPU{8D|I_Uzwbm zJB~Q(s2Y#lC6bBt`ysd*i|j%}_CwRLsxf{!h#%q2^sXi-8$@MW|0v=7N@C$fd)(|< z{3jSRNsf^??Jr10gLRZ)N980%#XX$Alipm*+U$lQpC)eyTd;s_m>vAU1LWkWW#CJU zoWj`$5Mm;k3R5@0)X#?%YdjL-fy5K9fkP|#+!eST?$A0kC7Yn4ox|y5~)TVYpv3AdrpBW$->BMD-6>aF) z&~vTFf(VqMuxu>N4_3DJoZrO55}bRwv96u*Oq=Tfv0!F`Ig2#`3)}+Vvc7>WRPd=p zQ=F<=+d|SxwV&Vv^J)uHg;|2TDRfrwFqDg|6F5P~GC%C63x3#mAP^p8*8*RNuQa#I zct_MhjM!m~H4pQOqyv^XY(aGFrFLmAci8GSs8BUh=Te-E(ltia5owl$e1z*^ycnsY z6`S~G^NvR$Cz7FPjir`=<&Qm=PdOSj2ulFV>$xKLMRCq@?}LcQO9VaqCN_u{fm-cnD)^g>I*iN6o-;F@Q5^0Q$!+!>mQw(z!EdFP_72IU$Uc zc3wpSNXTX0IO{#kMlcnGPqoBIz3?ugjAx=u$~ij?oxT8PXXJiwkF0qdu*e^B&&y+` z7vSjhMGZd-r>Q6_b5T^RBGFB@{YN|yv-~}#QqLz@c^G!t9Lhq=k`HmVX!**o*vfqu z{NN{$my=q7<#r%FW@n=Ov%`l^c_!JL!do9X%(opm@;R>MZ?)I&+Etz4$XeSxNaWT75|Q$!WXj$N7OLfIH&MgFno%YC(PGh^pshP7Zsx@0izF% z?PMi2`f|S)i!T|2+(Y3UjOd%KewP;#pt5)-!QnrN%Uqe7nwvn&kBQGuOyYmzFt6%s z1d0D%K;q@z^>>Zu+##$Mahp`9bz-JcaWgND#HTIGB5RyAaBcS+u+ z!TGAsG#ZU(bKQ4q7dvQLPy1_Iz-GN;?JQkmDqo9tey!g_4-7Jn%K!sk;vsYdpVQBL zJz!&fZp04(m>&H1Dz_KcUuj{`yn#`x)1x;x2is7kjCRzZlja771VGgFrbI>52cV{YR_lT7H^TRu+AW7xM8C~J&bgWa3xz%`<}11?v%}cg zj_GMp@sZg$BNq{9*(QB!c2p5orBxFkBo!7%N%_me*Ywc%FxT_+{AC>qT)U`4foW)h zKC$t}(55DbSW8T}h8)te9EVFlT9(KmJj(yMrP4~y_pLKKJG~X0nOpKTx9`?U&ySc= zA)Z3*#DA{~60yTg9jU+FfG593z$HOr%6&iZoFmI9OPVJD!`MPV#}KMIR?7odVMY&R z=o}X#@5!7~Gyf}{RXu9Pcd07RJ2am!+*2HT#q~ARgEsVW;;J5@ea#7Yx(4mZ0geoE z_#klya7IStCBY{GKjzMAhgNd8*U8-X{K~>y^JO?Xv*tx4=r_;o?lKXnd7+1H0L>Ky zh+?m%K^&MG^WjVcrP?dF-X6d|5wgPTn@Pv*-&70|<7_+^K{&EUJDeTTYe};P7B9dy z%=S=A*rRB~#lRcPiAQxRFivZLzWqE!Z1liUVLn`y;>hA{{kjZQWuhg-Q(9Pg@acdf zW>zMq=BAI&9iM5;%udbBo;X#+6JuN~z@oH`i^xNbhP~~FKl6EUn;ax3g(nGKh`u|W z%x1-v20&!KE7S?|fsYa^3=pOs&`3w;bU3CUPndW}-?*Yq8eB4Y5Naw-9Z#y-UA0a35_`MQF|}mY_r8a1sO?# z02b#c1TCiuQpl3pz?`zqpQN{JCPY@vLfMVLR5a(Kt?b^ngTGJ-tm@r0xW}}ic2l#Q z2MArKv}Gj7!-IoO*6w7I7X{<#Mn{t5vd+!`c@ZE&jUhrJ8|X37HzmvN{u}c^j$2lqDPdE>>6unl4d;Zj7;=$tpc*d+o z_fo;PgxW8IC!oHutr&^s=Y!yoxEPavn7_pBFpZw*Oujgj;2wOn-Sy|@_k+Gm>Dokt zg~4WloP#cc=L{>lkCWY3uqN39fCZ4Ff0JT5DSa?i{H&h`^UwG{7c>!Xxe))&O-TD$ zJbKBYwD;HSyi3$1P$-WTE+4gqgJ<-_yVKQAe$|3THrUaVi71q)%G|piJZr~RPZ{MG z_%%64pf!_+@u9wXbCkd6f`*xf$|CZQ{H#j@eP()qQFCRYAcwEaKUu&oh7F3L>iok# z7dKNit@E^iN^-o!FEf3+r=3r%EJZeYh5zX>}7b6B8#VPnD+Nm0VV-t?%ip8dnx={a9NnBZ zu}0B!cF1gziOFC$(c2MpJaOB;e@+se7Lur1NbeFogt9-Pr`mZv6VQkv`%FC{Q%paH z4T>ywF@(9}`T0>I2EAQYP}W~sk<0xU&_a;31Nd{Y+K6Q}lBH-qAM^p7=+YEv-KV+6 zi;h8NN!&Lvp75u(L!_$!LYZp@+CXq>vq$-@nH%m5tR~TAl?#G0MPDfTFHx(=h(^D)z1YVfuNYf5dRUUEG!hK%Hz+b{X3^YK)1WXjSKPKw6|zr#EYrvb zQlc0VyLNDkVA*MMUri<$VsjvscTo-qP^X+Hx?1{TVLv0~xtVyyW;k5#+6DJYD|WuW z*xYcavDV3n?x?8%Z=in3sNM!t;0S6+&`wiY0VOnq)I}5e-YyBVlj>)4J1mS+BO{JL zV7qc{HWlAA9s@vPX#^y@4fGbszk^`H^l$^_d*JxU@1Y_tl)? zU6J53lxd=xD)emcq8q4kVXfKi&p`y)NRDEX(SfaUpNoDZJip*=C6D9TB3j(EzA8wq97#Ae)Lw5u64=iI7)N* zH!(1)9ZGUc;mR>Z?Gik6Jq#3MUw*Cza+>)IN%~7t9}OpmnI21;Hs}sXXm;jMB(@*# zl~lufV$5GI|DoD6D%#4THlHFb?HGtfv}I`aCaI7Clxupt2KCIDekTY9b&x<=Ld zrx!UW9QQ*;I<$oIf3MDjCl=$`1EDuC4h+5lGAstq?zFu{ zufaDlwfTa=D_+?WeqN$+r$%X9=T`a(txsgRf;}a?7*-iM)wx)dtg!)E2ULXr1O7MF z91k0A$ZOg8cn($Sxo35+XTeA3D?hwICSZ1Y+QHB;*!v#=jqA1|R9L4RLkn>9#ye^akOGJ?o+l7zB-i=V5Dlzia8b&jmg=`<0ofFn6=Zn%Lk&Mw9?YELFAUr<@*7)kbzK)wMs1 z-v^eG7meDqKHh`K(WQ_0k??wsit*-?G{pKI;&(=}hxmoRsncP|(0*P! zWWX(yAxR6d*bVwkAb;~hATq3-OFJ|8o$~E}$mn2}$r!0?+f+q3hjw)BaJxdTgas%# zKR(eodHm$`)Z}cnw>y8Cz=GH@XKYyT`WDXFn{Cl%x7SRYC4k^`5fISBhYZ9jRFJgj zd-ADm-}wrd@L27YPG-T&W3{EW7>F#Zc%==mC|sZyV2S}e_&;$PA->rXTtNhJ^~+4G z5EOw*l$f`Z6DmX!p`n?oEA=%OG**BK4S@-woE zKTv&U&NOQ0kUJEWgFz_jFzQLRl*efWj|S~U_0fdu5h+=Sk{iBKcvYdy1fe()4M`Y; z5a6I?@T&MCVE6^;=9F?<2s+=YNTdP-Vg-zPgZ#g(nNRSSt148008J7C!_>s+kUIS0f7vmbg;El~gHVH(B~#u?cjvV`f5Uuk$T2 zxO+G2{Z4j0(#_%*+;yj^k`OHcNTBArxYZRs>}bMS4l(=#mF8nAa&o%smz-t%BYrci zWA)}^!jUKnN+0pF_0Fma(JGOYAbKhIrK^|;dy_c(B* z$;i@Y9(>jx8ZWGR@|g4tQn>ViKMh|GKc)|agkS;0Ql&o!%xQq2eq11*nkJMFPnx&su2%A6-h3X^_!p@kHjj_5p$Z%47| zF_cvHk&VqHzl)Odv;wF{^rSP;m$4s!fV>Rujox05GjopBmZe%&Jq^pa9YB*iV%Dzj zB8UvHf+TZBc;wn22Y?F8y#pS$_)nMS6vOb7n zEJNM7i>U;3$p8iNrKMf2xj4kSmEKA>MNYEa`hFy9#&tf z3Pxt6&ATh0si(K}89&zVsva<=BgDl*=_CHgzpZ-6%y=jx<^0kI9sITQ679yBoWu~L zR@xPxie6U8uyB5m=`G0z^6&De)^NmWmt;AymmKdhsIf36)PRg*rA$%QjldbFD{+Pi z(EQ7~jzj*kM+Owi z69E=v8225uZh|L(*40i+VlhVwC$fjo22a|jQiou`J-cj{?hI|QU9!1A1K+b9UUurVr zp?MY`JC_0)3}Eo$Oyktale06Em06C(%N{l8@>XB%t2b4VS0!V_xyP37sQv|U&nC+M zC=)SOQUObISUPuAsIt`TjUIvI4$!G^3RuA8;D1iOQB8@8H~1ddstIFn(P$qg;IQV! z1sFN23K-gHu-@q*#?VK}AAAU=ieWi!v{ohbP*lFa`;vd*8rL#JkPl(e9o70Ut54TF zgc0)=v2idLkSCt3UEj)F9nq%$M@OcEed%SoSbZo+LQVAXkWwtO+5fTV-ki#%`R;m` zF;2r^oi5;NN2?%FZ_?IGM(?#Z*po~N`1W^EleGvCg_HyP7buncCx+&3w%6@TR3=~7 zKS$c>mWzLUJk^?PQ>nuE!+!$I-dUnV-K7XeWi`>&;SQL#9`FHq&vbox!VcDnRrHI zH+1z#zGcKW95fgN-k(n=z!gNeG+36!=jxo@ky*If9vqX#jd+?1P^T7hRrvp;D~ev{ zqBdN*d=VLkYKt^g4|*aZWi{g7fCXl%K+w`o9ZOrb;7VOXGt5+Ns%KkLz@tH?6MVF! z%~Wl_y@`@|XkK;O@v%DFEySncWoA9wa*B_GrTQn<$p%C6_RuIj$YAUVpaLpHJR93{ z_;@RcyBl3)Q^8JiRaxz^C>{ZO{5a8uh>f5=Vr^^RChh13MSh@f9sHpg8?hC?`)4CN zlquAIc8EgNCP}VTJwe1}eh&m?QK4)=fwAIq^Vx%i%as3))#h0jgKda%${a0^y@;*6PyHtd_(_bxYD8Tz9~j`a{4m& z`5^2mq_w8V9tk}VciQl%a8ZFpJ?{~@H{Om&g$joNtN++Ns(`TG@jrQCa zN-864*e?R#yfkIr=UrO^vnrL|@K8bJ9WQu*u4 z0(h6>Ig@#A|SV*>=Q%Yg}}0U-V~WR^4k zDuFmQGmk(g$Ma;ld~qt@w@{YbKcUOwT5=_y%t~{RR-B&x)q9sG&%%>!fYJ$DRc-!trvt63b{n3QZkQKEg5JBuC>yu z%cXLerDaA(C<+>}*^+(wZfKn;Nljr_PhUdEUe->hp|KzYOy0O!&GvUzdzeF@jAn=9 z6X1Qljl)9`69)*DBEf@CfOj|FCv{Ydlx5l<2nA_ktlf)au&*QfEk2Ckx$d`hZ1;1t z@RG*{*nLUeADxy7R{BpC($(G6WY261YqclZgS_Bp3pKrHEn)C|6ICS0$3ZBmhbbwD z83O;oQ?jZ5u;gt49LUJS*JeKNvXBv`5P%3jZb{tF5#^- ztFkdOGSyS5K-Z$b8s90gIPCTC>}}RCK?UV*C=^%y3~(EpdO%Wn*Umj888st%17t;y zEbeB+<9~iZn}_Nm^`1$-55l10`P6--Q6BHk*d8j3QQFkmMAUP=2Oa@v22>+-<~w3t zifnYF(Bfl&FAxL--K?u*eY<_5y6CUvdEfGk14qJvtu4w!+>$)*+fvSPf8Wle@?dAL@qOH5KBPzMWf&&_2tYT9|wm;2!6s|J+4SF zDr|w+(bvzKcyRQ@8FB~&%pHwl3(Mj&ujvT_t5uvK7;g4Vkyf^e6j2sSzR4oCP*^V) zMUm!yy5iz=r;3YvUj-DNfjy`zbPm{RR(`|ULYH3355;A{mgRDAk|^AaIG-8e&azIn z0_@(vyY98aGhetEC9CtFJkNeZmSI&ct}?%>(JJt|ZBZ^HY-ZcI0|0f0hIF@nJf8AC zIICCZ)>};yH6}RJ1x&#K?tEw8$~DFIv3WS%n~&z_9#*~-mbjm`6ag zOLMlKR#h*2HRNnX73(%qW5BM?FbY0uBCw2iKLS2(9|o?TD=xNGbZRTlr02n3$q+~~ zIVP8|eQIv%#N70B14&&cW@jt&x{H?;aAKidtUueZ+yhnR^>uV}k{<8TTbqExp-e6_ z-kEN*w_jIwe3Vx27U=G@-q>!fwztDS15>}~icEZ`^qz%>v8)2xR{VN`irh686)>O} z7l<+CdpJH`#Pw~zXI?pOv^uakI~xiW@`f&&fc9=euyd9{0uhdJBFP?{ti26X?Eh-k z>%IsVZJ-h_pSEqaZ>^yB1If?LGze$T~OX-p&a~cP9yvBvMP^oxALo& zsUsF7w&Szc@^HlN2G1R@0(9}qA`tv8eeaxg)5i78rb43??#V6tN&ozFmX#Xyd=T zKH{*ru5X+{@&^!z%)cNa0Sn0N0g(nlP_{9uwa@t@z9e6VtQK*L-c5X&$`%n2w#qfy znAnoJaq6r~nW0!4^$NCTV}DG1wjoy&+>?CL)L)czC2HMoRZvY6A-2+ni7G)}% zTP#y#x%PF0%vODcK^dQ*xaY6@4!&pR`uBX3P74>A`k42-NP|vvQ9;KOYIw*%G|{&3 znYqm~q!PnTgf?p`^y9+mK|s`|Zxdfz5~+g!gr%6u6+>_0G{91p2YuSxms#TrUhM1C zUkv-j9l@-YDT^FAObcvtM4Q@Vd{aA6=-m#F1D*yo;A@c)YrC48?CuBieE6@_gBSJX z4<(SfgXFU;rF2VSj8oNvWH?28Tf-4dv}~o$-NqY%h#ehm#{5U@<98sjq- zu9+618Ubms@;$V0m$vWv3wrmF@x$-%)<$I;b6KWBUw@I6PO7quj|12f1Sb*zCV>Tk z(VdnTOIVR)rF>xTR(%he4lRg&6pBM{*W^tQRUI^6c=>GDSM0M!C%OK0F*h&)NIqjq zJ_etlNj)&l069#Z^-c7(O{Sh$kmai;#V>1Mw#p0Aj_0&#Y}`!PsmR$?o{(#HVXUls zse6IU9Zps_3?xo+^xz0H+!K$$Fsqx;e=Oy`8$`Bbj%~Hsyq)88?ICpvCnc8ds|AeEvkqpEY710iko7qz5`lOvBxJ^? za+{qehVqHe^f;3(kuG%I*8H3+*P`nHpYJk!9H*g4QzuPv!f!n9y<RW|2ze@eUle+yV1==wJyeRA=4S@F?QM+ zun2JB|2rddk!UwqbLpDB9@f4cU*nw6`g!1`Ew~XR z?t(XyRYAFN70jqnmctjJG8j-qei?4;sGA&^A;n6q@eFIU`7Cwl!S*+yjYPlV%`w0O z#ItWXxDII!XcLdWND~`b>XdT1zT$~R0G7PeM7pziNU#DG_10uVBSG?K&I zB~aTa!RON8*B?p?b_~dYF$aHI#5(`wyd`>Lw$>?-zHsxZPhBF=806^xoTfKdF))GkN=YI_;L zBcre|8ZJr^jb*|={9KlzVS20Ngh>Go551pJY!-rDSR=P%Nri&2>=|(jQ7A0sWPoS7Azjw=Dc^wmuPd88tPf{M#6K3pVPaLE~B4+oQ&+msK|z==M>Kfphf$#?gJ|+YZ^}tK zbs5qO=Uzcce3Qr)O+PW#^S}rYOjC_yV%IW4a&m({tOuWPp@k&96VgYBbrU zK&FStb@3HM2T7b`Kf5x4hIKO|XWbXC5f@2p`-={~3KAlGx~m{az~DP7C>0&OWfD9= zQ%Hw^nyt6Yl6U-M_Y4hdP?bfW;~@y@&1@faVLRXLtp8!f4HZWHp;Ob~il6!*O-`;z zNZLO)l_e8*Y=~UQ!_4a#8(#ls{s3qr6@B4W6fBzmn%>S*RVfOF?i|Ayk^-VI4z5@R zGbPza#2c`P7UOl1sUh9u!i|_Gz#&1=F6&}uA1dqp3N!RgpaYe0lFBjz1B`5J^-uy7 zs^|U;ZxU*Rn?A@ZOnP#oNs5gnlpgz?NFzNi1r26!YbsQyo{irT;i#?-c=I(9kHKh6 zK@N+x&46|u0Cqw05RPDymY7LsGl$%m8i9%q5e``+@_o#70d9dqgik2L1^@0mRPx4v zGm7JL!;b-+);)mk`Ltk*U(OR`N}c z0yKxfy}Lo)#*yt^rCT~2tL4J65)W5;44eRgU_#Z%_Tl@$(xz$}RqI?%I^M^&E+R)t z7I<#n!K%XNvJ;`ZlEF}?Pg@bKc~Y&-bS^)_2w4vcgbW-nbWpOs3oAG?mo?geIn{tP z@Mfezgg_vbmvmwj7$+PEqui&%!u$`_#D(}u$zopMVw9tYgDoOIhgL)JwmoP|EC9)0 zsHR|SE^4M^BD$y@R69*gHcrlBSk0-K5#!UVE*4$&?b|KWE~#P)dh5Qg(sWFsoIYKu zoLFE2xxra>Iw|;_N|Qb+q=T9-qVB&pp&$u?$aPoMbPzx=POB#WxbUE(1lNVr^nwL= z!(p)tAQL4}XFIQ!uGy!|va`Upk6U#^{ou#W08mF^!^WS&Z4O?4t^uCIvWF zD{Bu~Jc~%UPQwgk6%?)1R_u;1<{|`sZyl+MkcrwWi-AH3A+uQrqV)I$MxBx7bk%s* zGXw&}2#dPu@6sJjyw5?V#pg<9p?dNqs$r>ssz#t~oG$K<61y|gUS8{PNn2)%Pn$PU zF0 z_cdpxxkH@nLdPNZ`ci^H7KGK#T8wCfdO2&q3f!v)LYX#lsIDn&zHqVFyCv=@J5tlFNwIxS}TWXjpdafnsS1aN)5v91$GA$Fvxfhn*Py?!H zN|&ti%W|a-J+KTGV^fh+-1dU}@N=yNFh;*8qwbz7Q*k_O*jV6bo5(>RSE)mgX*I=` z+$T;=OwLT69IZo2FCL*o?x&=IXuhMQfe$V1Q4%w;>;UCPcN1vz=}LVA=sBIJX#ZVqKDYS%+RV1m_$;J^v{0{0o*%|hdeYXC zxV-jLgJC{s|9BpKfHP13l>I6X{JYq9Xmg>MFQnYchy(WVSZqO%SdiY=k*^+TkS^dVLN_r2@oFpRxNl z7SBFw%K$PGV2KOVsjf#0ONy%Q&lO6iGqahg#=X%vwqX+Nk-%q|#WtDMj;}pdg(K~j zr0z9$yksbv*+NlCFdzoktl3}^P?m5ZYA9Igi*acj{E7Y87)UE}Xfo-5z|-5>W^&Iy zk4XW$Br0V%Ve5mZ{*q`<+>}uX?vP7k$>;H0FBGCOx4=P0@e$#`iE1;~vvRUCaBW3^ z2iVd3>=@C)`H|O$Wu`ORY2^p{G9llH&kY1+iEf&LbcBt=f|ONZW75cj;{qY!FcqC| z=UXHkl-rZk18jc5g@S>WGkiuI#LF6Wk%p3D7QLP`*}f4^EOHjbAhu9YJQt_^+zNu* zW?0oN+7UDDr5TatGe5ldI={$YFUq;x*{04i`QV};ci#N~#utjfO4k?oEa#*wYSa9h z8kP`a9UgsFHG(vAgJYv^$aY5r$VCMYPr{<2Mz1&0*z*S#U@KdL@f7$GJ zwgaF>x@~Y~{VmQbUEOXc{($M0yQw!Zp6r|Zb*39_cIaCT`01#y+^&k!;Boasor<~& zJSX}iF4cTCnp=E0iaWOg5hYG%T|r>)^vO84aBD|QM+J{)c0@WC#E4mhhGU|gqSjuS3FD6(2a<;l~iZ8dTJ^r#0a)yp6CMca~u*zw>Awbeg_ zxd_{gVv7msTVxL(*~5jYkFX7%*HXhO-1!nxg-TAi+YYoW5a$BYEF4W)q3-O4$pW5a zTiBxjxo-qrFXjPW*eY*}VwHz4*~%dCR2EtoCl#cUV~}&>d`Ds6|9~bSpShAq!z-(| z&W0WS0#853K=fLBUrOHpRzWCTd4?C>%1a~ruwse9ELpe*9h|bz0#=Do0|e09~FSccE_fx>gLrK_^E(@2d2IW zM}9_QPqLI)5WReXupu6l5I7`x{HrC4X!{0B+)o{gbl2|JTt&F|A8-{g!2?TFg-B54 zo9^`oNY2n06a>AhW~$`WuLy}A+Gk+NxKH6dB4-i*(zUpXdfatH#X^s*@MgfM1baxV zv}s+O($VRTK+I zobF{lK}YQgLiJF!nQ(Y7K7CtktD-%T*bV}DOMHfxE{`sN85x^fu-fA;+SP3J_R7)Z zv^}8~Ja3$hWCj9b(r~p!;4C4_0AS75HlLfEX{}lq?RYyWxpB&kU`q3N5QIr7xO*o3 zd_QAn@7c$Ih+hcW)*zxiwW-=JjpBuK>2<&M*hlsJU8(f^xr}xS6nT?pG)j?pdZJAJ z#3{%WIE$Tvzi9`>K5j*P_|b!0=Jaz$_*gv^aJ!tt{LI6xO+Ar^ zx&C6Yx65D@2O)+fr|JkpxPN2bGSY&E;A7z>^4$3t!mZ8Yn-?Kc6$DwO6|jOBs;C6f zK->`OQnbVxh42h7Jq@AUHm%43D$V7IOF*YXEXFV=>NaH$K^1_%&J1F zswi;ih=NhK)`uS|nB`#s`U*&u)3&-Sw_);ANS*)!z(i)ajEdsj^$g&Jf2WE5Q~+>S zD5R33fI6I)kSA36W{d01@|S`aGpG}L4G=(DF`#VK*edK0)}fTYiBwR9E5Jb{nR%M~ zA#fh>*B{0`X>z|Udgv$FhA(2HJ8;nvK$=5PLDgkEu+6>Ur{__X#a(bDNWUURxqcJ% zMwr(PK#65B*oYK_9qWq*fQ4J8_(4FN1S&Ps>r*0Y--x8k;#~H@?AbO7Bz5UO&r@(5sP<`vYAvJ|_hA6|D)H85P^i zUo*nPbbA$tn6Tu5;~f`0_f0cxLMt2wcVcT<^9sYdL^w#AU}Z-Rdj7h3n=J-~*^CsC zqBB+o71SR=h_s)G@gO)f0?s+Tg70H4k8)IgHPMaaiEBw% zjUFVAtSRoYngGiblGawMY>3IiG7GJ-1*Bt{V|bL1HDWhoPK>EMV_Zxf=v z-cVH{p#d}X^%pEMMy$}Ioq-MDf)alb;1N)&jF@C)MH!@dJwTc~XH=t_gdp)nv!;{5 zQsbm%!Y&mxjPx^Lr!@do>9aDF z(jQE68cOIZ-Zb0ZPeNDy{fAAOn#6?Z?mbaQ>9c{;8jttjs{*c^-JyUt&eW8~iz9tlD&@E?zBSNzk z@y?dSP65bLD*GKt64e)c5=~=e`#UQ3|c%2uq3$7`S0Y-kiO8~-q1cOgpZ0=FeoN03eDzY4c7~; zY#DeL+}O06B)bKkW|wv%164 z$}zP$0t0UgB93HwOfXQd1Ov%-exmjv7?6FJ%ZC3L#z#KB4ui?LY4q(F?J&q)Gp1gD z$3@9u{(<8`sjDVq%i9mFtnK2mSb=q|)}b3lPs#VNp870=SSsYNr3eU+7IQ z;-1mM7vW+Fq|u=tL?9CjHZ039_^6|C+Ne>CE5d=$@1mIl&6!Sj166R$Pms=R1E~Zv z#R_eu_ha`;WK>60pk4vKj)x6m2?^cx)YD;!^{_-Z`hid+FgM^e&71fPlwMW-{Zyo>17pFg5I*MPu^Hnz`j{%IuY2fdB}?tkNn~ zPZ(~8{L@|9sHoXZdXh?}jZI~aU@51Otwj2-7E*LyUTj`ux6{Cg>=Zj~Z|T9`b$WS7~b}Iz2s4 z%+(>SIg!@9-j?Ue@c*TPh;DFyg39u!9s&QM(NKt@@n@6$i7Po?${;wTRH?nj@>h=3 z#WUpz4PRm?Y>9qiNIsgA^z||XnH5YAbQh=E3@Q?uiM{s4lW1xCiQa-@(dJS3KH8+O z^W~{3Of~W?lT1CvS2P~qbn83TiiqapiwyafV}cdP&5{}_2Nt;GN@|?aE=0LEk%(~> zWoGH?XLX7i-iiTWCnEP5IAxgfOngl=x9ouvM+%%IYM*mz!yy}AUn2k!_i*2%-Zb^E z?G8gC$CGzUH=Jb3oS>I|09~ZHm_%t#Pt%Q$?3#Z5vbv`Ip>b~*PtWuB1uuEy0&I2O zvgYy1eaEy?KO&{Q_tMH<+kg+fWcQ<&o%ivzKBwlU4h>i5t{GFmK1_f*4%ILr>}h@C zUG@Z)BT%>JG$!n#-xK8hiBO0Ty;Qs#(h^-;6kFsS6`T5Lh&Qi$X3LUh7gjX9us0eubg@p~;ZH_HYC>!+=H%m6AqWk={TK zvL!~^kU|(BLe%X4Hs)nHRjg{lmlYJ1>Hd)AZ_8(2EsFD?%}dHCEPn5Ko8Ej;o5$FF z(a{CgcBrCwr3H@wY8%l)4KapL527EFGqz(JO240$R4BhG-0Nx_JXSxJCglBF>v>(y z$AM}k#VKh?CbTQ7pu={_zl(Vco(W-z3K+d+%qLe9FXZn43V_a1Pmh@Q3OI>X$HJAq z-+^S{vzY(kiE2P<=wsOvSQP>jDdzaLD&C_ne0b%uuG{5ASie-#*olk9Fv3*Mgbr5Z zNTsjoWvoY>B-m2c@H{!o6f~&lWm7)Ik0&BI#&TA%ZN$Locq-lm*ioDA83_17{t!0g zvBiALg?yO_{Bx)}F87QqQv8U4V3VhS$3h||;Wmbpq!mb5cj&rf=i7wZxnu z#N81TE!r@0t^^b%V(-wPT2Ne<^a07O)2C(E+k1E_(gYYV)w=+=pivEb=ZWc#vGrWD zEK%iNj4{L?t?&&X`yq8XdOdwZ_vLR`5kI^Gs?nTK9ndu3NvnM0nmr@6sTX*aXwD1s z-3yG8C*FovnF|>`Ee51OIrT#X!f}iWAHw2KY@vr4bZN8>B6zcbBTW4hw>@46rKjT4 z3NRvl;ppqg_6DnEKA0a!qF(Mdy!nQ1ZuosN0Va9FoT~-skZ%m6FYc@7ft;K@qtm8o zz8-YYb-2m-*)2`mC9`C=u9E8bj~sP&TH(jHyiYm$uAre+ z?JAFa_)z+Y)UIM%_F6pbw+wOH#r$Yw(~kl_a9GWF{mqAx7;A-YAlmj4rx;!~wg5?* zyrylxCa2k;VQQ$iYxOk+#q|!wkZYQIIZcyx4IAb2|9s1n+(ZJ3IIg#>5U$?Z?Hqy1 zy28Ch98#<8tRGL3kHwlh)I`B{4uV;PgFy5|ou&197WVa?;2%8o*-LFWG_t+$GS-3s za>XAk8Cw$1_N=w=m5gu(FQT$+7wt)@j(8%qnRnuW;1Sgrg8vkgKeiz_;mD`Hf=Je6 zbxECymYtT1@kQV-+1}CYdtyK~;IvBps z(7`%h2Fh7>CmXCnW#P@`y=h!aDIMbxoi6MV@7AiSR7g(1sw>x7U_S zwKJC&lWq2f2yD?MvX${{nqRQWbPSf%(BkZzlrH|med|P%Q9)ODV^W>hd)k-aFK!WMv5-N-PzIs zNp*B)?G7{%jaM1{Bk4Qco2@=l5SjLuIs>fR(lZ=XQJim)(|Fvday+oop0x$8gXt1{lGx;*_B9cl#PP)Vop=c3Gfa|!X6o_jP zYycJ1q8&W6VjU`;mT=jj0@%Tt-OiWwn_Ca|_`p^!`jT(HDWi_sUFoc8Hh%3!Z>_aD zKz`i5IEYDt7-0{z5~UhJ$Q{~>dhXTZHpyD!8UM>J}_hz6SjI??}%iL2H=uq`vHK;Qt%UF26H3y7AOpSO4c1u-W z#I<;by$&im$kICJ+JLP@)^g{{c{8sLtVZBu-XR}c@s~v;EZdn|R*uK%r9!T1;CbR~ z8`2%yCohpdHwEg*d~p*oF8?c5f_GU^l{MezboX*-^XnG}%)Lw9|N6*y2=Wj|SxHo( z>H%yI<2T6K&=<(9?kDHKcD~xW5L<)iRSoml+Y`a zC+222v#~KTG4bl#YWb|6*{i%n^u~9B-r(5l`-N4!gE81aR^F8r)H94yYTv3Qzsfz6Q^FiEvZMti_t4)CnsM0+us8myL0vGCMM3cwy$1&oe1GaH_$Jp zkx@4f(5daJozAx`*7@K5=f8cAU+%O3G?>f$)SYY_R@TBkAm?ws-N`N?Dg+VrPb7W# zM*A%E{LTulZ)dMPK>NTJ2h%c*7_@Hxt-JILuJo4q`KNAgLuoDzx>@PlO|4AM&P}6) zjzh>M4tu+)$?~`RfxI1kaBKTlMet!6;oZLUTZMOf+b?+{Ynmxv)6Zm0kj3jA8s=BU zSmfvuP&W%}n=h{IUhcZbr`d_AKQIV*_%=*Yw~0rN8rE6bSYPH#Ga* zfBvce^iBS?xc)Q#uAcSQI<0Nu8T+D-em6mjrN<` zsPDH#Qor8s^*B-VU1kKb*HfNz4^fltx95J1*)0(#JT zn45+vgxhEijz9H1h;#V`5iR@;uqk1NBPtGA*5Y(bPSkcl^f%4~$`AtKkg#{T6y9TU zXAjtMlhMcyp9*}`gE%MV#hp<(r)JZ)c~qR@(XTXVUp`l{A$k9*llR&MF!$(|-kuvgHI_QK1_SIb)7{ijmmZHQELA zl^v^f(5hV&&@pi#;S;DEBr~+T+!S>E&^ z3!osjU=p|bYg_x`ACmx1<85Bt&0sA3>972Yzw+Z>`@ettKYI&YLATtW{`{Xl^Z);o zzjx)?#LsWA1>>)=hMhU05$4aN=cLZFBuC(ZUIiIpP1ggTZ(IT>B6|mvR^(S6I|sa*&4A=qk~uIQiSv?N;{5$H zDiOt+-t6&7cI>5=gr}9zmrXj_iS)q%K`3GyX-7Ceu*X%r1<{BnU>#eKN&JoK3_N#B z+)g5E*Aall@x)yr*2EXE?KFsD=&pz%2tvFkB}ZQx(BbTd&5;T%l{SbjZsEkkZJ?7( zd6~EdPSH|aw!<4aovR|Lv9ef0wY9lv&r1{eoj37#oxO^dF0~h zQ^R8E)5Bs(z*FIbRLiAbd&y4N5OascF->zGjsn;v7L$K&!m=j$PSlK_|U-A8ttwGU4oR TaKa(@Qzh$Y#D8My#H;@vafUBw diff --git a/AGENTS.md b/AGENTS.md deleted file mode 100644 index f1fe478c4..000000000 --- a/AGENTS.md +++ /dev/null @@ -1,81 +0,0 @@ -# Aetheria Agent Instructions - -This repo is mid-migration toward CultMesh-native gameplay state. Treat access -simplicity as an architectural requirement, not a nice-to-have. - -## Canonical Typed State - -- Default to one canonical shared typed document for each gameplay state concept. - The daemon may own authority over that document, but it must not imply a - separate private daemon truth plus a copied client-facing state document. -- The shared runtime assembly defines the document. The daemon mutates/publishes - it. Unity, Electron, tools, tests, and later runtimes read the same document - type through CultMesh. -- Client input should feel like grabbing a managed typed state handle and either - reading it for display or writing/submitting through it according to authority - policy. Prediction, debounce, routing, quorum, reconciliation, and smoothing - are CultMesh responsibilities. - -Use projections only when the state shape is intentionally different: - -- hidden-information filtering; -- expensive or shared derived aggregation; -- viewport/windowed selection; -- SoA/native render or physics layout; -- lossy UI summaries; -- Eve/CultUI surface documents; -- compatibility mirrors with a named removal stage. - -"The client needs to see it" is not a projection reason. - -## Access Simplicity - -- A UI-only inline value should be derived from already accessible typed state. -- A shared UI read should usually be: define the typed document, then read it - with `client.State.Reactive()` or `client.State.Latest()`. -- A simulation feature should usually be: define the canonical typed document, - let daemon authority mutate/publish it, then read or modify that same managed - document from clients according to authority policy. -- Add named handles only when type alone cannot identify the state, such as - parameterized viewport/detail documents, multiple documents sharing one CLR - type, semantic query identity, operation policy, or native view ownership. - -## Heretek Shape - -The facade/projector/adapter/surface-builder chain is heretek now. Do not add a -stack of translation layers to recover one typed domain value. - -One adapter at a true boundary is allowed: - -- Unity GameObject presentation; -- Eve/CultUI lowering; -- legacy import; -- persistence; -- native view ownership. - -Two or more translation layers in a row mean the code is missing a CultMesh -primitive, generated typed handle, canonical document, query surface, operation -handle, state pointer, or native view descriptor. - -## Verifier Discipline - -The verifier should encode the desired architecture, not the current accidental -shape. When removing old access paths, add checks that reject their return. Do -not write verifier rules that require wrapper/session/facade/projector chains as -proof of correctness. - -Good verifier pressure: - -- client code owns `CultMeshReactiveDocument`; -- client code reads `.Current`; -- client code uses generic typed document access where possible; -- hot render paths use named SoA/native views; -- operations are typed and authority-aware; -- legacy adapters are quarantined and named for deletion. - -Bad verifier pressure: - -- requiring `LatestFoo()`/`ReactiveFoo()` wrappers for single fixed documents; -- requiring one-document session wrappers; -- requiring facade/projector/adapter/surface-builder chains; -- treating renderer-local facade indexes as gameplay-state APIs. diff --git a/Aetheria RTS.lnk b/Aetheria RTS.lnk deleted file mode 100644 index 713c0ce380c621f7001ece1f0365987ec90164a6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1163 zcmb_aT}V@57=C6el)RXv!+ykrp+!H(&8eG%z%^S-)>=yD*~i zDk!@sB%&xR5&|Qm@-A;gy7EFHB#9yri7=vzp6}o$O`)4Uc)s^N&-;GQ`+o0tJOFgX zdE9|_+78zetwdb;t{NRbXLLV$cWq4<&gxz{yR>ik)@zwMZC}~ybj2w5eLONHeYLBG z`_uW@Z8F{6jB2M{S;@+JEih~ToYLVnAfU#=ov8#GQ2;#_-dt{dY@b)0CuQa=nhe}? z%MITv(*>X+hA=vjLV`I)oXDzPj2YnXNhBjtOAWAp4c%$$TDpBDPvA!n^^%An3JZ7~ zrDzeBSD#R>-jWcChUG{)lI>Tp^GdVU?~=YM+pj*`w(HwCLevXGrf!-L{Q}Lzw&i?b z_Yz_qB8rH0#Mp*CZK79jl*^M+GIvultzbJm2%?)4389@&=wOW`y$Ir*Q;^It9OvC6 z96$%z5TkLk|$G3dWJLMg1l1H8$9UK+0HiEA<)JiK>A{mUIRpa8pwe@M|!k4%GCilaag)^5N zKK+jClb-YG#iHBKU&wcUDO3N7f1pYM^#^FpexHBtTC3}|$L1XSz3FDvV07{VdA+Dq zPE-?q{#0h7j;PVRTH0EA7MN)3X>E*I&_J%9c^0yDurf=qHhO0IR&v4@I%b|Uk;|+G wI-;2oW3=h%K$rWGyHS3yFMXqoe7i_;i|t_lgyX%_)#vLiEy@pg1(jKU0Q^VlPXGV_ diff --git a/Aetheria.Rts.Web/Client/app.ts b/Aetheria.Rts.Web/Client/app.ts deleted file mode 100644 index 9d6ad1f84..000000000 --- a/Aetheria.Rts.Web/Client/app.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { EveBrowserProviderHost } from "../node_modules/@gamecult/eve-browser-lowering/dist/index.js"; -import { mountEveElectronProvider } from "../node_modules/@gamecult/eve-electron/src/eve-electron-renderer.mjs"; - -void mountEveElectronProvider({ EveBrowserProviderHost }); diff --git a/Aetheria.Rts.Web/Electron/aetheria-cultmesh.ts b/Aetheria.Rts.Web/Electron/aetheria-cultmesh.ts deleted file mode 100644 index 02ef90bed..000000000 --- a/Aetheria.Rts.Web/Electron/aetheria-cultmesh.ts +++ /dev/null @@ -1,797 +0,0 @@ -import { encode } from "@msgpack/msgpack"; -import { performance } from "node:perf_hooks"; -import { CultMesh } from "cultmesh-ts"; -import type { - CultMeshDocumentCatalog, - CultMeshDocumentPublicationSource, - CultMeshPublicationDocumentBinding, - CultMeshOperationContext, - CultMeshQueryContext, - CultMeshSurfaceCatalogIndexDiagnostic, - CultMeshVerse, -} from "cultmesh-ts"; -import type { - CultNetDocumentPutRawMessage, - CultNetPeer, -} from "cultnet-ts"; -import { - AetheriaRtsSchemas, - createAetheriaRuntimeGameDocuments, - createAetheriaRuntimeGameOperationHandles, - createAetheriaRuntimeGameVerseHandles, - createAetheriaRuntimeGameQueryHandles, - describeAetheriaRuntimeGameLiveFeedSurface, - describeAetheriaRuntimeGameQueryHandles, - describeAetheriaRuntimeGameSurfaceCatalog, - type AetheriaRuntimeDaemonCommandReceipt, - type AssetManifestDocument, - type AuthorityStatusDocument, - type DaemonHealthDocument, - type GravityViewportResponse, - type InventoryDocument, - type ObjectsViewportResponse, - type RenderSplatsViewportResponse, - type AetheriaRuntimeGameLiveFeedDiagnostic, - type AetheriaRuntimeGameQueryDiagnostic, - type AetheriaRuntimeGameSurfaceCatalogDiagnostic, - type AetheriaRuntimeGameOperationHandles, - type AetheriaRuntimeGameVerseHandles, - type AetheriaRuntimeGameDocuments, - type AetheriaRuntimeViewportFeedRequest, - type AetheriaRuntimeViewportFeedSnapshot, - type RtsSetMoveVectorRequest, - type RtsSetTargetRequest, - type SelectedObjectDocument, - type SelectedObjectRequest, - type StarbridgeSessionDocument, - type ViewportRequest, - type ViewportResponse, -} from "./aetheria-rts-bindings.js"; -import { - readAuthorityStatusDocument, - readAssetManifestDocument, - readDaemonHealthDocument, - readGravityViewportDocument, - readInventoryDocument, - readObjectsViewportDocument, - readRenderSplatsViewportDocument, - readSelectedObjectDocument, - readStarbridgeSessionSummaryDocument, - readViewportDocument, -} from "./aetheria-rts-local-documents.js"; - -const connectionId = 0x43554c54; - -type AetheriaPublicationDocumentSpec = CultMeshPublicationDocumentBinding & { - readonly localPath: string; - readonly remoteRecordKey?: string; -}; - -type AetheriaRuntimeGameQueryExecutors = { - mapViewport: (request: ViewportRequest) => Promise; - objectsViewport: (request: ViewportRequest) => Promise; - gravityViewport: (request: ViewportRequest) => Promise; - renderSplatsViewport: (request: ViewportRequest) => Promise; - selectedObject: (request: SelectedObjectRequest) => Promise; - inventory: (request: SelectedObjectRequest) => Promise; - daemonHealth: () => Promise; - authorityStatus: () => Promise; - starbridgeSession: () => Promise; - assetManifest: () => Promise; -}; - -export type AetheriaCultMeshClientOptions = { - publicationMode?: "local" | "remote"; - snapshotTimeoutMs?: number; -}; - -export type AetheriaCultMeshDaemonTarget = { - readonly uri: string; - readonly peerId?: string; - readonly verseId?: string; - readonly role?: string; - readonly endpoints?: readonly string[]; -}; - -export type { - BodyView, - EntityStatus, - GravityViewportResponse, - GravityInfluence, - InventoryItem, - ObjectsViewportResponse, - AetheriaRuntimeViewportFeedRequest, - AetheriaRuntimeViewportFeedSnapshot, - RtsSetMoveVectorRequest, - RtsSetTargetRequest, - SelectedObjectDocument, - SelectedObjectRequest, - StarbridgeSessionDocument, - ViewObject, - ViewportRequest, - ViewportResponse, -} from "./aetheria-rts-bindings.js"; - -export class AetheriaCultMeshClient { - #peer: CultNetPeer | null = null; - private readonly verse: CultMeshVerse; - private readonly queryVerse: CultMeshVerse; - private readonly commandVerse: CultMeshVerse; - private readonly daemonTarget: Required; - - public constructor( - daemonTarget: string | AetheriaCultMeshDaemonTarget, - statePath: string, - private readonly runtimeId = "aetheria-electron-client", - options: AetheriaCultMeshClientOptions = {}, - ) { - this.daemonTarget = normalizeDaemonTarget(daemonTarget); - const publicationMode = options.publicationMode ?? "local"; - this.publicationDescription = publicationMode === "remote" ? this.daemonTarget.uri : statePath; - this.verse = CultMesh.verse("aetheria.local", this.runtimeId); - this.queryVerse = publicationMode === "remote" - ? this.verse.withRoute("network", this.publicationDescription) - : this.verse.withRoute("shared-memory", this.publicationDescription); - this.commandVerse = this.verse - .withRoute("network", this.daemonTarget.uri) - .withClaim("commander-control", { shardId: "aetheria.local" }); - this.publications = CultMesh.documentsFromPublication( - this.createPublicationSource(publicationMode), - createAetheriaPublicationDocuments(statePath, publicationMode), - { - routeHint: this.queryVerse.context.routeHint, - timeoutMs: options.snapshotTimeoutMs, - pollMs: 50, - messageIdPrefix: `${this.runtimeId}:snapshot`, - }, - ); - const executors = { - mapViewport: async (request: ViewportRequest) => - readViewportDocument(await this.fetchViewportDocument(AetheriaRtsSchemas.gameViewport, "aetheria.viewport.map", request)), - objectsViewport: async (request: ViewportRequest) => - readObjectsViewportDocument(await this.fetchViewportDocument(AetheriaRtsSchemas.objectsViewport, "aetheria.viewport.objects", request)), - gravityViewport: async (request: ViewportRequest) => - readGravityViewportDocument(await this.fetchViewportDocument(AetheriaRtsSchemas.gravityViewport, "aetheria.viewport.gravity", request)), - renderSplatsViewport: async (request: ViewportRequest) => - readRenderSplatsViewportDocument(await this.fetchViewportDocument(AetheriaRtsSchemas.renderSplatsViewport, "aetheria.viewport.render_splats", request)), - selectedObject: async (request: SelectedObjectRequest) => - readSelectedObjectDocument(await this.fetchIndexedDocument(AetheriaRtsSchemas.selectedObject, "aetheria.object.selected", request.entityIndex)), - inventory: async (request: SelectedObjectRequest) => - readInventoryDocument(await this.fetchIndexedDocument(AetheriaRtsSchemas.inventory, "aetheria.inventory", request.entityIndex)), - 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.queryExecutors = executors; - this.queries = createAetheriaRuntimeGameQueryHandles( - executors, - this.queryVerse.context.routeHint, - { - objectsViewport: CultMesh.pollingQueryWatcher(executors.objectsViewport, { intervalMs: 50 }), - gravityViewport: CultMesh.pollingQueryWatcher(executors.gravityViewport, { intervalMs: 50 }), - renderSplatsViewport: CultMesh.pollingQueryWatcher(executors.renderSplatsViewport, { intervalMs: 50 }), - selectedObject: CultMesh.pollingQueryWatcher(executors.selectedObject, { intervalMs: 50 }), - inventory: CultMesh.pollingQueryWatcher(executors.inventory, { intervalMs: 50 }), - daemonHealth: CultMesh.pollingQueryWatcher(executors.daemonHealth, { intervalMs: 250 }), - authorityStatus: CultMesh.pollingQueryWatcher(executors.authorityStatus, { intervalMs: 250 }), - starbridgeSession: CultMesh.pollingQueryWatcher(executors.starbridgeSession, { intervalMs: 250 }), - assetManifest: CultMesh.pollingQueryWatcher(executors.assetManifest, { intervalMs: 1000 }), - }, - ); - this.documents = createAetheriaRuntimeGameDocuments( - this.queryVerse.context.routeHint, - { - daemonFrame: async () => this.fetchLatestFrameDocument(), - daemonHealth: async () => this.fetchDaemonHealthDocument(), - authorityPolicy: async () => this.fetchAuthorityPolicyDocument(), - starbridgeSession: async () => this.fetchStarbridgeSessionSummaryDocument(), - assetManifest: async () => this.fetchAssetManifestDocument(), - }, - ); - this.operations = createAetheriaRuntimeGameOperationHandles( - (commandId, issuedAtUtc, command, context) => - this.sendCommandDocument(commandId, issuedAtUtc, command, context), - ); - this.aetheria = createAetheriaRuntimeGameVerseHandles( - this.queryVerse.context, - this.commandVerse.context, - this.queries, - this.operations, - this.documents, - ); - } - - private readonly publicationDescription: string; - private readonly publications: CultMeshDocumentCatalog; - private readonly queryExecutors: AetheriaRuntimeGameQueryExecutors; - private readonly queries: ReturnType; - private readonly documents: AetheriaRuntimeGameDocuments; - private readonly operations: AetheriaRuntimeGameOperationHandles; - private readonly aetheria: AetheriaRuntimeGameVerseHandles; - - public async close(): Promise { - this.#peer?.close(); - this.#peer = null; - } - - public async waitForFrame(timeoutMs: number): Promise { - const started = Date.now(); - let lastError = ""; - while (Date.now() - started < timeoutMs) { - try { - await this.mapViewport(CultMesh.viewportRequest(CultMesh.rectFromBounds(-1000, -1000, 1000, 1000))); - return; - } catch (error) { - lastError = error instanceof Error ? error.message : String(error); - await delay(250); - } - } - - throw new Error(`Timed out waiting for Aetheria CultMesh frame at ${this.daemonTarget.uri}. ${lastError}`); - } - - public async mapViewport(request: ViewportRequest): Promise { - return this.aetheria.zone().viewport.within(request); - } - - public async objectsViewport(request: ViewportRequest): Promise { - return this.aetheria.zone().objects.visibleWithin(request); - } - - public async gravityViewport(request: ViewportRequest): Promise { - return this.aetheria.zone().gravity.within(request); - } - - public async renderSplatsViewport(request: ViewportRequest): Promise { - return this.aetheria.zone().renderSplats.within(request); - } - - public async selectedObject(request: SelectedObjectRequest): Promise { - return this.aetheria.selectedObject(request.entityIndex); - } - - public async inventory(request: SelectedObjectRequest): Promise { - return this.aetheria.inventory(request.entityIndex); - } - - public async daemonHealth(): Promise { - return this.aetheria.daemon.health(); - } - - public async authorityStatus(): Promise { - return this.aetheria.daemon.authorityStatus(); - } - - public async starbridgeSession(): Promise { - return this.aetheria.daemon.starbridgeSession(); - } - - public async assetManifest(): Promise { - return this.aetheria.daemon.assetManifest(); - } - - public queryDiagnostics(): Readonly> { - return describeAetheriaRuntimeGameQueryHandles(this.queries); - } - - public liveFeedDiagnostics(): Readonly> { - return { - viewportFeed: describeAetheriaRuntimeGameLiveFeedSurface(this.createViewportFeed()), - }; - } - - public surfaceCatalogDiagnostics(): AetheriaRuntimeGameSurfaceCatalogDiagnostic { - const catalog = describeAetheriaRuntimeGameSurfaceCatalog(this.queries, this.operations, this.documents); - return CultMesh.describeSurfaceCatalog(catalog.catalogId, [ - ...catalog.surfaces, - CultMesh.describeSurface(this.createViewportFeed()), - ]); - } - - public surfaceCatalogIndexDiagnostics(): CultMeshSurfaceCatalogIndexDiagnostic { - return CultMesh.surfaceCatalogIndex(this.surfaceCatalogDiagnostics()); - } - - public watchViewportFeed( - request: AetheriaRuntimeViewportFeedRequest, - callback: (snapshot: AetheriaRuntimeViewportFeedSnapshot) => void, - ): () => void { - return this.createViewportFeed().watch(request, this.queryContext(), callback); - } - - public async setMoveVector(request: RtsSetMoveVectorRequest): Promise { - return this.aetheria.entity(request.actorEntityKey).pilot.move( - CultMesh.vec2(request.directionX, request.directionY), - { - scalar: request.scalar, - observedFrameId: request.observedFrameId, - }, - ); - } - - public async setTarget(request: RtsSetTargetRequest): Promise { - return this.aetheria.entity(request.actorEntityKey).pilot.target(request.targetEntityKey, { - observedFrameId: request.observedFrameId, - }); - } - - private async fetchLatestFrameDocument(): Promise { - return this.fetchPublicationDocument(AetheriaRtsSchemas.daemonFrame); - } - - private async fetchDaemonHealthDocument(): Promise { - return this.fetchPublicationDocument(AetheriaRtsSchemas.daemonHealth); - } - - private async fetchAuthorityPolicyDocument(): Promise { - return this.fetchPublicationDocument(AetheriaRtsSchemas.verseAuthorityPolicy); - } - - private async fetchStarbridgeSessionSummaryDocument(): Promise { - return this.fetchPublicationDocument(AetheriaRtsSchemas.starbridgeSessionSummary); - } - - private async fetchAssetManifestDocument(): Promise { - return this.fetchPublicationDocument(AetheriaRtsSchemas.assetManifest); - } - - private async fetchViewportDocument( - schemaId: string, - recordPrefix: string, - request: ViewportRequest, - ): Promise { - const recordKey = managedViewportRecordKey(recordPrefix, request); - const document = CultMesh.documentFromPublication( - { - kind: "peer-snapshot", - peer: () => this.peer(), - endpoint: this.resolvedRudpEndpoint(), - }, - schemaId, - recordKey, - { - documentId: recordKey, - routeHint: this.queryVerse.context.routeHint, - sourceId: recordKey, - timeoutMs: 1500, - pollMs: 50, - messageIdPrefix: `${this.runtimeId}:viewport`, - }, - ); - - return retryTransientPublicationRead(() => document.latest(this.queryContext())); - } - - private async fetchIndexedDocument( - schemaId: string, - recordPrefix: string, - entityIndex: number, - ): Promise { - const recordKey = `${recordPrefix}.${Math.trunc(entityIndex)}`; - const document = CultMesh.documentFromPublication( - { - kind: "peer-snapshot", - peer: () => this.peer(), - endpoint: this.resolvedRudpEndpoint(), - }, - schemaId, - recordKey, - { - documentId: recordKey, - routeHint: this.queryVerse.context.routeHint, - sourceId: recordKey, - timeoutMs: 1500, - pollMs: 50, - messageIdPrefix: `${this.runtimeId}:indexed-document`, - }, - ); - - return retryTransientPublicationRead(() => document.latest(this.queryContext())); - } - - private fetchPublicationDocument(schemaId: string): Promise { - return retryTransientPublicationRead(() => this.publications.latest({ schemaId }, this.queryContext())); - } - - private createPublicationSource( - mode: "local" | "remote", - ): (binding: CultMeshPublicationDocumentBinding) => CultMeshDocumentPublicationSource { - if (mode === "remote") { - return () => ({ - kind: "peer-snapshot", - peer: () => this.peer(), - endpoint: this.resolvedRudpEndpoint(), - }); - } - - return binding => ({ - kind: "single-file", - path: (binding as AetheriaPublicationDocumentSpec).localPath, - }); - } - - private createViewportFeed() { - return CultMesh.liveFeed( - "gamecult.aetheria.viewport_feed.v1", - async (request, context) => { - const objects = await this.queries.objectsViewport.execute(request.viewport, context); - return this.createViewportFeedSnapshot(request, objects); - }, - { - sources: [ - ...this.queries.objectsViewport.sources, - ...this.queries.gravityViewport.sources, - ...this.queries.daemonHealth.sources, - ...this.queries.authorityStatus.sources, - ...this.queries.starbridgeSession.sources, - ...this.queries.assetManifest.sources, - ], - routeHint: this.queryVerse.context.routeHint, - watchFeed: (request, context, callback) => { - let disposed = false; - let sampling = false; - const unsubscribe = this.queries.objectsViewport.watch(request.viewport, context, objects => { - if (disposed || sampling) { - return; - } - - sampling = true; - void this.createViewportFeedSnapshot(request, objects) - .then(snapshot => { - if (!disposed) { - callback(snapshot); - } - }) - .finally(() => { - sampling = false; - }); - }); - - return () => { - disposed = true; - unsubscribe(); - }; - }, - }, - ); - } - - private async createViewportFeedSnapshot( - request: AetheriaRuntimeViewportFeedRequest, - objects: ObjectsViewportResponse, - ): Promise { - const startedAt = performance.now(); - const selectedEntityIndex = request.selectedEntityIndex ?? -1; - const [ - gravity, - daemonHealth, - authorityStatus, - starbridgeSession, - assetManifest, - selectedObject, - inventory, - ] = await Promise.all([ - this.queryExecutors.gravityViewport(request.viewport), - this.queryExecutors.daemonHealth(), - this.queryExecutors.authorityStatus(), - this.queryExecutors.starbridgeSession(), - this.queryExecutors.assetManifest(), - selectedEntityIndex >= 0 - ? this.queryExecutors.selectedObject({ entityIndex: selectedEntityIndex }) - : Promise.resolve(null), - selectedEntityIndex >= 0 - ? this.queryExecutors.inventory({ entityIndex: selectedEntityIndex }) - : Promise.resolve(null), - ]); - - return { - viewport: composeViewport(objects, gravity), - selectedObject, - inventory, - daemonHealth, - authorityStatus, - starbridgeSession, - assetManifest, - receivedAtUtc: new Date().toISOString(), - sampleMs: performance.now() - startedAt, - }; - } - - private queryContext(): CultMeshQueryContext { - return this.queryVerse.queryContext(); - } - - private async sendCommandDocument( - commandId: string, - issuedAtUtc: string, - command: unknown[], - context: CultMeshOperationContext, - ): Promise { - const message: CultNetDocumentPutRawMessage = { - schemaVersion: "cultnet.document_put_raw.v0", - messageId: commandId, - document: { - schemaId: AetheriaRtsSchemas.daemonCommand, - recordKey: `daemon:commands:${stableToken(commandId)}:${AetheriaRtsSchemas.daemonCommand}`, - storedAt: issuedAtUtc, - payloadEncoding: "messagepack", - payload: encode(command), - sourceRuntimeId: context.runtimeId, - sourceRole: "aetheria-client", - tags: ["aetheria"], - }, - }; - const peer = await this.peer(); - peer.send(message); - await delay(80); - } - - private async peer(): Promise { - if (this.#peer) - return this.#peer; - const peers = CultMesh.createPeerCatalog(); - const leases = CultMesh.createAuthorityLeaseCatalog(); - const leaseId = `${this.daemonTarget.peerId}:authority`; - peers.upsert({ - peerId: this.daemonTarget.peerId, - verseId: this.daemonTarget.verseId, - endpoints: this.daemonTarget.endpoints, - roles: [this.daemonTarget.role], - authorityLeaseId: leaseId, - }); - leases.upsert({ - leaseId, - verseId: this.daemonTarget.verseId, - peerId: this.daemonTarget.peerId, - roles: [this.daemonTarget.role], - validFrom: new Date(Date.now() - 60_000), - expiresAt: new Date(Date.now() + 60_000), - }); - this.#peer = await CultMesh.createRudpPeerForAuthorizedPeer( - this.runtimeId, - connectionId, - peers, - leases, - this.daemonTarget.verseId, - this.daemonTarget.role, - { - connectTimeoutMs: 2000, - maxFragmentBytes: 1200, - maxPendingReliablePackets: 512, - }, - ); - this.#peer.on("close", () => { - this.#peer = null; - }); - return this.#peer; - } - - private resolvedRudpEndpoint(): string { - const endpoint = this.daemonTarget.endpoints.find(value => value.toLowerCase().startsWith("rudp://")); - if (!endpoint) { - throw new Error(`Daemon ${this.daemonTarget.uri} has no resolved RUDP transport endpoint. Resolve it through Odin/CultMesh before opening a transport peer.`); - } - return endpoint; - } -} - -function normalizeDaemonTarget(target: string | AetheriaCultMeshDaemonTarget): Required { - const value = typeof target === "string" - ? { uri: target } - : target; - if (!value.uri.trim()) { - throw new Error("Aetheria CultMesh daemon target URI is required."); - } - if (value.uri.toLowerCase().startsWith("rudp://")) { - throw new Error("Aetheria daemon targets must be CultMesh URIs; raw RUDP endpoints belong in resolved peer-card endpoints."); - } - return { - uri: value.uri, - peerId: value.peerId?.trim() || "aetheria-daemon", - verseId: value.verseId?.trim() || "aetheria.local", - role: value.role?.trim() || "aetheria-daemon", - endpoints: value.endpoints ?? [], - }; -} - -function managedViewportRecordKey(prefix: string, request: ViewportRequest): string { - const minX = Math.min(finiteNumber(request.minX), finiteNumber(request.maxX)); - const minY = Math.min(finiteNumber(request.minY), finiteNumber(request.maxY)); - const maxX = Math.max(finiteNumber(request.minX), finiteNumber(request.maxX)); - const maxY = Math.max(finiteNumber(request.minY), finiteNumber(request.maxY)); - return `${prefix}.${viewportToken(minX)}.${viewportToken(minY)}.${viewportToken(maxX)}.${viewportToken(maxY)}`; -} - -function viewportToken(value: number): string { - const rounded = Math.round(value * 1000) / 1000; - return rounded - .toString() - .replace("-", "n") - .replace(".", "p"); -} - -function finiteNumber(value: unknown): number { - return typeof value === "number" && Number.isFinite(value) ? value : 0; -} - -async function retryTransientPublicationRead(read: () => Promise): Promise { - let lastError: unknown; - for (let attempt = 0; attempt < 40; attempt += 1) { - try { - return await read(); - } catch (error) { - lastError = error; - if (!isTransientPublicationReadError(error)) - throw error; - await delay(25 + attempt * 8); - } - } - throw lastError; -} - -function isTransientPublicationReadError(error: unknown): boolean { - const message = error instanceof Error ? error.message : String(error); - return message.includes("EBUSY") || - message.includes("EPERM") || - message.includes("ENOENT") || - message.includes("did not contain schema"); -} - -function stableToken(value: string): string { - return value - .replace(/[^a-zA-Z0-9]+/gu, "-") - .replace(/^-+|-+$/gu, "") - .replace(/--+/gu, "-") - .toLowerCase() || "empty"; -} - -function composeViewport( - objects: ObjectsViewportResponse, - gravity: GravityViewportResponse, -): ViewportResponse { - return { - schema: "gamecult.aetheria.game_viewport.v1", - frameId: objects.frameId, - publishedAtUtc: objects.publishedAtUtc, - simulationTimeSeconds: objects.simulationTimeSeconds, - runId: objects.runId, - zoneIndex: objects.zoneIndex, - zoneName: objects.zoneName, - currentEntityKey: objects.currentEntityKey, - viewport: objects.viewport, - controlledEntityIndices: objects.controlledEntityIndices, - objects: objects.objects, - gravityInfluences: gravity.gravityInfluences, - bodies: gravity.bodies, - }; -} - -function createAetheriaPublicationDocuments( - statePath: string, - mode: "local" | "remote", -): readonly AetheriaPublicationDocumentSpec[] { - const documents: readonly AetheriaPublicationDocumentSpec[] = [ - { - ...CultMesh.publicationDocument( - AetheriaRtsSchemas.daemonFrame, - "daemon:aetheria.frame.latest.v1", - { - documentId: "daemon:aetheria.frame.latest.v1", - sourceId: "daemon:aetheria.frame.latest.v1", - }, - ), - localPath: statePath, - }, - { - ...CultMesh.publicationDocument( - AetheriaRtsSchemas.daemonHealth, - "daemon:aetheria.health.latest.v1", - { - documentId: "daemon:aetheria.health.latest.v1", - sourceId: "daemon:aetheria.health.latest.v1", - }, - ), - localPath: statePath, - remoteRecordKey: "daemon:aetheria.health.v1", - }, - { - ...CultMesh.publicationDocument( - AetheriaRtsSchemas.verseAuthorityPolicy, - "daemon:aetheria.authority.policy.latest.v1", - { - documentId: "daemon:aetheria.authority.policy.latest.v1", - sourceId: "daemon:aetheria.authority.policy.latest.v1", - }, - ), - localPath: statePath, - remoteRecordKey: "global:aetheria.verse_authority_policy.v1", - }, - { - ...CultMesh.publicationDocument( - AetheriaRtsSchemas.starbridgeSessionSummary, - "daemon:aetheria.starbridge.session.latest.v1", - { - documentId: "daemon:aetheria.starbridge.session.latest.v1", - sourceId: "daemon:aetheria.starbridge.session.latest.v1", - }, - ), - localPath: statePath, - }, - { - ...CultMesh.publicationDocument( - AetheriaRtsSchemas.assetManifest, - "daemon:aetheria.asset_manifest.latest.v1", - { - documentId: "daemon:aetheria.asset_manifest.latest.v1", - sourceId: "daemon:aetheria.asset_manifest.latest.v1", - }, - ), - localPath: statePath, - }, - ]; - - return documents.map(document => mode === "remote" && document.remoteRecordKey - ? { - ...document, - recordKey: document.remoteRecordKey, - } - : document); -} - -function readField(value: unknown, property: string, index: number): unknown { - if (Array.isArray(value)) - return value[index]; - if (value && typeof value === "object") { - const record = value as Record; - if (property in record) - return record[property]; - const pascal = property.charAt(0).toUpperCase() + property.slice(1); - if (pascal in record) - return record[pascal]; - } - return undefined; -} - -function arrayValue(value: unknown): unknown[] { - return Array.isArray(value) ? value : []; -} - -function normalizeCommandPayload(payload: Record | undefined): Record { - const result: Record = {}; - for (const [key, value] of Object.entries(payload ?? {})) { - if (value == null) - continue; - result[key] = typeof value === "string" ? value : String(value); - } - return result; -} - -function objectValue(value: unknown): Record { - return value && typeof value === "object" && !Array.isArray(value) - ? value as Record - : {}; -} - -function stringOr(value: unknown, fallback: string): string { - return typeof value === "string" ? value : fallback; -} - -function numberOr(value: unknown, fallback: number): number { - return typeof value === "number" && Number.isFinite(value) ? value : fallback; -} - -function stringRecord(value: unknown): Record { - const source = objectValue(value); - const result: Record = {}; - for (const [key, entry] of Object.entries(source)) { - if (entry == null) - continue; - result[key] = typeof entry === "string" ? entry : String(entry); - } - return result; -} - -function delay(milliseconds: number): Promise { - return new Promise(resolve => setTimeout(resolve, milliseconds)); -} diff --git a/Aetheria.Rts.Web/Electron/aetheria-rts-bindings.ts b/Aetheria.Rts.Web/Electron/aetheria-rts-bindings.ts deleted file mode 100644 index 751958e79..000000000 --- a/Aetheria.Rts.Web/Electron/aetheria-rts-bindings.ts +++ /dev/null @@ -1,58 +0,0 @@ -import type { CultMeshViewportRequest } from "cultmesh-ts"; - -export { - AetheriaRtsIpcChannels, - AetheriaRtsSchemas, - AetheriaRuntimeDaemonCommandKinds, - aetheriaRuntimeEveCommandDocumentSlots, - createAetheriaRuntimeGameDocuments, - createAetheriaRuntimeGameOperationHandles, - createAetheriaRuntimeGameQueryHandles, - createAetheriaRuntimeGameStatePointers, - createAetheriaRuntimeGameVerseHandles, - describeAetheriaRuntimeGameLiveFeedSurface, - describeAetheriaRuntimeGameQueryHandles, - describeAetheriaRuntimeGameSurfaceCatalog, -} from "./aetheria-rts-generated-bindings.js"; - -export type { - AetheriaRuntimeDaemonCommandReceipt, - AetheriaRuntimeGameLiveFeedDiagnostic, - AetheriaRuntimeGameDocumentResolvers, - AetheriaRuntimeGameDocuments, - AetheriaRuntimeGameOperationHandles, - AetheriaRuntimeGameQueryDiagnostic, - AetheriaRuntimeGameStatePointerResolvers, - AetheriaRuntimeGameStatePointers, - AetheriaRuntimeGameSurfaceCatalogDiagnostic, - AetheriaRuntimeGameVerseHandles, - AetheriaRuntimeSelectedObjectRequest as SelectedObjectRequest, - AetheriaRuntimeSetMoveVectorRequest as RtsSetMoveVectorRequest, - AetheriaRuntimeSetTargetRequest as RtsSetTargetRequest, - AetheriaRuntimeViewportFeedRequest, - AetheriaRuntimeViewportFeedSnapshot, - AssetManifestEntry, - AssetManifestDocument, - AssetRef, - AuthorityRuleDocument, - AuthorityStatusDocument, - BodyView, - DaemonHealthDocument, - EntityStatus, - GravityInfluence, - GravityViewportResponse, - InventoryItem, - InventoryDocument, - ObjectsViewportResponse, - RenderSplatsViewportResponse, - SelectedObjectDocument, - StarbridgeBaseStatusDocument, - StarbridgeRuntimeRoleDocument, - StarbridgeSessionDocument, - StarbridgeStationStockItemDocument, - StarbridgeWaveForecastDocument, - ViewObject, - ViewportResponse, -} from "./aetheria-rts-generated-bindings.js"; - -export type ViewportRequest = CultMeshViewportRequest; diff --git a/Aetheria.Rts.Web/Electron/aetheria-rts-generated-bindings.ts b/Aetheria.Rts.Web/Electron/aetheria-rts-generated-bindings.ts deleted file mode 100644 index c4c7b9292..000000000 --- a/Aetheria.Rts.Web/Electron/aetheria-rts-generated-bindings.ts +++ /dev/null @@ -1,2596 +0,0 @@ -// -// Generated by scripts/generate-rts-bindings.mjs from Aetheria C# MessagePack documents. - -import { CultMesh } 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 = { - daemonCommand: "gamecult.aetheria.daemon_command.v1", - eveCommand: "gamecult.eve.command.v1", - daemonFrame: "gamecult.aetheria.daemon_frame.v1", - daemonHealth: "gamecult.aetheria.daemon_health.v1", - assetManifest: "gamecult.aetheria.asset_manifest.v1", - gameViewport: "gamecult.aetheria.game_viewport.v1", - objectsViewport: "gamecult.fields.objects.v1", - gravityViewport: "gamecult.fields.gravity.v1", - renderSplatsViewport: "gamecult.fields.splats.v1", - currentZone: "gamecult.aetheria.current_zone.v1", - currentEntity: "gamecult.aetheria.current_entity.v1", - currentDocking: "gamecult.aetheria.current_docking.v1", - stationRefit: "gamecult.aetheria.station_refit.v1", - sectorMap: "gamecult.aetheria.sector_map.v1", - zoneRender: "gamecult.aetheria.zone_render.v1", - selectedObject: "gamecult.aetheria.selected_object.v1", - inventory: "gamecult.aetheria.inventory.v1", - starbridgeScenario: "gamecult.aetheria.starbridge_scenario.v1", - starbridgeSession: "gamecult.aetheria.starbridge_session.v1", - starbridgeSessionSummary: "gamecult.aetheria.starbridge_session_summary.v1", - verseAuthorityPolicy: "gamecult.aetheria.verse_authority_policy.v1", - authorityLease: "gamecult.aetheria.authority_lease.v1", -} as const; - -export const AetheriaRuntimeDaemonCommandKinds = { - none: 0, - ping: 1, - setTarget: 2, - clearTarget: 3, - targetNearest: 4, - targetNext: 5, - targetPrevious: 6, - targetReticle: 7, - setMoveVector: 8, - setLookDirection: 9, - setTractorPower: 10, - fireWeaponGroup: 11, - setWeaponGroupActive: 12, - setWeaponGroupMembership: 13, - setBehaviorActive: 14, - activateConsumable: 15, - sensorPing: 16, - setItemEnabled: 17, - setItemOverrideShutdown: 18, - setThermotoggleTargetTemperature: 19, - pickUpLoot: 20, - restoreLoadout: 21, - dock: 22, - dockNearest: 23, - undock: 24, - interact: 25, - setDockedCurrentShip: 26, - towToStation: 27, - enterWormhole: 28, - tradePurchase: 29, - transferCargoItem: 30, - equipItem: 31, - storeItem: 32, - toggleHullConductivity: 33, - setEntityName: 34, - destroyEntity: 35, - setOverrideShutdown: 36, - setHeatsinksEnabled: 37, - setShutdownPerformance: 38, - toggleShieldEnabled: 39, - issueAgentTask: 40, - cancelAgentTask: 41, -} as const; - -export const AetheriaRuntimeEveCommandKinds = { - unknown: 0, - catalogRefresh: 1, - operationsRefresh: 2, - playerSettingsRefresh: 3, - setPlayerName: 4, - cycleTemperatureUnit: 5, - decrementSignificantDigits: 6, - incrementSignificantDigits: 7, - cycleNebulaQuality: 8, - toggleShowAsteroidsInMinimap: 9, - inputSettingsRefresh: 10, - beginInputCapture: 11, - cancelInputCapture: 12, - setBindingOverride: 13, - toggleActionBar: 14, - setActionBarEnabled: 15, - saveLoadoutTemplate: 16, - verseHostRefresh: 17, - cycleVerseHostVisibility: 18, - tradeValuePolicyRefresh: 19, - setTradeValueQualityMinimum: 20, - setTradeValueQualityMaximum: 21, - setTradeValueQualityExponent: 22, - setTradeValueTierQuality: 23, - mainMenuContinueRun: 24, - mainMenuNewGame: 25, - mainMenuShowSettings: 26, - mainMenuQuit: 27, - mainMenuShowPlayerSettings: 28, - mainMenuShowVerseSettings: 29, - mainMenuShowInputSettings: 30, - mainMenuBackToMain: 31, - mainMenuBackToSettings: 32, - mainMenuOpenRuntimeInputScreen: 33, -} as const; - -export const AetheriaRtsIpcChannels = { - mapViewport: "aetheria-rts:map-viewport", - objectsViewport: "aetheria-rts:objects-viewport", - gravityViewport: "aetheria-rts:gravity-viewport", - renderSplatsViewport: "aetheria-rts:render-splats-viewport", - viewportFeed: "aetheria-rts:viewport-feed", - viewportFeedStop: "aetheria-rts:viewport-feed-stop", - viewportFeedUpdate: "aetheria-rts:viewport-feed-update", - selectedObject: "aetheria-rts:selected-object", - inventory: "aetheria-rts:inventory", - daemonHealth: "aetheria-rts:daemon-health", - authorityStatus: "aetheria-rts:authority-status", - starbridgeSession: "aetheria-rts:starbridge-session", - assetManifest: "aetheria-rts:asset-manifest", - setMoveVector: "aetheria-rts:set-move-vector", - setTarget: "aetheria-rts:set-target", - surfaceCatalog: "aetheria-rts:surface-catalog", - surfaceCatalogIndex: "aetheria-rts:surface-catalog-index", - eveSurface: "eve:surface", - eveProviderAdvertisement: "eve:provider-advertisement", - eveDocument: "eve:embedded-document", - submitEveCommand: "eve:submit-command", - windowControl: "eve-electron:window-control", - health: "aetheria-rts:health", -} as const; - -export type AetheriaRuntimeDaemonCommandDocumentSlot = - | "schema" - | "commandId" - | "clientId" - | "issuedAtUtc" - | "sessionId" - | "observedFrameId" - | "kind" - | "actorEntityKey" - | "targetEntityKey" - | "targetZoneIndex" - | "equipmentIndex" - | "behaviorIndex" - | "weaponGroup" - | "positionX" - | "positionY" - | "positionZ" - | "directionX" - | "directionY" - | "scalarValue" - | "textValue" - | "cargoTransfer" - | "tradePurchase" - | "lootPickup" - | "loadoutRestore" - | "equipmentTransfer" - | "storeItem" - | "authorRuntimeId" - | "subjectKey" - | "claimKind" - | "agentTask"; - -export const aetheriaRuntimeDaemonCommandDocumentSlots: Record = { - schema: 0, - commandId: 1, - clientId: 2, - issuedAtUtc: 3, - sessionId: 4, - observedFrameId: 5, - kind: 6, - actorEntityKey: 7, - targetEntityKey: 8, - targetZoneIndex: 9, - equipmentIndex: 10, - behaviorIndex: 11, - weaponGroup: 12, - positionX: 13, - positionY: 14, - positionZ: 15, - directionX: 16, - directionY: 17, - scalarValue: 18, - textValue: 19, - cargoTransfer: 21, - tradePurchase: 22, - lootPickup: 23, - loadoutRestore: 24, - equipmentTransfer: 25, - storeItem: 26, - authorRuntimeId: 27, - subjectKey: 28, - claimKind: 29, - agentTask: 30, -}; - -export type AetheriaRuntimeEveCommandDocumentSlot = - | "schema" - | "commandId" - | "providerId" - | "surfaceId" - | "command" - | "issuedAtUtc" - | "clientId" - | "playerSettings" - | "loadoutTemplate" - | "inputSettings" - | "tradeValuePolicy" - | "payload" - | "operation"; - -export const aetheriaRuntimeEveCommandDocumentSlots: Record = { - schema: 0, - commandId: 1, - providerId: 2, - surfaceId: 3, - command: 4, - issuedAtUtc: 5, - clientId: 6, - playerSettings: 7, - loadoutTemplate: 8, - inputSettings: 9, - tradeValuePolicy: 10, - payload: 16, - operation: 17, -}; - -export type AetheriaRuntimeDaemonFrameDocumentSlot = - | "schema" - | "daemonId" - | "sessionId" - | "frameId" - | "publishedAtUtc" - | "simulationTimeSeconds" - | "fixedDeltaSeconds" - | "isAuthoritative" - | "stateSource" - | "run" - | "capabilities" - | "appliedCommandIds" - | "rejectedCommandIds" - | "accountedCommandIds" - | "cumulativeAppliedCommandIds" - | "cumulativeRejectedCommandIds" - | "importedFactIds" - | "rejectedImportedFactIds" - | "duplicateImportedFactIds" - | "cumulativeImportedFactIds" - | "cumulativeRejectedImportedFactIds" - | "renderSettings" - | "simulationSettings"; - -export const aetheriaRuntimeDaemonFrameDocumentSlots: Record = { - schema: 0, - daemonId: 1, - sessionId: 2, - frameId: 3, - publishedAtUtc: 4, - simulationTimeSeconds: 5, - fixedDeltaSeconds: 6, - isAuthoritative: 7, - stateSource: 8, - run: 9, - capabilities: 10, - appliedCommandIds: 11, - rejectedCommandIds: 12, - accountedCommandIds: 13, - cumulativeAppliedCommandIds: 14, - cumulativeRejectedCommandIds: 15, - importedFactIds: 16, - rejectedImportedFactIds: 17, - duplicateImportedFactIds: 18, - cumulativeImportedFactIds: 19, - cumulativeRejectedImportedFactIds: 20, - renderSettings: 21, - simulationSettings: 22, -}; - -export type AetheriaRuntimeDaemonHealthDocumentSlot = - | "schema" - | "daemonId" - | "verseId" - | "publishedAtUtc" - | "statePath" - | "frameId" - | "observedCommandCount" - | "appliedCommandCount" - | "rejectedCommandCount" - | "status" - | "publicationSource" - | "transport" - | "commandBoundaryPath"; - -export const aetheriaRuntimeDaemonHealthDocumentSlots: Record = { - schema: 0, - daemonId: 1, - verseId: 2, - publishedAtUtc: 3, - statePath: 4, - frameId: 5, - observedCommandCount: 6, - appliedCommandCount: 7, - rejectedCommandCount: 8, - status: 9, - publicationSource: 10, - transport: 11, - commandBoundaryPath: 12, -}; - -export type AetheriaRuntimeAssetRefSlot = - | "assetKey" - | "kind" - | "uri" - | "transport" - | "contentHash" - | "mimeType" - | "metadata"; - -export const aetheriaRuntimeAssetRefSlots: Record = { - assetKey: 0, - kind: 1, - uri: 2, - transport: 3, - contentHash: 4, - mimeType: 5, - metadata: 6, -}; - -export type AetheriaRuntimeAssetManifestDocumentSlot = - | "schema" - | "publishedAtUtc" - | "runId" - | "baseUri" - | "assets"; - -export const aetheriaRuntimeAssetManifestDocumentSlots: Record = { - schema: 0, - publishedAtUtc: 1, - runId: 2, - baseUri: 3, - assets: 4, -}; - -export type AetheriaRuntimeAssetManifestEntrySlot = - | "ref" - | "sizeBytes" - | "width" - | "height" - | "tags"; - -export const aetheriaRuntimeAssetManifestEntrySlots: Record = { - ref: 0, - sizeBytes: 1, - width: 2, - height: 3, - tags: 4, -}; - -export type AetheriaRuntimeGameViewportDocumentSlot = - | "schema" - | "frameId" - | "publishedAtUtc" - | "simulationTimeSeconds" - | "runId" - | "zoneIndex" - | "zoneName" - | "currentEntityKey" - | "viewport" - | "controlledEntityIndices" - | "objects" - | "gravityInfluences" - | "bodies"; - -export const aetheriaRuntimeGameViewportDocumentSlots: Record = { - schema: 0, - frameId: 1, - publishedAtUtc: 2, - simulationTimeSeconds: 3, - runId: 4, - zoneIndex: 5, - zoneName: 6, - currentEntityKey: 7, - viewport: 8, - controlledEntityIndices: 9, - objects: 10, - gravityInfluences: 11, - bodies: 12, -}; - -export type AetheriaRuntimeObjectsViewportDocumentSlot = - | "schema" - | "frameId" - | "publishedAtUtc" - | "simulationTimeSeconds" - | "runId" - | "zoneIndex" - | "zoneName" - | "currentEntityKey" - | "viewport" - | "controlledEntityIndices" - | "objects"; - -export const aetheriaRuntimeObjectsViewportDocumentSlots: Record = { - schema: 0, - frameId: 1, - publishedAtUtc: 2, - simulationTimeSeconds: 3, - runId: 4, - zoneIndex: 5, - zoneName: 6, - currentEntityKey: 7, - viewport: 8, - controlledEntityIndices: 9, - objects: 10, -}; - -export type AetheriaRuntimeGravityViewportDocumentSlot = - | "schema" - | "frameId" - | "publishedAtUtc" - | "simulationTimeSeconds" - | "runId" - | "zoneIndex" - | "zoneName" - | "viewport" - | "gravityInfluences" - | "bodies" - | "terrainRadius" - | "terrainDepth" - | "terrainDepthExponent" - | "terrainWaveFrequency"; - -export const aetheriaRuntimeGravityViewportDocumentSlots: Record = { - schema: 0, - frameId: 1, - publishedAtUtc: 2, - simulationTimeSeconds: 3, - runId: 4, - zoneIndex: 5, - zoneName: 6, - viewport: 7, - gravityInfluences: 8, - bodies: 9, - terrainRadius: 10, - terrainDepth: 11, - terrainDepthExponent: 12, - terrainWaveFrequency: 13, -}; - -export type AetheriaRuntimeRenderSplatsViewportDocumentSlot = - | "schema" - | "frameId" - | "publishedAtUtc" - | "simulationTimeSeconds" - | "runId" - | "zoneIndex" - | "zoneName" - | "viewport" - | "layers" - | "splats"; - -export const aetheriaRuntimeRenderSplatsViewportDocumentSlots: Record = { - schema: 0, - frameId: 1, - publishedAtUtc: 2, - simulationTimeSeconds: 3, - runId: 4, - zoneIndex: 5, - zoneName: 6, - viewport: 7, - layers: 8, - splats: 9, -}; - -export type AetheriaRuntimeRenderSplatLayerDefinitionSlot = - | "layerKey" - | "displayName" - | "channel" - | "blendMode" - | "graphicsFormat" - | "clearBeforeDraw" - | "clearR" - | "clearG" - | "clearB" - | "clearA"; - -export const aetheriaRuntimeRenderSplatLayerDefinitionSlots: Record = { - layerKey: 0, - displayName: 1, - channel: 2, - blendMode: 3, - graphicsFormat: 4, - clearBeforeDraw: 5, - clearR: 6, - clearG: 7, - clearB: 8, - clearA: 9, -}; - -export type AetheriaRuntimeRenderSplatSoaSlot = - | "count" - | "centerX" - | "centerY" - | "halfExtentX" - | "halfExtentY" - | "rotationCos" - | "rotationSin" - | "channel" - | "falloff" - | "valueR" - | "valueG" - | "valueB" - | "valueA" - | "sourceKey" - | "layerIndex" - | "sourceKind" - | "frequencyX" - | "frequencyY" - | "phaseX" - | "phaseY" - | "animationSpeed" - | "sourceFlags"; - -export const aetheriaRuntimeRenderSplatSoaSlots: Record = { - count: 0, - centerX: 1, - centerY: 2, - halfExtentX: 3, - halfExtentY: 4, - rotationCos: 5, - rotationSin: 6, - channel: 7, - falloff: 8, - valueR: 9, - valueG: 10, - valueB: 11, - valueA: 12, - sourceKey: 13, - layerIndex: 14, - sourceKind: 15, - frequencyX: 16, - frequencyY: 17, - phaseX: 18, - phaseY: 19, - animationSpeed: 20, - sourceFlags: 21, -}; - -export type AetheriaRuntimeCurrentZoneDocumentSlot = - | "schema" - | "frameId" - | "publishedAtUtc" - | "simulationTimeSeconds" - | "runId" - | "zoneIndex" - | "zoneName" - | "positionX" - | "positionY" - | "currentEntityKey" - | "adjacentZoneIndices"; - -export const aetheriaRuntimeCurrentZoneDocumentSlots: Record = { - schema: 0, - frameId: 1, - publishedAtUtc: 2, - simulationTimeSeconds: 3, - runId: 4, - zoneIndex: 5, - zoneName: 6, - positionX: 7, - positionY: 8, - currentEntityKey: 9, - adjacentZoneIndices: 10, -}; - -export type AetheriaRuntimeCurrentEntityDocumentSlot = - | "schema" - | "frameId" - | "publishedAtUtc" - | "simulationTimeSeconds" - | "runId" - | "zoneIndex" - | "entityKey" - | "entityIndex" - | "entity" - | "status" - | "inventory" - | "equipment" - | "cargo" - | "shutdownPerformance" - | "hud"; - -export const aetheriaRuntimeCurrentEntityDocumentSlots: Record = { - schema: 0, - frameId: 1, - publishedAtUtc: 2, - simulationTimeSeconds: 3, - runId: 4, - zoneIndex: 5, - entityKey: 6, - entityIndex: 7, - entity: 8, - status: 9, - inventory: 10, - equipment: 11, - cargo: 12, - shutdownPerformance: 13, - hud: 14, -}; - -export type AetheriaRuntimeCurrentDockingDocumentSlot = - | "schema" - | "frameId" - | "publishedAtUtc" - | "simulationTimeSeconds" - | "runId" - | "zoneIndex" - | "currentEntityKey" - | "currentEntityIndex" - | "isDocked" - | "dockParentEntityKey" - | "dockParentEntityIndex" - | "dockingBayIndex" - | "dockParent" - | "dockParentOrbitKey" - | "dockParentParentOrbitKey" - | "dockParentParentBodyKey"; - -export const aetheriaRuntimeCurrentDockingDocumentSlots: Record = { - schema: 0, - frameId: 1, - publishedAtUtc: 2, - simulationTimeSeconds: 3, - runId: 4, - zoneIndex: 5, - currentEntityKey: 6, - currentEntityIndex: 7, - isDocked: 8, - dockParentEntityKey: 9, - dockParentEntityIndex: 10, - dockingBayIndex: 11, - dockParent: 12, - dockParentOrbitKey: 13, - dockParentParentOrbitKey: 14, - dockParentParentBodyKey: 15, -}; - -export type AetheriaRuntimeStationRefitDocumentSlot = - | "schema" - | "frameId" - | "publishedAtUtc" - | "simulationTimeSeconds" - | "runId" - | "zoneIndex" - | "currentEntityKey" - | "currentEntityIndex" - | "isDocked" - | "dockParentEntityKey" - | "dockParentEntityIndex" - | "dockingBayIndex" - | "dockParent" - | "availableEntities" - | "credits" - | "stationStock" - | "dockingBays" - | "loadoutRestoreOptions" - | "cargoTargets"; - -export const aetheriaRuntimeStationRefitDocumentSlots: Record = { - schema: 0, - frameId: 1, - publishedAtUtc: 2, - simulationTimeSeconds: 3, - runId: 4, - zoneIndex: 5, - currentEntityKey: 6, - currentEntityIndex: 7, - isDocked: 8, - dockParentEntityKey: 9, - dockParentEntityIndex: 10, - dockingBayIndex: 11, - dockParent: 12, - availableEntities: 13, - credits: 14, - stationStock: 15, - dockingBays: 16, - loadoutRestoreOptions: 17, - cargoTargets: 18, -}; - -export type AetheriaRuntimeStationRefitEntityOptionSlot = - | "entityKey" - | "entityIndex" - | "displayName" - | "kind" - | "isCurrentEntity" - | "isPlayerShip" - | "cargoBayCount" - | "dockingBayIndex" - | "hullItemKey" - | "cargoItems"; - -export const aetheriaRuntimeStationRefitEntityOptionSlots: Record = { - entityKey: 0, - entityIndex: 1, - displayName: 2, - kind: 3, - isCurrentEntity: 4, - isPlayerShip: 5, - cargoBayCount: 6, - dockingBayIndex: 7, - hullItemKey: 8, - cargoItems: 9, -}; - -export type AetheriaRuntimeStationStockItemSlot = - | "itemKey" - | "quantity" - | "quality" - | "durability" - | "cargoBayIndex" - | "x" - | "y" - | "price" - | "canAfford" - | "ownedQuantity"; - -export const aetheriaRuntimeStationStockItemSlots: Record = { - itemKey: 0, - quantity: 1, - quality: 2, - durability: 3, - cargoBayIndex: 4, - x: 5, - y: 6, - price: 7, - canAfford: 8, - ownedQuantity: 9, -}; - -export type AetheriaRuntimeSectorMapDocumentSlot = - | "schema" - | "frameId" - | "publishedAtUtc" - | "simulationTimeSeconds" - | "runId" - | "currentZoneIndex" - | "entranceZoneIndex" - | "exitZoneIndex" - | "discoveredZoneIndices" - | "zones" - | "links" - | "isTutorial" - | "generationSeed" - | "factionRelationships"; - -export const aetheriaRuntimeSectorMapDocumentSlots: Record = { - schema: 0, - frameId: 1, - publishedAtUtc: 2, - simulationTimeSeconds: 3, - runId: 4, - currentZoneIndex: 5, - entranceZoneIndex: 6, - exitZoneIndex: 7, - discoveredZoneIndices: 8, - zones: 9, - links: 10, - isTutorial: 11, - generationSeed: 12, - factionRelationships: 13, -}; - -export type AetheriaRuntimeSectorMapZoneSlot = - | "zoneIndex" - | "name" - | "x" - | "y" - | "ownerFactionIndex" - | "factionIndices" - | "adjacentZoneIndices" - | "discovered" - | "current" - | "entrance" - | "exit"; - -export const aetheriaRuntimeSectorMapZoneSlots: Record = { - zoneIndex: 0, - name: 1, - x: 2, - y: 3, - ownerFactionIndex: 4, - factionIndices: 5, - adjacentZoneIndices: 6, - discovered: 7, - current: 8, - entrance: 9, - exit: 10, -}; - -export type AetheriaRuntimeSectorMapLinkSlot = - | "fromZoneIndex" - | "toZoneIndex" - | "discovered"; - -export const aetheriaRuntimeSectorMapLinkSlots: Record = { - fromZoneIndex: 0, - toZoneIndex: 1, - discovered: 2, -}; - -export type AetheriaRuntimeZoneRenderDocumentSlot = - | "schema" - | "frameId" - | "publishedAtUtc" - | "simulationTimeSeconds" - | "runId" - | "zoneIndex" - | "zoneName" - | "currentEntityKey" - | "zoneRenderRadius" - | "credits" - | "adjacentZones" - | "bodyPoses" - | "asteroidBeltPoses" - | "wormholeExits" - | "droppedPickups" - | "entitySnapshots" - | "orbits" - | "bodies" - | "projectiles"; - -export const aetheriaRuntimeZoneRenderDocumentSlots: Record = { - schema: 0, - frameId: 1, - publishedAtUtc: 2, - simulationTimeSeconds: 3, - runId: 4, - zoneIndex: 5, - zoneName: 6, - currentEntityKey: 7, - zoneRenderRadius: 8, - credits: 9, - adjacentZones: 12, - bodyPoses: 13, - asteroidBeltPoses: 14, - wormholeExits: 15, - droppedPickups: 16, - entitySnapshots: 17, - orbits: 18, - bodies: 19, - projectiles: 20, -}; - -export type AetheriaRuntimeZoneRenderBodyPoseSlot = - | "bodyKey" - | "orbitKey" - | "parentOrbitKey" - | "kind" - | "centerX" - | "centerZ" - | "parentCenterX" - | "parentCenterZ" - | "gravityWaveSpeed"; - -export const aetheriaRuntimeZoneRenderBodyPoseSlots: Record = { - bodyKey: 0, - orbitKey: 1, - parentOrbitKey: 2, - kind: 3, - centerX: 4, - centerZ: 5, - parentCenterX: 6, - parentCenterZ: 7, - gravityWaveSpeed: 8, -}; - -export type AetheriaRuntimeZoneRenderAsteroidBeltPoseSlot = - | "bodyKey" - | "orbitKey" - | "centerX" - | "centerZ" - | "radius" - | "asteroidCount" - | "instancePoses"; - -export const aetheriaRuntimeZoneRenderAsteroidBeltPoseSlots: Record = { - bodyKey: 0, - orbitKey: 1, - centerX: 2, - centerZ: 3, - radius: 4, - asteroidCount: 5, - instancePoses: 6, -}; - -export type AetheriaRuntimeSelectedObjectDocumentSlot = - | "schema" - | "frameId" - | "runId" - | "zoneIndex" - | "entityIndex" - | "selected"; - -export const aetheriaRuntimeSelectedObjectDocumentSlots: Record = { - schema: 0, - frameId: 1, - runId: 2, - zoneIndex: 3, - entityIndex: 4, - selected: 5, -}; - -export type AetheriaRuntimeInventoryDocumentSlot = - | "schema" - | "frameId" - | "runId" - | "zoneIndex" - | "entityIndex" - | "entityKey" - | "items" - | "equipment" - | "cargo"; - -export const aetheriaRuntimeInventoryDocumentSlots: Record = { - schema: 0, - frameId: 1, - runId: 2, - zoneIndex: 3, - entityIndex: 4, - entityKey: 5, - items: 6, - equipment: 7, - cargo: 8, -}; - -export type AetheriaRuntimeRunCheckpointCommitSlot = - | "runId" - | "isTutorial" - | "entranceZoneIndex" - | "exitZoneIndex" - | "currentZoneIndex" - | "discoveredZoneIndices" - | "zones" - | "factionRelationships" - | "generationSeed" - | "currentEntityKey" - | "credits" - | "agentTasks"; - -export const aetheriaRuntimeRunCheckpointCommitSlots: Record = { - runId: 0, - isTutorial: 1, - entranceZoneIndex: 2, - exitZoneIndex: 3, - currentZoneIndex: 4, - discoveredZoneIndices: 5, - zones: 6, - factionRelationships: 8, - generationSeed: 9, - currentEntityKey: 10, - credits: 11, - agentTasks: 12, -}; - -export type AetheriaRuntimeZoneSnapshotCommitSlot = - | "zoneIndex" - | "name" - | "positionX" - | "positionY" - | "adjacentZoneIndices" - | "factionIndices" - | "ownerFactionIndex" - | "entities" - | "orbits" - | "bodies" - | "droppedPickups" - | "gravityTerrainRadius" - | "gravityTerrainDepth" - | "gravityTerrainDepthExponent" - | "gravityTerrainBoundaryFog" - | "gravityTerrainWaveFrequency" - | "simulationTimeSeconds" - | "projectiles"; - -export const aetheriaRuntimeZoneSnapshotCommitSlots: Record = { - zoneIndex: 0, - name: 1, - positionX: 2, - positionY: 3, - adjacentZoneIndices: 4, - factionIndices: 5, - ownerFactionIndex: 6, - entities: 7, - orbits: 8, - bodies: 9, - droppedPickups: 10, - gravityTerrainRadius: 11, - gravityTerrainDepth: 12, - gravityTerrainDepthExponent: 13, - gravityTerrainBoundaryFog: 14, - gravityTerrainWaveFrequency: 15, - simulationTimeSeconds: 16, - projectiles: 17, -}; - -export type AetheriaRuntimeEntitySnapshotCommitSlot = - | "entityIndex" - | "name" - | "kind" - | "positionX" - | "positionY" - | "positionZ" - | "directionX" - | "directionY" - | "equipment" - | "cargoBays" - | "dockingBays" - | "childEntityIndices" - | "weaponGroups" - | "statGrids" - | "velocityX" - | "velocityY" - | "targetEntityIndex" - | "isActive" - | "heatsinksEnabled" - | "overrideShutdown" - | "tractorPower" - | "heatstroke" - | "hypothermia" - | "activeConsumables" - | "behaviorProgress" - | "weaponStates" - | "behaviorStates" - | "cargoContents" - | "dockingBayContents" - | "dockingBayAssignments" - | "visibility" - | "visibilitySourceCount" - | "contacts" - | "hullItemKey" - | "factionKey" - | "shutdownPerformance" - | "agentTaskCapabilities" - | "assignedAgentTaskId"; - -export const aetheriaRuntimeEntitySnapshotCommitSlots: Record = { - entityIndex: 0, - name: 1, - kind: 2, - positionX: 3, - positionY: 4, - positionZ: 5, - directionX: 6, - directionY: 7, - equipment: 10, - cargoBays: 11, - dockingBays: 12, - childEntityIndices: 13, - weaponGroups: 14, - statGrids: 15, - velocityX: 16, - velocityY: 17, - targetEntityIndex: 18, - isActive: 19, - heatsinksEnabled: 20, - overrideShutdown: 21, - tractorPower: 22, - heatstroke: 23, - hypothermia: 24, - activeConsumables: 25, - behaviorProgress: 26, - weaponStates: 27, - behaviorStates: 28, - cargoContents: 29, - dockingBayContents: 30, - dockingBayAssignments: 31, - visibility: 32, - visibilitySourceCount: 33, - contacts: 34, - hullItemKey: 35, - factionKey: 36, - shutdownPerformance: 37, - agentTaskCapabilities: 38, - assignedAgentTaskId: 39, -}; - -export type AetheriaRuntimeProjectileCommitSlot = - | "projectileId" - | "sourceEntityIndex" - | "targetEntityIndex" - | "factionKey" - | "positionX" - | "positionY" - | "positionZ" - | "directionX" - | "directionY" - | "velocityX" - | "velocityY" - | "damage" - | "radius" - | "ageSeconds" - | "lifetimeSeconds" - | "guided" - | "active" - | "weaponKind"; - -export const aetheriaRuntimeProjectileCommitSlots: Record = { - projectileId: 0, - sourceEntityIndex: 1, - targetEntityIndex: 2, - factionKey: 3, - positionX: 4, - positionY: 5, - positionZ: 6, - directionX: 7, - directionY: 8, - velocityX: 9, - velocityY: 10, - damage: 11, - radius: 12, - ageSeconds: 13, - lifetimeSeconds: 14, - guided: 15, - active: 16, - weaponKind: 17, -}; - -export type AetheriaRuntimeBodySnapshotCommitSlot = - | "kind" - | "name" - | "mass" - | "resources" - | "bodyRadiusMultiplier" - | "gravityRadiusMultiplier" - | "gravityDepthMultiplier" - | "gravityDepthExponent" - | "asteroids" - | "gasGiantVisual" - | "sunVisual" - | "bodyKey" - | "orbitKey" - | "gravityInfluenceCenterX" - | "gravityInfluenceCenterZ" - | "gravityInfluenceRadius" - | "gravityWellDepth" - | "gravityWaveRadius" - | "gravityWaveDepth" - | "gravityWaveSpeed" - | "iconSize"; - -export const aetheriaRuntimeBodySnapshotCommitSlots: Record = { - kind: 1, - name: 2, - mass: 4, - resources: 5, - bodyRadiusMultiplier: 6, - gravityRadiusMultiplier: 7, - gravityDepthMultiplier: 8, - gravityDepthExponent: 9, - asteroids: 10, - gasGiantVisual: 11, - sunVisual: 12, - bodyKey: 13, - orbitKey: 14, - gravityInfluenceCenterX: 15, - gravityInfluenceCenterZ: 16, - gravityInfluenceRadius: 17, - gravityWellDepth: 18, - gravityWaveRadius: 19, - gravityWaveDepth: 20, - gravityWaveSpeed: 21, - iconSize: 22, -}; - -export type AetheriaRuntimeSunVisualCommitSlot = - | "lightColorX" - | "lightColorY" - | "lightColorZ" - | "fogTintColorX" - | "fogTintColorY" - | "fogTintColorZ" - | "lightRadiusMultiplier"; - -export const aetheriaRuntimeSunVisualCommitSlots: Record = { - lightColorX: 0, - lightColorY: 1, - lightColorZ: 2, - fogTintColorX: 3, - fogTintColorY: 4, - fogTintColorZ: 5, - lightRadiusMultiplier: 6, -}; - -export type AetheriaRuntimeEntityStatGridCommitSlot = - | "name" - | "width" - | "height" - | "values"; - -export const aetheriaRuntimeEntityStatGridCommitSlots: Record = { - name: 0, - width: 1, - height: 2, - values: 3, -}; - -export type AetheriaRuntimeCargoBayLoadoutCommitSlot = - | "items"; - -export const aetheriaRuntimeCargoBayLoadoutCommitSlots: Record = { - items: 0, -}; - -export type AetheriaRuntimeLoadoutItemSlotCommitSlot = - | "x" - | "y" - | "item"; - -export const aetheriaRuntimeLoadoutItemSlotCommitSlots: Record = { - x: 0, - y: 1, - item: 2, -}; - -export type AetheriaRuntimeLoadoutItemCommitSlot = - | "quality" - | "durability" - | "quantity" - | "enabled" - | "itemKey" - | "overrideShutdown" - | "temperature"; - -export const aetheriaRuntimeLoadoutItemCommitSlots: Record = { - quality: 1, - durability: 2, - quantity: 3, - enabled: 4, - itemKey: 5, - overrideShutdown: 6, - temperature: 7, -}; - -export type AetheriaRuntimeGameViewportBoundsSlot = - | "minX" - | "minY" - | "maxX" - | "maxY"; - -export const aetheriaRuntimeGameViewportBoundsSlots: Record = { - minX: 0, - minY: 1, - maxX: 2, - maxY: 3, -}; - -export type AetheriaRuntimeGameViewportObjectSlot = - | "entityIndex" - | "entityKey" - | "displayName" - | "kind" - | "factionKey" - | "x" - | "y" - | "z" - | "directionX" - | "directionY" - | "velocityX" - | "velocityY" - | "controlled" - | "targetEntityIndex" - | "isActive" - | "visibility" - | "status" - | "inventory" - | "iconAsset"; - -export const aetheriaRuntimeGameViewportObjectSlots: Record = { - entityIndex: 0, - entityKey: 1, - displayName: 2, - kind: 3, - factionKey: 4, - x: 5, - y: 6, - z: 7, - directionX: 8, - directionY: 9, - velocityX: 10, - velocityY: 11, - controlled: 12, - targetEntityIndex: 13, - isActive: 14, - visibility: 15, - status: 16, - inventory: 17, - iconAsset: 18, -}; - -export type AetheriaRuntimeRtsEntityStatusSlot = - | "hull" - | "shield" - | "heat"; - -export const aetheriaRuntimeRtsEntityStatusSlots: Record = { - hull: 0, - shield: 1, - heat: 2, -}; - -export type AetheriaRuntimeRtsInventoryItemSlot = - | "source" - | "itemKey" - | "quantity" - | "quality" - | "durability" - | "enabled" - | "sourceIndex" - | "x" - | "y" - | "iconAsset"; - -export const aetheriaRuntimeRtsInventoryItemSlots: Record = { - source: 0, - itemKey: 1, - quantity: 2, - quality: 3, - durability: 4, - enabled: 5, - sourceIndex: 6, - x: 7, - y: 8, - iconAsset: 9, -}; - -export type AetheriaRuntimeRtsGravityInfluenceSlot = - | "bodyKey" - | "orbitKey" - | "kind" - | "x" - | "y" - | "radius" - | "gravityDepth" - | "gravityDepthExponent" - | "waveRadius" - | "waveDepth" - | "waveSpeed"; - -export const aetheriaRuntimeRtsGravityInfluenceSlots: Record = { - bodyKey: 0, - orbitKey: 1, - kind: 2, - x: 3, - y: 4, - radius: 5, - gravityDepth: 6, - gravityDepthExponent: 7, - waveRadius: 8, - waveDepth: 9, - waveSpeed: 10, -}; - -export type AetheriaRuntimeRtsBodyViewSlot = - | "bodyKey" - | "orbitKey" - | "name" - | "kind" - | "x" - | "y" - | "radius" - | "isAsteroidBelt" - | "body" - | "iconAsset" - | "iconSize"; - -export const aetheriaRuntimeRtsBodyViewSlots: Record = { - bodyKey: 0, - orbitKey: 1, - name: 2, - kind: 3, - x: 4, - y: 5, - radius: 6, - isAsteroidBelt: 7, - body: 8, - iconAsset: 9, - iconSize: 10, -}; - -export type AetheriaRuntimeStarbridgeScenarioDocumentSlot = - | "schema" - | "scenarioId" - | "displayName" - | "startingBaseKey" - | "stationStock" - | "availableShipKeys" - | "waves" - | "attackerMixKeys" - | "recoveredTechnologyPoolKeys" - | "runtimeRoles"; - -export const aetheriaRuntimeStarbridgeScenarioDocumentSlots: Record = { - schema: 0, - scenarioId: 1, - displayName: 2, - startingBaseKey: 3, - stationStock: 4, - availableShipKeys: 5, - waves: 6, - attackerMixKeys: 7, - recoveredTechnologyPoolKeys: 8, - runtimeRoles: 9, -}; - -export type AetheriaRuntimeStarbridgeSessionDocumentSlot = - | "schema" - | "sessionId" - | "scenarioId" - | "runId" - | "baseEntityKey" - | "stationEntityKey" - | "phase" - | "currentWaveIndex" - | "runtimeRoles"; - -export const aetheriaRuntimeStarbridgeSessionDocumentSlots: Record = { - schema: 0, - sessionId: 1, - scenarioId: 2, - runId: 3, - baseEntityKey: 4, - stationEntityKey: 5, - phase: 6, - currentWaveIndex: 7, - runtimeRoles: 8, -}; - -export type AetheriaRuntimeStarbridgeSessionSummaryDocumentSlot = - | "schema" - | "frameId" - | "publishedAtUtc" - | "sessionId" - | "scenarioId" - | "scenarioName" - | "runId" - | "zoneIndex" - | "zoneName" - | "phase" - | "currentWaveIndex" - | "baseStatus" - | "stationStock" - | "waveForecast" - | "runtimeRoles"; - -export const aetheriaRuntimeStarbridgeSessionSummaryDocumentSlots: Record = { - schema: 0, - frameId: 1, - publishedAtUtc: 2, - sessionId: 3, - scenarioId: 4, - scenarioName: 5, - runId: 6, - zoneIndex: 7, - zoneName: 8, - phase: 9, - currentWaveIndex: 10, - baseStatus: 11, - stationStock: 12, - waveForecast: 13, - runtimeRoles: 14, -}; - -export type AetheriaRuntimeStarbridgeStationStockItemSlot = - | "itemKey" - | "quantity" - | "quality" - | "durability" - | "source" - | "iconAsset"; - -export const aetheriaRuntimeStarbridgeStationStockItemSlots: Record = { - itemKey: 0, - quantity: 1, - quality: 2, - durability: 3, - source: 4, - iconAsset: 5, -}; - -export type AetheriaRuntimeStarbridgeWaveDefinitionSlot = - | "waveIndex" - | "displayName" - | "attackerKeys" - | "startsAfterSeconds" - | "bossKey" - | "recoveredTechnologyKeys"; - -export const aetheriaRuntimeStarbridgeWaveDefinitionSlots: Record = { - waveIndex: 0, - displayName: 1, - attackerKeys: 2, - startsAfterSeconds: 3, - bossKey: 4, - recoveredTechnologyKeys: 5, -}; - -export type AetheriaRuntimeStarbridgeWaveForecastSlot = - | "waveIndex" - | "displayName" - | "attackerKeys" - | "bossKey" - | "recoveredTechnologyKeys"; - -export const aetheriaRuntimeStarbridgeWaveForecastSlots: Record = { - waveIndex: 0, - displayName: 1, - attackerKeys: 2, - bossKey: 3, - recoveredTechnologyKeys: 4, -}; - -export type AetheriaRuntimeStarbridgeRuntimeRoleSlot = - | "runtimeId" - | "role" - | "entityKey"; - -export const aetheriaRuntimeStarbridgeRuntimeRoleSlots: Record = { - runtimeId: 0, - role: 1, - entityKey: 2, -}; - -export type AetheriaRuntimeStarbridgeBaseStatusSlot = - | "entityKey" - | "displayName" - | "hull" - | "shield" - | "heat" - | "isActive"; - -export const aetheriaRuntimeStarbridgeBaseStatusSlots: Record = { - entityKey: 0, - displayName: 1, - hull: 2, - shield: 3, - heat: 4, - isActive: 5, -}; - -export type AetheriaRuntimeVerseAuthorityPolicyDocumentSlot = - | "schema" - | "verseId" - | "policyId" - | "ruleVersion" - | "hostRuntimeId" - | "defaultMode" - | "rules" - | "updatedAtUtc" - | "deploymentMode" - | "runtimeRoles"; - -export const aetheriaRuntimeVerseAuthorityPolicyDocumentSlots: Record = { - schema: 0, - verseId: 1, - policyId: 2, - ruleVersion: 3, - hostRuntimeId: 4, - defaultMode: 5, - rules: 6, - updatedAtUtc: 7, - deploymentMode: 8, - runtimeRoles: 9, -}; - -export type AetheriaRuntimeAuthorityRuleSlot = - | "ruleId" - | "subjectPrefix" - | "claimKinds" - | "mode" - | "runtimeIds" - | "leaseScope" - | "priority"; - -export const aetheriaRuntimeAuthorityRuleSlots: Record = { - ruleId: 0, - subjectPrefix: 1, - claimKinds: 2, - mode: 3, - runtimeIds: 4, - leaseScope: 5, - priority: 6, -}; - -export type AetheriaRuntimeAuthorityRuntimeRoleSlot = - | "runtimeId" - | "roles"; - -export const aetheriaRuntimeAuthorityRuntimeRoleSlots: Record = { - runtimeId: 0, - roles: 1, -}; - -export type AetheriaRuntimeAuthorityLeaseDocumentSlot = - | "schema" - | "leaseId" - | "verseId" - | "runtimeId" - | "subjectPrefix" - | "claimKinds" - | "validFromUtc" - | "expiresAtUtc" - | "scope"; - -export const aetheriaRuntimeAuthorityLeaseDocumentSlots: Record = { - schema: 0, - leaseId: 1, - verseId: 2, - runtimeId: 3, - subjectPrefix: 4, - claimKinds: 5, - validFromUtc: 6, - expiresAtUtc: 7, - scope: 8, -}; - -export type ViewportResponse = { - schema: string; - frameId: number; - publishedAtUtc: string; - simulationTimeSeconds: number; - runId: string; - zoneIndex: number; - zoneName: string; - currentEntityKey: string; - viewport: CultMeshViewportRequest; - controlledEntityIndices: number[]; - objects: ViewObject[]; - gravityInfluences: GravityInfluence[]; - bodies: BodyView[]; -}; - -export type ObjectsViewportResponse = { - schema: string; - frameId: number; - publishedAtUtc: string; - simulationTimeSeconds: number; - runId: string; - zoneIndex: number; - zoneName: string; - currentEntityKey: string; - viewport: CultMeshViewportRequest; - controlledEntityIndices: number[]; - objects: ViewObject[]; -}; - -export type GravityViewportResponse = { - schema: string; - frameId: number; - publishedAtUtc: string; - simulationTimeSeconds: number; - runId: string; - zoneIndex: number; - zoneName: string; - viewport: CultMeshViewportRequest; - gravityInfluences: GravityInfluence[]; - bodies: BodyView[]; -}; - -export type RenderSplatsViewportResponse = { - schema: string; - frameId: number; - publishedAtUtc: string; - simulationTimeSeconds: number; - runId: string; - zoneIndex: number; - zoneName: string; - viewport: CultMeshViewportRequest; - layers: RenderSplatLayerDefinition[]; - splats: RenderSplatSoa; -}; - -export type RenderSplatLayerDefinition = { - layerKey: string; - displayName: string; - channel: number; - blendMode: string; - graphicsFormat: string; - clearBeforeDraw: boolean; - clearR: number; - clearG: number; - clearB: number; - clearA: number; -}; - -export type RenderSplatSoa = { - count: number; - centerX: number[]; - centerY: number[]; - halfExtentX: number[]; - halfExtentY: number[]; - rotationCos: number[]; - rotationSin: number[]; - channel: number[]; - falloff: number[]; - valueR: number[]; - valueG: number[]; - valueB: number[]; - valueA: number[]; - sourceKey: string[]; - layerIndex: number[]; - sourceKind: number[]; - frequencyX: number[]; - frequencyY: number[]; - phaseX: number[]; - phaseY: number[]; - animationSpeed: number[]; - sourceFlags: number[]; -}; - -export type ViewObject = { - entityIndex: number; - entityKey: string; - displayName: string; - kind: string; - factionKey: string; - x: number; - y: number; - z: number; - directionX: number; - directionY: number; - velocityX: number; - velocityY: number; - controlled: boolean; - targetEntityIndex: number; - isActive: boolean; - visibility: number; - iconAsset: AssetRef; - status: EntityStatus; - inventory: InventoryItem[]; -}; - -export type EntityStatus = { - hull: number; - shield: number; - heat: number; -}; - -export type InventoryItem = { - source: string; - itemKey: string; - quantity: number; - quality: number; - durability: number; - enabled: boolean; - iconAsset: AssetRef; -}; - -export type SelectedObjectDocument = { - schema: string; - frameId: number; - runId: string; - zoneIndex: number; - entityIndex: number; - selected: ViewObject | null; -}; - -export type InventoryDocument = { - schema: string; - frameId: number; - runId: string; - zoneIndex: number; - entityIndex: number; - entityKey: string; - items: InventoryItem[]; - equipment: InventoryItem[]; - cargo: InventoryItem[]; -}; - -export type DaemonHealthDocument = { - schema: string; - daemonId: string; - verseId: string; - publishedAtUtc: string; - statePath: string; - frameId: number; - observedCommandCount: number; - appliedCommandCount: number; - rejectedCommandCount: number; - status: string; - publicationSource: string; - transport: string; - commandBoundaryPath: string; -}; - -export type AuthorityStatusDocument = { - schema: string; - verseId: string; - policyId: string; - ruleVersion: string; - hostRuntimeId: string; - defaultMode: string; - updatedAtUtc: string; - rules: AuthorityRuleDocument[]; -}; - -export type AuthorityRuleDocument = { - ruleId: string; - subjectPrefix: string; - claimKinds: string[]; - mode: string; - runtimeIds: string[]; - leaseScope: string; - priority: number; -}; - -export type StarbridgeSessionDocument = { - schema: string; - frameId: number; - publishedAtUtc: string; - sessionId: string; - scenarioId: string; - scenarioName: string; - runId: string; - zoneIndex: number; - zoneName: string; - phase: string; - currentWaveIndex: number; - baseStatus: StarbridgeBaseStatusDocument; - stationStock: StarbridgeStationStockItemDocument[]; - waveForecast: StarbridgeWaveForecastDocument[]; - runtimeRoles: StarbridgeRuntimeRoleDocument[]; -}; - -export type StarbridgeBaseStatusDocument = { - entityKey: string; - displayName: string; - hull: number; - shield: number; - heat: number; - isActive: boolean; -}; - -export type StarbridgeStationStockItemDocument = { - itemKey: string; - quantity: number; - quality: number; - durability: number; - source: string; - iconAsset: AssetRef; -}; - -export type AssetManifestDocument = { - schema: string; - publishedAtUtc: string; - runId: string; - baseUri: string; - assets: AssetManifestEntry[]; -}; - -export type AssetManifestEntry = { - ref: AssetRef; - sizeBytes: number; - width: number; - height: number; - tags: string[]; -}; - -export type AssetRef = { - assetKey: string; - kind: string; - uri: string; - transport: string; - contentHash: string; - mimeType: string; - metadata: Record; -}; - -export type StarbridgeWaveForecastDocument = { - waveIndex: number; - displayName: string; - attackerKeys: string[]; - bossKey: string; - recoveredTechnologyKeys: string[]; -}; - -export type StarbridgeRuntimeRoleDocument = { - runtimeId: string; - role: string; - entityKey: string; -}; - -export type GravityInfluence = { - bodyKey: string; - orbitKey: string; - kind: string; - x: number; - y: number; - radius: number; - gravityDepth: number; - gravityDepthExponent: number; - waveRadius: number; - waveDepth: number; - waveSpeed: number; -}; - -export type BodyView = { - bodyKey: string; - orbitKey: string; - name: string; - kind: string; - x: number; - y: number; - radius: number; - isAsteroidBelt: boolean; - iconAsset: AssetRef; - iconSize: number; -}; - - -export type AetheriaRuntimeSetMoveVectorRequest = { - actorEntityKey: string; - directionX: number; - directionY: number; - scalar: number; - observedFrameId?: number; -}; - -export type AetheriaRuntimeSetTargetRequest = { - actorEntityKey: string; - targetEntityKey: string; - observedFrameId?: number; -}; - -export type AetheriaRuntimeDaemonCommandReceipt = CultMeshOperationReceipt & { - commandId: string; -}; - -export type AetheriaRuntimeSelectedObjectRequest = { - entityIndex: number; -}; - -export type AetheriaRuntimeViewportFeedRequest = { - viewport: CultMeshViewportRequest; - selectedEntityIndex?: number; -}; - -export type AetheriaRuntimeViewportFeedSnapshot = { - viewport: ViewportResponse; - selectedObject: SelectedObjectDocument | null; - inventory: InventoryDocument | null; - daemonHealth: DaemonHealthDocument; - authorityStatus: AuthorityStatusDocument; - starbridgeSession: StarbridgeSessionDocument; - assetManifest: AssetManifestDocument; - receivedAtUtc: string; - sampleMs: number; -}; - -export type AetheriaEveSurfaceRequest = { - surfaceId?: string; - recordKey?: string; -}; - -export type EveEmbeddedDocumentRequest = { - documentId: string; - schemaId?: string; - slotId?: string; - presentationKind?: string; - context?: Record; -}; - -export type EveResolvedDocument = { - documentId: string; - schemaId: string; - document?: unknown; - surface?: AetheriaMenuSurfaceTree; -}; - -export type EveProviderAdvertisement = { - providerId: string; - title: string; - kind: string; - surfaces: Array<{ - surfaceId: string; - key: string; - transport: string; - status: string; - }>; -}; - -export type AetheriaEveCommandRequest = { - providerId?: string; - surfaceId?: string; - command?: string; - clientId?: string; - issuedAtUtc?: string; - payload?: Record; -}; - -export type AetheriaMenuSurfaceDocument = { - providerId: string; - providerKind: string; - title: string; - version: number; - updatedAtUtc: string; - surface: AetheriaMenuSurfaceTree; - commands: AetheriaMenuSurfaceCommand[]; -}; - -export type AetheriaMenuSurfaceTree = { - id: string; - root: AetheriaMenuSurfaceComponent; - styles: AetheriaMenuStyleToken[]; -}; - -export type AetheriaMenuSurfaceComponent = { - id: string; - kind: string; - props: Record; - layout?: Record; - style?: Record; - embeddedDocuments?: AetheriaMenuEmbeddedDocumentSlot[]; - children: AetheriaMenuSurfaceComponent[]; -}; - -export type AetheriaMenuEmbeddedDocumentSlot = { - slotId: string; - documentId: string; - schemaId: string; - presentationKind: string; -}; - -export type AetheriaMenuStyleToken = { - name: string; - value: string; -}; - -export type AetheriaMenuSurfaceCommand = { - command: string; - label: string; - transport: string; -}; - -export type AetheriaRtsMainClient = { - mapViewport(request: CultMeshViewportRequest): Promise; - objectsViewport(request: CultMeshViewportRequest): Promise; - gravityViewport(request: CultMeshViewportRequest): Promise; - renderSplatsViewport(request: CultMeshViewportRequest): 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; - surfaceCatalogDiagnostics(): CultMeshSurfaceCatalogDiagnostic; - surfaceCatalogIndexDiagnostics(): CultMeshSurfaceCatalogIndexDiagnostic; -}; - -export type AetheriaRtsHealthProvider = () => unknown; - -export function registerAetheriaRtsIpcHandlers( - ipcMain: IpcMain, - getClient: () => AetheriaRtsMainClient, - getHealth: AetheriaRtsHealthProvider, -): void { - const viewportFeedSubscriptions = new Map(); - ipcMain.handle(AetheriaRtsIpcChannels.mapViewport, async (_event, request: CultMeshViewportRequest) => - getClient().mapViewport(request)); - ipcMain.handle(AetheriaRtsIpcChannels.objectsViewport, async (_event, request: CultMeshViewportRequest) => - getClient().objectsViewport(request)); - ipcMain.handle(AetheriaRtsIpcChannels.gravityViewport, async (_event, request: CultMeshViewportRequest) => - getClient().gravityViewport(request)); - ipcMain.handle(AetheriaRtsIpcChannels.renderSplatsViewport, async (_event, request: CultMeshViewportRequest) => - getClient().renderSplatsViewport(request)); - ipcMain.handle(AetheriaRtsIpcChannels.selectedObject, async (_event, request: AetheriaRuntimeSelectedObjectRequest) => - getClient().selectedObject(request)); - ipcMain.handle(AetheriaRtsIpcChannels.inventory, async (_event, request: AetheriaRuntimeSelectedObjectRequest) => - getClient().inventory(request)); - ipcMain.handle(AetheriaRtsIpcChannels.daemonHealth, async () => getClient().daemonHealth()); - ipcMain.handle(AetheriaRtsIpcChannels.authorityStatus, async () => getClient().authorityStatus()); - ipcMain.handle(AetheriaRtsIpcChannels.starbridgeSession, async () => getClient().starbridgeSession()); - ipcMain.handle(AetheriaRtsIpcChannels.assetManifest, async () => getClient().assetManifest()); - ipcMain.handle(AetheriaRtsIpcChannels.viewportFeed, async (event, request: { subscriptionId: string; feed: AetheriaRuntimeViewportFeedRequest }) => { - viewportFeedSubscriptions.get(request.subscriptionId)?.(); - const unsubscribe = getClient().watchViewportFeed(request.feed, snapshot => { - event.sender.send(AetheriaRtsIpcChannels.viewportFeedUpdate, { - subscriptionId: request.subscriptionId, - snapshot, - }); - }); - viewportFeedSubscriptions.set(request.subscriptionId, unsubscribe); - }); - ipcMain.handle(AetheriaRtsIpcChannels.viewportFeedStop, (_event, subscriptionId: string) => { - viewportFeedSubscriptions.get(subscriptionId)?.(); - viewportFeedSubscriptions.delete(subscriptionId); - }); - ipcMain.handle(AetheriaRtsIpcChannels.setMoveVector, async (_event, request: AetheriaRuntimeSetMoveVectorRequest) => - getClient().setMoveVector(request)); - ipcMain.handle(AetheriaRtsIpcChannels.setTarget, async (_event, request: AetheriaRuntimeSetTargetRequest) => - getClient().setTarget(request)); - ipcMain.handle(AetheriaRtsIpcChannels.surfaceCatalog, () => getClient().surfaceCatalogDiagnostics()); - ipcMain.handle(AetheriaRtsIpcChannels.surfaceCatalogIndex, () => getClient().surfaceCatalogIndexDiagnostics()); - ipcMain.handle(AetheriaRtsIpcChannels.health, () => getHealth()); -} - -export type AetheriaRuntimeDaemonCommandSender = ( - commandId: string, - issuedAtUtc: string, - command: unknown[], - context: CultMeshOperationContext, -) => Promise; - -export type AetheriaRuntimeViewportQueryExecutor = ( - request: CultMeshViewportRequest, - context: CultMeshQueryContext, -) => Promise; - -export type AetheriaRuntimeQueryExecutor = ( - request: TParameters, - context: CultMeshQueryContext, -) => Promise; - -export type AetheriaRuntimeGameQueryExecutors = { - mapViewport: AetheriaRuntimeViewportQueryExecutor; - objectsViewport: AetheriaRuntimeViewportQueryExecutor; - gravityViewport: AetheriaRuntimeViewportQueryExecutor; - renderSplatsViewport: AetheriaRuntimeViewportQueryExecutor; - selectedObject: AetheriaRuntimeQueryExecutor; - inventory: AetheriaRuntimeQueryExecutor; - daemonHealth: AetheriaRuntimeQueryExecutor; - authorityStatus: AetheriaRuntimeQueryExecutor; - starbridgeSession: AetheriaRuntimeQueryExecutor; - assetManifest: AetheriaRuntimeQueryExecutor; -}; - -export type AetheriaRuntimeGameQueryWatchers = Partial<{ - mapViewport: CultMeshQueryWatcher; - objectsViewport: CultMeshQueryWatcher; - gravityViewport: CultMeshQueryWatcher; - renderSplatsViewport: CultMeshQueryWatcher; - selectedObject: CultMeshQueryWatcher; - inventory: CultMeshQueryWatcher; - daemonHealth: CultMeshQueryWatcher; - authorityStatus: CultMeshQueryWatcher; - starbridgeSession: CultMeshQueryWatcher; - assetManifest: CultMeshQueryWatcher; -}>; - -export type AetheriaRuntimeGameQueryDiagnostic = CultMeshQuerySurfaceDiagnostic; - -export type AetheriaRuntimeGameLiveFeedDiagnostic = CultMeshLiveFeedDiagnostic; - -export type AetheriaRuntimeGameSurfaceCatalogDiagnostic = CultMeshSurfaceCatalogDiagnostic; - -export type AetheriaRuntimeGameOperationHandles = ReturnType; - -export type AetheriaRuntimeGameDocuments = ReturnType; - -export type AetheriaRuntimeGameStatePointers = AetheriaRuntimeGameDocuments; - -export type AetheriaRuntimeGameDocumentResolvers = Partial<{ - daemonFrame: (context: CultMeshQueryContext) => Promise; - daemonHealth: (context: CultMeshQueryContext) => Promise; - authorityPolicy: (context: CultMeshQueryContext) => Promise; - starbridgeSession: (context: CultMeshQueryContext) => Promise; - assetManifest: (context: CultMeshQueryContext) => Promise; -}>; - -export type AetheriaRuntimeGameStatePointerResolvers = AetheriaRuntimeGameDocumentResolvers; - -export const AetheriaRuntimeGameDocumentSources = { - daemonFrame: CultMesh.querySource("daemon:aetheria.frame.latest.v1", { - schemaId: AetheriaRtsSchemas.daemonFrame, - description: "latest daemon frame" - }), - daemonHealth: CultMesh.querySource("daemon:aetheria.health.latest.v1", { - schemaId: AetheriaRtsSchemas.daemonHealth, - description: "latest daemon health" - }), - authorityPolicy: CultMesh.querySource("daemon:aetheria.authority.policy.latest.v1", { - schemaId: AetheriaRtsSchemas.verseAuthorityPolicy, - description: "latest Verse authority policy" - }), - starbridgeSession: CultMesh.querySource("daemon:aetheria.starbridge.session.latest.v1", { - schemaId: AetheriaRtsSchemas.starbridgeSessionSummary, - description: "latest Starbridge session summary" - }), - assetManifest: CultMesh.querySource("daemon:aetheria.asset_manifest.latest.v1", { - schemaId: AetheriaRtsSchemas.assetManifest, - description: "latest daemon asset manifest" - }) -} as const; - -export function createAetheriaRuntimeGameDocuments( - routeHint: CultMeshRouteHint = CultMesh.routeHint(), - resolvers: AetheriaRuntimeGameDocumentResolvers = {}, -) { - return { - daemonFrame: CultMesh.document( - AetheriaRuntimeGameDocumentSources.daemonFrame.sourceId, - { schemaId: AetheriaRtsSchemas.daemonFrame }, - async (context) => resolvers.daemonFrame?.(context), - { - routeHint, - sources: [AetheriaRuntimeGameDocumentSources.daemonFrame], - }, - ), - daemonHealth: CultMesh.document( - AetheriaRuntimeGameDocumentSources.daemonHealth.sourceId, - { schemaId: AetheriaRtsSchemas.daemonHealth }, - async (context) => resolvers.daemonHealth?.(context), - { - routeHint, - sources: [AetheriaRuntimeGameDocumentSources.daemonHealth], - }, - ), - authorityPolicy: CultMesh.document( - AetheriaRuntimeGameDocumentSources.authorityPolicy.sourceId, - { schemaId: AetheriaRtsSchemas.verseAuthorityPolicy }, - async (context) => resolvers.authorityPolicy?.(context), - { - routeHint, - sources: [AetheriaRuntimeGameDocumentSources.authorityPolicy], - }, - ), - starbridgeSession: CultMesh.document( - AetheriaRuntimeGameDocumentSources.starbridgeSession.sourceId, - { schemaId: AetheriaRtsSchemas.starbridgeSessionSummary }, - async (context) => resolvers.starbridgeSession?.(context), - { - routeHint, - sources: [AetheriaRuntimeGameDocumentSources.starbridgeSession], - }, - ), - assetManifest: CultMesh.document( - AetheriaRuntimeGameDocumentSources.assetManifest.sourceId, - { schemaId: AetheriaRtsSchemas.assetManifest }, - async (context) => resolvers.assetManifest?.(context), - { - routeHint, - sources: [AetheriaRuntimeGameDocumentSources.assetManifest], - }, - ), - } as const; -} - -export const createAetheriaRuntimeGameStatePointers = createAetheriaRuntimeGameDocuments; - -export function createAetheriaRuntimeGameQueryHandles( - executors: AetheriaRuntimeGameQueryExecutors, - routeHint: CultMeshRouteHint = CultMesh.routeHint(), - watchers: AetheriaRuntimeGameQueryWatchers = {}, -) { - return { - mapViewport: CultMesh.query( - AetheriaRtsSchemas.gameViewport, - executors.mapViewport, - { routeHint, sources: [AetheriaRuntimeGameDocumentSources.daemonFrame], watchQuery: watchers.mapViewport }, - ), - objectsViewport: CultMesh.query( - AetheriaRtsSchemas.objectsViewport, - executors.objectsViewport, - { routeHint, sources: [AetheriaRuntimeGameDocumentSources.daemonFrame], watchQuery: watchers.objectsViewport }, - ), - gravityViewport: CultMesh.query( - AetheriaRtsSchemas.gravityViewport, - executors.gravityViewport, - { routeHint, sources: [AetheriaRuntimeGameDocumentSources.daemonFrame], watchQuery: watchers.gravityViewport }, - ), - renderSplatsViewport: CultMesh.query( - AetheriaRtsSchemas.renderSplatsViewport, - executors.renderSplatsViewport, - { routeHint, sources: [AetheriaRuntimeGameDocumentSources.daemonFrame], watchQuery: watchers.renderSplatsViewport }, - ), - selectedObject: CultMesh.query( - AetheriaRtsSchemas.selectedObject, - executors.selectedObject, - { routeHint, sources: [AetheriaRuntimeGameDocumentSources.daemonFrame], watchQuery: watchers.selectedObject }, - ), - inventory: CultMesh.query( - AetheriaRtsSchemas.inventory, - executors.inventory, - { routeHint, sources: [AetheriaRuntimeGameDocumentSources.daemonFrame], watchQuery: watchers.inventory }, - ), - daemonHealth: CultMesh.query( - AetheriaRtsSchemas.daemonHealth, - executors.daemonHealth, - { routeHint, sources: [AetheriaRuntimeGameDocumentSources.daemonHealth], watchQuery: watchers.daemonHealth }, - ), - authorityStatus: CultMesh.query( - AetheriaRtsSchemas.verseAuthorityPolicy, - executors.authorityStatus, - { routeHint, sources: [AetheriaRuntimeGameDocumentSources.authorityPolicy], watchQuery: watchers.authorityStatus }, - ), - starbridgeSession: CultMesh.query( - AetheriaRtsSchemas.starbridgeSessionSummary, - executors.starbridgeSession, - { routeHint, sources: [AetheriaRuntimeGameDocumentSources.starbridgeSession], watchQuery: watchers.starbridgeSession }, - ), - assetManifest: CultMesh.query( - AetheriaRtsSchemas.assetManifest, - executors.assetManifest, - { routeHint, sources: [AetheriaRuntimeGameDocumentSources.assetManifest], watchQuery: watchers.assetManifest }, - ), - } as const; -} - -export function describeAetheriaRuntimeGameQueryHandles( - handles: ReturnType, -) { - return { - mapViewport: describeAetheriaRuntimeGameQuerySurface(handles.mapViewport), - objectsViewport: describeAetheriaRuntimeGameQuerySurface(handles.objectsViewport), - gravityViewport: describeAetheriaRuntimeGameQuerySurface(handles.gravityViewport), - renderSplatsViewport: describeAetheriaRuntimeGameQuerySurface(handles.renderSplatsViewport), - selectedObject: describeAetheriaRuntimeGameQuerySurface(handles.selectedObject), - inventory: describeAetheriaRuntimeGameQuerySurface(handles.inventory), - daemonHealth: describeAetheriaRuntimeGameQuerySurface(handles.daemonHealth), - authorityStatus: describeAetheriaRuntimeGameQuerySurface(handles.authorityStatus), - starbridgeSession: describeAetheriaRuntimeGameQuerySurface(handles.starbridgeSession), - assetManifest: describeAetheriaRuntimeGameQuerySurface(handles.assetManifest), - } as const; -} - -export function describeAetheriaRuntimeGameSurfaceCatalog( - handles: ReturnType, - operations?: AetheriaRuntimeGameOperationHandles, - documents: AetheriaRuntimeGameDocuments = createAetheriaRuntimeGameDocuments(), -): AetheriaRuntimeGameSurfaceCatalogDiagnostic { - return CultMesh.describeSurfaceCatalog( - "gamecult.aetheria.surfaces.v1", - [ - CultMesh.describeSurface(documents.daemonFrame), - CultMesh.describeSurface(documents.daemonHealth), - CultMesh.describeSurface(documents.authorityPolicy), - CultMesh.describeSurface(documents.starbridgeSession), - CultMesh.describeSurface(documents.assetManifest), - CultMesh.describeSurface(handles.mapViewport), - CultMesh.describeSurface(handles.objectsViewport), - CultMesh.describeSurface(handles.gravityViewport), - CultMesh.describeSurface(handles.renderSplatsViewport), - CultMesh.describeSurface(handles.selectedObject), - CultMesh.describeSurface(handles.inventory), - CultMesh.describeSurface(handles.daemonHealth), - CultMesh.describeSurface(handles.authorityStatus), - CultMesh.describeSurface(handles.starbridgeSession), - CultMesh.describeSurface(handles.assetManifest), - ...(operations - ? [ - CultMesh.describeSurface(operations.setMoveVector), - CultMesh.describeSurface(operations.setTarget), - ] - : []), - ], - ); -} - -function describeAetheriaRuntimeGameQuerySurface(surface: { - queryId: string; - routeHint: CultMeshRouteHint; - sources: readonly CultMeshQuerySource[]; -}): AetheriaRuntimeGameQueryDiagnostic { - return CultMesh.describeQuerySurface(surface); -} - -export function describeAetheriaRuntimeGameLiveFeedSurface(surface: { - feedId: string; - routeHint: CultMeshRouteHint; - sources: readonly CultMeshQuerySource[]; -}): AetheriaRuntimeGameLiveFeedDiagnostic { - return CultMesh.describeLiveFeed(surface); -} - -export function createAetheriaRuntimeGameOperationHandles( - sendCommand: AetheriaRuntimeDaemonCommandSender, - createCommandId: () => string = createDefaultCommandId, -) { - return { - setMoveVector: CultMesh.operation( - "gamecult.aetheria.pilot.set_move_vector.v1", - async (request, context) => { - const commandId = context.idempotencyKey ?? createCommandId(); - const issuedAtUtc = new Date().toISOString(); - await sendCommand( - commandId, - issuedAtUtc, - encodeSetMoveVectorCommand(commandId, issuedAtUtc, context.runtimeId, request), - context, - ); - return { - commandId, - ...CultMesh.operationReceipt("gamecult.aetheria.pilot.set_move_vector.v1", true, { route: context.routeHint }), - }; - }, - ), - setTarget: CultMesh.operation( - "gamecult.aetheria.pilot.set_target.v1", - async (request, context) => { - const commandId = context.idempotencyKey ?? createCommandId(); - const issuedAtUtc = new Date().toISOString(); - await sendCommand( - commandId, - issuedAtUtc, - encodeSetTargetCommand(commandId, issuedAtUtc, context.runtimeId, request), - context, - ); - return { - commandId, - ...CultMesh.operationReceipt("gamecult.aetheria.pilot.set_target.v1", true, { route: context.routeHint }), - }; - }, - ), - } as const; -} - -export type AetheriaRuntimeGameVerseHandles = ReturnType; - -export function createAetheriaRuntimeGameVerseHandles( - queryVerse: CultMeshVerseContext, - commandVerse: CultMeshVerseContext, - queries: ReturnType, - operations: AetheriaRuntimeGameOperationHandles, - documents: AetheriaRuntimeGameDocuments = createAetheriaRuntimeGameDocuments(), -) { - const mapViewport = CultMesh.bindQuery(queryVerse, queries.mapViewport); - const objectsViewport = CultMesh.bindQuery(queryVerse, queries.objectsViewport); - const gravityViewport = CultMesh.bindQuery(queryVerse, queries.gravityViewport); - const renderSplatsViewport = CultMesh.bindQuery(queryVerse, queries.renderSplatsViewport); - const selectedObject = CultMesh.bindQuery(queryVerse, queries.selectedObject); - const inventory = CultMesh.bindQuery(queryVerse, queries.inventory); - const daemonHealth = CultMesh.bindQuery(queryVerse, queries.daemonHealth); - const authorityStatus = CultMesh.bindQuery(queryVerse, queries.authorityStatus); - const starbridgeSession = CultMesh.bindQuery(queryVerse, queries.starbridgeSession); - const assetManifest = CultMesh.bindQuery(queryVerse, queries.assetManifest); - const daemonFrameDocument = CultMesh.bindDocument(queryVerse, documents.daemonFrame); - const daemonHealthDocument = CultMesh.bindDocument(queryVerse, documents.daemonHealth); - const authorityPolicyDocument = CultMesh.bindDocument(queryVerse, documents.authorityPolicy); - const starbridgeSessionDocument = CultMesh.bindDocument(queryVerse, documents.starbridgeSession); - const assetManifestDocument = CultMesh.bindDocument(queryVerse, documents.assetManifest); - const setMoveVector = CultMesh.bindOperation(commandVerse, operations.setMoveVector); - const setTarget = CultMesh.bindOperation(commandVerse, operations.setTarget); - - return { - zone: (_zoneId = "current") => ({ - viewport: { - within: (request: CultMeshViewportRequest) => - mapViewport.execute(request), - watch: ( - request: CultMeshViewportRequest, - callback: (value: ViewportResponse) => void, - ) => mapViewport.watch(request, callback), - }, - objects: { - visibleWithin: (request: CultMeshViewportRequest) => - objectsViewport.execute(request), - watchVisibleWithin: ( - request: CultMeshViewportRequest, - callback: (value: ObjectsViewportResponse) => void, - ) => objectsViewport.watch(request, callback), - }, - gravity: { - within: (request: CultMeshViewportRequest) => - gravityViewport.execute(request), - watchWithin: ( - request: CultMeshViewportRequest, - callback: (value: GravityViewportResponse) => void, - ) => gravityViewport.watch(request, callback), - }, - renderSplats: { - within: (request: CultMeshViewportRequest) => - renderSplatsViewport.execute(request), - watchWithin: ( - request: CultMeshViewportRequest, - callback: (value: RenderSplatsViewportResponse) => void, - ) => renderSplatsViewport.watch(request, callback), - }, - }), - entity: (entityKey: string) => ({ - pilot: { - move: ( - direction: { readonly x: number; readonly y: number }, - options: { scalar?: number; observedFrameId?: number; commandId?: string } = {}, - ) => setMoveVector.invoke({ - actorEntityKey: entityKey, - directionX: direction.x, - directionY: direction.y, - scalar: options.scalar ?? 1, - observedFrameId: options.observedFrameId, - }, { idempotencyKey: options.commandId }), - target: ( - targetEntityKey: string, - options: { observedFrameId?: number; commandId?: string } = {}, - ) => setTarget.invoke({ - actorEntityKey: entityKey, - targetEntityKey, - observedFrameId: options.observedFrameId, - }, { idempotencyKey: options.commandId }), - }, - }), - selectedObject: (entityIndex: number) => - selectedObject.execute({ entityIndex }), - inventory: (entityIndex: number) => - inventory.execute({ entityIndex }), - daemon: { - state: { - frame: () => daemonFrameDocument.latest(), - health: () => daemonHealthDocument.latest(), - authorityPolicy: () => authorityPolicyDocument.latest(), - starbridgeSession: () => starbridgeSessionDocument.latest(), - assetManifest: () => assetManifestDocument.latest(), - }, - health: () => daemonHealth.execute(undefined), - authorityStatus: () => authorityStatus.execute(undefined), - starbridgeSession: () => starbridgeSession.execute(undefined), - assetManifest: () => assetManifest.execute(undefined), - }, - } as const; -} - -export function encodeSetMoveVectorCommand( - commandId: string, - issuedAtUtc: string, - runtimeId: string, - request: AetheriaRuntimeSetMoveVectorRequest, -): unknown[] { - const command = createBaseCommand(commandId, issuedAtUtc, runtimeId, request.actorEntityKey, request.observedFrameId); - command[aetheriaRuntimeDaemonCommandDocumentSlots.kind] = AetheriaRuntimeDaemonCommandKinds.setMoveVector; - command[aetheriaRuntimeDaemonCommandDocumentSlots.directionX] = request.directionX; - command[aetheriaRuntimeDaemonCommandDocumentSlots.directionY] = request.directionY; - command[aetheriaRuntimeDaemonCommandDocumentSlots.scalarValue] = Math.min(1, Math.max(0, request.scalar)); - return command; -} - -export function encodeSetTargetCommand( - commandId: string, - issuedAtUtc: string, - runtimeId: string, - request: AetheriaRuntimeSetTargetRequest, -): unknown[] { - const command = createBaseCommand(commandId, issuedAtUtc, runtimeId, request.actorEntityKey, request.observedFrameId); - command[aetheriaRuntimeDaemonCommandDocumentSlots.kind] = AetheriaRuntimeDaemonCommandKinds.setTarget; - command[aetheriaRuntimeDaemonCommandDocumentSlots.targetEntityKey] = request.targetEntityKey; - return command; -} - -function createBaseCommand( - commandId: string, - issuedAtUtc: string, - runtimeId: string, - actorEntityKey: string, - observedFrameId?: number, -): unknown[] { - const command = new Array(Object.keys(aetheriaRuntimeDaemonCommandDocumentSlots).length).fill(null); - command[aetheriaRuntimeDaemonCommandDocumentSlots.schema] = AetheriaRtsSchemas.daemonCommand; - command[aetheriaRuntimeDaemonCommandDocumentSlots.commandId] = commandId; - command[aetheriaRuntimeDaemonCommandDocumentSlots.clientId] = runtimeId; - command[aetheriaRuntimeDaemonCommandDocumentSlots.issuedAtUtc] = issuedAtUtc; - command[aetheriaRuntimeDaemonCommandDocumentSlots.sessionId] = "local"; - command[aetheriaRuntimeDaemonCommandDocumentSlots.observedFrameId] = observedFrameId ?? -1; - command[aetheriaRuntimeDaemonCommandDocumentSlots.kind] = 0; - command[aetheriaRuntimeDaemonCommandDocumentSlots.actorEntityKey] = actorEntityKey; - command[aetheriaRuntimeDaemonCommandDocumentSlots.targetEntityKey] = ""; - command[aetheriaRuntimeDaemonCommandDocumentSlots.targetZoneIndex] = -1; - command[aetheriaRuntimeDaemonCommandDocumentSlots.equipmentIndex] = -1; - command[aetheriaRuntimeDaemonCommandDocumentSlots.behaviorIndex] = -1; - command[aetheriaRuntimeDaemonCommandDocumentSlots.weaponGroup] = -1; - command[aetheriaRuntimeDaemonCommandDocumentSlots.positionX] = 0; - command[aetheriaRuntimeDaemonCommandDocumentSlots.positionY] = 0; - command[aetheriaRuntimeDaemonCommandDocumentSlots.positionZ] = 0; - command[aetheriaRuntimeDaemonCommandDocumentSlots.directionX] = 0; - command[aetheriaRuntimeDaemonCommandDocumentSlots.directionY] = 0; - command[aetheriaRuntimeDaemonCommandDocumentSlots.scalarValue] = 0; - command[aetheriaRuntimeDaemonCommandDocumentSlots.textValue] = ""; - command[aetheriaRuntimeDaemonCommandDocumentSlots.authorRuntimeId] = ""; - command[aetheriaRuntimeDaemonCommandDocumentSlots.subjectKey] = ""; - command[aetheriaRuntimeDaemonCommandDocumentSlots.claimKind] = ""; - return command; -} - -function createDefaultCommandId(): string { - const cryptoApi = globalThis.crypto as { randomUUID?: () => string } | undefined; - return cryptoApi?.randomUUID?.() ?? `command-${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`; -} diff --git a/Aetheria.Rts.Web/Electron/aetheria-rts-local-documents.ts b/Aetheria.Rts.Web/Electron/aetheria-rts-local-documents.ts deleted file mode 100644 index 3edd60603..000000000 --- a/Aetheria.Rts.Web/Electron/aetheria-rts-local-documents.ts +++ /dev/null @@ -1,495 +0,0 @@ -import { - AetheriaRtsSchemas, - aetheriaRuntimeAssetManifestDocumentSlots as assetManifestSlots, - aetheriaRuntimeAssetManifestEntrySlots as assetManifestEntrySlots, - aetheriaRuntimeAssetRefSlots as assetRefSlots, - aetheriaRuntimeAuthorityRuleSlots as authorityRuleSlots, - aetheriaRuntimeDaemonHealthDocumentSlots as healthSlots, - aetheriaRuntimeGameViewportBoundsSlots as viewportBoundsSlots, - aetheriaRuntimeGameViewportDocumentSlots as gameViewportSlots, - aetheriaRuntimeGameViewportObjectSlots as viewportObjectSlots, - aetheriaRuntimeGravityViewportDocumentSlots as gravityViewportSlots, - aetheriaRuntimeInventoryDocumentSlots as inventoryDocumentSlots, - aetheriaRuntimeObjectsViewportDocumentSlots as objectsViewportSlots, - aetheriaRuntimeRenderSplatLayerDefinitionSlots as renderSplatLayerSlots, - aetheriaRuntimeRenderSplatsViewportDocumentSlots as renderSplatsViewportSlots, - aetheriaRuntimeRenderSplatSoaSlots as renderSplatSoaSlots, - aetheriaRuntimeRtsBodyViewSlots as bodyViewSlots, - aetheriaRuntimeRtsEntityStatusSlots as viewportStatusSlots, - aetheriaRuntimeRtsGravityInfluenceSlots as gravityInfluenceSlots, - aetheriaRuntimeRtsInventoryItemSlots as viewportInventoryItemSlots, - aetheriaRuntimeSelectedObjectDocumentSlots as selectedObjectDocumentSlots, - aetheriaRuntimeStarbridgeBaseStatusSlots as starbridgeBaseSlots, - aetheriaRuntimeStarbridgeRuntimeRoleSlots as starbridgeRoleSlots, - aetheriaRuntimeStarbridgeSessionSummaryDocumentSlots as starbridgeSummarySlots, - aetheriaRuntimeStarbridgeStationStockItemSlots as starbridgeStockSlots, - aetheriaRuntimeStarbridgeWaveForecastSlots as starbridgeWaveForecastSlots, - aetheriaRuntimeVerseAuthorityPolicyDocumentSlots as authorityPolicySlots, -} from "./aetheria-rts-generated-bindings.js"; -import { cultMeshRectFromBounds, cultMeshViewportRequest } from "cultmesh-ts"; -import type { - AssetManifestDocument, - AssetRef, - AuthorityStatusDocument, - BodyView, - DaemonHealthDocument, - EntityStatus, - GravityViewportResponse, - GravityInfluence, - InventoryDocument, - InventoryItem, - ObjectsViewportResponse, - RenderSplatsViewportResponse, - SelectedObjectDocument, - StarbridgeSessionDocument, - ViewObject, - ViewportRequest, - ViewportResponse, -} from "./aetheria-rts-bindings.js"; - -const missingDaemonRunId = "aetheria.run.unknown"; - -export function readSelectedObjectDocument(selectedObjectDocument: unknown): SelectedObjectDocument { - const document = arr(selectedObjectDocument); - return { - schema: str(document[selectedObjectDocumentSlots.schema]) || AetheriaRtsSchemas.selectedObject, - frameId: num(document[selectedObjectDocumentSlots.frameId]), - runId: str(document[selectedObjectDocumentSlots.runId]) || missingDaemonRunId, - zoneIndex: num(document[selectedObjectDocumentSlots.zoneIndex]), - entityIndex: num(document[selectedObjectDocumentSlots.entityIndex], -1), - selected: readNullableViewportObject(document[selectedObjectDocumentSlots.selected]), - }; -} - -export function readInventoryDocument(inventoryDocument: unknown): InventoryDocument { - const document = arr(inventoryDocument); - return { - schema: str(document[inventoryDocumentSlots.schema]) || AetheriaRtsSchemas.inventory, - frameId: num(document[inventoryDocumentSlots.frameId]), - runId: str(document[inventoryDocumentSlots.runId]) || missingDaemonRunId, - zoneIndex: num(document[inventoryDocumentSlots.zoneIndex]), - entityIndex: num(document[inventoryDocumentSlots.entityIndex], -1), - entityKey: str(document[inventoryDocumentSlots.entityKey]), - items: list(document[inventoryDocumentSlots.items]).map(readViewportInventoryItem), - equipment: list(document[inventoryDocumentSlots.equipment]).map(readViewportInventoryItem), - cargo: list(document[inventoryDocumentSlots.cargo]).map(readViewportInventoryItem), - }; -} - -export function readViewportDocument(viewportDocument: unknown): ViewportResponse { - const document = arr(viewportDocument); - const objects = readViewportObjects(list(document[gameViewportSlots.objects])); - const gravityInfluences = readGravityInfluences(list(document[gameViewportSlots.gravityInfluences])); - const bodies = readBodyViews(list(document[gameViewportSlots.bodies])); - return { - schema: str(document[gameViewportSlots.schema]) || AetheriaRtsSchemas.gameViewport, - frameId: num(document[gameViewportSlots.frameId]), - publishedAtUtc: str(document[gameViewportSlots.publishedAtUtc]), - simulationTimeSeconds: num(document[gameViewportSlots.simulationTimeSeconds]), - runId: str(document[gameViewportSlots.runId]) || missingDaemonRunId, - zoneIndex: num(document[gameViewportSlots.zoneIndex]), - zoneName: str(document[gameViewportSlots.zoneName]), - currentEntityKey: str(document[gameViewportSlots.currentEntityKey]), - viewport: readViewportBounds(document[gameViewportSlots.viewport]), - controlledEntityIndices: numberList(document[gameViewportSlots.controlledEntityIndices]), - objects, - gravityInfluences, - bodies, - }; -} - -export function readObjectsViewportDocument(viewportDocument: unknown): ObjectsViewportResponse { - const document = arr(viewportDocument); - return { - schema: str(document[objectsViewportSlots.schema]) || AetheriaRtsSchemas.objectsViewport, - frameId: num(document[objectsViewportSlots.frameId]), - publishedAtUtc: str(document[objectsViewportSlots.publishedAtUtc]), - simulationTimeSeconds: num(document[objectsViewportSlots.simulationTimeSeconds]), - runId: str(document[objectsViewportSlots.runId]) || missingDaemonRunId, - zoneIndex: num(document[objectsViewportSlots.zoneIndex]), - zoneName: str(document[objectsViewportSlots.zoneName]), - currentEntityKey: str(document[objectsViewportSlots.currentEntityKey]), - viewport: readViewportBounds(document[objectsViewportSlots.viewport]), - controlledEntityIndices: numberList(document[objectsViewportSlots.controlledEntityIndices]), - objects: readViewportObjects(list(document[objectsViewportSlots.objects])), - }; -} - -export function readGravityViewportDocument(viewportDocument: unknown): GravityViewportResponse { - const document = arr(viewportDocument); - return { - schema: str(document[gravityViewportSlots.schema]) || AetheriaRtsSchemas.gravityViewport, - frameId: num(document[gravityViewportSlots.frameId]), - publishedAtUtc: str(document[gravityViewportSlots.publishedAtUtc]), - simulationTimeSeconds: num(document[gravityViewportSlots.simulationTimeSeconds]), - runId: str(document[gravityViewportSlots.runId]) || missingDaemonRunId, - zoneIndex: num(document[gravityViewportSlots.zoneIndex]), - zoneName: str(document[gravityViewportSlots.zoneName]), - viewport: readViewportBounds(document[gravityViewportSlots.viewport]), - gravityInfluences: readGravityInfluences(list(document[gravityViewportSlots.gravityInfluences])), - bodies: readBodyViews(list(document[gravityViewportSlots.bodies])), - }; -} - -export function readRenderSplatsViewportDocument(viewportDocument: unknown): RenderSplatsViewportResponse { - const document = arr(viewportDocument); - return { - schema: str(document[renderSplatsViewportSlots.schema]) || AetheriaRtsSchemas.renderSplatsViewport, - frameId: num(document[renderSplatsViewportSlots.frameId]), - publishedAtUtc: str(document[renderSplatsViewportSlots.publishedAtUtc]), - simulationTimeSeconds: num(document[renderSplatsViewportSlots.simulationTimeSeconds]), - runId: str(document[renderSplatsViewportSlots.runId]) || missingDaemonRunId, - zoneIndex: num(document[renderSplatsViewportSlots.zoneIndex]), - zoneName: str(document[renderSplatsViewportSlots.zoneName]), - viewport: readViewportBounds(document[renderSplatsViewportSlots.viewport]), - layers: list(document[renderSplatsViewportSlots.layers]).map(readRenderSplatLayer), - splats: readRenderSplatSoa(document[renderSplatsViewportSlots.splats]), - }; -} - -export function readDaemonHealthDocument(healthDocument: unknown): DaemonHealthDocument { - const health = arr(healthDocument); - return { - schema: str(health[healthSlots.schema]) || AetheriaRtsSchemas.daemonHealth, - daemonId: str(health[healthSlots.daemonId]), - verseId: str(health[healthSlots.verseId]), - publishedAtUtc: str(health[healthSlots.publishedAtUtc]), - statePath: str(health[healthSlots.statePath]), - frameId: num(health[healthSlots.frameId]), - observedCommandCount: num(health[healthSlots.observedCommandCount]), - appliedCommandCount: num(health[healthSlots.appliedCommandCount]), - rejectedCommandCount: num(health[healthSlots.rejectedCommandCount]), - status: str(health[healthSlots.status]) || "unknown", - publicationSource: str(health[healthSlots.publicationSource]), - transport: str(health[healthSlots.transport]), - commandBoundaryPath: str(health[healthSlots.commandBoundaryPath]), - }; -} - -export function readAuthorityStatusDocument(policyDocument: unknown): AuthorityStatusDocument { - const policy = arr(policyDocument); - return { - schema: str(policy[authorityPolicySlots.schema]) || AetheriaRtsSchemas.verseAuthorityPolicy, - verseId: str(policy[authorityPolicySlots.verseId]), - policyId: str(policy[authorityPolicySlots.policyId]), - ruleVersion: str(policy[authorityPolicySlots.ruleVersion]), - hostRuntimeId: str(policy[authorityPolicySlots.hostRuntimeId]), - defaultMode: str(policy[authorityPolicySlots.defaultMode]), - updatedAtUtc: str(policy[authorityPolicySlots.updatedAtUtc]), - rules: list(policy[authorityPolicySlots.rules]).map(rule => ({ - ruleId: str(rule[authorityRuleSlots.ruleId]), - subjectPrefix: str(rule[authorityRuleSlots.subjectPrefix]), - claimKinds: stringList(rule[authorityRuleSlots.claimKinds]), - mode: str(rule[authorityRuleSlots.mode]), - runtimeIds: stringList(rule[authorityRuleSlots.runtimeIds]), - leaseScope: str(rule[authorityRuleSlots.leaseScope]), - priority: num(rule[authorityRuleSlots.priority]), - })), - }; -} - -export function readStarbridgeSessionSummaryDocument(summaryDocument: unknown): StarbridgeSessionDocument { - const summary = arr(summaryDocument); - return { - schema: str(summary[starbridgeSummarySlots.schema]) || AetheriaRtsSchemas.starbridgeSessionSummary, - frameId: num(summary[starbridgeSummarySlots.frameId]), - publishedAtUtc: str(summary[starbridgeSummarySlots.publishedAtUtc]), - sessionId: str(summary[starbridgeSummarySlots.sessionId]) || "starbridge-session", - scenarioId: str(summary[starbridgeSummarySlots.scenarioId]) || "starbridge.local", - scenarioName: str(summary[starbridgeSummarySlots.scenarioName]) || "Starbridge", - runId: str(summary[starbridgeSummarySlots.runId]) || "local-starbridge", - zoneIndex: num(summary[starbridgeSummarySlots.zoneIndex]), - zoneName: str(summary[starbridgeSummarySlots.zoneName]), - phase: str(summary[starbridgeSummarySlots.phase]) || "setup", - currentWaveIndex: num(summary[starbridgeSummarySlots.currentWaveIndex]), - baseStatus: toStarbridgeBaseStatus(arr(summary[starbridgeSummarySlots.baseStatus])), - stationStock: list(summary[starbridgeSummarySlots.stationStock]).map(toStarbridgeStationStockItem), - waveForecast: list(summary[starbridgeSummarySlots.waveForecast]).map(toStarbridgeWaveForecast), - runtimeRoles: list(summary[starbridgeSummarySlots.runtimeRoles]).map(toStarbridgeRuntimeRole), - }; -} - -export function readAssetManifestDocument(assetManifestDocument: unknown): AssetManifestDocument { - const manifest = arr(assetManifestDocument); - return { - schema: str(manifest[assetManifestSlots.schema]) || AetheriaRtsSchemas.assetManifest, - publishedAtUtc: str(manifest[assetManifestSlots.publishedAtUtc]), - runId: str(manifest[assetManifestSlots.runId]), - baseUri: str(manifest[assetManifestSlots.baseUri]), - assets: list(manifest[assetManifestSlots.assets]).map(toAssetManifestEntry), - }; -} - -function readViewportBounds(value: unknown): ViewportRequest { - const bounds = arr(value); - return cultMeshViewportRequest( - cultMeshRectFromBounds( - num(bounds[viewportBoundsSlots.minX]), - num(bounds[viewportBoundsSlots.minY]), - num(bounds[viewportBoundsSlots.maxX]), - num(bounds[viewportBoundsSlots.maxY]), - ), - [], - ); -} - -function readViewportObjects(objects: unknown[][]): ViewObject[] { - return objects.map(readViewportObject); -} - -function readNullableViewportObject(value: unknown): ViewObject | null { - return Array.isArray(value) ? readViewportObject(value) : null; -} - -function readViewportObject(object: unknown[]): ViewObject { - return { - entityIndex: num(object[viewportObjectSlots.entityIndex], -1), - entityKey: str(object[viewportObjectSlots.entityKey]), - displayName: str(object[viewportObjectSlots.displayName]), - kind: str(object[viewportObjectSlots.kind]), - factionKey: str(object[viewportObjectSlots.factionKey]), - x: num(object[viewportObjectSlots.x]), - y: num(object[viewportObjectSlots.y]), - z: num(object[viewportObjectSlots.z]), - directionX: num(object[viewportObjectSlots.directionX]), - directionY: num(object[viewportObjectSlots.directionY]), - velocityX: num(object[viewportObjectSlots.velocityX]), - velocityY: num(object[viewportObjectSlots.velocityY]), - controlled: bool(object[viewportObjectSlots.controlled]), - targetEntityIndex: num(object[viewportObjectSlots.targetEntityIndex], -1), - isActive: object[viewportObjectSlots.isActive] !== false, - visibility: num(object[viewportObjectSlots.visibility]), - iconAsset: assetRef(arr(object[viewportObjectSlots.iconAsset])), - status: readViewportStatus(object[viewportObjectSlots.status]), - inventory: list(object[viewportObjectSlots.inventory]).map(readViewportInventoryItem), - }; -} - -function readViewportStatus(value: unknown): EntityStatus { - const status = arr(value); - return { - hull: num(status[viewportStatusSlots.hull]), - shield: num(status[viewportStatusSlots.shield]), - heat: num(status[viewportStatusSlots.heat]), - }; -} - -function readViewportInventoryItem(value: unknown[]): InventoryItem { - const itemKey = str(value[viewportInventoryItemSlots.itemKey]); - return { - source: str(value[viewportInventoryItemSlots.source]), - itemKey, - quantity: num(value[viewportInventoryItemSlots.quantity]), - quality: num(value[viewportInventoryItemSlots.quality]), - durability: num(value[viewportInventoryItemSlots.durability]), - enabled: value[viewportInventoryItemSlots.enabled] !== false, - iconAsset: assetRef(arr(value[viewportInventoryItemSlots.iconAsset])), - }; -} - -function readGravityInfluences(influences: unknown[][]): GravityInfluence[] { - return influences.map(influence => ({ - bodyKey: str(influence[gravityInfluenceSlots.bodyKey]), - orbitKey: str(influence[gravityInfluenceSlots.orbitKey]), - kind: str(influence[gravityInfluenceSlots.kind]), - x: num(influence[gravityInfluenceSlots.x]), - y: num(influence[gravityInfluenceSlots.y]), - radius: num(influence[gravityInfluenceSlots.radius]), - gravityDepth: num(influence[gravityInfluenceSlots.gravityDepth]), - gravityDepthExponent: num(influence[gravityInfluenceSlots.gravityDepthExponent]), - waveRadius: num(influence[gravityInfluenceSlots.waveRadius]), - waveDepth: num(influence[gravityInfluenceSlots.waveDepth]), - waveSpeed: num(influence[gravityInfluenceSlots.waveSpeed]), - })); -} - -function readBodyViews(bodies: unknown[][]): BodyView[] { - return bodies.map(body => { - const kind = str(body[bodyViewSlots.kind]); - return { - bodyKey: str(body[bodyViewSlots.bodyKey]), - orbitKey: str(body[bodyViewSlots.orbitKey]), - name: str(body[bodyViewSlots.name]), - kind, - x: num(body[bodyViewSlots.x]), - y: num(body[bodyViewSlots.y]), - radius: num(body[bodyViewSlots.radius]), - isAsteroidBelt: bool(body[bodyViewSlots.isAsteroidBelt]), - body: num(body[bodyViewSlots.body]), - iconAsset: assetRef(arr(body[bodyViewSlots.iconAsset])), - iconSize: num(body[bodyViewSlots.iconSize]), - }; - }); -} - -function readRenderSplatLayer(layer: unknown[]): RenderSplatsViewportResponse["layers"][number] { - return { - layerKey: str(layer[renderSplatLayerSlots.layerKey]), - displayName: str(layer[renderSplatLayerSlots.displayName]), - channel: num(layer[renderSplatLayerSlots.channel]), - blendMode: str(layer[renderSplatLayerSlots.blendMode]), - graphicsFormat: str(layer[renderSplatLayerSlots.graphicsFormat]), - clearBeforeDraw: bool(layer[renderSplatLayerSlots.clearBeforeDraw]), - clearR: num(layer[renderSplatLayerSlots.clearR]), - clearG: num(layer[renderSplatLayerSlots.clearG]), - clearB: num(layer[renderSplatLayerSlots.clearB]), - clearA: num(layer[renderSplatLayerSlots.clearA]), - }; -} - -function readRenderSplatSoa(value: unknown): RenderSplatsViewportResponse["splats"] { - const splats = arr(value); - return { - count: num(splats[renderSplatSoaSlots.count]), - centerX: numberList(splats[renderSplatSoaSlots.centerX]), - centerY: numberList(splats[renderSplatSoaSlots.centerY]), - halfExtentX: numberList(splats[renderSplatSoaSlots.halfExtentX]), - halfExtentY: numberList(splats[renderSplatSoaSlots.halfExtentY]), - rotationCos: numberList(splats[renderSplatSoaSlots.rotationCos]), - rotationSin: numberList(splats[renderSplatSoaSlots.rotationSin]), - channel: numberList(splats[renderSplatSoaSlots.channel]), - falloff: numberList(splats[renderSplatSoaSlots.falloff]), - valueR: numberList(splats[renderSplatSoaSlots.valueR]), - valueG: numberList(splats[renderSplatSoaSlots.valueG]), - valueB: numberList(splats[renderSplatSoaSlots.valueB]), - valueA: numberList(splats[renderSplatSoaSlots.valueA]), - sourceKey: stringList(splats[renderSplatSoaSlots.sourceKey]), - layerIndex: numberList(splats[renderSplatSoaSlots.layerIndex]), - sourceKind: numberList(splats[renderSplatSoaSlots.sourceKind]), - frequencyX: numberList(splats[renderSplatSoaSlots.frequencyX]), - frequencyY: numberList(splats[renderSplatSoaSlots.frequencyY]), - phaseX: numberList(splats[renderSplatSoaSlots.phaseX]), - phaseY: numberList(splats[renderSplatSoaSlots.phaseY]), - animationSpeed: numberList(splats[renderSplatSoaSlots.animationSpeed]), - sourceFlags: numberList(splats[renderSplatSoaSlots.sourceFlags]), - }; -} - -function toStarbridgeBaseStatus(status: unknown[]): StarbridgeSessionDocument["baseStatus"] { - return { - entityKey: str(status[starbridgeBaseSlots.entityKey]), - displayName: str(status[starbridgeBaseSlots.displayName]), - hull: num(status[starbridgeBaseSlots.hull]), - shield: num(status[starbridgeBaseSlots.shield]), - heat: num(status[starbridgeBaseSlots.heat]), - isActive: bool(status[starbridgeBaseSlots.isActive]), - }; -} - -function toStarbridgeStationStockItem(item: unknown[]): StarbridgeSessionDocument["stationStock"][number] { - return { - itemKey: str(item[starbridgeStockSlots.itemKey]), - quantity: num(item[starbridgeStockSlots.quantity]), - quality: num(item[starbridgeStockSlots.quality]), - durability: num(item[starbridgeStockSlots.durability]), - source: str(item[starbridgeStockSlots.source]) || "station", - iconAsset: assetRef(arr(item[starbridgeStockSlots.iconAsset])), - }; -} - -function toStarbridgeWaveForecast(wave: unknown[]): StarbridgeSessionDocument["waveForecast"][number] { - return { - waveIndex: num(wave[starbridgeWaveForecastSlots.waveIndex]), - displayName: str(wave[starbridgeWaveForecastSlots.displayName]), - attackerKeys: stringList(wave[starbridgeWaveForecastSlots.attackerKeys]), - bossKey: str(wave[starbridgeWaveForecastSlots.bossKey]), - recoveredTechnologyKeys: stringList(wave[starbridgeWaveForecastSlots.recoveredTechnologyKeys]), - }; -} - -function toStarbridgeRuntimeRole(role: unknown[]): StarbridgeSessionDocument["runtimeRoles"][number] { - return { - runtimeId: str(role[starbridgeRoleSlots.runtimeId]), - role: str(role[starbridgeRoleSlots.role]), - entityKey: str(role[starbridgeRoleSlots.entityKey]), - }; -} - -function toAssetManifestEntry(entry: unknown[]): AssetManifestDocument["assets"][number] { - return { - ref: assetRef(arr(entry[assetManifestEntrySlots.ref])), - sizeBytes: num(entry[assetManifestEntrySlots.sizeBytes]), - width: num(entry[assetManifestEntrySlots.width]), - height: num(entry[assetManifestEntrySlots.height]), - tags: stringList(entry[assetManifestEntrySlots.tags]), - }; -} - -function assetRef(value: unknown[]): AssetRef { - const assetKey = str(value[assetRefSlots.assetKey]); - if (assetKey.length === 0) - return emptyAsset(); - - return asset( - assetKey, - str(value[assetRefSlots.kind]), - str(value[assetRefSlots.uri]), - str(value[assetRefSlots.transport]), - str(value[assetRefSlots.mimeType]), - str(value[assetRefSlots.contentHash]), - stringRecord(value[assetRefSlots.metadata]), - ); -} - -function asset( - assetKey: string, - kind: string, - uri: string, - transport: string, - mimeType: string, - contentHash = "", - metadata: Record = {}, -): AssetRef { - return { - assetKey, - kind, - uri, - transport, - contentHash, - mimeType, - metadata, - }; -} - -function emptyAsset(kind = ""): AssetRef { - return asset("", kind, "", "", ""); -} - -function arr(value: unknown): unknown[] { - return Array.isArray(value) ? value : []; -} - -function list(value: unknown): T[] { - return Array.isArray(value) ? value.filter(Array.isArray) as T[] : []; -} - -function numberList(value: unknown): number[] { - return Array.isArray(value) ? value.map(candidate => num(candidate)) : []; -} - -function stringList(value: unknown): string[] { - return Array.isArray(value) ? value.map(candidate => str(candidate)).filter(candidate => candidate.length > 0) : []; -} - -function stringRecord(value: unknown): Record { - if (!value || typeof value !== "object" || Array.isArray(value)) - return {}; - - return Object.fromEntries( - Object.entries(value) - .map(([key, candidate]) => [key, str(candidate)]) - .filter(([, candidate]) => candidate.length > 0), - ); -} - -function str(value: unknown): string { - return typeof value === "string" ? value : ""; -} - -function num(value: unknown, fallback = 0): number { - return typeof value === "number" && Number.isFinite(value) ? value : fallback; -} - -function bool(value: unknown): boolean { - return value === true; -} diff --git a/Aetheria.Rts.Web/Electron/main.ts b/Aetheria.Rts.Web/Electron/main.ts deleted file mode 100644 index a35d903f5..000000000 --- a/Aetheria.Rts.Web/Electron/main.ts +++ /dev/null @@ -1,432 +0,0 @@ -import { app, BrowserWindow, ipcMain, protocol, shell } from "electron"; -import { spawn, type ChildProcessWithoutNullStreams } from "node:child_process"; -import { createWriteStream, existsSync, mkdirSync, readdirSync, statSync, writeFileSync } from "node:fs"; -import { dirname, resolve } from "node:path"; -import { fileURLToPath } from "node:url"; - -import { startEveElectronProviderHost } from "@gamecult/eve-electron/live-provider-host"; -import { CultMesh } from "cultmesh-ts"; -import { encode } from "@msgpack/msgpack"; - -const __dirname = dirname(fileURLToPath(import.meta.url)); -const projectRoot = resolve(__dirname, ".."); -const repoRoot = resolve(projectRoot, ".."); -const logsRoot = resolve(projectRoot, "logs"); -const runtimeRoot = resolve(process.env.AETHERIA_RUNTIME_ROOT ?? resolve(projectRoot, "runtime")); -const runtimeStatePath = resolve(runtimeRoot, "aetheria.cc"); -const daemonDll = resolve(repoRoot, "Aetheria.State.Daemon", "bin", "Debug", "net10.0", "Aetheria.State.Daemon.dll"); -const rendererIndex = resolve(projectRoot, "wwwroot", "index.html"); -const clientCultMeshPort = Number.parseInt(process.env.AETHERIA_CLIENT_CULTMESH_PORT ?? "3076", 10); -const verseId = process.env.AETHERIA_VERSE_ID?.trim() || "aetheria.local"; -const daemonId = process.env.AETHERIA_DAEMON_ID?.trim() || "aetheria-daemon"; -const configuredDaemonUri = process.env.AETHERIA_CULTMESH_URI?.trim() ?? ""; -const launchLocalDaemon = configuredDaemonUri.length === 0; -const daemonCultMeshUri = configuredDaemonUri || `cultmesh://aetheria/daemon/${encodeURIComponent(daemonId)}`; -const clientCultMeshAdvertiseHost = process.env.AETHERIA_CLIENT_CULTMESH_ADVERTISE_HOST?.trim() || "127.0.0.1"; -const localDaemonRudpEndpoint = launchLocalDaemon ? `rudp://127.0.0.1:${clientCultMeshPort}` : ""; -const electronSmoke = process.env.AETHERIA_ELECTRON_SMOKE === "1"; -const electronSmokeResultPath = process.env.AETHERIA_ELECTRON_SMOKE_RESULT; - -let daemonProcess: ChildProcessWithoutNullStreams | null = null; -let mainWindow: BrowserWindow | null = null; -let eveHost: { close(): Promise; window: BrowserWindow } | null = null; -let isQuitting = false; - -app.whenReady().then(async () => { - writeElectronSmokeResult({ ok: false, stage: "electron-ready" }); - - try { - if (launchLocalDaemon) { - await ensureDotnetBuild(); - mkdirSync(runtimeRoot, { recursive: true }); - daemonProcess = startDotnet("aetheria-daemon", daemonDll, [ - "--state", - runtimeStatePath, - "--verse-id", - verseId, - "--daemon-id", - daemonId, - "--client-cultmesh-port", - clientCultMeshPort.toString(), - "--client-cultmesh-advertise-host", - clientCultMeshAdvertiseHost, - "--tick-interval-ms", - "20", - "--fixed-delta-ms", - "20", - "--api-publication-interval-ms", - "100000", - "--no-odin-announcements", - ]); - } else { - mkdirSync(runtimeRoot, { recursive: true }); - } - - eveHost = await startEveElectronProviderHost({ - electron: { app, BrowserWindow, ipcMain, protocol, shell }, - dependencies: { CultMesh, encode }, - providerTarget: { - providerId: "aetheria.daemon", - advertisementRecordRef: "eve:provider:aetheria.daemon", - peerId: daemonId, - verseId, - role: "aetheria-daemon", - endpoints: localDaemonRudpEndpoint ? [localDaemonRudpEndpoint] : [], - }, - renderer: rendererIndex, - runtimeId: "eve-electron-aetheria", - surfaceId: process.env.EVE_SURFACE_ID || "aetheria.starbridge.commander", - window: { - show: !electronSmoke, - backgroundColor: "#0b1016", - title: "Aetheria Starbridge", - }, - }); - mainWindow = eveHost.window; - writeElectronSmokeResult({ ok: false, stage: "renderer-loaded" }); - if (electronSmoke) { - await withTimeout(mainWindow.webContents.executeJavaScript("window.eveProvider.providerAdvertisement()"), 10000, "provider advertisement smoke"); - writeElectronSmokeResult({ ok: false, stage: "provider-advertisement-readable" }); - await withTimeout(mainWindow.webContents.executeJavaScript("window.eveProvider.surface({ surfaceId: 'aetheria.starbridge.commander' })"), 10000, "surface smoke"); - writeElectronSmokeResult({ ok: false, stage: "provider-surface-readable" }); - await withTimeout(mainWindow.webContents.executeJavaScript(`window.eveProvider.submitCommand({ - providerId: 'aetheria.daemon', surfaceId: 'aetheria.starbridge.commander', command: 'aetheria.daemon.commands.SensorPing', - clientId: 'aetheria-electron-smoke-preflight', payload: {} - })`), 10000, "command receipt smoke"); - writeElectronSmokeResult({ ok: false, stage: "provider-command-receipt-readable" }); - const result = await runElectronSmoke(mainWindow); - writeElectronSmokeResult({ ok: true, result }); - console.log(JSON.stringify(result, null, 2)); - exitElectronSmoke(0); - } - } catch (error) { - if (electronSmoke) { - writeElectronSmokeResult({ - ok: false, - error: error instanceof Error ? error.stack ?? error.message : String(error), - }); - console.error(error instanceof Error ? error.stack ?? error.message : String(error)); - exitElectronSmoke(1); - } else { - console.error(error instanceof Error ? error.stack ?? error.message : String(error)); - } - } -}); - -app.on("before-quit", () => { - isQuitting = true; - stopChild(daemonProcess); -}); - -async function runElectronSmoke(window: BrowserWindow): Promise> { - const started = Date.now(); - let lastResult: Record | null = null; - while (Date.now() - started < 15000) { - lastResult = await window.webContents.executeJavaScript(` - (async () => { - await new Promise(resolve => setTimeout(resolve, 100)); - const eveProvider = window.eveProvider; - const status = document.querySelector("#status")?.textContent ?? ""; - const bodyMode = document.body.className; - const eveHostText = document.querySelector("#eve-surface-host")?.textContent ?? ""; - const worldScene = document.querySelector(".cultui-world-scene"); - const worldEntityCount = worldScene?.querySelectorAll(".cultui-world-entity").length ?? 0; - const controlledWorldEntityCount = worldScene?.querySelectorAll('.cultui-world-entity[data-controlled="true"]').length ?? 0; - const workerRosterCount = document.querySelectorAll('.cultui-list-item[data-component-kind="agent.item"]').length; - const providerAdvertisement = eveProvider ? await eveProvider.providerAdvertisement() : null; - const eveSurface = eveProvider ? await eveProvider.surface({ surfaceId: "aetheria.starbridge.commander" }) : null; - const pilotSurface = eveProvider ? await eveProvider.surface({ surfaceId: "aetheria.pilot" }) : null; - const findComponent = (component, predicate) => { - if (!component || typeof component !== "object") return null; - if (predicate(component)) return component; - for (const child of component.children ?? []) { - const match = findComponent(child, predicate); - if (match) return match; - } - return null; - }; - const eveFieldSurface = findComponent(pilotSurface?.surface?.root, component => - component.kind === "field.surface2d" || component.kind === "gravity.surface"); - const embedded = slotId => eveFieldSurface?.embeddedDocuments?.find(slot => slot.slotId === slotId) ?? null; - const resolveEmbedded = async slot => slot ? eveProvider.document({ documentId: slot.documentId, schemaId: slot.schemaId }) : null; - const [renderSplatsResolved, gravityResolved, objectsResolved] = await Promise.all([ - resolveEmbedded(embedded("renderSplats")), - resolveEmbedded(embedded("gravity")), - resolveEmbedded(embedded("objects")), - ]); - const assetProbe = await new Promise(resolve => { - const image = new Image(); - image.onload = () => resolve({ loaded: true, width: image.naturalWidth, height: image.naturalHeight }); - image.onerror = () => resolve({ loaded: false, width: 0, height: 0 }); - image.src = "eve-asset://asset?uri=" + encodeURIComponent("cultmesh://aetheria/assets/map/entity/player"); - }); - const eveReceipt = eveProvider ? await eveProvider.submitCommand({ - providerId: "aetheria.daemon", - surfaceId: "aetheria.starbridge.commander", - command: "aetheria.daemon.commands.SensorPing", - clientId: "aetheria-electron-smoke", - payload: {}, - }) : null; - return { - hasApi: !!eveProvider && - typeof eveProvider.providerAdvertisement === "function" && - typeof eveProvider.document === "function" && - typeof eveProvider.surface === "function" && - typeof eveProvider.submitCommand === "function", - status, - bodyMode, - eveHostText, - worldSceneReady: !!worldScene, - worldEntityCount, - controlledWorldEntityCount, - workerRosterCount, - providerAdvertisement, - eveSurface, - pilotSurface, - renderSplatsResolved, - gravityResolved, - objectsResolved, - assetProbe, - eveFieldSurface, - eveReceipt - }; - })() - `, true) as Record; - - if (isElectronSmokeReady(lastResult)) - return lastResult; - await delay(250); - } - - throw new Error(`Aetheria Starbridge Electron smoke did not reach ready state. Last result: ${JSON.stringify(lastResult)}`); -} - -function isElectronSmokeReady(result: Record): boolean { - const status = stringValue(result.status); - const bodyMode = stringValue(result.bodyMode); - const eveHostText = stringValue(result.eveHostText); - const eveSurface = objectValue(result.eveSurface); - const providerAdvertisement = objectValue(result.providerAdvertisement); - const renderSplatsResolved = objectValue(result.renderSplatsResolved); - const gravityResolved = objectValue(result.gravityResolved); - const objectsResolved = objectValue(result.objectsResolved); - const eveFieldSurface = objectValue(result.eveFieldSurface); - const eveReceipt = objectValue(result.eveReceipt); - return result.hasApi === true && - status.includes("Starbridge Commander") && - bodyMode.includes("eve-game-mode") && - result.worldSceneReady === true && - Number(result.worldEntityCount) > 0 && - Number(result.controlledWorldEntityCount) === 1 && - Number(result.workerRosterCount) > 0 && - eveHostText.includes("Station Stock") && - eveHostText.includes("Wave Forecast") && - arrayValue(providerAdvertisement?.surfaces).some(surface => objectValue(surface)?.surfaceId === "aetheria.starbridge.commander") && - objectValue(eveSurface?.surface)?.id === "aetheria.starbridge.commander" && - stringValue(renderSplatsResolved?.documentId).length > 0 && - stringValue(gravityResolved?.documentId).length > 0 && - stringValue(objectsResolved?.documentId).length > 0 && - renderSplatsResolved?.schemaId === "gamecult.fields.splats.v1" && - gravityResolved?.schemaId === "gamecult.fields.gravity.v1" && - objectsResolved?.schemaId === "gamecult.fields.objects.v1" && - objectValue(result.assetProbe)?.loaded === true && - Number(objectValue(result.assetProbe)?.width) > 0 && - Number(objectValue(result.assetProbe)?.height) > 0 && - stringValue(eveFieldSurface?.id).length > 0 && - arrayValue(eveFieldSurface?.embeddedDocuments).some(slot => - objectValue(slot)?.slotId === "renderSplats") && - arrayValue(eveFieldSurface?.embeddedDocuments).some(slot => - objectValue(slot)?.slotId === "gravity") && - arrayValue(eveFieldSurface?.embeddedDocuments).some(slot => - objectValue(slot)?.slotId === "objects") && - stringValue(eveReceipt?.commandId).length > 0 && - objectValue(eveReceipt?.route)?.kind === "network" && - eveReceipt?.accepted === true; -} - -function stringValue(value: unknown): string { - return typeof value === "string" ? value : ""; -} - -function objectValue(value: unknown): Record | null { - return value != null && typeof value === "object" ? value as Record : null; -} - -function arrayValue(value: unknown): unknown[] { - return Array.isArray(value) ? value : []; -} - -function writeElectronSmokeResult(result: Record): void { - if (!electronSmokeResultPath) - return; - - writeFileSync(electronSmokeResultPath, JSON.stringify(result, null, 2), "utf8"); -} - -function exitElectronSmoke(exitCode: number): void { - isQuitting = true; - void eveHost?.close(); - eveHost = null; - stopChild(daemonProcess); - daemonProcess = null; - mainWindow?.destroy(); - mainWindow = null; - app.exit(exitCode); -} - -function withTimeout(work: Promise, timeoutMs: number, label: string): Promise { - return Promise.race([ - work, - new Promise((_resolve, reject) => setTimeout(() => reject(new Error(`Timed out during ${label}.`)), timeoutMs)), - ]); -} - -async function ensureDotnetBuild(): Promise { - if (existsSync(daemonDll) && !daemonBuildInputsAreNewer(daemonDll)) - return; - - await runProcess("dotnet", ["build", resolve(repoRoot, "Aetheria.State.Daemon", "Aetheria.State.Daemon.csproj")], "Aetheria.State.Daemon.build"); -} - -function daemonBuildInputsAreNewer(outputPath: string): boolean { - const outputMtime = statSync(outputPath).mtimeMs; - const sourceRoots = [ - resolve(repoRoot, "Aetheria.State.Daemon"), - resolve(repoRoot, "Aetheria.State"), - resolve(repoRoot, "Packages", "org.gamecult.aetheria.state", "Runtime"), - ]; - - return sourceRoots.some(sourceRoot => newestSourceMtime(sourceRoot) > outputMtime); -} - -function newestSourceMtime(root: string): number { - if (!existsSync(root)) - return 0; - - const stat = statSync(root); - if (stat.isFile()) - return isBuildInput(root) ? stat.mtimeMs : 0; - - let newest = 0; - for (const entry of readdirSync(root, { withFileTypes: true })) { - const path = resolve(root, entry.name); - if (entry.isDirectory()) { - if (entry.name === "bin" || entry.name === "obj") - continue; - newest = Math.max(newest, newestSourceMtime(path)); - } else if (entry.isFile() && isBuildInput(path)) { - newest = Math.max(newest, statSync(path).mtimeMs); - } - } - return newest; -} - -function isBuildInput(path: string): boolean { - return path.endsWith(".cs") || path.endsWith(".csproj") || path.endsWith(".props") || path.endsWith(".targets"); -} - -function startDotnet(name: string, dllPath: string, args: string[]): ChildProcessWithoutNullStreams { - mkdirSync(logsRoot, { recursive: true }); - const child = spawn("dotnet", [dllPath, ...args], { - cwd: repoRoot, - windowsHide: true, - }); - pipeChildLogs(name, child); - child.once("exit", (code, signal) => { - if (!isQuitting) { - void showFailure(new Error(`${name} exited unexpectedly (${signal ?? code ?? "unknown"}). Check ${logsRoot}.`)); - } - }); - return child; -} - -function runProcess(command: string, args: string[], logName: string): Promise { - mkdirSync(logsRoot, { recursive: true }); - const child = spawn(command, args, { - cwd: repoRoot, - windowsHide: true, - }); - pipeChildLogs(logName, child); - return new Promise((resolvePromise, reject) => { - child.once("error", reject); - child.once("exit", code => { - if (code === 0) { - resolvePromise(); - return; - } - - reject(new Error(`${command} ${args.join(" ")} failed with exit code ${code}. Check ${logsRoot}.`)); - }); - }); -} - -function pipeChildLogs(name: string, child: ChildProcessWithoutNullStreams): void { - const stdout = createWriteStream(resolve(logsRoot, `${name}.log`), { flags: "a" }); - const stderr = createWriteStream(resolve(logsRoot, `${name}.err.log`), { flags: "a" }); - child.stdout.pipe(stdout); - child.stderr.pipe(stderr); -} - -function stopChild(child: ChildProcessWithoutNullStreams | null): void { - if (!child || child.killed) - return; - - child.kill(); -} - -function delay(milliseconds: number): Promise { - return new Promise(resolve => setTimeout(resolve, milliseconds)); -} - -async function showFailure(error: unknown): Promise { - const message = error instanceof Error ? error.message : String(error); - await mainWindow?.loadURL(`data:text/html;charset=utf-8,${encodeURIComponent(startupHtml("Aetheria Starbridge failed to start", message))}`); -} - -function startupHtml(title: string, detail: string): string { - return ` - - - - ${escapeHtml(title)} - - - -

-

${escapeHtml(title)}

-

${escapeHtml(detail)}

-
- -`; -} - -function escapeHtml(value: string): string { - return value - .replaceAll("&", "&") - .replaceAll("<", "<") - .replaceAll(">", ">") - .replaceAll('"', """); -} diff --git a/Aetheria.Rts.Web/package-lock.json b/Aetheria.Rts.Web/package-lock.json deleted file mode 100644 index b5e0a7e9c..000000000 --- a/Aetheria.Rts.Web/package-lock.json +++ /dev/null @@ -1,305 +0,0 @@ -{ - "name": "aetheria-rts-web", - "version": "0.1.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "aetheria-rts-web", - "version": "0.1.0", - "dependencies": { - "@gamecult/eve-browser-lowering": "file:../../Eve/packages/eve-browser-lowering", - "@gamecult/eve-electron": "file:../../EveElectron", - "@msgpack/msgpack": "^3.1.2", - "cultcache-ts": "file:../../CultLib/packages/cultcache-ts", - "cultmesh-ts": "file:../../CultLib/packages/cultmesh-ts", - "cultnet-ts": "file:../../CultLib/packages/cultnet-ts" - }, - "devDependencies": { - "@types/node": "^26.0.0", - "electron": "^42.4.1", - "typescript": "^5.6.3" - } - }, - "../../CultLib/packages/cultcache-ts": { - "version": "0.13.5", - "license": "MIT", - "dependencies": { - "@msgpack/msgpack": "^3.1.3", - "zod": "^3.25.76" - }, - "devDependencies": { - "@types/node": "^24.0.0", - "typescript": "^5.9.0" - } - }, - "../../CultLib/packages/cultmesh-ts": { - "version": "0.1.0", - "license": "MIT", - "dependencies": { - "@msgpack/msgpack": "^3.1.2", - "cultcache-ts": "file:../cultcache-ts", - "cultnet-ts": "file:../cultnet-ts" - }, - "devDependencies": { - "@types/node": "^24.0.0", - "typescript": "^5.9.0", - "zod": "^3.25.76" - } - }, - "../../CultLib/packages/cultnet-ts": { - "version": "0.1.0", - "license": "MIT", - "dependencies": { - "@msgpack/msgpack": "^3.1.3", - "ajv": "^8.17.1", - "cultcache-ts": "file:../cultcache-ts" - }, - "devDependencies": { - "@types/node": "^24.0.0", - "json-schema-to-typescript": "^15.0.4", - "typescript": "^5.9.0", - "zod": "^3.25.76" - } - }, - "../../Eve/packages/eve-browser-lowering": { - "name": "@gamecult/eve-browser-lowering", - "version": "0.1.0", - "devDependencies": { - "typescript": "^5.6.3" - } - }, - "../../EveElectron": { - "name": "@gamecult/eve-electron", - "version": "0.1.0", - "license": "MPL-2.0", - "peerDependencies": { - "electron": ">=30" - }, - "peerDependenciesMeta": { - "electron": { - "optional": true - } - } - }, - "node_modules/@electron-internal/extract-zip": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@electron-internal/extract-zip/-/extract-zip-1.0.3.tgz", - "integrity": "sha512-OjKpjB7gohtEjZiq6nDx1egqjZJhGPN1iFOIED+NFhB/MMkXw/XRcHjh1DGXKT5z2W9eW7Jy2UKU3gpjvusFTQ==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=22.12.0" - } - }, - "node_modules/@electron/get": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@electron/get/-/get-5.0.0.tgz", - "integrity": "sha512-pjoBpru1KdEtcExBnuHAP1cAc/5faoedw0hzJkL3o4/IJp7HNF1+fbrdxT3gMYRX2oJfvnA/WXeCTVQpYYxyJA==", - "dev": true, - "license": "MIT", - "dependencies": { - "debug": "^4.1.1", - "env-paths": "^3.0.0", - "graceful-fs": "^4.2.11", - "progress": "^2.0.3", - "semver": "^7.6.3", - "sumchecker": "^3.0.1" - }, - "engines": { - "node": ">=22.12.0" - }, - "optionalDependencies": { - "undici": "^7.24.4" - } - }, - "node_modules/@gamecult/eve-browser-lowering": { - "resolved": "../../Eve/packages/eve-browser-lowering", - "link": true - }, - "node_modules/@gamecult/eve-electron": { - "resolved": "../../EveElectron", - "link": true - }, - "node_modules/@msgpack/msgpack": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/@msgpack/msgpack/-/msgpack-3.1.3.tgz", - "integrity": "sha512-47XIizs9XZXvuJgoaJUIE2lFoID8ugvc0jzSHP+Ptfk8nTbnR8g788wv48N03Kx0UkAv559HWRQ3yzOgzlRNUA==", - "license": "ISC", - "engines": { - "node": ">= 18" - } - }, - "node_modules/@types/node": { - "version": "26.0.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-26.0.0.tgz", - "integrity": "sha512-vf2YFi1iY9lHGwNJMs01biZFbKJkrZR1T6/MlzjhJLPdntOHLhTrDSnSVcdtvjihi4VQNlrFRIxLsDBlQpAipA==", - "dev": true, - "license": "MIT", - "dependencies": { - "undici-types": "~8.3.0" - } - }, - "node_modules/cultcache-ts": { - "resolved": "../../CultLib/packages/cultcache-ts", - "link": true - }, - "node_modules/cultmesh-ts": { - "resolved": "../../CultLib/packages/cultmesh-ts", - "link": true - }, - "node_modules/cultnet-ts": { - "resolved": "../../CultLib/packages/cultnet-ts", - "link": true - }, - "node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/electron": { - "version": "42.4.1", - "resolved": "https://registry.npmjs.org/electron/-/electron-42.4.1.tgz", - "integrity": "sha512-8CYHJP5O4wFO+ycoJR98yy907MmPeo+vWXrzjxmGGgRNKqv8pOjjm+wphO0CCgQJnBU7+QUPSJS4QXhbKrO50w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@electron-internal/extract-zip": "^1.0.1", - "@electron/get": "^5.0.0", - "@types/node": "^24.9.0" - }, - "bin": { - "electron": "cli.js", - "install-electron": "install.js" - }, - "engines": { - "node": ">= 22.12.0" - } - }, - "node_modules/electron/node_modules/@types/node": { - "version": "24.13.2", - "resolved": "https://registry.npmjs.org/@types/node/-/node-24.13.2.tgz", - "integrity": "sha512-fRa09kZTgu8o71KFcDjUFuc7F+dEbZYZmkI0mg5YBTRs0yMKjYHsq/c0urDKeDb+D5qVgXOdFcuu+DZPKOITwA==", - "dev": true, - "license": "MIT", - "dependencies": { - "undici-types": "~7.18.0" - } - }, - "node_modules/electron/node_modules/undici-types": { - "version": "7.18.2", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", - "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", - "dev": true, - "license": "MIT" - }, - "node_modules/env-paths": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-3.0.0.tgz", - "integrity": "sha512-dtJUTepzMW3Lm/NPxRf3wP4642UWhjL2sQxc+ym2YMj1m/H2zDNQOlezafzkHwn6sMstjHTwG6iQQsctDW/b1A==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, - "license": "MIT" - }, - "node_modules/progress": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", - "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/semver": { - "version": "7.8.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", - "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/sumchecker": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/sumchecker/-/sumchecker-3.0.1.tgz", - "integrity": "sha512-MvjXzkz/BOfyVDkG0oFOtBxHX2u3gKbMHIF/dXblZsgD3BWOFLmHovIpZY7BykJdAjcqRCBi1WYBNdEC9yI7vg==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "debug": "^4.1.0" - }, - "engines": { - "node": ">= 8.0" - } - }, - "node_modules/typescript": { - "version": "5.9.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", - "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/undici": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/undici/-/undici-7.28.0.tgz", - "integrity": "sha512-cRZYrTDwWznlnRiPjggAGxZXanty6M8RV1ff8Wm4LWXBp7/IG8v5DnOm74DtUBp9OONpK75YlPnIjQqX0dBDtA==", - "dev": true, - "license": "MIT", - "optional": true, - "engines": { - "node": ">=20.18.1" - } - }, - "node_modules/undici-types": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-8.3.0.tgz", - "integrity": "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ==", - "dev": true, - "license": "MIT" - } - } -} diff --git a/Aetheria.Rts.Web/package.json b/Aetheria.Rts.Web/package.json deleted file mode 100644 index 23de3d9e3..000000000 --- a/Aetheria.Rts.Web/package.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "name": "aetheria-rts-web", - "private": true, - "version": "0.1.0", - "type": "module", - "main": "electron-dist/main.js", - "scripts": { - "build": "C:\\Progra~1\\nodejs\\node.exe ./node_modules/typescript/bin/tsc -p tsconfig.json && C:\\Progra~1\\nodejs\\node.exe ./node_modules/typescript/bin/tsc -p tsconfig.electron.json", - "build:renderer": "C:\\Progra~1\\nodejs\\node.exe ./node_modules/typescript/bin/tsc -p tsconfig.json", - "build:electron": "C:\\Progra~1\\nodejs\\node.exe ./node_modules/typescript/bin/tsc -p tsconfig.electron.json", - "generate:rts-bindings": "C:\\Progra~1\\nodejs\\node.exe scripts/generate-rts-bindings.mjs", - "check:rts-bindings": "C:\\Progra~1\\nodejs\\node.exe scripts/generate-rts-bindings.mjs --check", - "verify:stage7b": "C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe -NoProfile -ExecutionPolicy Bypass -File scripts\\verify-stage7b-rts-client.ps1", - "verify:stage7c": "C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe -NoProfile -ExecutionPolicy Bypass -File scripts\\verify-stage7c-local-runtime.ps1", - "verify:stage7c:electron": "C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe -NoProfile -ExecutionPolicy Bypass -File scripts\\verify-stage7c-electron-rts.ps1", - "verify:stage7d:remote": "C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe -NoProfile -ExecutionPolicy Bypass -File scripts\\verify-stage7d-remote-cultmesh.ps1", - "electron": "npm run build && .\\node_modules\\electron\\dist\\electron.exe .", - "watch": "C:\\Progra~1\\nodejs\\node.exe ./node_modules/typescript/bin/tsc -p tsconfig.json --watch" - }, - "devDependencies": { - "@types/node": "^26.0.0", - "electron": "^42.4.1", - "typescript": "^5.6.3" - }, - "dependencies": { - "@msgpack/msgpack": "^3.1.2", - "@gamecult/eve-browser-lowering": "file:../../Eve/packages/eve-browser-lowering", - "@gamecult/eve-electron": "file:../../EveElectron", - "cultcache-ts": "file:../../CultLib/packages/cultcache-ts", - "cultmesh-ts": "file:../../CultLib/packages/cultmesh-ts", - "cultnet-ts": "file:../../CultLib/packages/cultnet-ts" - } -} diff --git a/Aetheria.Rts.Web/scripts/generate-rts-bindings.mjs b/Aetheria.Rts.Web/scripts/generate-rts-bindings.mjs deleted file mode 100644 index 9948776e4..000000000 --- a/Aetheria.Rts.Web/scripts/generate-rts-bindings.mjs +++ /dev/null @@ -1,1641 +0,0 @@ -import { readFileSync, writeFileSync } from "node:fs"; -import { resolve } from "node:path"; -import { fileURLToPath } from "node:url"; - -const scriptPath = fileURLToPath(import.meta.url); -const root = resolve(scriptPath, "..", ".."); -const repoRoot = resolve(root, ".."); - -const sources = [ - resolve(repoRoot, "Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeDaemonDocuments.cs"), - resolve(repoRoot, "Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeEveCommandDocument.cs"), - resolve(repoRoot, "Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeAssetDocuments.cs"), - resolve(repoRoot, "Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeDaemonRenderQueries.cs"), - resolve(repoRoot, "Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeDaemonTradeItemQueries.cs"), - resolve(repoRoot, "Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeSnapshotDocuments.cs"), - resolve(repoRoot, "Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeGameViewportDocuments.cs"), - resolve(repoRoot, "Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeRenderSplatDocuments.cs"), - resolve(repoRoot, "Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeStarbridgeDocuments.cs"), - resolve(repoRoot, "Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeVerseAuthorityPolicy.cs"), -]; -const outputPath = resolve(root, "Electron/aetheria-rts-generated-bindings.ts"); -const check = process.argv.includes("--check"); - -const ipcChannels = [ - ["mapViewport", "aetheria-rts:map-viewport"], - ["objectsViewport", "aetheria-rts:objects-viewport"], - ["gravityViewport", "aetheria-rts:gravity-viewport"], - ["renderSplatsViewport", "aetheria-rts:render-splats-viewport"], - ["viewportFeed", "aetheria-rts:viewport-feed"], - ["viewportFeedStop", "aetheria-rts:viewport-feed-stop"], - ["viewportFeedUpdate", "aetheria-rts:viewport-feed-update"], - ["selectedObject", "aetheria-rts:selected-object"], - ["inventory", "aetheria-rts:inventory"], - ["daemonHealth", "aetheria-rts:daemon-health"], - ["authorityStatus", "aetheria-rts:authority-status"], - ["starbridgeSession", "aetheria-rts:starbridge-session"], - ["assetManifest", "aetheria-rts:asset-manifest"], - ["setMoveVector", "aetheria-rts:set-move-vector"], - ["setTarget", "aetheria-rts:set-target"], - ["surfaceCatalog", "aetheria-rts:surface-catalog"], - ["surfaceCatalogIndex", "aetheria-rts:surface-catalog-index"], - ["eveSurface", "eve:surface"], - ["eveProviderAdvertisement", "eve:provider-advertisement"], - ["eveDocument", "eve:embedded-document"], - ["submitEveCommand", "eve:submit-command"], - ["windowControl", "eve-electron:window-control"], - ["health", "aetheria-rts:health"], -]; - -const sourceText = sources.map(path => readFileSync(path, "utf8")).join("\n"); - -const enums = [ - { - exportName: "AetheriaRuntimeDaemonCommandKinds", - entries: parseEnum(sourceText, "AetheriaRuntimeDaemonCommandKinds"), - }, - { - exportName: "AetheriaRuntimeEveCommandKinds", - entries: parseEnum(sourceText, "AetheriaRuntimeEveCommandKind"), - }, -]; -const documents = [ - { - exportName: "aetheriaRuntimeDaemonCommandDocument", - className: "AetheriaRuntimeDaemonCommandDocument", - schemaName: "daemonCommand", - }, - { - exportName: "aetheriaRuntimeEveCommandDocument", - className: "AetheriaRuntimeEveCommandDocument", - schemaName: "eveCommand", - }, - { - exportName: "aetheriaRuntimeDaemonFrameDocument", - className: "AetheriaRuntimeDaemonFrameDocument", - schemaName: "daemonFrame", - }, - { - exportName: "aetheriaRuntimeDaemonHealthDocument", - className: "AetheriaRuntimeDaemonHealthDocument", - schemaName: "daemonHealth", - }, - { - exportName: "aetheriaRuntimeAssetRef", - className: "AetheriaRuntimeAssetRef", - }, - { - exportName: "aetheriaRuntimeAssetManifestDocument", - className: "AetheriaRuntimeAssetManifestDocument", - schemaName: "assetManifest", - }, - { - exportName: "aetheriaRuntimeAssetManifestEntry", - className: "AetheriaRuntimeAssetManifestEntry", - }, - { - exportName: "aetheriaRuntimeGameViewportDocument", - className: "AetheriaRuntimeGameViewportDocument", - schemaName: "gameViewport", - }, - { - exportName: "aetheriaRuntimeObjectsViewportDocument", - className: "AetheriaRuntimeObjectsViewportDocument", - schemaName: "objectsViewport", - }, - { - exportName: "aetheriaRuntimeGravityViewportDocument", - className: "AetheriaRuntimeGravityViewportDocument", - schemaName: "gravityViewport", - }, - { - exportName: "aetheriaRuntimeRenderSplatsViewportDocument", - className: "AetheriaRuntimeRenderSplatsViewportDocument", - schemaName: "renderSplatsViewport", - }, - { - exportName: "aetheriaRuntimeRenderSplatLayerDefinition", - className: "AetheriaRuntimeRenderSplatLayerDefinition", - }, - { - exportName: "aetheriaRuntimeRenderSplatSoa", - className: "AetheriaRuntimeRenderSplatSoa", - }, - { - exportName: "aetheriaRuntimeCurrentZoneDocument", - className: "AetheriaRuntimeCurrentZoneDocument", - schemaName: "currentZone", - }, - { - exportName: "aetheriaRuntimeCurrentEntityDocument", - className: "AetheriaRuntimeCurrentEntityDocument", - schemaName: "currentEntity", - }, - { - exportName: "aetheriaRuntimeCurrentDockingDocument", - className: "AetheriaRuntimeCurrentDockingDocument", - schemaName: "currentDocking", - }, - { - exportName: "aetheriaRuntimeStationRefitDocument", - className: "AetheriaRuntimeStationRefitDocument", - schemaName: "stationRefit", - }, - { - exportName: "aetheriaRuntimeStationRefitEntityOption", - className: "AetheriaRuntimeStationRefitEntityOption", - }, - { - exportName: "aetheriaRuntimeStationStockItem", - className: "AetheriaRuntimeStationStockItem", - }, - { - exportName: "aetheriaRuntimeSectorMapDocument", - className: "AetheriaRuntimeSectorMapDocument", - schemaName: "sectorMap", - }, - { - exportName: "aetheriaRuntimeSectorMapZone", - className: "AetheriaRuntimeSectorMapZone", - }, - { - exportName: "aetheriaRuntimeSectorMapLink", - className: "AetheriaRuntimeSectorMapLink", - }, - { - exportName: "aetheriaRuntimeZoneRenderDocument", - className: "AetheriaRuntimeZoneRenderDocument", - schemaName: "zoneRender", - }, - { - exportName: "aetheriaRuntimeZoneRenderBodyPose", - className: "AetheriaRuntimeZoneRenderBodyPose", - }, - { - exportName: "aetheriaRuntimeZoneRenderAsteroidBeltPose", - className: "AetheriaRuntimeZoneRenderAsteroidBeltPose", - }, - { - exportName: "aetheriaRuntimeSelectedObjectDocument", - className: "AetheriaRuntimeSelectedObjectDocument", - schemaName: "selectedObject", - }, - { - exportName: "aetheriaRuntimeInventoryDocument", - className: "AetheriaRuntimeInventoryDocument", - schemaName: "inventory", - }, - { - exportName: "aetheriaRuntimeRunCheckpointCommit", - className: "AetheriaRuntimeRunCheckpointCommit", - }, - { - exportName: "aetheriaRuntimeZoneSnapshotCommit", - className: "AetheriaRuntimeZoneSnapshotCommit", - }, - { - exportName: "aetheriaRuntimeEntitySnapshotCommit", - className: "AetheriaRuntimeEntitySnapshotCommit", - }, - { - exportName: "aetheriaRuntimeProjectileCommit", - className: "AetheriaRuntimeProjectileCommit", - }, - { - exportName: "aetheriaRuntimeBodySnapshotCommit", - className: "AetheriaRuntimeBodySnapshotCommit", - }, - { - exportName: "aetheriaRuntimeSunVisualCommit", - className: "AetheriaRuntimeSunVisualCommit", - }, - { - exportName: "aetheriaRuntimeEntityStatGridCommit", - className: "AetheriaRuntimeEntityStatGridCommit", - }, - { - exportName: "aetheriaRuntimeCargoBayLoadoutCommit", - className: "AetheriaRuntimeCargoBayLoadoutCommit", - }, - { - exportName: "aetheriaRuntimeLoadoutItemSlotCommit", - className: "AetheriaRuntimeLoadoutItemSlotCommit", - }, - { - exportName: "aetheriaRuntimeLoadoutItemCommit", - className: "AetheriaRuntimeLoadoutItemCommit", - }, - { - exportName: "aetheriaRuntimeGameViewportBounds", - className: "AetheriaRuntimeViewportBounds", - }, - { - exportName: "aetheriaRuntimeGameViewportObject", - className: "AetheriaRuntimeViewportObject", - }, - { - exportName: "aetheriaRuntimeRtsEntityStatus", - className: "AetheriaRuntimeEntityStatus", - }, - { - exportName: "aetheriaRuntimeRtsInventoryItem", - className: "AetheriaRuntimeInventoryItem", - }, - { - exportName: "aetheriaRuntimeRtsGravityInfluence", - className: "AetheriaRuntimeGravityInfluence", - }, - { - exportName: "aetheriaRuntimeRtsBodyView", - className: "AetheriaRuntimeBodyView", - }, - { - exportName: "aetheriaRuntimeStarbridgeScenarioDocument", - className: "AetheriaRuntimeStarbridgeScenarioDocument", - schemaName: "starbridgeScenario", - }, - { - exportName: "aetheriaRuntimeStarbridgeSessionDocument", - className: "AetheriaRuntimeStarbridgeSessionDocument", - schemaName: "starbridgeSession", - }, - { - exportName: "aetheriaRuntimeStarbridgeSessionSummaryDocument", - className: "AetheriaRuntimeStarbridgeSessionSummaryDocument", - schemaName: "starbridgeSessionSummary", - }, - { - exportName: "aetheriaRuntimeStarbridgeStationStockItem", - className: "AetheriaRuntimeStarbridgeStationStockItem", - }, - { - exportName: "aetheriaRuntimeStarbridgeWaveDefinition", - className: "AetheriaRuntimeStarbridgeWaveDefinition", - }, - { - exportName: "aetheriaRuntimeStarbridgeWaveForecast", - className: "AetheriaRuntimeStarbridgeWaveForecast", - }, - { - exportName: "aetheriaRuntimeStarbridgeRuntimeRole", - className: "AetheriaRuntimeStarbridgeRuntimeRole", - }, - { - exportName: "aetheriaRuntimeStarbridgeBaseStatus", - className: "AetheriaRuntimeStarbridgeBaseStatus", - }, - { - exportName: "aetheriaRuntimeVerseAuthorityPolicyDocument", - className: "AetheriaRuntimeVerseAuthorityPolicyDocument", - schemaName: "verseAuthorityPolicy", - }, - { - exportName: "aetheriaRuntimeAuthorityRule", - className: "AetheriaRuntimeAuthorityRule", - }, - { - exportName: "aetheriaRuntimeAuthorityRuntimeRole", - className: "AetheriaRuntimeAuthorityRuntimeRole", - }, - { - exportName: "aetheriaRuntimeAuthorityLeaseDocument", - className: "AetheriaRuntimeAuthorityLeaseDocument", - schemaName: "authorityLease", - }, -].map(document => ({ - ...document, - slots: parseMessagePackSlots(sourceText, document.className), - schema: parseCultDocumentSchema(sourceText, document.className), -})); - -const generated = render(enums, documents); -const existing = readExisting(outputPath); - -if (check) { - if (existing !== generated) { - console.error("RTS generated bindings are stale. Run `npm run generate:rts-bindings`."); - process.exit(1); - } -} else { - writeFileSync(outputPath, generated, "utf8"); -} - -function readExisting(path) { - try { - return readFileSync(path, "utf8"); - } catch { - return ""; - } -} - -function parseEnum(text, enumName) { - const body = readTypeBody(text, `enum ${enumName}`); - const entries = []; - let nextValue = 0; - for (const rawLine of body.split(/\r?\n/u)) { - const line = rawLine.replace(/\/\/.*$/u, "").trim().replace(/,$/u, ""); - if (!line) - continue; - - const match = /^([A-Za-z_][A-Za-z0-9_]*)(?:\s*=\s*(-?\d+))?$/u.exec(line); - if (!match) - continue; - - const value = match[2] === undefined ? nextValue : Number.parseInt(match[2], 10); - entries.push([toCamelCase(match[1]), value]); - nextValue = value + 1; - } - - return entries; -} - -function parseMessagePackSlots(text, className) { - const body = readTypeBody(text, `class ${className}`); - const slots = []; - const propertyPattern = /\[Key\((\d+)\)\][\s\S]*?public\s+[A-Za-z0-9_<>,.?[\]\s]+\s+([A-Za-z_][A-Za-z0-9_]*)\s*\{\s*get;\s*set;\s*\}/gu; - for (const match of body.matchAll(propertyPattern)) { - slots.push([toCamelCase(match[2]), Number.parseInt(match[1], 10)]); - } - - if (slots.length === 0) - throw new Error(`No [Key] properties found for ${className}.`); - - return slots.sort((left, right) => left[1] - right[1]); -} - -function parseCultDocumentSchema(text, className) { - const classIndex = text.indexOf(`class ${className}`); - if (classIndex < 0) - throw new Error(`Could not find ${className}.`); - - const prefix = text.slice(Math.max(0, classIndex - 500), classIndex); - const matches = [...prefix.matchAll(/\[CultDocument\("([^"]+)",\s*"([^"]+)"\)\]/gu)]; - return matches.length > 0 ? matches[matches.length - 1][2] : ""; -} - -function readTypeBody(text, declaration) { - const declarationIndex = text.indexOf(declaration); - if (declarationIndex < 0) - throw new Error(`Could not find ${declaration}.`); - - const openIndex = text.indexOf("{", declarationIndex); - if (openIndex < 0) - throw new Error(`Could not find body for ${declaration}.`); - - let depth = 0; - for (let index = openIndex; index < text.length; index += 1) { - const character = text[index]; - if (character === "{") - depth += 1; - if (character === "}") { - depth -= 1; - if (depth === 0) - return text.slice(openIndex + 1, index); - } - } - - throw new Error(`Could not read body for ${declaration}.`); -} - -function render(enums, documents) { - const schemas = documents - .filter(document => document.schema) - .map(document => ` ${document.schemaName}: "${document.schema}",`) - .join("\n"); - const enumBlocks = enums - .map(item => renderEnumBlock(item.exportName, item.entries)) - .join("\n\n"); - const slotBlocks = 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, 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) { - const lines = entries - .map(([name, value]) => ` ${name}: ${value},`) - .join("\n"); - return `export const ${exportName} = {\n${lines}\n} as const;`; -} - -function renderSlotBlock(exportName, slots) { - const slotLines = slots.map(([name, value]) => ` ${name}: ${value},`).join("\n"); - const slotNames = slots.map(([name]) => ` | "${name}"`).join("\n"); - const typeName = `${toPascalCase(exportName)}Slot`; - return `export type ${typeName} =\n${slotNames};\n\nexport const ${exportName}Slots: Record<${typeName}, number> = {\n${slotLines}\n};`; -} - -function toCamelCase(value) { - return value.slice(0, 1).toLowerCase() + value.slice(1); -} - -function toPascalCase(value) { - return value.slice(0, 1).toUpperCase() + value.slice(1); -} - -function renderRtsOperations() { - return `export type AetheriaRuntimeSetMoveVectorRequest = { - actorEntityKey: string; - directionX: number; - directionY: number; - scalar: number; - observedFrameId?: number; -}; - -export type AetheriaRuntimeSetTargetRequest = { - actorEntityKey: string; - targetEntityKey: string; - observedFrameId?: number; -}; - -export type AetheriaRuntimeDaemonCommandReceipt = CultMeshOperationReceipt & { - commandId: string; -}; - -export type AetheriaRuntimeSelectedObjectRequest = { - entityIndex: number; -}; - -export type AetheriaRuntimeViewportFeedRequest = { - viewport: CultMeshViewportRequest; - selectedEntityIndex?: number; -}; - -export type AetheriaRuntimeViewportFeedSnapshot = { - viewport: ViewportResponse; - selectedObject: SelectedObjectDocument | null; - inventory: InventoryDocument | null; - daemonHealth: DaemonHealthDocument; - authorityStatus: AuthorityStatusDocument; - starbridgeSession: StarbridgeSessionDocument; - assetManifest: AssetManifestDocument; - receivedAtUtc: string; - sampleMs: number; -}; - -export type AetheriaEveSurfaceRequest = { - surfaceId?: string; - recordKey?: string; -}; - -export type EveEmbeddedDocumentRequest = { - documentId: string; - schemaId?: string; - slotId?: string; - presentationKind?: string; - context?: Record; -}; - -export type EveResolvedDocument = { - documentId: string; - schemaId: string; - document?: unknown; - surface?: AetheriaMenuSurfaceTree; -}; - -export type EveProviderAdvertisement = { - providerId: string; - title: string; - kind: string; - surfaces: Array<{ - surfaceId: string; - key: string; - transport: string; - status: string; - }>; -}; - -export type AetheriaEveCommandRequest = { - providerId?: string; - surfaceId?: string; - command?: string; - clientId?: string; - issuedAtUtc?: string; - payload?: Record; -}; - -export type AetheriaMenuSurfaceDocument = { - providerId: string; - providerKind: string; - title: string; - version: number; - updatedAtUtc: string; - surface: AetheriaMenuSurfaceTree; - commands: AetheriaMenuSurfaceCommand[]; -}; - -export type AetheriaMenuSurfaceTree = { - id: string; - root: AetheriaMenuSurfaceComponent; - styles: AetheriaMenuStyleToken[]; -}; - -export type AetheriaMenuSurfaceComponent = { - id: string; - kind: string; - props: Record; - layout?: Record; - style?: Record; - embeddedDocuments?: AetheriaMenuEmbeddedDocumentSlot[]; - children: AetheriaMenuSurfaceComponent[]; -}; - -export type AetheriaMenuEmbeddedDocumentSlot = { - slotId: string; - documentId: string; - schemaId: string; - presentationKind: string; -}; - -export type AetheriaMenuStyleToken = { - name: string; - value: string; -}; - -export type AetheriaMenuSurfaceCommand = { - command: string; - label: string; - transport: string; -}; - -export type AetheriaRtsMainClient = { - mapViewport(request: CultMeshViewportRequest): Promise; - objectsViewport(request: CultMeshViewportRequest): Promise; - gravityViewport(request: CultMeshViewportRequest): Promise; - renderSplatsViewport(request: CultMeshViewportRequest): 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; - surfaceCatalogDiagnostics(): CultMeshSurfaceCatalogDiagnostic; - surfaceCatalogIndexDiagnostics(): CultMeshSurfaceCatalogIndexDiagnostic; -}; - -export type AetheriaRtsHealthProvider = () => unknown; - -export function registerAetheriaRtsIpcHandlers( - ipcMain: IpcMain, - getClient: () => AetheriaRtsMainClient, - getHealth: AetheriaRtsHealthProvider, -): void { - const viewportFeedSubscriptions = new Map(); - ipcMain.handle(AetheriaRtsIpcChannels.mapViewport, async (_event, request: CultMeshViewportRequest) => - getClient().mapViewport(request)); - ipcMain.handle(AetheriaRtsIpcChannels.objectsViewport, async (_event, request: CultMeshViewportRequest) => - getClient().objectsViewport(request)); - ipcMain.handle(AetheriaRtsIpcChannels.gravityViewport, async (_event, request: CultMeshViewportRequest) => - getClient().gravityViewport(request)); - ipcMain.handle(AetheriaRtsIpcChannels.renderSplatsViewport, async (_event, request: CultMeshViewportRequest) => - getClient().renderSplatsViewport(request)); - ipcMain.handle(AetheriaRtsIpcChannels.selectedObject, async (_event, request: AetheriaRuntimeSelectedObjectRequest) => - getClient().selectedObject(request)); - ipcMain.handle(AetheriaRtsIpcChannels.inventory, async (_event, request: AetheriaRuntimeSelectedObjectRequest) => - getClient().inventory(request)); - ipcMain.handle(AetheriaRtsIpcChannels.daemonHealth, async () => getClient().daemonHealth()); - ipcMain.handle(AetheriaRtsIpcChannels.authorityStatus, async () => getClient().authorityStatus()); - ipcMain.handle(AetheriaRtsIpcChannels.starbridgeSession, async () => getClient().starbridgeSession()); - ipcMain.handle(AetheriaRtsIpcChannels.assetManifest, async () => getClient().assetManifest()); - ipcMain.handle(AetheriaRtsIpcChannels.viewportFeed, async (event, request: { subscriptionId: string; feed: AetheriaRuntimeViewportFeedRequest }) => { - viewportFeedSubscriptions.get(request.subscriptionId)?.(); - const unsubscribe = getClient().watchViewportFeed(request.feed, snapshot => { - event.sender.send(AetheriaRtsIpcChannels.viewportFeedUpdate, { - subscriptionId: request.subscriptionId, - snapshot, - }); - }); - viewportFeedSubscriptions.set(request.subscriptionId, unsubscribe); - }); - ipcMain.handle(AetheriaRtsIpcChannels.viewportFeedStop, (_event, subscriptionId: string) => { - viewportFeedSubscriptions.get(subscriptionId)?.(); - viewportFeedSubscriptions.delete(subscriptionId); - }); - ipcMain.handle(AetheriaRtsIpcChannels.setMoveVector, async (_event, request: AetheriaRuntimeSetMoveVectorRequest) => - getClient().setMoveVector(request)); - ipcMain.handle(AetheriaRtsIpcChannels.setTarget, async (_event, request: AetheriaRuntimeSetTargetRequest) => - getClient().setTarget(request)); - ipcMain.handle(AetheriaRtsIpcChannels.surfaceCatalog, () => getClient().surfaceCatalogDiagnostics()); - ipcMain.handle(AetheriaRtsIpcChannels.surfaceCatalogIndex, () => getClient().surfaceCatalogIndexDiagnostics()); - ipcMain.handle(AetheriaRtsIpcChannels.health, () => getHealth()); -} - -export type AetheriaRuntimeDaemonCommandSender = ( - commandId: string, - issuedAtUtc: string, - command: unknown[], - context: CultMeshOperationContext, -) => Promise; - -export type AetheriaRuntimeViewportQueryExecutor = ( - request: CultMeshViewportRequest, - context: CultMeshQueryContext, -) => Promise; - -export type AetheriaRuntimeQueryExecutor = ( - request: TParameters, - context: CultMeshQueryContext, -) => Promise; - -export type AetheriaRuntimeGameQueryExecutors = { - mapViewport: AetheriaRuntimeViewportQueryExecutor; - objectsViewport: AetheriaRuntimeViewportQueryExecutor; - gravityViewport: AetheriaRuntimeViewportQueryExecutor; - renderSplatsViewport: AetheriaRuntimeViewportQueryExecutor; - selectedObject: AetheriaRuntimeQueryExecutor; - inventory: AetheriaRuntimeQueryExecutor; - daemonHealth: AetheriaRuntimeQueryExecutor; - authorityStatus: AetheriaRuntimeQueryExecutor; - starbridgeSession: AetheriaRuntimeQueryExecutor; - assetManifest: AetheriaRuntimeQueryExecutor; -}; - -export type AetheriaRuntimeGameQueryWatchers = Partial<{ - mapViewport: CultMeshQueryWatcher; - objectsViewport: CultMeshQueryWatcher; - gravityViewport: CultMeshQueryWatcher; - renderSplatsViewport: CultMeshQueryWatcher; - selectedObject: CultMeshQueryWatcher; - inventory: CultMeshQueryWatcher; - daemonHealth: CultMeshQueryWatcher; - authorityStatus: CultMeshQueryWatcher; - starbridgeSession: CultMeshQueryWatcher; - assetManifest: CultMeshQueryWatcher; -}>; - -export type AetheriaRuntimeGameQueryDiagnostic = CultMeshQuerySurfaceDiagnostic; - -export type AetheriaRuntimeGameLiveFeedDiagnostic = CultMeshLiveFeedDiagnostic; - -export type AetheriaRuntimeGameSurfaceCatalogDiagnostic = CultMeshSurfaceCatalogDiagnostic; - -export type AetheriaRuntimeGameOperationHandles = ReturnType; - -export type AetheriaRuntimeGameDocuments = ReturnType; - -export type AetheriaRuntimeGameStatePointers = AetheriaRuntimeGameDocuments; - -export type AetheriaRuntimeGameDocumentResolvers = Partial<{ - daemonFrame: (context: CultMeshQueryContext) => Promise; - daemonHealth: (context: CultMeshQueryContext) => Promise; - authorityPolicy: (context: CultMeshQueryContext) => Promise; - starbridgeSession: (context: CultMeshQueryContext) => Promise; - assetManifest: (context: CultMeshQueryContext) => Promise; -}>; - -export type AetheriaRuntimeGameStatePointerResolvers = AetheriaRuntimeGameDocumentResolvers; - -export const AetheriaRuntimeGameDocumentSources = { - daemonFrame: CultMesh.querySource("daemon:aetheria.frame.latest.v1", { - schemaId: AetheriaRtsSchemas.daemonFrame, - description: "latest daemon frame" - }), - daemonHealth: CultMesh.querySource("daemon:aetheria.health.latest.v1", { - schemaId: AetheriaRtsSchemas.daemonHealth, - description: "latest daemon health" - }), - authorityPolicy: CultMesh.querySource("daemon:aetheria.authority.policy.latest.v1", { - schemaId: AetheriaRtsSchemas.verseAuthorityPolicy, - description: "latest Verse authority policy" - }), - starbridgeSession: CultMesh.querySource("daemon:aetheria.starbridge.session.latest.v1", { - schemaId: AetheriaRtsSchemas.starbridgeSessionSummary, - description: "latest Starbridge session summary" - }), - assetManifest: CultMesh.querySource("daemon:aetheria.asset_manifest.latest.v1", { - schemaId: AetheriaRtsSchemas.assetManifest, - description: "latest daemon asset manifest" - }) -} as const; - -export function createAetheriaRuntimeGameDocuments( - routeHint: CultMeshRouteHint = CultMesh.routeHint(), - resolvers: AetheriaRuntimeGameDocumentResolvers = {}, -) { - return { - daemonFrame: CultMesh.document( - AetheriaRuntimeGameDocumentSources.daemonFrame.sourceId, - { schemaId: AetheriaRtsSchemas.daemonFrame }, - async (context) => resolvers.daemonFrame?.(context), - { - routeHint, - sources: [AetheriaRuntimeGameDocumentSources.daemonFrame], - }, - ), - daemonHealth: CultMesh.document( - AetheriaRuntimeGameDocumentSources.daemonHealth.sourceId, - { schemaId: AetheriaRtsSchemas.daemonHealth }, - async (context) => resolvers.daemonHealth?.(context), - { - routeHint, - sources: [AetheriaRuntimeGameDocumentSources.daemonHealth], - }, - ), - authorityPolicy: CultMesh.document( - AetheriaRuntimeGameDocumentSources.authorityPolicy.sourceId, - { schemaId: AetheriaRtsSchemas.verseAuthorityPolicy }, - async (context) => resolvers.authorityPolicy?.(context), - { - routeHint, - sources: [AetheriaRuntimeGameDocumentSources.authorityPolicy], - }, - ), - starbridgeSession: CultMesh.document( - AetheriaRuntimeGameDocumentSources.starbridgeSession.sourceId, - { schemaId: AetheriaRtsSchemas.starbridgeSessionSummary }, - async (context) => resolvers.starbridgeSession?.(context), - { - routeHint, - sources: [AetheriaRuntimeGameDocumentSources.starbridgeSession], - }, - ), - assetManifest: CultMesh.document( - AetheriaRuntimeGameDocumentSources.assetManifest.sourceId, - { schemaId: AetheriaRtsSchemas.assetManifest }, - async (context) => resolvers.assetManifest?.(context), - { - routeHint, - sources: [AetheriaRuntimeGameDocumentSources.assetManifest], - }, - ), - } as const; -} - -export const createAetheriaRuntimeGameStatePointers = createAetheriaRuntimeGameDocuments; - -export function createAetheriaRuntimeGameQueryHandles( - executors: AetheriaRuntimeGameQueryExecutors, - routeHint: CultMeshRouteHint = CultMesh.routeHint(), - watchers: AetheriaRuntimeGameQueryWatchers = {}, -) { - return { - mapViewport: CultMesh.query( - AetheriaRtsSchemas.gameViewport, - executors.mapViewport, - { routeHint, sources: [AetheriaRuntimeGameDocumentSources.daemonFrame], watchQuery: watchers.mapViewport }, - ), - objectsViewport: CultMesh.query( - AetheriaRtsSchemas.objectsViewport, - executors.objectsViewport, - { routeHint, sources: [AetheriaRuntimeGameDocumentSources.daemonFrame], watchQuery: watchers.objectsViewport }, - ), - gravityViewport: CultMesh.query( - AetheriaRtsSchemas.gravityViewport, - executors.gravityViewport, - { routeHint, sources: [AetheriaRuntimeGameDocumentSources.daemonFrame], watchQuery: watchers.gravityViewport }, - ), - renderSplatsViewport: CultMesh.query( - AetheriaRtsSchemas.renderSplatsViewport, - executors.renderSplatsViewport, - { routeHint, sources: [AetheriaRuntimeGameDocumentSources.daemonFrame], watchQuery: watchers.renderSplatsViewport }, - ), - selectedObject: CultMesh.query( - AetheriaRtsSchemas.selectedObject, - executors.selectedObject, - { routeHint, sources: [AetheriaRuntimeGameDocumentSources.daemonFrame], watchQuery: watchers.selectedObject }, - ), - inventory: CultMesh.query( - AetheriaRtsSchemas.inventory, - executors.inventory, - { routeHint, sources: [AetheriaRuntimeGameDocumentSources.daemonFrame], watchQuery: watchers.inventory }, - ), - daemonHealth: CultMesh.query( - AetheriaRtsSchemas.daemonHealth, - executors.daemonHealth, - { routeHint, sources: [AetheriaRuntimeGameDocumentSources.daemonHealth], watchQuery: watchers.daemonHealth }, - ), - authorityStatus: CultMesh.query( - AetheriaRtsSchemas.verseAuthorityPolicy, - executors.authorityStatus, - { routeHint, sources: [AetheriaRuntimeGameDocumentSources.authorityPolicy], watchQuery: watchers.authorityStatus }, - ), - starbridgeSession: CultMesh.query( - AetheriaRtsSchemas.starbridgeSessionSummary, - executors.starbridgeSession, - { routeHint, sources: [AetheriaRuntimeGameDocumentSources.starbridgeSession], watchQuery: watchers.starbridgeSession }, - ), - assetManifest: CultMesh.query( - AetheriaRtsSchemas.assetManifest, - executors.assetManifest, - { routeHint, sources: [AetheriaRuntimeGameDocumentSources.assetManifest], watchQuery: watchers.assetManifest }, - ), - } as const; -} - -export function describeAetheriaRuntimeGameQueryHandles( - handles: ReturnType, -) { - return { - mapViewport: describeAetheriaRuntimeGameQuerySurface(handles.mapViewport), - objectsViewport: describeAetheriaRuntimeGameQuerySurface(handles.objectsViewport), - gravityViewport: describeAetheriaRuntimeGameQuerySurface(handles.gravityViewport), - renderSplatsViewport: describeAetheriaRuntimeGameQuerySurface(handles.renderSplatsViewport), - selectedObject: describeAetheriaRuntimeGameQuerySurface(handles.selectedObject), - inventory: describeAetheriaRuntimeGameQuerySurface(handles.inventory), - daemonHealth: describeAetheriaRuntimeGameQuerySurface(handles.daemonHealth), - authorityStatus: describeAetheriaRuntimeGameQuerySurface(handles.authorityStatus), - starbridgeSession: describeAetheriaRuntimeGameQuerySurface(handles.starbridgeSession), - assetManifest: describeAetheriaRuntimeGameQuerySurface(handles.assetManifest), - } as const; -} - -export function describeAetheriaRuntimeGameSurfaceCatalog( - handles: ReturnType, - operations?: AetheriaRuntimeGameOperationHandles, - documents: AetheriaRuntimeGameDocuments = createAetheriaRuntimeGameDocuments(), -): AetheriaRuntimeGameSurfaceCatalogDiagnostic { - return CultMesh.describeSurfaceCatalog( - "gamecult.aetheria.surfaces.v1", - [ - CultMesh.describeSurface(documents.daemonFrame), - CultMesh.describeSurface(documents.daemonHealth), - CultMesh.describeSurface(documents.authorityPolicy), - CultMesh.describeSurface(documents.starbridgeSession), - CultMesh.describeSurface(documents.assetManifest), - CultMesh.describeSurface(handles.mapViewport), - CultMesh.describeSurface(handles.objectsViewport), - CultMesh.describeSurface(handles.gravityViewport), - CultMesh.describeSurface(handles.renderSplatsViewport), - CultMesh.describeSurface(handles.selectedObject), - CultMesh.describeSurface(handles.inventory), - CultMesh.describeSurface(handles.daemonHealth), - CultMesh.describeSurface(handles.authorityStatus), - CultMesh.describeSurface(handles.starbridgeSession), - CultMesh.describeSurface(handles.assetManifest), - ...(operations - ? [ - CultMesh.describeSurface(operations.setMoveVector), - CultMesh.describeSurface(operations.setTarget), - ] - : []), - ], - ); -} - -function describeAetheriaRuntimeGameQuerySurface(surface: { - queryId: string; - routeHint: CultMeshRouteHint; - sources: readonly CultMeshQuerySource[]; -}): AetheriaRuntimeGameQueryDiagnostic { - return CultMesh.describeQuerySurface(surface); -} - -export function describeAetheriaRuntimeGameLiveFeedSurface(surface: { - feedId: string; - routeHint: CultMeshRouteHint; - sources: readonly CultMeshQuerySource[]; -}): AetheriaRuntimeGameLiveFeedDiagnostic { - return CultMesh.describeLiveFeed(surface); -} - -export function createAetheriaRuntimeGameOperationHandles( - sendCommand: AetheriaRuntimeDaemonCommandSender, - createCommandId: () => string = createDefaultCommandId, -) { - return { - setMoveVector: CultMesh.operation( - "gamecult.aetheria.pilot.set_move_vector.v1", - async (request, context) => { - const commandId = context.idempotencyKey ?? createCommandId(); - const issuedAtUtc = new Date().toISOString(); - await sendCommand( - commandId, - issuedAtUtc, - encodeSetMoveVectorCommand(commandId, issuedAtUtc, context.runtimeId, request), - context, - ); - return { - commandId, - ...CultMesh.operationReceipt("gamecult.aetheria.pilot.set_move_vector.v1", true, { route: context.routeHint }), - }; - }, - ), - setTarget: CultMesh.operation( - "gamecult.aetheria.pilot.set_target.v1", - async (request, context) => { - const commandId = context.idempotencyKey ?? createCommandId(); - const issuedAtUtc = new Date().toISOString(); - await sendCommand( - commandId, - issuedAtUtc, - encodeSetTargetCommand(commandId, issuedAtUtc, context.runtimeId, request), - context, - ); - return { - commandId, - ...CultMesh.operationReceipt("gamecult.aetheria.pilot.set_target.v1", true, { route: context.routeHint }), - }; - }, - ), - } as const; -} - -export type AetheriaRuntimeGameVerseHandles = ReturnType; - -export function createAetheriaRuntimeGameVerseHandles( - queryVerse: CultMeshVerseContext, - commandVerse: CultMeshVerseContext, - queries: ReturnType, - operations: AetheriaRuntimeGameOperationHandles, - documents: AetheriaRuntimeGameDocuments = createAetheriaRuntimeGameDocuments(), -) { - const mapViewport = CultMesh.bindQuery(queryVerse, queries.mapViewport); - const objectsViewport = CultMesh.bindQuery(queryVerse, queries.objectsViewport); - const gravityViewport = CultMesh.bindQuery(queryVerse, queries.gravityViewport); - const renderSplatsViewport = CultMesh.bindQuery(queryVerse, queries.renderSplatsViewport); - const selectedObject = CultMesh.bindQuery(queryVerse, queries.selectedObject); - const inventory = CultMesh.bindQuery(queryVerse, queries.inventory); - const daemonHealth = CultMesh.bindQuery(queryVerse, queries.daemonHealth); - const authorityStatus = CultMesh.bindQuery(queryVerse, queries.authorityStatus); - const starbridgeSession = CultMesh.bindQuery(queryVerse, queries.starbridgeSession); - const assetManifest = CultMesh.bindQuery(queryVerse, queries.assetManifest); - const daemonFrameDocument = CultMesh.bindDocument(queryVerse, documents.daemonFrame); - const daemonHealthDocument = CultMesh.bindDocument(queryVerse, documents.daemonHealth); - const authorityPolicyDocument = CultMesh.bindDocument(queryVerse, documents.authorityPolicy); - const starbridgeSessionDocument = CultMesh.bindDocument(queryVerse, documents.starbridgeSession); - const assetManifestDocument = CultMesh.bindDocument(queryVerse, documents.assetManifest); - const setMoveVector = CultMesh.bindOperation(commandVerse, operations.setMoveVector); - const setTarget = CultMesh.bindOperation(commandVerse, operations.setTarget); - - return { - zone: (_zoneId = "current") => ({ - viewport: { - within: (request: CultMeshViewportRequest) => - mapViewport.execute(request), - watch: ( - request: CultMeshViewportRequest, - callback: (value: ViewportResponse) => void, - ) => mapViewport.watch(request, callback), - }, - objects: { - visibleWithin: (request: CultMeshViewportRequest) => - objectsViewport.execute(request), - watchVisibleWithin: ( - request: CultMeshViewportRequest, - callback: (value: ObjectsViewportResponse) => void, - ) => objectsViewport.watch(request, callback), - }, - gravity: { - within: (request: CultMeshViewportRequest) => - gravityViewport.execute(request), - watchWithin: ( - request: CultMeshViewportRequest, - callback: (value: GravityViewportResponse) => void, - ) => gravityViewport.watch(request, callback), - }, - renderSplats: { - within: (request: CultMeshViewportRequest) => - renderSplatsViewport.execute(request), - watchWithin: ( - request: CultMeshViewportRequest, - callback: (value: RenderSplatsViewportResponse) => void, - ) => renderSplatsViewport.watch(request, callback), - }, - }), - entity: (entityKey: string) => ({ - pilot: { - move: ( - direction: { readonly x: number; readonly y: number }, - options: { scalar?: number; observedFrameId?: number; commandId?: string } = {}, - ) => setMoveVector.invoke({ - actorEntityKey: entityKey, - directionX: direction.x, - directionY: direction.y, - scalar: options.scalar ?? 1, - observedFrameId: options.observedFrameId, - }, { idempotencyKey: options.commandId }), - target: ( - targetEntityKey: string, - options: { observedFrameId?: number; commandId?: string } = {}, - ) => setTarget.invoke({ - actorEntityKey: entityKey, - targetEntityKey, - observedFrameId: options.observedFrameId, - }, { idempotencyKey: options.commandId }), - }, - }), - selectedObject: (entityIndex: number) => - selectedObject.execute({ entityIndex }), - inventory: (entityIndex: number) => - inventory.execute({ entityIndex }), - daemon: { - state: { - frame: () => daemonFrameDocument.latest(), - health: () => daemonHealthDocument.latest(), - authorityPolicy: () => authorityPolicyDocument.latest(), - starbridgeSession: () => starbridgeSessionDocument.latest(), - assetManifest: () => assetManifestDocument.latest(), - }, - health: () => daemonHealth.execute(undefined), - authorityStatus: () => authorityStatus.execute(undefined), - starbridgeSession: () => starbridgeSession.execute(undefined), - assetManifest: () => assetManifest.execute(undefined), - }, - } as const; -} - -export function encodeSetMoveVectorCommand( - commandId: string, - issuedAtUtc: string, - runtimeId: string, - request: AetheriaRuntimeSetMoveVectorRequest, -): unknown[] { - const command = createBaseCommand(commandId, issuedAtUtc, runtimeId, request.actorEntityKey, request.observedFrameId); - command[aetheriaRuntimeDaemonCommandDocumentSlots.kind] = AetheriaRuntimeDaemonCommandKinds.setMoveVector; - command[aetheriaRuntimeDaemonCommandDocumentSlots.directionX] = request.directionX; - command[aetheriaRuntimeDaemonCommandDocumentSlots.directionY] = request.directionY; - command[aetheriaRuntimeDaemonCommandDocumentSlots.scalarValue] = Math.min(1, Math.max(0, request.scalar)); - return command; -} - -export function encodeSetTargetCommand( - commandId: string, - issuedAtUtc: string, - runtimeId: string, - request: AetheriaRuntimeSetTargetRequest, -): unknown[] { - const command = createBaseCommand(commandId, issuedAtUtc, runtimeId, request.actorEntityKey, request.observedFrameId); - command[aetheriaRuntimeDaemonCommandDocumentSlots.kind] = AetheriaRuntimeDaemonCommandKinds.setTarget; - command[aetheriaRuntimeDaemonCommandDocumentSlots.targetEntityKey] = request.targetEntityKey; - return command; -} - -function createBaseCommand( - commandId: string, - issuedAtUtc: string, - runtimeId: string, - actorEntityKey: string, - observedFrameId?: number, -): unknown[] { - const command = new Array(Object.keys(aetheriaRuntimeDaemonCommandDocumentSlots).length).fill(null); - command[aetheriaRuntimeDaemonCommandDocumentSlots.schema] = AetheriaRtsSchemas.daemonCommand; - command[aetheriaRuntimeDaemonCommandDocumentSlots.commandId] = commandId; - command[aetheriaRuntimeDaemonCommandDocumentSlots.clientId] = runtimeId; - command[aetheriaRuntimeDaemonCommandDocumentSlots.issuedAtUtc] = issuedAtUtc; - command[aetheriaRuntimeDaemonCommandDocumentSlots.sessionId] = "local"; - command[aetheriaRuntimeDaemonCommandDocumentSlots.observedFrameId] = observedFrameId ?? -1; - command[aetheriaRuntimeDaemonCommandDocumentSlots.kind] = 0; - command[aetheriaRuntimeDaemonCommandDocumentSlots.actorEntityKey] = actorEntityKey; - command[aetheriaRuntimeDaemonCommandDocumentSlots.targetEntityKey] = ""; - command[aetheriaRuntimeDaemonCommandDocumentSlots.targetZoneIndex] = -1; - command[aetheriaRuntimeDaemonCommandDocumentSlots.equipmentIndex] = -1; - command[aetheriaRuntimeDaemonCommandDocumentSlots.behaviorIndex] = -1; - command[aetheriaRuntimeDaemonCommandDocumentSlots.weaponGroup] = -1; - command[aetheriaRuntimeDaemonCommandDocumentSlots.positionX] = 0; - command[aetheriaRuntimeDaemonCommandDocumentSlots.positionY] = 0; - command[aetheriaRuntimeDaemonCommandDocumentSlots.positionZ] = 0; - command[aetheriaRuntimeDaemonCommandDocumentSlots.directionX] = 0; - command[aetheriaRuntimeDaemonCommandDocumentSlots.directionY] = 0; - command[aetheriaRuntimeDaemonCommandDocumentSlots.scalarValue] = 0; - command[aetheriaRuntimeDaemonCommandDocumentSlots.textValue] = ""; - command[aetheriaRuntimeDaemonCommandDocumentSlots.authorRuntimeId] = ""; - command[aetheriaRuntimeDaemonCommandDocumentSlots.subjectKey] = ""; - command[aetheriaRuntimeDaemonCommandDocumentSlots.claimKind] = ""; - return command; -} - -function createDefaultCommandId(): string { - const cryptoApi = globalThis.crypto as { randomUUID?: () => string } | undefined; - return cryptoApi?.randomUUID?.() ?? \`command-\${Date.now().toString(36)}-\${Math.random().toString(36).slice(2)}\`; -}`; -} - -function renderRendererContract() { - return `// -// Generated by scripts/generate-rts-bindings.mjs from the RTS CultMesh surface contract. - -import type { - CultMeshOperationReceipt, - CultMeshSurfaceCatalogDiagnostic, - CultMeshSurfaceCatalogIndexDiagnostic, - CultMeshViewportRequest, -} from "cultmesh-ts"; - -export type Viewport = CultMeshViewportRequest; - -export type AetheriaRuntimeSetMoveVectorRequest = { - actorEntityKey: string; - directionX: number; - directionY: number; - scalar: number; - observedFrameId?: number; -}; - -export type AetheriaRuntimeSetTargetRequest = { - actorEntityKey: string; - targetEntityKey: string; - observedFrameId?: number; -}; - -export type AetheriaRuntimeDaemonCommandReceipt = CultMeshOperationReceipt & { - commandId: string; -}; - -export type AetheriaRuntimeSelectedObjectRequest = { - entityIndex: number; -}; - -export type AetheriaRuntimeViewportFeedRequest = { - viewport: Viewport; - selectedEntityIndex?: number; -}; - -export type AetheriaRuntimeViewportFeedSnapshot = { - viewport: ViewportResponse; - selectedObject: SelectedObjectDocument | null; - inventory: InventoryDocument | null; - daemonHealth: DaemonHealthDocument; - authorityStatus: AuthorityStatusDocument; - starbridgeSession: StarbridgeSessionDocument; - assetManifest: AssetManifestDocument; - receivedAtUtc: string; - sampleMs: number; -}; - -${renderRtsDocumentTypes("Viewport")} - -export type AetheriaEveSurfaceRequest = { - surfaceId?: string; - recordKey?: string; -}; - -export type EveEmbeddedDocumentRequest = { - documentId: string; - schemaId?: string; - slotId?: string; - presentationKind?: string; - context?: Record; -}; - -export type EveResolvedDocument = { - documentId: string; - schemaId: string; - document?: unknown; - surface?: AetheriaMenuSurfaceTree; -}; - -export type EveProviderAdvertisement = { - providerId: string; - title: string; - kind: string; - surfaces: Array<{ - surfaceId: string; - key: string; - transport: string; - status: string; - }>; -}; - -export type AetheriaEveCommandRequest = { - providerId?: string; - surfaceId?: string; - command?: string; - clientId?: string; - issuedAtUtc?: string; - payload?: Record; -}; - -export type AetheriaMenuSurfaceDocument = { - providerId: string; - providerKind: string; - title: string; - version: number; - updatedAtUtc: string; - surface: AetheriaMenuSurfaceTree; - commands: AetheriaMenuSurfaceCommand[]; -}; - -export type AetheriaMenuSurfaceTree = { - id: string; - root: AetheriaMenuSurfaceComponent; - styles: AetheriaMenuStyleToken[]; -}; - -export type AetheriaMenuSurfaceComponent = { - id: string; - kind: string; - props: Record; - embeddedDocuments?: AetheriaMenuEmbeddedDocumentSlot[]; - children: AetheriaMenuSurfaceComponent[]; -}; - -export type AetheriaMenuEmbeddedDocumentSlot = { - slotId: string; - documentId: string; - schemaId: string; - presentationKind: string; -}; - -export type AetheriaMenuStyleToken = { - name: string; - value: string; -}; - -export type AetheriaMenuSurfaceCommand = { - command: string; - label: string; - transport: string; -}; - -export type AetheriaRtsApi = { - mapViewport(request: Viewport): Promise; - objectsViewport(request: Viewport): Promise; - gravityViewport(request: Viewport): Promise; - renderSplatsViewport(request: Viewport): 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; - surfaceCatalog(): Promise; - surfaceCatalogIndex(): Promise; - eveSurface(request: AetheriaEveSurfaceRequest): Promise; - eveProviderAdvertisement(): Promise; - eveDocument(request: EveEmbeddedDocumentRequest): Promise; - submitEveCommand(request: AetheriaEveCommandRequest): Promise; - windowControl(action: "minimize" | "maximize" | "close"): Promise; - health(): Promise; -}; -`; -} - -function renderRtsDocumentTypes(viewportTypeName) { - return `export type ViewportResponse = { - schema: string; - frameId: number; - publishedAtUtc: string; - simulationTimeSeconds: number; - runId: string; - zoneIndex: number; - zoneName: string; - currentEntityKey: string; - viewport: ${viewportTypeName}; - controlledEntityIndices: number[]; - objects: ViewObject[]; - gravityInfluences: GravityInfluence[]; - bodies: BodyView[]; -}; - -export type ObjectsViewportResponse = { - schema: string; - frameId: number; - publishedAtUtc: string; - simulationTimeSeconds: number; - runId: string; - zoneIndex: number; - zoneName: string; - currentEntityKey: string; - viewport: ${viewportTypeName}; - controlledEntityIndices: number[]; - objects: ViewObject[]; -}; - -export type GravityViewportResponse = { - schema: string; - frameId: number; - publishedAtUtc: string; - simulationTimeSeconds: number; - runId: string; - zoneIndex: number; - zoneName: string; - viewport: ${viewportTypeName}; - gravityInfluences: GravityInfluence[]; - bodies: BodyView[]; -}; - -export type RenderSplatsViewportResponse = { - schema: string; - frameId: number; - publishedAtUtc: string; - simulationTimeSeconds: number; - runId: string; - zoneIndex: number; - zoneName: string; - viewport: ${viewportTypeName}; - layers: RenderSplatLayerDefinition[]; - splats: RenderSplatSoa; -}; - -export type RenderSplatLayerDefinition = { - layerKey: string; - displayName: string; - channel: number; - blendMode: string; - graphicsFormat: string; - clearBeforeDraw: boolean; - clearR: number; - clearG: number; - clearB: number; - clearA: number; -}; - -export type RenderSplatSoa = { - count: number; - centerX: number[]; - centerY: number[]; - halfExtentX: number[]; - halfExtentY: number[]; - rotationCos: number[]; - rotationSin: number[]; - channel: number[]; - falloff: number[]; - valueR: number[]; - valueG: number[]; - valueB: number[]; - valueA: number[]; - sourceKey: string[]; - layerIndex: number[]; - sourceKind: number[]; - frequencyX: number[]; - frequencyY: number[]; - phaseX: number[]; - phaseY: number[]; - animationSpeed: number[]; - sourceFlags: number[]; -}; - -export type ViewObject = { - entityIndex: number; - entityKey: string; - displayName: string; - kind: string; - factionKey: string; - x: number; - y: number; - z: number; - directionX: number; - directionY: number; - velocityX: number; - velocityY: number; - controlled: boolean; - targetEntityIndex: number; - isActive: boolean; - visibility: number; - iconAsset: AssetRef; - status: EntityStatus; - inventory: InventoryItem[]; -}; - -export type EntityStatus = { - hull: number; - shield: number; - heat: number; -}; - -export type InventoryItem = { - source: string; - itemKey: string; - quantity: number; - quality: number; - durability: number; - enabled: boolean; - iconAsset: AssetRef; -}; - -export type SelectedObjectDocument = { - schema: string; - frameId: number; - runId: string; - zoneIndex: number; - entityIndex: number; - selected: ViewObject | null; -}; - -export type InventoryDocument = { - schema: string; - frameId: number; - runId: string; - zoneIndex: number; - entityIndex: number; - entityKey: string; - items: InventoryItem[]; - equipment: InventoryItem[]; - cargo: InventoryItem[]; -}; - -export type DaemonHealthDocument = { - schema: string; - daemonId: string; - verseId: string; - publishedAtUtc: string; - statePath: string; - frameId: number; - observedCommandCount: number; - appliedCommandCount: number; - rejectedCommandCount: number; - status: string; - publicationSource: string; - transport: string; - commandBoundaryPath: string; -}; - -export type AuthorityStatusDocument = { - schema: string; - verseId: string; - policyId: string; - ruleVersion: string; - hostRuntimeId: string; - defaultMode: string; - updatedAtUtc: string; - rules: AuthorityRuleDocument[]; -}; - -export type AuthorityRuleDocument = { - ruleId: string; - subjectPrefix: string; - claimKinds: string[]; - mode: string; - runtimeIds: string[]; - leaseScope: string; - priority: number; -}; - -export type StarbridgeSessionDocument = { - schema: string; - frameId: number; - publishedAtUtc: string; - sessionId: string; - scenarioId: string; - scenarioName: string; - runId: string; - zoneIndex: number; - zoneName: string; - phase: string; - currentWaveIndex: number; - baseStatus: StarbridgeBaseStatusDocument; - stationStock: StarbridgeStationStockItemDocument[]; - waveForecast: StarbridgeWaveForecastDocument[]; - runtimeRoles: StarbridgeRuntimeRoleDocument[]; -}; - -export type StarbridgeBaseStatusDocument = { - entityKey: string; - displayName: string; - hull: number; - shield: number; - heat: number; - isActive: boolean; -}; - -export type StarbridgeStationStockItemDocument = { - itemKey: string; - quantity: number; - quality: number; - durability: number; - source: string; - iconAsset: AssetRef; -}; - -export type AssetManifestDocument = { - schema: string; - publishedAtUtc: string; - runId: string; - baseUri: string; - assets: AssetManifestEntry[]; -}; - -export type AssetManifestEntry = { - ref: AssetRef; - sizeBytes: number; - width: number; - height: number; - tags: string[]; -}; - -export type AssetRef = { - assetKey: string; - kind: string; - uri: string; - transport: string; - contentHash: string; - mimeType: string; - metadata: Record; -}; - -export type StarbridgeWaveForecastDocument = { - waveIndex: number; - displayName: string; - attackerKeys: string[]; - bossKey: string; - recoveredTechnologyKeys: string[]; -}; - -export type StarbridgeRuntimeRoleDocument = { - runtimeId: string; - role: string; - entityKey: string; -}; - -export type GravityInfluence = { - bodyKey: string; - orbitKey: string; - kind: string; - x: number; - y: number; - radius: number; - gravityDepth: number; - gravityDepthExponent: number; - waveRadius: number; - waveDepth: number; - waveSpeed: number; -}; - -export type BodyView = { - bodyKey: string; - orbitKey: string; - name: string; - kind: string; - x: number; - y: number; - radius: number; - isAsteroidBelt: boolean; - iconAsset: AssetRef; - iconSize: number; -}; -`; -} - -function renderIpcChannelLines() { - return ipcChannels - .map(([name, channel]) => ` ${name}: "${channel}",`) - .join("\n"); -} - -function renderPreload() { - const channelLines = renderIpcChannelLines(); - const eveProviderMethods = new Set([ - "eveSurface", - "eveProviderAdvertisement", - "eveDocument", - "submitEveCommand", - "windowControl", - ]); - const apiLines = ipcChannels - .filter(([name]) => name !== "viewportFeedUpdate" && - name !== "viewportFeedStop" && - !eveProviderMethods.has(name)) - .map(([name]) => { - if (name === "viewportFeed") { - return ` watchViewportFeed: (request, callback) => { - const subscriptionId = \`viewport-feed-\${Date.now().toString(36)}-\${Math.random().toString(36).slice(2)}\`; - const listener = (_event, message) => { - if (message?.subscriptionId === subscriptionId) { - callback(message.snapshot); - } - }; - ipcRenderer.on(channels.viewportFeedUpdate, listener); - void ipcRenderer.invoke(channels.viewportFeed, { subscriptionId, feed: request }); - return () => { - ipcRenderer.off(channels.viewportFeedUpdate, listener); - void ipcRenderer.invoke(channels.viewportFeedStop, subscriptionId); - }; - },`; - } - const hasNoArguments = name === "health" || - name === "daemonHealth" || - name === "authorityStatus" || - name === "starbridgeSession" || - name === "assetManifest" || - name === "surfaceCatalog" || - name === "surfaceCatalogIndex"; - const parameters = hasNoArguments ? "()" : "request"; - const invokeArguments = hasNoArguments ? `channels.${name}` : `channels.${name}, request`; - return ` ${name}: ${parameters} => ipcRenderer.invoke(${invokeArguments}),`; - }) - .join("\n"); - - return `// -// Generated by scripts/generate-rts-bindings.mjs from the RTS IPC surface contract. - -const { contextBridge, ipcRenderer } = require("electron"); -const { installEveProviderBridge } = require("@gamecult/eve-electron/preload"); - -const channels = { -${channelLines} -}; - -contextBridge.exposeInMainWorld("aetheriaRts", { -${apiLines} -}); - -installEveProviderBridge({ - contextBridge, - ipcRenderer, - channels: { - providerAdvertisement: channels.eveProviderAdvertisement, - surface: channels.eveSurface, - document: channels.eveDocument, - submitCommand: channels.submitEveCommand, - windowControl: channels.windowControl, - }, -}); -`; -} diff --git a/Aetheria.Rts.Web/scripts/verify-stage7b-rts-client.ps1 b/Aetheria.Rts.Web/scripts/verify-stage7b-rts-client.ps1 deleted file mode 100644 index 95bbfe182..000000000 --- a/Aetheria.Rts.Web/scripts/verify-stage7b-rts-client.ps1 +++ /dev/null @@ -1,576 +0,0 @@ -$ErrorActionPreference = "Stop" - -$root = Split-Path -Parent $PSScriptRoot -Set-Location $root - -& node scripts/generate-rts-bindings.mjs --check -if ($LASTEXITCODE -ne 0) { - Write-Error "Stage 7B verifier failed: generated RTS bindings are stale." -} - -$forbidden = @( - 'window\.aetheriaRts\.command', - 'window\.aetheriaRts\.viewport', - 'ipcMain\.handle\("aetheria-rts:command', - 'ipcMain\.handle\("aetheria-rts:viewport', - 'function createCommandDocument', - 'export type RtsCommandRequest', - 'public async command\(', - 'public async viewport\(' -) - -$forbiddenPattern = ($forbidden -join '|') -$forbiddenHits = & rg -n $forbiddenPattern Client Electron 2>$null -if ($LASTEXITCODE -eq 0) { - Write-Error "Stage 7B verifier failed: public generic RTS command/viewport surface still exists.`n$forbiddenHits" -} -if ($LASTEXITCODE -gt 1) { - Write-Error "Stage 7B verifier could not run rg for forbidden public surfaces." -} - -$forbiddenFileBackedDebugSurfaceHits = & rg -n 'debugSurface|debug-surface|watchDebugSurface|AETHERIA_CULTUI_DEBUG_SURFACE_PATH|cultui-debug-surface|file_surface|debug-log' Client Electron scripts/generate-rts-bindings.mjs 2>$null -if ($LASTEXITCODE -eq 0) { - Write-Error "Stage 7B verifier failed: Electron must consume daemon Eve surfaces, not file-backed CultUI debug surfaces.`n$forbiddenFileBackedDebugSurfaceHits" -} -if ($LASTEXITCODE -gt 1) { - Write-Error "Stage 7B verifier could not run rg for file-backed debug surface checks." -} - -$forbiddenRtsSurfaceContractHits = & rg -n 'gamecult\.aetheria\.rts\.(surfaces|viewport_feed)' Client Electron scripts/generate-rts-bindings.mjs 2>$null -if ($LASTEXITCODE -eq 0) { - Write-Error "Stage 7B verifier failed: Aetheria CultMesh surface catalog/feed ids must not be RTS-branded.`n$forbiddenRtsSurfaceContractHits" -} -if ($LASTEXITCODE -gt 1) { - Write-Error "Stage 7B verifier could not run rg for RTS-branded surface contract checks." -} - -$required = @( - 'mapViewport', - 'objectsViewport', - 'gravityViewport', - 'selectedObject', - 'inventory', - 'daemonHealth', - 'authorityStatus', - 'starbridgeSession', - 'setMoveVector', - 'setTarget', - 'surfaceCatalogIndex', - 'AetheriaRtsIpcChannels' -) - -foreach ($symbol in $required) { - & rg -q $symbol Client Electron - if ($LASTEXITCODE -ne 0) { - Write-Error "Stage 7B verifier failed: expected typed RTS client symbol '$symbol' was not found." - } -} - -$rendererText = Get-Content Client/app.ts -Raw -if (-not $rendererText.Contains('window.aetheriaRts.eveSurface') -or - -not $rendererText.Contains('renderEveSurface') -or - -not $rendererText.Contains('resolveEveDocument') -or - -not $rendererText.Contains('submitEveCommand')) { - Write-Error "Stage 7B verifier failed: RTS renderer is not lowering daemon Eve through the generated preload API." -} - -$indexText = Get-Content wwwroot/index.html -Raw -if (-not $indexText.Contains('eve-surface-host')) { - Write-Error "Stage 7B verifier failed: RTS renderer is not mounting the daemon Eve surface host." -} - -$transportLayoutPatterns = @( - 'new Array', - 'bounds\[', - 'viewportSchema', - 'commandSchema', - 'createBaseCommandDocument', - 'decodeViewObject', - 'decodeInventoryItem', - 'decodeGravityInfluence', - 'decodeBodyView', - 'fetchDocument\(', - 'sendSnapshotRequest', - 'schemaIds', - 'recordKeys', - 'CultNetSnapshotResponseRawMessage', - 'AetheriaRtsSchemas\.rtsViewport' -) - -$transportLayoutPattern = ($transportLayoutPatterns -join '|') -$transportLayoutHits = & rg -n $transportLayoutPattern Electron/aetheria-cultmesh.ts 2>$null -if ($LASTEXITCODE -eq 0) { - Write-Error "Stage 7B verifier failed: RTS CultMesh transport wrapper still owns document layout/codec details.`n$transportLayoutHits" -} -if ($LASTEXITCODE -gt 1) { - Write-Error "Stage 7B verifier could not run rg for transport wrapper layout checks." -} - -$remotePublicationReaderPatterns = @( - 'sendSnapshotRequest', - 'snapshot_response_raw', - 'CultNetSnapshotResponseRawMessage', - 'decode\(' -) - -$remotePublicationReaderPattern = ($remotePublicationReaderPatterns -join '|') -$remotePublicationReaderHits = & rg -n $remotePublicationReaderPattern Electron/aetheria-cultmesh.ts 2>$null -if ($LASTEXITCODE -eq 0) { - Write-Error "Stage 7B verifier failed: RTS CultMesh client still owns raw snapshot/codec details.`n$remotePublicationReaderHits" -} -if ($LASTEXITCODE -gt 1) { - Write-Error "Stage 7B verifier could not run rg for remote publication layout checks." -} - -$requiredBindingSymbols = @( - 'createAetheriaRuntimeGameOperationHandles', - 'createAetheriaRuntimeGameQueryHandles', - 'AetheriaRtsSchemas' -) - -foreach ($symbol in $requiredBindingSymbols) { - & rg -q $symbol Electron/aetheria-rts-bindings.ts - if ($LASTEXITCODE -ne 0) { - Write-Error "Stage 7B verifier failed: expected RTS binding symbol '$symbol' was not found." - } -} - -$requiredGeneratedBindingSymbols = @( - 'AetheriaRtsIpcChannels', - 'registerAetheriaRtsIpcHandlers', - 'export type AetheriaRtsMainClient', - 'aetheria-rts:map-viewport', - 'aetheria-rts:objects-viewport', - 'aetheria-rts:viewport-feed', - 'aetheria-rts:viewport-feed-update', - 'aetheria-rts:set-move-vector', - 'export type AetheriaRuntimeSelectedObjectRequest', - 'export type AetheriaRuntimeViewportFeedRequest', - 'export type AetheriaRuntimeViewportFeedSnapshot', - 'aetheriaRuntimeDaemonCommandDocumentSlots', - 'encodeSetMoveVectorCommand', - 'encodeSetTargetCommand', - 'AetheriaRuntimeGameDocumentSources', - 'AetheriaRuntimeGameQueryDiagnostic', - 'AetheriaRuntimeGameLiveFeedDiagnostic', - 'AetheriaRuntimeGameSurfaceCatalogDiagnostic', - 'AetheriaRuntimeGameDocuments', - 'AetheriaRuntimeGameDocumentResolvers', - 'CultMeshSurfaceCatalogDiagnostic', - 'CultMeshSurfaceCatalogIndexDiagnostic', - 'CultMeshOperationReceipt', - 'aetheria-rts:surface-catalog', - 'aetheria-rts:surface-catalog-index', - 'surfaceCatalogDiagnostics(): CultMeshSurfaceCatalogDiagnostic', - 'surfaceCatalogIndexDiagnostics(): CultMeshSurfaceCatalogIndexDiagnostic', - 'ipcMain.handle(AetheriaRtsIpcChannels.surfaceCatalog', - 'ipcMain.handle(AetheriaRtsIpcChannels.surfaceCatalogIndex', - 'CultMesh.querySource', - 'CultMesh.query', - 'CultMeshQuerySource', - 'routeHint: CultMeshRouteHint', - 'CultMeshQueryWatcher', - 'AetheriaRuntimeGameQueryWatchers', - 'createAetheriaRuntimeGameDocuments', - 'CultMesh.document', - 'CultMesh.bindDocument', - 'CultMesh.describeSurface(documents.daemonFrame)', - 'watchQuery: watchers.objectsViewport', - 'sources: [AetheriaRuntimeGameDocumentSources.daemonFrame]', - 'describeAetheriaRuntimeGameQueryHandles', - 'describeAetheriaRuntimeGameLiveFeedSurface', - 'describeAetheriaRuntimeGameSurfaceCatalog', - 'CultMesh.describeQuerySurface', - 'CultMesh.describeLiveFeed', - 'CultMesh.describeSurfaceCatalog', - 'CultMesh.describeSurface', - 'CultMesh.describeSurface(operations.setMoveVector)', - 'CultMesh.describeSurface(operations.setTarget)', - 'CultMesh.operation', - 'CultMesh.operationReceipt', - 'AetheriaRuntimeGameOperationHandles', - 'AetheriaRuntimeGameVerseHandles', - 'createAetheriaRuntimeGameVerseHandles', - 'CultMesh.bindQuery', - 'CultMesh.bindOperation', - 'zone: (_zoneId =', - 'visibleWithin', - 'entity: (entityKey: string)', - 'pilot', - 'selectedObject: CultMesh.query', - 'inventory: CultMesh.query', - 'daemonHealth: CultMesh.query', - 'authorityStatus: CultMesh.query', - 'starbridgeSession: CultMesh.query' -) - -foreach ($symbol in $requiredGeneratedBindingSymbols) { - & rg -F -q $symbol Electron/aetheria-rts-generated-bindings.ts - if ($LASTEXITCODE -ne 0) { - Write-Error "Stage 7B verifier failed: expected generated RTS binding symbol '$symbol' was not found." - } -} - -$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 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 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 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." -} - -& rg -q 'createAetheriaRuntimeGameQueryHandles\(' Electron/aetheria-cultmesh.ts -if ($LASTEXITCODE -ne 0) { - Write-Error "Stage 7B verifier failed: RTS client is not creating generated query handles." -} - -$clientText = Get-Content Electron/aetheria-cultmesh.ts -Raw - -if (-not $clientText.Contains('this.documents = createAetheriaRuntimeGameDocuments(') -or - -not $clientText.Contains('this.queryVerse.context.routeHint,')) { - Write-Error "Stage 7B verifier failed: RTS client is not creating generated document handles with its shared Verse route." -} - -if (-not $clientText.Contains('daemonFrame: async () => this.fetchLatestFrameDocument()') -or - -not $clientText.Contains('authorityPolicy: async () => this.fetchAuthorityPolicyDocument()')) { - Write-Error "Stage 7B verifier failed: RTS client document handles are not resolving through the daemon publication catalog." -} - -if (-not $clientText.Contains('this.verse = CultMesh.verse("aetheria.local", this.runtimeId)')) { - Write-Error "Stage 7B verifier failed: RTS client should bind to the shared CultMesh Verse primitive instead of constructing local contexts directly." -} - -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 query reads." -} - -if (-not $clientText.Contains('this.commandVerse = this.verse') -or - -not $clientText.Contains('.withRoute("network", this.daemonTarget.uri)') -or - -not $clientText.Contains('.withClaim("commander-control"')) { - Write-Error "Stage 7B verifier failed: RTS client is not deriving a network Verse view with an explicit command authority claim." -} - -if (-not $clientText.Contains('this.publicationDescription = publicationMode === "remote" ? this.daemonTarget.uri : statePath')) { - 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 = createAetheriaRuntimeGameVerseHandles(')) { - Write-Error "Stage 7B verifier failed: RTS client should bind generated Aetheria domain sugar to its Verse contexts." -} - -if (-not $clientText.Contains('return this.aetheria.zone().objects.visibleWithin(request);') -or - -not $clientText.Contains('return this.aetheria.entity(request.actorEntityKey).pilot.move(') -or - -not $clientText.Contains('return this.aetheria.entity(request.actorEntityKey).pilot.target(')) { - Write-Error "Stage 7B verifier failed: RTS client should route viewport and pilot operations through generated Verse domain sugar." -} - -$forbiddenRawContextHits = & rg -n 'CultMesh\.(operationContext|queryContext)\(this\.runtimeId' Electron/aetheria-cultmesh.ts 2>$null -if ($LASTEXITCODE -eq 0) { - Write-Error "Stage 7B verifier failed: RTS client still constructs raw runtime-id contexts instead of using the shared Verse primitive.`n$forbiddenRawContextHits" -} -if ($LASTEXITCODE -gt 1) { - Write-Error "Stage 7B verifier could not run rg for raw context checks." -} - -if (-not $clientText.Contains('queryDiagnostics()')) { - Write-Error "Stage 7B verifier failed: RTS client does not expose generated query diagnostics." -} - -if (-not $clientText.Contains('describeAetheriaRuntimeGameQueryHandles(this.queries)')) { - Write-Error "Stage 7B verifier failed: RTS query diagnostics should be read from generated query handle metadata." -} - -if (-not $clientText.Contains('liveFeedDiagnostics()')) { - Write-Error "Stage 7B verifier failed: RTS client does not expose generated live feed diagnostics." -} - -if (-not $clientText.Contains('describeAetheriaRuntimeGameLiveFeedSurface(this.createViewportFeed())')) { - Write-Error "Stage 7B verifier failed: RTS live feed diagnostics should be read from generated live feed metadata." -} - -if (-not $clientText.Contains('surfaceCatalogDiagnostics()')) { - Write-Error "Stage 7B verifier failed: RTS client does not expose a unified CultMesh surface catalog diagnostic." -} - -if (-not $clientText.Contains('surfaceCatalogIndexDiagnostics()')) { - Write-Error "Stage 7B verifier failed: RTS client does not expose a grouped CultMesh surface catalog diagnostic." -} - -if (-not $clientText.Contains('CultMesh.surfaceCatalogIndex(this.surfaceCatalogDiagnostics())')) { - Write-Error "Stage 7B verifier failed: RTS grouped surface catalog should use the shared CultMesh catalog index primitive." -} - -if (-not $clientText.Contains('describeAetheriaRuntimeGameSurfaceCatalog(this.queries, this.operations, this.documents)')) { - Write-Error "Stage 7B verifier failed: RTS surface catalog should be read from generated query, operation, and document metadata." -} - -$forbiddenDirectClientOperationHits = & rg -n 'operations\.set(MoveVector|Target)\.invoke' Electron/aetheria-cultmesh.ts 2>$null -if ($LASTEXITCODE -eq 0) { - Write-Error "Stage 7B verifier failed: RTS client still invokes operation handles directly instead of generated domain sugar.`n$forbiddenDirectClientOperationHits" -} -if ($LASTEXITCODE -gt 1) { - Write-Error "Stage 7B verifier could not run rg for direct operation handle checks." -} - -if (-not $clientText.Contains('CultMesh.liveFeed')) { - Write-Error "Stage 7B verifier failed: RTS viewport feed should use the shared CultMesh live feed primitive." -} - -$requiredGeneratedPreloadSymbols = @( - '$null -if ($LASTEXITCODE -eq 0) { - Write-Error "Stage 7B verifier failed: Electron main still registers raw RTS IPC channel strings.`n$forbiddenMainIpcStringHits" -} -if ($LASTEXITCODE -gt 1) { - Write-Error "Stage 7B verifier could not run rg for raw Electron main IPC checks." -} - -$forbiddenMainGeneratedHandlerMirrors = @( - 'AetheriaRtsIpcChannels\.mapViewport', - 'AetheriaRtsIpcChannels\.objectsViewport', - 'AetheriaRtsIpcChannels\.setMoveVector' -) -$forbiddenMainGeneratedHandlerPattern = ($forbiddenMainGeneratedHandlerMirrors -join '|') -$forbiddenMainGeneratedHandlerHits = & rg -n $forbiddenMainGeneratedHandlerPattern Electron/main.ts 2>$null -if ($LASTEXITCODE -eq 0) { - Write-Error "Stage 7B verifier failed: Electron main still mirrors generated IPC handler registration.`n$forbiddenMainGeneratedHandlerHits" -} -if ($LASTEXITCODE -gt 1) { - Write-Error "Stage 7B verifier could not run rg for generated IPC handler mirror checks." -} - -& rg -q 'registerAetheriaRtsIpcHandlers' Electron/main.ts -if ($LASTEXITCODE -ne 0) { - Write-Error "Stage 7B verifier failed: Electron main is not using generated RTS IPC handler registration." -} - -$forbiddenClientDirectDocumentReturns = @( - 'return projectSelectedObjectFromFrame', - 'return projectInventoryFromFrame', - 'return projectDaemonHealth', - 'return projectAuthorityStatus', - 'return projectStarbridgeSessionSummary' -) -$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$forbiddenClientDirectDocumentHits" -} -if ($LASTEXITCODE -gt 1) { - Write-Error "Stage 7B verifier could not run rg for direct document return checks." -} - -$requiredRendererContractSymbols = @( - 'export type AetheriaRtsApi', - 'export type ViewportResponse', - 'export type ObjectsViewportResponse', - 'export type GravityViewportResponse', - 'export type AetheriaRuntimeViewportFeedRequest', - 'export type AetheriaRuntimeViewportFeedSnapshot', - 'watchViewportFeed', - 'export type AetheriaRuntimeSetMoveVectorRequest', - 'export type AetheriaRuntimeSetTargetRequest' -) - -foreach ($symbol in $requiredRendererContractSymbols) { - & rg -q $symbol Client/aetheria-rts-contract.ts - if ($LASTEXITCODE -ne 0) { - Write-Error "Stage 7B verifier failed: expected generated renderer contract symbol '$symbol' was not found." - } -} - -$forbiddenRendererAppPatterns = @( - '^type ViewportResponse =', - '^type ObjectsViewportResponse =', - '^type GravityViewportResponse =', - '^type AetheriaRtsApi =', - '^type ViewObject =', - '^type InventoryItem =' -) - -$forbiddenRendererAppPattern = ($forbiddenRendererAppPatterns -join '|') -$forbiddenRendererAppHits = & rg -n $forbiddenRendererAppPattern Client/app.ts 2>$null -if ($LASTEXITCODE -eq 0) { - Write-Error "Stage 7B verifier failed: renderer still owns duplicated RTS contract types.`n$forbiddenRendererAppHits" -} -if ($LASTEXITCODE -gt 1) { - Write-Error "Stage 7B verifier could not run rg for duplicated renderer contract checks." -} - -$requiredEveRendererSymbols = @( - 'renderEveSurface', - 'eve-surface-host', - 'window.aetheriaRts.eveSurface', - 'resolveEveDocument', - 'gamecult.eve.surface.v1', - 'aetheria.daemon.game' -) - -foreach ($symbol in $requiredEveRendererSymbols) { - & rg -F -q $symbol Client/app.ts wwwroot/index.html - if ($LASTEXITCODE -ne 0) { - Write-Error "Stage 7B verifier failed: expected daemon Eve renderer symbol '$symbol' was not found." - } -} - -$forbiddenBindingPatterns = @( - 'decodeViewportDocument', - 'viewportRecordKey', - 'aetheriaRuntimeRtsViewportDocumentSlots', - 'aetheriaRuntimeRtsViewportObjectSlots', - '^export type ViewportResponse =', - '^export type ObjectsViewportResponse =', - '^export type GravityViewportResponse =', - '^export type ViewObject =', - '^export type InventoryItem =', - '^export type SelectedObjectRequest =' -) - -$forbiddenBindingPattern = ($forbiddenBindingPatterns -join '|') -$forbiddenBindingHits = & rg -n $forbiddenBindingPattern Electron/aetheria-rts-bindings.ts 2>$null -if ($LASTEXITCODE -eq 0) { - Write-Error "Stage 7B verifier failed: stale remote viewport decoder surface still exists in RTS bindings.`n$forbiddenBindingHits" -} -if ($LASTEXITCODE -gt 1) { - Write-Error "Stage 7B verifier could not run rg for stale binding checks." -} - -$requiredLocalDocumentSymbols = @( - 'readViewportDocument', - 'readObjectsViewportDocument', - 'readGravityViewportDocument', - 'readRenderSplatsViewportDocument', - 'AetheriaRtsSchemas.objectsViewport', - 'AetheriaRtsSchemas.gravityViewport', - 'readSelectedObjectDocument', - 'readInventoryDocument', - 'AetheriaRtsSchemas.selectedObject', - 'AetheriaRtsSchemas.inventory', - 'readDaemonHealthDocument', - 'readAuthorityStatusDocument', - 'readStarbridgeSessionSummaryDocument', - 'AetheriaRtsSchemas.starbridgeSessionSummary' -) - -foreach ($symbol in $requiredLocalDocumentSymbols) { - & rg -q $symbol Electron/aetheria-rts-local-documents.ts - if ($LASTEXITCODE -ne 0) { - Write-Error "Stage 7B verifier failed: expected daemon-authored document decoder symbol '$symbol' was not found." - } -} - -$forbiddenLocalDocumentBuilders = & rg -n 'build(Viewport|ObjectsViewport|GravityViewport|RenderSplatsViewport|SelectedObject|Inventory)DocumentFromFrame|frameContext\(frameDocument' Electron/aetheria-rts-local-documents.ts 2>$null -if ($LASTEXITCODE -eq 0) { - Write-Error "Stage 7B verifier failed: Electron must decode daemon-authored managed documents, not rebuild them from daemonFrame.`n$forbiddenLocalDocumentBuilders" -} -if ($LASTEXITCODE -gt 1) { - Write-Error "Stage 7B verifier could not run rg for stale local document builder checks." -} - -& rg -q 'CultMesh.documentsFromPublication' Electron/aetheria-cultmesh.ts -if ($LASTEXITCODE -ne 0) { - Write-Error "Stage 7B verifier failed: RTS publications should be read through a configured CultMesh document catalog." -} - -$forbiddenPublicationReaderFiles = & rg -n 'AetheriaLocalPublicationReader|AetheriaRemotePublicationReader|aetheria-local-publication-reader|aetheria-remote-publication-reader' Electron 2>$null -if ($LASTEXITCODE -eq 0) { - Write-Error "Stage 7B verifier failed: RTS publication reader wrappers should be collapsed into the CultMesh document catalog.`n$forbiddenPublicationReaderFiles" -} -if ($LASTEXITCODE -gt 1) { - Write-Error "Stage 7B verifier could not run rg for stale publication reader checks." -} - -& rg -q 'AetheriaRtsSchemas.starbridgeSessionSummary' Electron/aetheria-cultmesh.ts -if ($LASTEXITCODE -ne 0) { - Write-Error "Stage 7B verifier failed: Starbridge summary publication binding was not found." -} - -$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 local publication bindings do not point at the canonical daemon store." -} - -$requiredGeneratedBindingSymbols = @( - 'aetheriaRuntimeDaemonCommandDocumentSlots', - 'aetheriaRuntimeDaemonFrameDocumentSlots', - 'aetheriaRuntimeDaemonHealthDocumentSlots', - 'aetheriaRuntimeGameViewportDocumentSlots', - 'aetheriaRuntimeObjectsViewportDocumentSlots', - 'aetheriaRuntimeGravityViewportDocumentSlots', - 'aetheriaRuntimeRenderSplatsViewportDocumentSlots', - 'aetheriaRuntimeSelectedObjectDocumentSlots', - 'aetheriaRuntimeInventoryDocumentSlots', - 'aetheriaRuntimeStarbridgeScenarioDocumentSlots', - 'aetheriaRuntimeStarbridgeSessionDocumentSlots', - 'aetheriaRuntimeStarbridgeSessionSummaryDocumentSlots', - 'aetheriaRuntimeStarbridgeStationStockItemSlots', - 'aetheriaRuntimeStarbridgeWaveDefinitionSlots', - 'aetheriaRuntimeStarbridgeWaveForecastSlots', - 'aetheriaRuntimeStarbridgeRuntimeRoleSlots', - 'aetheriaRuntimeStarbridgeBaseStatusSlots', - 'aetheriaRuntimeGameViewportBoundsSlots', - 'aetheriaRuntimeGameViewportObjectSlots', - 'aetheriaRuntimeVerseAuthorityPolicyDocumentSlots', - 'aetheriaRuntimeAuthorityRuleSlots', - 'aetheriaRuntimeAuthorityLeaseDocumentSlots', - 'aetheriaRuntimeRunCheckpointCommitSlots', - 'aetheriaRuntimeZoneSnapshotCommitSlots', - 'aetheriaRuntimeEntitySnapshotCommitSlots', - 'aetheriaRuntimeBodySnapshotCommitSlots', - 'aetheriaRuntimeEntityStatGridCommitSlots', - 'aetheriaRuntimeCargoBayLoadoutCommitSlots', - 'aetheriaRuntimeLoadoutItemSlotCommitSlots', - 'aetheriaRuntimeLoadoutItemCommitSlots', - 'AetheriaRuntimeDaemonCommandKinds', - 'AetheriaRtsSchemas' -) - -foreach ($symbol in $requiredGeneratedBindingSymbols) { - & rg -q $symbol Electron/aetheria-rts-generated-bindings.ts - if ($LASTEXITCODE -ne 0) { - Write-Error "Stage 7B verifier failed: expected generated RTS binding symbol '$symbol' was not found." - } -} - -Write-Host "Stage 7B RTS client verifier passed." diff --git a/Aetheria.Rts.Web/scripts/verify-stage7c-electron-rts.ps1 b/Aetheria.Rts.Web/scripts/verify-stage7c-electron-rts.ps1 deleted file mode 100644 index 446997ec9..000000000 --- a/Aetheria.Rts.Web/scripts/verify-stage7c-electron-rts.ps1 +++ /dev/null @@ -1,136 +0,0 @@ -$ErrorActionPreference = "Stop" - -$root = Split-Path -Parent $PSScriptRoot -Set-Location $root - -npm run build -if ($LASTEXITCODE -ne 0) { - Write-Error "Stage 7C Electron RTS verifier failed: RTS build failed." -} - -if (-not (Get-Content wwwroot\index.html -Raw).Contains('eve-surface-host')) { - Write-Error "Stage 7C Electron RTS verifier failed: built renderer markup does not host the daemon Eve surface." -} - -$appText = Get-Content wwwroot\app.js -Raw -if (-not $appText.Contains('mountEveElectronProvider') -or - $appText.Contains('window.aetheriaRts.eveProviderAdvertisement') -or - $appText.Contains('window.aetheriaRts.eveDocument') -or - $appText.Contains('eve:surface:aetheria.daemon.game') -or - $appText.Contains('gamecult.fields.gravity.v1') -or - $appText.Contains('gamecult.fields.splats.v1') -or - $appText.Contains('gamecult.fields.objects.v1') -or - $appText.Contains('legacyViewportMode') -or - $appText.Contains('mainMenuSurface') -or - $appText.Contains('main-menu-mode')) { - Write-Error "Stage 7C Electron RTS verifier failed: renderer is not daemon-Eve-only." -} -$rendererRuntimeText = Get-Content node_modules\@gamecult\eve-electron\src\eve-electron-renderer.mjs -Raw -if (-not $rendererRuntimeText.Contains('EveBrowserProviderHost') -or - -not $rendererRuntimeText.Contains('eveProvider.providerAdvertisement') -or - -not $rendererRuntimeText.Contains('eveProvider.document')) { - Write-Error "Stage 7C Electron RTS verifier failed: EveElectron does not own the generic provider renderer bootstrap." -} -if ($appText.Contains('aetheria.rts.electron') -or - $appText.Contains('Aetheria RTS')) { - Write-Error "Stage 7C Electron RTS verifier failed: renderer still advertises an RTS-branded client identity." -} - -$mainText = Get-Content Electron\main.ts -Raw -if (-not $mainText.Contains('startEveElectronProviderHost') -or (Test-Path Electron\preload.cjs)) { - Write-Error "Stage 7C Electron RTS verifier failed: Electron does not consume the EveElectron-owned preload entry." -} -$hostRuntimeText = Get-Content node_modules\@gamecult\eve-electron\src\live-provider-host.mjs -Raw -if (-not $hostRuntimeText.Contains('eve-provider-preload-entry.cjs') -or - -not $hostRuntimeText.Contains('registerEveProviderIpc') -or - -not $hostRuntimeText.Contains('registerEveAssetProtocol') -or - -not $hostRuntimeText.Contains('createEveElectronWindow')) { - Write-Error "Stage 7C Electron RTS verifier failed: EveElectron does not own the live provider host lifecycle." -} -if (-not $mainText.Contains('eve-asset://asset') -or - -not $mainText.Contains('assetProbe') -or - $mainText.Contains('aetheria-cdn')) { - Write-Error "Stage 7C Electron RTS verifier failed: Electron smoke does not verify EveElectron-owned provider asset delivery." -} -if (-not $mainText.Contains('await eveProvider.providerAdvertisement()')) { - Write-Error "Stage 7C Electron RTS verifier failed: Electron smoke does not verify provider advertisement discovery through preload." -} - -if (-not $mainText.Contains('resolveEmbedded') -or - -not $mainText.Contains('renderSplatsResolved') -or - -not $mainText.Contains('gravityResolved') -or - -not $mainText.Contains('objectsResolved') -or - -not $mainText.Contains('eveFieldSurface') -or - -not $mainText.Contains('embeddedDocuments')) { - Write-Error "Stage 7C Electron RTS verifier failed: Electron smoke does not verify daemon Eve field document lowering." -} - -if (-not $mainText.Contains('await eveProvider.submitCommand') -or - -not $mainText.Contains('eveReceipt?.commandId') -or - -not $mainText.Contains('eveReceipt?.accepted') -or - -not $mainText.Contains('eveReceipt?.route')) { - Write-Error "Stage 7C Electron RTS verifier failed: Electron smoke does not verify typed operation receipts through preload." -} - -if (-not $mainText.Contains('AETHERIA_VERSE_ID') -or - -not $mainText.Contains('--verse-id') -or - -not $mainText.Contains('AETHERIA_DAEMON_ID') -or - -not $mainText.Contains('--daemon-id')) { - Write-Error "Stage 7C Electron RTS verifier failed: Electron launcher does not configure daemon Verse identity." -} -if ($mainText.Contains('AETHERIA_RTS_') -or - $mainText.Contains('Aetheria RTS') -or - $mainText.Contains('starfire-rts') -or - $mainText.Contains('aetheria-rts-daemon') -or - $mainText.Contains('aetheria.rts.electron') -or - $mainText.Contains('aetheria-rts-electron') -or - $mainText.Contains('rtsCultMesh') -or - $mainText.Contains('rtsDaemonId') -or - $mainText.Contains('rtsVerseId')) { - Write-Error "Stage 7C Electron RTS verifier failed: Electron launcher still uses RTS-branded daemon connection authority." -} - -$electron = Join-Path $root "node_modules\electron\dist\electron.exe" -if (!(Test-Path -LiteralPath $electron)) { - Write-Error "Stage 7C Electron RTS verifier failed: electron.exe was not found at $electron." -} - -$tempRoot = Join-Path ([System.IO.Path]::GetTempPath()) ("aetheria-stage7c-electron-" + [Guid]::NewGuid().ToString("N")) -New-Item -ItemType Directory -Path $tempRoot | Out-Null -$resultPath = Join-Path $tempRoot "electron-smoke-result.json" -$udp = [System.Net.Sockets.UdpClient]::new(0) -$port = ([System.Net.IPEndPoint]$udp.Client.LocalEndPoint).Port -$udp.Close() - -try { - $env:AETHERIA_ELECTRON_SMOKE = "1" - $env:AETHERIA_RUNTIME_ROOT = $tempRoot - $env:AETHERIA_CLIENT_CULTMESH_PORT = $port.ToString() - $env:AETHERIA_VERSE_ID = "aetheria.stage7c.electron" - $env:AETHERIA_DAEMON_ID = "stage7c-aetheria-daemon" - $env:AETHERIA_ELECTRON_SMOKE_RESULT = $resultPath - $process = Start-Process -FilePath $electron -ArgumentList "." -WorkingDirectory $root -Wait -PassThru - if ($process.ExitCode -ne 0) { - Write-Error "Stage 7C Electron RTS verifier failed: Electron exited with code $($process.ExitCode)." - } - - if (!(Test-Path -LiteralPath $resultPath)) { - Write-Error "Stage 7C Electron RTS verifier failed: Electron did not write a smoke result." - } - - $result = Get-Content -Raw -LiteralPath $resultPath | ConvertFrom-Json - if ($result.ok -ne $true) { - Write-Error "Stage 7C Electron RTS verifier failed: $($result.error)" - } -} -finally { - Remove-Item Env:\AETHERIA_ELECTRON_SMOKE -ErrorAction SilentlyContinue - Remove-Item Env:\AETHERIA_RUNTIME_ROOT -ErrorAction SilentlyContinue - Remove-Item Env:\AETHERIA_CLIENT_CULTMESH_PORT -ErrorAction SilentlyContinue - Remove-Item Env:\AETHERIA_VERSE_ID -ErrorAction SilentlyContinue - Remove-Item Env:\AETHERIA_DAEMON_ID -ErrorAction SilentlyContinue - Remove-Item Env:\AETHERIA_ELECTRON_SMOKE_RESULT -ErrorAction SilentlyContinue - Remove-Item -LiteralPath $tempRoot -Recurse -Force -ErrorAction SilentlyContinue -} - -Write-Host "Stage 7C Electron RTS verifier passed." diff --git a/Aetheria.Rts.Web/scripts/verify-stage7c-local-runtime.ps1 b/Aetheria.Rts.Web/scripts/verify-stage7c-local-runtime.ps1 deleted file mode 100644 index a788f8ad6..000000000 --- a/Aetheria.Rts.Web/scripts/verify-stage7c-local-runtime.ps1 +++ /dev/null @@ -1,195 +0,0 @@ -$ErrorActionPreference = "Stop" - -$root = Split-Path -Parent $PSScriptRoot -$repoRoot = Split-Path -Parent $root -Set-Location $root - -npm run build -if ($LASTEXITCODE -ne 0) { - Write-Error "Stage 7C verifier failed: RTS build failed." -} - -$tempRoot = Join-Path ([System.IO.Path]::GetTempPath()) ("aetheria-stage7c-" + [Guid]::NewGuid().ToString("N")) -New-Item -ItemType Directory -Path $tempRoot | Out-Null -$statePath = Join-Path $tempRoot "aetheria.cc" -$daemonOut = Join-Path $tempRoot "daemon.out.log" -$daemonErr = Join-Path $tempRoot "daemon.err.log" -$daemonProject = Join-Path $repoRoot "Aetheria.State.Daemon\Aetheria.State.Daemon.csproj" -$port = Get-Random -Minimum 41000 -Maximum 64000 -$endpoint = $null -$daemon = $null - -try { - $daemon = Start-Process -FilePath "dotnet" -ArgumentList @( - "run", - "--project", - $daemonProject, - "--", - "--state", - $statePath, - "--client-cultmesh-host", - "127.0.0.1", - "--client-cultmesh-port", - $port, - "--tick-interval-ms", - "20", - "--fixed-delta-ms", - "20", - "--api-publication-interval-ms", - "100000", - "--no-odin-announcements" - ) -RedirectStandardOutput $daemonOut -RedirectStandardError $daemonErr -WindowStyle Hidden -PassThru - - $deadline = (Get-Date).AddSeconds(40) - while ((Get-Date) -lt $deadline) { - if ($daemon.HasExited) { - Write-Error "Stage 7C verifier failed: daemon exited early ($($daemon.ExitCode)).`n$(if (Test-Path $daemonErr) { Get-Content $daemonErr -Raw })`n$(if (Test-Path $daemonOut) { Get-Content $daemonOut -Raw })" - } - - if ((Test-Path $daemonOut) -and ((Get-Content $daemonOut -Raw) -match "Aetheria client CultMesh endpoint: (rudp://127\.0\.0\.1:\d+)")) { - $endpoint = $Matches[1] - break - } - - Start-Sleep -Milliseconds 250 - } - - if (Test-Path $daemonOut) { - Get-Content $daemonOut | Out-Host - } - if ([string]::IsNullOrWhiteSpace($endpoint)) { - Write-Error "Stage 7C verifier failed: daemon did not report a client CultMesh endpoint.`n$(if (Test-Path $daemonErr) { Get-Content $daemonErr -Raw })`n$(if (Test-Path $daemonOut) { Get-Content $daemonOut -Raw })" - } - - $smokeScript = Join-Path $tempRoot "stage7c-smoke.mjs" - @' -import { pathToFileURL } from "node:url"; - -const statePath = process.env.AETHERIA_STAGE7C_STATE_PATH; -const endpoint = process.env.AETHERIA_STAGE7C_ENDPOINT; -const clientModulePath = process.env.AETHERIA_STAGE7C_CLIENT_MODULE; -if (!statePath || !endpoint) - throw new Error("Stage 7C verifier missing state path or endpoint."); -if (!clientModulePath) - throw new Error("Stage 7C verifier missing compiled client module path."); - -const { AetheriaCultMeshClient } = await import(pathToFileURL(clientModulePath).href); - -const client = new AetheriaCultMeshClient({ - uri: "cultmesh://aetheria/daemon/stage7c-aetheria-daemon", - peerId: "stage7c-daemon", - verseId: "aetheria.local", - role: "aetheria-daemon", - endpoints: [endpoint], -}, statePath, "stage7c-verifier"); -const viewport = await client.mapViewport({ minX: -5000, minY: -5000, maxX: 5000, maxY: 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 query did not produce a frame id."); -if (!Array.isArray(viewport.objects) || viewport.objects.length === 0) - throw new Error("Viewport query did not expose visible objects."); -if (objectsViewport.schema !== "gamecult.fields.objects.v1") - throw new Error(`Objects viewport used unexpected schema: ${objectsViewport.schema}`); -if (gravityViewport.schema !== "gamecult.fields.gravity.v1") - throw new Error(`Gravity viewport used unexpected schema: ${gravityViewport.schema}`); -if (!Array.isArray(objectsViewport.objects) || objectsViewport.objects.length !== viewport.objects.length) - throw new Error("Objects viewport did not match composed map object set."); -if (!Array.isArray(gravityViewport.gravityInfluences) || gravityViewport.gravityInfluences.length !== viewport.gravityInfluences.length) - throw new Error("Gravity viewport did not match composed map gravity set."); - -const selectedIndex = viewport.controlledEntityIndices[0] ?? viewport.objects[0]?.entityIndex; -if (!Number.isFinite(selectedIndex)) - 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 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 query did not return equipment and cargo arrays."); - -const health = await client.daemonHealth(); -if (health.status !== "healthy") - throw new Error(`Daemon health document was not healthy: ${health.status}`); - -const authority = await client.authorityStatus(); -if (authority.policyId !== "aetheria.trusted-coop.v1") - throw new Error(`Unexpected authority policy id: ${authority.policyId}`); - -const starbridge = await client.starbridgeSession(); -if (starbridge.schema !== "gamecult.aetheria.starbridge_session_summary.v1") - throw new Error(`Unexpected Starbridge schema: ${starbridge.schema}`); -if (starbridge.scenarioName !== "Frontier Fabricator Defense") - throw new Error(`Unexpected Starbridge scenario: ${starbridge.scenarioName}`); -if (!Array.isArray(starbridge.stationStock) || !starbridge.stationStock.some(item => item.itemKey === "repair-parts")) - throw new Error("Starbridge session did not expose daemon-seeded station stock."); -if (!Array.isArray(starbridge.waveForecast) || !starbridge.waveForecast.some(wave => wave.displayName === "Scout Probe")) - throw new Error("Starbridge session did not expose daemon-seeded wave forecast."); - -const surfaceCatalog = client.surfaceCatalogDiagnostics(); -if (surfaceCatalog.catalogId !== "gamecult.aetheria.surfaces.v1") - throw new Error(`Unexpected Aetheria surface catalog id: ${surfaceCatalog.catalogId}`); -if (!surfaceCatalog.surfaces.some(surface => surface.surfaceId === "gamecult.aetheria.viewport_feed.v1" && surface.kind === "live-feed")) - throw new Error("Aetheria surface catalog did not expose the viewport live feed."); -if (!surfaceCatalog.surfaces.some(surface => surface.surfaceId === "gamecult.aetheria.pilot.set_move_vector.v1" && surface.kind === "operation")) - throw new Error("RTS surface catalog did not expose the move operation."); -if (!surfaceCatalog.surfaces.some(surface => - surface.surfaceId === "daemon:aetheria.frame.latest.v1" && - surface.kind === "document" && - surface.routeHint?.kind === "shared-memory" && - surface.sources?.some(source => source.schemaId === "gamecult.aetheria.daemon_frame.v1"))) - throw new Error("RTS surface catalog did not expose the daemon frame document with shared route/source metadata."); - -const surfaceIndex = client.surfaceCatalogIndexDiagnostics(); -if (surfaceIndex.catalogId !== surfaceCatalog.catalogId) - throw new Error("RTS surface catalog index did not preserve the catalog id."); -if (!Array.isArray(surfaceIndex.operations) || surfaceIndex.operations.length < 2) - throw new Error("RTS surface catalog index did not group operation surfaces."); -if (!Array.isArray(surfaceIndex.liveFeeds) || !surfaceIndex.liveFeeds.some(surface => surface.surfaceId === "gamecult.aetheria.viewport_feed.v1")) - throw new Error("Aetheria surface catalog index did not group the viewport live feed."); -if (!Array.isArray(surfaceIndex.queries) || !surfaceIndex.queries.some(surface => surface.surfaceId === "gamecult.fields.objects.v1")) - throw new Error("RTS surface catalog index did not group generated query surfaces."); -if (!Array.isArray(surfaceIndex.documents) || !surfaceIndex.documents.some(surface => surface.surfaceId === "daemon:aetheria.frame.latest.v1")) - throw new Error("RTS surface catalog index did not group generated document surfaces."); - -console.log(JSON.stringify({ - frameId: viewport.frameId, - visibleObjects: viewport.objects.length, - gravityInfluences: gravityViewport.gravityInfluences.length, - selectedEntityIndex: selected.selected.entityIndex, - equipmentItems: inventory.equipment.length, - cargoItems: inventory.cargo.length, - health: health.status, - policyId: authority.policyId, - starbridgeScenario: starbridge.scenarioName, - starbridgeStock: starbridge.stationStock.length, - surfaceCatalog: { - surfaces: surfaceCatalog.surfaces.length, - operations: surfaceIndex.operations.length, - queries: surfaceIndex.queries.length, - liveFeeds: surfaceIndex.liveFeeds.length, - documents: surfaceIndex.documents.length - } -}, null, 2)); -await client.close(); -'@ | Set-Content -LiteralPath $smokeScript -Encoding UTF8 - - $env:AETHERIA_STAGE7C_STATE_PATH = $statePath - $env:AETHERIA_STAGE7C_ENDPOINT = $endpoint - $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 handles could not read daemon publications." - } -} -finally { - if ($daemon -and -not $daemon.HasExited) { - Stop-Process -Id $daemon.Id -Force - $daemon.WaitForExit() - } - Remove-Item -LiteralPath $tempRoot -Recurse -Force -ErrorAction SilentlyContinue -} - -Write-Host "Stage 7C local runtime verifier passed." diff --git a/Aetheria.Rts.Web/scripts/verify-stage7d-remote-cultmesh.ps1 b/Aetheria.Rts.Web/scripts/verify-stage7d-remote-cultmesh.ps1 deleted file mode 100644 index a2c85a334..000000000 --- a/Aetheria.Rts.Web/scripts/verify-stage7d-remote-cultmesh.ps1 +++ /dev/null @@ -1,145 +0,0 @@ -$ErrorActionPreference = "Stop" - -$root = Split-Path -Parent $PSScriptRoot -$repoRoot = Split-Path -Parent $root -Set-Location $root - -npm run build -if ($LASTEXITCODE -ne 0) { - Write-Error "Stage 7D remote CultMesh verifier failed: RTS build failed." -} - -$daemonProject = Join-Path $repoRoot "Aetheria.State.Daemon\Aetheria.State.Daemon.csproj" -dotnet build $daemonProject -v:minimal -if ($LASTEXITCODE -ne 0) { - Write-Error "Stage 7D remote CultMesh verifier failed: daemon build failed." -} - -$tempRoot = Join-Path ([System.IO.Path]::GetTempPath()) ("aetheria-stage7d-remote-" + [Guid]::NewGuid().ToString("N")) -New-Item -ItemType Directory -Path $tempRoot | Out-Null -$statePath = Join-Path $tempRoot "aetheria-raven.cc" -$daemonOut = Join-Path $tempRoot "daemon.out.log" -$daemonErr = Join-Path $tempRoot "daemon.err.log" -$port = Get-Random -Minimum 41000 -Maximum 64000 -$endpoint = "rudp://127.0.0.1:$port" -$daemon = $null - -try { - $daemon = Start-Process -FilePath "dotnet" -ArgumentList @( - "run", - "--no-build", - "--project", - $daemonProject, - "--", - "--state", - $statePath, - "--client-cultmesh-host", - "127.0.0.1", - "--client-cultmesh-port", - $port, - "--tick-interval-ms", - "20", - "--fixed-delta-ms", - "20", - "--api-publication-interval-ms", - "100000" - ) -RedirectStandardOutput $daemonOut -RedirectStandardError $daemonErr -WindowStyle Hidden -PassThru - - $deadline = (Get-Date).AddSeconds(40) - while ((Get-Date) -lt $deadline) { - if ($daemon.HasExited) { - Write-Error "Stage 7D remote CultMesh verifier failed: daemon exited early ($($daemon.ExitCode)).`n$(Get-Content $daemonErr -Raw)`n$(Get-Content $daemonOut -Raw)" - } - - if ((Test-Path $daemonOut) -and ((Get-Content $daemonOut -Raw) -match "Aetheria Verse daemon published frame")) { - break - } - - Start-Sleep -Milliseconds 250 - } - - if (-not (Test-Path $daemonOut) -or -not ((Get-Content $daemonOut -Raw) -match "Aetheria Verse daemon published frame")) { - Write-Error "Stage 7D remote CultMesh verifier failed: daemon did not publish a frame.`n$(if (Test-Path $daemonOut) { Get-Content $daemonOut -Raw })" - } - - $smokeScript = Join-Path $tempRoot "remote-smoke.mjs" - @' -import { pathToFileURL } from "node:url"; - -const endpoint = process.env.AETHERIA_STAGE7D_RESOLVED_RUDP_ENDPOINT; -const clientModulePath = process.env.AETHERIA_STAGE7D_REMOTE_CLIENT_MODULE; -if (!endpoint) - throw new Error("Stage 7D verifier missing resolved RUDP endpoint."); -if (!clientModulePath) - throw new Error("Stage 7D verifier missing compiled client module path."); - -const { AetheriaCultMeshClient } = await import(pathToFileURL(clientModulePath).href); -const client = new AetheriaCultMeshClient({ - uri: "cultmesh://aetheria/daemon/stage7d-aetheria-daemon", - peerId: "stage7d-daemon", - verseId: "aetheria.local", - role: "aetheria-daemon", - endpoints: [endpoint], -}, "unused-local-state.cc", "stage7d-verifier", { - publicationMode: "remote", - snapshotTimeoutMs: 5000, -}); - -try { - await client.waitForFrame(15000); - const viewport = await client.mapViewport({ minX: -5000, minY: -5000, maxX: 5000, maxY: 5000 }); - const actor = viewport.objects.find(object => object.controlled); - if (!actor) - throw new Error("Remote CultMesh viewport did not expose a controlled unit."); - - const health = await client.daemonHealth(); - const authority = await client.authorityStatus(); - const starbridge = await client.starbridgeSession(); - const receipt = await client.setMoveVector({ - actorEntityKey: actor.entityKey, - directionX: 1, - directionY: 0, - scalar: 0.1, - observedFrameId: viewport.frameId, - }); - - if (health.status !== "healthy") - throw new Error(`Unexpected daemon health ${health.status}.`); - if (authority.policyId !== "aetheria.trusted-coop.v1") - throw new Error(`Unexpected authority policy ${authority.policyId}.`); - if (starbridge.scenarioName !== "Frontier Fabricator Defense") - throw new Error(`Unexpected Starbridge scenario ${starbridge.scenarioName}.`); - if (!receipt.accepted) - throw new Error(`Remote setMoveVector was rejected: ${receipt.diagnostic ?? "no diagnostic"}`); - - console.log(JSON.stringify({ - endpoint, - frameId: viewport.frameId, - visibleObjects: viewport.objects.length, - health: health.status, - policyId: authority.policyId, - starbridgeScenario: starbridge.scenarioName, - operationId: receipt.operationId, - }, null, 2)); -} finally { - await client.close(); -} -'@ | Set-Content -LiteralPath $smokeScript -Encoding UTF8 - - $env:AETHERIA_STAGE7D_RESOLVED_RUDP_ENDPOINT = $endpoint - $env:AETHERIA_STAGE7D_REMOTE_CLIENT_MODULE = Join-Path $root "electron-dist\aetheria-cultmesh.js" - node $smokeScript - if ($LASTEXITCODE -ne 0) { - Write-Error "Stage 7D remote CultMesh verifier failed: remote RTS client could not read or command the daemon over CultMesh." - } -} -finally { - Remove-Item Env:\AETHERIA_STAGE7D_RESOLVED_RUDP_ENDPOINT -ErrorAction SilentlyContinue - Remove-Item Env:\AETHERIA_STAGE7D_REMOTE_CLIENT_MODULE -ErrorAction SilentlyContinue - if ($daemon -and -not $daemon.HasExited) { - Stop-Process -Id $daemon.Id -Force - } - Remove-Item -LiteralPath $tempRoot -Recurse -Force -ErrorAction SilentlyContinue -} - -Write-Host "Stage 7D remote CultMesh verifier passed." diff --git a/Aetheria.Rts.Web/tsconfig.electron.json b/Aetheria.Rts.Web/tsconfig.electron.json deleted file mode 100644 index 4b6ac93d0..000000000 --- a/Aetheria.Rts.Web/tsconfig.electron.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "compilerOptions": { - "target": "ES2022", - "module": "NodeNext", - "moduleResolution": "NodeNext", - "preserveSymlinks": true, - "skipLibCheck": true, - "strict": true, - "noEmitOnError": true, - "outDir": "electron-dist", - "rootDir": "Electron", - "types": [ - "node" - ] - }, - "include": [ - "Electron/**/*.ts" - ] -} diff --git a/Aetheria.Rts.Web/tsconfig.json b/Aetheria.Rts.Web/tsconfig.json deleted file mode 100644 index d6bd9c69c..000000000 --- a/Aetheria.Rts.Web/tsconfig.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "compilerOptions": { - "target": "ES2022", - "module": "ES2022", - "moduleResolution": "Bundler", - "strict": true, - "noEmitOnError": true, - "outDir": "wwwroot", - "rootDir": "Client" - }, - "include": [ - "Client/**/*.ts" - ] -} diff --git a/Aetheria.Rts.Web/wwwroot/index.html b/Aetheria.Rts.Web/wwwroot/index.html deleted file mode 100644 index e0fefe2df..000000000 --- a/Aetheria.Rts.Web/wwwroot/index.html +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - Aetheria Starbridge - - - - -
Connecting
- -
- - - -
-
- - - diff --git a/Aetheria.Rts.Web/wwwroot/styles.css b/Aetheria.Rts.Web/wwwroot/styles.css deleted file mode 100644 index 4789911ba..000000000 --- a/Aetheria.Rts.Web/wwwroot/styles.css +++ /dev/null @@ -1,90 +0,0 @@ -:root { - color-scheme: dark; - background: #020909; - color: #e8edf0; - font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; -} - -* { - box-sizing: border-box; -} - -html, -body { - width: 100%; - height: 100%; - margin: 0; -} - -body { - -webkit-app-region: drag; - overflow: hidden; - background: #020909; -} - -.eve-surface-host { - -webkit-app-region: no-drag; - display: block; - width: 100%; - min-height: 100%; -} - -.status { - position: fixed; - right: 112px; - bottom: 10px; - z-index: 30; - max-width: min(560px, calc(100vw - 140px)); - overflow: hidden; - color: rgba(219, 250, 255, 0.45); - font: 400 11px/1.2 "Ubuntu", system-ui, sans-serif; - text-overflow: ellipsis; - white-space: nowrap; - pointer-events: none; -} - -.window-drag-region { - -webkit-app-region: drag; - position: fixed; - top: 0; - right: 0; - left: 0; - z-index: 15; - height: 44px; -} - -.window-controls { - -webkit-app-region: no-drag; - position: fixed; - top: 8px; - right: 10px; - z-index: 20; - display: flex; - gap: 2px; -} - -.window-control { - min-width: 0; - width: 30px; - height: 24px; - padding: 0; - border: 0; - border-radius: 0; - background: transparent; - color: rgba(219, 250, 255, 0.5); - font-family: "Ubuntu", system-ui, sans-serif; - font-size: 12px; - line-height: 24px; -} - -.window-control:hover, -.window-control:focus-visible { - background: rgba(219, 250, 255, 0.09); - color: rgba(240, 252, 255, 0.95); - outline: 0; -} - -.window-control-close:hover, -.window-control-close:focus-visible { - background: rgba(255, 88, 88, 0.28); -} diff --git a/Aetheria.State.AuthoritySmoke/Aetheria.State.AuthoritySmoke.csproj b/Aetheria.State.AuthoritySmoke/Aetheria.State.AuthoritySmoke.csproj deleted file mode 100644 index 8c34ca0a4..000000000 --- a/Aetheria.State.AuthoritySmoke/Aetheria.State.AuthoritySmoke.csproj +++ /dev/null @@ -1,13 +0,0 @@ - - - Exe - net10.0 - enable - latest - enable - - - - - - diff --git a/Aetheria.State.AuthoritySmoke/Program.cs b/Aetheria.State.AuthoritySmoke/Program.cs deleted file mode 100644 index 036dab214..000000000 --- a/Aetheria.State.AuthoritySmoke/Program.cs +++ /dev/null @@ -1,2026 +0,0 @@ -using Aetheria.State; -using Aetheria.State.Daemon; -using Aetheria.State.Documents; -using GameCult.Aetheria.State.Verse; -using System.Diagnostics; -using System.Net; -using System.Net.Sockets; -using System.Text; - -var checks = new AuthoritySmokeChecks(); -await checks.RunAsync(); -Console.WriteLine("Authority smoke passed."); - -internal sealed class AuthoritySmokeChecks -{ - private static readonly object ProcessOutputLock = new(); - private static readonly Dictionary> ProcessOutput = new(); - - public async Task RunAsync() - { - DefaultTrustedPolicyAcceptsCommand(); - DelegatedRuntimeAcceptsOnlyListedRuntime(); - HostAuthoritativeAcceptsOnlyHostRuntime(); - InterestLeaseAcceptsOnlyActiveMatchingLease(); - UnsupportedAuthorityModesReject(); - AuthorizedCommandsReportsRejectedIds(); - PreRejectedCommandsEnterFrameReceipts(); - ClientTargetCarriesRuntimeIdentity(); - TwoLocalRuntimeDelegatedPolicyHarness(); - GameDocumentsBuildLocalViewportFromFrame(); - StarbridgeSessionSummaryProjectsScenarioFacts(); - await AetheriaClientStateDocumentsProjectAndSubmitAsync().ConfigureAwait(false); - await DaemonOncePublishesStarbridgeSessionFactsAsync().ConfigureAwait(false); - await SamePolicyDocumentCanBeLoadedByTwoNodesAsync().ConfigureAwait(false); - await TwoDaemonProcessesApplyDelegatedPolicyAsync().ConfigureAwait(false); - await ConcurrentDaemonsPublishRemoteFramesToLocalReplicasAsync().ConfigureAwait(false); - TrustedCommittedFactsConvergeLocalRuns(); - } - - private static void DefaultTrustedPolicyAcceptsCommand() - { - var command = Command("pilot-client", "entity:raven"); - var decision = AetheriaRuntimeAuthorityRouter.Authorize( - command, - policy: null, - leases: null, - localRuntimeId: "aetheria-daemon"); - - Require(decision.Authorized, "default trusted policy should accept trusted local commands"); - RequireEqual(AetheriaRuntimeAuthorityModes.AnyTrustedRuntime, decision.Mode, "default policy mode"); - RequireEqual("entity:raven", decision.SubjectKey, "default policy subject"); - RequireEqual(AetheriaRuntimeClaimKinds.Movement, decision.ClaimKind, "default policy claim"); - } - - private static void ClientTargetCarriesRuntimeIdentity() - { - var smokeId = Guid.NewGuid().ToString("N"); - var gameData = Directory.CreateDirectory(Path.Combine(Path.GetTempPath(), $"aetheria-client-target-{smokeId}")); - try - { - var state = AetheriaState.At(gameData); - var target = state.ClientTarget.Refresh(); - RequireEqual(AetheriaRuntimeStateBoundary.DefaultClientRuntimeId, target.RuntimeId, "client target default runtime id"); - - target = state.ClientTarget.RequestRuntimeId("raven-test-runtime"); - RequireEqual("raven-test-runtime", target.RuntimeId, "client target should persist requested runtime id"); - - var boot = AetheriaRuntimeStateBoot.Inspect(gameData); - RequireEqual("raven-test-runtime", boot.RuntimeId, "boot report should expose client target runtime id"); - - var previousRuntimeOverride = Environment.GetEnvironmentVariable(AetheriaRuntimeStateBoundary.RuntimeIdOverrideEnvironmentVariable); - try - { - Environment.SetEnvironmentVariable(AetheriaRuntimeStateBoundary.RuntimeIdOverrideEnvironmentVariable, "raven-env-runtime"); - boot = AetheriaRuntimeStateBoot.Inspect(gameData); - RequireEqual("raven-env-runtime", boot.RuntimeId, "generic runtime id override should win"); - } - finally - { - Environment.SetEnvironmentVariable(AetheriaRuntimeStateBoundary.RuntimeIdOverrideEnvironmentVariable, previousRuntimeOverride); - } - } - finally - { - try - { - gameData.Delete(recursive: true); - } - catch - { - } - } - } - - private static void DelegatedRuntimeAcceptsOnlyListedRuntime() - { - var policy = Policy( - AetheriaRuntimeAuthorityModes.AnyTrustedRuntime, - new AetheriaRuntimeAuthorityRule - { - RuleId = "raven.movement", - SubjectPrefix = "entity:raven", - ClaimKinds = [AetheriaRuntimeClaimKinds.Movement], - Mode = AetheriaRuntimeAuthorityModes.DelegatedRuntime, - RuntimeIds = ["pilot-client"], - Priority = 10 - }); - - var accepted = AetheriaRuntimeAuthorityRouter.Authorize( - Command("pilot-client", "entity:raven"), - policy, - leases: null, - localRuntimeId: "commander-client"); - var rejected = AetheriaRuntimeAuthorityRouter.Authorize( - Command("commander-client", "entity:raven"), - policy, - leases: null, - localRuntimeId: "commander-client"); - - Require(accepted.Authorized, "delegated runtime should accept listed runtime id"); - Require(!rejected.Authorized, "delegated runtime should reject unlisted runtime id"); - RequireEqual("delegated-runtime-required", rejected.Reason, "delegated rejection reason"); - } - - private static void HostAuthoritativeAcceptsOnlyHostRuntime() - { - var policy = Policy(AetheriaRuntimeAuthorityModes.HostAuthoritative); - - var accepted = AetheriaRuntimeAuthorityRouter.Authorize( - Command("host-daemon", "entity:raven"), - policy, - leases: null, - localRuntimeId: "host-daemon"); - var rejected = AetheriaRuntimeAuthorityRouter.Authorize( - Command("pilot-client", "entity:raven"), - policy, - leases: null, - localRuntimeId: "host-daemon"); - - Require(accepted.Authorized, "host-authoritative should accept host runtime"); - Require(!rejected.Authorized, "host-authoritative should reject non-host runtime"); - RequireEqual("host-authority-required", rejected.Reason, "host-authoritative rejection reason"); - } - - private static void InterestLeaseAcceptsOnlyActiveMatchingLease() - { - var now = DateTimeOffset.UtcNow; - var policy = Policy( - AetheriaRuntimeAuthorityModes.AnyTrustedRuntime, - new AetheriaRuntimeAuthorityRule - { - RuleId = "hostile.combat.lease", - SubjectPrefix = "entity:hostile", - ClaimKinds = [AetheriaRuntimeClaimKinds.Combat], - Mode = AetheriaRuntimeAuthorityModes.InterestLease, - LeaseScope = "combat", - Priority = 20 - }); - - var leases = new[] - { - new AetheriaRuntimeAuthorityLeaseDocument - { - LeaseId = "lease:commander:hostile", - RuntimeId = "commander-client", - SubjectPrefix = "entity:hostile", - ClaimKinds = [AetheriaRuntimeClaimKinds.Combat], - Scope = "combat", - ValidFromUtc = now.AddSeconds(-5).ToString("O"), - ExpiresAtUtc = now.AddSeconds(30).ToString("O") - } - }; - - var accepted = AetheriaRuntimeAuthorityRouter.Authorize( - Command("commander-client", "entity:hostile:001", AetheriaRuntimeDaemonCommandKinds.FireWeaponGroup), - policy, - leases, - localRuntimeId: "pilot-client"); - var rejected = AetheriaRuntimeAuthorityRouter.Authorize( - Command("pilot-client", "entity:hostile:001", AetheriaRuntimeDaemonCommandKinds.FireWeaponGroup), - policy, - leases, - localRuntimeId: "pilot-client"); - - Require(accepted.Authorized, "interest lease should accept active matching lease"); - Require(!rejected.Authorized, "interest lease should reject missing runtime lease"); - RequireEqual("authority-lease-required", rejected.Reason, "interest lease rejection reason"); - } - - private static void UnsupportedAuthorityModesReject() - { - var policy = Policy(AetheriaRuntimeAuthorityModes.WitnessQuorum); - var decision = AetheriaRuntimeAuthorityRouter.Authorize( - Command("pilot-client", "entity:raven"), - policy, - leases: null, - localRuntimeId: "pilot-client"); - - Require(!decision.Authorized, "witness quorum should be represented but fail closed"); - RequireEqual("authority-mode-not-implemented", decision.Reason, "unsupported mode rejection reason"); - } - - private static void AuthorizedCommandsReportsRejectedIds() - { - var policy = Policy(AetheriaRuntimeAuthorityModes.HostAuthoritative); - var host = Command("host-daemon", "entity:raven"); - var client = Command("pilot-client", "entity:raven"); - var rejectedIds = new List(); - - var accepted = AetheriaRuntimeAuthorityRouter.AuthorizedCommands( - [host, client], - policy, - leases: null, - localRuntimeId: "host-daemon", - rejectedCommandIds: rejectedIds); - - RequireEqual(1, accepted.Count, "accepted command count"); - RequireEqual(host.CommandId, accepted[0].CommandId, "accepted command id"); - RequireEqual(1, rejectedIds.Count, "rejected command count"); - RequireEqual(client.CommandId, rejectedIds[0], "rejected command id"); - } - - private static void PreRejectedCommandsEnterFrameReceipts() - { - var command = Command("pilot-client", "entity:raven"); - using var physics = new AetheriaYmirWorldPhysics(); - var result = AetheriaRuntimeDaemonTickRunner.Tick( - Path.Combine(Path.GetTempPath(), "aetheria-authority-smoke.cc"), - new AetheriaRuntimeRunCheckpointCommit(), - new AetheriaRuntimeDaemonTickOptions - { - DaemonId = "host-daemon", - SessionId = "authority-smoke", - FrameId = 42, - SimulationTimeSeconds = 1, - FixedDeltaSeconds = 0.02, - WorldPhysics = physics, - ObservedCommands = Array.Empty(), - PreRejectedCommandIds = [command.CommandId], - BuildPublications = false - }); - - Require(result.Frame.RejectedCommandIds.Contains(command.CommandId), "pre-rejected command should enter frame rejection receipts"); - Require(result.Frame.AccountedCommandIds.Contains(command.CommandId), "pre-rejected command should enter accounted receipts"); - } - - private static void TwoLocalRuntimeDelegatedPolicyHarness() - { - const string pilotRuntime = "pilot-client"; - const string commanderRuntime = "commander-client"; - var ravenKey = EntityKey("coop-smoke", 0, 0); - var hostileKey = EntityKey("coop-smoke", 0, 1); - var policy = CoopPolicy(); - - var ravenByRaven = MovementCommand(pilotRuntime, ravenKey, directionX: 1, directionY: 0, magnitude: 1); - var hostileByRaven = MovementCommand(pilotRuntime, hostileKey, directionX: 0, directionY: 1, magnitude: 1); - var ravenFrame = TickWithPolicy( - "pilot-local", - InitialCoopRun(), - policy, - [ravenByRaven, hostileByRaven]); - - Require(ravenFrame.AppliedCommandIds.Contains(ravenByRaven.CommandId), "raven node should apply Raven-authored Raven movement"); - Require(ravenFrame.RejectedCommandIds.Contains(hostileByRaven.CommandId), "raven node should reject Raven-authored hostile movement"); - Require(ravenFrame.AccountedCommandIds.Contains(ravenByRaven.CommandId), "raven node should account applied Raven command"); - Require(ravenFrame.AccountedCommandIds.Contains(hostileByRaven.CommandId), "raven node should account rejected hostile command"); - - var ravenByCommander = MovementCommand(commanderRuntime, ravenKey, directionX: -1, directionY: 0, magnitude: 1); - var hostileByCommander = MovementCommand(commanderRuntime, hostileKey, directionX: 0, directionY: -1, magnitude: 1); - var commanderFrame = TickWithPolicy( - "commander-local", - InitialCoopRun(), - policy, - [ravenByCommander, hostileByCommander]); - - Require(commanderFrame.RejectedCommandIds.Contains(ravenByCommander.CommandId), "commander node should reject Commander-authored Raven movement"); - Require(commanderFrame.AppliedCommandIds.Contains(hostileByCommander.CommandId), "commander node should apply Commander-authored hostile movement"); - Require(commanderFrame.AccountedCommandIds.Contains(ravenByCommander.CommandId), "commander node should account rejected Raven command"); - Require(commanderFrame.AccountedCommandIds.Contains(hostileByCommander.CommandId), "commander node should account applied hostile command"); - } - - private static void GameDocumentsBuildLocalViewportFromFrame() - { - var run = new AetheriaRuntimeRunCheckpointCommit - { - RunId = "game-documents-smoke", - CurrentZoneIndex = 0, - EntranceZoneIndex = 0, - ExitZoneIndex = 1, - DiscoveredZoneIndices = [0], - CurrentEntityKey = EntityKey("game-documents-smoke", 0, 0), - Zones = - [ - new AetheriaRuntimeZoneSnapshotCommit - { - ZoneIndex = 0, - Name = "Document Zone", - PositionX = 12, - PositionY = 34, - AdjacentZoneIndices = [1], - Entities = - [ - SnapshotEntity( - 0, - "Raven", - "player", - x: 0, - z: 0, - visibility: 80, - hull: 120, - cargoItem: "reactor-cell"), - SnapshotEntity( - 1, - "Commander", - "player", - x: 240, - z: 0, - visibility: 160, - hull: 140, - cargoItem: "sensor-pack"), - SnapshotEntity( - 2, - "Raider In Shared Sight", - "raider", - x: 360, - z: 0, - visibility: 60, - hull: 70, - cargoItem: "scrap"), - SnapshotEntity( - 3, - "Outside Viewport", - "raider", - x: 900, - z: 0, - visibility: 60, - hull: 70, - cargoItem: "scrap") - ], - Bodies = - [ - new AetheriaRuntimeBodySnapshotCommit - { - BodyKey = "body:near", - OrbitKey = "orbit:near", - Name = "Gravity Brush", - Kind = "planet", - GravityInfluenceCenterX = 390, - GravityInfluenceCenterZ = 0, - GravityInfluenceRadius = 75, - GravityWellDepth = 3, - GravityDepthExponent = 2, - GravityWaveRadius = 32, - GravityWaveDepth = 0.5, - GravityWaveSpeed = 1 - }, - new AetheriaRuntimeBodySnapshotCommit - { - BodyKey = "body:far", - OrbitKey = "orbit:far", - Name = "Too Far", - Kind = "planet", - GravityInfluenceCenterX = 1200, - GravityInfluenceCenterZ = 0, - GravityInfluenceRadius = 50 - } - ] - }, - new AetheriaRuntimeZoneSnapshotCommit - { - ZoneIndex = 1, - Name = "Document Neighbor", - PositionX = 72, - PositionY = 34, - AdjacentZoneIndices = [0], - OwnerFactionIndex = 2, - FactionIndices = [2] - } - ] - }; - var frame = AetheriaRuntimeDaemonFrameDocument.Create( - run, - "documents-daemon", - "documents-session", - 12, - 0.24, - 0.02); - - var viewportDocument = AetheriaRuntimeGameDocuments.Viewport( - frame, - new AetheriaRuntimeViewportBounds - { - MinX = 500, - MinY = -100, - MaxX = -50, - MaxY = 100 - }); - var objectsViewport = AetheriaRuntimeGameDocuments.ObjectsViewport( - frame, - viewportDocument.Viewport); - var gravityViewport = AetheriaRuntimeGameDocuments.GravityViewport( - frame, - viewportDocument.Viewport); - var currentZone = AetheriaRuntimeGameDocuments.CurrentZone(frame); - var sectorMap = AetheriaRuntimeGameDocuments.SectorMap(frame); - - RequireEqual(AetheriaRuntimeDaemonSchemas.GameViewport, 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, 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 = SnapshotEntity( - 4, - "Starbridge Base", - "player", - x: 0, - z: 0, - visibility: 500, - hull: 850, - cargoItem: "coolant-beam"); - baseEntity.Kind = "station"; - - var run = new AetheriaRuntimeRunCheckpointCommit - { - RunId = "starbridge-session-smoke", - CurrentZoneIndex = 0, - Zones = - [ - new AetheriaRuntimeZoneSnapshotCommit - { - ZoneIndex = 0, - Name = "Starbridge Defense", - Entities = - [ - baseEntity, - SnapshotEntity(5, "Pilot Raven", "player", 120, 0, 300, 140, "repair-pod") - ] - } - ] - }; - var frame = AetheriaRuntimeDaemonFrameDocument.Create( - run, - "starbridge-daemon", - "starbridge-smoke-session", - 19, - 0.38, - 0.02); - var scenario = new AetheriaRuntimeStarbridgeScenarioDocument - { - ScenarioId = "starbridge.frontier-fabricator", - DisplayName = "Frontier Fabricator Defense", - StartingBaseKey = EntityKey("starbridge-session-smoke", 0, 4), - StationStock = - [ - new AetheriaRuntimeStarbridgeStationStockItem - { - ItemKey = "coolant-beam", - Quantity = 2, - Quality = 0.75, - Durability = 1 - } - ], - Waves = - [ - new AetheriaRuntimeStarbridgeWaveDefinition - { - WaveIndex = 0, - DisplayName = "Scout Probe", - AttackerKeys = ["scout"], - BossKey = "scout-captain", - RecoveredTechnologyKeys = ["sensor-calibration"] - }, - new AetheriaRuntimeStarbridgeWaveDefinition - { - WaveIndex = 1, - DisplayName = "Bomber Line", - AttackerKeys = ["bomber", "skirmisher"], - BossKey = "bomber-frame", - RecoveredTechnologyKeys = ["missile-rack-burst"] - } - ], - RuntimeRoles = - [ - new AetheriaRuntimeStarbridgeRuntimeRole - { - RuntimeId = "commander-client", - Role = "commander" - }, - new AetheriaRuntimeStarbridgeRuntimeRole - { - RuntimeId = "pilot-client", - Role = "pilot", - EntityKey = EntityKey("starbridge-session-smoke", 0, 5) - } - ] - }; - var session = new AetheriaRuntimeStarbridgeSessionDocument - { - SessionId = "starbridge-smoke-session", - ScenarioId = scenario.ScenarioId, - RunId = run.RunId, - BaseEntityKey = EntityKey("starbridge-session-smoke", 0, 4), - StationEntityKey = EntityKey("starbridge-session-smoke", 0, 4), - Phase = "pre-wave", - CurrentWaveIndex = 1 - }; - - 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"); - RequireEqual("Frontier Fabricator Defense", summary.ScenarioName, "starbridge scenario name"); - RequireEqual("pre-wave", summary.Phase, "starbridge session phase"); - RequireEqual("Starbridge Base", summary.BaseStatus.DisplayName, "starbridge base status name"); - RequireEqual(850.0, summary.BaseStatus.Hull, "starbridge base hull"); - Require(summary.StationStock.Any(item => item.ItemKey == "coolant-beam"), "starbridge station stock should project scenario stock"); - Require(summary.WaveForecast.Length == 1 && summary.WaveForecast[0].DisplayName == "Bomber Line", "starbridge wave forecast should start at current wave"); - Require(summary.RuntimeRoles.Any(role => role.RuntimeId == "commander-client" && role.Role == "commander"), "starbridge runtime roles should project scenario roles"); - - var gameSurface = AetheriaRuntimeDaemonGameSurfaceBuilder.BuildCommander( - frame, - new AetheriaRuntimeDaemonHealthDocument - { - DaemonId = "starbridge-daemon", - VerseId = "aetheria.starbridge-smoke", - Status = "healthy" - }, - AetheriaRuntimeDaemonCommandBoundaryDocument.Create("starbridge-daemon"), - summary); - RequireSurfaceMetric(gameSurface, "aetheria.starbridge.commander.session.scenario", "Frontier Fabricator Defense", "commander surface starbridge scenario"); - RequireSurfaceMetric(gameSurface, "aetheria.starbridge.commander.session.base", "Starbridge Base", "commander surface starbridge base"); - RequireSurfaceMetric(gameSurface, "aetheria.daemon.game.starbridge.stock.0.item", "coolant-beam", "daemon game surface starbridge stock"); - RequireSurfaceMetric(gameSurface, "aetheria.daemon.game.starbridge.wave.0.name", "Bomber Line", "daemon game surface starbridge wave forecast"); - RequireSurfaceMetric(gameSurface, "aetheria.daemon.game.starbridge.role.0.runtime", "commander-client", "daemon game surface starbridge runtime role"); - } - - private static async Task AetheriaClientStateDocumentsProjectAndSubmitAsync() - { - var smokeId = Guid.NewGuid().ToString("N"); - var statePath = Path.Combine(Path.GetTempPath(), $"aetheria-client-state-documents-{smokeId}.cc"); - var frame = AetheriaRuntimeDaemonFrameDocument.Create( - new AetheriaRuntimeRunCheckpointCommit - { - RunId = "client-state-documents-smoke", - CurrentZoneIndex = 0, - CurrentEntityKey = EntityKey("client-state-documents-smoke", 0, 0), - Zones = - [ - new AetheriaRuntimeZoneSnapshotCommit - { - ZoneIndex = 0, - Name = "Client State Documents Zone", - Entities = - [ - SnapshotEntity(0, "Document Raven", "player", 0, 0, 600, 140, "document-cargo"), - SnapshotEntity(1, "Document Raider", "raider", 100, 0, 120, 80, "raider-cargo") - ] - } - ] - }, - "state-documents-daemon", - "state-documents-session", - 7, - 0.14, - 0.02); - var health = new AetheriaRuntimeDaemonHealthDocument - { - DaemonId = "state-documents-daemon", - VerseId = "aetheria.state-documents-smoke", - FrameId = frame.FrameId, - Status = "healthy", - Transport = "cultcache-witness" - }; - var policy = AetheriaRuntimeVerseAuthorityPolicyDocument.TrustedCoop( - "aetheria.state-documents-smoke", - "state-documents-daemon"); - var scenario = new AetheriaRuntimeStarbridgeScenarioDocument - { - ScenarioId = "starbridge.state-documents", - DisplayName = "Document Starbridge", - StartingBaseKey = EntityKey("client-state-documents-smoke", 0, 0), - StationStock = - [ - new AetheriaRuntimeStarbridgeStationStockItem - { - ItemKey = "document-cargo", - Quantity = 3, - Quality = 1, - Durability = 1 - } - ], - Waves = - [ - new AetheriaRuntimeStarbridgeWaveDefinition - { - WaveIndex = 0, - DisplayName = "Document Wave", - AttackerKeys = ["raider"], - BossKey = "document-boss" - } - ] - }; - var session = new AetheriaRuntimeStarbridgeSessionDocument - { - SessionId = "state-documents-session", - ScenarioId = "starbridge.state-documents", - BaseEntityKey = EntityKey("client-state-documents-smoke", 0, 0), - Phase = "pre-wave" - }; - - using (var writer = await AetheriaRuntimeVerseClient - .OpenAsync(statePath, "state-documents-writer", startServer: false, pullOnOpen: false) - .ConfigureAwait(false)) - { - await writer.Database - .PutAsync(AetheriaRuntimeVerseRecordKeys.DaemonFrameLatest, frame) - .ConfigureAwait(false); - await writer.Database - .PutAsync(AetheriaRuntimeVerseRecordKeys.DaemonHealth, health) - .ConfigureAwait(false); - await writer.MutableDocument(AetheriaRuntimeVerseRecordKeys.VerseAuthorityPolicy) - .ReplaceAsync(policy) - .ConfigureAwait(false); - await writer.MutableDocument(AetheriaRuntimeVerseRecordKeys.StarbridgeScenarioLatest) - .ReplaceAsync(scenario) - .ConfigureAwait(false); - await writer.MutableDocument(AetheriaRuntimeVerseRecordKeys.StarbridgeSessionLatest) - .ReplaceAsync(session) - .ConfigureAwait(false); - await writer.FlushAsync().ConfigureAwait(false); - } - - using var client = await AetheriaClient - .OpenAsync(statePath, "pilot-client", sessionId: "state-documents-session", pullOnOpen: true) - .ConfigureAwait(false); - var state = client.State; - var viewportBounds = new AetheriaRuntimeViewportBounds { MinX = -20, MinY = -20, MaxX = 150, MaxY = 20 }; - var viewport = await state - .GameViewport(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 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 state - .GravityViewport(viewportBounds) - .LatestAsync() - .ConfigureAwait(false); - RequireEqual(AetheriaRuntimeDaemonSchemas.GravityViewport, gravityViewport.Schema, "managed gravity viewport document schema"); - - 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 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 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 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 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 healthDocument = await state.Health.LatestAsync().ConfigureAwait(false); - Require(healthDocument?.Status == "healthy", "managed daemon health document should read local health"); - - 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"); - RequireEqual("pilot-client", command.ClientId, "managed client command client id"); - RequireEqual(frame.FrameId, command.ObservedFrameId, "managed client command observed frame id"); - - using var reader = await AetheriaRuntimeVerseClient - .OpenAsync(statePath, "state-documents-reader", startServer: false, pullOnOpen: true) - .ConfigureAwait(false); - var stored = await reader.Database - .GetAsync( - AetheriaRuntimeVerseRecordKeys.DaemonCommand(command.CommandId)) - .ConfigureAwait(false); - Require(stored != null, "managed client command should be stored as typed daemon command document"); - RequireEqual(AetheriaRuntimeClaimKinds.Movement, stored!.ClaimKind, "managed client command claim kind"); - } - - private static async Task DaemonOncePublishesStarbridgeSessionFactsAsync() - { - var smokeId = Guid.NewGuid().ToString("N"); - var statePath = Path.Combine(Path.GetTempPath(), $"aetheria-starbridge-daemon-{smokeId}.cc"); - var clientCultMeshPort = GetFreeUdpPort(); - const string runtimeId = "starbridge-daemon-smoke"; - - await SeedCanonicalCatalogAsync(statePath, runtimeId).ConfigureAwait(false); - await RunDaemonOnceAsync(statePath, runtimeId, clientCultMeshPort).ConfigureAwait(false); - - using var client = await AetheriaClient - .OpenAsync(statePath, "starbridge-smoke-client", sessionId: "authority-smoke", pullOnOpen: true) - .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"); - RequireEqual("Frontier Fabricator Defense", summary.ScenarioName, "daemon starbridge scenario name"); - RequireEqual("authority-smoke", summary.SessionId, "daemon starbridge session id"); - RequireEqual("pre-wave", summary.Phase, "daemon starbridge phase"); - RequireEqual("Anchor Station", summary.BaseStatus.DisplayName, "daemon starbridge base name"); - Require(summary.StationStock.Any(item => item.ItemKey == "repair-parts"), "daemon starbridge summary should expose station stock"); - Require(summary.WaveForecast.Any(wave => wave.DisplayName == "Scout Probe"), "daemon starbridge summary should expose wave forecast"); - Require(summary.RuntimeRoles.Any(role => role.RuntimeId == "commander-client" && role.Role == "commander"), "daemon starbridge summary should expose commander role"); - Require(summary.RuntimeRoles.Any(role => role.RuntimeId == "pilot-client" && role.Role == "pilot"), "daemon starbridge summary should expose pilot role"); - } - - private static async Task SamePolicyDocumentCanBeLoadedByTwoNodesAsync() - { - var smokeId = Guid.NewGuid().ToString("N"); - var ravenPath = Path.Combine(Path.GetTempPath(), $"aetheria-authority-raven-{smokeId}.cc"); - var commanderPath = Path.Combine(Path.GetTempPath(), $"aetheria-authority-commander-{smokeId}.cc"); - var policy = CoopPolicy(); - - await using var ravenNode = await AetheriaStateNode.OpenAsync( - ravenPath, - runtimeId: "pilot-local", - startServer: false, - enableDurableShardLogs: false).ConfigureAwait(false); - await using var commanderNode = await AetheriaStateNode.OpenAsync( - commanderPath, - runtimeId: "commander-local", - startServer: false, - enableDurableShardLogs: false).ConfigureAwait(false); - - await ravenNode.MutableDocument(AetheriaRuntimeVerseRecordKeys.VerseAuthorityPolicy) - .ReplaceAsync(policy) - .ConfigureAwait(false); - await commanderNode.MutableDocument(AetheriaRuntimeVerseRecordKeys.VerseAuthorityPolicy) - .ReplaceAsync(policy) - .ConfigureAwait(false); - - var ravenPolicy = await ravenNode.MutableDocument(AetheriaRuntimeVerseRecordKeys.VerseAuthorityPolicy).ReadAsync().ConfigureAwait(false); - var commanderPolicy = await commanderNode.MutableDocument(AetheriaRuntimeVerseRecordKeys.VerseAuthorityPolicy).ReadAsync().ConfigureAwait(false); - - Require(ravenPolicy != null, "raven node should load authority policy"); - Require(commanderPolicy != null, "commander node should load authority policy"); - RequireEqual(policy.PolicyId, ravenPolicy!.PolicyId, "raven loaded policy id"); - RequireEqual(policy.PolicyId, commanderPolicy!.PolicyId, "commander loaded policy id"); - RequireEqual(policy.Rules.Length, ravenPolicy.Rules.Length, "raven loaded rule count"); - RequireEqual(policy.Rules.Length, commanderPolicy.Rules.Length, "commander loaded rule count"); - } - - private static async Task TwoDaemonProcessesApplyDelegatedPolicyAsync() - { - var smokeId = Guid.NewGuid().ToString("N"); - var policy = CoopPolicy(); - var ravenKey = EntityKey("coop-smoke", 0, 0); - var hostileKey = EntityKey("coop-smoke", 0, 1); - - var ravenByRaven = MovementCommand("pilot-client", ravenKey, directionX: 1, directionY: 0, magnitude: 1); - var hostileByRaven = MovementCommand("pilot-client", hostileKey, directionX: 0, directionY: 1, magnitude: 1); - var ravenStatePath = Path.Combine(Path.GetTempPath(), $"aetheria-authority-raven-daemon-{smokeId}.cc"); - await SeedDaemonStateAsync( - ravenStatePath, - "pilot-local", - policy, - [ravenByRaven, hostileByRaven]).ConfigureAwait(false); - await RunDaemonOnceAsync(ravenStatePath, "pilot-local", clientCultMeshPort: 41076).ConfigureAwait(false); - var ravenFrame = await ReadPublishedFrameAsync(ravenStatePath, "pilot-local").ConfigureAwait(false); - - Require(ravenFrame.AppliedCommandIds.Contains(ravenByRaven.CommandId), "raven daemon should apply Raven-authored Raven movement"); - Require(ravenFrame.RejectedCommandIds.Contains(hostileByRaven.CommandId), "raven daemon should reject Raven-authored hostile movement"); - Require(ravenFrame.AccountedCommandIds.Contains(ravenByRaven.CommandId), "raven daemon should account applied command"); - Require(ravenFrame.AccountedCommandIds.Contains(hostileByRaven.CommandId), "raven daemon should account rejected command"); - - var ravenByCommander = MovementCommand("commander-client", ravenKey, directionX: -1, directionY: 0, magnitude: 1); - var hostileByCommander = MovementCommand("commander-client", hostileKey, directionX: 0, directionY: -1, magnitude: 1); - var commanderStatePath = Path.Combine(Path.GetTempPath(), $"aetheria-authority-commander-daemon-{smokeId}.cc"); - await SeedDaemonStateAsync( - commanderStatePath, - "commander-local", - policy, - [ravenByCommander, hostileByCommander]).ConfigureAwait(false); - await RunDaemonOnceAsync(commanderStatePath, "commander-local", clientCultMeshPort: 41077).ConfigureAwait(false); - var commanderFrame = await ReadPublishedFrameAsync(commanderStatePath, "commander-local").ConfigureAwait(false); - - Require(commanderFrame.RejectedCommandIds.Contains(ravenByCommander.CommandId), "commander daemon should reject Commander-authored Raven movement"); - Require(commanderFrame.AppliedCommandIds.Contains(hostileByCommander.CommandId), "commander daemon should apply Commander-authored hostile movement"); - Require(commanderFrame.AccountedCommandIds.Contains(ravenByCommander.CommandId), "commander daemon should account rejected command"); - Require(commanderFrame.AccountedCommandIds.Contains(hostileByCommander.CommandId), "commander daemon should account applied command"); - } - - private static async Task ConcurrentDaemonsPublishRemoteFramesToLocalReplicasAsync() - { - var smokeId = Guid.NewGuid().ToString("N"); - var policy = CoopPolicy(); - var ravenKey = EntityKey("coop-smoke", 0, 0); - var hostileKey = EntityKey("coop-smoke", 0, 1); - var ravenStatePath = Path.Combine(Path.GetTempPath(), $"aetheria-authority-raven-live-{smokeId}.cc"); - var commanderStatePath = Path.Combine(Path.GetTempPath(), $"aetheria-authority-commander-live-{smokeId}.cc"); - var ravenReplicaPath = Path.Combine(Path.GetTempPath(), $"aetheria-authority-raven-replica-{smokeId}.cc"); - var commanderReplicaPath = Path.Combine(Path.GetTempPath(), $"aetheria-authority-commander-replica-{smokeId}.cc"); - var ravenPort = GetFreeUdpPort(); - var commanderPort = GetFreeUdpPort(); - - var ravenByRaven = MovementCommand("pilot-client", ravenKey, directionX: 1, directionY: 0, magnitude: 1); - var hostileByRaven = MovementCommand("pilot-client", hostileKey, directionX: 0, directionY: 1, magnitude: 1); - await SeedDaemonStateAsync( - ravenStatePath, - "pilot-local", - policy, - [ravenByRaven, hostileByRaven]).ConfigureAwait(false); - - var ravenByCommander = MovementCommand("commander-client", ravenKey, directionX: -1, directionY: 0, magnitude: 1); - var hostileByCommander = MovementCommand("commander-client", hostileKey, directionX: 0, directionY: -1, magnitude: 1); - await SeedDaemonStateAsync( - commanderStatePath, - "commander-local", - policy, - [ravenByCommander, hostileByCommander]).ConfigureAwait(false); - - using var ravenDaemon = StartDaemonProcess( - ravenStatePath, - "pilot-local", - ravenPort, - once: false, - peerEndpoints: [$"rudp://127.0.0.1:{commanderPort}"]); - using var commanderDaemon = StartDaemonProcess( - commanderStatePath, - "commander-local", - commanderPort, - once: false, - peerEndpoints: [$"rudp://127.0.0.1:{ravenPort}"]); - try - { - var ravenRemoteFrame = await WaitForReplicaFrameAsync( - $"rudp://127.0.0.1:{ravenPort}", - commanderReplicaPath, - "commander-observes-raven", - [ravenDaemon, commanderDaemon], - frame => - string.Equals(frame.DaemonId, "pilot-local", StringComparison.Ordinal) && - frame.CumulativeAppliedCommandIds.Contains(ravenByRaven.CommandId) && - frame.CumulativeRejectedCommandIds.Contains(hostileByRaven.CommandId), - "Raven frame with Raven movement applied and hostile movement rejected").ConfigureAwait(false); - var commanderRemoteFrame = await WaitForReplicaFrameAsync( - $"rudp://127.0.0.1:{commanderPort}", - ravenReplicaPath, - "raven-observes-commander", - [ravenDaemon, commanderDaemon], - frame => - string.Equals(frame.DaemonId, "commander-local", StringComparison.Ordinal) && - frame.CumulativeAppliedCommandIds.Contains(hostileByCommander.CommandId) && - frame.CumulativeRejectedCommandIds.Contains(ravenByCommander.CommandId), - "Commander frame with hostile movement applied and Raven movement rejected").ConfigureAwait(false); - - RequireEqual("pilot-local", ravenRemoteFrame.DaemonId, "Commander local replica should observe Raven daemon id"); - Require(ravenRemoteFrame.CumulativeAppliedCommandIds.Contains(ravenByRaven.CommandId), "Commander local replica should observe Raven-applied Raven command"); - Require(ravenRemoteFrame.CumulativeRejectedCommandIds.Contains(hostileByRaven.CommandId), "Commander local replica should observe Raven rejection receipt"); - var commanderObservedRavenFacts = await WaitForReplicaFactsAsync( - $"rudp://127.0.0.1:{ravenPort}", - commanderReplicaPath, - "commander-observes-raven-facts", - [ravenDaemon, commanderDaemon], - facts => - facts.Any(fact => - string.Equals(fact.SourceDaemonId, "pilot-local", StringComparison.Ordinal) && - string.Equals(fact.CommandId, ravenByRaven.CommandId, StringComparison.Ordinal) && - string.Equals(fact.Outcome, AetheriaRuntimeCommandFactOutcomes.Applied, StringComparison.Ordinal)) && - facts.Any(fact => - string.Equals(fact.SourceDaemonId, "pilot-local", StringComparison.Ordinal) && - string.Equals(fact.CommandId, hostileByRaven.CommandId, StringComparison.Ordinal) && - string.Equals(fact.Outcome, AetheriaRuntimeCommandFactOutcomes.Rejected, StringComparison.Ordinal)), - "Raven applied and rejected committed facts").ConfigureAwait(false); - Require( - commanderObservedRavenFacts.Any(fact => - string.Equals(fact.SourceDaemonId, "pilot-local", StringComparison.Ordinal) && - string.Equals(fact.CommandId, ravenByRaven.CommandId, StringComparison.Ordinal) && - string.Equals(fact.Outcome, AetheriaRuntimeCommandFactOutcomes.Applied, StringComparison.Ordinal)), - "Commander local replica should observe Raven committed movement fact"); - Require( - commanderObservedRavenFacts.Any(fact => - string.Equals(fact.SourceDaemonId, "pilot-local", StringComparison.Ordinal) && - string.Equals(fact.CommandId, hostileByRaven.CommandId, StringComparison.Ordinal) && - string.Equals(fact.Outcome, AetheriaRuntimeCommandFactOutcomes.Rejected, StringComparison.Ordinal)), - "Commander local replica should observe Raven policy rejection fact"); - - RequireEqual("commander-local", commanderRemoteFrame.DaemonId, "Raven local replica should observe Commander daemon id"); - Require(commanderRemoteFrame.CumulativeAppliedCommandIds.Contains(hostileByCommander.CommandId), "Raven local replica should observe Commander-applied hostile command"); - Require(commanderRemoteFrame.CumulativeRejectedCommandIds.Contains(ravenByCommander.CommandId), "Raven local replica should observe Commander rejection receipt"); - var ravenObservedCommanderFacts = await WaitForReplicaFactsAsync( - $"rudp://127.0.0.1:{commanderPort}", - ravenReplicaPath, - "raven-observes-commander-facts", - [ravenDaemon, commanderDaemon], - facts => - facts.Any(fact => - string.Equals(fact.SourceDaemonId, "commander-local", StringComparison.Ordinal) && - string.Equals(fact.CommandId, hostileByCommander.CommandId, StringComparison.Ordinal) && - string.Equals(fact.Outcome, AetheriaRuntimeCommandFactOutcomes.Applied, StringComparison.Ordinal)) && - facts.Any(fact => - string.Equals(fact.SourceDaemonId, "commander-local", StringComparison.Ordinal) && - string.Equals(fact.CommandId, ravenByCommander.CommandId, StringComparison.Ordinal) && - string.Equals(fact.Outcome, AetheriaRuntimeCommandFactOutcomes.Rejected, StringComparison.Ordinal)), - "Commander applied and rejected committed facts").ConfigureAwait(false); - Require( - ravenObservedCommanderFacts.Any(fact => - string.Equals(fact.SourceDaemonId, "commander-local", StringComparison.Ordinal) && - string.Equals(fact.CommandId, hostileByCommander.CommandId, StringComparison.Ordinal) && - string.Equals(fact.Outcome, AetheriaRuntimeCommandFactOutcomes.Applied, StringComparison.Ordinal)), - "Raven local replica should observe Commander committed hostile fact"); - Require( - ravenObservedCommanderFacts.Any(fact => - string.Equals(fact.SourceDaemonId, "commander-local", StringComparison.Ordinal) && - string.Equals(fact.CommandId, ravenByCommander.CommandId, StringComparison.Ordinal) && - string.Equals(fact.Outcome, AetheriaRuntimeCommandFactOutcomes.Rejected, StringComparison.Ordinal)), - "Raven local replica should observe Commander policy rejection fact"); - - var ravenMovementFactId = commanderObservedRavenFacts.First(fact => - string.Equals(fact.SourceDaemonId, "pilot-local", StringComparison.Ordinal) && - string.Equals(fact.CommandId, ravenByRaven.CommandId, StringComparison.Ordinal) && - string.Equals(fact.Outcome, AetheriaRuntimeCommandFactOutcomes.Applied, StringComparison.Ordinal)).FactId; - var commanderHostileFactId = ravenObservedCommanderFacts.First(fact => - string.Equals(fact.SourceDaemonId, "commander-local", StringComparison.Ordinal) && - string.Equals(fact.CommandId, hostileByCommander.CommandId, StringComparison.Ordinal) && - string.Equals(fact.Outcome, AetheriaRuntimeCommandFactOutcomes.Applied, StringComparison.Ordinal)).FactId; - - var commanderConvergedFrame = await WaitForLocalFrameAsync( - commanderStatePath, - "commander-local-converges-raven", - [ravenDaemon, commanderDaemon], - frame => - frame.CumulativeImportedFactIds.Contains(ravenMovementFactId) && - Entity(frame.Run, ravenKey).PositionX > 0, - "Commander live daemon imports Raven movement fact and moves Raven locally").ConfigureAwait(false); - Require( - commanderConvergedFrame.CumulativeImportedFactIds.Any(), - "Commander live daemon should record imported remote fact ids"); - - var ravenConvergedFrame = await WaitForLocalFrameAsync( - ravenStatePath, - "pilot-local-converges-commander", - [ravenDaemon, commanderDaemon], - frame => - frame.CumulativeImportedFactIds.Contains(commanderHostileFactId) && - Entity(frame.Run, hostileKey).PositionZ < 0, - "Raven live daemon imports Commander hostile movement fact and moves hostile locally").ConfigureAwait(false); - Require( - ravenConvergedFrame.CumulativeImportedFactIds.Any(), - "Raven live daemon should record imported remote fact ids"); - } - finally - { - StopProcess(ravenDaemon); - StopProcess(commanderDaemon); - } - } - - private static async Task> ReadFactsAsync( - string statePath, - string runtimeId) - { - await using var node = await AetheriaStateNode.OpenAsync( - statePath, - runtimeId, - startServer: false, - enableDurableShardLogs: false).ConfigureAwait(false); - return node.Documents() - .OrderBy(fact => fact.CommittedAtUtc ?? "", StringComparer.Ordinal) - .ThenBy(fact => fact.FactId ?? "", StringComparer.Ordinal) - .ToArray(); - } - - private static void TrustedCommittedFactsConvergeLocalRuns() - { - using var physics = new AetheriaYmirWorldPhysics(); - var smokeId = Guid.NewGuid().ToString("N"); - var policy = CoopPolicyWithMetadata(); - var ravenKey = EntityKey("coop-smoke", 0, 0); - var hostileKey = EntityKey("coop-smoke", 0, 1); - - var ravenMovement = MovementCommand("pilot-client", ravenKey, directionX: 1, directionY: 0, magnitude: 1); - var ravenSourceFrame = TickWithPolicy( - "pilot-local", - InitialCoopFactRun(), - policy, - [ravenMovement]); - var ravenMovementFact = AetheriaRuntimeCommittedCommandFactDocument.FromAppliedCommand( - ravenSourceFrame, - ravenMovement, - policy.VerseId); - - var commanderImport = AetheriaRuntimeCommittedFactImporter.ImportIntoFrame( - Path.Combine(Path.GetTempPath(), $"aetheria-authority-facts-commander-{smokeId}.cc"), - AetheriaRuntimeDaemonFrameDocument.Create( - InitialCoopFactRun(), - "commander-local", - "authority-smoke", - frameId: 0, - simulationTimeSeconds: 0, - fixedDeltaSeconds: 0.02), - [ravenMovementFact], - policy, - leases: null, - localRuntimeId: "commander-local", - daemonId: "commander-local", - sessionId: "authority-smoke", - verseId: policy.VerseId, - worldPhysics: physics); - - Require(commanderImport.AcceptedFactIds.Contains(ravenMovementFact.FactId), "Commander should accept Raven's committed Raven movement fact"); - Require(!commanderImport.RejectedFactIds.Contains(ravenMovementFact.FactId), "Commander should not reject Raven's authorized movement fact"); - Require(Entity(commanderImport.Run, ravenKey).PositionX > 0, "Commander local run should converge Raven movement into local state"); - - var hostileRename = MetadataNameCommand("commander-client", hostileKey, "Commander Marked Hostile"); - var commanderSourceFrame = TickWithPolicy( - "commander-local", - InitialCoopFactRun(), - policy, - [hostileRename]); - var hostileRenameFact = AetheriaRuntimeCommittedCommandFactDocument.FromAppliedCommand( - commanderSourceFrame, - hostileRename, - policy.VerseId); - - var ravenImport = AetheriaRuntimeCommittedFactImporter.ImportIntoFrame( - Path.Combine(Path.GetTempPath(), $"aetheria-authority-facts-raven-{smokeId}.cc"), - AetheriaRuntimeDaemonFrameDocument.Create( - InitialCoopFactRun(), - "pilot-local", - "authority-smoke", - frameId: 0, - simulationTimeSeconds: 0, - fixedDeltaSeconds: 0.02), - [hostileRenameFact], - policy, - leases: null, - localRuntimeId: "pilot-local", - daemonId: "pilot-local", - sessionId: "authority-smoke", - verseId: policy.VerseId, - worldPhysics: physics); - - Require(ravenImport.AcceptedFactIds.Contains(hostileRenameFact.FactId), "Raven should accept Commander's hostile metadata fact"); - RequireEqual("Commander Marked Hostile", Entity(ravenImport.Run, hostileKey).Name, "Raven local run should converge Commander hostile metadata into local state"); - - var unauthorizedRename = MetadataNameCommand("pilot-client", hostileKey, "Raven Should Not Own This"); - var unauthorizedFact = AetheriaRuntimeCommittedCommandFactDocument.FromAppliedCommand( - ravenSourceFrame, - unauthorizedRename, - policy.VerseId); - var rejectedImport = AetheriaRuntimeCommittedFactImporter.ImportIntoFrame( - Path.Combine(Path.GetTempPath(), $"aetheria-authority-facts-rejected-{smokeId}.cc"), - AetheriaRuntimeDaemonFrameDocument.Create( - InitialCoopFactRun(), - "commander-local", - "authority-smoke", - frameId: 0, - simulationTimeSeconds: 0, - fixedDeltaSeconds: 0.02), - [unauthorizedFact], - policy, - leases: null, - localRuntimeId: "commander-local", - daemonId: "commander-local", - sessionId: "authority-smoke", - verseId: policy.VerseId, - worldPhysics: physics); - - Require(rejectedImport.RejectedFactIds.Contains(unauthorizedFact.FactId), "Commander should reject Raven-authored hostile metadata fact"); - RequireEqual("Commander Hostile", Entity(rejectedImport.Run, hostileKey).Name, "Rejected facts should not mutate local state"); - } - - private static async Task SeedDaemonStateAsync( - string statePath, - string runtimeId, - AetheriaRuntimeVerseAuthorityPolicyDocument policy, - IReadOnlyList commands) - { - await SeedCanonicalCatalogAsync(statePath, runtimeId).ConfigureAwait(false); - await using var node = await AetheriaStateNode.OpenAsync( - statePath, - runtimeId, - startServer: false, - enableDurableShardLogs: false).ConfigureAwait(false); - - await node.MutableDocument(AetheriaRuntimeVerseRecordKeys.VerseAuthorityPolicy) - .ReplaceAsync(policy) - .ConfigureAwait(false); - foreach (var command in commands) - await node.SubmitDaemonCommandAsync(command).ConfigureAwait(false); - await node.FlushAsync().ConfigureAwait(false); - - await node.MutableDocument(AetheriaRuntimeVerseRecordKeys.DaemonFrameLatest) - .ReplaceAsync(AetheriaRuntimeDaemonFrameDocument.Create( - InitialCoopRun(), - runtimeId, - "authority-smoke", - frameId: -1, - simulationTimeSeconds: 0, - fixedDeltaSeconds: 0.02)) - .ConfigureAwait(false); - await node.FlushAsync().ConfigureAwait(false); - } - - private static async Task SeedCanonicalCatalogAsync(string statePath, string runtimeId) - { - await using var node = await AetheriaStateNode.OpenAsync( - statePath, - runtimeId, - startServer: false, - enableDurableShardLogs: false).ConfigureAwait(false); - - const string manufacturerLegacyId = "authority-smoke-player"; - foreach (var corporationLegacyId in new[] - { - manufacturerLegacyId, - "authority-smoke-raider", - "authority-smoke-neutral" - }) - { - await node.MutableDocument( - AetheriaCatalogKeys.CorporationFromLegacyId(corporationLegacyId)) - .ReplaceAsync(new AetheriaCorporation - { - Name = corporationLegacyId, - LegacyId = corporationLegacyId, - ShortName = corporationLegacyId, - InfluenceDistance = 1, - AllegianceCount = 1, - Allegiances = - [ - new AetheriaCorporationAllegiance - { - CorporationLegacyId = manufacturerLegacyId, - Weight = 1 - } - ] - }).ConfigureAwait(false); - } - - foreach (var item in AuthoritySmokeLoadoutItems(manufacturerLegacyId)) - { - await node.MutableDocument( - AetheriaCatalogKeys.ItemDefinitionFromLegacyId(item.LegacyId)) - .ReplaceAsync(item).ConfigureAwait(false); - } - var baseEntityKey = AetheriaRuntimeRunCheckpointCommit.EntityRecordKey("local-terminus", 0, 0); - const string scenarioId = "starbridge.frontier-fabricator"; - await node.MutableDocument( - AetheriaRuntimeVerseRecordKeys.StarbridgeScenarioLatest) - .ReplaceAsync(new AetheriaRuntimeStarbridgeScenarioDocument - { - ScenarioId = scenarioId, - DisplayName = "Frontier Fabricator Defense", - StartingBaseKey = baseEntityKey, - StationStock = - [ - new AetheriaRuntimeStarbridgeStationStockItem - { - ItemKey = "repair-parts", - Quantity = 4, - Quality = 1, - Durability = 1 - } - ], - Waves = - [ - new AetheriaRuntimeStarbridgeWaveDefinition - { - WaveIndex = 0, - DisplayName = "Scout Probe", - AttackerKeys = ["scout"] - } - ], - RuntimeRoles = - [ - new AetheriaRuntimeStarbridgeRuntimeRole { RuntimeId = "commander-client", Role = "commander" }, - new AetheriaRuntimeStarbridgeRuntimeRole - { - RuntimeId = "pilot-client", - Role = "pilot", - EntityKey = AetheriaRuntimeRunCheckpointCommit.EntityRecordKey("local-terminus", 0, 1) - } - ] - }).ConfigureAwait(false); - await node.MutableDocument( - AetheriaRuntimeVerseRecordKeys.StarbridgeSessionLatest) - .ReplaceAsync(new AetheriaRuntimeStarbridgeSessionDocument - { - SessionId = "authority-smoke", - ScenarioId = scenarioId, - RunId = "local-terminus", - BaseEntityKey = baseEntityKey, - StationEntityKey = baseEntityKey, - Phase = "pre-wave" - }).ConfigureAwait(false); - await node.FlushAsync().ConfigureAwait(false); - await node.RefreshRuntimeCatalogAsync().ConfigureAwait(false); - - var catalog = await node.RuntimeCatalog().LatestAsync().ConfigureAwait(false); - Require(catalog.Corporations.Any(corporation => !string.IsNullOrWhiteSpace(corporation.CorporationKey)), - "daemon fixture must contain a typed corporation key"); - Require(catalog.Items.Any(item => item.HullType == "Ship") && - catalog.Items.Any(item => item.HullType == "Station"), - "daemon fixture must contain typed ship and station hulls"); - } - - private static IReadOnlyList AuthoritySmokeLoadoutItems(string manufacturerLegacyId) - { - static AetheriaShapeCell[] Rectangle(int width, int height) => - Enumerable.Range(0, height) - .SelectMany(y => Enumerable.Range(0, width).Select(x => new AetheriaShapeCell { X = x, Y = y })) - .ToArray(); - - static AetheriaBehaviorPayload Behavior(string kind) => new() - { - Kind = kind, - BehaviorId = "authority-smoke." + kind.ToLowerInvariant() - }; - - AetheriaItemDefinition Item( - string legacyId, - string category, - string hardpointType, - string behaviorKind = "") => new() - { - Name = legacyId, - LegacyId = legacyId, - Category = category, - ManufacturerLegacyId = manufacturerLegacyId, - Price = 10, - Mass = 1, - Volume = 1, - ShapeWidth = 1, - ShapeHeight = 1, - OccupiedCells = 1, - ShapeCells = Rectangle(1, 1), - HardpointType = hardpointType, - BehaviorKinds = string.IsNullOrWhiteSpace(behaviorKind) ? [] : [behaviorKind], - BehaviorCount = string.IsNullOrWhiteSpace(behaviorKind) ? 0 : 1, - BehaviorPayloads = string.IsNullOrWhiteSpace(behaviorKind) ? [] : [Behavior(behaviorKind)] - }; - - AetheriaItemDefinition Hull(string legacyId, string hullType) - { - var hull = Item(legacyId, AetheriaRuntimeItemCategories.Hull, "Hull"); - hull.HullType = hullType; - hull.ShapeWidth = 6; - hull.ShapeHeight = 6; - hull.OccupiedCells = 36; - hull.ShapeCells = Rectangle(6, 6); - hull.Hardpoints = - [ - new AetheriaItemHardpoint - { - Type = "ControlModule", - ShapeWidth = 1, - ShapeHeight = 1, - OccupiedCells = 1, - ShapeCells = Rectangle(1, 1) - } - ]; - return hull; - } - - var cargo = Item("authority-smoke-cargo", AetheriaRuntimeItemCategories.CargoBay, "Internal"); - cargo.InteriorShapeWidth = 3; - cargo.InteriorShapeHeight = 3; - cargo.InteriorOccupiedCells = 9; - cargo.InteriorShapeCells = Rectangle(3, 3); - - return - [ - Hull("authority-smoke-ship", "Ship"), - Hull("authority-smoke-station", "Station"), - Item("authority-smoke-cockpit", AetheriaRuntimeItemCategories.Gear, "ControlModule", "Cockpit"), - Item("authority-smoke-turret-controller", AetheriaRuntimeItemCategories.Gear, "ControlModule", "TurretController"), - cargo, - Item("authority-smoke-docking", AetheriaRuntimeItemCategories.DockingBay, "Internal"), - Item("authority-smoke-reactor", AetheriaRuntimeItemCategories.Gear, "Internal", "Reactor"), - Item("authority-smoke-capacitor", AetheriaRuntimeItemCategories.Gear, "Internal", "Capacitor") - ]; - } - - private static async Task RunDaemonOnceAsync( - string statePath, - string runtimeId, - int clientCultMeshPort) - { - using var process = StartDaemonProcess(statePath, runtimeId, clientCultMeshPort, once: true); - await WaitForProcessSuccessAsync(process, runtimeId, TimeSpan.FromSeconds(90)).ConfigureAwait(false); - } - - private static Process StartDaemonProcess( - string statePath, - string runtimeId, - int clientCultMeshPort, - bool once) - { - return StartDaemonProcess(statePath, runtimeId, clientCultMeshPort, once, peerEndpoints: null); - } - - private static Process StartDaemonProcess( - string statePath, - string runtimeId, - int clientCultMeshPort, - bool once, - IReadOnlyList? peerEndpoints) - { - var repoRoot = Directory.GetCurrentDirectory(); - var executablePath = Path.Combine( - repoRoot, - "Aetheria.State.Daemon", - "bin", - "Debug", - "net10.0", - "Aetheria.State.Daemon.exe"); - var dllPath = Path.Combine( - repoRoot, - "Aetheria.State.Daemon", - "bin", - "Debug", - "net10.0", - "Aetheria.State.Daemon.dll"); - var projectPath = Path.Combine( - repoRoot, - "Aetheria.State.Daemon", - "Aetheria.State.Daemon.csproj"); - var executableExists = File.Exists(executablePath); - var dllExists = File.Exists(dllPath); - var commandPrefix = executableExists - ? Quote(executablePath) - : dllExists - ? "dotnet " + Quote(dllPath) - : "dotnet run --project " + Quote(projectPath) + " --"; - var arguments = string.Join( - " ", - "--state", - Quote(statePath), - "--daemon-id", - runtimeId, - "--verse-id", - "aetheria.coop-smoke", - "--session-id", - "authority-smoke", - "--client-cultmesh-port", - clientCultMeshPort.ToString(), - "--api-publication-interval-ms", - "50", - string.Join(" ", (peerEndpoints ?? Array.Empty()) - .Select(endpoint => "--peer-cultmesh-endpoint " + Quote(endpoint))), - once ? "--once" : ""); - var startInfo = executableExists - ? new ProcessStartInfo(executablePath, arguments) - : dllExists - ? new ProcessStartInfo("dotnet", Quote(dllPath) + " " + arguments) - : new ProcessStartInfo("dotnet", "run --project " + Quote(projectPath) + " -- " + arguments); - startInfo.WorkingDirectory = repoRoot; - startInfo.RedirectStandardOutput = true; - startInfo.RedirectStandardError = true; - startInfo.UseShellExecute = false; - startInfo.CreateNoWindow = true; - - if (string.IsNullOrWhiteSpace(commandPrefix)) - { - throw new InvalidOperationException("Cannot resolve daemon command."); - } - - var process = Process.Start(startInfo) ?? - throw new InvalidOperationException("Failed to start Aetheria daemon child process."); - CaptureProcessOutput(process, runtimeId); - return process; - } - - private static async Task WaitForProcessSuccessAsync( - Process process, - string runtimeId, - TimeSpan timeout) - { - var waitTask = process.WaitForExitAsync(); - var completed = await Task.WhenAny(waitTask, Task.Delay(timeout)) - .ConfigureAwait(false); - if (completed != waitTask) - { - try - { - process.Kill(entireProcessTree: true); - } - catch - { - } - - throw new TimeoutException($"Aetheria daemon child process timed out for {runtimeId}."); - } - - if (process.ExitCode != 0) - { - throw new InvalidOperationException( - $"Aetheria daemon child process failed for {runtimeId} with exit code {process.ExitCode}.\n{GetProcessOutputTail(process)}"); - } - } - - private static void CaptureProcessOutput(Process process, string runtimeId) - { - lock (ProcessOutputLock) - ProcessOutput[process.Id] = new List(); - - process.OutputDataReceived += (_, eventArgs) => - { - if (eventArgs.Data != null) - AppendProcessOutput(process.Id, $"{runtimeId} stdout: {eventArgs.Data}"); - }; - process.ErrorDataReceived += (_, eventArgs) => - { - if (eventArgs.Data != null) - AppendProcessOutput(process.Id, $"{runtimeId} stderr: {eventArgs.Data}"); - }; - process.BeginOutputReadLine(); - process.BeginErrorReadLine(); - } - - private static void AppendProcessOutput(int processId, string line) - { - lock (ProcessOutputLock) - { - if (!ProcessOutput.TryGetValue(processId, out var lines)) - { - lines = new List(); - ProcessOutput[processId] = lines; - } - - lines.Add(line); - if (lines.Count > 80) - lines.RemoveRange(0, lines.Count - 80); - } - } - - private static string GetProcessOutputTail(Process process) - { - lock (ProcessOutputLock) - { - if (!ProcessOutput.TryGetValue(process.Id, out var lines) || lines.Count == 0) - return "No child output captured."; - - var builder = new StringBuilder(); - foreach (var line in lines) - builder.AppendLine(line); - return builder.ToString(); - } - } - - private static async Task WaitForReplicaFrameAsync( - string endpoint, - string replicaPath, - string runtimeId, - IReadOnlyList childProcesses, - Func isReady, - string expectedFrame) - { - var deadline = DateTimeOffset.UtcNow.AddSeconds(45); - Exception? lastError = null; - AetheriaRuntimeDaemonFrameDocument? lastFrame = null; - while (DateTimeOffset.UtcNow < deadline) - { - try - { - await AetheriaVerseReplica.SyncScopedSnapshotAsync( - replicaPath, - endpoint, - runtimeId, - schemaIds: - [ - AetheriaRuntimeDaemonSchemas.Frame, - AetheriaRuntimeDaemonSchemas.CommittedCommandFact - ]).ConfigureAwait(false); - await using var node = await AetheriaStateNode.OpenAsync( - replicaPath, - runtimeId, - startServer: false, - enableDurableShardLogs: false).ConfigureAwait(false); - var frame = await node.MutableDocument(AetheriaRuntimeVerseRecordKeys.DaemonFrameLatest).ReadAsync().ConfigureAwait(false); - if (frame != null) - { - lastFrame = frame; - } - - if (frame != null && frame.FrameId >= 0 && isReady(frame)) - return frame; - } - catch (Exception ex) - { - lastError = ex; - } - - await Task.Delay(250).ConfigureAwait(false); - } - - throw new TimeoutException( - $"Timed out waiting for replica frame from {endpoint}. Expected {expectedFrame}." + - $" Requested schemas=[{AetheriaRuntimeDaemonSchemas.Frame},{AetheriaRuntimeDaemonSchemas.CommittedCommandFact}]." + - (lastFrame == null ? "" : $" Last frame: daemon={lastFrame.DaemonId}, frame={lastFrame.FrameId}, applied=[{string.Join(",", lastFrame.AppliedCommandIds)}], rejected=[{string.Join(",", lastFrame.RejectedCommandIds)}], cumulativeApplied=[{string.Join(",", lastFrame.CumulativeAppliedCommandIds)}], cumulativeRejected=[{string.Join(",", lastFrame.CumulativeRejectedCommandIds)}].") + - ProcessDiagnostics(childProcesses) + - (lastError == null ? "" : $" Last error: {lastError.GetType().Name}: {lastError.Message}")); - } - - private static async Task> WaitForReplicaFactsAsync( - string endpoint, - string replicaPath, - string runtimeId, - IReadOnlyList childProcesses, - Func, bool> isReady, - string expectedFacts) - { - var deadline = DateTimeOffset.UtcNow.AddSeconds(45); - Exception? lastError = null; - IReadOnlyList lastFacts = Array.Empty(); - while (DateTimeOffset.UtcNow < deadline) - { - try - { - await AetheriaVerseReplica.SyncScopedSnapshotAsync( - replicaPath, - endpoint, - runtimeId, - schemaIds: - [ - AetheriaRuntimeDaemonSchemas.Frame, - AetheriaRuntimeDaemonSchemas.CommittedCommandFact - ]).ConfigureAwait(false); - var facts = await ReadFactsAsync(replicaPath, runtimeId).ConfigureAwait(false); - lastFacts = facts; - if (isReady(facts)) - return facts; - } - catch (Exception ex) - { - lastError = ex; - } - - await Task.Delay(250).ConfigureAwait(false); - } - - throw new TimeoutException( - $"Timed out waiting for committed facts from {endpoint}. Expected {expectedFacts}." + - $" Requested schemas=[{AetheriaRuntimeDaemonSchemas.Frame},{AetheriaRuntimeDaemonSchemas.CommittedCommandFact}]." + - $" Last facts=[{string.Join(",", lastFacts.Select(fact => $"{fact.SourceDaemonId}:{fact.CommandId}:{fact.Outcome}"))}]." + - ProcessDiagnostics(childProcesses) + - (lastError == null ? "" : $" Last error: {lastError.GetType().Name}: {lastError.Message}")); - } - - private static async Task WaitForLocalFrameAsync( - string statePath, - string runtimeId, - IReadOnlyList childProcesses, - Func isReady, - string expectedFrame) - { - var deadline = DateTimeOffset.UtcNow.AddSeconds(45); - Exception? lastError = null; - AetheriaRuntimeDaemonFrameDocument? lastFrame = null; - while (DateTimeOffset.UtcNow < deadline) - { - try - { - var frame = await TryReadPublishedFrameAsync(statePath, runtimeId).ConfigureAwait(false); - if (frame != null) - { - lastFrame = frame; - if (isReady(frame)) - return frame; - } - } - catch (Exception ex) - { - lastError = ex; - } - - await Task.Delay(250).ConfigureAwait(false); - } - - throw new TimeoutException( - $"Timed out waiting for local daemon frame at {statePath}. Expected {expectedFrame}." + - (lastFrame == null ? "" : $" Last frame: daemon={lastFrame.DaemonId}, frame={lastFrame.FrameId}, imported=[{string.Join(",", lastFrame.ImportedFactIds)}], cumulativeImported=[{string.Join(",", lastFrame.CumulativeImportedFactIds)}], rejectedImported=[{string.Join(",", lastFrame.CumulativeRejectedImportedFactIds)}].") + - ProcessDiagnostics(childProcesses) + - (lastError == null ? "" : $" Last error: {lastError.GetType().Name}: {lastError.Message}")); - } - - private static async Task ReadPublishedFrameAsync( - string statePath, - string runtimeId) - { - var frame = await TryReadPublishedFrameAsync(statePath, runtimeId).ConfigureAwait(false); - if (frame == null) - throw new InvalidOperationException($"No managed latest daemon frame is published at {statePath}."); - - return frame; - } - - private static async Task TryReadPublishedFrameAsync( - string statePath, - string runtimeId) - { - await using var node = await AetheriaStateNode.OpenAsync( - statePath, - runtimeId, - startServer: false, - enableDurableShardLogs: false).ConfigureAwait(false); - return await node.MutableDocument(AetheriaRuntimeVerseRecordKeys.DaemonFrameLatest).ReadAsync().ConfigureAwait(false); - } - - private static string ProcessDiagnostics(IReadOnlyList childProcesses) - { - var lines = new List(); - foreach (var process in childProcesses ?? Array.Empty()) - { - try - { - lines.Add(process.HasExited - ? $" Child pid={process.Id} exited code={process.ExitCode}." - : $" Child pid={process.Id} still running."); - lines.Add(Environment.NewLine); - lines.Add(GetProcessOutputTail(process)); - } - catch (Exception ex) - { - lines.Add($" Child diagnostics failed: {ex.GetType().Name}: {ex.Message}."); - } - } - - return lines.Count == 0 ? "" : string.Concat(lines); - } - - private static void StopProcess(Process process) - { - try - { - if (!process.HasExited) - process.Kill(entireProcessTree: true); - } - catch - { - } - - try - { - process.WaitForExit(5000); - } - catch - { - } - } - - private static AetheriaRuntimeDaemonFrameDocument TickWithPolicy( - string daemonId, - AetheriaRuntimeRunCheckpointCommit run, - AetheriaRuntimeVerseAuthorityPolicyDocument policy, - IReadOnlyList observedCommands) - { - var rejectedIds = new List(); - var authorizedCommands = AetheriaRuntimeAuthorityRouter.AuthorizedCommands( - observedCommands, - policy, - leases: null, - localRuntimeId: daemonId, - rejectedCommandIds: rejectedIds); - - using var physics = new AetheriaYmirWorldPhysics(); - var result = AetheriaRuntimeDaemonTickRunner.Tick( - Path.Combine(Path.GetTempPath(), $"aetheria-authority-smoke-{daemonId}.cc"), - run, - new AetheriaRuntimeDaemonTickOptions - { - DaemonId = daemonId, - SessionId = "authority-smoke", - VerseId = policy.VerseId, - FrameId = 1, - SimulationTimeSeconds = 0.02, - FixedDeltaSeconds = 0.02, - WorldPhysics = physics, - ObservedCommands = authorizedCommands, - PreRejectedCommandIds = rejectedIds, - BuildPublications = false - }); - - return result.Frame; - } - - private static AetheriaRuntimeVerseAuthorityPolicyDocument Policy( - string defaultMode, - params AetheriaRuntimeAuthorityRule[] rules) - { - return new AetheriaRuntimeVerseAuthorityPolicyDocument - { - VerseId = "aetheria.test", - HostRuntimeId = "host-daemon", - DefaultMode = defaultMode, - Rules = rules - }; - } - - private static AetheriaRuntimeVerseAuthorityPolicyDocument CoopPolicy() - { - var ravenKey = EntityKey("coop-smoke", 0, 0); - var hostileKey = EntityKey("coop-smoke", 0, 1); - var policy = Policy( - AetheriaRuntimeAuthorityModes.HostAuthoritative, - new AetheriaRuntimeAuthorityRule - { - RuleId = "raven.runtime.owns.raven.movement", - SubjectPrefix = ravenKey, - ClaimKinds = [AetheriaRuntimeClaimKinds.Movement], - Mode = AetheriaRuntimeAuthorityModes.DelegatedRuntime, - RuntimeIds = ["pilot-client"], - Priority = 100 - }, - new AetheriaRuntimeAuthorityRule - { - RuleId = "commander.runtime.owns.hostile.movement", - SubjectPrefix = hostileKey, - ClaimKinds = [AetheriaRuntimeClaimKinds.Movement], - Mode = AetheriaRuntimeAuthorityModes.DelegatedRuntime, - RuntimeIds = ["commander-client"], - Priority = 100 - }); - policy.VerseId = "aetheria.coop-smoke"; - policy.PolicyId = "aetheria.coop-smoke.delegated.v1"; - return policy; - } - - private static AetheriaRuntimeVerseAuthorityPolicyDocument CoopPolicyWithMetadata() - { - var policy = CoopPolicy(); - var ravenKey = EntityKey("coop-smoke", 0, 0); - var hostileKey = EntityKey("coop-smoke", 0, 1); - policy.Rules = policy.Rules - .Concat(new[] - { - new AetheriaRuntimeAuthorityRule - { - RuleId = "raven.runtime.owns.raven.metadata", - SubjectPrefix = ravenKey, - ClaimKinds = [AetheriaRuntimeClaimKinds.Metadata], - Mode = AetheriaRuntimeAuthorityModes.DelegatedRuntime, - RuntimeIds = ["pilot-client"], - Priority = 90 - }, - new AetheriaRuntimeAuthorityRule - { - RuleId = "commander.runtime.owns.hostile.metadata", - SubjectPrefix = hostileKey, - ClaimKinds = [AetheriaRuntimeClaimKinds.Metadata], - Mode = AetheriaRuntimeAuthorityModes.DelegatedRuntime, - RuntimeIds = ["commander-client"], - Priority = 90 - } - }) - .ToArray(); - return policy; - } - - private static AetheriaRuntimeDaemonCommandDocument Command( - string runtimeId, - string subjectKey, - AetheriaRuntimeDaemonCommandKinds kind = AetheriaRuntimeDaemonCommandKinds.SetMoveVector) - { - var command = AetheriaRuntimeDaemonCommandDocument.Create( - kind, - runtimeId, - sessionId: "authority-smoke", - observedFrameId: 0, - actorEntityKey: subjectKey); - command.SubjectKey = subjectKey; - command.AuthorRuntimeId = runtimeId; - return command; - } - - private static AetheriaRuntimeDaemonCommandDocument MovementCommand( - string runtimeId, - string subjectKey, - double directionX, - double directionY, - double magnitude) - { - var command = Command(runtimeId, subjectKey, AetheriaRuntimeDaemonCommandKinds.SetMoveVector); - command.DirectionX = directionX; - command.DirectionY = directionY; - command.ScalarValue = magnitude; - return command; - } - - private static AetheriaRuntimeDaemonCommandDocument MetadataNameCommand( - string runtimeId, - string subjectKey, - string name) - { - var command = Command(runtimeId, subjectKey, AetheriaRuntimeDaemonCommandKinds.SetEntityName); - command.TargetEntityKey = subjectKey; - command.TextValue = name; - command.ClaimKind = AetheriaRuntimeClaimKinds.Metadata; - return command; - } - - private static AetheriaRuntimeRunCheckpointCommit InitialCoopRun() - { - return new AetheriaRuntimeRunCheckpointCommit - { - RunId = "coop-smoke", - CurrentZoneIndex = 0, - CurrentEntityKey = EntityKey("coop-smoke", 0, 0), - Zones = - [ - new AetheriaRuntimeZoneSnapshotCommit - { - ZoneIndex = 0, - Name = "Co-op Smoke Zone", - Entities = - [ - new AetheriaRuntimeEntitySnapshotCommit - { - EntityIndex = 0, - Name = "Raven Pilot", - Kind = "ship", - FactionKey = "player", - IsActive = true - }, - new AetheriaRuntimeEntitySnapshotCommit - { - EntityIndex = 1, - Name = "Commander Hostile", - Kind = "ship", - FactionKey = "player", - IsActive = true - } - ] - } - ] - }; - } - - private static AetheriaRuntimeRunCheckpointCommit InitialCoopFactRun() - { - var run = InitialCoopRun(); - var zone = run.Zones[0]; - zone.Entities[0].FactionKey = "player"; - zone.Entities[1].FactionKey = "raider"; - return run; - } - - private static AetheriaRuntimeEntitySnapshotCommit Entity( - AetheriaRuntimeRunCheckpointCommit run, - string entityKey) - { - var parts = entityKey.Split('.'); - var zoneIndex = Array.IndexOf(parts, "zone") >= 0 && int.TryParse(parts[Array.IndexOf(parts, "zone") + 1], out var parsedZone) - ? parsedZone - : 0; - var entityIndex = Array.IndexOf(parts, "entity") >= 0 && int.TryParse(parts[Array.IndexOf(parts, "entity") + 1], out var parsedEntity) - ? parsedEntity - : 0; - var zone = (run.Zones ?? Array.Empty()) - .First(candidate => candidate.ZoneIndex == zoneIndex); - return (zone.Entities ?? Array.Empty()) - .First(candidate => candidate.EntityIndex == entityIndex); - } - - private static AetheriaRuntimeEntitySnapshotCommit SnapshotEntity( - int entityIndex, - string name, - string factionKey, - double x, - double z, - double visibility, - double hull, - string cargoItem) - { - return new AetheriaRuntimeEntitySnapshotCommit - { - EntityIndex = entityIndex, - Name = name, - Kind = "ship", - FactionKey = factionKey, - IsActive = true, - PositionX = x, - PositionZ = z, - DirectionX = 1, - DirectionY = 0, - Visibility = visibility, - TargetEntityIndex = -1, - StatGrids = - [ - new AetheriaRuntimeEntityStatGridCommit - { - Name = "hull", - Width = 1, - Height = 1, - Values = [hull] - }, - new AetheriaRuntimeEntityStatGridCommit - { - Name = "shield", - Width = 1, - Height = 1, - Values = [25] - }, - new AetheriaRuntimeEntityStatGridCommit - { - Name = "heat", - Width = 1, - Height = 1, - Values = [4] - } - ], - CargoContents = - [ - new AetheriaRuntimeCargoBayLoadoutCommit - { - Items = - [ - new AetheriaRuntimeLoadoutItemSlotCommit - { - Item = new AetheriaRuntimeLoadoutItemCommit - { - ItemKey = cargoItem, - Quantity = 1, - Quality = 1, - Durability = 1, - Enabled = true - } - } - ] - } - ] - }; - } - - private static string EntityKey(string runId, int zoneIndex, int entityIndex) - { - return $"global:aetheria.run_state.{runId}.zone.{zoneIndex}.entity.{entityIndex}.v1"; - } - - private static string Quote(string value) - { - return "\"" + value.Replace("\"", "\\\"", StringComparison.Ordinal) + "\""; - } - - private static int GetFreeUdpPort() - { - using var socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); - socket.Bind(new IPEndPoint(IPAddress.Loopback, 0)); - return ((IPEndPoint)socket.LocalEndPoint!).Port; - } - - private static void Require(bool condition, string message) - { - if (!condition) - throw new InvalidOperationException(message); - } - - private static void RequireEqual(T expected, T actual, string message) - { - if (!EqualityComparer.Default.Equals(expected, actual)) - throw new InvalidOperationException($"{message}: expected {expected}, got {actual}"); - } - - private static void RequireSurfaceMetric( - AetheriaRuntimeSurfaceDocument surface, - string componentId, - string expectedValue, - string message) - { - var component = Flatten(surface.Surface.Root) - .FirstOrDefault(candidate => string.Equals(candidate.Id, componentId, StringComparison.Ordinal)); - if (component == null) - throw new InvalidOperationException($"{message}: missing component {componentId}"); - if (!component.Props.TryGetValue("value", out var actualValue) || - !string.Equals(actualValue, expectedValue, StringComparison.Ordinal)) - { - throw new InvalidOperationException($"{message}: expected {expectedValue}, got {actualValue ?? "(missing)"}"); - } - } - - private static IEnumerable Flatten(AetheriaRuntimeSurfaceComponent component) - { - yield return component; - foreach (var child in component.Children ?? Array.Empty()) - { - foreach (var nested in Flatten(child)) - yield return nested; - } - } -} diff --git a/Aetheria.State.Daemon.Smoke/Aetheria.State.Daemon.Smoke.csproj b/Aetheria.State.Daemon.Smoke/Aetheria.State.Daemon.Smoke.csproj deleted file mode 100644 index 1f56dc6b2..000000000 --- a/Aetheria.State.Daemon.Smoke/Aetheria.State.Daemon.Smoke.csproj +++ /dev/null @@ -1,17 +0,0 @@ - - - - - Exe - net10.0 - enable - latest - enable - - - - - - - - diff --git a/Aetheria.State.Daemon.Smoke/Program.cs b/Aetheria.State.Daemon.Smoke/Program.cs deleted file mode 100644 index 59e4cd2fe..000000000 --- a/Aetheria.State.Daemon.Smoke/Program.cs +++ /dev/null @@ -1,9834 +0,0 @@ -using Aetheria.State; -using Aetheria.State.Daemon; -using Aetheria.State.Documents; -using GameCult.Aetheria.State.Verse; -using GameCult.Caching; -using GameCult.Eve.PluginFields; -using GameCult.Eve.Surface; -using GameCult.Mesh; -using System.Globalization; - -var checks = new AetheriaDaemonYmirSmokeChecks(); -if (args.Contains("--gravity", StringComparer.Ordinal)) -{ - checks.RunGravity(); - Console.WriteLine("Daemon Ymir gravity-sign smoke passed."); -} -else if (args.Contains("--loadout", StringComparer.Ordinal)) -{ - checks.RunLoadout(); - Console.WriteLine("Daemon loadout hardpoint smoke passed."); -} -else if (args.Contains("--pickup", StringComparer.Ordinal)) -{ - checks.RunPickup(); - Console.WriteLine("Daemon pickup-proximity smoke passed."); -} -else if (args.Contains("--payload", StringComparer.Ordinal)) -{ - checks.RunPayload(); - Console.WriteLine("Daemon retained Ymir payload smoke passed."); -} -else if (args.Contains("--tutorial-topology", StringComparer.Ordinal)) -{ - checks.RunTutorialTopology(); - Console.WriteLine("Daemon tutorial topology smoke passed."); -} -else if (args.Contains("--regular-topology", StringComparer.Ordinal)) -{ - checks.RunRegularTopology(); - Console.WriteLine("Daemon regular-sector topology smoke passed."); -} -else if (args.Contains("--regular-run", StringComparer.Ordinal)) -{ - checks.RunRegularRun(); - Console.WriteLine("Daemon persisted regular-sector run smoke passed."); -} -else if (args.Contains("--zone-bodies", StringComparer.Ordinal)) -{ - checks.RunZoneBodies(); - Console.WriteLine("Daemon generated zone-body smoke passed."); -} -else if (args.Contains("--tutorial-world", StringComparer.Ordinal)) -{ - checks.RunTutorialWorld(); - Console.WriteLine("Daemon tutorial world-plan smoke passed."); -} -else if (args.Contains("--tutorial-materialization", StringComparer.Ordinal)) -{ - checks.RunTutorialMaterialization(); - Console.WriteLine("Daemon tutorial entity-materialization smoke passed."); -} -else if (args.Contains("--tutorial-run", StringComparer.Ordinal)) -{ - checks.RunTutorialRun(); - Console.WriteLine("Daemon persisted tutorial-run smoke passed."); -} -else if (args.Contains("--orbits", StringComparer.Ordinal)) -{ - checks.RunOrbits(); - Console.WriteLine("Daemon dynamic-orbit smoke passed."); -} -else if (args.Contains("--patrol", StringComparer.Ordinal)) -{ - checks.RunPatrol(); - Console.WriteLine("Daemon generated-agent patrol smoke passed."); -} -else if (args.Contains("--combat-lock", StringComparer.Ordinal)) -{ - checks.RunCombatLock(); - Console.WriteLine("Daemon look-direction weapon-lock smoke passed."); -} -else if (args.Contains("--hangar", StringComparer.Ordinal)) -{ - checks.RunHangar(); - Console.WriteLine("Daemon Hangar deployment and continuation smoke passed."); -} -else -{ - checks.Run(); - Console.WriteLine("Daemon Ymir physical payload smoke passed."); -} - -internal sealed class AetheriaDaemonYmirSmokeChecks -{ - private static readonly List OwnedPhysics = []; - - public void RunGravity() => RunCheck(PositiveGravityDepthAttractsAndProjectsAsAWell); - - public void RunCombatLock() => RunCheck(InstantWeaponRequestSurvivesLockAcquisition); - - public void RunHangar() => RunCheck(HangarLaunchUsesConfiguredLoadoutAndContinuesSavedRun); - - public void RunLoadout() - { - DaemonNativeDockyardMaterializesTypedCatalog(); - DaemonLoadoutsRespectFactionAvailabilityAndHullRoles(); - } - - public void RunPickup() - { - RunCheck(RetainedWorldAdvancesEveryFixedSubstep); - RunCheck(StableEntityIdentitySurvivesCrossZoneReindex); - RunCheck(TractorRampsAndPullsThroughYmirWithoutTeleportingCargo); - RunCheck(PickupProximityPrecedesYmirCollisionShell); - RunCheck(CatalogItemIndexPreservesSnapshotLookupSemantics); - RunCheck(PickupIsCapacityCheckedExactlyOnceAndExpires); - RunCheck(StationBodiesCannotConsumePickups); - RunCheck(PickupProximityCollectsOrBounces); - RunCheck(PausedFirstFrameCreatesPersistableYmirWorld); - RunCheck(PickupRemovalSurvivesRestartWithoutReplay); - } - - public void RunPayload() - { - RunCheck(YmirMovesProjectileAndReportsStableContact); - RunCheck(PayloadQueryExcludesItsSource); - RunCheck(PayloadBodiesDoNotCollideWithEachOther); - RunCheck(OverlappingMineRemainsQueryableAfterArming); - } - - public void RunTutorialTopology() => RunCheck(TutorialTopologyIsDeterministicConnectedAndRoleOwned); - public void RunRegularTopology() => RunCheck(RegularTopologyOwnsFossilSectorStructure); - public void RunRegularRun() => RunCheck(RegularRunPersistsPlayableWorldTruth); - public void RunZoneBodies() => RunCheck(GeneratedZoneBodiesPreserveFossilHierarchy); - public void RunTutorialWorld() => RunCheck(TutorialWorldMaterializesEveryTopologyZone); - public void RunTutorialMaterialization() => RunCheck(TutorialPopulationPreservesFossilEntityMechanics); - public void RunTutorialRun() => RunCheck(TutorialRunPersistsCanonicalWorldTruth); - public void RunOrbits() => RunCheck(GeneratedOrbitsAdvanceOrbitalWorldTruth); - public void RunPatrol() - { - RunCheck(SecurityZonesOwnFossilTrespasserHostility); - RunCheck(AgentPatrolsHistoricalOrbitCircuitThroughMovementCommands); - RunCheck(PatrolCombatPreemptsAndResumesTheSameCircuit); - } - - private static void HangarLaunchUsesConfiguredLoadoutAndContinuesSavedRun() - { - var runtimeCatalog = TutorialPopulationCatalog(out _); - runtimeCatalog.Items.Single(item => string.Equals(item.HullType, "Station", StringComparison.Ordinal)).ItemKey = - AetheriaDaemonNativeCatalog.DockyardHullItemKey; - var root = Path.Combine(Path.GetTempPath(), $"aetheria-hangar-{Guid.NewGuid():N}"); - var statePath = Path.Combine(root, "aetheria.cc"); - Directory.CreateDirectory(root); - try - { - string runKey; - string deploymentId; - string removedItemKey; - using (var node = AetheriaStateNode.OpenAsync(statePath).GetAwaiter().GetResult()) - { - AetheriaDaemonHangarCoordinator.EnsureAsync(node, runtimeCatalog, "2026-08-08T00:00:00Z") - .GetAwaiter().GetResult(); - var hangar = node.MutableDocument(AetheriaStateNode.HangarKey) - .ReadAsync().GetAwaiter().GetResult()!; - var ship = hangar.Ships.Single(); - var template = node.MutableDocument(new CultRecordKey(ship.LoadoutTemplateKey)) - .ReadAsync().GetAwaiter().GetResult()!; - removedItemKey = template.RootEntity.Equipment[0].Item.ItemKey; - var mutation = AetheriaHangar.RemoveAsync(node, ship.ShipId, 0, hangar.Revision, "2026-08-08T00:00:01Z") - .GetAwaiter().GetResult(); - Require(mutation.Accepted, "Hangar loadout customization must commit through the canonical Hangar owner"); - hangar = node.MutableDocument(AetheriaStateNode.HangarKey) - .ReadAsync().GetAwaiter().GetResult()!; - template = node.MutableDocument(new CultRecordKey(ship.LoadoutTemplateKey)) - .ReadAsync().GetAwaiter().GetResult()!; - var surface = AetheriaRuntimeHangarSurfaceBuilder.Build( - hangar, ship.ShipId, AetheriaGameModes.Terminus, "2026-08-08T00:00:01Z", 1, - AetheriaRuntimeStateMapper.ToRuntimeLoadoutTemplate(template), runtimeCatalog); - var world = surface.Surface.Root.Children.Single(component => component.Kind == "world.scene3d"); - Require(surface.Surface.Id == AetheriaRuntimeHangarCommands.SurfaceId && - world.Props.Any(prop => prop.Key == "entityViewPointerId" && !string.IsNullOrWhiteSpace(prop.Value)) && - world.Props.Any(prop => prop.Key == "entityBodyId" && !string.IsNullOrWhiteSpace(prop.Value)) && - surface.Commands.Any(command => command.Command == AetheriaRuntimeHangarCommands.EquipItem) && - surface.Commands.Any(command => command.Command == AetheriaRuntimeHangarCommands.RemoveItem) && - surface.Commands.Any(command => command.Command == AetheriaRuntimeHangarCommands.Launch) && - surface.Commands.Any(command => command.Command == AetheriaRuntimeHangarCommands.Continue), - "the daemon-owned Hangar surface must advertise loadout, launch, and continuation operations"); - - var receipt = AetheriaDaemonHangarCoordinator.LaunchTerminusAsync( - node, runtimeCatalog, "launch-configured", ship.ShipId, mutation.HangarRevision, "2026-08-08T00:00:02Z") - .GetAwaiter().GetResult(); - Require(receipt.Accepted, "a valid configured Hangar deployment must launch Terminus"); - deploymentId = receipt.DeploymentId; - var settings = node.MutableDocument(AetheriaStateNode.PlayerSettingsKey) - .ReadAsync().GetAwaiter().GetResult()!; - runKey = settings.ActiveRunKey; - var run = node.MutableDocument(new CultRecordKey(runKey)) - .ReadAsync().GetAwaiter().GetResult()!; - var player = node.MutableDocument(new CultRecordKey(run.CurrentEntityKey)) - .ReadAsync().GetAwaiter().GetResult()!; - Require(run.GameMode == AetheriaGameModes.Terminus && - run.GenerationSeed == AetheriaDaemonRunFactory.StableSeed(deploymentId), - "the accepted Hangar deployment must own the new Terminus run identity"); - Require(player.Equipment.All(slot => !string.Equals(slot.ItemKey, removedItemKey, StringComparison.Ordinal)), - "Terminus must instantiate the configured Hangar loadout, not regenerate the starter fit"); - } - - using (var reopened = AetheriaStateNode.OpenAsync(statePath).GetAwaiter().GetResult()) - { - var canContinue = AetheriaDaemonHangarCoordinator.CanContinueTerminusAsync( - reopened, AetheriaDaemonHangarCoordinator.StarterShipId, deploymentId) - .GetAwaiter().GetResult(); - var settings = reopened.MutableDocument(AetheriaStateNode.PlayerSettingsKey) - .ReadAsync().GetAwaiter().GetResult(); - var run = reopened.MutableDocument(new CultRecordKey(runKey)) - .ReadAsync().GetAwaiter().GetResult(); - Require(canContinue && settings?.ActiveRunKey == runKey && run != null, - "Continue must reopen the saved Terminus checkpoint without creating another run"); - } - } - finally - { - if (Directory.Exists(root)) Directory.Delete(root, recursive: true); - } - } - - private static void TutorialRunPersistsCanonicalWorldTruth() - { - var catalog = TutorialPopulationCatalog(out _); - var root = Path.Combine(Path.GetTempPath(), $"aetheria-tutorial-run-{Guid.NewGuid():N}"); - var statePath = Path.Combine(root, "aetheria.cc"); - Directory.CreateDirectory(root); - try - { - using var node = AetheriaStateNode.OpenAsync(statePath).GetAwaiter().GetResult(); - var written = AetheriaDaemonTutorialRunWriter.WriteAsync( - node, catalog, "2026-07-17T00:00:00.0000000Z").GetAwaiter().GetResult(); - var run = node.MutableDocument(new CultRecordKey(written.RunKey)) - .ReadAsync().GetAwaiter().GetResult(); - var settings = node.MutableDocument(AetheriaStateNode.PlayerSettingsKey) - .ReadAsync().GetAwaiter().GetResult(); - Require(run != null && run.IsTutorial && run.ZoneKeys.Length == 64 && - run.EntranceZoneIndex == run.CurrentZoneIndex && run.ExitZoneIndex == -1, - "persisted New Game truth must be the complete authored tutorial graph, not the Terminus witness fixture"); - Require(run!.HomeZones.Length == 6 && run.HomeZones.All(entry => entry.ZoneIndex >= 0), - "persisted tutorial truth must retain every faction home landmark for the sector map"); - Require(run!.AgentTasks.Length > 0 && run.AgentTasks.All(task => - task.TaskType == "patrol" && task.Status == "assigned" && task.TargetOrbitKeys.Length == 4), - "persisted tutorial agents must retain their assigned four-orbit fossil patrols"); - Require(settings != null && settings.ActiveRunKey == written.RunKey && !settings.TutorialPassed, - "tutorial persistence must select the new run without falsely awarding completion"); - Require(run!.CurrentEntityKey == written.CurrentEntityKey, - "the run and session handoff must share one canonical controlled-entity identity"); - var entrance = node.MutableDocument( - new CultRecordKey(run.ZoneKeys[run.EntranceZoneIndex])) - .ReadAsync().GetAwaiter().GetResult(); - Require(entrance != null && entrance.AdjacentZoneIndices.Length > 0 && - entrance.EntityKeys.Contains(run.CurrentEntityKey, StringComparer.Ordinal) && - entrance.Bodies.Length > 0 && entrance.Orbits.Length >= entrance.Bodies.Length, - "the persisted entrance must contain the starter ship, celestial hierarchy, and derived wormhole adjacency"); - var player = node.MutableDocument(new CultRecordKey(run.CurrentEntityKey)) - .ReadAsync().GetAwaiter().GetResult(); - Require(player != null && player.Kind == "ship" && player.LoadoutGeneration != null, - "the persisted current entity must be a playable daemon-generated ship with loadout provenance"); - } - finally - { - if (Directory.Exists(root)) Directory.Delete(root, recursive: true); - } - } - - private static void RegularTopologyOwnsFossilSectorStructure() - { - var factions = Enumerable.Range(0, 12) - .Select(index => new AetheriaDaemonTutorialFactionInput( - $"faction.{index}", $"Faction {index}", $"F{index}", 3 + index % 3) - { - BossHullItemKey = index < 3 ? $"boss-hull-{index}" : "", - TrainingNames = Enumerable.Range(0, 256) - .Select(value => string.Concat( - (char)('a' + value % 26), - (char)('a' + value / 26 % 26), - (char)('a' + index % 26), - (char)('a' + (value * 7 + index) % 26), - (char)('a' + (value * 11 + index) % 26), - (char)('a' + (value * 17 + index) % 26))) - .ToArray() - }) - .ToArray(); - const uint seed = 0x5EC7_0206u; - var first = AetheriaDaemonRegularTopologyGenerator.GenerateFossil(factions, seed); - var second = AetheriaDaemonRegularTopologyGenerator.GenerateFossil(factions, seed); - - RequireEqual(196, first.Zones.Count, - "regular generation must use the fossil 196-zone sector setting"); - RequireEqual(12, first.HomeZoneByFactionKey.Count, - "regular generation must place all twelve selected corporations"); - RequireEqual(3, first.BossZoneByFactionKey.Count, - "regular generation must place the fossil three boss corporations"); - Require(first.EntranceZoneIndex >= 0 && first.ExitZoneIndex >= 0 && - first.EntranceZoneIndex != first.ExitZoneIndex, - "regular generation must own distinct entrance and exit zones"); - Require(first.Zones.All(zone => !string.IsNullOrWhiteSpace(zone.Name)), - "regular generation must name every owned and catalogue zone"); - Require(first.Zones.All(zone => zone.AdjacentZoneIndices.Count > 0 && - zone.AdjacentZoneIndices.All(adjacent => - first.Zones[adjacent].AdjacentZoneIndices.Contains(zone.ZoneIndex))), - "regular generation must publish one connected undirected link graph"); - - int[] DistancesFrom(int source) - { - var distances = Enumerable.Repeat(-1, first.Zones.Count).ToArray(); - var queue = new Queue(); - distances[source] = 0; - queue.Enqueue(source); - while (queue.Count > 0) - { - var current = queue.Dequeue(); - foreach (var adjacent in first.Zones[current].AdjacentZoneIndices) - { - if (distances[adjacent] >= 0) continue; - distances[adjacent] = distances[current] + 1; - queue.Enqueue(adjacent); - } - } - return distances; - } - - var isolation = first.Zones - .Select(zone => DistancesFrom(zone.ZoneIndex).Sum()) - .ToArray(); - RequireEqual(isolation.Max(), isolation[first.ExitZoneIndex], - "regular exit must be a maximally isolated zone"); - var exitDistances = DistancesFrom(first.ExitZoneIndex); - RequireEqual(exitDistances.Max(), exitDistances[first.EntranceZoneIndex], - "regular entrance must be furthest from the exit"); - Require(first.DiscoveredZoneIndices.OrderBy(value => value).SequenceEqual( - new[] { first.EntranceZoneIndex } - .Concat(first.Zones[first.EntranceZoneIndex].AdjacentZoneIndices) - .Distinct() - .OrderBy(value => value)), - "regular discovery must begin with only the entrance and its neighbors"); - foreach (var home in first.HomeZoneByFactionKey) - Require(first.Zones[home.Value].FactionKeys.Contains(home.Key), - "each regular corporation must be present at its daemon-owned home zone"); - - Require(first.Zones.Select(zone => (zone.X, zone.Y, Links: string.Join(',', zone.AdjacentZoneIndices), - zone.OwnerFactionKey, zone.Name)).SequenceEqual( - second.Zones.Select(zone => (zone.X, zone.Y, Links: string.Join(',', zone.AdjacentZoneIndices), - zone.OwnerFactionKey, zone.Name))) && - first.HomeZoneByFactionKey.OrderBy(value => value.Key).SequenceEqual( - second.HomeZoneByFactionKey.OrderBy(value => value.Key)) && - first.BossZoneByFactionKey.OrderBy(value => value.Key).SequenceEqual( - second.BossZoneByFactionKey.OrderBy(value => value.Key)), - "regular topology, faction placement, boss placement, and names must be seed deterministic"); - } - - private static void RegularRunPersistsPlayableWorldTruth() - { - var tutorialCatalog = TutorialPopulationCatalog(out var factions); - var manufacturer = factions[0].CorporationKey; - foreach (var item in tutorialCatalog.Items) - item.ManufacturerKey = manufacturer; - var corporations = factions.Select((faction, index) => new AetheriaRuntimeCorporation( - faction.CorporationKey, - faction.Name, - faction.ShortName, - "", - $"names.{faction.ShortName}", - index < 2 ? "tutorial-ship-hull" : "", - 4, - 1, - [new AetheriaRuntimeCorporationAllegiance(manufacturer, 1)])).ToArray(); - var catalog = new AetheriaRuntimeCatalogSnapshot( - tutorialCatalog.Items.ToArray(), corporations, tutorialCatalog.NameFiles.ToArray()); - var root = Path.Combine(Path.GetTempPath(), $"aetheria-regular-run-{Guid.NewGuid():N}"); - var statePath = Path.Combine(root, "aetheria.cc"); - Directory.CreateDirectory(root); - try - { - string runKey; - using (var node = AetheriaStateNode.OpenAsync(statePath).GetAwaiter().GetResult()) - { - node.MutableDocument(AetheriaStateNode.PlayerSettingsKey) - .ReplaceAsync(new AetheriaPlayerSettings { TutorialPassed = true }) - .GetAwaiter().GetResult(); - var written = AetheriaDaemonRunFactory.WriteAsync( - node, - catalog, - "2026-07-17T00:00:00.0000000Z", - "regular-run-command", - new AetheriaDaemonRegularTopologySettings(24, 0.5f, 6, 2)) - .GetAwaiter().GetResult(); - runKey = written.RunKey; - var run = node.MutableDocument(new CultRecordKey(runKey)) - .ReadAsync().GetAwaiter().GetResult(); - var settings = node.MutableDocument(AetheriaStateNode.PlayerSettingsKey) - .ReadAsync().GetAwaiter().GetResult(); - Require(run != null && !run.IsTutorial && - run.GameMode == AetheriaGameSessionState.AetheriaMode && - run.ZoneKeys.Length == 24 && run.ExitZoneIndex >= 0 && - run.CurrentZoneIndex == run.EntranceZoneIndex, - "regular writer must persist an Aetheria-mode sector rather than a Terminus scenario"); - RequireEqual(AetheriaDaemonRunFactory.StableSeed("regular-run-command"), run!.GenerationSeed, - "accepted New Game identity must deterministically own regular-sector generation"); - Require(written.SessionMode == AetheriaGameSessionState.AetheriaMode && - settings?.ActiveRunKey == runKey && settings.TutorialPassed, - "regular writer must select the new sector without erasing tutorial completion"); - Require(run!.AgentTasks.Length > 0 && run.AgentTasks.All(task => - task.TaskType == AetheriaRuntimeAgentTaskTypes.Patrol && - task.Status == AetheriaRuntimeAgentTaskStatuses.Assigned), - "regular population must persist autonomous patrol inhabitants"); - Require(run.HomeZones.Length == 6 && run.BossZones.Length == 2 && - run.HomeZones.All(entry => entry.ZoneIndex >= 0) && - run.BossZones.All(entry => entry.ZoneIndex >= 0), - "regular persistence must retain every home and executive sector-map landmark"); - Require(run.ZoneKeys.All(key => key.Contains(run.RunId, StringComparison.Ordinal)), - "regular zone records must be namespaced by the generated run identity"); - var entrance = node.MutableDocument( - new CultRecordKey(run.ZoneKeys[run.EntranceZoneIndex])) - .ReadAsync().GetAwaiter().GetResult(); - Require(entrance != null && entrance.EntityKeys.Contains(run.CurrentEntityKey) && - entrance.EntityKeys.All(key => !key.Contains("tutorial", StringComparison.OrdinalIgnoreCase)), - "regular entrance must contain the controlled starter under regular identities"); - var starter = node.MutableDocument( - new CultRecordKey(run.CurrentEntityKey)) - .ReadAsync().GetAwaiter().GetResult(); - Require(starter != null && starter.IsActive && - !string.IsNullOrWhiteSpace(starter.HullItemKey) && - starter.Equipment.Length > 0 && starter.LoadoutGeneration.Selections.Length > 0, - "regular entrance must persist a generated, equipped, controllable starter ship"); - } - - using var reopened = AetheriaStateNode.OpenAsync(statePath).GetAwaiter().GetResult(); - var restored = reopened.MutableDocument(new CultRecordKey(runKey)) - .ReadAsync().GetAwaiter().GetResult(); - Require(restored != null && restored.GameMode == AetheriaGameSessionState.AetheriaMode && - restored.ZoneKeys.Length == 24 && !string.IsNullOrWhiteSpace(restored.CurrentEntityKey) && - restored.HomeZones.Length == 6 && restored.BossZones.Length == 2, - "regular sector selection, controlled identity, and map landmarks must survive a hard CultCache reopen"); - } - finally - { - if (Directory.Exists(root)) Directory.Delete(root, recursive: true); - } - } - - private static void TutorialPopulationPreservesFossilEntityMechanics() - { - var catalog = TutorialPopulationCatalog(out var factions); - var settings = AetheriaDaemonTutorialTopologySettings.Fossil; - var firstWorld = AetheriaDaemonTutorialWorldGenerator.Generate(factions, 0xA37E_2026u, settings); - var secondWorld = AetheriaDaemonTutorialWorldGenerator.Generate(factions, 0xA37E_2026u, settings); - var first = AetheriaDaemonTutorialWorldMaterializer.Materialize(firstWorld, factions, catalog); - var second = AetheriaDaemonTutorialWorldMaterializer.Materialize(secondWorld, factions, catalog); - - RequireEqual(first.Topology.EntranceZoneIndex, first.PlayerZoneIndex, - "the starter ship must enter the authored tutorial entrance zone"); - var entrance = first.Zones[first.PlayerZoneIndex]; - Require(entrance.PlayerEntityIndex >= 0 && - entrance.Entities[entrance.PlayerEntityIndex].Kind == "ship" && - entrance.Entities[entrance.PlayerEntityIndex].FactionKey == factions[0].CorporationKey, - "the tutorial must materialize one protagonist starter ship as daemon world truth"); - Require(first.Zones.Values.Any(zone => zone.Entities.Any(entity => entity.Kind == "station")) && - first.Zones.Values.Any(zone => zone.Entities.Any(entity => entity.Kind == "turret")) && - first.Zones.Values.Any(zone => zone.Entities.Any(entity => - entity.Kind == "ship" && entity.AgentTaskCapabilities.Contains("attack", StringComparer.Ordinal))), - "fossil station, defensive turret, and hostile-agent population must all survive materialization"); - Require(first.Zones.Values.SelectMany(zone => zone.Entities) - .Where(entity => entity.Kind == "ship" && entity.AgentTaskCapabilities.Contains("attack", StringComparer.Ordinal)) - .All(entity => entity.Position.X != 0 || entity.Position.Z != 0), - "generated non-player ships must retain the fossil activation rule that distributes zero-pose ships through the zone"); - Require(first.Zones.Values.All(zone => - { - var orbitKeys = zone.Orbits.Select(orbit => orbit.OrbitKey).ToHashSet(StringComparer.Ordinal); - var agents = zone.Entities.Where(entity => - entity.Kind == "ship" && entity.AgentTaskCapabilities.Contains("attack", StringComparer.Ordinal)).ToArray(); - return zone.AgentTasks.Length == agents.Length && - zone.AgentTasks.All(task => task.TaskType == "patrol" && - task.Status == "assigned" && task.TargetOrbitKeys.Length == 4 && - task.TargetOrbitKeys.Distinct(StringComparer.Ordinal).Count() == 4 && - task.TargetOrbitKeys.All(orbitKeys.Contains)) && - agents.All(entity => zone.AgentTasks.Any(task => task.TaskId == entity.AssignedAgentTaskId)); - }), "every generated NPC must begin with one assigned four-orbit patrol owned by daemon task state"); - Require(first.Zones.Values.SelectMany(zone => zone.Entities) - .Where(entity => !string.IsNullOrWhiteSpace(entity.OrbitKey)) - .All(entity => entity.Position.X != 0 || entity.Position.Z != 0), - "orbital stations and turrets must enter world truth at their resolved orbit position, not at the player origin"); - var player = first.Zones[first.PlayerZoneIndex].Entities[first.PlayerEntityIndex]; - Require(player.Position.X == 0 && player.Position.Z == 0, - "the controlled starter ship alone must retain the tutorial entrance origin"); - Require(first.Zones.Values.All(zone => - { - var orbitKeys = zone.Orbits.Select(orbit => orbit.OrbitKey).ToHashSet(StringComparer.Ordinal); - return zone.Entities.Where(entity => entity.Kind is "station" or "turret") - .All(entity => !string.IsNullOrWhiteSpace(entity.OrbitKey) && orbitKeys.Contains(entity.OrbitKey)); - }), "every orbital entity must reference an orbit owned by its canonical zone document"); - Require(first.Zones.Values.SelectMany(zone => zone.Entities) - .Where(entity => entity.Kind is "station" or "turret" or "ship") - .All(entity => entity.LoadoutGeneration != null && - entity.LoadoutGeneration.SourceZoneIndex >= 0 && - entity.LoadoutGeneration.Selections.Any(selection => selection.Role == "hull")), - "every generated entity must retain its daemon loadout provenance receipt"); - Require(first.Zones.OrderBy(pair => pair.Key) - .Select(pair => string.Join('|', pair.Value.Entities.Select(entity => - $"{entity.Kind}:{entity.FactionKey}:{entity.HullItemKey}:{entity.OrbitKey}"))) - .SequenceEqual(second.Zones.OrderBy(pair => pair.Key) - .Select(pair => string.Join('|', pair.Value.Entities.Select(entity => - $"{entity.Kind}:{entity.FactionKey}:{entity.HullItemKey}:{entity.OrbitKey}")))), - "fixed topology, catalog, and post-body random state must reproduce entity population exactly"); - } - - private static AetheriaRuntimeCatalogSnapshot TutorialPopulationCatalog( - out AetheriaDaemonTutorialFactionInput[] factions) - { - var one = new[] { new AetheriaRuntimeShapeCell(0, 0) }; - var hullCells = Enumerable.Range(0, 6).SelectMany(y => Enumerable.Range(0, 6) - .Select(x => new AetheriaRuntimeShapeCell(x, y))).ToArray(); - // The recovered tutorial catalog includes allied manufacturers which do - // not own one of the six prelude home zones. Prelude availability must - // retain those authored items without inventing another faction home. - var manufacturer = "manufacturer.common"; - - AetheriaRuntimeCatalogItem Item(string key, string category, string hardpointType = "", params string[] behaviors) - { - var item = CatalogItem(key, behaviors.Select((kind, index) => - new AetheriaRuntimeBehaviorPayload(index, kind, 0, Array.Empty())).ToArray()); - item.Category = category; - item.ManufacturerKey = manufacturer; - item.Price = 10; - item.HardpointType = hardpointType; - item.ShapeWidth = 1; - item.ShapeHeight = 1; - item.OccupiedCells = 1; - item.ShapeCells = one; - item.BehaviorKinds = behaviors; - return item; - } - - AetheriaRuntimeCatalogItem Hull(string key, string hullType, string controller) - { - var hull = Item(key, AetheriaRuntimeItemCategories.Hull, "Hull"); - hull.HullType = hullType; - hull.ShapeWidth = 6; - hull.ShapeHeight = 6; - hull.OccupiedCells = hullCells.Length; - hull.ShapeCells = hullCells; - hull.Hardpoints = - [ - new AetheriaRuntimeHardpoint("ControlModule", 0, 0, 1, 1, 1, one, "", "None", 0), - new AetheriaRuntimeHardpoint("Weapon", 1, 0, 1, 1, 1, one, "", "None", 0) - ]; - return hull; - } - - var shipHull = Hull("tutorial-ship-hull", "Ship", "Cockpit"); - var stationHull = Hull("tutorial-station-hull", "Station", "TurretController"); - var cockpit = Item("tutorial-cockpit", AetheriaRuntimeItemCategories.Gear, "ControlModule", "Cockpit"); - var turret = Item("tutorial-turret-controller", AetheriaRuntimeItemCategories.Gear, "ControlModule", "TurretController"); - var weapon = Item("tutorial-weapon", AetheriaRuntimeItemCategories.Weapon, "Weapon", "LockWeapon"); - var cargo = Item("tutorial-cargo", AetheriaRuntimeItemCategories.CargoBay, "Internal"); - cargo.InteriorShapeWidth = 4; - cargo.InteriorShapeHeight = 4; - cargo.InteriorOccupiedCells = 16; - cargo.InteriorShapeCells = Enumerable.Range(0, 4).SelectMany(y => Enumerable.Range(0, 4) - .Select(x => new AetheriaRuntimeShapeCell(x, y))).ToArray(); - var docking = Item("tutorial-docking", AetheriaRuntimeItemCategories.DockingBay, "Internal"); - var reactor = Item("tutorial-reactor", AetheriaRuntimeItemCategories.Gear, "Internal", "Reactor"); - var capacitor = Item("tutorial-capacitor", AetheriaRuntimeItemCategories.Gear, "Internal", "Capacitor"); - var names = new[] { "Miss", "Zhe", "Luc", "Aero", "Finch", "Adras" }; - var tutorialCorporations = names.Select(name => new AetheriaRuntimeCorporation( - $"faction.{name.ToLowerInvariant()}", name, name, "", $"names.{name}", "", 4, 1, - [new AetheriaRuntimeCorporationAllegiance(manufacturer, 1)])).ToArray(); - var corporations = tutorialCorporations.Append(new AetheriaRuntimeCorporation( - manufacturer, "Common Manufacturer", "Common", "", "", "", 0, 0, - Array.Empty())).ToArray(); - factions = names.Select((name, index) => new AetheriaDaemonTutorialFactionInput( - tutorialCorporations[index].CorporationKey, name, name, 4) - { - TrainingNames = Enumerable.Range(0, 128).Select(value => $"{name}{value:D3}").ToArray() - }).ToArray(); - var nameFiles = names.Select(name => new AetheriaRuntimeNameFile( - $"names.{name}", name, 128, - Enumerable.Range(0, 8).Select(value => $"{name}{value:D3}").ToArray(), - Enumerable.Range(0, 128).Select(value => $"{name}{value:D3}").ToArray())).ToArray(); - return new AetheriaRuntimeCatalogSnapshot( - [shipHull, stationHull, cockpit, turret, weapon, cargo, docking, reactor, capacitor], - corporations, - nameFiles); - } - - private static void TutorialWorldMaterializesEveryTopologyZone() - { - AetheriaDaemonTutorialFactionInput Faction(string shortName) => - new($"faction.{shortName.ToLowerInvariant()}", shortName, shortName, 2) - { - TrainingNames = Enumerable.Range(0, 512).Select(index => - $"{(char)('a' + index % 26)}{(char)('a' + index / 26 % 26)}{(char)('a' + index / 676 % 26)}{shortName.ToLowerInvariant()}") - .ToArray() - }; - var factions = new[] - { - Faction("Miss"), Faction("Zhe"), Faction("Luc"), - Faction("Aero"), Faction("Finch"), Faction("Adras") - }; - var world = AetheriaDaemonTutorialWorldGenerator.Generate(factions, 0xA37E_2026u); - - RequireEqual(world.Topology.Zones.Count, world.Zones.Count, - "the daemon tutorial world plan must materialize every topology zone exactly once"); - Require(world.Topology.Zones.All(zone => world.Zones.ContainsKey(zone.ZoneIndex)), - "tutorial topology identities must be the sole keys of generated zone plans"); - Require(world.Zones.Values.All(zone => zone.Orbits.Length > 0 && zone.Bodies.Length > 0), - "no selectable tutorial zone may be published as an empty celestial shell"); - Require(world.Zones.Values.All(zone => - Math.Abs(zone.Mass - zone.HierarchyMass) <= Math.Max(0.01, zone.Mass * 0.00001)), - "every tutorial zone must conserve its authored mass independently"); - Require(world.Zones.Values.SelectMany(zone => zone.Bodies).Any(body => body.Kind == "asteroid_belt"), - "the complete tutorial world must retain fossil asteroid-belt generation"); - Require(world.Zones.Values.SelectMany(zone => zone.Bodies).Count(body => body.Kind == "sun") >= world.Zones.Count, - "every generated tutorial zone must contain at least one stellar primary"); - } - - private static void GeneratedZoneBodiesPreserveFossilHierarchy() - { - var zone = new AetheriaDaemonTutorialZoneTopology( - 7, - "Morrowind", - 0.42f, - 0.63f, - new[] { 3, 8 }, - new[] { "faction.miss" }, - "faction.miss"); - var first = AetheriaDaemonZoneBodyGenerator.Generate(0xA37E_2026u, zone, 1.25f); - var second = AetheriaDaemonZoneBodyGenerator.Generate(0xA37E_2026u, zone, 1.25f); - - RequireEqual(first.GenerationSeed, second.GenerationSeed, - "daemon zone generation must derive one stable seed from galaxy and zone identity"); - RequireNear(first.Radius, second.Radius, 0, - "fixed zone inputs must reproduce the authored radius curve exactly"); - RequireNear(first.Mass, first.HierarchyMass, Math.Max(0.01, first.Mass * 0.00001), - "planet hierarchy normalization must conserve the generated zone mass"); - Require(first.Radius >= 1000 && first.Radius <= 10000 && first.Mass >= 10000 && first.Mass <= 500000, - "zone radius and mass must remain inside the authored exponential-lerp bounds"); - Require(first.Orbits.Length >= first.Bodies.Length && first.Orbits.Length > 1, - "rosette placeholders may own orbits without bodies, but every generated zone must retain its hierarchy"); - var orbitKeys = first.Orbits.Select(orbit => orbit.OrbitKey).ToHashSet(StringComparer.Ordinal); - Require(first.Orbits.All(orbit => orbit.ParentOrbitKey.Length == 0 || orbitKeys.Contains(orbit.ParentOrbitKey)) && - first.Bodies.All(body => orbitKeys.Contains(body.OrbitKey)), - "every generated parent and body orbit reference must resolve inside the daemon plan"); - Require(first.Bodies.All(body => body.GravityInfluenceRadius > 0 && body.GravityWellDepth > 0 && - body.GravityWaveRadius > 0 && body.GravityWaveDepth > 0), - "generated body mass must project the authored positive gravity and wave curves for Ymir and Eve"); - Require(first.Bodies.Select(body => (body.BodyKey, body.Kind, body.Mass, body.OrbitKey, body.Asteroids.Length)) - .SequenceEqual(second.Bodies.Select(body => (body.BodyKey, body.Kind, body.Mass, body.OrbitKey, body.Asteroids.Length))), - "body kinds, masses, identities, orbits, and asteroid cardinality must be deterministic"); - Require(first.Bodies.Any(body => body.Kind == "sun") && first.Bodies.Any(body => body.Kind != "sun"), - "the fossil solar hierarchy must produce both a stellar primary and subordinate bodies"); - } - - private static void TutorialTopologyIsDeterministicConnectedAndRoleOwned() - { - AetheriaDaemonTutorialFactionInput Faction( - string selector, - string name, - string shortName, - int influence = 2) => - new($"faction.{selector.ToLowerInvariant()}", name, shortName, influence) - { - TrainingNames = Enumerable.Range(0, 512).Select(index => - $"{(char)('a' + index % 26)}{(char)('a' + index / 26 % 26)}{(char)('a' + index / 676 % 26)}{selector.ToLowerInvariant()}") - .ToArray() - }; - var factions = new[] - { - Faction("Miss", "Miss Terri's Sugariffic Snack Company", "Miss Terri's"), - Faction("Zhe", "Zhestokost", "Zhestokost"), - Faction("Luc", "Lucent Media", "Lucent"), - Faction("Aero", "Aeronautics Unlimited", "AU"), - Faction("Finch", "Finch Cybernetics", "Finch"), - Faction("Adras", "Adrasteia", "Adrasteia") - }; - var first = AetheriaDaemonTutorialTopologyGenerator.GenerateFossil(factions, 0xA37E_2026u); - var second = AetheriaDaemonTutorialTopologyGenerator.GenerateFossil(factions, 0xA37E_2026u); - RequireEqual(64, first.Zones.Count, - "the daemon tutorial must retain the authored 64-zone topology size"); - Require(first.Zones.All(zone => !string.IsNullOrWhiteSpace(zone.Name)) && - first.Zones.Select(zone => zone.Name).Distinct(StringComparer.Ordinal).Count() == first.Zones.Count, - "daemon tutorial naming must produce one stable unique catalog name per zone"); - Require(first.Zones.Select(zone => (zone.X, zone.Y, string.Join(',', zone.AdjacentZoneIndices))) - .SequenceEqual(second.Zones.Select(zone => (zone.X, zone.Y, string.Join(',', zone.AdjacentZoneIndices)))), - "tutorial topology must be deterministic for a fixed daemon seed and density field"); - RequireNear(first.NoisePosition, second.NoisePosition, 0, - "the daemon seed must reproduce the authored tutorial cloud field position"); - Require(AetheriaDaemonTutorialTopologyGenerator.TutorialCloudDensity(0.5f, 0.5f, first.NoisePosition) >= 0.5f, - "tutorial cloud selection must retain the fossil's dense-center admission gate"); - RequireEqual(6, first.HomeZoneByFactionKey.Count, - "every authored tutorial faction role must receive one daemon-owned home zone"); - Require(first.HomeZoneByFactionKey["faction.miss"] != first.HomeZoneByFactionKey["faction.zhe"], - "tutorial protagonist and antagonist homes must not collapse onto one zone"); - Require(first.Zones[first.EntranceZoneIndex].OwnerFactionKey.Length == 0, - "the tutorial entrance must retain the fossil's unowned-zone rule"); - Require(first.DiscoveredZoneIndices.SequenceEqual( - new[] { first.EntranceZoneIndex } - .Concat(first.Zones[first.EntranceZoneIndex].AdjacentZoneIndices) - .Distinct() - .OrderBy(value => value)), - "initial tutorial discovery must contain exactly the entrance and its adjacent zones"); - Require(first.Zones.All(zone => zone.AdjacentZoneIndices.Count > 0) && - first.Zones.All(zone => zone.AdjacentZoneIndices.All(neighbor => - first.Zones[neighbor].AdjacentZoneIndices.Contains(zone.ZoneIndex))), - "tutorial link pruning must preserve one connected undirected graph"); - var reachable = new HashSet { first.EntranceZoneIndex }; - var queue = new Queue(); - queue.Enqueue(first.EntranceZoneIndex); - while (queue.Count > 0) - { - foreach (var neighbor in first.Zones[queue.Dequeue()].AdjacentZoneIndices) - if (reachable.Add(neighbor)) queue.Enqueue(neighbor); - } - RequireEqual(first.Zones.Count, reachable.Count, - "tutorial link elimination must never strand a zone outside the playable graph"); - } - - private static void RetainedWorldAdvancesEveryFixedSubstep() - { - var ship = Entity(0, 0, "player"); - ship.VelocityX = 10; - var run = new AetheriaRuntimeRunCheckpointCommit - { - RunId = "retained-substep-smoke", - CurrentZoneIndex = 0, - CurrentEntityKey = "zone.0.entity.0", - Zones = [new AetheriaRuntimeZoneSnapshotCommit { ZoneIndex = 0, Entities = [ship] }] - }; - using var physics = NewPhysics(); - AetheriaRuntimeDaemonTickRunner.Tick( - Path.Combine(Path.GetTempPath(), "aetheria-retained-substeps.cc"), - run, - new AetheriaRuntimeDaemonTickOptions - { - WorldPhysics = physics, - FrameId = 1, - FixedDeltaSeconds = 0.1, - SimulationStepCount = 3, - SimulationTimeSeconds = 0.3, - BuildPublications = false - }); - RequireNear(3, ship.PositionX, 0.05, - "one retained Box3D world must advance once for every fixed substep, not once per publication frame"); - } - - private static void GeneratedOrbitsAdvanceOrbitalWorldTruth() - { - var orbital = Entity(0, 0, "neutral"); - orbital.Kind = "station"; - orbital.OrbitKey = "orbit.station"; - orbital.PositionX = 10; - var orbit = new AetheriaRuntimeOrbitSnapshotCommit - { - OrbitKey = orbital.OrbitKey, - Distance = 10, - Phase = 0, - Period = 10 - }; - var zone = new AetheriaRuntimeZoneSnapshotCommit - { - ZoneIndex = 0, - Entities = [orbital], - Orbits = [orbit] - }; - - AetheriaRuntimeOrbitSimulation.StepZone(zone, [orbital], 2); - - RequireNear(0.2, orbit.Phase, 0.000001, - "the canonical orbit phase must advance by fixed delta divided by authored period"); - var expectedX = Math.Cos(0.2 * Math.PI * 2) * 10; - var expectedZ = Math.Sin(0.2 * Math.PI * 2) * 10; - RequireNear(expectedX, orbital.PositionX, 0.000001, - "orbital entity X must derive from the same advanced orbit observed by render queries"); - RequireNear(expectedZ, orbital.PositionZ, 0.000001, - "orbital entity Z must derive from the same advanced orbit observed by render queries"); - RequireNear((expectedX - 10) / 2, orbital.VelocityX, 0.000001, - "orbital X velocity must be the fixed-step pose delta consumed by Ymir"); - RequireNear(expectedZ / 2, orbital.VelocityY, 0.000001, - "orbital Z velocity must be the fixed-step pose delta consumed by Ymir"); - - var run = new AetheriaRuntimeRunCheckpointCommit - { - RunId = "dynamic-orbit-smoke", - CurrentZoneIndex = 0, - CurrentEntityKey = "zone.0.entity.0", - Zones = [zone] - }; - using var physics = NewPhysics(); - AetheriaRuntimeDaemonSimulation.Step( - run, - new AetheriaRuntimeDaemonIntentState(), - 0.1, - AetheriaRuntimeDaemonSimulationSettings.AetheriaDefault, - physics, - frameId: 1, - simulationTimeSeconds: 2.1); - var integratedPhase = 0.21; - RequireNear(integratedPhase, orbit.Phase, 0.000001, - "the complete daemon step must advance the canonical orbit before Ymir"); - RequireNear(Math.Cos(integratedPhase * Math.PI * 2) * 10, orbital.PositionX, 0.05, - "Ymir must retain the daemon's advanced orbital X pose instead of restoring a stale body transform"); - RequireNear(Math.Sin(integratedPhase * Math.PI * 2) * 10, orbital.PositionZ, 0.05, - "Ymir must retain the daemon's advanced orbital Z pose instead of restoring a stale body transform"); - } - - private static void StableEntityIdentitySurvivesCrossZoneReindex() - { - var departing = Entity(0, 0, "player"); - departing.EntityId = "cross-zone.departing"; - var survivor = Entity(1, 100, "player"); - survivor.EntityId = "cross-zone.survivor"; - var source = new AetheriaRuntimeZoneSnapshotCommit - { - ZoneIndex = 0, - Entities = [departing, survivor] - }; - var destination = new AetheriaRuntimeZoneSnapshotCommit { ZoneIndex = 1, Entities = [] }; - var run = new AetheriaRuntimeRunCheckpointCommit - { - RunId = "cross-zone-retained-smoke", - CurrentZoneIndex = 0, - CurrentEntityKey = "zone.0.entity.0", - Zones = [source, destination] - }; - using var physics = NewPhysics(); - AetheriaRuntimeDaemonTickRunner.Tick( - Path.Combine(Path.GetTempPath(), "aetheria-cross-zone-before.cc"), - run, - new AetheriaRuntimeDaemonTickOptions - { - WorldPhysics = physics, - FrameId = 1, - FixedDeltaSeconds = 0.1, - SimulationTimeSeconds = 0.1, - BuildPublications = false - }); - - survivor.EntityIndex = 0; - departing.EntityIndex = 0; - departing.PositionX = 250; - source.Entities = [survivor]; - destination.Entities = [departing]; - run.CurrentZoneIndex = 1; - run.CurrentEntityKey = "zone.1.entity.0"; - AetheriaRuntimeDaemonTickRunner.Tick( - Path.Combine(Path.GetTempPath(), "aetheria-cross-zone-after.cc"), - run, - new AetheriaRuntimeDaemonTickOptions - { - WorldPhysics = physics, - FrameId = 2, - FixedDeltaSeconds = 0.1, - SimulationTimeSeconds = 0.2, - BuildPublications = false - }); - - RequireNear(100, survivor.PositionX, 0.05, - "source-zone reindex must not give a survivor the departed entity's retained Box3D body"); - RequireNear(250, departing.PositionX, 0.05, - "a cross-zone arrival must retain its stable identity while spawning in the destination session"); - } - - public void Run() - { - Action[] checks = - [ - PositiveGravityDepthAttractsAndProjectsAsAWell, - TutorialTopologyIsDeterministicConnectedAndRoleOwned, - RegularTopologyOwnsFossilSectorStructure, - RegularRunPersistsPlayableWorldTruth, - GeneratedZoneBodiesPreserveFossilHierarchy, - TutorialWorldMaterializesEveryTopologyZone, - GeneratedOrbitsAdvanceOrbitalWorldTruth, - SectorMapSurfacePublishesDiscoveredGraphAndVisualLevers, - VolumeSurfaceKeepsNativeShaderAbiInAssetVariant, - YmirMovesProjectileAndReportsStableContact, - CompressedTerminusStopsAtAttentionBoundary, - InstantWeaponRequestSurvivesLockAcquisition, - AutoWeaponRetriggersWhileHeldAndStopsOnRelease, - GuidedWeaponPublishesPresentationProfileWithoutPhysicalAuthority, - CombatLockSurvivesPublicFrameRestart, - ConstantWeaponRunsOnDaemonThroughYmirBeamContact, - ChargedWeaponCannotBypassChargeLifecycle, - ChargedWeaponHoldRiskMalfunctionsDeterministically, - DeployableWeaponRunsThroughYmirAndDetonatesOnDaemon, - DeployableRangeExpiryDetonatesAfterYmirMovement, - CanonicalCatalogPublishesRecoveredMineLauncher, - EnergyFundedShieldInterceptsDamageBeforeHull, - ArmorCellsResolveBeforeEquipmentAndHull, - PenetrationStopsAtFirstHullGap, - ShipSchematicProjectsDaemonTopologyAndEquipmentState, - EquipmentDestructionDisablesBehaviorAndCockpitKillsBeforeHull, - DestructionDropsLootExactlyOnce, - DaemonSimulationTreatsYmirHitAsPresentationOnly, - ProjectileContactCannotKill, - MissingWorldPhysicsOwnerCannotAdvanceShips, - DockingUsesRealBaysAndFossilUndockRules, - DockedEntitiesCannotAttackOrBeTargeted, - RefitIsAtomicAndUsesTypedPlacement, - CargoTransferUsesSpatialAtomicTransaction, - DockedRefitSurfacePublishesGenericCommands, - FossilVelocityBehaviorsRunInDaemonFlightStep, - BehaviorChainsGateAuthoritativeStatModifiers, - ResourceBehaviorsStopEquipmentChainsInAuthoredOrder, - MiningAndScanningResultsGateTrailingBehaviorChains, - BehaviorStateProjectionIsReadStableAndReconcilesShapeChanges, - ThermotoggleSeedsAndGatesFromDaemonOwnedTarget, - InputCapabilityPublishesExactBehaviorLevers, - InputCapabilityPublishesConsumableActionBarState, - InputCapabilityPublishesHeldWeaponGroupLever, - InputCapabilityPublishesNativeTargetAndInteractControls, - ReactiveGameplayPublishesOnlyNewEventFacts, - ReflectorUsesSharedStellarLightFieldWithoutAccumulating, - SensorPingIsActorScopedEnergyGatedAndReconnectable, - TurretControllerAcquiresAimsAndTriggersExactWeapons, - TractorRampsAndPullsThroughYmirWithoutTeleportingCargo, - PickupIsCapacityCheckedExactlyOnceAndExpires, - TradePurchaseDerivesAcceptanceFromDaemonState, - TradeSaleIsDaemonPricedSpatialAndAtomic, - StationBodiesCannotConsumePickups, - PickupProximityCollectsOrBounces, - CultCacheRestartPreservesLoadoutAndBehaviorState, - PickupRemovalSurvivesRestartWithoutReplay, - ThermalCellsUseFossilConductionAndRadiation, - EnergyNetworkSettlesReactorAfterConsumers, - RadiatorPumpsHeatBeforeReactorSettlement, - EquipmentThermalPerformanceOwnsShutdownAndWear, - ThermalTopologyComesFromHullAndEquipment, - ThermalMedicalExposureUsesCockpitTemperature, - ThermalMedicalDeathUsesOrdinaryDestructionPath, - MultipleActorsUseTheSameMovementLever, - DirectionalThrustersOwnOrdinaryFlightFeedback, - UnpoweredThrustersCannotMoveOrAdvertisePlume, - SpecializedBehaviorSuccessGatesTrailingWear, - RareAetherDriveSpoolsOnlyOnModifiedHullFixture, - LookDirectionRejectsInvalidVectorsWithoutMutatingTheShip, - AgentClaimsAndCompletesExploreTaskThroughCommands, - AgentTaskSurvivesCultCacheRestart, - SchedulerAssignsHighestPriorityCompatibleTask, - SchedulerRequeuesTaskFromDeadAgent, - SchedulerCollapsesDuplicateAssignmentMarkers, - CancellingAgentTaskReleasesSchedulerControls, - SchedulerAssignsShortestGalaxyRoute, - WormholeTraversalUsesPersistedEnterTransferExitPhases, - AgentTraversesGalaxyRouteBeforeExecutingTask, - IdleAgentReturnsToCanonicalHomeAndDocks, - ControlledShipDoesNotReceiveAutonomousHelmCommands, - AttackAgentControlsOptimumRangeThroughMovementLever, - AttackTaskAdmissionRejectsImpossibleWork, - AgentCompletesAttackTaskThroughTargetFireAndYmir, - AgentCompletesHaulTaskThroughMovementAndCargoCommands, - RejectedHaulTransferDoesNotAdvanceTask, - AgentPatrolsHistoricalOrbitCircuitThroughMovementCommands, - TickReconcilesAndEvaluatesCatalogBehaviors, - DaemonLoadoutsRespectFactionAvailabilityAndHullRoles, - AgentMinesAsteroidThroughEquippedBehavior, - CargoCapacityComesFromHullAndCatalogVolumes, - AgentSurveysBodyIntoCorporationKnowledge, - AgentTowsStationIntoPersistentOrbit - ]; - foreach (var check in checks) - RunCheck(check); - } - - private static void CompressedTerminusStopsAtAttentionBoundary() - { - var source = Entity(0, -100, "player"); - var target = Entity(1, 0, "raider"); - var run = new AetheriaRuntimeRunCheckpointCommit - { - RunId = "terminus-attention-smoke", - CurrentZoneIndex = 0, - CurrentEntityKey = "zone.0.entity.0", - Zones = [new AetheriaRuntimeZoneSnapshotCommit - { - ZoneIndex = 0, - Entities = [source, target], - PhysicalPayloads = [new AetheriaRuntimePhysicalPayloadCommit - { - PayloadId = "attention-mine", - PayloadKind = "mine", - WeaponItemKey = "attention-charge", - SourceEntityIndex = source.EntityIndex, - PositionX = target.PositionX, - PositionZ = target.PositionZ, - LifetimeSeconds = 30, - BlastRadius = 200, - PayloadMagnitude = 10, - DamageType = "Kinetic", - TriggeredAtSeconds = 0, - DetonationDelaySeconds = 0, - Stationary = true, - Active = true - }] - }] - }; - - var tick = AetheriaRuntimeDaemonTickRunner.Tick( - Path.Combine(Path.GetTempPath(), "aetheria-terminus-attention-smoke.cc"), - run, - new AetheriaRuntimeDaemonTickOptions - { - FrameId = 7, - SimulationTimeSeconds = 0.4, - FixedDeltaSeconds = 0.1, - AdvanceSimulation = true, - SimulationStepCount = 4, - StopCompressedSimulationOnAttention = true, - WorldPhysics = NewPhysics(), - BuildPublications = false - }); - - RequireEqual(1, tick.SimulationStepsExecuted, - "compressed Terminus must stop on the internal step that produces a decision event"); - RequireNear(0.1, tick.Frame.SimulationTimeSeconds, 0.000001, - "authoritative frame time must stop at the attention boundary instead of claiming the requested batch horizon"); - Require(tick.AttentionInterruption != null && - tick.AttentionInterruption.CauseKind == "entity.damaged" && - Math.Abs(tick.AttentionInterruption.SimulationTimeSeconds - 0.1) < 0.000001, - "tick result must retain the exact event and simulation time that interrupted compression"); - RequireNear(90, Stat(target, "hull"), 0.000001, - "the causal damage step must commit before compressed simulation pauses"); - var interruption = run.GameEvents.Single(value => value.Kind == "simulation.interrupted"); - Require(interruption.Reason == "entity.damaged" && interruption.ScalarValue == 4 && - interruption.AuxiliaryValue == 1, - "daemon must publish requested versus executed steps with the attention cause"); - tick.Frame.GameMode = "terminus"; - tick.Frame.RequestedSimulationRate = 4; - tick.Frame.EffectiveSimulationRate = 0; - tick.Frame.SimulationStepsExecuted = tick.SimulationStepsExecuted; - tick.Frame.AttentionCauseKind = tick.AttentionInterruption?.CauseKind ?? ""; - var surface = AetheriaRuntimeDaemonGameSurfaceBuilder.Build( - tick.Frame, - new AetheriaRuntimeDaemonHealthDocument(), - AetheriaRuntimeDaemonCommandBoundaryDocument.Create("daemon")); - var reactiveMount = Flatten(surface.Surface.Root).Single(node => - node.Id == "aetheria.daemon.game.reactive"); - var reactiveSlot = reactiveMount.EmbeddedDocuments.Single(); - Require(reactiveSlot.DocumentId == AetheriaRuntimeVerseRecordKeys.DaemonGameReactiveSurface.ToString() && - reactiveSlot.SchemaId == EveSurfaceDocument.SchemaId && - reactiveSlot.RouteHint.Kind == CultMeshLocalityKind.SharedMemory, - "the static pilot surface must advertise one exact, mapped-friendly reactive gameplay document"); - Require(!Flatten(surface.Surface.Root).Any(node => - node.Kind is "feedback.event" or "shot.receipt"), - "the static pilot surface must not retain mutable gameplay facts"); - var reactive = AetheriaRuntimeDaemonGameSurfaceBuilder.BuildReactiveGameplay(tick.Frame); - Require(reactive.Version == interruption.FrameId, - "reactive Eve surfaces and command receipts must share daemon frame-version semantics"); - Require(Flatten(reactive.Surface.Root).Any(node => node.Kind == "feedback.event" && - node.Props["eventKind"] == "simulation.interrupted" && - node.Props["reason"] == "entity.damaged" && - node.Props["scalarValue"] == "4" && node.Props["auxiliaryValue"] == "1"), - "generic Eve feedback must expose why compressed world time stopped"); - var world = Flatten(surface.Surface.Root).Single(node => node.Kind == "world.scene3d"); - Require(world.Props["gameMode"] == "terminus" && - world.Props["requestedSimulationRate"] == "4" && - world.Props["effectiveSimulationRate"] == "0" && - world.Props["simulationStepsExecuted"] == "1" && - world.Props["attentionCauseKind"] == "entity.damaged", - "generic Eve world state must expose requested and effective clock state without owning it"); - var restoredFrame = MessagePack.MessagePackSerializer.Deserialize( - MessagePack.MessagePackSerializer.Serialize(tick.Frame)); - Require(restoredFrame.RequestedSimulationRate == 4 && restoredFrame.EffectiveSimulationRate == 0 && - restoredFrame.SimulationStepsExecuted == 1 && restoredFrame.AttentionCauseKind == "entity.damaged", - "clock interruption state must survive authoritative frame serialization"); - } - - private static void BehaviorChainsGateAuthoritativeStatModifiers() - { - var thruster = CatalogItem("modifier-target", new AetheriaRuntimeBehaviorPayload( - 0, "Thruster", 0, - [new AetheriaRuntimeBehaviorField(1, PerformanceStat(10))])); - var controls = CatalogItem("modifier-controls", - new AetheriaRuntimeBehaviorPayload(0, "Switch", 0, []), - new AetheriaRuntimeBehaviorPayload(1, "StatModifier", 0, - [ - new AetheriaRuntimeBehaviorField(1, StatReference("ThrusterData", "Thrust")), - new AetheriaRuntimeBehaviorField(2, PerformanceStat(2)), - new AetheriaRuntimeBehaviorField(3, StringValue("Multiplier")) - ]), - new AetheriaRuntimeBehaviorPayload(2, "Trigger", 1, []), - new AetheriaRuntimeBehaviorPayload(3, "StatModifier", 1, - [ - new AetheriaRuntimeBehaviorField(1, StatReference("Thruster", "Thrust")), - new AetheriaRuntimeBehaviorField(2, PerformanceStat(5)), - new AetheriaRuntimeBehaviorField(3, StringValue("Constant")) - ])); - var catalog = new AetheriaRuntimeCatalogSnapshot([thruster, controls], [], []); - var entity = Entity(0, 0, "player"); - entity.Equipment = - [ - new AetheriaRuntimeLoadoutItemSlotCommit { Item = new AetheriaRuntimeLoadoutItemCommit - { ItemKey = thruster.ItemKey, Enabled = true, Durability = 1, Quality = 1 } }, - new AetheriaRuntimeLoadoutItemSlotCommit { Item = new AetheriaRuntimeLoadoutItemCommit - { ItemKey = controls.ItemKey, Enabled = true, Durability = 1, Quality = 1 } } - ]; - var activate = new[] - { - new AetheriaRuntimeDaemonBehaviorIntent - { ActorEntityKey = "zone.0.entity.0", EquipmentIndex = 1, BehaviorIndex = 0, Active = true }, - new AetheriaRuntimeDaemonBehaviorIntent - { ActorEntityKey = "zone.0.entity.0", EquipmentIndex = 1, BehaviorIndex = 2, Active = true } - }; - - AetheriaRuntimeBehaviorSimulation.Step(0, [entity], - [new AetheriaRuntimeDaemonBehaviorIntent - { ActorEntityKey = "zone.1.entity.0", EquipmentIndex = 1, BehaviorIndex = 0, Active = true }], - catalog, 0.1); - var target = AetheriaRuntimeEquippedBehaviorQueries.FindOperational(entity, catalog, "Thruster").Single(); - RequireNear(10, target.EvaluateStat(1), 0.000001, - "a behavior command for the same entity index in another zone must not cross-activate this ship"); - - AetheriaRuntimeBehaviorSimulation.Step(0, [entity], activate, catalog, 0.1); - RequireNear(25, target.EvaluateStat(1), 0.000001, - "switch and one-shot trigger groups must multiply then add through the shared daemon stat path"); - var modifierStates = entity.BehaviorStates.Where(value => value.BehaviorKind == "StatModifier").ToArray(); - Require(modifierStates.All(value => value.StatModifierApplied && value.StatModifierTargetStatCount == 1), - "applied modifier state must name the exact live target count"); - - AetheriaRuntimeBehaviorSimulation.Step(0, [entity], [], catalog, 0.1); - RequireNear(20, target.EvaluateStat(1), 0.000001, - "trigger modifiers must disappear on the next tick without another pull"); - - AetheriaRuntimeBehaviorSimulation.Step(0, [entity], - [new AetheriaRuntimeDaemonBehaviorIntent - { ActorEntityKey = "zone.0.entity.0", EquipmentIndex = 1, BehaviorIndex = 0, Active = false }], - catalog, 0.1); - RequireNear(10, target.EvaluateStat(1), 0.000001, - "deactivating the switch must restore the unmodified authored stat exactly"); - } - - private static void ResourceBehaviorsStopEquipmentChainsInAuthoredOrder() - { - var targetItem = CatalogItem("resource-target", new AetheriaRuntimeBehaviorPayload( - 0, "Thruster", 0, [new AetheriaRuntimeBehaviorField(1, PerformanceStat(10))])); - var controlItem = CatalogItem("resource-controls", - new AetheriaRuntimeBehaviorPayload(0, "Switch", 0, []), - new AetheriaRuntimeBehaviorPayload(1, "Cooldown", 0, - [new AetheriaRuntimeBehaviorField(1, PerformanceStat(1))]), - new AetheriaRuntimeBehaviorPayload(2, "EnergyDraw", 0, - [ - new AetheriaRuntimeBehaviorField(1, PerformanceStat(5)), - new AetheriaRuntimeBehaviorField(2, BoolValue(false)) - ]), - new AetheriaRuntimeBehaviorPayload(3, "Heat", 0, - [ - new AetheriaRuntimeBehaviorField(1, PerformanceStat(10)), - new AetheriaRuntimeBehaviorField(2, BoolValue(false)) - ]), - new AetheriaRuntimeBehaviorPayload(4, "ItemUsage", 0, - [new AetheriaRuntimeBehaviorField(1, ItemKeyValue("resource-ammo"))]), - new AetheriaRuntimeBehaviorPayload(5, "Wear", 0, - [new AetheriaRuntimeBehaviorField(1, BoolValue(true))]), - new AetheriaRuntimeBehaviorPayload(6, "StatModifier", 0, - [ - new AetheriaRuntimeBehaviorField(1, StatReference("Thruster", "Thrust")), - new AetheriaRuntimeBehaviorField(2, PerformanceStat(2)), - new AetheriaRuntimeBehaviorField(3, StringValue("Multiplier")) - ])); - var capacitorItem = CatalogItem("resource-capacitor", CapacitorPayload(10, 1)); - var ammoItem = CatalogItem("resource-ammo"); - var catalog = new AetheriaRuntimeCatalogSnapshot( - [targetItem, controlItem, capacitorItem, ammoItem], [], []); - var entity = Entity(0, 0, "player"); - entity.Equipment = - [ - Equipment(targetItem.ItemKey), - Equipment(controlItem.ItemKey), - Equipment(capacitorItem.ItemKey) - ]; - entity.CargoContents = [new AetheriaRuntimeCargoBayLoadoutCommit - { - Items = [new AetheriaRuntimeLoadoutItemSlotCommit - { Item = new AetheriaRuntimeLoadoutItemCommit { ItemKey = ammoItem.ItemKey, Quantity = 2 } }] - }]; - AetheriaRuntimeThermalSimulation.EnsureTopology(entity, catalog); - AetheriaRuntimeThermalSimulation.EnsureState(entity); - AetheriaRuntimeThermalSimulation.UpdateEquipmentStates(entity, catalog, 0.1); - AetheriaRuntimeBehaviorStateProjector.EnsureEquipmentBehaviorStates(entity, catalog); - AetheriaRuntimeEnergySimulation.BeginTick(entity, catalog); - var capacitor = AetheriaRuntimeEquippedBehaviorQueries.Find(entity, catalog, "Capacitor").Single(); - capacitor.State.CapacitorCharge = 10; - var controlEquipmentState = entity.EquipmentStates.Single(value => value.EquipmentIndex == 1); - controlEquipmentState.Wear = 2; - var initialTemperature = AetheriaRuntimeThermalSimulation.EquipmentTemperature(entity, catalog, 1); - var target = AetheriaRuntimeEquippedBehaviorQueries.FindOperational(entity, catalog, "Thruster").Single(); - - AetheriaRuntimeBehaviorSimulation.Step(0, [entity], - [new AetheriaRuntimeDaemonBehaviorIntent - { ActorEntityKey = "zone.0.entity.0", EquipmentIndex = 1, BehaviorIndex = 0, Active = true }], - catalog, 0.1); - RequireNear(10, capacitor.State.CapacitorCharge, 0.000001, - "a newly constructed cooldown must stop its group before any resource transaction"); - RequireEqual(2, CargoQuantity(entity, ammoItem.ItemKey), - "cooldown must stop item usage before cargo mutation"); - - AetheriaRuntimeBehaviorSimulation.Step(0, [entity], [], catalog, 0.1); - RequireNear(5, capacitor.State.CapacitorCharge, 0.000001, - "energy draw must debit the daemon capacitor exactly once when the cooldown opens"); - RequireEqual(1, CargoQuantity(entity, ammoItem.ItemKey), - "item usage must remove exactly one authored cargo item"); - RequireNear(0.8, entity.Equipment[1].Item.Durability, 0.000001, - "wear must use the thermal owner's pressure only after preceding gates succeed"); - Require(AetheriaRuntimeThermalSimulation.EquipmentTemperature(entity, catalog, 1) > initialTemperature, - "heat must enter the source equipment's authoritative thermal cells"); - RequireNear(20, target.EvaluateStat(1), 0.000001, - "downstream modifiers must apply only after every preceding resource gate succeeds"); - RequireNear(0.9, entity.BehaviorProgress.Single(value => - value.OwnerIndex == 1 && value.BehaviorKind == "Cooldown").Progress, 0.000001, - "cooldown state must project a generic Eve progress lever from the authoritative behavior row"); - - var cooldown = entity.BehaviorStates.Single(value => - value.OwnerIndex == 1 && value.BehaviorKind == "Cooldown"); - cooldown.CooldownProgress = -1; - capacitor.State.CapacitorCharge = 0; - var temperatureBeforeRefusal = AetheriaRuntimeThermalSimulation.EquipmentTemperature(entity, catalog, 1); - var durabilityBeforeRefusal = entity.Equipment[1].Item.Durability; - AetheriaRuntimeBehaviorSimulation.Step(0, [entity], [], catalog, 0.1); - RequireEqual(1, CargoQuantity(entity, ammoItem.ItemKey), - "an empty capacitor must stop the group before item usage"); - RequireNear(durabilityBeforeRefusal, entity.Equipment[1].Item.Durability, 0.000001, - "an empty capacitor must stop the group before wear"); - RequireNear(temperatureBeforeRefusal, - AetheriaRuntimeThermalSimulation.EquipmentTemperature(entity, catalog, 1), 0.000001, - "an empty capacitor must stop the group before heat"); - RequireNear(10, target.EvaluateStat(1), 0.000001, - "an empty capacitor must remove a downstream modifier instead of leaving stale application state"); - - static AetheriaRuntimeLoadoutItemSlotCommit Equipment(string itemKey) => new() - { - Item = new AetheriaRuntimeLoadoutItemCommit - { ItemKey = itemKey, Enabled = true, Durability = 1, Quality = 1 } - }; - } - - private static void MiningAndScanningResultsGateTrailingBehaviorChains() - { - var miningSuccess = MiningScenario(0, "mining-chain-success"); - AetheriaRuntimeDaemonSimulation.Step( - miningSuccess.Run, - miningSuccess.Intents, - 0.1, - AetheriaRuntimeDaemonSimulationSettings.AetheriaDefault, - NewPhysics(), - miningSuccess.Catalog, - 1, - 0.1); - RequireEqual(0, CargoQuantity(miningSuccess.Entity, "mining-chain-charge"), - "a successful in-range MiningTool must resume its authored trailing ItemUsage exactly once"); - var successfulMiningState = miningSuccess.Entity.BehaviorStates.Single(value => - value.BehaviorKind == "MiningTool"); - Require(successfulMiningState.ChainReached && successfulMiningState.ChainSucceeded && - successfulMiningState.ChainCompleted, - "MiningTool success must be the explicit owner that completes its deferred behavior group"); - - var miningFailure = MiningScenario(100, "mining-chain-failure"); - AetheriaRuntimeDaemonSimulation.Step( - miningFailure.Run, - miningFailure.Intents, - 0.1, - AetheriaRuntimeDaemonSimulationSettings.AetheriaDefault, - NewPhysics(), - miningFailure.Catalog, - 1, - 0.1); - RequireEqual(1, CargoQuantity(miningFailure.Entity, "mining-chain-charge"), - "an out-of-range MiningTool must stop before trailing cargo mutation"); - Require(!miningFailure.Entity.BehaviorStates.Single(value => - value.BehaviorKind == "MiningTool").ChainSucceeded && - !miningFailure.Entity.BehaviorStates.Single(value => - value.BehaviorKind == "ItemUsage").ChainReached, - "MiningTool failure must leave every trailing behavior unreachable"); - - var scanSuccess = ScannerScenario(0, "scanner-chain-success"); - AetheriaRuntimeDaemonSimulation.Step( - scanSuccess.Run, - scanSuccess.Intents, - 0.1, - AetheriaRuntimeDaemonSimulationSettings.AetheriaDefault, - NewPhysics(), - scanSuccess.Catalog, - 1, - 0.1); - RequireEqual(0, CargoQuantity(scanSuccess.Entity, "scanner-chain-charge"), - "a successful in-range ResourceScanner must resume its authored trailing ItemUsage exactly once"); - var successfulScannerState = scanSuccess.Entity.BehaviorStates.Single(value => - value.BehaviorKind == "ResourceScanner"); - Require(successfulScannerState.ChainReached && successfulScannerState.ChainSucceeded && - successfulScannerState.ChainCompleted, - "ResourceScanner success must be the explicit owner that completes its deferred behavior group"); - - var scanFailure = ScannerScenario(100, "scanner-chain-failure"); - AetheriaRuntimeDaemonSimulation.Step( - scanFailure.Run, - scanFailure.Intents, - 0.1, - AetheriaRuntimeDaemonSimulationSettings.AetheriaDefault, - NewPhysics(), - scanFailure.Catalog, - 1, - 0.1); - RequireEqual(1, CargoQuantity(scanFailure.Entity, "scanner-chain-charge"), - "an out-of-range ResourceScanner must stop before trailing cargo mutation"); - Require(!scanFailure.Entity.BehaviorStates.Single(value => - value.BehaviorKind == "ResourceScanner").ChainSucceeded && - !scanFailure.Entity.BehaviorStates.Single(value => - value.BehaviorKind == "ItemUsage").ChainReached, - "ResourceScanner failure must leave every trailing behavior unreachable"); - - static (AetheriaRuntimeRunCheckpointCommit Run, - AetheriaRuntimeDaemonIntentState Intents, - AetheriaRuntimeCatalogSnapshot Catalog, - AetheriaRuntimeEntitySnapshotCommit Entity) MiningScenario(double entityX, string runId) - { - var entity = Entity(0, entityX, "workers"); - entity.Equipment = [new AetheriaRuntimeLoadoutItemSlotCommit - { - Item = new AetheriaRuntimeLoadoutItemCommit - { ItemKey = "mining-chain-tool", Enabled = true, Durability = 1, Quality = 1 } - }]; - entity.CargoContents = [Cargo(("mining-chain-charge", 1, 0, 0))]; - var tool = CatalogItem("mining-chain-tool", - new AetheriaRuntimeBehaviorPayload(0, "MiningTool", 0, - [ - new AetheriaRuntimeBehaviorField(1, PerformanceStat(0)), - new AetheriaRuntimeBehaviorField(2, PerformanceStat(0)), - new AetheriaRuntimeBehaviorField(3, PerformanceStat(1)), - new AetheriaRuntimeBehaviorField(4, PerformanceStat(10)) - ]), - new AetheriaRuntimeBehaviorPayload(1, "ItemUsage", 0, - [new AetheriaRuntimeBehaviorField(1, ItemKeyValue("mining-chain-charge"))])); - var catalog = new AetheriaRuntimeCatalogSnapshot( - [tool, CatalogItem("mining-chain-charge")], [], []); - var run = new AetheriaRuntimeRunCheckpointCommit - { - RunId = runId, - CurrentZoneIndex = 0, - CurrentEntityKey = "zone.0.entity.0", - Zones = [new AetheriaRuntimeZoneSnapshotCommit - { - ZoneIndex = 0, - Entities = [entity], - Bodies = [new AetheriaRuntimeBodySnapshotCommit - { - BodyKey = "mining-chain-belt", - Kind = "asteroid_belt", - Asteroids = [new AetheriaRuntimeAsteroidCommit { Distance = 0, Size = 6 }] - }] - }] - }; - var intents = new AetheriaRuntimeDaemonIntentState(); - intents.Behaviors.Add(new AetheriaRuntimeDaemonBehaviorIntent - { - ActorEntityKey = run.CurrentEntityKey, - EquipmentIndex = 0, - BehaviorIndex = 0, - Active = true, - TargetBodyKey = "mining-chain-belt", - TargetAsteroidIndex = 0 - }); - return (run, intents, catalog, entity); - } - - static (AetheriaRuntimeRunCheckpointCommit Run, - AetheriaRuntimeDaemonIntentState Intents, - AetheriaRuntimeCatalogSnapshot Catalog, - AetheriaRuntimeEntitySnapshotCommit Entity) ScannerScenario(double entityX, string runId) - { - var entity = Entity(0, entityX, "workers"); - entity.Equipment = [new AetheriaRuntimeLoadoutItemSlotCommit - { - Item = new AetheriaRuntimeLoadoutItemCommit - { ItemKey = "scanner-chain-tool", Enabled = true, Durability = 1, Quality = 1 } - }]; - entity.CargoContents = [Cargo(("scanner-chain-charge", 1, 0, 0))]; - var scanner = CatalogItem("scanner-chain-tool", - new AetheriaRuntimeBehaviorPayload(0, "ResourceScanner", 0, - [ - new AetheriaRuntimeBehaviorField(1, PerformanceStat(10)), - new AetheriaRuntimeBehaviorField(2, PerformanceStat(1)), - new AetheriaRuntimeBehaviorField(3, PerformanceStat(10)) - ]), - new AetheriaRuntimeBehaviorPayload(1, "ItemUsage", 0, - [new AetheriaRuntimeBehaviorField(1, ItemKeyValue("scanner-chain-charge"))])); - var catalog = new AetheriaRuntimeCatalogSnapshot( - [scanner, CatalogItem("scanner-chain-charge")], [], []); - var run = new AetheriaRuntimeRunCheckpointCommit - { - RunId = runId, - CurrentZoneIndex = 0, - CurrentEntityKey = "zone.0.entity.0", - Zones = [new AetheriaRuntimeZoneSnapshotCommit - { - ZoneIndex = 0, - Entities = [entity], - Orbits = [new AetheriaRuntimeOrbitSnapshotCommit - { OrbitKey = "scanner-chain-orbit", FixedPositionX = 0, FixedPositionY = 0 }], - Bodies = [new AetheriaRuntimeBodySnapshotCommit - { - BodyKey = "scanner-chain-world", - Kind = "planet", - OrbitKey = "scanner-chain-orbit" - }] - }] - }; - var intents = new AetheriaRuntimeDaemonIntentState(); - intents.Behaviors.Add(new AetheriaRuntimeDaemonBehaviorIntent - { - ActorEntityKey = run.CurrentEntityKey, - EquipmentIndex = 0, - BehaviorIndex = 0, - Active = true, - TargetBodyKey = "scanner-chain-world", - TargetAsteroidIndex = -1 - }); - return (run, intents, catalog, entity); - } - } - - private static void BehaviorStateProjectionIsReadStableAndReconcilesShapeChanges() - { - var thermostat = CatalogItem("projection-thermostat", - new AetheriaRuntimeBehaviorPayload(0, "Thermotoggle", 0, - [ - new AetheriaRuntimeBehaviorField(1, Number(300)) - ])); - var catalog = new AetheriaRuntimeCatalogSnapshot([thermostat], [], []); - var entity = Entity(0, 0, "projection-player"); - entity.Equipment = [new AetheriaRuntimeLoadoutItemSlotCommit - { - Item = new AetheriaRuntimeLoadoutItemCommit - { ItemKey = thermostat.ItemKey, Enabled = true, Durability = 1, Quality = 1 } - }]; - - AetheriaRuntimeBehaviorStateProjector.EnsureEquipmentBehaviorStates(entity, catalog); - var originalStates = entity.BehaviorStates; - var originalThermostat = originalStates.Single(); - originalThermostat.ThermotoggleTargetTemperature = 412; - - AetheriaRuntimeBehaviorStateProjector.EnsureEquipmentBehaviorStates(entity, catalog); - Require(ReferenceEquals(originalStates, entity.BehaviorStates), - "an unchanged equipment graph must remain a read and preserve the behavior-state array"); - RequireNear(412, entity.BehaviorStates.Single().ThermotoggleTargetTemperature, 0.000001, - "an unchanged equipment graph must preserve daemon-owned mutable behavior state"); - - var expandedThermostat = CatalogItem("projection-thermostat", - new AetheriaRuntimeBehaviorPayload(0, "Thermotoggle", 0, - [ - new AetheriaRuntimeBehaviorField(1, Number(300)) - ]), - new AetheriaRuntimeBehaviorPayload(1, "Radiator", 0, [])); - var expandedCatalog = new AetheriaRuntimeCatalogSnapshot([expandedThermostat], [], []); - AetheriaRuntimeBehaviorStateProjector.EnsureEquipmentBehaviorStates(entity, expandedCatalog); - - Require(!ReferenceEquals(originalStates, entity.BehaviorStates), - "a changed catalog behavior shape must reconcile the projected behavior-state array"); - RequireEqual(2, entity.BehaviorStates.Count, - "catalog reconciliation must add the newly authored behavior state exactly once"); - Require(ReferenceEquals(originalThermostat, - entity.BehaviorStates.Single(state => state.BehaviorKind == "Thermotoggle")), - "catalog reconciliation must retain matching daemon-owned behavior state"); - RequireNear(412, - entity.BehaviorStates.Single(state => state.BehaviorKind == "Thermotoggle") - .ThermotoggleTargetTemperature, - 0.000001, - "catalog reconciliation must not reset retained mutable behavior state"); - } - - private static void ThermotoggleSeedsAndGatesFromDaemonOwnedTarget() - { - var thermostat = CatalogItem("thermostat", - new AetheriaRuntimeBehaviorPayload(0, "Thermotoggle", 0, - [ - new AetheriaRuntimeBehaviorField(1, Number(300)), - new AetheriaRuntimeBehaviorField(2, BoolValue(false)), - new AetheriaRuntimeBehaviorField(3, BoolValue(true)) - ]), - new AetheriaRuntimeBehaviorPayload(1, "Radiator", 0, - [ - new AetheriaRuntimeBehaviorField(1, PerformanceStat(0)), - new AetheriaRuntimeBehaviorField(2, PerformanceStat(1)), - new AetheriaRuntimeBehaviorField(3, Number(200)), - new AetheriaRuntimeBehaviorField(4, PerformanceStat(0)), - new AetheriaRuntimeBehaviorField(5, PerformanceStat(0)), - new AetheriaRuntimeBehaviorField(6, PerformanceStat(10)) - ]), - new AetheriaRuntimeBehaviorPayload(2, "Heat", 0, - [ - new AetheriaRuntimeBehaviorField(1, PerformanceStat(10)), - new AetheriaRuntimeBehaviorField(2, BoolValue(false)) - ]), - new AetheriaRuntimeBehaviorPayload(3, "Wear", 0, - [new AetheriaRuntimeBehaviorField(1, BoolValue(true))])); - var catalog = new AetheriaRuntimeCatalogSnapshot([thermostat], [], []); - var entity = Entity(0, 0, "player"); - entity.HeatsinksEnabled = true; - entity.Equipment = [new AetheriaRuntimeLoadoutItemSlotCommit - { - Item = new AetheriaRuntimeLoadoutItemCommit - { ItemKey = thermostat.ItemKey, Enabled = true, Durability = 1, Quality = 1 } - }]; - AetheriaRuntimeThermalSimulation.EnsureTopology(entity, catalog); - AetheriaRuntimeThermalSimulation.EnsureState(entity); - AetheriaRuntimeThermalSimulation.UpdateEquipmentStates(entity, catalog, 0.1); - entity.EquipmentStates.Single().Wear = 1; - AetheriaRuntimeBehaviorStateProjector.EnsureEquipmentBehaviorStates(entity, catalog); - var thermostatState = entity.BehaviorStates.Single(value => value.BehaviorKind == "Thermotoggle"); - RequireNear(300, thermostatState.ThermotoggleTargetTemperature, 0.000001, - "new thermotoggle state must seed from the authored target instead of zero"); - var run = new AetheriaRuntimeRunCheckpointCommit - { - RunId = "thermotoggle-smoke", - CurrentZoneIndex = 0, - CurrentEntityKey = "zone.0.entity.0", - Zones = [new AetheriaRuntimeZoneSnapshotCommit { ZoneIndex = 0, Entities = [entity] }] - }; - var context = new AetheriaRuntimeDaemonOperationContext { Catalog = catalog }; - - SetTarget(200, 1); - var before = AetheriaRuntimeThermalSimulation.EquipmentTemperature(entity, catalog, 0); - AetheriaRuntimeBehaviorSimulation.Step(0, [entity], [], catalog, 0.1); - AetheriaRuntimeEnergySimulation.StepRadiators(entity, catalog, 0.1); - AetheriaRuntimeBehaviorSimulation.CompleteDeferredChains(entity, catalog, 0.1); - RequireNear(before, AetheriaRuntimeThermalSimulation.EquipmentTemperature(entity, catalog, 0), 0.000001, - "a low-pass thermotoggle above its target must stop downstream heat"); - Require(!entity.BehaviorStates.Single(value => value.BehaviorKind == "Heat").ChainReached, - "a closed thermostat must keep downstream specialized and common behaviors unreachable"); - Require(!entity.BehaviorStates.Single(value => value.BehaviorKind == "Radiator").ChainSucceeded, - "a closed thermostat must keep the radiator Execute path from succeeding"); - RequireNear(0, entity.BehaviorStates.Single(value => value.BehaviorKind == "Radiator").PumpedHeat, 0.000001, - "a closed thermostat must not refresh Execute-owned radiator throughput"); - RequireNear(1, entity.Equipment[0].Item.Durability, 0.000001, - "a closed thermostat must not apply trailing wear"); - - SetTarget(300, 2); - AetheriaRuntimeBehaviorSimulation.Step(0, [entity], [], catalog, 0.1); - AetheriaRuntimeEnergySimulation.StepRadiators(entity, catalog, 0.1); - AetheriaRuntimeBehaviorSimulation.CompleteDeferredChains(entity, catalog, 0.1); - Require(AetheriaRuntimeThermalSimulation.EquipmentTemperature(entity, catalog, 0) > before, - "a low-pass thermotoggle below its target must admit downstream heat"); - Require(entity.BehaviorStates.Single(value => value.BehaviorKind == "Heat").ChainReached, - "an open thermostat must publish that the downstream behavior was reached this tick"); - RequireNear(1, entity.BehaviorStates.Single(value => value.BehaviorKind == "Radiator").PumpedHeat, 0.000001, - "an open thermostat must admit the radiator subsystem through the same chain state"); - RequireNear(0.9, entity.Equipment[0].Item.Durability, 0.000001, - "a successful radiator must apply its authored trailing wear exactly once"); - - var radiatorPayload = thermostat.BehaviorPayloads.Single(value => value.Kind == "Radiator"); - radiatorPayload.Fields = radiatorPayload.Fields.Select(field => field.Key == 5 - ? new AetheriaRuntimeBehaviorField(5, PerformanceStat(10)) - : field).ToArray(); - var durabilityBeforeEnergyRefusal = entity.Equipment[0].Item.Durability; - AetheriaRuntimeBehaviorSimulation.Step(0, [entity], [], catalog, 0.1); - AetheriaRuntimeEnergySimulation.StepRadiators(entity, catalog, 0.1); - AetheriaRuntimeBehaviorSimulation.CompleteDeferredChains(entity, catalog, 0.1); - Require(!entity.BehaviorStates.Single(value => value.BehaviorKind == "Radiator").ChainSucceeded, - "an unfunded radiator must report its fossil Execute failure"); - RequireNear(durabilityBeforeEnergyRefusal, entity.Equipment[0].Item.Durability, 0.000001, - "an unfunded radiator must stop the chain before trailing wear"); - - void SetTarget(double target, long sequence) - { - var command = AetheriaRuntimeDaemonCommandDocument.Create( - AetheriaRuntimeDaemonCommandKinds.SetThermotoggleTargetTemperature, - "smoke", "thermostat-session", sequence, "zone.0.entity.0"); - command.TargetEntityKey = "zone.0.entity.0"; - command.EquipmentIndex = 0; - command.BehaviorIndex = 0; - command.ScalarValue = target; - var result = AetheriaRuntimeDaemonOperations.Execute(run, [command], context); - RequireEqual(1, result.AppliedCommandIds.Count, - "an installed adjustable thermotoggle must accept its typed daemon command"); - } - } - - private static void InputCapabilityPublishesExactBehaviorLevers() - { - var controls = CatalogItem("behavior-controls", - new AetheriaRuntimeBehaviorPayload(0, "Thruster", 0, []), - new AetheriaRuntimeBehaviorPayload(1, "Switch", 1, []), - new AetheriaRuntimeBehaviorPayload(2, "Trigger", 2, []), - new AetheriaRuntimeBehaviorPayload(3, "Thermotoggle", 3, - [ - new AetheriaRuntimeBehaviorField(1, Number(300)), - new AetheriaRuntimeBehaviorField(2, BoolValue(false)), - new AetheriaRuntimeBehaviorField(3, BoolValue(true)) - ]), - new AetheriaRuntimeBehaviorPayload(4, "Thermotoggle", 4, - [ - new AetheriaRuntimeBehaviorField(1, Number(350)), - new AetheriaRuntimeBehaviorField(2, BoolValue(false)), - new AetheriaRuntimeBehaviorField(3, BoolValue(false)) - ])); - controls.Name = "Climate Control"; - controls.ActionBarIcon = "Assets/Resources/Sprites/Icons/Stroked/Power.png"; - var catalog = new AetheriaRuntimeCatalogSnapshot([controls], [], []); - var entity = Entity(0, 0, "player"); - entity.Equipment = [new AetheriaRuntimeLoadoutItemSlotCommit - { - Item = new AetheriaRuntimeLoadoutItemCommit - { ItemKey = controls.ItemKey, Enabled = true, Durability = 1, Quality = 1 } - }]; - AetheriaRuntimeBehaviorStateProjector.EnsureEquipmentBehaviorStates(entity, catalog); - entity.EquipmentStates = [new AetheriaRuntimeEquipmentStateCommit - { EquipmentIndex = 0, Online = true }]; - var run = new AetheriaRuntimeRunCheckpointCommit - { - RunId = "behavior-input-smoke", - CurrentZoneIndex = 0, - Zones = [new AetheriaRuntimeZoneSnapshotCommit { ZoneIndex = 0, Entities = [entity] }] - }; - run.CurrentEntityKey = run.EntityRecordKey(0, entity.EntityIndex); - var frame = AetheriaRuntimeDaemonFrameDocument.Create(run, "smoke", "behavior-input", 1, 0.1, 0.1); - var capability = AetheriaRuntimeInputCapabilityDocument.FromFrame(frame, catalog: catalog); - RequireEqual("aetheria", capability.ProviderId, - "input capability must retain provider identity instead of substituting daemon service identity"); - var actions = capability.Actions.Where(value => value.Category == "equipment").ToArray(); - RequireEqual(3, actions.Length, - "input capability must omit non-interactive and fixed behaviors instead of activating behavior zero blindly"); - var switchAction = actions.Single(value => value.ActionId == "equipment.0.behavior.1"); - var triggerAction = actions.Single(value => value.ActionId == "equipment.0.behavior.2"); - var thermostatAction = actions.Single(value => value.ActionId == "equipment.0.behavior.3"); - RequireEqual("button-hold.v1", switchAction.InputValue?.Model ?? "", - "switch controls must advertise the fossil press/release input model"); - RequireEqual("active", triggerAction.InputValue?.PayloadKey ?? "", - "trigger controls must route their input value into the typed active field"); - RequireEqual("scalar.v1", thermostatAction.InputValue?.Model ?? "", - "adjustable thermostats must advertise a runtime-neutral scalar lever"); - RequireNear(300, thermostatAction.InputValue?.CurrentValue ?? double.NaN, 0.000001, - "thermostat controls must publish the daemon-owned current target as typed scalar state"); - RequireEqual("kelvin", thermostatAction.InputValue?.Unit ?? "", - "thermostat controls must publish the scalar unit without hiding it in command payload strings"); - Require(actions.All(value => value.ActionBar && value.IconRef == "item.behavior-controls.icon"), - "interactive equipment behaviors must expose provider-icon action-bar suggestions"); - var portableThermostat = capability.ToEveDocument().Actions - .Single(value => value.ActionId == thermostatAction.ActionId); - RequireNear(300, portableThermostat.InputValue?.CurrentValue ?? double.NaN, 0.000001, - "the portable Eve document must retain typed scalar current state"); - Require(portableThermostat.ActionBar && portableThermostat.IconRef == "item.behavior-controls.icon", - "the portable Eve action must retain the provider action-bar suggestion and asset key"); - - var switchRequest = new EveSurfaceCommandRequest( - capability.ProviderId, - AetheriaRuntimeDaemonGameSurfaceBuilder.PilotSurfaceId, - CultMesh.OperationInvocation(switchAction.Operation), - CultMesh.OperationPayload( - ("entityId", run.CurrentEntityKey), - ("equipmentIndex", "0"), - ("behaviorIndex", "1"), - ("active", "1")), - DateTimeOffset.UtcNow, - "behavior-input-smoke"); - var switchTranslated = AetheriaRuntimeDaemonOperationsClient.TryCreateSurfaceCommandDocument( - switchRequest, frame, ".", "behavior-input-smoke", "behavior-input", out var switchCommand); - Require(switchTranslated && - switchCommand?.Kind == AetheriaRuntimeDaemonCommandKinds.SetBehaviorActive && - switchCommand.EquipmentIndex == 0 && switchCommand.BehaviorIndex == 1 && - switchCommand.ScalarValue > 0.5, - $"the generic Eve action must translate into the exact typed switch command; accepted={switchTranslated}, kind={switchCommand?.Kind}, equipment={switchCommand?.EquipmentIndex}, behavior={switchCommand?.BehaviorIndex}, scalar={switchCommand?.ScalarValue}"); - var switchResult = AetheriaRuntimeDaemonOperations.Execute( - run, [switchCommand!], new AetheriaRuntimeDaemonOperationContext { Catalog = catalog }); - AetheriaRuntimeBehaviorSimulation.Step(0, [entity], switchResult.Intents.Behaviors, catalog, 0.1); - Require(entity.BehaviorStates.Single(value => value.BehaviorIndex == 1).SwitchActivated, - "the advertised switch lever must reach the daemon behavior owner"); - - var thermostatRequest = new EveSurfaceCommandRequest( - capability.ProviderId, - AetheriaRuntimeDaemonGameSurfaceBuilder.PilotSurfaceId, - CultMesh.OperationInvocation(thermostatAction.Operation), - CultMesh.OperationPayload( - ("entityId", run.CurrentEntityKey), - ("targetEntityKey", run.CurrentEntityKey), - ("equipmentIndex", "0"), - ("behaviorIndex", "3"), - ("scalarValue", "425")), - DateTimeOffset.UtcNow, - "behavior-input-smoke"); - Require(AetheriaRuntimeDaemonOperationsClient.TryCreateSurfaceCommandDocument( - thermostatRequest, frame, ".", "behavior-input-smoke", "behavior-input", out var thermostatCommand) && - thermostatCommand?.Kind == AetheriaRuntimeDaemonCommandKinds.SetThermotoggleTargetTemperature, - "the generic Eve scalar lever must translate into the typed thermostat command"); - var thermostatResult = AetheriaRuntimeDaemonOperations.Execute( - run, [thermostatCommand!], new AetheriaRuntimeDaemonOperationContext { Catalog = catalog }); - RequireEqual(1, thermostatResult.AppliedCommandIds.Count, - "the translated thermostat command must pass daemon catalog authorization"); - RequireNear(425, entity.BehaviorStates.Single(value => value.BehaviorIndex == 3) - .ThermotoggleTargetTemperature, 0.000001, - "the advertised scalar lever must mutate only daemon-owned thermostat state"); - - entity.EquipmentStates[0].Online = false; - var offline = AetheriaRuntimeInputCapabilityDocument.FromFrame(frame, catalog: catalog); - Require(offline.Actions.Where(value => value.Category == "equipment") - .All(value => value.Availability == "unavailable"), - "offline equipment must retain stable action identity while advertising current unavailability"); - } - - private static void InputCapabilityPublishesConsumableActionBarState() - { - var repairGel = CatalogItem("repair-gel"); - repairGel.Name = "Repair Gel"; - repairGel.Category = AetheriaRuntimeItemCategories.Consumable; - repairGel.Duration = 4; - repairGel.Stackable = false; - repairGel.ActionBarIcon = "Assets/Resources/Sprites/Icons/Stroked/Repair.png"; - var ore = CatalogItem("ore"); - ore.Category = AetheriaRuntimeItemCategories.SimpleCommodity; - var catalog = new AetheriaRuntimeCatalogSnapshot([repairGel, ore], [], []); - var entity = Entity(0, 0, "player"); - entity.CargoContents = - [ - new AetheriaRuntimeCargoBayLoadoutCommit - { - Items = - [ - new AetheriaRuntimeLoadoutItemSlotCommit - { - Item = new AetheriaRuntimeLoadoutItemCommit { ItemKey = "repair-gel", Quantity = 3 } - }, - new AetheriaRuntimeLoadoutItemSlotCommit - { - Item = new AetheriaRuntimeLoadoutItemCommit { ItemKey = "ore", Quantity = 7 } - } - ] - } - ]; - var run = new AetheriaRuntimeRunCheckpointCommit - { - RunId = "consumable-input-smoke", - CurrentZoneIndex = 0, - Zones = [new AetheriaRuntimeZoneSnapshotCommit { ZoneIndex = 0, Entities = [entity] }] - }; - run.CurrentEntityKey = run.EntityRecordKey(0, entity.EntityIndex); - var frame = AetheriaRuntimeDaemonFrameDocument.Create(run, "smoke", "consumable-input", 1, 0.1, 0.1); - var capability = AetheriaRuntimeInputCapabilityDocument.FromFrame(frame, catalog: catalog); - var action = capability.Actions.Single(value => value.Category == "consumable"); - RequireEqual("3", action.Payload["quantityRemaining"], - "consumable action bars must derive quantity from daemon cargo truth"); - RequireEqual("0", action.Payload["fillValue"], - "inactive consumables must advertise an empty remaining-duration fill"); - RequireEqual("remaining-ratio.v1", action.Payload["fillModel"], - "generic lowerers must receive the fossil action-bar fill semantics explicitly"); - RequireEqual("available", action.Availability, - "a valid consumable in cargo must advertise current command availability"); - Require(action.ActionBar && action.IconRef == "item.repair-gel.icon", - "consumables must suggest an action-bar lever using their provider-owned catalog icon"); - Require(!capability.Actions.Any(value => value.Category == "consumable" && value.Payload["itemKey"] == "ore"), - "non-consumable cargo must not become an action-bar command"); - - entity.CargoContents = Array.Empty(); - entity.ActiveConsumables = - [ - new AetheriaRuntimeActiveConsumableCommit - { - ItemKey = "repair-gel", - Duration = 4, - RemainingDuration = 1 - } - ]; - var activeAction = AetheriaRuntimeInputCapabilityDocument.FromFrame(frame, catalog: catalog) - .ToEveDocument().Actions.Single(value => value.Category == "consumable"); - RequireEqual("0", activeAction.Payload["quantityRemaining"], - "the action must survive depletion while its daemon-owned effect remains active"); - RequireEqual("1", activeAction.Payload["activeEffectCount"], - "the portable Eve action must expose daemon-owned active effect state"); - RequireEqual("1", activeAction.Payload["remainingDurationSeconds"], - "generic clients must receive exact remaining duration without querying a Unity effect object"); - RequireEqual("4", activeAction.Payload["durationSeconds"], - "generic clients must receive exact authored consumable duration"); - RequireEqual("0.25", activeAction.Payload["fillValue"], - "remaining duration must lower to the fossil action-bar ratio exactly"); - RequireEqual("unavailable", activeAction.Availability, - "a depleted non-stackable active consumable must remain visible but reject activation"); - } - - private static void InputCapabilityPublishesHeldWeaponGroupLever() - { - var entity = Entity(0, 0, "player"); - entity.WeaponGroups = [new[] { 0 }]; - var run = new AetheriaRuntimeRunCheckpointCommit - { - RunId = "weapon-input-smoke", - CurrentZoneIndex = 0, - Zones = [new AetheriaRuntimeZoneSnapshotCommit { ZoneIndex = 0, Entities = [entity] }] - }; - run.CurrentEntityKey = run.EntityRecordKey(0, entity.EntityIndex); - var frame = AetheriaRuntimeDaemonFrameDocument.Create(run, "smoke", "weapon-input", 1, 0.1, 0.1); - var capability = AetheriaRuntimeInputCapabilityDocument.FromFrame(frame); - var action = capability.Actions.Single(value => value.ActionId == "weapon-group.0.fire"); - RequireEqual("aetheria.daemon.commands.SetWeaponGroupActive", action.Operation, - "weapon input must advertise the daemon's press/release owner instead of the one-shot compatibility command"); - RequireEqual("button-hold.v1", action.InputValue?.Model ?? "", - "generic Eve clients must know that weapon firing is a held control"); - RequireEqual("active", action.InputValue?.PayloadKey ?? "", - "weapon press/release values must route into the typed active field"); - Require(action.ActionBar, - "weapon groups must remain suggested action-bar levers without making the client own firing"); - - AetheriaRuntimeDaemonCommandDocument Translate(string active) - { - var request = new EveSurfaceCommandRequest( - capability.ProviderId, - AetheriaRuntimeDaemonGameSurfaceBuilder.PilotSurfaceId, - CultMesh.OperationInvocation(action.Operation), - CultMesh.OperationPayload( - ("entityId", run.CurrentEntityKey), - ("weaponGroup", "0"), - ("active", active)), - DateTimeOffset.UtcNow, - "weapon-input-smoke"); - Require(AetheriaRuntimeDaemonOperationsClient.TryCreateSurfaceCommandDocument( - request, frame, ".", "weapon-input-smoke", "weapon-input", out var command) && - command?.Kind == AetheriaRuntimeDaemonCommandKinds.SetWeaponGroupActive, - $"generic weapon input value {active} must translate into the typed held-fire command"); - return command!; - } - - var pressed = AetheriaRuntimeDaemonOperations.Execute( - run, [Translate("1")], new AetheriaRuntimeDaemonOperationContext()); - RequireEqual(1, pressed.Intents.WeaponGroups.Count, - "weapon press must reach the daemon intent owner exactly once"); - Require(pressed.Intents.WeaponGroups[0].Active, - "weapon press must activate the exact advertised group"); - Require(entity.ActiveWeaponGroups.SequenceEqual(new[] { true }), - "weapon press must persist the daemon-owned held state beyond its command frame"); - var heldCapability = AetheriaRuntimeInputCapabilityDocument.FromFrame(frame); - RequireEqual("1", heldCapability.Actions.Single(value => value.ActionId == "weapon-group.0.fire") - .Payload["currentValue"], - "the Eve lever must derive its current value from the daemon-owned held state"); - var restartedRun = MessagePack.MessagePackSerializer.Deserialize( - MessagePack.MessagePackSerializer.Serialize(run)); - var restartedFrame = AetheriaRuntimeDaemonFrameDocument.Create( - restartedRun, "smoke", "weapon-input-restart", 2, 0.2, 0.1); - Require(restartedRun.Zones[0].Entities[0].ActiveWeaponGroups.SequenceEqual(new[] { true }) && - AetheriaRuntimeInputCapabilityDocument.FromFrame(restartedFrame).Actions - .Single(value => value.ActionId == "weapon-group.0.fire").Payload["currentValue"] == "1", - "daemon restart must retain held weapon-group truth and re-advertise it through Eve"); - - var released = AetheriaRuntimeDaemonOperations.Execute( - run, [Translate("0")], new AetheriaRuntimeDaemonOperationContext()); - RequireEqual(1, released.Intents.WeaponGroups.Count, - "weapon release must reach the daemon intent owner exactly once"); - Require(!released.Intents.WeaponGroups[0].Active, - "weapon release must deactivate the exact advertised group"); - Require(entity.ActiveWeaponGroups.SequenceEqual(new[] { false }), - "weapon release must clear the daemon-owned held state"); - } - - private static void InputCapabilityPublishesNativeTargetAndInteractControls() - { - var actor = Entity(0, 0, "player"); - actor.VelocityX = 12; - actor.VelocityY = -7; - actor.HelmStrafe = 0.5; - actor.HelmForward = 1; - var near = Entity(1, 0, "enemy"); - near.PositionX = 4; - var middle = Entity(2, 0, "enemy"); - middle.PositionX = 8; - var far = Entity(3, 0, "enemy"); - far.PositionZ = 12; - var hidden = Entity(4, 0, "enemy"); - hidden.PositionX = 2; - actor.Contacts = - [ - new AetheriaRuntimeEntityContactCommit - { TargetEntityIndex = near.EntityIndex, Visible = true, Hostile = true }, - new AetheriaRuntimeEntityContactCommit - { TargetEntityIndex = middle.EntityIndex, Visible = true, Hostile = true }, - new AetheriaRuntimeEntityContactCommit - { TargetEntityIndex = far.EntityIndex, Visible = true, Hostile = true } - ]; - var run = new AetheriaRuntimeRunCheckpointCommit - { - RunId = "native-input-smoke", - CurrentZoneIndex = 0, - Zones = [new AetheriaRuntimeZoneSnapshotCommit { ZoneIndex = 0, Entities = [actor, near, middle, far, hidden] }] - }; - run.CurrentEntityKey = run.EntityRecordKey(0, actor.EntityIndex); - var frame = AetheriaRuntimeDaemonFrameDocument.Create(run, "smoke", "native-input", 1, 0.1, 0.1); - var capability = AetheriaRuntimeInputCapabilityDocument.FromFrame(frame); - var targeting = capability.Actions.Where(value => value.Category == "targeting").ToArray(); - Require(targeting.Select(value => value.ActionId).ToHashSet(StringComparer.Ordinal).SetEquals( - ["pilot.target-nearest", "pilot.target-previous", "pilot.target-next", "pilot.target-reticle", "pilot.target-clear"]), - "portable input must advertise every daemon target-selection lever"); - Require(targeting.Where(value => value.ActionId != "pilot.target-clear") - .All(value => value.Availability == "available" && - value.Payload["visibleHostileCount"] == "3" && - value.Payload["currentTargetEntityIndex"] == "-1") && - targeting.Single(value => value.ActionId == "pilot.target-clear").Availability == "unavailable", - "target controls must derive availability and current selection from daemon contact truth"); - - var keyboard = capability.DefaultProfiles.Single(value => value.DeviceClass == "keyboard-mouse"); - string Control(string actionId) => keyboard.Bindings.Single(value => value.ActionId == actionId).Gesture.Controls.Single(); - RequireEqual("keyboard.f", Control("pilot.interact"), - "the generic default profile must preserve the fossil interaction key"); - RequireEqual("keyboard.t", Control("pilot.target-nearest"), - "the generic default profile must preserve the fossil nearest-target key"); - RequireEqual("keyboard.y", Control("pilot.target-previous"), - "the generic default profile must preserve the fossil previous-target key"); - RequireEqual("keyboard.u", Control("pilot.target-next"), - "the generic default profile must preserve the fossil next-target key"); - RequireEqual("keyboard.r", Control("pilot.target-reticle"), - "the generic default profile must preserve the fossil reticle-target key"); - Require(keyboard.Bindings.Where(value => value.ActionId == "pilot.interact" || - value.ActionId.StartsWith("pilot.target-", StringComparison.Ordinal)) - .All(value => !value.ActionBar), - "ordinary fossil shortcuts must not masquerade as action-bar slot suggestions"); - var reticleAction = capability.ToEveDocument().Actions.Single(value => value.ActionId == "pilot.target-reticle"); - RequireEqual("view-direction.v1", reticleAction.InputValue?.Model ?? "", - "reticle targeting must advertise the generic active-view direction model"); - Require((reticleAction.InputValue?.PayloadKeys ?? Array.Empty()).SequenceEqual( - ["directionX", "directionY", "directionZ"]), - "reticle targeting must preserve the portable world-direction payload order"); - - AetheriaRuntimeDaemonCommandDocument Translate(AetheriaRuntimeInputActionDocument action) - { - var request = new EveSurfaceCommandRequest( - capability.ProviderId, - AetheriaRuntimeDaemonGameSurfaceBuilder.PilotSurfaceId, - CultMesh.OperationInvocation(action.Operation), - CultMesh.OperationPayload(("entityId", run.CurrentEntityKey)), - DateTimeOffset.UtcNow, - "native-input-smoke"); - Require(AetheriaRuntimeDaemonOperationsClient.TryCreateSurfaceCommandDocument( - request, frame, ".", "native-input-smoke", "native-input", out var command), - $"advertised action {action.ActionId} must translate through the generic Eve command path"); - return command!; - } - - var previousCommand = Translate(targeting.Single(value => value.ActionId == "pilot.target-previous")); - var previousResult = AetheriaRuntimeDaemonOperations.Execute( - run, [previousCommand], new AetheriaRuntimeDaemonOperationContext()); - RequireEqual(1, previousResult.AppliedCommandIds.Count, - "the advertised previous-target action must reach the daemon owner"); - RequireEqual(middle.EntityIndex, actor.TargetEntityIndex, - "previous from no selection must preserve the fossil's second-to-last index arithmetic"); - - var clearCommand = Translate(targeting.Single(value => value.ActionId == "pilot.target-clear")); - var clearResult = AetheriaRuntimeDaemonOperations.Execute( - run, [clearCommand], new AetheriaRuntimeDaemonOperationContext()); - RequireEqual(1, clearResult.AppliedCommandIds.Count, - "the generic clear-target action must return selection ownership to the daemon"); - - var nearestCommand = Translate(targeting.Single(value => value.ActionId == "pilot.target-nearest")); - RequireEqual(AetheriaRuntimeDaemonCommandKinds.TargetNearest, nearestCommand.Kind, - "nearest target action must preserve daemon command identity"); - var result = AetheriaRuntimeDaemonOperations.Execute( - run, [nearestCommand], new AetheriaRuntimeDaemonOperationContext()); - RequireEqual(1, result.AppliedCommandIds.Count, - "the advertised target action must reach the daemon owner"); - RequireEqual(far.EntityIndex, actor.TargetEntityIndex, - "the historically misnamed nearest action must preserve the fossil's MaxBy-distance behavior"); - var selected = AetheriaRuntimeInputCapabilityDocument.FromFrame(frame); - Require(selected.Actions.Single(value => value.ActionId == "pilot.target-clear").Availability == "available" && - selected.Actions.Where(value => value.Category == "targeting") - .All(value => value.Payload["currentTargetEntityIndex"] == far.EntityIndex.ToString()), - "all portable target levers must refresh from the newly selected daemon target"); - - var clearAfterNearest = AetheriaRuntimeDaemonOperations.Execute( - run, [Translate(selected.Actions.Single(value => value.ActionId == "pilot.target-clear"))], - new AetheriaRuntimeDaemonOperationContext()); - RequireEqual(1, clearAfterNearest.AppliedCommandIds.Count, - "generic Eve must clear the fossil-selected target before the independent reticle selection proof"); - - var reticleRequest = new EveSurfaceCommandRequest( - capability.ProviderId, - AetheriaRuntimeDaemonGameSurfaceBuilder.PilotSurfaceId, - CultMesh.OperationInvocation(reticleAction.Operation), - CultMesh.OperationPayload( - ("entityId", run.CurrentEntityKey), - ("directionX", "0"), - ("directionY", "0"), - ("directionZ", "1")), - DateTimeOffset.UtcNow, - "native-input-smoke"); - Require(AetheriaRuntimeDaemonOperationsClient.TryCreateSurfaceCommandDocument( - reticleRequest, frame, ".", "native-input-smoke", "native-input", out var reticleCommand) && - reticleCommand?.Kind == AetheriaRuntimeDaemonCommandKinds.TargetReticle, - "the generic Eve view-direction action must translate into the typed reticle command"); - var reticleResult = AetheriaRuntimeDaemonOperations.Execute( - run, [reticleCommand!], new AetheriaRuntimeDaemonOperationContext()); - RequireEqual(1, reticleResult.AppliedCommandIds.Count, - "the translated reticle action must reach the daemon target owner"); - RequireEqual(far.EntityIndex, actor.TargetEntityIndex, - "reticle selection must rank visible hostile contacts from the advertised view ray inside the daemon"); - - EveSurfaceCommandRequest ExplicitTarget(string targetEntityKey) => new( - capability.ProviderId, - AetheriaRuntimeDaemonGameSurfaceBuilder.PilotSurfaceId, - CultMesh.OperationInvocation(AetheriaRuntimeDaemonSurfaceCommandCatalog.CommandName( - AetheriaRuntimeDaemonCommandKinds.SetTarget)), - CultMesh.OperationPayload( - ("sourceEntityId", run.CurrentEntityKey), - ("targetEntityId", targetEntityKey)), - DateTimeOffset.UtcNow, - "native-input-smoke"); - - Require(AetheriaRuntimeDaemonOperationsClient.TryCreateSurfaceCommandDocument( - ExplicitTarget(run.EntityRecordKey(0, near.EntityIndex)), - frame, - ".", - "native-input-smoke", - "native-input", - out var explicitTarget) && - explicitTarget?.ActorEntityKey == run.CurrentEntityKey && - explicitTarget.TargetEntityKey == run.EntityRecordKey(0, near.EntityIndex), - "generic Eve explicit targeting must preserve both source and target entity identities"); - var explicitResult = AetheriaRuntimeDaemonOperations.Execute( - run, [explicitTarget!], new AetheriaRuntimeDaemonOperationContext()); - Require(explicitResult.AppliedCommandIds.Contains(explicitTarget!.CommandId) && - actor.TargetEntityIndex == near.EntityIndex, - "a generic scene click must select the visible entity through daemon contact truth"); - - Require(AetheriaRuntimeDaemonOperationsClient.TryCreateSurfaceCommandDocument( - ExplicitTarget(run.EntityRecordKey(0, hidden.EntityIndex)), - frame, - ".", - "native-input-smoke", - "native-input", - out var hiddenTarget), - "generic Eve must translate an explicit target attempt without deciding visibility client-side"); - var hiddenResult = AetheriaRuntimeDaemonOperations.Execute( - run, [hiddenTarget!], new AetheriaRuntimeDaemonOperationContext()); - RequireEqual(AetheriaRuntimeDaemonRejectionReasons.TargetNotVisible, - hiddenResult.RejectedCommandReasons[hiddenTarget!.CommandId], - "the daemon must reject a generic explicit target absent from authoritative visible contacts"); - RequireEqual(near.EntityIndex, actor.TargetEntityIndex, - "a rejected explicit target must not disturb the prior valid selection"); - var rejectedFrame = AetheriaRuntimeDaemonFrameDocument.Create( - run, "smoke", "native-input", 2, 0.2, 0.1); - rejectedFrame.RejectedCommandIds = hiddenResult.RejectedCommandIds; - rejectedFrame.RejectedCommandReasons = hiddenResult.RejectedCommandReasons; - var targetReceipt = AetheriaRuntimeDaemonReceiptProjector.Project( - AetheriaRuntimeCommittedCommandFactDocument.FromRejectedCommand( - rejectedFrame, hiddenTarget!, "aetheria.local"), - AetheriaRuntimeDaemonGameSurfaceBuilder.PilotSurfaceId); - Require(targetReceipt.State == "denied" && - targetReceipt.Message.Contains( - AetheriaRuntimeDaemonRejectionReasons.TargetNotVisible, - StringComparison.Ordinal), - "generic Eve must receive the daemon's exact explicit-target rejection reason"); - - var interact = capability.Actions.Single(value => value.ActionId == "pilot.interact"); - RequireEqual(AetheriaRuntimeDaemonCommandKinds.Interact, Translate(interact).Kind, - "the fossil interaction binding must reach the shared wormhole-first/then-dock daemon command"); - } - - private static void DockedRefitSurfacePublishesGenericCommands() - { - var hull = HullCatalogItem("surface-hull", 9, 7, 100); - var cargoBay = CatalogItem("surface-cargo-bay"); - cargoBay.InteriorShapeWidth = 6; - cargoBay.InteriorShapeHeight = 4; - var catalog = new AetheriaRuntimeCatalogSnapshot([hull, cargoBay, CatalogItem("ore")], [], []); - var ship = Entity(0, 0, "player"); - ship.Name = "Pilot"; - ship.HullItemKey = hull.ItemKey; - ship.CargoBays = [ItemSlot(cargoBay.ItemKey)]; - ship.CargoContents = [new AetheriaRuntimeCargoBayLoadoutCommit { Items = [ItemSlot("ore", 2, 3)] }]; - var station = Entity(1, 0, "player"); - station.Kind = "station"; - station.Name = "Station"; - station.DockingBayAssignments = [ship.EntityIndex]; - station.CargoBays = [ItemSlot(cargoBay.ItemKey)]; - station.CargoContents = [new AetheriaRuntimeCargoBayLoadoutCommit()]; - var run = new AetheriaRuntimeRunCheckpointCommit - { - RunId = "generic-refit-surface-smoke", - CurrentZoneIndex = 0, - CurrentEntityKey = "zone.0.entity.0", - Zones = [new AetheriaRuntimeZoneSnapshotCommit { ZoneIndex = 0, Entities = [ship, station] }] - }; - var frame = AetheriaRuntimeDaemonFrameDocument.Create(run, "smoke", "surface", 1, 0.02, 0.02); - var shipKey = run.EntityRecordKey(0, ship.EntityIndex); - var stationKey = run.EntityRecordKey(0, station.EntityIndex); - var surface = AetheriaRuntimeDaemonGameSurfaceBuilder.Build( - frame, - new AetheriaRuntimeDaemonHealthDocument(), - AetheriaRuntimeDaemonCommandBoundaryDocument.Create("smoke"), - catalog: catalog); - var equipment = Find(surface.Surface.Root, "aetheria.daemon.game.refit.ship.equipment"); - var shipCargo = Find(surface.Surface.Root, "aetheria.daemon.game.refit.ship.cargo.0"); - var stationCargo = Find(surface.Surface.Root, "aetheria.daemon.game.refit.station.cargo.0"); - Require(equipment != null && equipment.Kind == "inventory.grid" && - equipment.Props["columns"] == "9" && equipment.Props["rows"] == "7", - "docked pilot surface must publish authored ship equipment geometry"); - Require(shipCargo != null && stationCargo != null && - shipCargo.Props["targetEntityKey"] == shipKey && - stationCargo.Props["targetEntityKey"] == stationKey, - "docked pilot surface must publish both ship and station cargo targets"); - Require(surface.Commands.Any(value => value.Command == - AetheriaRuntimeDaemonSurfaceCommandCatalog.CommandName(AetheriaRuntimeDaemonCommandKinds.TransferCargoItem)) && - surface.Commands.Any(value => value.Command == - AetheriaRuntimeDaemonSurfaceCommandCatalog.CommandName(AetheriaRuntimeDaemonCommandKinds.EquipItem)) && - surface.Commands.Any(value => value.Command == - AetheriaRuntimeDaemonSurfaceCommandCatalog.CommandName(AetheriaRuntimeDaemonCommandKinds.StoreItem)), - "docked pilot surface must advertise all atomic refit operations"); - - var request = new EveSurfaceCommandRequest( - "aetheria", - AetheriaRuntimeDaemonGameSurfaceBuilder.PilotSurfaceId, - CultMesh.OperationInvocation( - AetheriaRuntimeDaemonSurfaceCommandCatalog.CommandName(AetheriaRuntimeDaemonCommandKinds.TransferCargoItem)), - CultMesh.OperationPayload( - ("entityId", shipKey), - ("originEntityKey", shipKey), - ("originCargoIndex", "0"), - ("destinationEntityKey", stationKey), - ("destinationCargoIndex", "0"), - ("itemKey", "ore"), - ("quantity", "1"), - ("sourceX", "2"), - ("sourceY", "3"), - ("destinationX", "0"), - ("destinationY", "0"), - ("hasDestinationPosition", "true")), - DateTimeOffset.UtcNow, - "generic-smoke"); - Require(AetheriaRuntimeDaemonOperationsClient.TryCreateSurfaceCommandDocument( - request, frame, ".", "generic-smoke", "surface", out var translated) && - translated?.CargoTransfer.OriginCargoIndex == 0 && - translated.CargoTransfer.DestinationEntityKey == stationKey && - translated.CargoTransfer.HasDestinationPosition, - "generic Eve inventory drop must translate into the exact typed daemon cargo command"); - - var unauthorized = AetheriaRuntimeDaemonCommandDocument.Create( - AetheriaRuntimeDaemonCommandKinds.TransferCargoItem, - "generic-smoke", "surface", 1, shipKey); - unauthorized.TextValue = "ore"; - unauthorized.CargoTransfer.OriginEntityKey = stationKey; - unauthorized.CargoTransfer.DestinationEntityKey = stationKey; - unauthorized.CargoTransfer.OriginCargoIndex = 0; - unauthorized.CargoTransfer.DestinationCargoIndex = 0; - var rejected = AetheriaRuntimeDaemonOperations.Execute(run, [unauthorized], - new AetheriaRuntimeDaemonOperationContext { Catalog = catalog }); - Require(rejected.RejectedCommandReasons[unauthorized.CommandId] == - AetheriaRuntimeDaemonRejectionReasons.CargoAccessDenied, - "cargo transfer actor must own one endpoint even when the target is physically nearby"); - - station.DockingBayAssignments = []; - var undocked = AetheriaRuntimeDaemonGameSurfaceBuilder.Build( - frame, - new AetheriaRuntimeDaemonHealthDocument(), - AetheriaRuntimeDaemonCommandBoundaryDocument.Create("smoke"), - catalog: catalog); - Require(Find(undocked.Surface.Root, "aetheria.daemon.game.refit") == null, - "undocked pilot surfaces must not expose station refit controls"); - - static AetheriaRuntimeSurfaceComponent? Find(AetheriaRuntimeSurfaceComponent component, string id) => - component.Id == id - ? component - : component.Children.Select(child => Find(child, id)).FirstOrDefault(value => value != null); - - static AetheriaRuntimeLoadoutItemSlotCommit ItemSlot(string itemKey, int x = 0, int y = 0) => new() - { - X = x, - Y = y, - Item = new AetheriaRuntimeLoadoutItemCommit - { - ItemKey = itemKey, - Quantity = 1, - Quality = 1, - Durability = 1, - Enabled = true - } - }; - } - - private static void FossilVelocityBehaviorsRunInDaemonFlightStep() - { - var conversion = new AetheriaRuntimeBehaviorPayload( - 0, - AetheriaRuntimeBehaviorKinds.VelocityConversion, - 0, - [new AetheriaRuntimeBehaviorField(1, PerformanceStat(Math.Log(2)))]); - var limit = new AetheriaRuntimeBehaviorPayload( - 1, - AetheriaRuntimeBehaviorKinds.VelocityLimit, - 0, - [new AetheriaRuntimeBehaviorField(1, PerformanceStat(4))]); - var hull = HullCatalogItem("velocity-hull", 1, 1, 100); - var controller = CatalogItem("velocity-controller", conversion, limit); - var catalog = new AetheriaRuntimeCatalogSnapshot([hull, controller], [], []); - var ship = Entity(0, 0, "player"); - ship.HullItemKey = hull.ItemKey; - ship.DirectionX = 1; - ship.DirectionY = 0; - ship.VelocityX = 0; - ship.VelocityY = 10; - ship.Equipment = - [ - new AetheriaRuntimeLoadoutItemSlotCommit - { - Item = new AetheriaRuntimeLoadoutItemCommit - { - ItemKey = controller.ItemKey, - Quantity = 1, - Quality = 1, - Durability = 1, - Enabled = true - } - } - ]; - - AetheriaRuntimeBehaviorStateProjector.EnsureEquipmentBehaviorStates(ship, catalog); - AetheriaRuntimeFlightSimulation.Step([ship], [], catalog, 1, 1, 1, 0, 1); - - RequireNear(4, Math.Sqrt(ship.VelocityX * ship.VelocityX + ship.VelocityY * ship.VelocityY), 0.000001, - "VelocityLimit must clamp the post-conversion speed in the authoritative flight step"); - RequireNear(ship.VelocityX, ship.VelocityY, 0.000001, - "VelocityConversion must exponentially align velocity toward the ship direction before limiting"); - var limitState = ship.BehaviorStates.Single(state => - state.BehaviorKind == AetheriaRuntimeBehaviorKinds.VelocityLimit); - RequireNear(4, limitState.VelocityLimit, 0.000001, - "VelocityLimit must publish its evaluated runtime value for Eve and reconnect"); - } - - private static void ReflectorUsesSharedStellarLightFieldWithoutAccumulating() - { - var reflector = new AetheriaRuntimeBehaviorPayload( - 0, - AetheriaRuntimeBehaviorKinds.Reflector, - 0, - [new AetheriaRuntimeBehaviorField(1, PerformanceStat(2))]); - var constantVisibility = new AetheriaRuntimeBehaviorPayload( - 1, - AetheriaRuntimeBehaviorKinds.Visibility, - 0, - [new AetheriaRuntimeBehaviorField(1, PerformanceStat(5))]); - var controller = CatalogItem("reflector-controller", reflector, constantVisibility); - var catalog = new AetheriaRuntimeCatalogSnapshot([controller], [], []); - var ship = Entity(0, 250, "player"); - ship.Visibility = 3; - ship.Equipment = - [ - new AetheriaRuntimeLoadoutItemSlotCommit - { - Item = new AetheriaRuntimeLoadoutItemCommit - { - ItemKey = controller.ItemKey, - Quantity = 1, - Quality = 1, - Durability = 1, - Enabled = true - } - } - ]; - var zone = new AetheriaRuntimeZoneSnapshotCommit - { - ZoneIndex = 0, - Entities = [ship], - Orbits = - [ - new AetheriaRuntimeOrbitSnapshotCommit - { - OrbitKey = "sun-orbit", - FixedPositionX = 100 - } - ], - Bodies = - [ - new AetheriaRuntimeBodySnapshotCommit - { - Kind = "sun", - Mass = 16, - OrbitKey = "sun-orbit", - SunVisual = new AetheriaRuntimeSunVisualCommit { LightRadiusMultiplier = 1 } - } - ] - }; - var settings = AetheriaRuntimeDaemonRenderSettings.AetheriaDefault; - var radius = settings.ResolveLightRadius(16); - var pulseX = Math.Min(1, 150 / radius * 2); - var expectedReflection = 2 * Math.Pow((pulseX + 1) * (1 - pulseX), 8); - - AetheriaRuntimeVisibilitySimulation.StepZone(zone, [ship], catalog, settings); - AetheriaRuntimeVisibilitySimulation.StepZone(zone, [ship], catalog, settings); - - RequireNear(3 + 5 + expectedReflection, ship.Visibility, 0.000001, - "Visibility and Reflector must publish their fossil sources without accumulating per tick"); - var equipmentSource = ship.StatGrids.Single(grid => grid.Name == "equipment-visibility"); - RequireNear(5, equipmentSource.Values.Single(), 0.000001, - "Operational Visibility equipment must publish its evaluated signature as an inspectable source"); - var source = ship.StatGrids.Single(grid => grid.Name == "reflector-visibility"); - RequireNear(expectedReflection, source.Values.Single(), 0.000001, - "Reflector visibility must remain an inspectable daemon-owned source for Eve and reconnect"); - } - - private static void SensorPingIsActorScopedEnergyGatedAndReconnectable() - { - var sensorItem = CatalogItem("sensor-ping-array", SensorPayload( - sensitivity: 0.01, - pingBoost: 1, - pingEnergy: 5, - pingVisibility: 4, - pingRange: 200, - pingCooldown: 1, - pingDuration: 1, - pingRadiusExponent: 1)); - var capacitorItem = CatalogItem("sensor-ping-capacitor", CapacitorPayload(10, 1)); - var catalog = new AetheriaRuntimeCatalogSnapshot([sensorItem, capacitorItem], [], []); - - static AetheriaRuntimeEntitySnapshotCommit Observer(int index, string sensorKey, string capacitorKey) - { - var entity = Entity(index, 0, "player"); - entity.Equipment = - [ - new AetheriaRuntimeLoadoutItemSlotCommit { Item = new AetheriaRuntimeLoadoutItemCommit - { ItemKey = sensorKey, Quality = 1, Durability = 1, Enabled = true } }, - new AetheriaRuntimeLoadoutItemSlotCommit { Item = new AetheriaRuntimeLoadoutItemCommit - { ItemKey = capacitorKey, Quality = 1, Durability = 1, Enabled = true } } - ]; - entity.BehaviorStates = - [ - new AetheriaRuntimeBehaviorStateCommit - { - OwnerKind = AetheriaRuntimeBehaviorStateProjector.EquipmentOwnerKind, - OwnerIndex = 1, - BehaviorIndex = 0, - BehaviorKind = "Capacitor", - CapacitorCharge = 10, - CapacitorCapacity = 10, - CapacitorEfficiency = 1 - } - ]; - return entity; - } - - var actor = Observer(0, sensorItem.ItemKey, capacitorItem.ItemKey); - var other = Observer(1, sensorItem.ItemKey, capacitorItem.ItemKey); - var target = Entity(2, 40, "raider"); - target.Visibility = 1; - var zone = new AetheriaRuntimeZoneSnapshotCommit { ZoneIndex = 0, Entities = [actor, other, target] }; - var run = new AetheriaRuntimeRunCheckpointCommit - { - RunId = "sensor-ping-smoke", - Zones = [zone] - }; - - AetheriaRuntimeSensorSimulation.StepZone( - run, zone, [actor, other, target], [], catalog, 0.25, 0.5, 0.1, 1); - AetheriaRuntimeSensorSimulation.StepZone( - run, - zone, - [actor, other, target], - [new AetheriaRuntimeDaemonSensorPingIntent { ActorEntityKey = "zone.0.entity.0" }], - catalog, - 0.25, - 0.5, - 0.1, - 2); - - var sensor = actor.BehaviorStates.Single(state => state.BehaviorKind == AetheriaRuntimeBehaviorKinds.Sensor); - var otherSensor = other.BehaviorStates.Single(state => state.BehaviorKind == AetheriaRuntimeBehaviorKinds.Sensor); - Require(sensor.Pinging && !otherSensor.Pinging, - "sensor ping intent must retain actor identity instead of activating every sensor"); - RequireEqual(2, sensor.PingedEntityCount, - "expanding ping must persist every crossed target index for reconnect-safe exactly-once boost"); - Require(sensor.PingedEntityIndices.SequenceEqual(new[] { 1, 2 }), - "pinged target indices must survive in stable order rather than only an unverifiable count"); - RequireNear(5, actor.BehaviorStates.Single(state => state.BehaviorKind == "Capacitor").CapacitorCharge, - 0.000001, "accepted ping must consume its authored energy exactly once"); - Require(actor.Contacts.Single(contact => contact.TargetEntityIndex == 2).Visible, - "ping boost must cross the daemon-owned detection threshold"); - RequireNear(4, actor.StatGrids.Single(grid => grid.Name == "transient-visibility:sensor:0:0").Values.Single(), - 0.000001, "ping must publish its authored transient visibility source"); - RequireEqual(1, run.GameEvents.Count(value => value.Kind == "sensor.ping.started"), - "accepted ping must emit one provider-owned start event"); - } - - private static void TurretControllerAcquiresAimsAndTriggersExactWeapons() - { - var weaponPayload = new AetheriaRuntimeBehaviorPayload( - 0, - AetheriaRuntimeBehaviorKinds.InstantWeapon, - 0, - [ - new AetheriaRuntimeBehaviorField(2, PerformanceStat(10)), - new AetheriaRuntimeBehaviorField(5, PerformanceStat(0)), - new AetheriaRuntimeBehaviorField(6, PerformanceStat(200)), - new AetheriaRuntimeBehaviorField(11, PerformanceStat(7)), - new AetheriaRuntimeBehaviorField(16, PerformanceStat(100)), - new AetheriaRuntimeBehaviorField(17, PerformanceStat(1)), - new AetheriaRuntimeBehaviorField(19, PerformanceStat(0.1)), - new AetheriaRuntimeBehaviorField(21, PerformanceStat(100)), - new AetheriaRuntimeBehaviorField(22, PerformanceStat(0)), - new AetheriaRuntimeBehaviorField(23, PerformanceStat(180)), - new AetheriaRuntimeBehaviorField(24, PerformanceStat(1)), - new AetheriaRuntimeBehaviorField(25, PerformanceStat(1)) - ]); - var weaponItem = WearableWeaponCatalogItem("turret-smoke-weapon", weaponPayload); - var controllerItem = CatalogItem( - "turret-smoke-controller", - new AetheriaRuntimeBehaviorPayload(0, "EnergyDraw", 0, - [ - new AetheriaRuntimeBehaviorField(1, PerformanceStat(5)), - new AetheriaRuntimeBehaviorField(2, BoolValue(false)) - ]), - new AetheriaRuntimeBehaviorPayload(1, AetheriaRuntimeBehaviorKinds.TurretController, 0, [])); - var capacitorItem = CatalogItem("turret-smoke-capacitor", CapacitorPayload(20, 1)); - var catalog = new AetheriaRuntimeCatalogSnapshot([controllerItem, weaponItem, capacitorItem], [], []); - - var uncontrolled = Entity(0, 0, "raider"); - uncontrolled.DirectionX = 1; - uncontrolled.WeaponGroups = [new[] { 0 }]; - uncontrolled.TargetEntityIndex = 1; - uncontrolled.Equipment = [new AetheriaRuntimeLoadoutItemSlotCommit - { - Item = new AetheriaRuntimeLoadoutItemCommit - { ItemKey = weaponItem.ItemKey, Quality = 0, Durability = 1, Enabled = true } - }]; - uncontrolled.Contacts = [new AetheriaRuntimeEntityContactCommit - { TargetEntityIndex = 1, InfoGathered = 1, Visible = true, Hostile = true }]; - var uncontrolledTarget = Entity(1, 100, "player"); - var uncontrolledZone = new AetheriaRuntimeZoneSnapshotCommit - { ZoneIndex = 0, Entities = [uncontrolled, uncontrolledTarget] }; - var uncontrolledRun = new AetheriaRuntimeRunCheckpointCommit - { RunId = "uncontrolled-hostile-smoke", Zones = [uncontrolledZone] }; - using (var physics = NewPhysics()) - { - for (var frame = 0; frame < 3; frame++) - AetheriaRuntimeDaemonSimulation.Step( - uncontrolledRun, - new AetheriaRuntimeDaemonIntentState(), - 0.1, - new AetheriaRuntimeDaemonSimulationSettings(), - physics, - catalog, - frame, - frame * 0.1); - } - RequireEqual(0, uncontrolledRun.ShotReceipts.Count, - "non-player entities without a controller or command must not inherit the removed group-zero fire authority"); - - var turret = Entity(0, 0, "player"); - turret.Kind = "station"; - turret.DirectionX = 1; - turret.DirectionY = 0; - turret.Equipment = - [ - new AetheriaRuntimeLoadoutItemSlotCommit - { - Item = new AetheriaRuntimeLoadoutItemCommit - { ItemKey = controllerItem.ItemKey, Quality = 1, Durability = 1, Enabled = true } - }, - new AetheriaRuntimeLoadoutItemSlotCommit - { - Rotation = "None", - Item = new AetheriaRuntimeLoadoutItemCommit - { ItemKey = weaponItem.ItemKey, Quality = 0, Durability = 1, Enabled = true } - }, - new AetheriaRuntimeLoadoutItemSlotCommit - { - Rotation = "Half", - Item = new AetheriaRuntimeLoadoutItemCommit - { ItemKey = weaponItem.ItemKey, Quality = 0, Durability = 1, Enabled = true } - }, - new AetheriaRuntimeLoadoutItemSlotCommit - { - Item = new AetheriaRuntimeLoadoutItemCommit - { ItemKey = capacitorItem.ItemKey, Quality = 1, Durability = 1, Enabled = true } - } - ]; - turret.WeaponGroups = [new[] { 1, 2 }]; - turret.Contacts = [new AetheriaRuntimeEntityContactCommit - { TargetEntityIndex = 1, InfoGathered = 1, Visible = true, Hostile = true }]; - var target = Entity(1, 100, "raider"); - target.VelocityY = 5; - var zone = new AetheriaRuntimeZoneSnapshotCommit { ZoneIndex = 0, Entities = [turret, target] }; - var run = new AetheriaRuntimeRunCheckpointCommit { RunId = "turret-controller-smoke", Zones = [zone] }; - using (var physics = NewPhysics()) - { - AetheriaRuntimeDaemonSimulation.Step(run, new AetheriaRuntimeDaemonIntentState(), 0.1, - new AetheriaRuntimeDaemonSimulationSettings(), physics, catalog, 0, 0); - RequireEqual(-1, turret.TargetEntityIndex, - "an unfunded controller must remain unreachable behind its authored EnergyDraw gate"); - RequireEqual(0, run.ShotReceipts.Count, - "an unfunded controller must not aim or trigger weapons behind the behavior chain"); - - var capacitor = turret.BehaviorStates.Single(state => state.BehaviorKind == "Capacitor"); - capacitor.CapacitorCharge = 20; - AetheriaRuntimeDaemonSimulation.Step(run, new AetheriaRuntimeDaemonIntentState(), 0.1, - new AetheriaRuntimeDaemonSimulationSettings(), physics, catalog, 1, 0.1); - RequireEqual(target.EntityIndex, turret.TargetEntityIndex, - "an idle turret controller must acquire the first visible hostile ship without firing on the acquisition tick"); - RequireEqual(0, run.ShotReceipts.Count, - "target acquisition must not smuggle an extra trigger into the same fossil controller tick"); - AetheriaRuntimeDaemonSimulation.Step(run, new AetheriaRuntimeDaemonIntentState(), 0.1, - new AetheriaRuntimeDaemonSimulationSettings(), physics, catalog, 2, 0.2); - } - - var controllerState = turret.BehaviorStates.Single(state => - state.BehaviorKind == AetheriaRuntimeBehaviorKinds.TurretController); - RequireEqual(2, controllerState.TurretControllerWeaponCount, - "turret controller initialization must publish its exact operational weapon count"); - Require(controllerState.TurretControllerPredictShots && - Math.Abs(controllerState.TurretControllerShotSpeed - 100) < 0.000001 && - turret.LookDirectionY > 0, - "projectile velocity must enable the fossil first-order intercept aim and persist its controller state"); - Require(run.ShotReceipts.Count > 0 && run.ShotReceipts.All(receipt => receipt.WeaponOwnerIndex == 1), - "turret controller must trigger only the exact in-range aligned weapon behavior, not its group or reversed neighbor"); - RequireNear(7, turret.StatGrids.Single(grid => - grid.Name == "transient-visibility:weapon:equipment:1:0").Values.Single(), 0.000001, - "turret-triggered fire must publish the selected weapon's authored visibility through the shared shot transaction"); - Require(turret.Equipment[1].Item!.Durability < 1 && - Math.Abs(turret.Equipment[2].Item!.Durability - 1) < 0.000001, - "turret-triggered fire must wear only the exact weapon that committed a shot"); - } - - private static void DockingUsesRealBaysAndFossilUndockRules() - { - var catalog = new AetheriaRuntimeCatalogSnapshot( - [ - CatalogItem("dock-cockpit", new AetheriaRuntimeBehaviorPayload(0, "Cockpit", 0, [])), - CatalogItem("dock-thruster", new AetheriaRuntimeBehaviorPayload(0, "Thruster", 0, [])), - CatalogItem("dock-drive", new AetheriaRuntimeBehaviorPayload(0, "AetherDrive", 0, [])), - CatalogItem("dock-reactor", new AetheriaRuntimeBehaviorPayload(0, "Reactor", 0, [])) - ], [], []); - var context = new AetheriaRuntimeDaemonOperationContext { Catalog = catalog }; - - var physicsShip = Entity(0, 24, "player"); - var physicsStation = Entity(1, 0, "player"); - physicsStation.Kind = "station"; - physicsStation.DockingBays = - [ - new AetheriaRuntimeLoadoutItemSlotCommit - { - Item = new AetheriaRuntimeLoadoutItemCommit { ItemKey = "dock-bay", Quantity = 1 } - } - ]; - physicsStation.DockingBayAssignments = [-1]; - physicsStation.DockingBayContents = [Cargo()]; - var physicsZone = new AetheriaRuntimeZoneSnapshotCommit - { - ZoneIndex = 0, - Entities = [physicsShip, physicsStation] - }; - var physicsStep = NewPhysics().Step( - "dock-collision-exclusion-smoke", - 1, - 0, - physicsZone, - physicsZone.Entities, - 0.1); - var steppedShip = physicsStep.Bodies.Single(body => body.EntityIndex == physicsShip.EntityIndex); - RequireNear(24, steppedShip.PositionX, 0.000001, - "Ymir must not invent ship/station collision separation that pushes the fossil docking radius out of reach"); - physicsShip.PositionX = steppedShip.PositionX; - physicsShip.PositionZ = steppedShip.PositionZ; - var physicsRun = new AetheriaRuntimeRunCheckpointCommit - { - RunId = "dock-collision-exclusion-smoke", - CurrentZoneIndex = 0, - Zones = [physicsZone] - }; - physicsRun.CurrentEntityKey = physicsRun.EntityRecordKey(0, physicsShip.EntityIndex); - var physicsDock = AetheriaRuntimeDaemonCommandDocument.Create( - AetheriaRuntimeDaemonCommandKinds.DockNearest, - "pilot", - physicsRun.RunId, - 1, - physicsRun.CurrentEntityKey); - var physicsDockResult = AetheriaRuntimeDaemonOperations.Execute(physicsRun, [physicsDock], context); - Require(physicsDockResult.AppliedCommandIds.Contains(physicsDock.CommandId, StringComparer.Ordinal) && - physicsStation.DockingBayAssignments.Single() == physicsShip.EntityIndex, - "the daemon must still accept docking after Ymir advances the overlapping ship/station bodies: " + - string.Join(",", physicsDockResult.RejectedCommandReasons.Values)); - - static AetheriaRuntimeEntitySnapshotCommit DockTarget(int index, double x, bool hasBay) - { - var target = Entity(index, x, "station"); - target.PositionZ = 0; - if (hasBay) - { - target.DockingBays = [new AetheriaRuntimeLoadoutItemSlotCommit - { - Item = new AetheriaRuntimeLoadoutItemCommit { ItemKey = "dock-bay" } - }]; - target.DockingBayAssignments = [-1]; - target.DockingBayContents = [Cargo()]; - } - return target; - } - - var actor = Entity(0, 0, "player"); - actor.WeaponGroups = [new[] { 0 }]; - actor.ActiveWeaponGroups = [true]; - actor.WeaponStates = [new AetheriaRuntimeWeaponStateCommit - { - OwnerKind = AetheriaRuntimeBehaviorStateProjector.EquipmentOwnerKind, - OwnerIndex = 0, - BehaviorIndex = 0, - BehaviorKind = AetheriaRuntimeBehaviorKinds.ChargedWeapon, - Firing = true, - TriggerPending = true, - BurstRemaining = 2, - Charging = true, - Charged = true, - Charge = 1, - ChargeHoldSeconds = 3, - ChargeRiskChecks = 2, - ChargeMalfunctionRisk = 0.5, - LockTargetEntityIndex = 4, - LockProgress = 1 - }]; - var ineligible = DockTarget(1, 5, false); - var outOfRange = DockTarget(2, 30, true); - var firstEligible = DockTarget(3, 20, true); - var nearerButLater = DockTarget(4, 10, true); - var selectionRun = new AetheriaRuntimeRunCheckpointCommit - { - RunId = "dock-selection-smoke", - CurrentZoneIndex = 0, - CurrentEntityKey = "zone.0.entity.0", - Zones = [new AetheriaRuntimeZoneSnapshotCommit - { - ZoneIndex = 0, - Entities = [actor, ineligible, outOfRange, firstEligible, nearerButLater] - }] - }; - var dock = AetheriaRuntimeDaemonCommandDocument.Create( - AetheriaRuntimeDaemonCommandKinds.DockNearest, - "pilot", - "dock-selection-smoke", - 0, - selectionRun.CurrentEntityKey); - dock.ScalarValue = 100; - var dockResult = AetheriaRuntimeDaemonOperations.Execute(selectionRun, [dock], context); - RequireEqual(1, dockResult.AppliedCommandIds.Count, - "DockNearest must accept the first entity with a real empty bay inside the fossil radius"); - RequireEqual(actor.EntityIndex, firstEligible.DockingBayAssignments.Single(), - "docking must use the first eligible entity in stable zone order instead of the closest entity"); - RequireEqual(-1, nearerButLater.DockingBayAssignments.Single(), - "a later closer station must not override the fossil first-eligible selection"); - RequireEqual(0, ineligible.DockingBayAssignments.Count, - "docking must not manufacture a bay assignment on an entity with no docking-bay equipment"); - RequireEqual(-1, outOfRange.DockingBayAssignments.Single(), - "a client scalar must not widen the daemon-owned fossil docking radius"); - var dockedEvent = selectionRun.GameEvents.Single(value => value.Kind == "ship.docked"); - Require(dockedEvent.SourceEntityIndex == actor.EntityIndex && - dockedEvent.TargetEntityIndex == firstEligible.EntityIndex && - dockedEvent.ScalarValue == 0 && dockedEvent.SubjectKey == selectionRun.CurrentEntityKey, - "accepted docking must publish one retained actor/parent/bay transition fact"); - var disarmed = actor.WeaponStates.Single(); - RequireEqual(0, actor.ActiveWeaponGroups.Count, - "docking must clear the daemon-owned held weapon-group latch"); - RequireNear(0, actor.VelocityX, 0.000001, - "docking must clear retained X velocity before Ymir excludes the attached child body"); - RequireNear(0, actor.VelocityY, 0.000001, - "docking must clear retained Z velocity before Ymir excludes the attached child body"); - RequireNear(0, actor.HelmStrafe, 0.000001, - "docking must clear retained strafe intent"); - RequireNear(0, actor.HelmForward, 0.000001, - "docking must clear retained forward intent"); - Require(!disarmed.Firing && !disarmed.TriggerPending && disarmed.BurstRemaining == 0 && - !disarmed.Charging && !disarmed.Charged && disarmed.Charge == 0 && - disarmed.LockTargetEntityIndex < 0 && disarmed.LockProgress == 0, - "docking must cancel every transient weapon lifecycle before the ship enters its bay"); - - var dockedFire = AetheriaRuntimeDaemonCommandDocument.Create( - AetheriaRuntimeDaemonCommandKinds.SetWeaponGroupActive, - "pilot", - selectionRun.RunId, - 1, - selectionRun.CurrentEntityKey); - dockedFire.CommandId = "docked-fire-forbidden"; - dockedFire.WeaponGroup = 0; - dockedFire.ScalarValue = 1; - var dockedFireResult = AetheriaRuntimeDaemonOperations.Execute(selectionRun, [dockedFire], context); - Require(dockedFireResult.RejectedCommandIds.Contains(dockedFire.CommandId) && - dockedFireResult.Intents.WeaponGroups.Count == 0 && actor.ActiveWeaponGroups.Count == 0, - "a docked actor must reject held-fire commands without rearming daemon state"); - - AetheriaRuntimeRunCheckpointCommit DockedRun( - IReadOnlyList itemKeys, - bool bayHasCargo = false) - { - var ship = Entity(0, 7, "player"); - ship.PositionZ = 9; - ship.VelocityX = 3; - ship.VelocityY = -4; - ship.DirectionX = 0.6; - ship.DirectionY = 0.8; - ship.Equipment = itemKeys.Select(itemKey => new AetheriaRuntimeLoadoutItemSlotCommit - { - Item = new AetheriaRuntimeLoadoutItemCommit - { - ItemKey = itemKey, - Durability = 1, - Enabled = true - } - }).ToArray(); - var station = DockTarget(1, 7, true); - station.PositionZ = 9; - station.ChildEntityIndices = [0]; - station.DockingBayAssignments = [0]; - station.DockingBayContents = [bayHasCargo - ? Cargo(("forgotten-cargo", 1, 0, 0)) - : Cargo()]; - return new AetheriaRuntimeRunCheckpointCommit - { - RunId = "undock-parity-smoke", - CurrentZoneIndex = 0, - CurrentEntityKey = "zone.0.entity.0", - Zones = [new AetheriaRuntimeZoneSnapshotCommit { ZoneIndex = 0, Entities = [ship, station] }] - }; - } - - AetheriaRuntimeDaemonOperationResult RejectUndock( - IReadOnlyList itemKeys, - string expectedReason, - bool bayHasCargo = false) - { - var run = DockedRun(itemKeys, bayHasCargo); - var command = AetheriaRuntimeDaemonCommandDocument.Create( - AetheriaRuntimeDaemonCommandKinds.Undock, - "pilot", - "undock-parity-smoke", - 0, - run.CurrentEntityKey); - command.CommandId = $"undock:{expectedReason}"; - var result = AetheriaRuntimeDaemonOperations.Execute(run, [command], context); - RequireEqual(expectedReason, result.RejectedCommandReasons[command.CommandId], - "undock rejection must retain the exact daemon-owned fossil reason"); - Require(run.Zones[0].Entities[1].DockingBayAssignments.Contains(0), - "a rejected undock must leave parentage and bay assignment untouched"); - Require(!run.GameEvents.Any(value => value.Kind == "ship.undocked"), - "a rejected undock must not publish a false presentation transition"); - return result; - } - - RejectUndock(["dock-thruster", "dock-reactor"], - AetheriaRuntimeDaemonRejectionReasons.MissingCockpit); - RejectUndock(["dock-cockpit", "dock-reactor"], - AetheriaRuntimeDaemonRejectionReasons.MissingPropulsion); - RejectUndock(["dock-cockpit", "dock-thruster"], - AetheriaRuntimeDaemonRejectionReasons.MissingReactor); - var cargoRejected = RejectUndock( - ["dock-cockpit", "dock-thruster", "dock-reactor"], - AetheriaRuntimeDaemonRejectionReasons.DockingBayCargoNotEmpty, - bayHasCargo: true); - - var cargoFrame = AetheriaRuntimeDaemonFrameDocument.Create( - cargoRejected.Run, "daemon", "session", 4, 0.08, 0.02); - cargoFrame.RejectedCommandIds = cargoRejected.RejectedCommandIds; - cargoFrame.RejectedCommandReasons = cargoRejected.RejectedCommandReasons; - var cargoCommand = AetheriaRuntimeDaemonCommandDocument.Create( - AetheriaRuntimeDaemonCommandKinds.Undock, - "pilot", - "undock-parity-smoke", - 0, - cargoRejected.Run.CurrentEntityKey); - cargoCommand.CommandId = "undock:docking-bay-cargo-not-empty"; - var fact = AetheriaRuntimeCommittedCommandFactDocument.FromRejectedCommand( - cargoFrame, cargoCommand, "aetheria.local"); - RequireEqual(AetheriaRuntimeDaemonRejectionReasons.DockingBayCargoNotEmpty, fact.RejectionReason, - "the committed command fact must carry the daemon-owned undock rejection reason"); - var receipt = AetheriaRuntimeDaemonReceiptProjector.Project( - fact, AetheriaRuntimeDaemonGameSurfaceBuilder.SurfaceId); - Require(receipt.State == "denied" && - receipt.Message.Contains(AetheriaRuntimeDaemonRejectionReasons.DockingBayCargoNotEmpty, - StringComparison.Ordinal), - "the generic Eve receipt must expose the exact rejection reason without client reconstruction"); - - var successRun = DockedRun(["dock-cockpit", "dock-drive", "dock-reactor"]); - var successShip = successRun.Zones[0].Entities[0]; - var poseBefore = ( - successShip.PositionX, - successShip.PositionZ, - successShip.VelocityX, - successShip.VelocityY, - successShip.DirectionX, - successShip.DirectionY); - var undock = AetheriaRuntimeDaemonCommandDocument.Create( - AetheriaRuntimeDaemonCommandKinds.Undock, - "pilot", - "undock-parity-smoke", - 0, - successRun.CurrentEntityKey); - var undockResult = AetheriaRuntimeDaemonOperations.Execute(successRun, [undock], context); - RequireEqual(1, undockResult.AppliedCommandIds.Count, - "AetherDrive must satisfy the fossil propulsion alternative for rare compatible ships"); - RequireEqual(poseBefore, ( - successShip.PositionX, - successShip.PositionZ, - successShip.VelocityX, - successShip.VelocityY, - successShip.DirectionX, - successShip.DirectionY), - "undocking must preserve the ship pose, velocity, and direction instead of applying an invented launch"); - Require(!successRun.Zones[0].Entities[1].DockingBayAssignments.Contains(0), - "successful undocking must release the exact occupied bay"); - var undockedEvent = successRun.GameEvents.Single(value => value.Kind == "ship.undocked"); - Require(undockedEvent.SourceEntityIndex == successShip.EntityIndex && - undockedEvent.TargetEntityIndex == successRun.Zones[0].Entities[1].EntityIndex && - undockedEvent.ScalarValue == 0, - "accepted undocking must retain the released parent and bay after state removal"); - var surface = AetheriaRuntimeDaemonGameSurfaceBuilder.Build( - new AetheriaRuntimeDaemonFrameDocument { FrameId = 1, Run = successRun }, - new AetheriaRuntimeDaemonHealthDocument(), - AetheriaRuntimeDaemonCommandBoundaryDocument.Create("daemon"), - catalog: catalog); - var reactive = AetheriaRuntimeDaemonGameSurfaceBuilder.BuildReactiveGameplay( - new AetheriaRuntimeDaemonFrameDocument { FrameId = 1, Run = successRun }); - Require(Flatten(reactive.Surface.Root).Any(node => node.Kind == "feedback.event" && - node.Props["eventKind"] == "ship.undocked" && - node.Props["sourceEntityIndex"] == "0" && node.Props["targetEntityIndex"] == "1" && - node.Props["scalarValue"] == "0"), - "generic Eve feedback must expose authoritative docking chronology and bay identity"); - } - - private static void DockedEntitiesCannotAttackOrBeTargeted() - { - var weaponPayload = new AetheriaRuntimeBehaviorPayload( - 0, - AetheriaRuntimeBehaviorKinds.ConstantWeapon, - 0, - [ - new AetheriaRuntimeBehaviorField(2, PerformanceStat(10)), - new AetheriaRuntimeBehaviorField(6, PerformanceStat(200)), - new AetheriaRuntimeBehaviorField(9, PerformanceStat(0)), - new AetheriaRuntimeBehaviorField(10, PerformanceStat(0)), - new AetheriaRuntimeBehaviorField(11, PerformanceStat(0)), - new AetheriaRuntimeBehaviorField(13, Number(0)), - new AetheriaRuntimeBehaviorField(14, Number(1)), - new AetheriaRuntimeBehaviorField(17, Number(1)) - ]); - var weapon = CatalogItem("docked-combat-weapon", weaponPayload); - var catalog = new AetheriaRuntimeCatalogSnapshot([weapon], [], []); - - static AetheriaRuntimeEntitySnapshotCommit ArmedShip(int index, double x, string faction, string itemKey) - { - var ship = Entity(index, x, faction); - ship.WeaponGroups = [new[] { 0 }]; - ship.ActiveWeaponGroups = [true]; - ship.Equipment = [new AetheriaRuntimeLoadoutItemSlotCommit - { - Item = new AetheriaRuntimeLoadoutItemCommit - { ItemKey = itemKey, Quality = 1, Durability = 1, Enabled = true } - }]; - return ship; - } - - var dockedAttacker = ArmedShip(0, 0, "player", weapon.ItemKey); - dockedAttacker.TargetEntityIndex = 2; - var station = Entity(1, 0, "player"); - station.ChildEntityIndices = [0]; - station.DockingBayAssignments = [0]; - var exposedTarget = Entity(2, 50, "raider"); - var attackerRun = new AetheriaRuntimeRunCheckpointCommit - { - RunId = "docked-attacker-smoke", - CurrentZoneIndex = 0, - Zones = [new AetheriaRuntimeZoneSnapshotCommit - { ZoneIndex = 0, Entities = [dockedAttacker, station, exposedTarget] }] - }; - var forgedIntent = new AetheriaRuntimeDaemonIntentState(); - forgedIntent.WeaponGroups.Add(new AetheriaRuntimeDaemonWeaponGroupIntent - { ActorEntityKey = "zone.0.entity.0", WeaponGroup = 0, Fire = true, Active = true }); - AetheriaRuntimeDaemonSimulation.Step( - attackerRun, forgedIntent, 0.1, new AetheriaRuntimeDaemonSimulationSettings(), - NewPhysics(), catalog, 0, 0); - RequireEqual(0, attackerRun.ShotReceipts.Count, - "combat must subtract docked attackers even when stale state and a forged pulse both request fire"); - RequireNear(100, Stat(exposedTarget, "hull"), 0.000001, - "a docked child must not damage an exposed world entity"); - - var exposedAttacker = ArmedShip(0, 0, "player", weapon.ItemKey); - exposedAttacker.TargetEntityIndex = 1; - var dockedTarget = Entity(1, 50, "raider"); - var hostileStation = Entity(2, 50, "raider"); - hostileStation.ChildEntityIndices = [1]; - hostileStation.DockingBayAssignments = [1]; - var targetRun = new AetheriaRuntimeRunCheckpointCommit - { - RunId = "docked-target-smoke", - CurrentZoneIndex = 0, - Zones = [new AetheriaRuntimeZoneSnapshotCommit - { ZoneIndex = 0, Entities = [exposedAttacker, dockedTarget, hostileStation] }] - }; - AetheriaRuntimeDaemonSimulation.Step( - targetRun, new AetheriaRuntimeDaemonIntentState(), 0.1, - new AetheriaRuntimeDaemonSimulationSettings(), NewPhysics(), catalog, 0, 0); - RequireEqual(0, targetRun.ShotReceipts.Count, - "combat target lookup must subtract docked children from world participants"); - RequireNear(100, Stat(dockedTarget, "hull"), 0.000001, - "a docked child must not receive ordinary world combat damage through stale targeting"); - } - - private static void RefitIsAtomicAndUsesTypedPlacement() - { - static AetheriaRuntimeCatalogItem ShapedItem( - string key, - string category, - string hardpointType, - int width, - int height, - params (int X, int Y)[] cells) - { - var item = CatalogItem(key); - item.Category = category; - item.HardpointType = hardpointType; - item.ShapeWidth = width; - item.ShapeHeight = height; - item.OccupiedCells = cells.Length; - item.ShapeCells = cells.Select(cell => new AetheriaRuntimeShapeCell(cell.X, cell.Y)).ToArray(); - return item; - } - - var hull = HullCatalogItem("refit-hull", 4, 3, 1); - hull.Hardpoints = - [ - new AetheriaRuntimeHardpoint( - "Weapon", - 2, - 0, - 1, - 2, - 2, - [new AetheriaRuntimeShapeCell(0, 0), new AetheriaRuntimeShapeCell(0, 1)], - "", - "Clockwise", - 0) - ]; - var weapon = ShapedItem( - "refit-weapon", - AetheriaRuntimeItemCategories.Weapon, - "Weapon", - 2, - 1, - (0, 0), - (1, 0)); - var blocker = ShapedItem("refit-blocker", AetheriaRuntimeItemCategories.Gear, "", 1, 1, (0, 0)); - var cargoBay = ShapedItem("refit-cargo-bay", AetheriaRuntimeItemCategories.CargoBay, "", 1, 1, (0, 0)); - cargoBay.InteriorShapeWidth = 4; - cargoBay.InteriorShapeHeight = 3; - cargoBay.InteriorOccupiedCells = 12; - cargoBay.InteriorShapeCells = Enumerable.Range(0, 3) - .SelectMany(y => Enumerable.Range(0, 4).Select(x => new AetheriaRuntimeShapeCell(x, y))) - .ToArray(); - var catalog = new AetheriaRuntimeCatalogSnapshot([hull, weapon, blocker, cargoBay], [], []); - - var station = Entity(0, 0, "player"); - station.Kind = "station"; - station.CargoBays = - [ - new AetheriaRuntimeLoadoutItemSlotCommit - { - Item = new AetheriaRuntimeLoadoutItemCommit { ItemKey = cargoBay.ItemKey, Quantity = 1 } - } - ]; - station.CargoContents = [new AetheriaRuntimeCargoBayLoadoutCommit()]; - station.DockingBays = - [ - new AetheriaRuntimeLoadoutItemSlotCommit - { - Item = new AetheriaRuntimeLoadoutItemCommit { ItemKey = "station-refit-dock", Quantity = 1 } - } - ]; - station.DockingBayAssignments = [1]; - station.DockingBayContents = [new AetheriaRuntimeCargoBayLoadoutCommit()]; - station.ChildEntityIndices = [1]; - - var ship = Entity(1, 0, "player"); - ship.HullItemKey = hull.ItemKey; - ship.Equipment = - [ - new AetheriaRuntimeLoadoutItemSlotCommit - { - X = 0, - Y = 0, - Item = new AetheriaRuntimeLoadoutItemCommit - { - ItemKey = blocker.ItemKey, - Quality = 0.8, - Durability = 0.7, - Quantity = 1, - Enabled = false, - OverrideShutdown = true, - Temperature = 321 - } - } - ]; - ship.CargoBays = - [ - new AetheriaRuntimeLoadoutItemSlotCommit - { - X = 3, - Y = 2, - Item = new AetheriaRuntimeLoadoutItemCommit { ItemKey = cargoBay.ItemKey, Quantity = 1 } - } - ]; - ship.CargoContents = - [ - new AetheriaRuntimeCargoBayLoadoutCommit - { - Items = - [ - new AetheriaRuntimeLoadoutItemSlotCommit - { - X = 0, - Y = 0, - Item = new AetheriaRuntimeLoadoutItemCommit - { - ItemKey = weapon.ItemKey, - Quality = 0.9, - Durability = 0.6, - Quantity = 1, - Enabled = true, - Temperature = 444 - } - }, - new AetheriaRuntimeLoadoutItemSlotCommit - { - X = 1, - Y = 0, - Item = new AetheriaRuntimeLoadoutItemCommit { ItemKey = blocker.ItemKey, Quantity = 1 } - }, - new AetheriaRuntimeLoadoutItemSlotCommit - { - X = 2, - Y = 0, - Item = new AetheriaRuntimeLoadoutItemCommit { ItemKey = cargoBay.ItemKey, Quantity = 1 } - } - ] - } - ]; - var run = new AetheriaRuntimeRunCheckpointCommit - { - RunId = "refit-smoke", - CurrentZoneIndex = 0, - CurrentEntityKey = "zone.0.entity.1", - Zones = [new AetheriaRuntimeZoneSnapshotCommit { ZoneIndex = 0, Entities = [station, ship] }] - }; - var context = new AetheriaRuntimeDaemonOperationContext { Catalog = catalog }; - - AetheriaRuntimeDaemonCommandDocument Equip( - string id, - string sourceKind, - int sourceIndex, - string itemKey, - int sourceX, - int sourceY, - int destinationX, - int destinationY) - { - var command = AetheriaRuntimeDaemonCommandDocument.Create( - AetheriaRuntimeDaemonCommandKinds.EquipItem, - "pilot", - run.RunId, - 1, - run.CurrentEntityKey); - command.CommandId = id; - command.TextValue = itemKey; - command.EquipmentTransfer = new AetheriaRuntimeEquipmentTransferCommand - { - SourceKind = sourceKind, - OriginEntityKey = run.CurrentEntityKey, - OriginIndex = sourceIndex, - SourceX = sourceX, - SourceY = sourceY, - DestinationEntityKey = run.CurrentEntityKey, - DestinationX = destinationX, - DestinationY = destinationY, - HasDestinationPosition = true - }; - return command; - } - - var equipWeapon = AetheriaRuntimeDaemonOperations.Execute( - run, - [Equip("refit-weapon", AetheriaRuntimeRefitSourceKinds.Cargo, 0, weapon.ItemKey, 0, 0, 2, 0)], - context); - Require(equipWeapon.AppliedCommandIds.Contains("refit-weapon"), - "a docked cargo weapon must equip through the daemon transaction"); - var equippedWeapon = ship.Equipment.Single(slot => slot.Item.ItemKey == weapon.ItemKey); - RequireEqual("Clockwise", equippedWeapon.Rotation, - "hardpoint equipment must inherit the authored hardpoint rotation"); - RequireNear(0.9, equippedWeapon.Item.Quality, 0.000001, - "refit must preserve the exact item instance quality"); - RequireNear(444, equippedWeapon.Item.Temperature, 0.000001, - "refit must preserve the exact item instance temperature"); - - var rejectedSource = ship.CargoContents[0].Items.Single(slot => slot.Item.ItemKey == blocker.ItemKey); - var rejectedBefore = ( - Count: ship.CargoContents[0].Items.Count, - X: rejectedSource.X, - Y: rejectedSource.Y, - Quality: rejectedSource.Item.Quality, - Durability: rejectedSource.Item.Durability, - Temperature: rejectedSource.Item.Temperature); - var overlap = AetheriaRuntimeDaemonOperations.Execute( - run, - [Equip("refit-overlap", AetheriaRuntimeRefitSourceKinds.Cargo, 0, blocker.ItemKey, 1, 0, 0, 0)], - context); - RequireEqual(AetheriaRuntimeDaemonRejectionReasons.RefitNoFit, - overlap.RejectedCommandReasons["refit-overlap"], - "occupied interior cells must reject with the daemon-owned fit reason"); - var rejectedAfter = ship.CargoContents[0].Items.Single(slot => slot.Item.ItemKey == blocker.ItemKey); - RequireEqual(rejectedBefore.Count, ship.CargoContents[0].Items.Count, - "rejected refit must not remove the source item"); - Require(rejectedBefore.X == rejectedAfter.X && rejectedBefore.Y == rejectedAfter.Y && - rejectedBefore.Quality == rejectedAfter.Item.Quality && - rejectedBefore.Durability == rejectedAfter.Item.Durability && - rejectedBefore.Temperature == rejectedAfter.Item.Temperature, - "rejected refit must preserve every source instance field"); - - var equipBay = AetheriaRuntimeDaemonOperations.Execute( - run, - [Equip("refit-bay", AetheriaRuntimeRefitSourceKinds.Cargo, 0, cargoBay.ItemKey, 2, 0, 1, 2)], - context); - Require(equipBay.AppliedCommandIds.Contains("refit-bay") && ship.CargoBays.Count == 2 && ship.CargoContents.Count == 2, - "catalog cargo-bay category must route into aligned typed bay and contents collections"); - - ship.CargoContents[1].Items = - [ - new AetheriaRuntimeLoadoutItemSlotCommit - { - Item = new AetheriaRuntimeLoadoutItemCommit { ItemKey = blocker.ItemKey, Quantity = 1 } - } - ]; - var storeBay = AetheriaRuntimeDaemonCommandDocument.Create( - AetheriaRuntimeDaemonCommandKinds.StoreItem, - "pilot", - run.RunId, - 1, - run.CurrentEntityKey); - storeBay.CommandId = "refit-nonempty-bay"; - storeBay.TextValue = cargoBay.ItemKey; - storeBay.StoreItem = new AetheriaRuntimeStoreItemCommand - { - SourceKind = AetheriaRuntimeRefitSourceKinds.CargoBay, - OriginEntityKey = run.CurrentEntityKey, - SourceEquipmentIndex = 1, - DestinationEntityKey = "zone.0.entity.0", - DestinationCargoIndex = 0, - DestinationX = 0, - DestinationY = 0, - HasDestinationPosition = true - }; - var nonempty = AetheriaRuntimeDaemonOperations.Execute(run, [storeBay], context); - RequireEqual(AetheriaRuntimeDaemonRejectionReasons.RefitBayNotEmpty, - nonempty.RejectedCommandReasons[storeBay.CommandId], - "a nonempty cargo bay must reject unequip before any mutation"); - RequireEqual(2, ship.CargoBays.Count, - "nonempty bay rejection must preserve the installed bay"); - - var frame = AetheriaRuntimeDaemonFrameDocument.Create(run, "daemon", "session", 8, 0.16, 0.02); - frame.RejectedCommandIds = nonempty.RejectedCommandIds; - frame.RejectedCommandReasons = nonempty.RejectedCommandReasons; - var fact = AetheriaRuntimeCommittedCommandFactDocument.FromRejectedCommand(frame, storeBay, "aetheria.local"); - var receipt = AetheriaRuntimeDaemonReceiptProjector.Project( - fact, - AetheriaRuntimeDaemonGameSurfaceBuilder.SurfaceId); - RequireEqual(AetheriaRuntimeDaemonRejectionReasons.RefitBayNotEmpty, fact.RejectionReason, - "refit rejection reason must survive the committed command fact"); - Require(receipt.State == "denied" && - receipt.Message.Contains(AetheriaRuntimeDaemonRejectionReasons.RefitBayNotEmpty, StringComparison.Ordinal), - "generic Eve receipt must expose the exact refit rejection reason"); - - station.DockingBayAssignments = [-1]; - station.ChildEntityIndices = []; - var deployed = AetheriaRuntimeDaemonOperations.Execute( - run, - [Equip("refit-deployed", AetheriaRuntimeRefitSourceKinds.Equipment, 0, blocker.ItemKey, int.MinValue, int.MinValue, 1, 1)], - context); - RequireEqual(AetheriaRuntimeDaemonRejectionReasons.RefitRequiresDocked, - deployed.RejectedCommandReasons["refit-deployed"], - "deployed ships must not refit even when the requested placement would fit"); - Require(ship.Equipment.Any(slot => slot.Item.ItemKey == blocker.ItemKey), - "deployed refit rejection must preserve installed equipment"); - } - - private static void CargoTransferUsesSpatialAtomicTransaction() - { - var cargoBay = CatalogItem("grid-cargo-bay"); - cargoBay.Category = AetheriaRuntimeItemCategories.CargoBay; - cargoBay.ShapeWidth = 1; - cargoBay.ShapeHeight = 1; - cargoBay.ShapeCells = [new AetheriaRuntimeShapeCell(0, 0)]; - cargoBay.InteriorShapeWidth = 2; - cargoBay.InteriorShapeHeight = 1; - cargoBay.InteriorOccupiedCells = 2; - cargoBay.InteriorShapeCells = [new AetheriaRuntimeShapeCell(0, 0), new AetheriaRuntimeShapeCell(1, 0)]; - var commodity = CatalogItem("grid-commodity"); - commodity.Category = AetheriaRuntimeItemCategories.SimpleCommodity; - commodity.ShapeWidth = 1; - commodity.ShapeHeight = 1; - commodity.ShapeCells = [new AetheriaRuntimeShapeCell(0, 0)]; - commodity.Stackable = true; - commodity.MaxStack = 10; - var blocker = CatalogItem("grid-blocker"); - blocker.ShapeWidth = 1; - blocker.ShapeHeight = 1; - blocker.ShapeCells = [new AetheriaRuntimeShapeCell(0, 0)]; - var catalog = new AetheriaRuntimeCatalogSnapshot([cargoBay, commodity, blocker], [], []); - - AetheriaRuntimeEntitySnapshotCommit CargoEntity(int index, params AetheriaRuntimeLoadoutItemSlotCommit[] items) - { - var entity = Entity(index, 0, "player"); - entity.CargoBays = - [ - new AetheriaRuntimeLoadoutItemSlotCommit - { - Item = new AetheriaRuntimeLoadoutItemCommit { ItemKey = cargoBay.ItemKey, Quantity = 1 } - } - ]; - entity.CargoContents = [new AetheriaRuntimeCargoBayLoadoutCommit { Items = items }]; - return entity; - } - - var source = CargoEntity(0, new AetheriaRuntimeLoadoutItemSlotCommit - { - X = 0, - Y = 0, - Rotation = "None", - Item = new AetheriaRuntimeLoadoutItemCommit - { - ItemKey = commodity.ItemKey, - Quantity = 4, - Quality = 0.75, - Durability = 0.5, - Temperature = 333 - } - }); - var destination = CargoEntity( - 1, - new AetheriaRuntimeLoadoutItemSlotCommit - { - X = 0, - Y = 0, - Item = new AetheriaRuntimeLoadoutItemCommit { ItemKey = blocker.ItemKey, Quantity = 1 } - }, - new AetheriaRuntimeLoadoutItemSlotCommit - { - X = 1, - Y = 0, - Item = new AetheriaRuntimeLoadoutItemCommit { ItemKey = commodity.ItemKey, Quantity = 8 } - }); - var run = new AetheriaRuntimeRunCheckpointCommit - { - RunId = "cargo-grid-smoke", - CurrentZoneIndex = 0, - CurrentEntityKey = "zone.0.entity.0", - Zones = [new AetheriaRuntimeZoneSnapshotCommit { ZoneIndex = 0, Entities = [source, destination] }] - }; - var context = new AetheriaRuntimeDaemonOperationContext { Catalog = catalog }; - - AetheriaRuntimeDaemonCommandDocument Transfer(string id, int quantity, int x, bool positioned = true) - { - var command = AetheriaRuntimeDaemonCommandDocument.Create( - AetheriaRuntimeDaemonCommandKinds.TransferCargoItem, - "pilot", - run.RunId, - 0, - run.CurrentEntityKey); - command.CommandId = id; - command.TextValue = commodity.ItemKey; - command.CargoTransfer = new AetheriaRuntimeCargoTransferCommand - { - OriginEntityKey = "zone.0.entity.0", - OriginCargoIndex = 0, - DestinationEntityKey = "zone.0.entity.1", - DestinationCargoIndex = 0, - SourceX = 0, - SourceY = 0, - DestinationX = x, - DestinationY = 0, - HasDestinationPosition = positioned, - Quantity = quantity - }; - return command; - } - - var overfill = AetheriaRuntimeDaemonOperations.Execute(run, [Transfer("cargo-overfill", 3, 1)], context); - RequireEqual(AetheriaRuntimeDaemonRejectionReasons.CargoStackLimit, - overfill.RejectedCommandReasons["cargo-overfill"], - "an explicit commodity stack overfill must reject atomically"); - RequireEqual(4, source.CargoContents[0].Items.Single().Item.Quantity, - "stack-limit rejection must not decrement the source stack"); - RequireEqual(8, destination.CargoContents[0].Items.Single(slot => slot.Item.ItemKey == commodity.ItemKey).Item.Quantity, - "stack-limit rejection must not partially fill the destination stack"); - - var stacked = AetheriaRuntimeDaemonOperations.Execute(run, [Transfer("cargo-stack", 2, 1)], context); - Require(stacked.AppliedCommandIds.Contains("cargo-stack"), - "a complete explicit stack merge must commit"); - RequireEqual(2, source.CargoContents[0].Items.Single().Item.Quantity, - "accepted stack merge must remove exactly the requested source quantity"); - RequireEqual(10, destination.CargoContents[0].Items.Single(slot => slot.Item.ItemKey == commodity.ItemKey).Item.Quantity, - "accepted stack merge must fill the destination to the typed maximum"); - - var occupied = AetheriaRuntimeDaemonOperations.Execute(run, [Transfer("cargo-overlap", 1, 0)], context); - RequireEqual(AetheriaRuntimeDaemonRejectionReasons.CargoNoFit, - occupied.RejectedCommandReasons["cargo-overlap"], - "an occupied cell containing another item must reject spatial placement"); - RequireEqual(2, source.CargoContents[0].Items.Single().Item.Quantity, - "spatial rejection must leave the source stack untouched"); - - destination.CargoContents[0].Items = []; - var placed = AetheriaRuntimeDaemonOperations.Execute(run, [Transfer("cargo-place", 2, 0, positioned: false)], context); - Require(placed.AppliedCommandIds.Contains("cargo-place") && source.CargoContents[0].Items.Count == 0, - "unpositioned transfer must commit only after finding the first authored interior cell"); - var moved = destination.CargoContents[0].Items.Single(); - Require(moved.X == 0 && moved.Y == 0 && moved.Item.Quantity == 2, - "unpositioned cargo placement must use deterministic interior order"); - RequireNear(0.75, moved.Item.Quality, 0.000001, - "cargo transfer must preserve source instance quality"); - RequireNear(333, moved.Item.Temperature, 0.000001, - "cargo transfer must preserve source instance temperature"); - } - - private static void SectorMapSurfacePublishesDiscoveredGraphAndVisualLevers() - { - var map = new AetheriaRuntimeSectorMapDocument - { - FrameId = 42, - RunId = "run.map", - CurrentZoneIndex = 1, - EntranceZoneIndex = 0, - ExitZoneIndex = 2, - DiscoveredZoneIndices = new[] { 0, 1 }, - Zones = new[] - { - new AetheriaRuntimeSectorMapZone - { - ZoneIndex = 0, - Name = "Gate", - X = -10, - Y = 5, - Entrance = true, - Discovered = true - }, - new AetheriaRuntimeSectorMapZone - { - ZoneIndex = 1, - Name = "Here", - X = 4, - Y = 7, - Current = true, - Discovered = true, - OwnerFactionIndex = 3, - FactionIndices = new[] { 3, 4 } - }, - new AetheriaRuntimeSectorMapZone - { - ZoneIndex = 2, - Name = "Unseen Exit", - X = 20, - Y = 12, - Exit = true - } - }, - Links = new[] - { - new AetheriaRuntimeSectorMapLink - { - FromZoneIndex = 0, - ToZoneIndex = 1, - Discovered = true - }, - new AetheriaRuntimeSectorMapLink - { - FromZoneIndex = 1, - ToZoneIndex = 2, - Discovered = false - } - }, - HomeZones = new[] - { - new AetheriaRuntimeFactionZoneCommit { FactionIndex = 3, ZoneIndex = 1 } - }, - BossZones = new[] - { - new AetheriaRuntimeFactionZoneCommit { FactionIndex = 3, ZoneIndex = 1 } - } - }; - - var surface = AetheriaRuntimeSectorMapSurfaceBuilder.Build( - map, - "2026-07-17T00:00:00.0000000Z"); - var graph = surface.Surface.Root.Children.Single(child => child.Kind == "graph"); - var legend = surface.Surface.Root.Children.Single(child => child.Kind == "graph.legend"); - var nodes = graph.Children.Where(child => child.Kind == "graph.node").ToArray(); - var edges = graph.Children.Where(child => child.Kind == "graph.edge").ToArray(); - var regions = graph.Children.Where(child => child.Kind == "graph.region").ToArray(); - - RequireEqual(AetheriaRuntimeSectorMapSurfaceBuilder.SurfaceId, surface.Surface.Id, - "the portable sector map must have one stable advertised surface identity"); - RequireEqual("graph", graph.Kind, - "the sector map must lower through Eve's generic graph component"); - RequireEqual(2, nodes.Length, - "the sector-map surface must not reveal an undiscovered zone through presentation state"); - RequireEqual(1, edges.Length, - "the sector-map surface must publish only discovered links whose endpoints are visible"); - Require(edges.Single().Props["critical"] == "true", - "links on the weighted entrance-to-Terminus route must carry the historical critical-path role"); - Require(nodes.Single(node => node.Props["zoneIndex"] == "1").Props["role"] == "current", - "the daemon current-zone fact must select the graph's current semantic role"); - Require(nodes.Single(node => node.Props["zoneIndex"] == "1").Props["landmarkAssetKeys"] - .Contains(AetheriaRuntimeSectorMapSurfaceBuilder.HomeIconAssetKey, StringComparison.Ordinal) && - nodes.Single(node => node.Props["zoneIndex"] == "1").Props["landmarkAssetKeys"] - .Contains(AetheriaRuntimeSectorMapSurfaceBuilder.ExecutiveIconAssetKey, StringComparison.Ordinal), - "home and executive landmarks must survive daemon persistence as independent map semantics"); - Require(!nodes.Any(node => node.Props["label"] == "Unseen Exit"), - "provider presentation must not leak undiscovered zone labels"); - RequireEqual(2, regions.Length, - "every visible faction must publish its own historical influence scalar field"); - Require(regions.Single(region => region.Props["factionIndex"] == "3").Children - .Single(sample => sample.Props["nodeId"].EndsWith(".zone.1", StringComparison.Ordinal)) - .Props["value"] == "10", - "an owning faction must contribute the fossil owner influence value at its zone"); - Require(legend.Children.Any(item => item.Props.TryGetValue("role", out var role) && role == "executive"), - "the portable map legend must retain the historical executive landmark semantics"); - Require(surface.Surface.Styles.Any(style => style.Name == "sectorMap.zone.current.labelColor") && - surface.Surface.Styles.Any(style => style.Name == "sectorMap.link.critical.width") && - surface.Surface.Styles.Any(style => style.Name == "sectorMap.reveal.linkDurationSeconds") && - surface.Surface.Styles.Any(style => style.Name == "sectorMap.zoom.maximum") && - surface.Surface.Styles.Any(style => style.Name == "sectorMap.faction.3.primary"), - "provider-owned map color, geometry, motion, and camera levers must travel with the surface"); - Require(surface.Surface.Styles.Single(style => style.Name == "sectorMap.link.width").Value == "0.002" && - surface.Surface.Styles.Single(style => style.Name == "sectorMap.influence.fillTiling").Value == "1024" && - graph.Props["backgroundAssetKey"] == AetheriaRuntimeSectorMapSurfaceBuilder.BackgroundAssetKey, - "the surface must preserve the authored scene and influence-shader values rather than generic substitutes"); - var assets = AetheriaRuntimeAssets.ProjectManifest(new AetheriaRuntimeCatalogSnapshot()); - Require(assets.Assets.Any(asset => asset.Ref.AssetKey == AetheriaRuntimeSectorMapSurfaceBuilder.BackgroundAssetKey) && - assets.Assets.Any(asset => asset.Ref.AssetKey == AetheriaRuntimeSectorMapSurfaceBuilder.InfluenceAssetKey) && - assets.Assets.Any(asset => asset.Ref.AssetKey == AetheriaRuntimeSectorMapSurfaceBuilder.HomeIconAssetKey) && - assets.Assets.Any(asset => asset.Ref.AssetKey == AetheriaRuntimeSectorMapSurfaceBuilder.ExecutiveIconAssetKey), - "provider asset delivery must include the historical galaxy, influence, and landmark assets"); - } - - private static void VolumeSurfaceKeepsNativeShaderAbiInAssetVariant() - { - var player = Entity(0, 0, "player"); - var run = new AetheriaRuntimeRunCheckpointCommit - { - RunId = "volume-program-ownership-smoke", - CurrentZoneIndex = 0, - CurrentEntityKey = "zone.0.entity.0", - Zones = [new AetheriaRuntimeZoneSnapshotCommit { ZoneIndex = 0, Entities = [player] }] - }; - var surface = AetheriaRuntimeDaemonGameSurfaceBuilder.Build( - new AetheriaRuntimeDaemonFrameDocument { FrameId = 1, Run = run }, - new AetheriaRuntimeDaemonHealthDocument(), - AetheriaRuntimeDaemonCommandBoundaryDocument.Create("daemon")); - var volume = Flatten(surface.Surface.Root).Single(node => node.Kind == "field.volume3d"); - var stardust = Flatten(surface.Surface.Root).Single(node => node.Kind == "field.particles3d"); - var gravityField = Flatten(surface.Surface.Root).Single(node => node.Kind == "field.surface2d"); - Require(volume.Props.Values.All(value => value == null || !value.Contains("_Nebula", StringComparison.Ordinal)) && - volume.Props["layerBindings"].Contains("fog.surface_height=surfaceHeight", StringComparison.Ordinal) && - volume.Props["layerTargetDescriptors"].Contains("fog.surface_height=2,2,false,bilinear", StringComparison.Ordinal) && - volume.Props["layerTargetDescriptors"].Contains("fog.tint=0.5,0.5,true,trilinear", StringComparison.Ordinal) && - volume.Props["viewportTextureScaleBindings"] == "ditherCoordinates=dither" && - volume.Props["viewportAnchor"] == stardust.Props["viewportAnchor"] && - volume.Props["span"] == stardust.Props["span"] && - volume.Props["cellWorldSize"] == stardust.Props["cellWorldSize"] && - volume.Props["gravityTexelsPerCell"] == stardust.Props["gravityTexelsPerCell"] && - volume.Props["viewportSnapLayer"] == stardust.Props["viewportSnapLayer"] && - volume.Props["viewportSnapTexels"] == stardust.Props["viewportSnapTexels"] && - volume.Props["documentFloatBindings"] == "simulationTimeSeconds=flowScroll,0.025,0" && - !volume.Props.ContainsKey("vectorParameters"), - "portable Eve volume surfaces must name logical ports rather than Unity shader properties"); - Require(gravityField.Props["minX"] == "-768" && - gravityField.Props["minY"] == "-768" && - gravityField.Props["maxX"] == "768" && - gravityField.Props["maxY"] == "768" && - volume.Props["documentRef"] == gravityField.Props["renderSplatsDocumentId"], - "flight fog rasterization must share the exact 256-by-6 Stardust lattice viewport"); - Require(stardust.Props["documentRef"] == gravityField.Props["renderSplatsDocumentId"] && - stardust.Props["computeProgramAssetRef"] == "compute.environment.stardust" && - stardust.Props["materialAssetRef"] == "material.environment.stardust" && - stardust.Props["span"] == "256" && - stardust.Props["threadGroupSize"] == "128" && - stardust.Props["particleStrideBytes"] == "28" && - stardust.Props["viewportAnchor"] == "active-camera.xz" && - stardust.Props["layerBindings"] == "fog.surface_height=surfaceHeight;fog.tint=tint" && - stardust.Props["materialAssetTextureBindings"] == "texture.environment.volume-dither=dither" && - stardust.Props["materialViewportTextureScaleBindings"] == "ditherCoordinates=dither" && - stardust.Props["materialRenderFrameIndexPort"] == "frameIndex" && - stardust.Props["cellWorldSize"] == "6" && - stardust.Props["gravityTexelsPerCell"] == "8" && - stardust.Props["viewportSnapLayer"] == "fog.surface_height" && - stardust.Props["viewportSnapTexels"] == "8" && - stardust.Props["documentFloatBindings"] == - "simulationTimeSeconds=time,1,0;simulationTimeSeconds=flowScroll,0.025,0" && - stardust.Props["documentTimeVectorPort"] == "timeVector" && - stardust.Props["floatParameters"].Contains( - "period=2;minimumSize=0.25;maximumSize=0.75;spacing=6;ceilingHeight=0;" + - "floorHeight=-10;minHeadroom=25;maxHeadroom=100;heightExponent=3", - StringComparison.Ordinal) && - !stardust.Props["floatParameters"].Contains("patch", StringComparison.OrdinalIgnoreCase), - "portable Stardust semantics must preserve the grid/gravity lattice, dispatch, camera snap, time, and particle values exactly"); - - var shader = AetheriaRuntimeAssets.ProjectManifest(null).Assets.Single(asset => - asset.Ref.AssetKey == "shader.environment.gravity-fog"); - var postProcess = AetheriaRuntimeAssets.ProjectManifest(null).Assets.Single(asset => - asset.Ref.AssetKey == "profile.environment.flight"); - var stardustCompute = AetheriaRuntimeAssets.ProjectManifest(null).Assets.Single(asset => - asset.Ref.AssetKey == "compute.environment.stardust"); - var stardustMaterial = AetheriaRuntimeAssets.ProjectManifest(null).Assets.Single(asset => - asset.Ref.AssetKey == "material.environment.stardust"); - var stardustColors = AetheriaRuntimeAssets.ProjectManifest(null).Assets.Single(asset => - asset.Ref.AssetKey == "texture.environment.stardust-colors"); - Require(postProcess.Ref.Kind == AetheriaRuntimeAssetKinds.VolumeProfile && - postProcess.Ref.Metadata["presentationRole"] == "environment.post-process.flight", - "provider asset catalog must carry the advertised flight post-process profile variant"); - Require(shader.Ref.Metadata["unity.volume.texturePort.surfaceHeight"] == "_NebulaSurfaceHeight" && - shader.Ref.Metadata["unity.volume.matrixPort.cameraToWorld"] == "_CamToWorld" && - shader.Ref.Metadata["unity.volume.pass.raymarch"] == "0" && - shader.Ref.Metadata["unity.volume.pass.composite"] == "2" && - !shader.Ref.Metadata.ContainsKey("unity.volume.pass.temporal") && - !shader.Ref.Metadata.ContainsKey("unity.volume.texturePort.currentSample") && - !shader.Ref.Metadata.ContainsKey("unity.volume.texturePort.history") && - !shader.Ref.Metadata.ContainsKey("unity.volume.matrixPort.previousViewProjection") && - !shader.Ref.Metadata.ContainsKey("unity.volume.floatPort.resetHistory"), - "provider asset metadata must expose current-frame raymarch and composite while URP owns temporal history"); - Require(stardustCompute.Ref.Kind == AetheriaRuntimeAssetKinds.ComputeShader && - stardustCompute.Ref.Metadata["unity.particles.kernel.update"] == "UpdateParticles" && - stardustCompute.Ref.Metadata["unity.particles.bufferPort.particles"] == "particles" && - !stardustCompute.Ref.Metadata.ContainsKey("unity.particles.texturePort.patch") && - !stardustCompute.Ref.Metadata.ContainsKey("unity.particles.texturePort.patchHeight") && - !stardustCompute.Ref.Metadata.ContainsKey("unity.particles.floatPort.patchDensity") && - !stardustCompute.Ref.Metadata.ContainsKey("unity.particles.floatPort.patchBlend") && - stardustCompute.Ref.Metadata["unity.particles.vectorPort.viewportTransform"] == "_GridTransform" && - stardustCompute.Ref.Metadata["unity.particles.vectorPort.timeVector"] == "_Time" && - stardustCompute.Ref.Metadata["unity.particles.intPort.span"] == "span" && - stardustMaterial.Ref.Metadata["unity.particles.bufferPort.particles"] == "particles" && - stardustMaterial.Ref.Metadata["unity.particles.bufferPort.quadPoints"] == "quadPoints" && - stardustMaterial.Ref.Metadata["unity.particles.texturePort.dither"] == "_DitheringTex" && - stardustMaterial.Ref.Metadata["unity.particles.vectorPort.ditherCoordinates"] == "_DitheringCoords" && - stardustMaterial.Ref.Metadata["unity.particles.intPort.frameIndex"] == "_FrameNumber" && - stardustColors.Ref.Metadata["unityAssetPath"] == "Assets/Resources/Gradients/blackbody.png", - "provider asset metadata must own the exact Stardust compute/render ABI and authored color ramp"); - var fogVolume = Flatten(surface.Surface.Root) - .Single(component => string.Equals(component.Kind, "field.volume3d", StringComparison.Ordinal)); - Require(fogVolume.Props.TryGetValue("floatParameters", out var fogParameters) && - fogParameters.Contains("compositeOpacity=1", StringComparison.Ordinal), - "the portable fog surface must retain the fossil cloud shader's full-strength composite default"); - } - - private static AetheriaYmirWorldPhysics NewPhysics() - { - AetheriaYmirWorldPhysics physics = new(); - OwnedPhysics.Add(physics); - return physics; - } - - private static void RunCheck(Action check) - { - try - { - check(); - } - finally - { - foreach (var physics in OwnedPhysics) - physics.Dispose(); - OwnedPhysics.Clear(); - } - } - - private static void PositiveGravityDepthAttractsAndProjectsAsAWell() - { - var body = new AetheriaRuntimeBodySnapshotCommit - { - BodyKey = "gravity-owner", - Kind = "gas_giant", - GravityInfluenceCenterX = 0, - GravityInfluenceCenterZ = 0, - GravityInfluenceRadius = 100, - GravityWellDepth = 20, - GravityWaveRadius = 80, - GravityWaveDepth = 3, - GravityWaveSpeed = 2, - GravityWaveFrequency = 6 - }; - var actor = Entity(0, 10, "player"); - actor.PositionZ = 0; - actor.VelocityX = 0; - actor.VelocityY = 0; - var zone = new AetheriaRuntimeZoneSnapshotCommit - { - ZoneIndex = 0, - Bodies = [body], - Entities = [actor], - GravityTerrainRadius = 100, - GravityTerrainDepth = 5 - }; - - using var physics = NewPhysics(); - physics.RetainWorlds("gravity-sign-smoke", [zone.ZoneIndex]); - var step = physics.Step("gravity-sign-smoke", 1, 0, zone, zone.Entities, 0.1); - var steppedActor = step.Bodies.Single(value => value.EntityIndex == actor.EntityIndex); - Require(steppedActor.VelocityX < 0, - "positive authored gravity depth must accelerate a body toward the well center through Ymir"); - Require(AetheriaRuntimeDaemonRenderQueries.EvaluateGravityTerrainHeight(zone, 10, 0, 0) < 0, - "the same positive gravity-depth magnitude must project as a negative terrain well"); - var splats = AetheriaRuntimeGameDocuments.RenderSplatsViewport( - new AetheriaRuntimeDaemonFrameDocument - { - Run = new AetheriaRuntimeRunCheckpointCommit - { - RunId = "gravity-sign-smoke", - CurrentZoneIndex = 0, - Zones = [zone] - } - }, - new AetheriaRuntimeViewportBounds { MinX = -20, MinY = -20, MaxX = 20, MaxY = 20 }); - var bodySplatIndex = splats.Splats.SourceKey - .Select((sourceKey, index) => (sourceKey, index)) - .Single(value => value.sourceKey == body.BodyKey).index; - var terrainSplatIndex = splats.Splats.SourceKey - .Select((sourceKey, index) => (sourceKey, index)) - .Single(value => value.sourceKey == "environment.gravity_terrain").index; - var fogSurfaceSplatIndex = splats.Splats.SourceKey - .Select((sourceKey, index) => (sourceKey, index)) - .Single(value => value.sourceKey == "environment.gravity_terrain:fog.surface_height").index; - var gravityWaveIndex = splats.Splats.SourceKey - .Select((sourceKey, index) => (sourceKey, index)) - .Single(value => value.sourceKey == body.BodyKey + ":gravity.wave").index; - var fogSurfaceWaveIndex = splats.Splats.SourceKey - .Select((sourceKey, index) => (sourceKey, index)) - .Single(value => value.sourceKey == body.BodyKey + ":fog.surface_height.wave").index; - var fogPatchWaveIndex = splats.Splats.SourceKey - .Select((sourceKey, index) => (sourceKey, index)) - .Single(value => value.sourceKey == body.BodyKey + ":fog.patch_height.wave").index; - Require(splats.Splats.ValueR[bodySplatIndex] < 0, - "render-splat projection must convert positive canonical body depth into a negative gravity-height value"); - Require(splats.Splats.Falloff[terrainSplatIndex] == EveFieldsSplatFalloffs.PowerPulse && - splats.Splats.CenterX[terrainSplatIndex] == 0 && - splats.Splats.CenterY[terrainSplatIndex] == 0 && - splats.Splats.HalfExtentX[terrainSplatIndex] == zone.GravityTerrainRadius && - splats.Splats.FalloffScale[terrainSplatIndex] == 1, - "zone gravity terrain must remain the fossil radial PowerPulse rather than a viewport-sized solid slab"); - Require(splats.Splats.Falloff[bodySplatIndex] == EveFieldsSplatFalloffs.PowerPulse && - splats.Splats.FalloffScale[bodySplatIndex] == 2 && - splats.Splats.FalloffExponent[bodySplatIndex] == body.GravityDepthExponent, - "body gravity wells must publish the fossil radial PowerPulse scale and exponent"); - Require(splats.Splats.LayerIndex[fogSurfaceSplatIndex] != splats.Splats.LayerIndex[terrainSplatIndex] && - splats.Splats.ValueR[fogSurfaceSplatIndex] == -splats.Splats.ValueR[terrainSplatIndex] && - splats.Splats.ValueR[fogSurfaceSplatIndex] > 0 && - splats.Splats.Falloff[fogSurfaceSplatIndex] == EveFieldsSplatFalloffs.PowerPulse, - "the fog surface must project positive fossil PowerBrush depth without changing negative gameplay terrain height"); - foreach (var waveIndex in new[] { gravityWaveIndex, fogSurfaceWaveIndex, fogPatchWaveIndex }) - { - Require(splats.Splats.SourceKind[waveIndex] == EveFieldsSplatSourceKinds.AnimatedRadialCosine && - splats.Splats.HalfExtentX[waveIndex] == body.GravityWaveRadius * 0.5 && - splats.Splats.FrequencyX[waveIndex] == body.GravityWaveFrequency && - splats.Splats.FrequencyY[waveIndex] == 1.25 && - splats.Splats.AnimationSpeed[waveIndex] == body.GravityWaveSpeed && - splats.Splats.Falloff[waveIndex] == EveFieldsSplatFalloffs.PowerPulse && - splats.Splats.FalloffExponent[waveIndex] == 8, - "gravity, fog surface, and fog patch height must share the exact fossil radial-wave source"); - } - var patchLayerIndex = splats.Layers - .Select((layer, index) => (layer, index)) - .Single(value => value.layer.LayerKey == EveFieldsSplatLayerKeys.FogPatch).index; - var patchLayer = splats.Layers[patchLayerIndex]; - var patchMicroIndex = splats.Splats.SourceKey - .Select((sourceKey, index) => (sourceKey, index)) - .Single(value => value.sourceKey == "environment.fog_patch.depth_micro").index; - var patchMacroIndex = splats.Splats.SourceKey - .Select((sourceKey, index) => (sourceKey, index)) - .Single(value => value.sourceKey == "environment.fog_patch.depth_macro").index; - var ambientTintIndex = splats.Splats.SourceKey - .Select((sourceKey, index) => (sourceKey, index)) - .Single(value => value.sourceKey == "environment.fog_tint.ambient").index; - Require(patchLayer.BlendMode == EveFieldsSplatBlendModes.Add, - "fossil patch depth brushes must add instead of taking the maximum"); - Require(splats.Splats.SourceKind[patchMicroIndex] == EveFieldsSplatSourceKinds.AnimatedSimplexNoise && - splats.Splats.SourceFlags[patchMicroIndex] == EveFieldsSplatSourceFlags.AbsoluteValue && - splats.Splats.FrequencyX[patchMicroIndex] == 0.005 && - splats.Splats.AnimationSpeed[patchMicroIndex] == 0.025 && - splats.Splats.HalfExtentX[patchMicroIndex] == 1500, - "fog micro-patch must retain the fossil world-space animated absolute-simplex brush"); - Require(splats.Splats.SourceKind[patchMacroIndex] == EveFieldsSplatSourceKinds.AnimatedCellNoiseB && - splats.Splats.FrequencyX[patchMacroIndex] == 0.001 && - splats.Splats.AnimationSpeed[patchMacroIndex] == 0.025 && - splats.Splats.ValueR[patchMacroIndex] == 20, - "fog macro-patch must retain the fossil moving cellular-B brush"); - Require(splats.Splats.ValueR[ambientTintIndex] > 0 && - splats.Splats.ValueB[ambientTintIndex] > splats.Splats.ValueG[ambientTintIndex] && - splats.Splats.ValueG[ambientTintIndex] > splats.Splats.ValueR[ambientTintIndex], - "fog tint must include the fossil ambient blue brush before local body lights"); - Require(!splats.Splats.SourceKey.Contains("environment.fog_patch") && - !splats.Splats.SourceKey.Contains("environment.fog_patch_height"), - "the viewport-local placeholder fog producers must remain deleted"); - var ordinaryPlanetSplats = AetheriaRuntimeGameDocuments.RenderSplatsViewport( - new AetheriaRuntimeDaemonFrameDocument - { - Run = new AetheriaRuntimeRunCheckpointCommit - { - CurrentZoneIndex = 0, - Zones = - [ - new AetheriaRuntimeZoneSnapshotCommit - { - ZoneIndex = 0, - Bodies = - [ - new AetheriaRuntimeBodySnapshotCommit - { - BodyKey = "ordinary-planet", - Kind = "planet", - GravityInfluenceRadius = 20, - GravityWaveRadius = 20, - GravityWaveDepth = 4 - } - ] - } - ] - } - }, - new AetheriaRuntimeViewportBounds { MinX = -30, MinY = -30, MaxX = 30, MaxY = 30 }); - Require(!ordinaryPlanetSplats.Splats.SourceKey.Any(key => - key.StartsWith("ordinary-planet:", StringComparison.Ordinal) && - key.EndsWith(".wave", StringComparison.Ordinal)), - "ordinary planets must not acquire gas-giant gravity-wave visuals merely because compatibility rows contain wave values"); - - var run = new AetheriaRuntimeRunCheckpointCommit - { - RunId = "gravity-height-smoke", - CurrentZoneIndex = zone.ZoneIndex, - Zones = [zone] - }; - AetheriaRuntimeDaemonSimulation.Step( - run, - new AetheriaRuntimeDaemonIntentState(), - 0.1, - new AetheriaRuntimeDaemonSimulationSettings(), - physics, - simulationTimeSeconds: 0.1); - RequireNear( - AetheriaRuntimeDaemonRenderQueries.EvaluateGravityTerrainHeight( - zone, actor.PositionX, actor.PositionZ, 0.1), - actor.PositionY, - 0.0001, - "daemon entities must retain the fossil terrain-height projection while Ymir owns XZ physics"); - } - - private static void ConstantWeaponRunsOnDaemonThroughYmirBeamContact() - { - var source = Entity(0, 0, "player"); - source.DirectionX = 1; - source.DirectionY = 0; - source.TargetEntityIndex = 2; - source.Contacts = [new AetheriaRuntimeEntityContactCommit - { - TargetEntityIndex = 2, InfoGathered = 1, Visible = true, Hostile = true - }]; - source.WeaponGroups = [new[] { 0 }]; - source.Equipment = - [ - new AetheriaRuntimeLoadoutItemSlotCommit { Item = new AetheriaRuntimeLoadoutItemCommit - { ItemKey = "test-beam", Quality = 0, Durability = 1, Enabled = true } }, - new AetheriaRuntimeLoadoutItemSlotCommit { Item = new AetheriaRuntimeLoadoutItemCommit - { ItemKey = "test-beam-capacitor", Quality = 1, Durability = 1, Enabled = true } } - ]; - source.BehaviorStates = [new AetheriaRuntimeBehaviorStateCommit - { - OwnerKind = AetheriaRuntimeBehaviorStateProjector.EquipmentOwnerKind, OwnerIndex = 1, BehaviorIndex = 0, - BehaviorKind = "Capacitor", CapacitorCharge = 10, CapacitorCapacity = 10, CapacitorEfficiency = 1 - }]; - source.CargoContents = [Cargo(("beam-ammo", 1, 0, 0))]; - var blocker = Entity(1, 40, "neutral"); - var selectedTarget = Entity(2, 100, "raider"); - var zone = new AetheriaRuntimeZoneSnapshotCommit { ZoneIndex = 0, Entities = [source, blocker, selectedTarget] }; - var run = new AetheriaRuntimeRunCheckpointCommit - { - RunId = "constant-weapon-smoke", CurrentZoneIndex = 0, - CurrentEntityKey = AetheriaRuntimeRunCheckpointCommit.EntityRecordKey("constant-weapon-smoke", 0, 0), - Zones = [zone] - }; - var payload = new AetheriaRuntimeBehaviorPayload(0, AetheriaRuntimeBehaviorKinds.ConstantWeapon, 0, - [ - new AetheriaRuntimeBehaviorField(2, PerformanceStat(10)), - new AetheriaRuntimeBehaviorField(6, PerformanceStat(150)), - new AetheriaRuntimeBehaviorField(9, PerformanceStat(2)), - new AetheriaRuntimeBehaviorField(10, PerformanceStat(3)), - new AetheriaRuntimeBehaviorField(11, PerformanceStat(6)), - new AetheriaRuntimeBehaviorField(12, new AetheriaRuntimeBehaviorValue( - "item-key", "", 0, false, "", "beam-ammo", [], [])), - new AetheriaRuntimeBehaviorField(13, Number(3)), - new AetheriaRuntimeBehaviorField(14, Number(0.3)), - new AetheriaRuntimeBehaviorField(17, Number(0.15)) - ]); - var catalog = new AetheriaRuntimeCatalogSnapshot( - [WearableWeaponCatalogItem("test-beam", payload), CatalogItem("test-beam-capacitor", CapacitorPayload(10, 1))], [], []); - var press = AetheriaRuntimeDaemonCommandDocument.Create( - AetheriaRuntimeDaemonCommandKinds.SetWeaponGroupActive, - "pilot", - run.RunId, - 0, - run.CurrentEntityKey); - press.CommandId = "constant-weapon-press"; - press.WeaponGroup = 0; - press.ScalarValue = 1; - var pressed = AetheriaRuntimeDaemonOperations.Execute( - run, [press], new AetheriaRuntimeDaemonOperationContext()); - for (var frame = 0; frame < 7; frame++) - AetheriaRuntimeDaemonSimulation.Step(run, - frame == 0 ? pressed.Intents : new AetheriaRuntimeDaemonIntentState(), 0.1, - new AetheriaRuntimeDaemonSimulationSettings(), - NewPhysics(), catalog, frame, frame * 0.1); - - var state = source.WeaponStates.Single(value => value.BehaviorKind == AetheriaRuntimeBehaviorKinds.ConstantWeapon); - Require(run.GameEvents.Count(value => value.Kind == "weapon.firing.started") == 1, - "held constant fire must publish one start transition"); - Require(run.GameEvents.Count(value => value.Kind == "weapon.reload.started") == 1 && - run.GameEvents.Count(value => value.Kind == "weapon.firing.stopped") == 1, - "empty constant weapon magazine must start reload and stop the held effect once"); - Require(state.Reloading && !state.Firing, - "constant weapon reload must remain authoritative persisted state"); - Require(Math.Abs(Stat(blocker, "hull") - 100) < 0.000001 && Stat(selectedTarget, "hull") < 100, - "constant weapon must resolve against its firing solution rather than a presentation collider"); - Require(run.ShotReceipts.Any(value => value.TargetEntityIndex == selectedTarget.EntityIndex && - value.WeaponItemKey == "test-beam" && value.Hit), - "daemon must publish accepted continuous shot receipts with authored beam identity"); - Require(CargoQuantity(source, "beam-ammo") == 0, - "constant weapon reload must consume its reserve cargo through the shared transaction"); - Require(source.BehaviorStates.Single(value => value.BehaviorKind == "Capacitor").CapacitorCharge < 10, - "constant weapon must pay elapsed-time energy through canonical capacitor state"); - RequireNear(6, source.StatGrids.Single(grid => - grid.Name == "transient-visibility:weapon:equipment:0:0").Values.Single(), 0.000001, - "constant fire must refresh its authored weapon visibility while the beam is committed"); - Require(source.Equipment[0].Item!.Durability < 1, - "constant fire must apply elapsed-time wear only while its beam transaction commits"); - for (var frame = 7; frame < 11; frame++) - AetheriaRuntimeDaemonSimulation.Step(run, new AetheriaRuntimeDaemonIntentState(), 0.1, - new AetheriaRuntimeDaemonSimulationSettings(), - NewPhysics(), catalog, frame, frame * 0.1); - var release = AetheriaRuntimeDaemonCommandDocument.Create( - AetheriaRuntimeDaemonCommandKinds.SetWeaponGroupActive, - "pilot", - run.RunId, - 11, - run.CurrentEntityKey); - release.CommandId = "constant-weapon-release"; - release.WeaponGroup = 0; - release.ScalarValue = 0; - var released = AetheriaRuntimeDaemonOperations.Execute( - run, [release], new AetheriaRuntimeDaemonOperationContext()); - AetheriaRuntimeDaemonSimulation.Step(run, released.Intents, 0.1, - new AetheriaRuntimeDaemonSimulationSettings(), - NewPhysics(), catalog, 11, 1.1); - Require(!state.Firing && run.GameEvents.Count(value => value.Kind == "weapon.firing.stopped") == 2, - "one release command must stop daemon-latched constant fire and publish its transition"); - var surface = AetheriaRuntimeDaemonGameSurfaceBuilder.Build( - new AetheriaRuntimeDaemonFrameDocument { FrameId = 11, Run = run }, - new AetheriaRuntimeDaemonHealthDocument(), - AetheriaRuntimeDaemonCommandBoundaryDocument.Create("daemon")); - var weaponNodes = Flatten(surface.Surface.Root).Where(node => node.Kind == "weapon.state").ToArray(); - Require(weaponNodes.Any(node => - node.Props["behaviorKind"] == AetheriaRuntimeBehaviorKinds.ConstantWeapon && - node.Props["itemKey"] == "test-beam" && node.Props["firing"] == "false"), - $"Eve world entity must project recoverable generic ConstantWeapon state; nodes={string.Join(";", weaponNodes.Select(node => string.Join(",", node.Props.Select(pair => $"{pair.Key}={pair.Value}"))))}"); - var reactive = AetheriaRuntimeDaemonGameSurfaceBuilder.BuildReactiveGameplay( - new AetheriaRuntimeDaemonFrameDocument { FrameId = 11, Run = run }); - Require(Flatten(reactive.Surface.Root).Any(node => node.Kind == "feedback.event" && - node.Props["eventKind"] == "weapon.firing.started" && node.Props["itemKey"] == "test-beam"), - "Eve feedback stream must project continuous weapon transition chronology"); - var cockpit = Flatten(surface.Surface.Root).Single(node => node.Id == "aetheria.daemon.game.cockpit"); - Require(cockpit.Kind == "pane" && cockpit.Props["role"] == "pilot.cockpit" && - cockpit.Layout["position"] == "absolute" && cockpit.Style["background"] == "transparent", - "pilot surface must publish a transparent provider-authored cockpit overlay"); - Require(Flatten(cockpit).Count(node => node.Kind == "progress") >= 7 && - Flatten(cockpit).Any(node => node.Id == "aetheria.daemon.game.cockpit.capacitor") && - Flatten(cockpit).Any(node => node.Id == "aetheria.daemon.game.cockpit.targetLock" && - node.Props["label"] == "TARGET LOCK") && - Flatten(cockpit).Any(node => node.Id == "aetheria.daemon.game.cockpit.targetHull"), - "pilot cockpit must expose native ship and target instrumentation through generic Eve progress components"); - Require(Flatten(surface.Surface.Root).Where(node => - node.Id is "aetheria.daemon.game.frame" or "aetheria.daemon.game.player" or "aetheria.daemon.game.commands") - .All(node => node.Layout.TryGetValue("display", out var display) && display == "none"), - "operator diagnostics must remain published state without becoming pilot-camera UI"); - var mainMenu = Flatten(surface.Surface.Root).Single(node => node.Id == "aetheria.daemon.game.main_menu"); - Require(mainMenu.Layout.TryGetValue("display", out var menuDisplay) && menuDisplay == "none" && - mainMenu.EmbeddedDocuments.Count == 0, - "an active pilot surface must not embed or display the main menu until daemon menu state opens it"); - } - - private static void ChargedWeaponCannotBypassChargeLifecycle() - { - var source = Entity(0, 0, "player"); - source.DirectionX = 1; - source.TargetEntityIndex = -1; - source.WeaponGroups = [new[] { 0 }]; - source.Equipment = [new AetheriaRuntimeLoadoutItemSlotCommit - { - Item = new AetheriaRuntimeLoadoutItemCommit - { ItemKey = "test-charged", Quality = 0, Durability = 1, Enabled = true } - }]; - var target = Entity(1, 80, "raider"); - var zone = new AetheriaRuntimeZoneSnapshotCommit { ZoneIndex = 0, Entities = [source, target] }; - var run = new AetheriaRuntimeRunCheckpointCommit - { - RunId = "charged-weapon-admission-smoke", CurrentZoneIndex = 0, - CurrentEntityKey = "zone.0.entity.0", Zones = [zone] - }; - var payload = new AetheriaRuntimeBehaviorPayload(0, AetheriaRuntimeBehaviorKinds.ChargedWeapon, 0, - [ - new AetheriaRuntimeBehaviorField(2, PerformanceStat(20)), - new AetheriaRuntimeBehaviorField(6, PerformanceStat(150)), - new AetheriaRuntimeBehaviorField(11, PerformanceStat(5)), - new AetheriaRuntimeBehaviorField(15, PerformanceStat(1000000000)), - new AetheriaRuntimeBehaviorField(16, PerformanceStat(200)), - new AetheriaRuntimeBehaviorField(17, PerformanceStat(1)), - new AetheriaRuntimeBehaviorField(19, PerformanceStat(0.5)), - new AetheriaRuntimeBehaviorField(21, PerformanceStat(1)), - new AetheriaRuntimeBehaviorField(24, new AetheriaRuntimeBehaviorValue("bool", "", 0, true, "", "", [], [])), - new AetheriaRuntimeBehaviorField(27, Number(2)), - new AetheriaRuntimeBehaviorField(30, Number(3)) - ]); - var catalog = new AetheriaRuntimeCatalogSnapshot( - [WearableWeaponCatalogItem("test-charged", payload)], [], []); - var intents = new AetheriaRuntimeDaemonIntentState(); - intents.WeaponGroups.Add(new AetheriaRuntimeDaemonWeaponGroupIntent - { - ActorEntityKey = "zone.0.entity.0", WeaponGroup = 0, Fire = true, Active = true - }); - AetheriaRuntimeDaemonSimulation.Step(run, intents, 0.1, - new AetheriaRuntimeDaemonSimulationSettings(), - NewPhysics(), - catalog, 0, 0); - - Require(zone.PhysicalPayloads.Count == 0 && !run.GameEvents.Any(value => value.Kind == "shot.committed"), - "pressing an authored ChargedWeapon must not bypass charging through the instant weapon resolver"); - Require(Math.Abs(Stat(target, "hull") - 100) < 0.000001, - "charged weapon admission must not apply damage before a daemon-owned release commits the shot"); - var state = source.WeaponStates.Single(value => value.BehaviorKind == AetheriaRuntimeBehaviorKinds.ChargedWeapon); - for (var frame = 1; frame < 11; frame++) - AetheriaRuntimeDaemonSimulation.Step(run, new AetheriaRuntimeDaemonIntentState(), 0.1, - new AetheriaRuntimeDaemonSimulationSettings(), - NewPhysics(), - catalog, frame, frame * 0.1); - Require(state.Charging && state.Charged && state.Charge >= 1 && state.ChargeHoldSeconds > 0, - "one semantic request must precharge without a firing solution and enter persisted hold state"); - Require(!run.GameEvents.Any(value => value.Kind == "shot.committed"), - "ready charged weapon must hold rather than invent a firing solution"); - source.TargetEntityIndex = target.EntityIndex; - AetheriaRuntimeDaemonSimulation.Step(run, new AetheriaRuntimeDaemonIntentState(), 0.1, - new AetheriaRuntimeDaemonSimulationSettings(), - NewPhysics(), - catalog, 11, 1.1); - Require(run.GameEvents.Count(value => value.Kind == "weapon.charge.committed") == 1 && - run.GameEvents.Any(value => value.Kind == "shot.committed" && - value.ItemKey == "test-charged" && Math.Abs(value.ScalarValue - 40) < 0.000001), - "stored full charge must commit automatically when a firing solution becomes available"); - RequireNear(15, source.StatGrids.Single(grid => - grid.Name == "transient-visibility:weapon:equipment:0:0").Values.Single(), 0.000001, - "charged fire must publish base visibility multiplied by its committed charge modifier"); - Require(source.Equipment[0].Item!.Durability < 1, - "charged hold must not suppress wear when the stored shot finally commits"); - var miss = run.ShotReceipts.Single(value => value.WeaponItemKey == "test-charged"); - var missDx = miss.EndpointX - target.PositionX; - var missDz = miss.EndpointZ - target.PositionZ; - Require(!miss.Hit && miss.AppliedDamage == 0 && miss.HitRoll > miss.HitProbability, - "extreme authored dispersion must deterministically resolve a miss without damage"); - RequireEqual("none", miss.ImpactKind, "a miss must not advertise a fabricated impact response"); - RequireEqual("bolt", miss.PresentationKind, "instant weapon receipts must advertise a runtime-neutral travelling effect"); - Require(Math.Sqrt(missDx * missDx + missDz * missDz) > 20 && - miss.ImpactAngleRoll >= 0 && miss.ImpactAngleRoll < 1 && - miss.ImpactRadiusRoll >= 0 && miss.ImpactRadiusRoll < 1, - "miss receipt must carry independent named impact rolls and an endpoint outside the target silhouette"); - var surface = AetheriaRuntimeDaemonGameSurfaceBuilder.Build( - new AetheriaRuntimeDaemonFrameDocument { FrameId = 11, Run = run }, - new AetheriaRuntimeDaemonHealthDocument(), - AetheriaRuntimeDaemonCommandBoundaryDocument.Create("daemon")); - Require(Flatten(surface.Surface.Root).Any(node => node.Kind == "weapon.state" && - node.Props.ContainsKey("chargeMalfunctionRisk")), - "Eve must expose charged hold duration and malfunction risk generically"); - var reactive = AetheriaRuntimeDaemonGameSurfaceBuilder.BuildReactiveGameplay( - new AetheriaRuntimeDaemonFrameDocument { FrameId = 11, Run = run }); - Require(Flatten(reactive.Surface.Root).Any(node => node.Kind == "feedback.event" && - node.Props["eventKind"] == "weapon.charge.committed"), - "Eve feedback must expose charged solution commit chronology"); - } - - private static void InstantWeaponRequestSurvivesLockAcquisition() - { - var source = Entity(0, 0, "player"); - source.DirectionX = 0; - source.DirectionY = 1; - source.LookDirectionX = 1; - source.LookDirectionY = 0; - source.VelocityX = 1; - source.TargetEntityIndex = 1; - source.Contacts = [new AetheriaRuntimeEntityContactCommit - { - TargetEntityIndex = 1, InfoGathered = 1, Hostile = true, Visible = true - }]; - source.WeaponGroups = [new[] { 0 }]; - source.Equipment = [new AetheriaRuntimeLoadoutItemSlotCommit - { - Item = new AetheriaRuntimeLoadoutItemCommit - { ItemKey = "test-instant", Quality = 0, Durability = 1, Enabled = true } - }]; - var target = Entity(1, 80, "raider"); - var zone = new AetheriaRuntimeZoneSnapshotCommit - { - ZoneIndex = 0, - Entities = [source, target], - Bodies = - [ - new AetheriaRuntimeBodySnapshotCommit - { - Kind = "sun", - Mass = 16, - SunVisual = new AetheriaRuntimeSunVisualCommit - { - LightColorX = 0.5, - LightColorY = 0.75, - LightColorZ = 1, - LightRadiusMultiplier = 1 - } - } - ] - }; - var run = new AetheriaRuntimeRunCheckpointCommit - { - RunId = "instant-trigger-smoke", CurrentZoneIndex = 0, - CurrentEntityKey = "zone.0.entity.0", Zones = [zone] - }; - var payload = new AetheriaRuntimeBehaviorPayload(0, AetheriaRuntimeBehaviorKinds.InstantWeapon, 0, - [ - new AetheriaRuntimeBehaviorField(2, PerformanceStat(20)), - new AetheriaRuntimeBehaviorField(6, PerformanceStat(150)), - new AetheriaRuntimeBehaviorField(11, PerformanceStat(8)), - new AetheriaRuntimeBehaviorField(15, PerformanceStat(0)), - new AetheriaRuntimeBehaviorField(16, PerformanceStat(200)), - new AetheriaRuntimeBehaviorField(17, PerformanceStat(1)), - new AetheriaRuntimeBehaviorField(19, PerformanceStat(0.5)), - new AetheriaRuntimeBehaviorField(23, PerformanceStat(45)), - new AetheriaRuntimeBehaviorField(24, PerformanceStat(0)), - new AetheriaRuntimeBehaviorField(25, PerformanceStat(1)) - ]); - var catalog = new AetheriaRuntimeCatalogSnapshot([WearableWeaponCatalogItem("test-instant", payload)], [], []); - RequireNear(45, - AetheriaRuntimeEquippedBehaviorQueries.Find(source, catalog, AetheriaRuntimeBehaviorKinds.InstantWeapon) - .Single().EvaluateStat(23), - 0.000001, - "lock-transition fixture must retain its authored 45-degree acquisition cone"); - var fire = new AetheriaRuntimeDaemonIntentState(); - fire.WeaponGroups.Add(new AetheriaRuntimeDaemonWeaponGroupIntent - { - ActorEntityKey = "zone.0.entity.0", WeaponGroup = 0, Fire = true, Active = true - }); - - AetheriaRuntimeDaemonSimulation.Step(run, fire, 0.1, - AetheriaRuntimeDaemonSimulationSettings.AetheriaDefault, - NewPhysics(), catalog, 0, 0); - for (var frame = 1; frame < 12 && run.ShotReceipts.Count == 0; frame++) - AetheriaRuntimeDaemonSimulation.Step(run, new AetheriaRuntimeDaemonIntentState(), 0.1, - AetheriaRuntimeDaemonSimulationSettings.AetheriaDefault, - NewPhysics(), catalog, frame, frame * 0.1); - - Require(run.ShotReceipts.Count == 1 && run.ShotReceipts[0].SourceEntityIndex == source.EntityIndex, - $"one instant weapon request must remain pending until daemon lock acquisition commits its shot; " + - $"receipts={run.ShotReceipts.Count}, hull={source.DirectionX},{source.DirectionY}, look={source.LookDirectionX},{source.LookDirectionY}, contacts={string.Join(";", (source.Contacts ?? []).Select(value => $"{value.TargetEntityIndex}:{value.InfoGathered}"))}, states={string.Join(";", (source.WeaponStates ?? []).Select(value => $"{value.BehaviorKind}:pending={value.TriggerPending}:lock={value.LockProgress}:target={value.LockTargetEntityIndex}:burst={value.BurstRemaining}"))}"); - Require(run.GameEvents.Count(value => value.Kind == "weapon.lock.started" && - value.SourceEntityIndex == 0 && value.TargetEntityIndex == 1) == 1 && - run.GameEvents.Count(value => value.Kind == "weapon.lock.acquired" && - value.SourceEntityIndex == 0 && value.TargetEntityIndex == 1) == 1, - "daemon lock acquisition must publish one start and one completed transition instead of client-timed feedback"); - var state = source.WeaponStates.Single(value => value.BehaviorKind == AetheriaRuntimeBehaviorKinds.InstantWeapon); - Require(!state.TriggerPending, - "instant weapon request must clear after the committed burst begins"); - var weaponVisibility = source.StatGrids.Single(grid => - grid.Name == "transient-visibility:weapon:equipment:0:0"); - RequireNear(8, weaponVisibility.Values.Single(), 0.000001, - "committed instant shot must publish its authored visibility as a named transient source"); - Require(source.Equipment[0].Item!.Durability < 1, - "committed instant shot must apply one round of thermal wear to its owning equipment"); - AetheriaRuntimeVisibilitySimulation.BeginTick([source], 1, 0.5); - RequireNear(8 * Math.Exp(-0.5), weaponVisibility.Values.Single(), 0.000001, - "weapon visibility must use the fossil exponential source decay rather than vanish with the effect"); - var surface = AetheriaRuntimeDaemonGameSurfaceBuilder.Build( - new AetheriaRuntimeDaemonFrameDocument { FrameId = 12, Run = run }, - new AetheriaRuntimeDaemonHealthDocument(), - AetheriaRuntimeDaemonCommandBoundaryDocument.Create("daemon")); - var reactiveSurface = AetheriaRuntimeDaemonGameSurfaceBuilder.BuildReactiveGameplay( - new AetheriaRuntimeDaemonFrameDocument { FrameId = 12, Run = run }); - Require(!Flatten(surface.Surface.Root).Any(node => node.Kind == "combat.presentation"), - "the topology surface must not retain dynamic combat presentation authority"); - var combat = Flatten(reactiveSurface.Surface.Root).Single(node => node.Kind == "combat.presentation"); - var world = Flatten(surface.Surface.Root).Single(node => node.Kind == "world.scene3d"); - var aim = Flatten(surface.Surface.Root).Single(node => node.Kind == "aim.presentation"); - Require(!string.IsNullOrWhiteSpace(world.Props["lookCommand"]) && - world.Props["lookModel"] == "planar-yaw.v1" && - world.Props["lookSensitivityRadians"] == "-0.001", - "playable world must advertise the daemon-owned continuous look command and fossil pointer response"); - Require(world.Props["cameraRig"] == "perspective.entity-forward-follow.v1" && - world.Props["cameraLookAt"] == "aim.convergence-point.v1" && - world.Props["cameraDistance"] == "30" && - world.Props["cameraVerticalFieldOfViewDegrees"] == "60" && - world.Props["cameraTargetScreenX"] == "0.64" && - world.Props["cameraTargetScreenY"] == "0.19" && - world.Props["cameraPositionDamping"] == "0" && - world.Props["cameraNearClipPlane"] == "1" && - world.Props["cameraFarClipPlane"] == "4096", - "undocked world must translate the authored ARPG Third Person Rig into bottom-origin Eve viewport coordinates"); - Require(!world.Props.ContainsKey("skyboxAssetRef") && - world.Props["ambientLightColor"] == "0.5,0.75,1" && - world.Props["reflectionAssetRef"] == "texture.environment.reflection" && - world.Props["postProcessProfileAssetRef"] == "profile.environment.flight" && - world.Props["colorGradingSpace"] == "hdr-before-tonemap.v1" && - world.Props["exposureMode"] == "histogram.v1" && - world.Props["exposureLowPercent"] == "47.37294" && - world.Props["exposureHighPercent"] == "99" && - world.Props["exposureMinimumEv"] == "-3" && - world.Props["exposureMaximumEv"] == "0.3" && - world.Props["exposureKeyValue"] == "0.5" && - world.Props["exposureAdaptation"] == "progressive" && - world.Props["exposureSpeedUp"] == "2" && - world.Props["exposureSpeedDown"] == "1" && - world.Props["cameraReconstruction"] == "temporal-reprojection.v1" && - world.Props["temporalQuality"] == "high" && - world.Props["temporalHistoryBlend"] == "0.99" && - world.Props["temporalJitterScale"] == "0.1" && - world.Props["temporalSharpening"] == "0" && - world.Props["reflectionIntensity"] == "1" && - world.Props["ambientLightIntensity"] == "1.46" && - !world.Props.ContainsKey("keyLightDirection") && - !world.Props.ContainsKey("keyLightColor") && - !world.Props.ContainsKey("keyLightIntensity"), - "playable world must light from stellar tint, use the studio HDRI only for reflections, and leave the raymarch pass in charge of the frame"); - Require(aim.Props["controlledEntityIndex"] == "0" && - aim.Props["convergenceTargetEntityId"] == run.EntityRecordKey(0, 1) && - aim.Props["minimumConvergenceDistance"] == "50", - "Eve aim presentation must bind the controlled body direction to the fossil convergence semantic"); - Require(combat.Props["controlledEntityIndex"] == "0" && - combat.Props["selectedTargetEntityIndex"] == "1" && - combat.Props["targetVisible"] == "true" && - combat.Props["targetHostile"] == "true" && - double.Parse(combat.Props["lockProgress"], CultureInfo.InvariantCulture) > 0.99, - "Eve combat presentation must expose daemon-owned selection, contact, and completed lock without duplicating body transforms"); - var cockpitLock = Flatten(surface.Surface.Root).Single(node => - node.Id == "aetheria.daemon.game.cockpit.targetLock"); - Require(cockpitLock.Kind == "progress" && cockpitLock.Props["label"] == "TARGET LOCK" && - double.Parse(cockpitLock.Props["value"], CultureInfo.InvariantCulture) > 0.99, - "provider cockpit must project the same completed daemon lock through a generic Eve progress component"); - Require(combat.Props["hitMarkerDurationSeconds"] == "0.25" && - combat.Props["radialFillMinimum"] == "0.25" && - combat.Props["radialFillMaximum"] == "0.75", - "Eve combat presentation must preserve the fossil's presentation timing and radial meter semantics"); - - var secondTarget = Entity(2, 100, "raider"); - zone.Entities = [source, target, secondTarget]; - source.TargetEntityIndex = secondTarget.EntityIndex; - source.Contacts = - [ - new AetheriaRuntimeEntityContactCommit - { TargetEntityIndex = 1, InfoGathered = 1, Hostile = true, Visible = true }, - new AetheriaRuntimeEntityContactCommit - { TargetEntityIndex = 2, InfoGathered = 1, Hostile = true, Visible = true } - ]; - AetheriaRuntimeDaemonSimulation.Step(run, new AetheriaRuntimeDaemonIntentState(), 0.1, - AetheriaRuntimeDaemonSimulationSettings.AetheriaDefault, - NewPhysics(), catalog, 13, 1.3); - Require(state.LockTargetEntityIndex == 2 && state.LockProgress > 0 && state.LockProgress < 0.99 && - run.GameEvents.Count(value => value.Kind == "weapon.lock.lost" && - value.TargetEntityIndex == 1 && value.Reason == "target-changed") == 1 && - run.GameEvents.Count(value => value.Kind == "weapon.lock.started" && - value.TargetEntityIndex == 2) == 1 && - run.GameEvents.Count(value => value.Kind == "weapon.lock.acquired" && - value.TargetEntityIndex == 2) == 0, - $"idle authored lock behavior must tick every daemon step and transfer chronology exactly once on target change; " + - $"target={state.LockTargetEntityIndex} progress={state.LockProgress:0.###} events={string.Join('|', run.GameEvents.Where(value => value.Kind.StartsWith("weapon.lock.", StringComparison.Ordinal)).Select(value => $"{value.Kind}:{value.TargetEntityIndex}:{value.Reason}:{value.AuxiliaryValue:0.###}->{value.ScalarValue:0.###}"))}"); - - for (var frame = 14; frame <= 17; frame++) - AetheriaRuntimeDaemonSimulation.Step(run, new AetheriaRuntimeDaemonIntentState(), 0.1, - AetheriaRuntimeDaemonSimulationSettings.AetheriaDefault, - NewPhysics(), catalog, frame, frame * 0.1); - Require(state.LockProgress > 0.99 && - run.GameEvents.Count(value => value.Kind == "weapon.lock.started" && - value.TargetEntityIndex == 2) == 1 && - run.GameEvents.Count(value => value.Kind == "weapon.lock.acquired" && - value.TargetEntityIndex == 2) == 1, - "idle authored lock behavior must continue progressive acquisition without duplicating transition events"); - - source.LookDirectionX = -1; - source.LookDirectionY = 0; - AetheriaRuntimeDaemonSimulation.Step(run, new AetheriaRuntimeDaemonIntentState(), 0.1, - AetheriaRuntimeDaemonSimulationSettings.AetheriaDefault, - NewPhysics(), catalog, 18, 1.8); - AetheriaRuntimeDaemonSimulation.Step(run, new AetheriaRuntimeDaemonIntentState(), 0.1, - AetheriaRuntimeDaemonSimulationSettings.AetheriaDefault, - NewPhysics(), catalog, 19, 1.9); - Require(state.LockProgress < 0.99 && - run.GameEvents.Count(value => value.Kind == "weapon.lock.lost" && - value.TargetEntityIndex == 2 && value.Reason == "angle") == 1, - $"crossing the authored lock angle must publish one loss transition while continued decay remains silent; " + - $"hull={source.DirectionX},{source.DirectionY} look={source.LookDirectionX},{source.LookDirectionY} source={source.PositionX:0.###},{source.PositionZ:0.###} target={secondTarget.PositionX:0.###},{secondTarget.PositionZ:0.###} progress={state.LockProgress:0.###} events={string.Join('|', run.GameEvents.Where(value => value.Kind.StartsWith("weapon.lock.", StringComparison.Ordinal)).Select(value => $"{value.Kind}:{value.TargetEntityIndex}:{value.Reason}:{value.AuxiliaryValue:0.###}->{value.ScalarValue:0.###}"))}"); - - source.LookDirectionX = 1; - source.LookDirectionY = 0; - for (var frame = 20; frame <= 21; frame++) - AetheriaRuntimeDaemonSimulation.Step(run, new AetheriaRuntimeDaemonIntentState(), 0.1, - AetheriaRuntimeDaemonSimulationSettings.AetheriaDefault, - NewPhysics(), catalog, frame, frame * 0.1); - Require(state.LockProgress > 0.99 && - run.GameEvents.Count(value => value.Kind == "weapon.lock.acquired" && - value.TargetEntityIndex == 2) == 2, - "restored facing must reacquire through a second completed transition without replaying lock start"); - - zone.Entities = [source, target]; - AetheriaRuntimeDaemonSimulation.Step(run, new AetheriaRuntimeDaemonIntentState(), 0.1, - AetheriaRuntimeDaemonSimulationSettings.AetheriaDefault, - NewPhysics(), catalog, 22, 2.2); - Require(state.LockTargetEntityIndex == -1 && state.LockProgress == 0 && - run.ShotReceipts.Count == 1 && - run.GameEvents.Count(value => value.Kind == "weapon.lock.lost" && - value.TargetEntityIndex == 2 && value.Reason == "target-invalid") == 1, - $"an invalid target must clear an idle weapon's authoritative lock and publish one loss transition; " + - $"target={state.LockTargetEntityIndex} progress={state.LockProgress:0.###} shots={run.ShotReceipts.Count} events={string.Join('|', run.GameEvents.Where(value => value.Kind.StartsWith("weapon.lock.", StringComparison.Ordinal)).Select(value => $"{value.Kind}:{value.TargetEntityIndex}:{value.Reason}:{value.AuxiliaryValue:0.###}->{value.ScalarValue:0.###}"))}"); - var resetSurface = AetheriaRuntimeDaemonGameSurfaceBuilder.BuildReactiveGameplay( - new AetheriaRuntimeDaemonFrameDocument { FrameId = 22, Run = run }); - var resetCombat = Flatten(resetSurface.Surface.Root).Single(node => node.Kind == "combat.presentation"); - Require(resetSurface.Version == 22 && - resetCombat.Props["selectedTargetEntityId"] == "" && - resetCombat.Props["selectedTargetEntityIndex"] == "-1" && - resetCombat.Props["targetVisible"] == "false", - "reactive combat presentation must advance with the daemon frame and clear an invalid target"); - Require(Flatten(resetSurface.Surface.Root).Any(node => node.Kind == "feedback.event" && - node.Props["eventKind"] == "weapon.lock.lost" && - node.Props["targetEntityIndex"] == "2" && - node.Props["reason"] == "target-invalid" && - double.Parse(node.Props["auxiliaryValue"], CultureInfo.InvariantCulture) > 0.99), - "Eve feedback must project the daemon lock-loss transition and its prior completed progress"); - } - - private static void AutoWeaponRetriggersWhileHeldAndStopsOnRelease() - { - var root = Path.Combine(Path.GetTempPath(), "aetheria-auto-weapon-" + Guid.NewGuid().ToString("N")); - Directory.CreateDirectory(root); - var statePath = Path.Combine(root, "world.cc"); - try - { - var source = Entity(0, 0, "player"); - source.DirectionX = 1; - source.LookDirectionX = 1; - source.TargetEntityIndex = 1; - source.Contacts = [new AetheriaRuntimeEntityContactCommit - { - TargetEntityIndex = 1, InfoGathered = 1, Hostile = true, Visible = true - }]; - source.WeaponGroups = [new[] { 0 }]; - source.Equipment = [new AetheriaRuntimeLoadoutItemSlotCommit - { - Item = new AetheriaRuntimeLoadoutItemCommit - { ItemKey = "test-auto", Quality = 1, Durability = 1, Enabled = true } - }]; - var target = Entity(1, 50, "raider"); - var run = new AetheriaRuntimeRunCheckpointCommit - { - RunId = "auto-weapon-smoke", - CurrentZoneIndex = 0, - Zones = [new AetheriaRuntimeZoneSnapshotCommit { ZoneIndex = 0, Entities = [source, target] }] - }; - run.CurrentEntityKey = run.EntityRecordKey(0, source.EntityIndex); - var payload = new AetheriaRuntimeBehaviorPayload(0, AetheriaRuntimeBehaviorKinds.AutoWeapon, 0, - [ - new AetheriaRuntimeBehaviorField(2, PerformanceStat(10)), - new AetheriaRuntimeBehaviorField(6, PerformanceStat(100)), - new AetheriaRuntimeBehaviorField(15, PerformanceStat(0)), - new AetheriaRuntimeBehaviorField(16, PerformanceStat(100)), - new AetheriaRuntimeBehaviorField(17, PerformanceStat(1)), - new AetheriaRuntimeBehaviorField(18, PerformanceStat(0)), - new AetheriaRuntimeBehaviorField(19, PerformanceStat(0.2)), - new AetheriaRuntimeBehaviorField(21, PerformanceStat(100)), - new AetheriaRuntimeBehaviorField(23, PerformanceStat(180)), - new AetheriaRuntimeBehaviorField(24, PerformanceStat(0)), - new AetheriaRuntimeBehaviorField(25, PerformanceStat(0)) - ]); - var catalog = new AetheriaRuntimeCatalogSnapshot( - [WearableWeaponCatalogItem("test-auto", payload)], [], []); - var press = AetheriaRuntimeDaemonCommandDocument.Create( - AetheriaRuntimeDaemonCommandKinds.SetWeaponGroupActive, - "pilot", run.RunId, 0, run.CurrentEntityKey); - press.CommandId = "auto-weapon-press"; - press.WeaponGroup = 0; - press.ScalarValue = 1; - var pressed = AetheriaRuntimeDaemonOperations.Execute( - run, [press], new AetheriaRuntimeDaemonOperationContext()); - - AetheriaRuntimeDaemonSimulation.Step(run, pressed.Intents, 0.1, - AetheriaRuntimeDaemonSimulationSettings.AetheriaDefault, - NewPhysics(), catalog, 0, 0); - RequireEqual(1, run.ShotReceipts.Count, - "pressing an AutoWeapon group must commit its first burst exactly once"); - var state = source.WeaponStates.Single(value => - value.BehaviorKind == AetheriaRuntimeBehaviorKinds.AutoWeapon); - Require(state.CoolingDown && source.ActiveWeaponGroups.Single(), - "AutoWeapon cooldown and the held group latch must be durable daemon state"); - - using (var node = AetheriaStateNode.OpenAsync(statePath).GetAwaiter().GetResult()) - { - node.MutableDocument( - AetheriaRuntimeVerseRecordKeys.DaemonFrameLatest) - .ReplaceAsync(AetheriaRuntimeDaemonFrameDocument.Create( - run, "smoke", "auto-weapon", 0, 0, 0.1)) - .GetAwaiter().GetResult(); - node.FlushAsync(soft: false).GetAwaiter().GetResult(); - } - - AetheriaRuntimeRunCheckpointCommit restoredRun; - using (var node = AetheriaStateNode.OpenAsync(statePath).GetAwaiter().GetResult()) - { - restoredRun = node.MutableDocument( - AetheriaRuntimeVerseRecordKeys.DaemonFrameLatest) - .ReadAsync().GetAwaiter().GetResult()?.Run - ?? throw new InvalidOperationException("Durable AutoWeapon frame is missing."); - } - var restoredSource = restoredRun.Zones.Single().Entities.Single(value => value.EntityIndex == 0); - for (var frame = 1; frame <= 3; frame++) - AetheriaRuntimeDaemonSimulation.Step(restoredRun, new AetheriaRuntimeDaemonIntentState(), 0.1, - AetheriaRuntimeDaemonSimulationSettings.AetheriaDefault, - NewPhysics(), catalog, frame, frame * 0.1); - RequireEqual(2, restoredRun.ShotReceipts.Count, - "a held AutoWeapon must retrigger on the fixed step after its cooldown completes, including after restart"); - - var release = AetheriaRuntimeDaemonCommandDocument.Create( - AetheriaRuntimeDaemonCommandKinds.SetWeaponGroupActive, - "pilot", restoredRun.RunId, 4, restoredRun.CurrentEntityKey); - release.CommandId = "auto-weapon-release"; - release.WeaponGroup = 0; - release.ScalarValue = 0; - var released = AetheriaRuntimeDaemonOperations.Execute( - restoredRun, [release], new AetheriaRuntimeDaemonOperationContext()); - for (var frame = 4; frame < 10; frame++) - AetheriaRuntimeDaemonSimulation.Step(restoredRun, - frame == 4 ? released.Intents : new AetheriaRuntimeDaemonIntentState(), 0.1, - AetheriaRuntimeDaemonSimulationSettings.AetheriaDefault, - NewPhysics(), catalog, frame, frame * 0.1); - RequireEqual(2, restoredRun.ShotReceipts.Count, - "releasing an AutoWeapon group must prevent every later automatic burst"); - Require(!restoredSource.ActiveWeaponGroups.Single() && - !restoredSource.WeaponStates.Single(value => - value.BehaviorKind == AetheriaRuntimeBehaviorKinds.AutoWeapon).TriggerPending, - "release must clear the only daemon input that authorizes AutoWeapon retriggering"); - - var ordinaryPayload = new AetheriaRuntimeBehaviorPayload( - payload.UnionKey, - AetheriaRuntimeBehaviorKinds.InstantWeapon, - payload.Group, - payload.Fields); - var ordinary = Entity(0, 0, "player"); - ordinary.DirectionX = 1; - ordinary.LookDirectionX = 1; - ordinary.TargetEntityIndex = 1; - ordinary.Contacts = source.Contacts; - ordinary.WeaponGroups = [new[] { 0 }]; - ordinary.ActiveWeaponGroups = [true]; - ordinary.Equipment = [new AetheriaRuntimeLoadoutItemSlotCommit - { - Item = new AetheriaRuntimeLoadoutItemCommit - { ItemKey = "test-instant-held", Quality = 1, Durability = 1, Enabled = true } - }]; - var ordinaryTarget = Entity(1, 50, "raider"); - var ordinaryRun = new AetheriaRuntimeRunCheckpointCommit - { - RunId = "ordinary-held-smoke", CurrentZoneIndex = 0, - CurrentEntityKey = "zone.0.entity.0", - Zones = [new AetheriaRuntimeZoneSnapshotCommit { ZoneIndex = 0, Entities = [ordinary, ordinaryTarget] }] - }; - var ordinaryCatalog = new AetheriaRuntimeCatalogSnapshot( - [WearableWeaponCatalogItem("test-instant-held", ordinaryPayload)], [], []); - var pulse = new AetheriaRuntimeDaemonIntentState(); - pulse.WeaponGroups.Add(new AetheriaRuntimeDaemonWeaponGroupIntent - { - ActorEntityKey = ordinaryRun.CurrentEntityKey, - WeaponGroup = 0, - Fire = true, - Active = true - }); - for (var frame = 0; frame < 10; frame++) - AetheriaRuntimeDaemonSimulation.Step(ordinaryRun, - frame == 0 ? pulse : new AetheriaRuntimeDaemonIntentState(), 0.1, - AetheriaRuntimeDaemonSimulationSettings.AetheriaDefault, - NewPhysics(), ordinaryCatalog, frame, frame * 0.1); - RequireEqual(1, ordinaryRun.ShotReceipts.Count, - "an ordinary InstantWeapon must remain press-edge driven even while its group latch is held"); - } - finally - { - if (Directory.Exists(root)) - Directory.Delete(root, recursive: true); - } - } - - private static void GuidedWeaponPublishesPresentationProfileWithoutPhysicalAuthority() - { - var source = Entity(0, 0, "player"); - source.DirectionX = 1; - source.LookDirectionX = 1; - source.TargetEntityIndex = 1; - source.Contacts = [new AetheriaRuntimeEntityContactCommit - { - TargetEntityIndex = 1, InfoGathered = 1, Hostile = true, Visible = true - }]; - source.WeaponGroups = [new[] { 0 }]; - source.Equipment = [new AetheriaRuntimeLoadoutItemSlotCommit - { - Item = new AetheriaRuntimeLoadoutItemCommit - { ItemKey = "test-launcher", Quality = 1, Durability = 1, Enabled = true } - }]; - var target = Entity(1, 80, "raider"); - var zone = new AetheriaRuntimeZoneSnapshotCommit { ZoneIndex = 0, Entities = [source, target] }; - var run = new AetheriaRuntimeRunCheckpointCommit - { - RunId = "guided-presentation-smoke", CurrentZoneIndex = 0, - CurrentEntityKey = "zone.0.entity.0", Zones = [zone] - }; - var guidance = Curve( - new AetheriaRuntimeCurveKey(0, 0.25, 0, 0), - new AetheriaRuntimeCurveKey(1, 1, 0, 0)); - var thrust = Curve( - new AetheriaRuntimeCurveKey(0, 1, 0, 0), - new AetheriaRuntimeCurveKey(1, 0.5, 0, 0)); - var lift = Curve( - new AetheriaRuntimeCurveKey(0, 0, 0, 0), - new AetheriaRuntimeCurveKey(1, 0.75, 0, 0)); - var payload = new AetheriaRuntimeBehaviorPayload(0, AetheriaRuntimeBehaviorKinds.Launcher, 0, - [ - new AetheriaRuntimeBehaviorField(2, PerformanceStat(10)), - new AetheriaRuntimeBehaviorField(6, PerformanceStat(150)), - new AetheriaRuntimeBehaviorField(16, PerformanceStat(20)), - new AetheriaRuntimeBehaviorField(17, PerformanceStat(1)), - new AetheriaRuntimeBehaviorField(19, PerformanceStat(0.1)), - new AetheriaRuntimeBehaviorField(21, PerformanceStat(100)), - new AetheriaRuntimeBehaviorField(22, PerformanceStat(0)), - new AetheriaRuntimeBehaviorField(23, PerformanceStat(180)), - new AetheriaRuntimeBehaviorField(24, PerformanceStat(1)), - new AetheriaRuntimeBehaviorField(25, PerformanceStat(1)), - new AetheriaRuntimeBehaviorField(26, guidance), - new AetheriaRuntimeBehaviorField(27, thrust), - new AetheriaRuntimeBehaviorField(28, lift), - new AetheriaRuntimeBehaviorField(29, PerformanceStat(5)), - new AetheriaRuntimeBehaviorField(30, Number(2)), - new AetheriaRuntimeBehaviorField(31, PerformanceStat(40)) - ]); - var catalog = new AetheriaRuntimeCatalogSnapshot([CatalogItem("test-launcher", payload)], [], []); - var fire = new AetheriaRuntimeDaemonIntentState(); - fire.WeaponGroups.Add(new AetheriaRuntimeDaemonWeaponGroupIntent - { ActorEntityKey = run.CurrentEntityKey, WeaponGroup = 0, Fire = true, Active = true }); - - for (var frame = 0; frame < 4 && run.ShotReceipts.Count == 0; frame++) - AetheriaRuntimeDaemonSimulation.Step(run, - frame == 0 ? fire : new AetheriaRuntimeDaemonIntentState(), 0.1, - AetheriaRuntimeDaemonSimulationSettings.AetheriaDefault, - NewPhysics(), catalog, frame, frame * 0.1); - - RequireEqual(0, zone.PhysicalPayloads.Count, - "fossil guided presentation must not be promoted into a damage-owning Ymir body"); - var receipt = run.ShotReceipts.Single(value => value.WeaponItemKey == "test-launcher"); - RequireEqual("guided", receipt.PresentationKind, - "Launcher receipts must select the generic guided presentation path"); - RequireEqual("target-entity", receipt.GuidanceMode, - "Launcher guidance must retain its fossil target-entity mode"); - RequireEqual(2, receipt.GuidanceCurve.Count, - "guided receipt must retain the complete authored guidance curve"); - RequireNear(5, receipt.GuidanceThrust, 0.000001, - "guided receipt must retain evaluated authored thrust"); - RequireNear(40, receipt.GuidanceTopSpeed, 0.000001, - "guided receipt must retain evaluated authored missile velocity"); - RequireNear(2, receipt.GuidanceDodgeFrequency, 0.000001, - "guided receipt must retain authored dodge frequency"); - var restored = MessagePack.MessagePackSerializer.Deserialize( - MessagePack.MessagePackSerializer.Serialize(run)); - RequireEqual("target-entity", restored.ShotReceipts.Single().GuidanceMode, - "guided presentation levers must survive daemon checkpoint restart"); - var surface = AetheriaRuntimeDaemonGameSurfaceBuilder.BuildReactiveGameplay( - new AetheriaRuntimeDaemonFrameDocument { FrameId = 4, Run = run }); - var shot = Flatten(surface.Surface.Root).Single(node => - node.Kind == "shot.receipt" && node.Props.TryGetValue("itemKey", out var itemKey) && - itemKey == "test-launcher"); - Require(shot.Props["guidanceMode"] == "target-entity" && - shot.Props["guidanceCurve"] == "0,0.25,0,0;1,1,0,0" && - shot.Props["guidanceThrustCurve"] == "0,1,0,0;1,0.5,0,0" && - shot.Props["guidanceLiftCurve"] == "0,0,0,0;1,0.75,0,0", - "generic Eve shot facts must expose the authored guidance curves without provider internals"); - } - - private static void DeployableWeaponRunsThroughYmirAndDetonatesOnDaemon() - { - var source = Entity(0, 0, "player"); - source.DirectionX = 1; - source.WeaponGroups = [new[] { 0 }]; - source.Equipment = [new AetheriaRuntimeLoadoutItemSlotCommit - { - Item = new AetheriaRuntimeLoadoutItemCommit - { ItemKey = "test-mine-layer", Quality = 0, Durability = 1, Enabled = true } - }]; - var target = Entity(1, 50, "neutral"); - var zone = new AetheriaRuntimeZoneSnapshotCommit { ZoneIndex = 0, Entities = [source, target] }; - var run = new AetheriaRuntimeRunCheckpointCommit - { - RunId = "deployable-smoke", CurrentZoneIndex = 0, - CurrentEntityKey = "zone.0.entity.0", Zones = [zone] - }; - var payload = new AetheriaRuntimeBehaviorPayload(0, AetheriaRuntimeBehaviorKinds.DeployableWeapon, 0, - [ - new AetheriaRuntimeBehaviorField(2, PerformanceStat(35)), - new AetheriaRuntimeBehaviorField(6, PerformanceStat(100)), - new AetheriaRuntimeBehaviorField(11, PerformanceStat(4)), - new AetheriaRuntimeBehaviorField(16, PerformanceStat(10)), - new AetheriaRuntimeBehaviorField(17, PerformanceStat(1)), - new AetheriaRuntimeBehaviorField(19, PerformanceStat(1)), - new AetheriaRuntimeBehaviorField(26, Number(0.2)), - new AetheriaRuntimeBehaviorField(27, Number(5)), - new AetheriaRuntimeBehaviorField(28, PerformanceStat(30)), - new AetheriaRuntimeBehaviorField(29, Number(0.2)), - new AetheriaRuntimeBehaviorField(30, PerformanceStat(50)) - ]); - var catalog = new AetheriaRuntimeCatalogSnapshot( - [WearableWeaponCatalogItem("test-mine-layer", payload)], [], []); - var fire = new AetheriaRuntimeDaemonIntentState(); - fire.WeaponGroups.Add(new AetheriaRuntimeDaemonWeaponGroupIntent - { ActorEntityKey = "zone.0.entity.0", WeaponGroup = 0, Fire = true, Active = true }); - - AetheriaRuntimeDaemonSimulation.Step(run, fire, 0.1, - AetheriaRuntimeDaemonSimulationSettings.AetheriaDefault, - NewPhysics(), catalog, 1, 0.1); - RequireEqual(1, zone.PhysicalPayloads.Count, "deployable fire must create one persistent physical payload"); - Require(!run.ShotReceipts.Any(), "deployable fire must not masquerade as an ordinary shot receipt"); - RequireNear(4, source.StatGrids.Single(grid => - grid.Name == "transient-visibility:weapon:equipment:0:0").Values.Single(), 0.000001, - "successful deployment must publish the launcher's authored weapon visibility"); - Require(source.Equipment[0].Item!.Durability < 1, - "successful deployment must apply one round of thermal wear to its launcher"); - var mine = zone.PhysicalPayloads.Single(); - Require(mine.PositionX > 2 && mine.TriggeredAtSeconds < 0, - "Ymir must advance the unarmed mine without inventing a trigger"); - - AetheriaRuntimeDaemonSimulation.Step(run, new AetheriaRuntimeDaemonIntentState(), 0.1, - AetheriaRuntimeDaemonSimulationSettings.AetheriaDefault, - NewPhysics(), catalog, 2, 0.2); - mine = zone.PhysicalPayloads.Single(); - Require(mine.Stationary && mine.TriggeredAtSeconds >= 0 && - run.GameEvents.Count(value => value.Kind == "deployable.triggered") == 1, - "an armed Ymir contact must become one daemon-owned trigger transition"); - RequireNear(100, Stat(target, "hull"), 0.000001, - "trigger contact must not bypass the authored detonation delay"); - - using var soaPublisher = new AetheriaRuntimeDaemonSoaFramePublisher(producerEpoch: 1); - using var soaFrame = soaPublisher.BuildCurrentZoneEntities( - new AetheriaRuntimeDaemonFrameDocument { FrameId = 2, Run = run }); - var mineIdentity = soaFrame.View.Identities.Single(identity => - identity.EntityId == $"{run.RunId}:zone:0:physical-payload:{mine.PayloadId}"); - Require(mineIdentity.Kind == "physical-payload" && mineIdentity.AssetRef == "prefab.entity.mine" && - !mineIdentity.Selectable && mineIdentity.EntityIndex < -1, - "the playable SoA must carry the retained Ymir mine through its provider-owned asset identity"); - var activeBodyExtent = soaFrame.View.Buffers[0].ByteLength; - var activeColumns = soaFrame.View.Columns.ToDictionary( - column => column.ColumnId, - column => (column.ByteOffset, column.ElementStride), - StringComparer.Ordinal); - soaFrame.Dispose(); - - var objects = AetheriaRuntimeGameDocuments.ObjectsViewport( - new AetheriaRuntimeDaemonFrameDocument { FrameId = 2, Run = run }, - new AetheriaRuntimeViewportBounds { MinX = -1000, MinY = -1000, MaxX = 1000, MaxY = 1000 }); - var mineObject = objects.Objects.Single(value => value.EntityKey == mine.PayloadId); - RequireEqual("projectile", mineObject.Kind, - "the viewport projection must expose the retained physical payload without reviving world.scene3d as a transform authority"); - Require(mineObject.IsActive && mineObject.IconAsset != null, - "the viewport projection must expose an active provider-authored presentation for the retained mine body"); - Require(mine.Stationary && mine.TriggeredAtSeconds >= 0, - "daemon state must retain Ymir-derived motion and trigger state independently of presentation"); - RequireNear(35, mine.PayloadMagnitude, 0.000001, "deployable must retain authored payload magnitude"); - RequireNear(50, mine.BlastRadius, 0.000001, "deployable must retain authored blast radius"); - Require(Math.Abs(target.PositionX - mine.PositionX) <= mine.BlastRadius, - $"triggered target must remain inside authored blast radius (mine {mine.PositionX}, target {target.PositionX})"); - - AetheriaRuntimeDaemonSimulation.Step(run, new AetheriaRuntimeDaemonIntentState(), 0.21, - AetheriaRuntimeDaemonSimulationSettings.AetheriaDefault, - NewPhysics(), catalog, 3, 0.41); - RequireEqual(0, zone.PhysicalPayloads.Count, "detonated mine must leave canonical payload state"); - RequireNear(65, Stat(target, "hull"), 0.000001, - "Aetheria must apply authored blast damage after the delay"); - Require(run.GameEvents.Count(value => value.Kind == "deployable.detonated") == 1, - "daemon must publish one authoritative detonation event"); - - using var settledSoaFrame = soaPublisher.BuildCurrentZoneEntities( - new AetheriaRuntimeDaemonFrameDocument { FrameId = 3, Run = run }); - Require(activeBodyExtent == settledSoaFrame.View.Buffers[0].ByteLength, - "one SoA layout version must retain a fixed body extent when active entity count changes"); - foreach (var column in settledSoaFrame.View.Columns) - { - var activeColumn = activeColumns[column.ColumnId]; - Require(activeColumn.ByteOffset == column.ByteOffset && - activeColumn.ElementStride == column.ElementStride, - $"SoA column '{column.ColumnId}' must retain its byte interpretation across active row changes"); - } - } - - private static void DeployableRangeExpiryDetonatesAfterYmirMovement() - { - var source = Entity(0, -50, "player"); - var target = Entity(1, 35, "neutral"); - var zone = new AetheriaRuntimeZoneSnapshotCommit - { - ZoneIndex = 0, - Entities = [source, target], - PhysicalPayloads = [new AetheriaRuntimePhysicalPayloadCommit - { - PayloadId = "range-expiry-mine", - PayloadKind = "mine", - WeaponItemKey = "canonical-mine", - SourceEntityIndex = source.EntityIndex, - PositionX = 5, - VelocityX = 20, - Radius = 1, - ActivationDelaySeconds = 10, - LifetimeSeconds = 30, - MaximumSourceDistance = 6, - BlastRadius = 50, - PayloadMagnitude = 15, - TriggeredAtSeconds = -1, - Active = true - }] - }; - var run = new AetheriaRuntimeRunCheckpointCommit - { - RunId = "range-expiry-smoke", CurrentZoneIndex = 0, - CurrentEntityKey = "zone.0.entity.0", Zones = [zone] - }; - - AetheriaRuntimeDaemonSimulation.Step(run, new AetheriaRuntimeDaemonIntentState(), 0.1, - AetheriaRuntimeDaemonSimulationSettings.AetheriaDefault, - NewPhysics(), frameId: 4, - simulationTimeSeconds: 0.1); - - RequireEqual(0, zone.PhysicalPayloads.Count, - "mine crossing source-relative range after Ymir movement must detonate in the same tick"); - RequireNear(85, Stat(target, "hull"), 0.000001, - "range expiry must preserve the fossil splash consequence"); - Require(run.GameEvents.Any(value => value.Kind == "deployable.expired" && - value.Reason == "range" && value.ItemKey == "canonical-mine"), - "daemon must explain range-triggered expiry with stable item identity"); - Require(run.GameEvents.Any(value => value.Kind == "deployable.detonated" && - value.Reason == "range" && value.ItemKey == "canonical-mine"), - "range expiry must flow through the shared detonation transaction"); - } - - private static void CanonicalCatalogPublishesRecoveredMineLauncher() - { - var catalog = AetheriaRuntimeCatalogStore.OpenReadOnly( - Path.Combine(Directory.GetCurrentDirectory(), "GameData", "aetheria-world.cc")); - var drives = catalog.Items - .Where(item => item.BehaviorKinds.Contains(AetheriaRuntimeBehaviorKinds.AetherDrive)) - .ToArray(); - var driveHulls = catalog.Items - .Where(item => item.Hardpoints.Any(point => point.Type == "AetherDrive")) - .ToArray(); - Require(drives.Length == 1 && drives[0].Name == "Traction" && - drives[0].HardpointType == "AetherDrive" && drives[0].Price == 250000 && - drives[0].Tags.Contains("rarity:rare", StringComparer.Ordinal), - "canonical AetherDrive must remain the singular expensive Traction retrofit"); - Require(driveHulls.Length == 1 && driveHulls[0].Name == "LonginusX" && - driveHulls[0].Price == 7500000 && - driveHulls[0].Tags.Contains("rarity:rare", StringComparer.Ordinal), - "canonical AetherDrive compatibility must remain confined to the rare modified LonginusX hull"); - var ordinaryShipHulls = catalog.Items.Where(item => item.HullType == "Ship" && - item.Hardpoints.All(point => point.Type != "AetherDrive") && - !item.Tags.Contains("rarity:rare", StringComparer.Ordinal)) - .ToArray(); - Require(ordinaryShipHulls.Any(item => item.Name == "Longinus") && - ordinaryShipHulls.Any(item => item.Name == "Djinni"), - "the recovered common Longinus and Djinni must own ordinary ship generation"); - var mine = catalog.Items.Single(item => - item.ItemKey == "aetheria.item_definition:legacy:e78d3670-3ac6-4d9b-834c-1da4228ac311"); - RequireEqual("Mine Launcher", mine.Name, "recovered item must retain fossil identity"); - RequireEqual("Mine", mine.WeaponType, "recovered item must publish normalized semantic weapon type"); - var behavior = mine.BehaviorPayloads.Single(value => - value.Kind == AetheriaRuntimeBehaviorKinds.DeployableWeapon); - RequireNear(2, behavior.Fields.Single(field => field.Key == 26).Value.NumberValue, 0.000001, - "catalog mine must retain two-second activation delay"); - RequireNear(30, behavior.Fields.Single(field => field.Key == 27).Value.NumberValue, 0.000001, - "catalog mine must retain thirty-second lifetime"); - var manifest = AetheriaRuntimeAssets.ProjectManifest(catalog); - Require(!manifest.Assets.Any(value => value.Ref.AssetKey == "material.environment.skybox"), - "provider catalog must not retain the obsolete skybox authority when raymarched fog owns the frame"); - var reflection = manifest.Assets.Single(value => value.Ref.AssetKey == "texture.environment.reflection"); - RequireEqual(AetheriaRuntimeAssetKinds.Texture, reflection.Ref.Kind, - "provider must type the pre-generated environment map as a texture asset"); - RequireEqual("environment.reflection", reflection.Ref.Metadata["presentationRole"], - "provider must advertise the generic reflection presentation role"); - RequireEqual("Assets/Textures/studio2.hdr", reflection.Ref.Metadata["unityAssetPath"], - "provider bundle must own the pre-generated reflection map without an importer plugin"); - var asset = manifest.Assets.Single(value => value.Ref.AssetKey == "prefab.entity.mine"); - RequireEqual("physical-payload.mine", asset.Ref.Metadata["presentationRole"], - "provider must advertise the script-free mine presentation role"); - RequireEqual("0.25", asset.Ref.Metadata["triggeredPulseSeconds"], - "mine presentation must retain the fossil triggered pulse cadence"); - - var rareHull = driveHulls.Single(); - var rareHullAssetKey = AetheriaRuntimeAssets.HullPrefabAssetKey(rareHull.ItemKey); - var rareHullAsset = manifest.Assets.Single(value => value.Ref.AssetKey == rareHullAssetKey); - var expectedHullResourcePath = rareHull.HullPrefab.Replace('\\', '/'); - const string resourcesPrefix = "Assets/Resources/"; - if (expectedHullResourcePath.StartsWith(resourcesPrefix, StringComparison.OrdinalIgnoreCase)) - expectedHullResourcePath = expectedHullResourcePath[resourcesPrefix.Length..]; - if (expectedHullResourcePath.EndsWith(".prefab", StringComparison.OrdinalIgnoreCase)) - expectedHullResourcePath = expectedHullResourcePath[..^".prefab".Length]; - RequireEqual("entity.hull", rareHullAsset.Ref.Metadata["presentationRole"], - "provider manifest must type catalog hull prefabs as generic hull presentation assets"); - RequireEqual(expectedHullResourcePath, rareHullAsset.Ref.Metadata["resourcesPath"], - "provider hull asset must retain the catalog-authored prefab instead of a faction fallback"); - - var rareShip = Entity(0, 0, "player"); - rareShip.HullItemKey = rareHull.ItemKey; - var rareRun = new AetheriaRuntimeRunCheckpointCommit - { - RunId = "rare-hull-presentation-smoke", - CurrentZoneIndex = 0, - CurrentEntityKey = "zone.0.entity.0", - Zones = [new AetheriaRuntimeZoneSnapshotCommit { ZoneIndex = 0, Entities = [rareShip] }] - }; - var rareFrame = new AetheriaRuntimeDaemonFrameDocument { FrameId = 1, Run = rareRun }; - var rareSurface = AetheriaRuntimeDaemonGameSurfaceBuilder.Build( - rareFrame, - new AetheriaRuntimeDaemonHealthDocument(), - AetheriaRuntimeDaemonCommandBoundaryDocument.Create("daemon"), - catalog: catalog); - var rareSurfaceEntity = Flatten(rareSurface.Surface.Root).Single(node => - node.Id == "aetheria.daemon.game.world.entity.0"); - RequireEqual(rareHullAssetKey, rareSurfaceEntity.Props["assetRef"], - "Eve scene projection must select the typed hull asset rather than the player Djinni fallback"); - using var rareSoaPublisher = new AetheriaRuntimeDaemonSoaFramePublisher(producerEpoch: 1); - var rareSoa = rareSoaPublisher.BuildCurrentZoneEntities(rareFrame, catalog); - RequireEqual(rareHullAssetKey, rareSoa.View.Identities.Single().AssetRef, - "SoA identity and Eve scene projection must share the catalog-owned hull asset key"); - } - - private static void EnergyFundedShieldInterceptsDamageBeforeHull() - { - var source = Entity(0, -50, "player"); - var target = Entity(1, 0, "neutral"); - target.Equipment = - [ - new AetheriaRuntimeLoadoutItemSlotCommit { Item = new AetheriaRuntimeLoadoutItemCommit - { ItemKey = "test-energy-shield", Quality = 1, Durability = 1, Enabled = true } }, - new AetheriaRuntimeLoadoutItemSlotCommit { Item = new AetheriaRuntimeLoadoutItemCommit - { ItemKey = "test-shield-capacitor", Quality = 1, Durability = 1, Enabled = true } } - ]; - target.BehaviorStates = [new AetheriaRuntimeBehaviorStateCommit - { - OwnerKind = AetheriaRuntimeBehaviorStateProjector.EquipmentOwnerKind, OwnerIndex = 1, BehaviorIndex = 0, - BehaviorKind = "Capacitor", CapacitorCharge = 100, CapacitorCapacity = 100, - CapacitorEfficiency = 1 - }]; - var shield = new AetheriaRuntimeBehaviorPayload(0, "Shield", 0, - [ - new AetheriaRuntimeBehaviorField(1, PerformanceStat(2)), - new AetheriaRuntimeBehaviorField(2, PerformanceStat(3)) - ]); - var catalog = new AetheriaRuntimeCatalogSnapshot( - [CatalogItem("test-energy-shield", shield), CatalogItem("test-shield-capacitor", CapacitorPayload(100, 1))], [], []); - var zone = new AetheriaRuntimeZoneSnapshotCommit { ZoneIndex = 0, Entities = [source, target] }; - var run = new AetheriaRuntimeRunCheckpointCommit - { - RunId = "energy-shield-smoke", CurrentZoneIndex = 0, - CurrentEntityKey = "zone.0.entity.0", Zones = [zone] - }; - - void AddDetonatingPayload(string id, double damage) - { - zone.PhysicalPayloads = [new AetheriaRuntimePhysicalPayloadCommit - { - PayloadId = id, PayloadKind = "mine", WeaponItemKey = "test-blast", - SourceEntityIndex = source.EntityIndex, PositionX = target.PositionX, - LifetimeSeconds = 30, BlastRadius = 10, PayloadMagnitude = damage, - TriggeredAtSeconds = 0, DetonationDelaySeconds = 0, Stationary = true, Active = true - }]; - } - - AddDetonatingPayload("shielded-blast", 20); - AetheriaRuntimeDaemonSimulation.Step(run, new AetheriaRuntimeDaemonIntentState(), 0.1, - AetheriaRuntimeDaemonSimulationSettings.AetheriaDefault, - NewPhysics(), catalog, 1, 0.1); - var capacitor = target.BehaviorStates.Single(value => value.BehaviorKind == "Capacitor"); - RequireNear(100, Stat(target, "hull"), 0.000001, - "funded shield must intercept damage before hull"); - RequireNear(40, capacitor.CapacitorCharge, 0.000001, - "shield must consume damage multiplied by authored energy usage"); - RequireNear(40.0 / 3.0, Stat(target, "shield"), 0.000001, - "legacy shield meter must be derived from remaining funded absorption, not own hit points"); - Require(run.GameEvents.Any(value => value.Kind == "shield.absorbed" && - Math.Abs(value.ScalarValue - 20) < 0.000001 && - Math.Abs(value.AuxiliaryValue - 10) < 0.000001), - "shield outcome must publish absorbed damage and authored heat contribution"); - var surface = AetheriaRuntimeDaemonGameSurfaceBuilder.BuildReactiveGameplay( - new AetheriaRuntimeDaemonFrameDocument { FrameId = 1, Run = run }); - Require(Flatten(surface.Surface.Root).Any(node => node.Kind == "feedback.event" && - node.Props["eventKind"] == "shield.absorbed" && - node.Props["scalarValue"] == "20" && node.Props["auxiliaryValue"] == "10"), - "Eve feedback must expose exact shield absorption and heat facts"); - var shieldAsset = AetheriaRuntimeAssets.ProjectManifest(catalog).Assets.Single(value => - value.Ref.Metadata.TryGetValue("presentationRole", out var role) && role == "effect.impact.shield"); - RequireEqual("prefab.effect.impact.shield", shieldAsset.Ref.AssetKey, - "provider must advertise the authored shield visual by semantic impact role"); - - capacitor.CapacitorCharge = 0; - AddDetonatingPayload("unfunded-blast", 20); - AetheriaRuntimeDaemonSimulation.Step(run, new AetheriaRuntimeDaemonIntentState(), 0.1, - AetheriaRuntimeDaemonSimulationSettings.AetheriaDefault, - NewPhysics(), catalog, 2, 0.2); - RequireNear(80, Stat(target, "hull"), 0.000001, - "unfunded shield must not manufacture absorption"); - } - - private static void ArmorCellsResolveBeforeEquipmentAndHull() - { - var source = Entity(0, -100, "player"); - var target = Entity(1, 0, "neutral"); - target.HullItemKey = "test-cell-hull"; - target.Equipment = [new AetheriaRuntimeLoadoutItemSlotCommit - { - X = 0, Y = 0, Rotation = "Clockwise", - Item = new AetheriaRuntimeLoadoutItemCommit - { ItemKey = "test-cell-gear", Quality = 1, Durability = 2, Enabled = true } - }]; - var hull = HullCatalogItem("test-cell-hull", 3, 3, 5); - var gear = CatalogItem("test-cell-gear"); - gear.ShapeWidth = 2; - gear.ShapeHeight = 1; - gear.ShapeCells = [new AetheriaRuntimeShapeCell(1, 0)]; - var catalog = new AetheriaRuntimeCatalogSnapshot([hull, gear], [], []); - var zone = new AetheriaRuntimeZoneSnapshotCommit - { ZoneIndex = 0, Entities = [source, target], PhysicalPayloads = [new AetheriaRuntimePhysicalPayloadCommit - { - PayloadId = "armor-blast", PayloadKind = "mine", WeaponItemKey = "test-cannon", - SourceEntityIndex = source.EntityIndex, PositionX = target.PositionX, PositionZ = target.PositionZ, - LifetimeSeconds = 30, BlastRadius = 200, PayloadMagnitude = 20, - DamageType = "Corrosive", Penetration = 0, DamageSpread = 0, - TriggeredAtSeconds = 0, DetonationDelaySeconds = 0, Stationary = true, Active = true - }] }; - var run = new AetheriaRuntimeRunCheckpointCommit - { RunId = "armor-cell-smoke", CurrentZoneIndex = 0, - CurrentEntityKey = "zone.0.entity.0", Zones = [zone] }; - - AetheriaRuntimeDaemonSimulation.Step(run, new AetheriaRuntimeDaemonIntentState(), 0.1, - AetheriaRuntimeDaemonSimulationSettings.AetheriaDefault, - NewPhysics(), catalog, 1, 0.1); - - var armor = target.StatGrids.Single(value => value.Name == "armor"); - RequireNear(0, armor.Values[3], 0.000001, "source-facing splash cells must absorb damage first"); - RequireNear(1.0 / 3.0, target.Equipment.Single().Item.Durability, 0.000001, - "rotated installed equipment at the impact cell must receive residual damage second"); - RequireNear(100 - (10.0 / 3.0), Stat(target, "hull"), 0.000001, - "only residual damage after armor and equipment may reach hull durability"); - var surface = AetheriaRuntimeDaemonGameSurfaceBuilder.Build( - new AetheriaRuntimeDaemonFrameDocument { FrameId = 1, Run = run }, - new AetheriaRuntimeDaemonHealthDocument(), - AetheriaRuntimeDaemonCommandBoundaryDocument.Create("daemon")); - var targetNode = Flatten(surface.Surface.Root).Single(node => - node.Id == "aetheria.daemon.game.world.entity.1"); - RequireEqual("entity.presentation", targetNode.Kind, - "Eve semantic entity facts must not become a second body-transform authority"); - RequireEqual("30", targetNode.Props["armor"], - "Eve world projection must expose aggregate armor as derived presentation state"); - RequireEqual("0,5,5,0,5,5,0,5,5", targetNode.Props["armorGrid"], - "Eve world projection must expose the exact daemon-owned schematic grid"); - } - - private static void PenetrationStopsAtFirstHullGap() - { - var source = Entity(0, 0, "player"); - source.DirectionX = 1; - source.LookDirectionX = 1; - source.VelocityX = 1; - source.TargetEntityIndex = 1; - source.Contacts = [new AetheriaRuntimeEntityContactCommit - { - TargetEntityIndex = 1, InfoGathered = 1, Hostile = true, Visible = true - }]; - source.WeaponGroups = [new[] { 0 }]; - source.Equipment = [new AetheriaRuntimeLoadoutItemSlotCommit - { - Item = new AetheriaRuntimeLoadoutItemCommit - { ItemKey = "gap-penetrator", Quality = 1, Durability = 10, Enabled = true } - }]; - var target = Entity(1, 80, "raider"); - target.HullItemKey = "disconnected-hull"; - var hull = HullCatalogItem("disconnected-hull", 4, 1, 0); - hull.ShapeCells = - [ - new AetheriaRuntimeShapeCell(0, 0), - new AetheriaRuntimeShapeCell(1, 0), - new AetheriaRuntimeShapeCell(3, 0) - ]; - var payload = new AetheriaRuntimeBehaviorPayload(0, AetheriaRuntimeBehaviorKinds.InstantWeapon, 0, - [ - new AetheriaRuntimeBehaviorField(2, PerformanceStat(10)), - new AetheriaRuntimeBehaviorField(3, PerformanceStat(3)), - new AetheriaRuntimeBehaviorField(6, PerformanceStat(150)), - new AetheriaRuntimeBehaviorField(16, PerformanceStat(200)), - new AetheriaRuntimeBehaviorField(17, PerformanceStat(1)), - new AetheriaRuntimeBehaviorField(19, PerformanceStat(0.1)), - new AetheriaRuntimeBehaviorField(21, PerformanceStat(100)), - new AetheriaRuntimeBehaviorField(23, PerformanceStat(180)), - new AetheriaRuntimeBehaviorField(24, PerformanceStat(1)), - new AetheriaRuntimeBehaviorField(25, PerformanceStat(1)) - ]); - var catalog = new AetheriaRuntimeCatalogSnapshot( - [WearableWeaponCatalogItem("gap-penetrator", payload), hull], [], []); - var run = new AetheriaRuntimeRunCheckpointCommit - { - RunId = "penetration-gap-smoke", GenerationSeed = 91, CurrentZoneIndex = 0, - CurrentEntityKey = "zone.0.entity.0", - Zones = [new AetheriaRuntimeZoneSnapshotCommit { ZoneIndex = 0, Entities = [source, target] }] - }; - var fire = new AetheriaRuntimeDaemonIntentState(); - fire.WeaponGroups.Add(new AetheriaRuntimeDaemonWeaponGroupIntent - { ActorEntityKey = run.CurrentEntityKey, WeaponGroup = 0, Fire = true, Active = true }); - - for (var frame = 0; frame < 12 && run.ShotReceipts.Count == 0; frame++) - AetheriaRuntimeDaemonSimulation.Step(run, - frame == 0 ? fire : new AetheriaRuntimeDaemonIntentState(), 0.1, - AetheriaRuntimeDaemonSimulationSettings.AetheriaDefault, - NewPhysics(), catalog, frame, frame * 0.1); - - var receipt = run.ShotReceipts.Single(value => value.WeaponItemKey == "gap-penetrator"); - Require(receipt.Hit && receipt.DamageCells.Count == 1 && - receipt.DamageCells[0].X == 1 && receipt.DamageCells[0].Y == 0, - $"impact UV must scale by the full fossil schematic dimensions and penetration must stop " + - $"at the first empty hull sample instead of damaging a disconnected island; " + - $"endpoint={receipt.EndpointX:0.###},{receipt.EndpointZ:0.###} " + - $"cells={string.Join(';', receipt.DamageCells.Select(cell => $"{cell.X},{cell.Y}"))}"); - RequireNear(90, Stat(target, "hull"), 0.000001, - "a disconnected hull island must not divide or receive penetration damage beyond a gap"); - var surface = AetheriaRuntimeDaemonGameSurfaceBuilder.BuildReactiveGameplay( - new AetheriaRuntimeDaemonFrameDocument { FrameId = receipt.FrameId, Run = run }); - var shot = Flatten(surface.Surface.Root).Single(node => - node.Kind == "shot.receipt" && node.Props["itemKey"] == "gap-penetrator"); - RequireEqual("1", shot.Props["damageCellCount"], - "generic Eve shot receipts must expose the daemon's contiguous penetration result"); - var damageCell = shot.Children.Single(node => node.Kind == "shot.damage-cell"); - Require(damageCell.Props["x"] == "1" && damageCell.Props["y"] == "0" && - damageCell.Props["armorAppliedDamage"] == "0" && - damageCell.Props["equipmentIndex"] == "-1" && - damageCell.Props["equipmentAppliedDamage"] == "0" && - damageCell.Props["hullAppliedDamage"] == "10", - "generic Eve must retain the exact per-cell layer outcome without reconstructing damage"); - } - - private static void ShipSchematicProjectsDaemonTopologyAndEquipmentState() - { - var player = Entity(0, -50, "player"); - player.TargetEntityIndex = 1; - var target = Entity(1, 0, "raider"); - var observed = Entity(2, 50, "neutral"); - target.HullItemKey = "schematic-hull"; - target.StatGrids = - [ - Grid("hull", 100), Grid("shield", 0), Grid("heat", 400), - Grid("armor", 3, 2, 10, 8, 6, 4, 2, 0), - Grid("maximumArmor", 3, 2, 10, 10, 10, 10, 10, 10) - ]; - target.Equipment = [new AetheriaRuntimeLoadoutItemSlotCommit - { - X = 1, Y = 0, Rotation = "Clockwise", - Item = new AetheriaRuntimeLoadoutItemCommit - { - ItemKey = "schematic-launcher", Quality = 1, Durability = 5, - Temperature = 400, Enabled = true - } - }]; - target.EquipmentStates = [new AetheriaRuntimeEquipmentStateCommit - { - EquipmentIndex = 0, Temperature = 400, ThermalPerformance = 0.75, - DurabilityPerformance = 0.5, Wear = 0.02, Online = true, - ThermalOnline = true, DurabilityOnline = true - }]; - target.WeaponStates = [new AetheriaRuntimeWeaponStateCommit - { - OwnerKind = AetheriaRuntimeBehaviorStateProjector.EquipmentOwnerKind, - OwnerIndex = 0, BehaviorIndex = 0, BehaviorKind = AetheriaRuntimeBehaviorKinds.Launcher, - Ammo = 3, CooldownProgress = 0.4, CoolingDown = true - }]; - var hull = HullCatalogItem("schematic-hull", 3, 2, 10); - var launcher = CatalogItem("schematic-launcher", new AetheriaRuntimeBehaviorPayload( - 0, AetheriaRuntimeBehaviorKinds.Launcher, 0, [])); - launcher.ShapeWidth = 2; - launcher.ShapeHeight = 1; - launcher.OccupiedCells = 2; - launcher.ShapeCells = - [ - new AetheriaRuntimeShapeCell(0, 0), - new AetheriaRuntimeShapeCell(1, 0) - ]; - launcher.Durability = 10; - launcher.MinimumTemperature = 200; - launcher.MaximumTemperature = 600; - var catalog = new AetheriaRuntimeCatalogSnapshot([hull, launcher], [], []); - var zone = new AetheriaRuntimeZoneSnapshotCommit { ZoneIndex = 0, Entities = [player, target, observed] }; - var run = new AetheriaRuntimeRunCheckpointCommit - { - RunId = "schematic-surface-smoke", CurrentZoneIndex = 0, - CurrentEntityKey = AetheriaRuntimeRunCheckpointCommit.EntityRecordKey( - "schematic-surface-smoke", 0, player.EntityIndex), - Zones = [zone] - }; - - var surface = AetheriaRuntimeDaemonGameSurfaceBuilder.Build( - new AetheriaRuntimeDaemonFrameDocument { FrameId = 1, Run = run }, - new AetheriaRuntimeDaemonHealthDocument(), - AetheriaRuntimeDaemonCommandBoundaryDocument.Create("daemon"), - catalog: catalog); - var schematic = Flatten(surface.Surface.Root).Single(node => - node.Id == "aetheria.daemon.game.world.entity.1.schematic"); - Require(schematic.Kind == "ship.schematic" && schematic.Props["role"] == "target" && - schematic.Props["width"] == "3" && schematic.Props["height"] == "2", - "selected target must publish its daemon-owned hull topology as a generic ship schematic"); - var hullCells = Flatten(schematic).Where(node => node.Kind == "schematic.hull-cell").ToArray(); - RequireEqual(6, hullCells.Length, - "schematic must publish every occupied hull cell rather than a renderer-invented rectangle"); - var damagedCell = hullCells.Single(node => node.Props["x"] == "1" && node.Props["y"] == "0"); - Require(damagedCell.Props["armor"] == "8" && damagedCell.Props["maximumArmor"] == "10" && - damagedCell.Props["armorRatio"] == "0.8", - "schematic cell must expose exact current and maximum daemon armor"); - var item = Flatten(schematic).Single(node => node.Kind == "schematic.item"); - Require(item.Props["itemKey"] == "schematic-launcher" && item.Props["durabilityRatio"] == "0.5" && - item.Props["temperatureRatio"] == "0.5" && item.Props["thermalPerformance"] == "0.75" && - item.Props["online"] == "true", - "schematic item must expose daemon-derived durability and thermal state"); - var itemCells = Flatten(item).Where(node => node.Kind == "schematic.item-cell") - .Select(node => $"{node.Props["x"]},{node.Props["y"]}").ToArray(); - RequireEqual("1,0;1,1", string.Join(";", itemCells), - "schematic footprint must use the same clockwise placement geometry as refit and damage"); - var weapon = Flatten(item).Single(node => node.Kind == "schematic.weapon"); - Require(weapon.Props["ammo"] == "3" && weapon.Props["cooldownProgress"] == "0.4", - "schematic weapon row must expose authoritative ammo and cooldown levers"); - Require(Flatten(surface.Surface.Root).Single(node => - node.Id == "aetheria.daemon.game.world.entity.0.schematic").Props["role"] == "self", - "controlled entity must publish the same schematic contract with a self role"); - Require(!Flatten(surface.Surface.Root).Any(node => - node.Id == "aetheria.daemon.game.world.entity.2.schematic"), - "unselected observed entities must not bloat the live surface with unused schematic trees"); - } - - private static void EquipmentDestructionDisablesBehaviorAndCockpitKillsBeforeHull() - { - var source = Entity(0, -100, "player"); - var target = Entity(1, 0, "neutral"); - target.HullItemKey = "equipment-damage-hull"; - target.Equipment = [new AetheriaRuntimeLoadoutItemSlotCommit - { - X = 0, Y = 0, - Item = new AetheriaRuntimeLoadoutItemCommit - { ItemKey = "damageable-weapon", Quality = 1, Durability = 1, Enabled = true } - }]; - target.WeaponStates = [new AetheriaRuntimeWeaponStateCommit - { - OwnerKind = AetheriaRuntimeBehaviorStateProjector.EquipmentOwnerKind, - OwnerIndex = 0, BehaviorIndex = 0, BehaviorKind = "InstantWeapon", - Firing = true, TriggerPending = true, BurstRemaining = 2, - Charging = true, Charged = true, Charge = 1, - LockProgress = 1, LockTargetEntityIndex = source.EntityIndex - }]; - var hull = HullCatalogItem("equipment-damage-hull", 1, 1, 0); - var weapon = CatalogItem("damageable-weapon", new AetheriaRuntimeBehaviorPayload( - 0, "InstantWeapon", 0, [])); - weapon.ShapeCells = [new AetheriaRuntimeShapeCell(0, 0)]; - var catalog = new AetheriaRuntimeCatalogSnapshot([hull, weapon], [], []); - var zone = new AetheriaRuntimeZoneSnapshotCommit - { - ZoneIndex = 0, Entities = [source, target], PhysicalPayloads = [new AetheriaRuntimePhysicalPayloadCommit - { - PayloadId = "equipment-destruction", PayloadKind = "mine", WeaponItemKey = "test-cannon", - SourceEntityIndex = source.EntityIndex, PositionX = target.PositionX, PositionZ = target.PositionZ, - LifetimeSeconds = 30, BlastRadius = 200, PayloadMagnitude = 1, - TriggeredAtSeconds = 0, DetonationDelaySeconds = 0, Stationary = true, Active = true - }] - }; - var run = new AetheriaRuntimeRunCheckpointCommit - { - RunId = "equipment-destruction-smoke", CurrentZoneIndex = 0, - CurrentEntityKey = "zone.0.entity.0", Zones = [zone] - }; - - AetheriaRuntimeDaemonSimulation.Step(run, new AetheriaRuntimeDaemonIntentState(), 0.1, - AetheriaRuntimeDaemonSimulationSettings.AetheriaDefault, NewPhysics(), catalog, 1, 0.1); - - Require(target.IsActive && Math.Abs(Stat(target, "hull") - 100) < 0.000001, - "destroying ordinary equipment must not manufacture hull damage or destroy the entity"); - RequireNear(0, target.Equipment.Single().Item.Durability, 0.000001, - "equipment damage must durably cross into the fossil destroyed state"); - RequireEqual(1, AetheriaRuntimeEquippedBehaviorQueries.Find(target, catalog, "InstantWeapon").Count, - "destroyed equipment must remain installed as durable loadout state"); - RequireEqual(0, AetheriaRuntimeEquippedBehaviorQueries.FindOperational(target, catalog, "InstantWeapon").Count, - "destroyed equipment behavior must stop contributing immediately"); - var destroyedWeaponState = target.WeaponStates.Single(); - Require(!destroyedWeaponState.Firing && !destroyedWeaponState.TriggerPending && - !destroyedWeaponState.Charging && !destroyedWeaponState.Charged && - destroyedWeaponState.BurstRemaining == 0 && destroyedWeaponState.LockTargetEntityIndex < 0, - "destroyed weapon state must deactivate atomically instead of publishing a ghost firing effect"); - RequireEqual(1, run.GameEvents.Count(value => value.Kind == "equipment.destroyed" && - value.TargetEntityIndex == target.EntityIndex && value.ItemKey == "damageable-weapon"), - "the durability crossing must publish exactly one generic equipment destruction fact"); - var surface = AetheriaRuntimeDaemonGameSurfaceBuilder.BuildReactiveGameplay( - new AetheriaRuntimeDaemonFrameDocument { FrameId = 1, Run = run }); - Require(Flatten(surface.Surface.Root).Any(node => node.Kind == "feedback.event" && - node.Props["eventKind"] == "equipment.destroyed" && - node.Props["itemKey"] == "damageable-weapon"), - "generic Eve clients must receive equipment destruction through provider feedback facts"); - - var cockpitTarget = Entity(2, 0, "neutral"); - cockpitTarget.HullItemKey = "cockpit-damage-hull"; - cockpitTarget.Equipment = [new AetheriaRuntimeLoadoutItemSlotCommit - { - X = 0, Y = 0, - Item = new AetheriaRuntimeLoadoutItemCommit - { ItemKey = "damageable-cockpit", Quality = 1, Durability = 1, Enabled = true } - }]; - var cockpitHull = HullCatalogItem("cockpit-damage-hull", 1, 1, 0); - var cockpit = CatalogItem("damageable-cockpit", new AetheriaRuntimeBehaviorPayload(0, "Cockpit", 0, [])); - cockpit.ShapeCells = [new AetheriaRuntimeShapeCell(0, 0)]; - var cockpitCatalog = new AetheriaRuntimeCatalogSnapshot([cockpitHull, cockpit], [], []); - var cockpitZone = new AetheriaRuntimeZoneSnapshotCommit - { - ZoneIndex = 0, Entities = [source, cockpitTarget], PhysicalPayloads = [new AetheriaRuntimePhysicalPayloadCommit - { - PayloadId = "cockpit-destruction", PayloadKind = "mine", WeaponItemKey = "test-cannon", - SourceEntityIndex = source.EntityIndex, PositionX = cockpitTarget.PositionX, PositionZ = cockpitTarget.PositionZ, - LifetimeSeconds = 30, BlastRadius = 200, PayloadMagnitude = 1, - TriggeredAtSeconds = 0, DetonationDelaySeconds = 0, Stationary = true, Active = true - }] - }; - var cockpitRun = new AetheriaRuntimeRunCheckpointCommit - { - RunId = "cockpit-destruction-smoke", CurrentZoneIndex = 0, - CurrentEntityKey = AetheriaRuntimeRunCheckpointCommit.EntityRecordKey( - "cockpit-destruction-smoke", 0, cockpitTarget.EntityIndex), - Zones = [cockpitZone] - }; - - AetheriaRuntimeDaemonSimulation.Step(cockpitRun, new AetheriaRuntimeDaemonIntentState(), 0.1, - AetheriaRuntimeDaemonSimulationSettings.AetheriaDefault, NewPhysics(), cockpitCatalog, 1, 0.1); - - Require(!cockpitTarget.IsActive && cockpitTarget.CauseOfDeath == "cockpit-destroyed", - "cockpit durability crossing must use the fossil cockpit death path before hull depletion"); - RequireNear(100, Stat(cockpitTarget, "hull"), 0.000001, - "cockpit destruction must not counterfeit scalar hull depletion"); - var chronology = cockpitRun.GameEvents.Where(value => - value.Kind == "equipment.destroyed" || value.Kind == "entity.destroyed") - .Select(value => value.Kind).ToArray(); - RequireEqual("equipment.destroyed,entity.destroyed", string.Join(",", chronology), - "equipment destruction feedback must precede the cause-specific entity destruction fact"); - RequireEqual(AetheriaRuntimeRunLifecycle.Failed, cockpitRun.LifecyclePhase, - "destruction of the controlled entity must end the daemon-owned run"); - RequireEqual("cockpit-destroyed", cockpitRun.TerminalReason, - "the run terminal reason must preserve the fossil cause of death"); - RequireEqual(1L, cockpitRun.TerminalFrameId, - "the run terminal frame must be committed with the lethal transaction"); - RequireEqual("", cockpitRun.CurrentEntityKey, - "the failed run must release its controlled entity after recording the terminal cause"); - RequireEqual(1, cockpitRun.GameEvents.Count(value => value.Kind == "run.failed" && - value.Reason == "cockpit-destroyed"), - "the lethal transaction must publish one durable run failure fact"); - - var restartedRun = MessagePack.MessagePackSerializer.Deserialize( - MessagePack.MessagePackSerializer.Serialize(cockpitRun)); - restartedRun.Zones[0].Entities[0].VelocityX = 10; - var sourcePosition = restartedRun.Zones[0].Entities[0].PositionX; - var rejectedAfterFailure = MovementCommand("move-after-run-failure", "zone.0.entity.0", 1, 0); - using var restartedPhysics = NewPhysics(); - var restarted = AetheriaRuntimeDaemonTickRunner.Tick( - Path.Combine(Path.GetTempPath(), "aetheria-failed-run-restart.cc"), - restartedRun, - new AetheriaRuntimeDaemonTickOptions - { - FrameId = 2, - FixedDeltaSeconds = 0.1, - SimulationTimeSeconds = 0.2, - ObservedCommands = [rejectedAfterFailure], - WorldPhysics = restartedPhysics, - BuildPublications = false - }); - Require(restarted.OperationResult.RejectedCommandIds.Contains("move-after-run-failure"), - "a terminal run must reject later gameplay commands after restart"); - RequireNear(sourcePosition, restartedRun.Zones[0].Entities[0].PositionX, 0.000001, - "a terminal run must not advance surviving world bodies after restart"); - RequireEqual(1, restartedRun.GameEvents.Count(value => value.Kind == "run.failed"), - "restart must not replay the run failure transition"); - - var failedSurface = AetheriaRuntimeDaemonGameSurfaceBuilder.Build( - restarted.Frame, - new AetheriaRuntimeDaemonHealthDocument(), - AetheriaRuntimeDaemonCommandBoundaryDocument.Create("daemon")); - var failedWorld = Flatten(failedSurface.Surface.Root) - .Single(node => node.Id == "aetheria.daemon.game.world"); - RequireEqual("terminal", failedWorld.Props["presentationMode"], - "the generic Eve world must receive a terminal presentation lever"); - RequireEqual("false", failedWorld.Props["movementEnabled"], - "the generic Eve world must not advertise movement after run failure"); - RequireEqual("cockpit-destroyed", failedWorld.Props["runTerminalReason"], - "the generic Eve world must receive the authoritative terminal cause"); - Require(AetheriaRuntimeStateRefResolver.TryResolveDaemonStateRef( - restarted.Frame, - new AetheriaRuntimeDaemonHealthDocument(), - AetheriaRuntimeDaemonCommandBoundaryDocument.Create("daemon"), - AetheriaRuntimeDaemonStateRefs.CurrentRunLifecycle, - out var projectedLifecycle) && - projectedLifecycle == AetheriaRuntimeRunLifecycle.Failed, - "Eve state bindings must resolve the daemon-owned run lifecycle without client inference"); - } - - private static void DestructionDropsLootExactlyOnce() - { - var source = Entity(0, -100, "player"); - var target = Entity(1, 0, "raider"); - target.HullItemKey = "drop-hull"; - target.StatGrids = [Grid("hull", 1), Grid("shield", 0), Grid("heat", 0)]; - target.Equipment = [new AetheriaRuntimeLoadoutItemSlotCommit - { - Item = new AetheriaRuntimeLoadoutItemCommit - { ItemKey = "drop-gear", Quality = 0.8, Durability = 0.6, Quantity = 1, Enabled = true } - }]; - target.CargoContents = [Cargo(("drop-cargo", 4, 0, 0))]; - var zone = new AetheriaRuntimeZoneSnapshotCommit - { - ZoneIndex = 0, Entities = [source, target], - PhysicalPayloads = [new AetheriaRuntimePhysicalPayloadCommit - { - PayloadId = "destruction-mine", PayloadKind = "mine", WeaponItemKey = "destruction-charge", - SourceEntityIndex = 0, PositionX = 0, PositionZ = 0, BlastRadius = 200, - PayloadMagnitude = 20, TriggeredAtSeconds = 0, DetonationDelaySeconds = 0, - LifetimeSeconds = 30, Active = true, Stationary = true - }] - }; - var run = new AetheriaRuntimeRunCheckpointCommit - { - RunId = "destruction-smoke", GenerationSeed = 29, CurrentZoneIndex = 0, - CurrentEntityKey = "zone.0.entity.0", Zones = [zone] - }; - var settings = LootSettings(1); - - AetheriaRuntimeDaemonSimulation.Step(run, new AetheriaRuntimeDaemonIntentState(), 0.1, settings, - NewPhysics(), null, 1, 0.1); - - Require(!target.IsActive && target.DestroyedFrameId == 1 && - !string.IsNullOrWhiteSpace(target.DestructionId), - "lethal daemon damage must commit one durable destruction identity and deactivate the entity"); - RequireEqual(2, zone.DroppedPickups.Count, - "guaranteed fossil loot roll must drop non-hull equipment and every cargo stack"); - Require(!zone.DroppedPickups.Any(value => value.Item.ItemKey == "drop-hull"), - "the equipped hull must remain excluded from fossil destruction loot"); - RequireEqual(4, zone.DroppedPickups.Single(value => value.Item.ItemKey == "drop-cargo").Item.Quantity, - "cargo drops must preserve exact stack quantity"); - Require(zone.DroppedPickups.All(value => Math.Abs(Math.Sqrt( - value.VelocityX * value.VelocityX + value.VelocityY * value.VelocityY + - value.VelocityZ * value.VelocityZ) - 25) < 0.000001 && - Math.Abs(value.LifetimeSeconds - 30) < 0.000001), - "destruction pickups must retain fossil launch speed and lifetime"); - RequireEqual(2, run.GameEvents.Count(value => value.Kind == "pickup.dropped"), - "each durable pickup must publish one drop event"); - RequireEqual(1, run.GameEvents.Count(value => value.Kind == "entity.destroyed"), - "destruction chronology must publish exactly once"); - RequireEqual(AetheriaRuntimeRunLifecycle.Active, run.LifecyclePhase, - "destroying a non-controlled entity must not counterfeit a run failure"); - - var surface = AetheriaRuntimeDaemonGameSurfaceBuilder.Build( - new AetheriaRuntimeDaemonFrameDocument { FrameId = 1, Run = run }, - new AetheriaRuntimeDaemonHealthDocument(), - AetheriaRuntimeDaemonCommandBoundaryDocument.Create("daemon")); - Require(!Flatten(surface.Surface.Root).Any(node => node.Id == "aetheria.daemon.game.world.entity.1"), - "Eve must not project a destroyed tombstone as an active world entity"); - var destructionAsset = AetheriaRuntimeAssets.ProjectManifest(null).Assets.Single(value => - value.Ref.Metadata.TryGetValue("presentationRole", out var role) && - role == "effect.feedback.entity.destroyed"); - RequireEqual("prefab.effect.entity.destroyed", destructionAsset.Ref.AssetKey, - "provider must advertise the original destruction effect by semantic feedback role"); - - AetheriaRuntimeDaemonSimulation.Step(run, new AetheriaRuntimeDaemonIntentState(), 0.1, settings, - NewPhysics(), null, 2, 0.2); - RequireEqual(2, zone.DroppedPickups.Count, - "later ticks must not duplicate an already committed destruction transaction"); - RequireEqual(1, run.GameEvents.Count(value => value.Kind == "entity.destroyed"), - "later ticks must not duplicate destruction chronology"); - } - - private static void ChargedWeaponHoldRiskMalfunctionsDeterministically() - { - var source = Entity(0, 0, "player"); - source.WeaponGroups = [new[] { 0 }]; - var item = new AetheriaRuntimeLoadoutItemCommit - { ItemKey = "risk-charger", Quality = 1, Durability = 1, Enabled = true }; - source.Equipment = [new AetheriaRuntimeLoadoutItemSlotCommit { Item = item }]; - var run = new AetheriaRuntimeRunCheckpointCommit - { - RunId = "charged-risk-smoke", GenerationSeed = 17, - CurrentZoneIndex = 0, CurrentEntityKey = "zone.0.entity.0", - Zones = [new AetheriaRuntimeZoneSnapshotCommit { ZoneIndex = 0, Entities = [source] }] - }; - var payload = new AetheriaRuntimeBehaviorPayload(0, AetheriaRuntimeBehaviorKinds.ChargedWeapon, 0, - [ - new AetheriaRuntimeBehaviorField(2, PerformanceStat(20)), - new AetheriaRuntimeBehaviorField(6, PerformanceStat(150)), - new AetheriaRuntimeBehaviorField(17, PerformanceStat(1)), - new AetheriaRuntimeBehaviorField(19, PerformanceStat(0.5)), - new AetheriaRuntimeBehaviorField(21, PerformanceStat(1)), - new AetheriaRuntimeBehaviorField(25, Number(1)), - new AetheriaRuntimeBehaviorField(26, Number(0.25)) - ]); - var catalog = new AetheriaRuntimeCatalogSnapshot([CatalogItem(item.ItemKey, payload)], [], []); - for (var frame = 0; frame < 21; frame++) - { - var intents = new AetheriaRuntimeDaemonIntentState(); - if (frame == 0) intents.WeaponGroups.Add(new AetheriaRuntimeDaemonWeaponGroupIntent - { ActorEntityKey = "zone.0.entity.0", WeaponGroup = 0, Fire = true, Active = true }); - AetheriaRuntimeDaemonSimulation.Step(run, intents, 0.1, - new AetheriaRuntimeDaemonSimulationSettings(), - NewPhysics(), catalog, frame, frame * 0.1); - } - var state = source.WeaponStates.Single(value => value.BehaviorKind == AetheriaRuntimeBehaviorKinds.ChargedWeapon); - Require(!state.Charging && !state.Charged && state.CoolingDown && state.ChargeRiskChecks == 1 && - Math.Abs(state.ChargeMalfunctionRisk - 1) < 0.000001, - "held full charge must persist its risk check and fail when authored risk reaches certainty"); - RequireNear(0.75, item.Durability, 0.000001, - "charged malfunction must damage canonical equipped-item durability"); - Require(run.GameEvents.Count(value => value.Kind == "weapon.charge.malfunctioned" && - value.SubjectKey == "hold-risk" && value.ItemKey == item.ItemKey) == 1, - "charged hold risk must emit one authoritative malfunction event"); - Require(!run.GameEvents.Any(value => value.Kind == "shot.committed"), - "malfunction without a firing solution must never leak a projectile"); - } - - private static void AgentTowsStationIntoPersistentOrbit() - { - var tug = Entity(0, 0, "workers"); - var station = Entity(1, 0, "workers"); - station.Kind = "station"; - tug.AgentTaskCapabilities = [AetheriaRuntimeAgentTaskTypes.Tow]; - var zone = new AetheriaRuntimeZoneSnapshotCommit - { - ZoneIndex = 0, Entities = [tug, station], - Orbits = [new AetheriaRuntimeOrbitSnapshotCommit { OrbitKey = "parent-orbit", FixedPositionX = 20 }], - Bodies = [new AetheriaRuntimeBodySnapshotCommit { BodyKey = "parent-body", Kind = "planet", OrbitKey = "parent-orbit" }] - }; - var run = new AetheriaRuntimeRunCheckpointCommit - { - CurrentZoneIndex = 0, CurrentEntityKey = "", Zones = [zone], - AgentTasks = [new AetheriaRuntimeAgentTaskCommit { TaskId = "tow-1", CorporationKey = "workers", TaskType = AetheriaRuntimeAgentTaskTypes.Tow, ZoneIndex = 0, Status = AetheriaRuntimeAgentTaskStatuses.Queued, TargetEntityIndex = 1, OrbitParentKey = "parent-body", OrbitDistance = 20, CompletionRadius = 5 }] - }; - AetheriaRuntimeDaemonTickRunner.Tick(Path.Combine(Path.GetTempPath(), "aetheria-tow-attach-smoke.cc"), run, - new AetheriaRuntimeDaemonTickOptions { WorldPhysics = NewPhysics(), FrameId = 1, FixedDeltaSeconds = 0.1, SimulationTimeSeconds = 1, BuildPublications = false }); - Require(run.Zones[0].Entities[0].ChildEntityIndices.Contains(1), "tow pickup must attach station to tug parentage"); - RequireEqual("", run.Zones[0].Entities[1].OrbitKey, "attached station must no longer own an orbit"); - - AetheriaRuntimeDaemonTickRunner.Tick(Path.Combine(Path.GetTempPath(), "aetheria-tow-detach-smoke.cc"), run, - new AetheriaRuntimeDaemonTickOptions { WorldPhysics = NewPhysics(), FrameId = 2, FixedDeltaSeconds = 0.1, SimulationTimeSeconds = 2, BuildPublications = false }); - Require(!run.Zones[0].Entities[0].ChildEntityIndices.Contains(1), "tow delivery must detach station parentage"); - Require(!string.IsNullOrWhiteSpace(run.Zones[0].Entities[1].OrbitKey), "delivered station must own a persistent orbit"); - Require(run.Zones[0].Orbits.Any(orbit => orbit.OrbitKey == run.Zones[0].Entities[1].OrbitKey && orbit.ParentOrbitKey == "parent-orbit" && Math.Abs(orbit.Distance - 20) < 0.001), - "delivered orbit must preserve requested parent and radius"); - RequireEqual(AetheriaRuntimeAgentTaskStatuses.Completed, run.AgentTasks[0].Status, "tow task must complete only after detach applies"); - } - - private static void AgentSurveysBodyIntoCorporationKnowledge() - { - var surveyor = Entity(0, 0, "workers"); - surveyor.AgentTaskCapabilities = [AetheriaRuntimeAgentTaskTypes.Explore]; - surveyor.Equipment = [new AetheriaRuntimeLoadoutItemSlotCommit { Item = new AetheriaRuntimeLoadoutItemCommit { ItemKey = "scanner", Enabled = true } }]; - var scanner = CatalogItem("scanner", new AetheriaRuntimeBehaviorPayload(0, "ResourceScanner", 0, - [new AetheriaRuntimeBehaviorField(1, PerformanceStat(100)), new AetheriaRuntimeBehaviorField(2, PerformanceStat(4)), new AetheriaRuntimeBehaviorField(3, PerformanceStat(0.5))])); - var catalog = new AetheriaRuntimeCatalogSnapshot([scanner], [], []); - var run = new AetheriaRuntimeRunCheckpointCommit - { - CurrentZoneIndex = 0, CurrentEntityKey = "", - Zones = [new AetheriaRuntimeZoneSnapshotCommit - { - ZoneIndex = 0, Entities = [surveyor], - Orbits = [new AetheriaRuntimeOrbitSnapshotCommit { OrbitKey = "survey-orbit", FixedPositionX = 10 }], - Bodies = [new AetheriaRuntimeBodySnapshotCommit { BodyKey = "survey-world", Kind = "planet", OrbitKey = "survey-orbit" }] - }], - AgentTasks = [new AetheriaRuntimeAgentTaskCommit { TaskId = "survey-1", CorporationKey = "workers", TaskType = AetheriaRuntimeAgentTaskTypes.Explore, ZoneIndex = 0, Status = AetheriaRuntimeAgentTaskStatuses.Queued, TargetBodyKeys = ["survey-world"] }] - }; - AetheriaRuntimeDaemonTickRunner.Tick(Path.Combine(Path.GetTempPath(), "aetheria-survey-smoke.cc"), run, - new AetheriaRuntimeDaemonTickOptions { WorldPhysics = NewPhysics(), FrameId = 1, FixedDeltaSeconds = 1, SimulationTimeSeconds = 1, Catalog = catalog, BuildPublications = false }); - var knowledge = run.CorporationSurveys.Single(); - RequireEqual("workers", knowledge.CorporationKey, "survey knowledge must belong to the agent corporation"); - RequireEqual("survey-world", knowledge.BodyKey, "survey knowledge must identify the scanned body"); - RequireNear(4, knowledge.DensityFloor, 0.000001, "survey must publish the scanner minimum density"); - - AetheriaRuntimeDaemonTickRunner.Tick(Path.Combine(Path.GetTempPath(), "aetheria-survey-complete-smoke.cc"), run, - new AetheriaRuntimeDaemonTickOptions { WorldPhysics = NewPhysics(), FrameId = 2, FixedDeltaSeconds = 1, SimulationTimeSeconds = 2, Catalog = catalog, BuildPublications = false }); - RequireEqual(AetheriaRuntimeAgentTaskStatuses.Completed, run.AgentTasks[0].Status, - "survey order must complete when corporation knowledge satisfies the scanner threshold"); - } - - private static void CargoCapacityComesFromHullAndCatalogVolumes() - { - var hull = CatalogItem("hauler-hull"); - hull.HullCapacity = PerformanceStat(20); - var gear = CatalogItem("scanner"); - gear.Volume = 3; - var cargoBay = CatalogItem("hauler-cargo"); - cargoBay.Volume = 0; - cargoBay.InteriorOccupiedCells = 16; - var ore = CatalogItem("ore"); - ore.Volume = 2; - var catalog = new AetheriaRuntimeCatalogSnapshot([hull, gear, cargoBay, ore], [], []); - var entity = Entity(0, 0, "workers"); - entity.HullItemKey = hull.ItemKey; - entity.Equipment = - [ - new AetheriaRuntimeLoadoutItemSlotCommit { Item = new AetheriaRuntimeLoadoutItemCommit { ItemKey = gear.ItemKey } } - ]; - entity.CargoBays = [new AetheriaRuntimeLoadoutItemSlotCommit { Item = new AetheriaRuntimeLoadoutItemCommit { ItemKey = cargoBay.ItemKey } }]; - entity.CargoContents = [new AetheriaRuntimeCargoBayLoadoutCommit { Items = [new AetheriaRuntimeLoadoutItemSlotCommit { Item = new AetheriaRuntimeLoadoutItemCommit { ItemKey = ore.ItemKey, Quantity = 4 } }] }]; - - RequireNear(20, AetheriaRuntimeCargoCapacityQueries.Capacity(entity, catalog), 0.000001, - "cargo capacity must evaluate the authored hull performance stat"); - RequireNear(11, AetheriaRuntimeCargoCapacityQueries.Occupied(entity, catalog), 0.000001, - "equipment and stacked cargo must consume catalog volume"); - RequireEqual(4, AetheriaRuntimeCargoCapacityQueries.UnitsThatFit(entity, catalog, ore.ItemKey), - "the installed cargo bay's remaining volume must determine whole units that fit"); - } - - private static void AgentMinesAsteroidThroughEquippedBehavior() - { - var miner = Entity(0, 0, "workers"); - var home = Entity(1, 0, "workers"); - miner.AgentTaskCapabilities = [AetheriaRuntimeAgentTaskTypes.Mine]; - miner.Equipment = - [ - new AetheriaRuntimeLoadoutItemSlotCommit { Item = new AetheriaRuntimeLoadoutItemCommit { ItemKey = "mining-tool", Enabled = true, Quality = 1, Durability = 1 } } - ]; - miner.CargoBays = [new AetheriaRuntimeLoadoutItemSlotCommit { Item = new AetheriaRuntimeLoadoutItemCommit { ItemKey = "miner-cargo", Enabled = true, Quality = 1, Durability = 1 } }]; - home.CargoBays = [new AetheriaRuntimeLoadoutItemSlotCommit { Item = new AetheriaRuntimeLoadoutItemCommit { ItemKey = "home-cargo", Enabled = true, Quality = 1, Durability = 1 } }]; - var minerHull = CatalogItem("miner-hull"); - minerHull.HullCapacity = PerformanceStat(3); - var homeHull = CatalogItem("home-hull"); - homeHull.HullCapacity = PerformanceStat(100); - var iron = CatalogItem("iron"); - iron.SimpleCommodityCategory = "ore"; - iron.Category = AetheriaRuntimeItemCategories.SimpleCommodity; - iron.ShapeWidth = 1; - iron.ShapeHeight = 1; - iron.ShapeCells = [new AetheriaRuntimeShapeCell(0, 0)]; - iron.Stackable = true; - iron.MaxStack = 100; - iron.Volume = 1; - var minerCargo = CatalogItem("miner-cargo"); - minerCargo.Category = AetheriaRuntimeItemCategories.CargoBay; - minerCargo.ShapeWidth = 1; - minerCargo.ShapeHeight = 1; - minerCargo.ShapeCells = [new AetheriaRuntimeShapeCell(0, 0)]; - minerCargo.InteriorShapeWidth = 2; - minerCargo.InteriorShapeHeight = 1; - minerCargo.InteriorOccupiedCells = 2; - minerCargo.InteriorShapeCells = [new AetheriaRuntimeShapeCell(0, 0), new AetheriaRuntimeShapeCell(1, 0)]; - var homeCargo = CatalogItem("home-cargo"); - homeCargo.Category = AetheriaRuntimeItemCategories.CargoBay; - homeCargo.ShapeWidth = 1; - homeCargo.ShapeHeight = 1; - homeCargo.ShapeCells = [new AetheriaRuntimeShapeCell(0, 0)]; - homeCargo.InteriorShapeWidth = 10; - homeCargo.InteriorShapeHeight = 10; - homeCargo.InteriorOccupiedCells = 100; - homeCargo.InteriorShapeCells = Enumerable.Range(0, 10) - .SelectMany(y => Enumerable.Range(0, 10).Select(x => new AetheriaRuntimeShapeCell(x, y))) - .ToArray(); - miner.HullItemKey = minerHull.ItemKey; - home.HullItemKey = homeHull.ItemKey; - miner.CargoContents = [new AetheriaRuntimeCargoBayLoadoutCommit()]; - home.CargoContents = [new AetheriaRuntimeCargoBayLoadoutCommit()]; - var catalog = new AetheriaRuntimeCatalogSnapshot( - [minerHull, homeHull, iron, minerCargo, homeCargo, CatalogItem("mining-tool", new AetheriaRuntimeBehaviorPayload(0, "MiningTool", 0, - [new AetheriaRuntimeBehaviorField(1, PerformanceStat(1000)), new AetheriaRuntimeBehaviorField(2, PerformanceStat(1000000000)), new AetheriaRuntimeBehaviorField(3, PerformanceStat(2)), new AetheriaRuntimeBehaviorField(4, PerformanceStat(1000))]))], - [], []); - var asteroid = new AetheriaRuntimeAsteroidCommit { Distance = 0, Size = 6 }; - var zone = new AetheriaRuntimeZoneSnapshotCommit - { - ZoneIndex = 0, - Entities = [miner, home], - Bodies = [new AetheriaRuntimeBodySnapshotCommit { BodyKey = "belt", Kind = "asteroid_belt", Asteroids = [asteroid], Resources = [new AetheriaRuntimeBodyResourceCommit { ItemKey = "iron", Amount = 1 }] }] - }; - var run = new AetheriaRuntimeRunCheckpointCommit - { - GenerationSeed = 7, - CurrentZoneIndex = 0, - CurrentEntityKey = "", - Zones = [zone], - AgentTasks = [new AetheriaRuntimeAgentTaskCommit { TaskId = "mine-1", CorporationKey = "workers", TaskType = AetheriaRuntimeAgentTaskTypes.Mine, ZoneIndex = 0, OriginEntityIndex = 1, CompletionRadius = 10, Status = AetheriaRuntimeAgentTaskStatuses.Queued, TargetBodyKeys = ["belt"] }] - }; - - var result = AetheriaRuntimeDaemonTickRunner.Tick(Path.Combine(Path.GetTempPath(), "aetheria-mining-smoke.cc"), run, - new AetheriaRuntimeDaemonTickOptions { WorldPhysics = NewPhysics(), FrameId = 1, FixedDeltaSeconds = 1, SimulationTimeSeconds = 1, Catalog = catalog, BuildPublications = false }); - - Require(run.AgentTasks[0].Phase == "mining", "mining task must activate its equipped tool"); - Require(result.OperationResult.Intents.Behaviors.Count == 1, - $"mining behavior command must become daemon intent (count={result.OperationResult.Intents.Behaviors.Count}, applied={string.Join(',', result.Frame.AppliedCommandIds)}, rejected={string.Join(',', result.Frame.RejectedCommandIds)})"); - var committedMiner = run.Zones[0].Entities.Single(entity => entity.EntityIndex == 0); - var committedAsteroid = run.Zones[0].Bodies[0].Asteroids[0]; - Require(committedAsteroid.RespawnTimer > 0, $"mining damage must deplete the asteroid and start historical respawn (damage={committedAsteroid.Damage}, respawn={committedAsteroid.RespawnTimer})"); - Require(committedMiner.CargoContents.SelectMany(bay => bay.Items).Any(slot => slot.Item.ItemKey == "iron" && slot.Item.Quantity > 0), - "historical mining yield must enter daemon-owned cargo"); - - AetheriaRuntimeDaemonTickRunner.Tick(Path.Combine(Path.GetTempPath(), "aetheria-mining-offload-smoke.cc"), run, - new AetheriaRuntimeDaemonTickOptions { WorldPhysics = NewPhysics(), FrameId = 2, FixedDeltaSeconds = 1, SimulationTimeSeconds = 2, Catalog = catalog, BuildPublications = false }); - Require(!run.Zones[0].Entities[0].CargoContents.SelectMany(bay => bay.Items).Any(), - "full miner must offload through the shared cargo transfer command"); - Require(run.Zones[0].Entities[1].CargoContents.SelectMany(bay => bay.Items).Any(slot => slot.Item.ItemKey == "iron" && slot.Item.Quantity == 1), - "home storage must receive the mined commodity"); - RequireEqual(AetheriaRuntimeAgentTaskStatuses.Assigned, run.AgentTasks[0].Status, - "miner must remain assigned after a successful offload"); - } - - private static void TickReconcilesAndEvaluatesCatalogBehaviors() - { - var entity = Entity(0, 0, "workers"); - entity.Equipment = - [ - new AetheriaRuntimeLoadoutItemSlotCommit - { - Item = new AetheriaRuntimeLoadoutItemCommit - { - ItemKey = "mining-tool", - Quality = 1, - Durability = 1, - Enabled = true - } - } - ]; - entity.BehaviorStates = Array.Empty(); - var payload = new AetheriaRuntimeBehaviorPayload( - 0, - "MiningTool", - 0, - [ - new AetheriaRuntimeBehaviorField(1, PerformanceStat(12)), - new AetheriaRuntimeBehaviorField(2, PerformanceStat(0.8)), - new AetheriaRuntimeBehaviorField(3, PerformanceStat(2)), - new AetheriaRuntimeBehaviorField(4, PerformanceStat(50)) - ]); - var catalog = new AetheriaRuntimeCatalogSnapshot( - [CatalogItem("mining-tool", payload)], - Array.Empty(), - Array.Empty()); - var run = new AetheriaRuntimeRunCheckpointCommit - { - RunId = "behavior-query-smoke", - CurrentZoneIndex = 0, - CurrentEntityKey = "", - Zones = [new AetheriaRuntimeZoneSnapshotCommit { ZoneIndex = 0, Entities = [entity] }] - }; - - AetheriaRuntimeDaemonTickRunner.Tick( - Path.Combine(Path.GetTempPath(), "aetheria-behavior-query-smoke.cc"), - run, - new AetheriaRuntimeDaemonTickOptions - { - WorldPhysics = NewPhysics(), - FrameId = 1, - FixedDeltaSeconds = 0, - Catalog = catalog, - BuildPublications = false - }); - - var behavior = AetheriaRuntimeEquippedBehaviorQueries.Find(entity, catalog, "MiningTool").Single(); - RequireEqual("MiningTool", behavior.State.BehaviorKind, - "tick must reconcile equipped catalog payloads into persistent behavior state"); - RequireNear(12, behavior.EvaluateStat(1), 0.000001, "behavior query must evaluate mining damage from catalog"); - RequireNear(50, behavior.EvaluateStat(4), 0.000001, "behavior query must evaluate mining range from catalog"); - } - - private static void DaemonLoadoutsRespectFactionAvailabilityAndHullRoles() - { - var cells4 = Enumerable.Range(0, 4).SelectMany(y => Enumerable.Range(0, 4) - .Select(x => new AetheriaRuntimeShapeCell(x, y))).ToArray(); - var one = new[] { new AetheriaRuntimeShapeCell(0, 0) }; - var cells2 = Enumerable.Range(0, 2).SelectMany(y => Enumerable.Range(0, 2) - .Select(x => new AetheriaRuntimeShapeCell(x, y))).ToArray(); - - AetheriaRuntimeCatalogItem Item(string key, string category, string manufacturer, int price, - string hardpointType = "", params string[] behaviors) - { - var item = CatalogItem(key, behaviors.Select((kind, index) => - new AetheriaRuntimeBehaviorPayload(index, kind, 0, Array.Empty())).ToArray()); - item.Category = category; item.ManufacturerKey = manufacturer; item.Price = price; - item.HardpointType = hardpointType; item.ShapeWidth = 1; item.ShapeHeight = 1; - item.OccupiedCells = 1; item.ShapeCells = one; item.BehaviorKinds = behaviors; - return item; - } - - var availableHull = Item("available-hull", AetheriaRuntimeItemCategories.Hull, "forge", 100); - availableHull.HardpointType = "Hull"; - availableHull.HullType = "Ship"; availableHull.ShapeWidth = 4; availableHull.ShapeHeight = 4; - availableHull.OccupiedCells = 16; availableHull.ShapeCells = cells4; - availableHull.Hardpoints = - [ - new AetheriaRuntimeHardpoint("ControlModule", 0, 0, 1, 1, 1, one, "", "None", 0), - new AetheriaRuntimeHardpoint("Weapon", 1, 0, 1, 1, 1, one, "", "Clockwise", 0), - new AetheriaRuntimeHardpoint("Sensors", 2, 0, 1, 1, 1, one, "", "None", 0) - ]; - var unavailableHull = Item("cheap-foreign-hull", AetheriaRuntimeItemCategories.Hull, "foreign", 1); - unavailableHull.HardpointType = "Hull"; - unavailableHull.HullType = "Ship"; unavailableHull.ShapeWidth = 4; unavailableHull.ShapeHeight = 4; - unavailableHull.OccupiedCells = 16; unavailableHull.ShapeCells = cells4; - var stationHull = Item("station-hull", AetheriaRuntimeItemCategories.Hull, "forge", 120); - stationHull.HardpointType = "Hull"; stationHull.HullType = "Station"; - stationHull.ShapeWidth = 4; stationHull.ShapeHeight = 4; stationHull.OccupiedCells = 16; - stationHull.ShapeCells = cells4; - stationHull.Hardpoints = - [ - new AetheriaRuntimeHardpoint("ControlModule", 0, 0, 1, 1, 1, one, "", "None", 0), - new AetheriaRuntimeHardpoint("Sensors", 1, 1, 2, 2, 4, cells2, "", "None", 0) - ]; - var cockpit = Item("cockpit", AetheriaRuntimeItemCategories.Gear, "forge", 20, "ControlModule", "Cockpit"); - var wrongController = Item("cheap-turret-controller", AetheriaRuntimeItemCategories.Gear, "forge", 1, - "ControlModule", "TurretController"); - var weapon = Item("cannon", AetheriaRuntimeItemCategories.Weapon, "forge", 40, "Weapon", "LockWeapon"); - var shipSensor = Item("ship-sensor", AetheriaRuntimeItemCategories.Gear, "forge", 15, "Sensors", "Sensor"); - var stationSensor = Item("station-sensor", AetheriaRuntimeItemCategories.Gear, "forge", 80, "Sensors", "Sensor"); - stationSensor.ShapeWidth = 2; stationSensor.ShapeHeight = 2; - stationSensor.OccupiedCells = 4; stationSensor.ShapeCells = cells2; - var cargo = Item("cargo-bay", AetheriaRuntimeItemCategories.CargoBay, "forge", 30); - cargo.HardpointType = "Internal"; - cargo.InteriorShapeWidth = 3; cargo.InteriorShapeHeight = 3; cargo.InteriorOccupiedCells = 9; - cargo.InteriorShapeCells = Enumerable.Range(0, 3).SelectMany(y => Enumerable.Range(0, 3) - .Select(x => new AetheriaRuntimeShapeCell(x, y))).ToArray(); - var docking = Item("docking-bay", AetheriaRuntimeItemCategories.DockingBay, "forge", 35); - docking.HardpointType = "Tool"; - var dockyardBay = Item("dockyard-bay", AetheriaRuntimeItemCategories.DockingBay, "forge", 70, "DockingBay"); - var dockyardHull = Item("dockyard-hull", AetheriaRuntimeItemCategories.Hull, "forge", 600); - dockyardHull.HardpointType = "Hull"; - dockyardHull.HullType = "Station"; - dockyardHull.ShapeWidth = 6; - dockyardHull.ShapeHeight = 4; - dockyardHull.ShapeCells = Enumerable.Range(0, 4).SelectMany(y => Enumerable.Range(0, 6) - .Select(x => new AetheriaRuntimeShapeCell(x, y))).ToArray(); - dockyardHull.OccupiedCells = dockyardHull.ShapeCells.Count; - dockyardHull.Hardpoints = Enumerable.Range(0, 6) - .SelectMany(x => new[] - { - new AetheriaRuntimeHardpoint("DockingBay", x, 0, 1, 1, 1, one, "", "None", 0), - new AetheriaRuntimeHardpoint("DockingBay", x, 3, 1, 1, 1, one, "", "None", 0) - }) - .ToArray(); - var capacitor = Item("capacitor", AetheriaRuntimeItemCategories.Gear, "forge", 25, "", "Capacitor"); - capacitor.HardpointType = "Internal"; - var faction = new AetheriaRuntimeCorporation("forge", "Forge", "F", "", "", "", 1, 1, - [new AetheriaRuntimeCorporationAllegiance("forge", 1)]); - var foreign = new AetheriaRuntimeCorporation("foreign", "Foreign", "X", "", "", "", 1, 2, - [new AetheriaRuntimeCorporationAllegiance("foreign", 1), new AetheriaRuntimeCorporationAllegiance("forge", 0.5)]); - var reactor = Item("reactor", AetheriaRuntimeItemCategories.Gear, "forge", 25, "", "Reactor"); - reactor.HardpointType = "Internal"; - var catalog = new AetheriaRuntimeCatalogSnapshot( - [availableHull, unavailableHull, stationHull, dockyardHull, cockpit, wrongController, weapon, shipSensor, stationSensor, cargo, docking, dockyardBay, capacitor, reactor], - [faction, foreign], Array.Empty()); - var fallbackCatalog = new AetheriaRuntimeCatalogSnapshot( - [availableHull, unavailableHull, stationHull, cockpit, wrongController, weapon, shipSensor, cargo, docking, capacitor, reactor], - [faction, foreign], Array.Empty()); - var homes = new Dictionary { ["forge"] = 0, ["foreign"] = 1 }; - var adjacency = new Dictionary> { [0] = [1], [1] = [0] }; - - var rareHull = Item("rare-hull", AetheriaRuntimeItemCategories.Hull, "forge", 100); - rareHull.HardpointType = availableHull.HardpointType; - rareHull.HullType = availableHull.HullType; - rareHull.ShapeWidth = availableHull.ShapeWidth; - rareHull.ShapeHeight = availableHull.ShapeHeight; - rareHull.OccupiedCells = availableHull.OccupiedCells; - rareHull.ShapeCells = availableHull.ShapeCells; - rareHull.Hardpoints = availableHull.Hardpoints; - rareHull.Tags = ["rarity:rare"]; - var rarityCatalog = new AetheriaRuntimeCatalogSnapshot( - [availableHull, rareHull, cockpit, wrongController, weapon, shipSensor, cargo, capacitor, reactor], - [faction], Array.Empty()); - - var first = new AetheriaDaemonLoadoutGenerator(catalog, 42, 0, homes, adjacency).Build("ship", "forge"); - var second = new AetheriaDaemonLoadoutGenerator(catalog, 42, 0, homes, adjacency).Build("ship", "forge"); - var station = new AetheriaDaemonLoadoutGenerator(catalog, 84, 0, homes, adjacency) - .Build("station", "forge", "station-hull"); - var fallbackStation = new AetheriaDaemonLoadoutGenerator(fallbackCatalog, 84, 0, homes, adjacency) - .Build("station", "forge", "station-hull"); - var dockyard = new AetheriaDaemonLoadoutGenerator(catalog, 85, 0, homes, adjacency) - .Build("station", "forge", "dockyard-hull"); - var uninterruptedForge = new AetheriaDaemonLoadoutGenerator(catalog, 101, 0, homes, adjacency); - var interleavedForge = new AetheriaDaemonLoadoutGenerator(catalog, 101, 0, homes, adjacency); - var foreignStream = new AetheriaDaemonLoadoutGenerator(catalog, 202, 1, homes, adjacency); - var uninterruptedSequence = new[] - { - uninterruptedForge.Build("ship", "forge"), - uninterruptedForge.Build("ship", "forge") - }; - var interleavedFirst = interleavedForge.Build("ship", "forge"); - foreignStream.Build("ship", "foreign"); - var interleavedSecond = interleavedForge.Build("ship", "forge"); - var rarityGenerator = new AetheriaDaemonLoadoutGenerator( - rarityCatalog, 9001, 0, homes, adjacency); - var rareSelections = Enumerable.Range(0, 2000) - .Select(_ => rarityGenerator.Build("ship", "forge").HullItemKey) - .Count(key => key == "rare-hull"); - - RequireEqual("available-hull", first.HullItemKey, - "loadout generation must exclude manufacturers outside the faction allegiance graph"); - Require(first.Equipment.Any(value => value.ItemKey == "cockpit") && - first.Equipment.All(value => value.ItemKey != "cheap-turret-controller"), - "ship control hardpoints must require the cockpit role even when the wrong controller is cheaper"); - Require(first.Equipment.Any(value => value.ItemKey == "cannon") && - first.CargoBays.Any(value => value.ItemKey == "cargo-bay") && - first.Equipment.Any(value => value.ItemKey == "capacitor") && - first.Equipment.Any(value => value.ItemKey == "ship-sensor") && - first.Equipment.All(value => value.ItemKey != "station-sensor"), - "generated ships must fit hardpoint equipment and reject station-sized sensor gear"); - RequireEqual("Clockwise", first.Equipment.Single(value => value.ItemKey == "cannon").Rotation, - "generated equipment must preserve the placement rotation used by hardpoint fitting"); - Require(first.HullItemKey == second.HullItemKey && - first.Equipment.Select(value => value.ItemKey).SequenceEqual(second.Equipment.Select(value => value.ItemKey)), - "same seed, map, faction and catalog must produce the same loadout"); - Require(first.Cargo.Length == 0 && first.Receipt.Selections.All(value => value.Role != "cargo"), - "ordinary generated ships must not acquire cargo from non-canonical scenario aliases"); - Require(first.Receipt.Seed == 42 && first.Receipt.SourceZoneIndex == 0 && - first.Receipt.AvailabilityFactionKey == "forge" && - first.Receipt.Selections.Any(value => value.Role == "hull" && value.ItemKey == "available-hull" && - value.ManufacturerKey == "forge" && value.ManufacturerDistance == 1 && value.Allegiance == 1), - "generated loadouts must carry daemon-authored source and selection provenance"); - Require(rareSelections > 0 && rareSelections < 200, - $"rare catalog candidates must remain possible without becoming a default loadout path (selected {rareSelections}/2000)"); - Require(station.DockingBays.Any(value => value.ItemKey == "docking-bay") && - station.CargoBays.Any(value => value.ItemKey == "cargo-bay") && - station.Equipment.Any(value => value.ItemKey == "capacitor") && - station.Equipment.Any(value => value.ItemKey == "cheap-turret-controller") && - station.Equipment.Any(value => value.ItemKey == "station-sensor") && - station.Equipment.All(value => value.ItemKey != "ship-sensor"), - "station generation must fill a large sensor hardpoint with the largest available compatible array"); - Require(fallbackStation.Equipment.Any(value => value.ItemKey == "ship-sensor"), - "a smaller sensor must remain a valid fallback when it fits inside a larger same-type hardpoint"); - Require(fallbackStation.DockingBays.Single().ItemKey == "docking-bay", - "a station without dedicated docking mounts must retain the canonical non-dedicated free-space bay path"); - RequireEqual("dockyard-hull", dockyard.HullItemKey, - "an authored station variant must be selectable without bypassing hull generation"); - RequireEqual(12, dockyard.DockingBays.Length, - "the dockyard hull must fill every authored docking hardpoint through normal loadout generation"); - Require(dockyard.DockingBays.All(value => value.ItemKey == "dockyard-bay"), - "dedicated docking hardpoints must only accept the matching docking-bay equipment type"); - Require(station.Cargo.Length > 0 && station.Cargo.Length <= cargo.InteriorOccupiedCells && - station.Cargo.All(value => value.Item.ItemKey != "cheap-foreign-hull" && - catalog.FindItem(value.Item.ItemKey) != null) && - station.Receipt.Selections.Where(value => value.Role == "cargo") - .Select(value => value.ItemKey) - .SequenceEqual(station.Cargo.Select(value => value.Item.ItemKey)), - "station inventory draws must use canonical catalog keys, respect cargo capacity, exclude unavailable manufacturers, and preserve exact provenance"); - Require(LoadoutKeys(uninterruptedSequence[0]).SequenceEqual(LoadoutKeys(interleavedFirst)) && - LoadoutKeys(uninterruptedSequence[1]).SequenceEqual(LoadoutKeys(interleavedSecond)), - "generation in another faction stream must not perturb this faction's continuing loadout sequence"); - } - - private static IEnumerable LoadoutKeys(AetheriaDaemonLoadout loadout) => - new[] { loadout.HullItemKey } - .Concat(loadout.Equipment.Select(value => value.ItemKey)) - .Concat(loadout.CargoBays.Select(value => value.ItemKey)) - .Concat(loadout.DockingBays.Select(value => value.ItemKey)) - .Concat(loadout.Cargo.Select(value => value.Item.ItemKey)); - - private static AetheriaRuntimeBehaviorValue PerformanceStat(double value) => new( - "performance-stat", - "", - 0, - false, - "", - "", - [Number(value), Number(value), Number(0), Number(0), Number(0)], - Array.Empty()); - - private static AetheriaRuntimeBehaviorValue Number(double value) => new( - "number", - "", - value, - false, - "", - "", - Array.Empty(), - Array.Empty()); - - private static AetheriaRuntimeBehaviorValue StringValue(string value) => new( - "string", value, 0, false, "", "", - Array.Empty(), - Array.Empty()); - - private static AetheriaRuntimeBehaviorValue BoolValue(bool value) => new( - "bool", "", 0, value, "", "", - Array.Empty(), - Array.Empty()); - - private static AetheriaRuntimeBehaviorValue ItemKeyValue(string value) => new( - "item-key", "", 0, false, "", value, - Array.Empty(), - Array.Empty()); - - private static AetheriaRuntimeBehaviorValue StatReference(string target, string stat) => new( - "stat-reference", "", 0, false, "", "", - [StringValue(""), StringValue(target), StringValue(stat)], - Array.Empty()); - - private static AetheriaRuntimeBehaviorValue Vector(params double[] values) => new( - "float3", - "", - 0, - false, - "", - "", - values.Select(Number).ToArray(), - Array.Empty()); - - private static AetheriaRuntimeBehaviorValue Curve(params AetheriaRuntimeCurveKey[] keys) => new( - "bezier-curve", - "", - 0, - false, - "", - "", - [new AetheriaRuntimeBehaviorValue( - "array", - "", - 0, - false, - "", - "", - keys.Select(key => Vector(key.Time, key.Value, key.InTangent, key.OutTangent)).ToArray(), - Array.Empty())], - Array.Empty()); - - private static AetheriaRuntimeBehaviorPayload AetherDrivePayload( - double couplingEfficiency = 1, - double energyDraw = 0) => new( - 0, - AetheriaRuntimeBehaviorKinds.AetherDrive, - 0, - [ - new AetheriaRuntimeBehaviorField(1, Vector(100, 100, 100)), - new AetheriaRuntimeBehaviorField(2, Vector(10, 10, 0.1)), - new AetheriaRuntimeBehaviorField(3, PerformanceStat(100)), - new AetheriaRuntimeBehaviorField(4, Vector(10, 10, 10)), - new AetheriaRuntimeBehaviorField(5, PerformanceStat(1)), - new AetheriaRuntimeBehaviorField(6, PerformanceStat(couplingEfficiency)), - new AetheriaRuntimeBehaviorField(7, PerformanceStat(10000)), - new AetheriaRuntimeBehaviorField(8, Curve( - new AetheriaRuntimeCurveKey(0, 1, 0, 0), - new AetheriaRuntimeCurveKey(1, 1, 0, 0))), - new AetheriaRuntimeBehaviorField(9, PerformanceStat(energyDraw)), - new AetheriaRuntimeBehaviorField(10, PerformanceStat(0)) - ]); - - private static AetheriaRuntimeBehaviorPayload ThrusterPayload() => new( - 0, - AetheriaRuntimeBehaviorKinds.Thruster, - 0, - [ - new AetheriaRuntimeBehaviorField(1, PerformanceStat(90)), - new AetheriaRuntimeBehaviorField(2, PerformanceStat(2)), - new AetheriaRuntimeBehaviorField(3, PerformanceStat(1)), - new AetheriaRuntimeBehaviorField(4, PerformanceStat(0)) - ]); - - private static AetheriaRuntimeCatalogSnapshot EquipThrusterBank( - IEnumerable entities, - params AetheriaRuntimeCatalogItem[] additionalItems) - { - var hull = HullCatalogItem("smoke-standard-thruster-hull", 5, 5, 0); - hull.HullDrag = 1; - var thruster = CatalogItem("smoke-directional-thruster", ThrusterPayload()); - var placements = new[] - { - (0, 2, "None"), (4, 2, "None"), - (0, 1, "Half"), (4, 1, "Half"), - (2, 0, "CounterClockwise"), (2, 4, "CounterClockwise"), - (1, 0, "Clockwise"), (1, 4, "Clockwise") - }; - foreach (var entity in entities.Where(entity => entity.Kind == "ship")) - { - if (string.IsNullOrWhiteSpace(entity.HullItemKey)) - entity.HullItemKey = hull.ItemKey; - entity.Equipment = (entity.Equipment ?? Array.Empty()) - .Concat(placements.Select(value => new AetheriaRuntimeLoadoutItemSlotCommit - { - X = value.Item1, - Y = value.Item2, - Rotation = value.Item3, - Item = new AetheriaRuntimeLoadoutItemCommit - { - ItemKey = thruster.ItemKey, - Quantity = 1, - Durability = 1, - Enabled = true - } - })) - .ToArray(); - } - return new AetheriaRuntimeCatalogSnapshot( - additionalItems.Concat([hull, thruster]).ToArray(), [], []); - } - - private static void DaemonNativeDockyardMaterializesTypedCatalog() - { - Require(AetheriaDocumentRegistry.DocumentTypes.Contains( - typeof(GameCult.Eve.PluginFields.EveFieldsSplatsDocument)), - "Aetheria's public registry must include every typed Eve Fields document the daemon persists"); - const string zenithId = "82efc0a5-1ba5-4ff3-a281-b2e6e247521d"; - const string mediumBayId = "3e930a2c-ac72-4385-98aa-1c5b0b90db46"; - var root = Path.Combine(Path.GetTempPath(), $"aetheria-dockyard-catalog-{Guid.NewGuid():N}"); - var statePath = Path.Combine(root, "aetheria.cc"); - Directory.CreateDirectory(root); - try - { - using var node = AetheriaStateNode.OpenAsync(statePath).GetAwaiter().GetResult(); - node.MutableDocument(AetheriaCatalogKeys.ItemDefinitionFromLegacyId(zenithId)) - .ReplaceAsync(new AetheriaItemDefinition - { - Name = "Zenith", - LegacyId = zenithId, - Category = AetheriaRuntimeItemCategories.Hull, - HullType = "Station", - HardpointType = "Hull", - ManufacturerLegacyId = "11111111-1111-1111-1111-111111111111", - Price = 100, - Mass = 100, - ShapeWidth = 12, - ShapeHeight = 12, - ShapeCells = Enumerable.Range(0, 12).SelectMany(y => Enumerable.Range(0, 12) - .Select(x => new AetheriaShapeCell { X = x, Y = y })).ToArray(), - HullPrefab = "Assets/Prefabs/Zenith.prefab", - HullArmor = 4, - HullDrag = 2 - }).GetAwaiter().GetResult(); - node.MutableDocument(AetheriaCatalogKeys.ItemDefinitionFromLegacyId(mediumBayId)) - .ReplaceAsync(new AetheriaItemDefinition - { - Name = "Medium Docking Bay", - LegacyId = mediumBayId, - Category = AetheriaRuntimeItemCategories.DockingBay, - HardpointType = "Tool", - ManufacturerLegacyId = "11111111-1111-1111-1111-111111111111", - Price = 20, - ShapeWidth = 4, - ShapeHeight = 4, - OccupiedCells = 16, - ShapeCells = Enumerable.Range(0, 4).SelectMany(y => Enumerable.Range(0, 4) - .Select(x => new AetheriaShapeCell { X = x, Y = y })).ToArray(), - DockingMaxSizeX = 12, - DockingMaxSizeY = 12 - }).GetAwaiter().GetResult(); - - Require(AetheriaDaemonNativeCatalog.EnsureAsync(node).GetAwaiter().GetResult(), - "daemon-native catalog must report its first materialization"); - node.FlushAsync().GetAwaiter().GetResult(); - node.RefreshRuntimeCatalogAsync().GetAwaiter().GetResult(); - Require(!AetheriaDaemonNativeCatalog.EnsureAsync(node).GetAwaiter().GetResult(), - "daemon-native catalog must not rewrite an unchanged generated revision"); - var catalog = node.RuntimeCatalog().Latest(); - var hull = catalog.FindItem(AetheriaDaemonNativeCatalog.DockyardHullItemKey); - var bay = catalog.FindItem(AetheriaDaemonNativeCatalog.DockyardBayItemKey); - Require(hull != null && hull.HullType == "Station" && hull.ShapeWidth == 24 && hull.ShapeHeight == 24, - "daemon-native station content must materialize as a distinct typed hull definition"); - RequireEqual(12, hull!.Hardpoints.Count(hardpoint => hardpoint.Type == "DockingBay"), - "dockyard capacity must be authored by twelve docking hardpoints on the hull"); - Require(bay != null && bay.Category == AetheriaRuntimeItemCategories.DockingBay && - bay.HardpointType == "DockingBay" && bay.DockingMaxSizeX == 12 && bay.DockingMaxSizeY == 12, - "dockyard berths must remain typed docking-bay equipment with the source bay's ship-size contract"); - - var dockyardEntity = Entity(0, 0, "neutral"); - dockyardEntity.Kind = "station"; - dockyardEntity.HullItemKey = hull.ItemKey; - var zenithItemKey = $"aetheria.item_definition:legacy:{zenithId}"; - var zenithAssetKey = AetheriaRuntimeAssets.HullPrefabAssetKey(zenithItemKey); - RequireEqual(zenithAssetKey, AetheriaRuntimeAssets.ResolveEntityPrefabAssetRef(dockyardEntity, catalog), - "daemon-native hull variants must explicitly reuse their provider-owned presentation hull asset"); - var manifest = AetheriaRuntimeAssets.ProjectManifest(catalog); - Require(manifest.Assets.Any(asset => asset.Ref.AssetKey == zenithAssetKey), - "provider manifest must advertise the canonical Zenith presentation asset used by the dockyard"); - Require(!manifest.Assets.Any(asset => asset.Ref.AssetKey == - AetheriaRuntimeAssets.HullPrefabAssetKey(hull.ItemKey)), - "provider manifest must not invent an unbaked prefab for a hull with an explicit presentation alias"); - } - finally - { - if (Directory.Exists(root)) Directory.Delete(root, recursive: true); - } - } - - private static AetheriaRuntimeCatalogSnapshot EquipAetherDrive( - IEnumerable entities, - params AetheriaRuntimeCatalogItem[] additionalItems) - { - var hull = HullCatalogItem("smoke-rare-aether-drive-hull", 3, 3, 0); - hull.Hardpoints = - [ - new AetheriaRuntimeHardpoint( - "AetherDrive", 1, 1, 1, 1, 1, - [new AetheriaRuntimeShapeCell(0, 0)], "", "None", 0) - ]; - var drive = CatalogItem("smoke-aether-drive", AetherDrivePayload()); - drive.HardpointType = "AetherDrive"; - foreach (var entity in entities.Where(entity => entity.Kind == "ship")) - { - if (string.IsNullOrWhiteSpace(entity.HullItemKey)) - entity.HullItemKey = hull.ItemKey; - entity.Equipment = (entity.Equipment ?? Array.Empty()) - .Concat([new AetheriaRuntimeLoadoutItemSlotCommit - { - X = 1, - Y = 1, - Rotation = "None", - Item = new AetheriaRuntimeLoadoutItemCommit - { - ItemKey = drive.ItemKey, - Quantity = 1, - Durability = 1, - Enabled = true - } - }]) - .ToArray(); - } - return new AetheriaRuntimeCatalogSnapshot( - additionalItems.Concat([hull, drive]).ToArray(), [], []); - } - - private static AetheriaRuntimeBehaviorPayload CapacitorPayload(double capacity, double efficiency) => new( - 0, "Capacitor", 0, - [ - new AetheriaRuntimeBehaviorField(1, PerformanceStat(capacity)), - new AetheriaRuntimeBehaviorField(2, PerformanceStat(efficiency)) - ]); - - private static AetheriaRuntimeBehaviorPayload SensorPayload( - double sensitivity = 1, - double pingBoost = 1, - double pingEnergy = 0, - double pingVisibility = 3, - double pingRange = 200, - double pingCooldown = 1, - double pingDuration = 2, - double pingRadiusExponent = 0.5) => new( - 0, AetheriaRuntimeBehaviorKinds.Sensor, 0, - [ - new AetheriaRuntimeBehaviorField(3, PerformanceStat(sensitivity)), - new AetheriaRuntimeBehaviorField(4, Curve( - new AetheriaRuntimeCurveKey(0, 1, 0, 0), - new AetheriaRuntimeCurveKey(1, 1, 0, 0))), - new AetheriaRuntimeBehaviorField(5, PerformanceStat(pingBoost)), - new AetheriaRuntimeBehaviorField(6, PerformanceStat(pingEnergy)), - new AetheriaRuntimeBehaviorField(7, PerformanceStat(pingVisibility)), - new AetheriaRuntimeBehaviorField(8, PerformanceStat(pingRange)), - new AetheriaRuntimeBehaviorField(9, PerformanceStat(pingCooldown)), - new AetheriaRuntimeBehaviorField(10, Number(pingDuration)), - new AetheriaRuntimeBehaviorField(11, Number(pingRadiusExponent)) - ]); - - private static AetheriaRuntimeCatalogItem CatalogItem(string itemKey, params AetheriaRuntimeBehaviorPayload[] payloads) => new( - itemKey, itemKey, "equipment", "", "", 0, 1, 1, 1, 1, - 1, 1, 1, Array.Empty(), - 0, 0, 0, Array.Empty(), - Array.Empty(), payloads, - "utility", "", payloads.Select(payload => payload.Kind).ToArray(), - 1, false, 0, 1, "", "", "", "", "", - 0, 1000, Array.Empty(), "", 1, 0, 0, 0, false, - 0, 0, "", Array.Empty(), Array.Empty(), "", ""); - - private static AetheriaRuntimeCatalogItem WearableWeaponCatalogItem( - string itemKey, params AetheriaRuntimeBehaviorPayload[] payloads) - { - var item = CatalogItem(itemKey, payloads); - item.Durability = 1; - item.ThermalResilience = 1; - item.MinimumTemperature = 0; - item.MaximumTemperature = 1; - item.ThermalPerformanceCurveKeys = - [ - new AetheriaRuntimeCurveKey(0, 0.9, 0, 0), - new AetheriaRuntimeCurveKey(1, 0.9, 0, 0) - ]; - return item; - } - - private static AetheriaRuntimeCatalogItem HullCatalogItem(string itemKey, int width, int height, double armor) - { - var cells = Enumerable.Range(0, height) - .SelectMany(y => Enumerable.Range(0, width).Select(x => new AetheriaRuntimeShapeCell(x, y))) - .ToArray(); - return new AetheriaRuntimeCatalogItem( - itemKey, itemKey, "hull", "", "", 0, 1, 1, 1, 1, - width, height, cells.Length, cells, - width, height, cells.Length, cells, - Array.Empty(), Array.Empty(), - "", "ship", Array.Empty(), - 1, false, 0, 1, "", "", "", "", "", - 0, 1000, Array.Empty(), "", 1, 0, armor, 0, false, - 0, 0, "", Array.Empty(), Array.Empty(), "", ""); - } - - private static AetheriaRuntimeDaemonSimulationSettings LootSettings(double probability) - { - var value = AetheriaRuntimeDaemonSimulationSettings.AetheriaDefault; - return new AetheriaRuntimeDaemonSimulationSettings( - value.PawnSpeed, value.RaiderSpeed, value.AttackRange, value.AttackHoldRatio, - value.PawnProjectileDamage, value.RaiderProjectileDamage, value.WeaponCooldownSeconds, - value.ProjectileSpeed, value.ProjectileRadius, value.ProjectileLifetimeSeconds, - value.ProjectileSpawnOffset, value.ProjectileHeatScale, value.HeatDissipationPerSecond, - value.StationSensorRange, value.EntitySensorRange, value.PlayerStationHull, - value.HostileStationHull, value.PlayerEntityHull, value.RaiderEntityHull, - value.StationShield, value.EntityShield, value.WeaponLockSpeed, - value.WeaponLockSensorImpact, value.WeaponLockAngleDegrees, - value.WeaponLockDirectionImpact, value.WeaponLockDecayPerSecond, - probability, value.LootDropVelocity, value.PickupLifetimeSeconds); - } - - private static void AgentPatrolsHistoricalOrbitCircuitThroughMovementCommands() - { - var agent = Entity(0, 0, "workers"); - agent.AgentTaskCapabilities = [AetheriaRuntimeAgentTaskTypes.Patrol]; - var catalog = EquipThrusterBank([agent]); - var run = new AetheriaRuntimeRunCheckpointCommit - { - RunId = "agent-patrol-smoke", - CurrentZoneIndex = 0, - CurrentEntityKey = "", - Zones = - [ - new AetheriaRuntimeZoneSnapshotCommit - { - ZoneIndex = 0, - Entities = [agent], - Orbits = - [ - new AetheriaRuntimeOrbitSnapshotCommit { OrbitKey = "orbit:east", FixedPositionX = 20 }, - new AetheriaRuntimeOrbitSnapshotCommit { OrbitKey = "orbit:west", FixedPositionX = -20 } - ], - Bodies = - [ - new AetheriaRuntimeBodySnapshotCommit { BodyKey = "body:east", OrbitKey = "orbit:east", Kind = "planet" }, - new AetheriaRuntimeBodySnapshotCommit { BodyKey = "body:west", OrbitKey = "orbit:west", Kind = "planet" } - ] - } - ] - }; - var issue = AetheriaRuntimeDaemonCommandDocument.Create( - AetheriaRuntimeDaemonCommandKinds.IssueAgentTask, - "commander-smoke", - "starbridge-smoke", - 0, - ""); - issue.CommandId = "issue-patrol"; - issue.AgentTask = new AetheriaRuntimeAgentTaskCommand - { - TaskId = "patrol-orbits", - CorporationKey = "workers", - TaskType = AetheriaRuntimeAgentTaskTypes.Patrol, - Priority = 10, - ZoneIndex = 0, - CompletionRadius = 5, - TargetOrbitKeys = ["orbit:east", "orbit:west"] - }; - var sawWestLeg = false; - var sawReturnToEast = false; - var physics = NewPhysics(); - for (var frame = 0; frame < 100; frame++) - { - var tick = AetheriaRuntimeDaemonTickRunner.Tick( - Path.Combine(Path.GetTempPath(), "aetheria-agent-patrol-smoke.cc"), - run, - new AetheriaRuntimeDaemonTickOptions - { - WorldPhysics = physics, - Catalog = catalog, - FrameId = frame, - FixedDeltaSeconds = 0.1, - SimulationTimeSeconds = frame * 0.1, - ObservedCommands = frame == 0 ? [issue] : Array.Empty(), - BuildPublications = false - }); - Require(tick.OperationResult.AppliedCommandIds.Any(id => id.EndsWith(":move", StringComparison.Ordinal)), - "patrol controller must drive the shared movement command every tick"); - var cursor = run.AgentTasks.Single().CircuitIndex; - sawWestLeg |= cursor == 1; - sawReturnToEast |= sawWestLeg && cursor == 0; - if (sawReturnToEast) - break; - } - - Require(sawWestLeg && sawReturnToEast, "patrol must advance through and wrap its authored orbit circuit"); - RequireEqual(AetheriaRuntimeAgentTaskStatuses.Assigned, run.AgentTasks.Single().Status, - "patrol is persistent work and must remain assigned after one circuit"); - } - - private static void SecurityZonesOwnFossilTrespasserHostility() - { - var guard = Entity(0, 0, "owners"); - guard.SecurityLevel = 2; - guard.SecurityRadius = 100; - var visitor = Entity(1, 10, "visitors"); - guard.Contacts = [new AetheriaRuntimeEntityContactCommit { TargetEntityIndex = 1, InfoGathered = 1 }]; - visitor.Contacts = [new AetheriaRuntimeEntityContactCommit { TargetEntityIndex = 0, InfoGathered = 1 }]; - var zone = new AetheriaRuntimeZoneSnapshotCommit - { - ZoneIndex = 0, - OwnerFactionIndex = 0, - Entities = [guard, visitor] - }; - var run = new AetheriaRuntimeRunCheckpointCommit - { - CurrentEntityKey = "zone.0.entity.1", - FactionRelationships = - [ - new AetheriaRuntimeFactionRelationshipCommit - { - FactionKey = "owners", Relationship = "Neutral" - } - ], - Zones = [zone] - }; - var catalog = new AetheriaRuntimeCatalogSnapshot([], [new AetheriaRuntimeCorporation( - "owners", "Owners", "Owners", "", "", "", 4, 0, - Array.Empty())], []); - - AetheriaRuntimeSensorSimulation.StepZone( - run, zone, [guard, visitor], null, catalog, 0.1, 0, 0.5, 1); - Require(guard.Contacts.Single().Hostile && visitor.Contacts.Single().Hostile, - "a neutral visitor inside an owner critical-security field must be mutually hostile"); - - visitor.PositionX = 1000; - AetheriaRuntimeSensorSimulation.StepZone( - run, zone, [guard, visitor], null, catalog, 0.1, 0, 0.5, 2); - Require(!guard.Contacts.Single().Hostile && !visitor.Contacts.Single().Hostile, - "the same neutral visitor outside security fields must cease being hostile"); - } - - private static void PatrolCombatPreemptsAndResumesTheSameCircuit() - { - var patrol = Entity(0, 0, "owners"); - patrol.AgentTaskCapabilities = [AetheriaRuntimeAgentTaskTypes.Patrol]; - patrol.WeaponGroups = [new[] { 0 }]; - patrol.Equipment = - [ - new AetheriaRuntimeLoadoutItemSlotCommit - { - Item = new AetheriaRuntimeLoadoutItemCommit - { - ItemKey = "patrol-cannon", Quality = 1, Durability = 1, Enabled = true - } - } - ]; - patrol.Contacts = - [ - new AetheriaRuntimeEntityContactCommit - { - TargetEntityIndex = 1, InfoGathered = 1, Visible = true, Hostile = true - } - ]; - var hostile = Entity(1, 0, "visitors"); - hostile.PositionZ = 50; - var weapon = new AetheriaRuntimeBehaviorPayload(0, AetheriaRuntimeBehaviorKinds.InstantWeapon, 0, - [ - new AetheriaRuntimeBehaviorField(2, PerformanceStat(10)), - new AetheriaRuntimeBehaviorField(5, PerformanceStat(0)), - new AetheriaRuntimeBehaviorField(6, PerformanceStat(150)), - new AetheriaRuntimeBehaviorField(16, PerformanceStat(200)), - new AetheriaRuntimeBehaviorField(19, PerformanceStat(0.5)) - ]); - var catalog = new AetheriaRuntimeCatalogSnapshot( - [WearableWeaponCatalogItem("patrol-cannon", weapon)], [], []); - var task = new AetheriaRuntimeAgentTaskCommit - { - TaskId = "patrol-combat", - CorporationKey = "owners", - TaskType = AetheriaRuntimeAgentTaskTypes.Patrol, - Status = AetheriaRuntimeAgentTaskStatuses.Assigned, - ZoneIndex = 0, - AssignedEntityIndex = 0, - CompletionRadius = 5, - TargetOrbitKeys = ["orbit:east", "orbit:west"], - CircuitIndex = 1 - }; - patrol.AssignedAgentTaskId = task.TaskId; - var run = new AetheriaRuntimeRunCheckpointCommit - { - RunId = "patrol-combat-smoke", - CurrentEntityKey = "", - AgentTasks = [task], - Zones = - [ - new AetheriaRuntimeZoneSnapshotCommit - { - ZoneIndex = 0, - Entities = [patrol, hostile], - Orbits = - [ - new AetheriaRuntimeOrbitSnapshotCommit { OrbitKey = "orbit:east", FixedPositionX = 20 }, - new AetheriaRuntimeOrbitSnapshotCommit { OrbitKey = "orbit:west", FixedPositionX = -20 } - ] - } - ] - }; - - var combatCommands = AetheriaRuntimeAgentScheduler.AssignAndPlan( - run, 1, catalog, 0.1, AetheriaRuntimeDaemonSimulationSettings.AetheriaDefault); - Require(combatCommands.Any(command => - command.Kind == AetheriaRuntimeDaemonCommandKinds.SetTarget && - AetheriaRuntimeRunCheckpointCommit.TryParseEntityKey( - command.TargetEntityKey, out var zoneIndex, out var entityIndex) && - zoneIndex == 0 && entityIndex == 1), - "a visible hostile ship must pre-empt patrol movement through the shared target command"); - Require(combatCommands.Any(command => command.Kind == AetheriaRuntimeDaemonCommandKinds.FireWeaponGroup), - "an aligned in-range patrol agent must use the shared weapon command"); - RequireEqual(1, task.CircuitIndex, - "opportunistic combat must not replace or advance the patrol circuit"); - - patrol.Contacts = Array.Empty(); - var resumed = AetheriaRuntimeAgentScheduler.AssignAndPlan( - run, 2, catalog, 0.2, AetheriaRuntimeDaemonSimulationSettings.AetheriaDefault); - Require(resumed.Any(command => command.Kind == AetheriaRuntimeDaemonCommandKinds.SetMoveVector), - "loss of hostile visibility must resume the same patrol circuit"); - RequireEqual(AetheriaRuntimeAgentTaskStatuses.Assigned, task.Status, - "opportunistic combat must never complete or fail the patrol task"); - } - - private static void RejectedHaulTransferDoesNotAdvanceTask() - { - var origin = Entity(0, 0, "workers"); - origin.CargoContents = [Cargo(("ore", 2, 0, 0))]; - var agent = Entity(1, 0, "workers"); - agent.AgentTaskCapabilities = [AetheriaRuntimeAgentTaskTypes.Haul]; - agent.CargoContents = Array.Empty(); - var destination = Entity(2, 20, "workers"); - destination.CargoContents = [Cargo()]; - var task = new AetheriaRuntimeAgentTaskCommit - { - TaskId = "rejected-haul", - CorporationKey = "workers", - TaskType = AetheriaRuntimeAgentTaskTypes.Haul, - Priority = 1, - ZoneIndex = 0, - OriginEntityIndex = 0, - TargetEntityIndex = 2, - ItemKey = "ore", - RequestedQuantity = 1, - CompletionRadius = 5, - Phase = "pickup" - }; - var run = new AetheriaRuntimeRunCheckpointCommit - { - RunId = "rejected-haul-smoke", - Zones = [new AetheriaRuntimeZoneSnapshotCommit { ZoneIndex = 0, Entities = [origin, agent, destination] }], - AgentTasks = [task] - }; - - var planned = AetheriaRuntimeAgentScheduler.AssignAndPlan(run, 1); - var reduced = AetheriaRuntimeDaemonOperations.Execute(run, planned); - AetheriaRuntimeAgentScheduler.Reconcile(run, 1, reduced.AppliedCommandIds, reduced.RejectedCommandIds); - - Require(reduced.RejectedCommandIds.Any(id => id.EndsWith(":pickup", StringComparison.Ordinal)), - "invalid pickup must be rejected by the normal cargo reducer"); - RequireEqual("pickup", task.Phase, "rejected pickup must not advance the haul task"); - RequireEqual(0, task.PendingQuantity, "rejected pickup must clear pending transfer state"); - RequireEqual(2, CargoQuantity(origin, "ore"), "rejected pickup must leave origin cargo untouched"); - } - - private static void AgentCompletesHaulTaskThroughMovementAndCargoCommands() - { - var cargoBay = CatalogItem("haul-cargo-bay"); - cargoBay.Category = AetheriaRuntimeItemCategories.CargoBay; - cargoBay.ShapeWidth = 1; - cargoBay.ShapeHeight = 1; - cargoBay.ShapeCells = [new AetheriaRuntimeShapeCell(0, 0)]; - cargoBay.InteriorShapeWidth = 4; - cargoBay.InteriorShapeHeight = 4; - cargoBay.InteriorOccupiedCells = 16; - cargoBay.InteriorShapeCells = Enumerable.Range(0, 4) - .SelectMany(y => Enumerable.Range(0, 4).Select(x => new AetheriaRuntimeShapeCell(x, y))) - .ToArray(); - var ore = CatalogItem("ore"); - ore.Category = AetheriaRuntimeItemCategories.SimpleCommodity; - ore.ShapeWidth = 1; - ore.ShapeHeight = 1; - ore.ShapeCells = [new AetheriaRuntimeShapeCell(0, 0)]; - ore.Stackable = true; - ore.MaxStack = 100; - var catalog = new AetheriaRuntimeCatalogSnapshot([cargoBay, ore], [], []); - AetheriaRuntimeLoadoutItemSlotCommit InstalledCargoBay() => new() - { - Item = new AetheriaRuntimeLoadoutItemCommit { ItemKey = cargoBay.ItemKey, Quantity = 1 } - }; - var origin = Entity(0, 0, "workers"); - origin.Kind = "station"; - origin.CargoBays = [InstalledCargoBay()]; - origin.CargoContents = [Cargo(("ore", 5, 2, 3))]; - var agent = Entity(1, 0, "workers"); - agent.AgentTaskCapabilities = [AetheriaRuntimeAgentTaskTypes.Haul]; - agent.CargoBays = [InstalledCargoBay()]; - agent.CargoContents = [Cargo()]; - var destination = Entity(2, 50, "workers"); - destination.Kind = "station"; - destination.CargoBays = [InstalledCargoBay()]; - destination.CargoContents = [Cargo()]; - var run = new AetheriaRuntimeRunCheckpointCommit - { - RunId = "agent-haul-smoke", - CurrentZoneIndex = 0, - CurrentEntityKey = "", - Zones = [new AetheriaRuntimeZoneSnapshotCommit { ZoneIndex = 0, Entities = [origin, agent, destination] }] - }; - var issue = AetheriaRuntimeDaemonCommandDocument.Create( - AetheriaRuntimeDaemonCommandKinds.IssueAgentTask, - "commander-smoke", - "starbridge-smoke", - 0, - ""); - issue.CommandId = "issue-haul"; - issue.AgentTask = new AetheriaRuntimeAgentTaskCommand - { - TaskId = "haul-ore", - CorporationKey = "workers", - TaskType = AetheriaRuntimeAgentTaskTypes.Haul, - Priority = 40, - ZoneIndex = 0, - OriginEntityIndex = 0, - TargetEntityIndex = 2, - ItemKey = "ore", - Quantity = 3, - CompletionRadius = 5 - }; - var sawPickup = false; - var sawDelivery = false; - for (var frame = 0; frame < 30; frame++) - { - var tick = AetheriaRuntimeDaemonTickRunner.Tick( - Path.Combine(Path.GetTempPath(), "aetheria-agent-haul-smoke.cc"), - run, - new AetheriaRuntimeDaemonTickOptions - { - WorldPhysics = NewPhysics(), - FrameId = frame, - FixedDeltaSeconds = 0.1, - SimulationTimeSeconds = frame * 0.1, - ObservedCommands = frame == 0 ? [issue] : Array.Empty(), - Catalog = catalog, - BuildPublications = false - }); - sawPickup |= tick.OperationResult.AppliedCommandIds.Any(id => id.EndsWith(":pickup", StringComparison.Ordinal)); - sawDelivery |= tick.OperationResult.AppliedCommandIds.Any(id => id.EndsWith(":delivery", StringComparison.Ordinal)); - if (string.Equals(run.AgentTasks.Single().Status, AetheriaRuntimeAgentTaskStatuses.Completed, StringComparison.Ordinal)) - break; - } - - Require(sawPickup && sawDelivery, "haul task must use accepted pickup and delivery cargo commands"); - RequireEqual(AetheriaRuntimeAgentTaskStatuses.Completed, run.AgentTasks.Single().Status, - "haul task must complete only after accepted delivery"); - RequireEqual(2, CargoQuantity(origin, "ore"), "origin must retain the unrequested stack quantity"); - RequireEqual(3, CargoQuantity(destination, "ore"), "destination must receive exactly the requested quantity"); - RequireEqual(0, CargoQuantity(agent, "ore"), "hauler must finish with no in-transit cargo"); - } - - private static AetheriaRuntimeCargoBayLoadoutCommit Cargo(params (string ItemKey, int Quantity, int X, int Y)[] items) => new() - { - Items = items.Select(item => new AetheriaRuntimeLoadoutItemSlotCommit - { - X = item.X, - Y = item.Y, - Item = new AetheriaRuntimeLoadoutItemCommit { ItemKey = item.ItemKey, Quantity = item.Quantity } - }).ToArray() - }; - - private static int CargoQuantity(AetheriaRuntimeEntitySnapshotCommit entity, string itemKey) => - (entity.CargoContents ?? Array.Empty()) - .SelectMany(bay => bay.Items) - .Where(slot => string.Equals(slot.Item.ItemKey, itemKey, StringComparison.Ordinal)) - .Sum(slot => slot.Item.Quantity); - - private static void AgentCompletesAttackTaskThroughTargetFireAndYmir() - { - var agent = Entity(0, 0, "player"); - agent.AgentTaskCapabilities = [AetheriaRuntimeAgentTaskTypes.Attack]; - agent.WeaponGroups = [new[] { 0 }]; - agent.Equipment = - [ - new AetheriaRuntimeLoadoutItemSlotCommit - { - Item = new AetheriaRuntimeLoadoutItemCommit - { - ItemKey = "test-lock-cannon", Quality = 1, Durability = 1, Enabled = true - } - }, - new AetheriaRuntimeLoadoutItemSlotCommit { Item = new AetheriaRuntimeLoadoutItemCommit - { ItemKey = "test-attack-capacitor", Quality = 1, Durability = 1, Enabled = true } }, - new AetheriaRuntimeLoadoutItemSlotCommit { Item = new AetheriaRuntimeLoadoutItemCommit - { ItemKey = "test-attack-sensor", Quality = 1, Durability = 1, Enabled = true } } - ]; - agent.BehaviorStates = - [ - new AetheriaRuntimeBehaviorStateCommit - { - OwnerKind = AetheriaRuntimeBehaviorStateProjector.EquipmentOwnerKind, OwnerIndex = 1, BehaviorIndex = 0, - BehaviorKind = "Capacitor", CapacitorCharge = 50, CapacitorCapacity = 50, CapacitorEfficiency = 1 - } - ]; - agent.CargoContents = [Cargo(("test-ammo", 2, 0, 0))]; - var weaponPayload = new AetheriaRuntimeBehaviorPayload( - 0, - "LockWeapon", - 0, - [ - new AetheriaRuntimeBehaviorField(1, Number(1)), - new AetheriaRuntimeBehaviorField(2, PerformanceStat(7)), - new AetheriaRuntimeBehaviorField(3, PerformanceStat(1.5)), - new AetheriaRuntimeBehaviorField(4, PerformanceStat(2)), - new AetheriaRuntimeBehaviorField(6, PerformanceStat(145)), - new AetheriaRuntimeBehaviorField(9, PerformanceStat(5)), - new AetheriaRuntimeBehaviorField(10, PerformanceStat(3)), - new AetheriaRuntimeBehaviorField(12, new AetheriaRuntimeBehaviorValue( - "item-key", "", 0, false, "", "test-ammo", - Array.Empty(), Array.Empty())), - new AetheriaRuntimeBehaviorField(13, Number(3)), - new AetheriaRuntimeBehaviorField(14, Number(0.3)), - new AetheriaRuntimeBehaviorField(16, PerformanceStat(330)), - new AetheriaRuntimeBehaviorField(17, PerformanceStat(3)), - new AetheriaRuntimeBehaviorField(18, PerformanceStat(0.3)), - new AetheriaRuntimeBehaviorField(19, PerformanceStat(0.2)), - new AetheriaRuntimeBehaviorField(21, PerformanceStat(2)), - new AetheriaRuntimeBehaviorField(22, PerformanceStat(1)), - new AetheriaRuntimeBehaviorField(23, PerformanceStat(45)), - new AetheriaRuntimeBehaviorField(24, PerformanceStat(1)), - new AetheriaRuntimeBehaviorField(25, PerformanceStat(1)) - ]); - var catalog = EquipThrusterBank( - [agent], - CatalogItem("test-lock-cannon", weaponPayload), - CatalogItem("test-attack-capacitor", CapacitorPayload(50, 1)), - CatalogItem("test-attack-sensor", SensorPayload())); - var target = Entity(1, 105, "raider"); - target.Kind = "station"; - target.Visibility = 10; - target.StatGrids = [Grid("hull", 14), Grid("shield", 0), Grid("heat", 0)]; - var run = new AetheriaRuntimeRunCheckpointCommit - { - RunId = "agent-attack-smoke", - CurrentZoneIndex = 0, - CurrentEntityKey = "", - Zones = [new AetheriaRuntimeZoneSnapshotCommit { ZoneIndex = 0, Entities = [agent, target] }] - }; - var issue = AetheriaRuntimeDaemonCommandDocument.Create( - AetheriaRuntimeDaemonCommandKinds.IssueAgentTask, - "commander-smoke", - "starbridge-smoke", - 0, - ""); - issue.CommandId = "issue-attack"; - issue.AgentTask = new AetheriaRuntimeAgentTaskCommand - { - TaskId = "attack-raider", - CorporationKey = "player", - TaskType = AetheriaRuntimeAgentTaskTypes.Attack, - Priority = 100, - ZoneIndex = 0, - TargetEntityIndex = 1, - CompletionRadius = 25, - WeaponGroup = 0 - }; - var sawTargetCommand = false; - var sawFireCommand = false; - var sawPartialLock = false; - var lockTrace = new List(); - var attackTrace = new List(); - var firstLaunchFrame = -1; - var physics = NewPhysics(); - for (var frame = 0; frame < 60; frame++) - { - var tick = AetheriaRuntimeDaemonTickRunner.Tick( - Path.Combine(Path.GetTempPath(), "aetheria-agent-attack-smoke.cc"), - run, - new AetheriaRuntimeDaemonTickOptions - { - WorldPhysics = physics, - FrameId = frame, - FixedDeltaSeconds = 0.1, - SimulationTimeSeconds = frame * 0.1, - ObservedCommands = frame == 0 ? [issue] : Array.Empty(), - Catalog = catalog, - BuildPublications = false - }); - sawTargetCommand |= tick.OperationResult.AppliedCommandIds.Any(id => id.EndsWith(":target", StringComparison.Ordinal)); - sawFireCommand |= tick.OperationResult.AppliedCommandIds.Any(id => id.Contains(":fire-", StringComparison.Ordinal)); - var weapon = (agent.WeaponStates ?? Array.Empty()) - .Single(value => value.OwnerKind == AetheriaRuntimeBehaviorStateProjector.EquipmentOwnerKind); - lockTrace.Add(weapon.LockProgress); - attackTrace.Add($"{frame}:{agent.TargetEntityIndex}:{agent.PositionX:0.##},{agent.PositionZ:0.##}:{agent.DirectionX:0.##},{agent.DirectionY:0.##}:{weapon.LockTargetEntityIndex}"); - sawPartialLock |= weapon.LockProgress > 0 && weapon.LockProgress < 0.99; - if (firstLaunchFrame < 0 && run.GameEvents.Any(value => value.Kind == "shot.committed")) - firstLaunchFrame = frame; - if (string.Equals(run.AgentTasks.Single().Status, AetheriaRuntimeAgentTaskStatuses.Completed, StringComparison.Ordinal)) - break; - } - - Require(sawTargetCommand, "attack agent must target through the shared target command"); - Require(sawFireCommand, "attack agent must fire through the shared weapon-group command"); - Require(sawPartialLock, - $"attack agent must acquire a persisted partial weapon lock before firing; locks={string.Join(',', lockTrace.Select(value => value.ToString("0.###", CultureInfo.InvariantCulture)))} states={string.Join('|', attackTrace)}"); - Require(firstLaunchFrame > 1, "accepted fire intent must not bypass progressive weapon lock acquisition"); - Require(run.GameEvents.Any(value => value.Kind == "shot.committed" && value.SourceEntityIndex == agent.EntityIndex), - "accepted fire control must emit authoritative shot commitment chronology"); - Require(run.GameEvents.Any(value => value.Kind == "shot.committed" && - value.ItemKey == "test-lock-cannon" && Math.Abs(value.ScalarValue - (7.0 / 3.0)) < 0.000001), - "daemon combat must divide authored damage across the configured burst rounds"); - Require((agent.WeaponStates ?? Array.Empty()).Any(value => value.OwnerKind == AetheriaRuntimeBehaviorStateProjector.EquipmentOwnerKind && - value.OwnerIndex == 0 && value.BehaviorKind == "LockWeapon"), - "weapon progress must belong to the equipped authored behavior instead of a synthetic entity weapon"); - var authoredWeapon = agent.WeaponStates.Single(value => - value.OwnerKind == AetheriaRuntimeBehaviorStateProjector.EquipmentOwnerKind); - RequireEqual(0, authoredWeapon.Ammo, - "two three-round authored bursts must consume both complete magazines"); - RequireNear(40, agent.BehaviorStates.Single(value => value.BehaviorKind == "Capacitor").CapacitorCharge, - 0.000001, "two authored bursts must drain exactly their combined energy from canonical capacitor state"); - RequireEqual(1, CargoQuantity(agent, "test-ammo"), - "one empty magazine must consume exactly one reserve cargo commodity before reload"); - Require(run.GameEvents.Count(value => value.Kind == "shot.committed" && value.ItemKey == "test-lock-cannon") >= 6, - "two authored bursts must commit all six due rounds through the shot resolver"); - Require(run.ShotReceipts.Count(value => value.WeaponItemKey == "test-lock-cannon") >= 6 && - run.ShotReceipts.All(value => value.Hit && value.HullAppliedDamage >= 0 && - value.HullAppliedDamage <= value.NominalDamage && value.ShieldAbsorbedDamage == 0 && - value.DamageType == "Corrosive" && Math.Abs(value.Penetration - 1.5) < 0.000001 && - Math.Abs(value.DamageSpread - 2) < 0.000001), - "authored rounds must retain damage type, penetration, spread, and exact damage receipts before presentation travel"); - Require(run.ShotReceipts.Select(value => value.ShotId).Distinct(StringComparer.Ordinal).Count() == - run.ShotReceipts.Count, - "every burst round must own a stable independent shot identity"); - Require(run.GameEvents.Count(value => value.Kind == "weapon.reload.started") == 1 && - run.GameEvents.Count(value => value.Kind == "weapon.reload.completed") == 1, - "empty magazine transition must publish one authoritative reload start and completion pair"); - var reloadStarted = run.GameEvents.Single(value => value.Kind == "weapon.reload.started").FrameId; - var reloadCompleted = run.GameEvents.Single(value => value.Kind == "weapon.reload.completed").FrameId; - Require(reloadCompleted > reloadStarted && !run.GameEvents.Any(value => - value.Kind == "shot.committed" && value.FrameId > reloadStarted && value.FrameId < reloadCompleted), - "reload interval must be a shot-free authoritative timeline, not a cosmetic client delay"); - Require(!target.IsActive, - $"attack task must end through daemon shot resolution before presentation projectile contacts; hull={Stat(target, "hull"):0.###} " + - $"agent={agent.PositionX:0.###},{agent.PositionZ:0.###} target={target.PositionX:0.###},{target.PositionZ:0.###} " + - $"projectiles={run.Zones[0].PhysicalPayloads.Count} task={run.AgentTasks.Single().Status}"); - RequireEqual(AetheriaRuntimeAgentTaskStatuses.Completed, run.AgentTasks.Single().Status, - "attack task must complete when its target dies"); - var shotSurface = AetheriaRuntimeDaemonGameSurfaceBuilder.BuildReactiveGameplay( - new AetheriaRuntimeDaemonFrameDocument { FrameId = 60, Run = run }); - Require(Flatten(shotSurface.Surface.Root).Any(node => node.Kind == "shot.receipt" && - node.Props["itemKey"] == "test-lock-cannon" && node.Props["outcome"] == "hit" && - node.Props["presentationKind"] == "bolt" && - node.Props.ContainsKey("presentationIntensity") && - node.Props.ContainsKey("impactKind")), - "Eve must project inspectable deterministic shot receipts without reconstructing combat"); - } - - private static void AttackTaskAdmissionRejectsImpossibleWork() - { - var agent = Entity(0, 0, "player"); - agent.AgentTaskCapabilities = [AetheriaRuntimeAgentTaskTypes.Attack]; - agent.WeaponGroups = [new[] { 0 }]; - var hostile = Entity(1, 100, "raider"); - var friendly = Entity(2, 100, "player"); - var run = new AetheriaRuntimeRunCheckpointCommit - { - RunId = "attack-admission-smoke", - CurrentEntityKey = "", - Zones = [new AetheriaRuntimeZoneSnapshotCommit { ZoneIndex = 0, Entities = [agent, hostile, friendly] }] - }; - - AetheriaRuntimeDaemonCommandDocument Command(string id, int targetIndex, int weaponGroup) - { - var command = AetheriaRuntimeDaemonCommandDocument.Create( - AetheriaRuntimeDaemonCommandKinds.IssueAgentTask, "commander-smoke", "starbridge-smoke", 0, ""); - command.CommandId = id; - command.AgentTask = new AetheriaRuntimeAgentTaskCommand - { - TaskId = id, - CorporationKey = "player", - TaskType = AetheriaRuntimeAgentTaskTypes.Attack, - ZoneIndex = 0, - TargetEntityIndex = targetIndex, - WeaponGroup = weaponGroup - }; - return command; - } - - var result = AetheriaRuntimeDaemonOperations.Execute(run, - [ - Command("missing-target", 99, 0), - Command("friendly-target", 2, 0), - Command("missing-group", 1, 1), - Command("valid-attack", 1, 0) - ]); - - Require(result.RejectedCommandIds.SequenceEqual(new[] { "missing-target", "friendly-target", "missing-group" }), - "attack admission must reject missing/friendly targets and unavailable weapon groups"); - Require(result.AppliedCommandIds.SequenceEqual(new[] { "valid-attack" }), - "attack admission must accept executable hostile work"); - Require(run.AgentTasks.Count == 1 && run.AgentTasks[0].TaskId == "valid-attack", - "rejected attack orders must not enter the durable corporation queue"); - } - - private static void AttackAgentControlsOptimumRangeThroughMovementLever() - { - var closing = AttackPlanAtDistance(250, facingTarget: false); - var closingMove = closing.Commands.Single(command => command.Kind == AetheriaRuntimeDaemonCommandKinds.SetMoveVector); - Require(closingMove.DirectionX > 0.5 && closingMove.DirectionY < -0.5 && closingMove.ScalarValue > 0.99, - "loadout-derived long-range attack must blend closing motion with the fossil target-relative strafe"); - - var retreating = AttackPlanAtDistance(50, facingTarget: false); - var retreatingMove = retreating.Commands.Single(command => command.Kind == AetheriaRuntimeDaemonCommandKinds.SetMoveVector); - Require(retreatingMove.DirectionX < -0.5 && retreatingMove.DirectionY < -0.5, - "loadout-derived short-range attack must blend opening motion with the fossil target-relative strafe"); - - var broadside = AttackPlanAtDistance(150, facingTarget: false); - Require(!broadside.Commands.Any(command => command.Kind == AetheriaRuntimeDaemonCommandKinds.FireWeaponGroup), - "an attack agent must not fire a selected group through a hardpoint facing away from its aim"); - - var firing = AttackPlanAtDistance(150, facingTarget: true, targetVelocityZ: 8); - RequireEqual(1, firing.Task.WeaponGroup, - "agent combat must select the highest current range-DPS group from operational equipment"); - var fire = firing.Commands.Single(command => command.Kind == AetheriaRuntimeDaemonCommandKinds.FireWeaponGroup); - RequireEqual(1, fire.WeaponGroup, - "the daemon must fire the derived group instead of the task's stale authored group"); - var look = firing.Commands.Single(command => command.Kind == AetheriaRuntimeDaemonCommandKinds.SetLookDirection); - Require(look.DirectionX > 0.9 && look.PositionZ > 0.05, - "projectile agent aim must lead target velocity through CultMath first-order intercept semantics"); - } - - private static (IReadOnlyList Commands, AetheriaRuntimeAgentTaskCommit Task) - AttackPlanAtDistance(double distance, bool facingTarget, double targetVelocityZ = 0) - { - var agent = Entity(0, 0, "workers"); - agent.DirectionX = facingTarget ? 1 : 0; - agent.DirectionY = facingTarget ? 0 : 1; - agent.AgentTaskCapabilities = [AetheriaRuntimeAgentTaskTypes.Attack]; - agent.Equipment = - [ - new AetheriaRuntimeLoadoutItemSlotCommit - { - Rotation = "None", - Item = new AetheriaRuntimeLoadoutItemCommit - { ItemKey = "agent-short-weapon", Quality = 1, Durability = 1, Enabled = true } - }, - new AetheriaRuntimeLoadoutItemSlotCommit - { - Rotation = "None", - Item = new AetheriaRuntimeLoadoutItemCommit - { ItemKey = "agent-long-weapon", Quality = 1, Durability = 1, Enabled = true } - } - ]; - agent.WeaponGroups = [new[] { 0 }, new[] { 1 }]; - var target = Entity(1, distance, "raiders"); - target.DirectionY = 1; - target.VelocityY = targetVelocityZ; - var task = new AetheriaRuntimeAgentTaskCommit - { - TaskId = "range-control", - CorporationKey = "workers", - TaskType = AetheriaRuntimeAgentTaskTypes.Attack, - ZoneIndex = 0, - TargetEntityIndex = 1, - WeaponGroup = 0 - }; - AetheriaRuntimeBehaviorPayload Weapon(double damage, double minimumRange, double range, double velocity) => - new(0, AetheriaRuntimeBehaviorKinds.InstantWeapon, 0, - [ - new AetheriaRuntimeBehaviorField(2, PerformanceStat(damage)), - new AetheriaRuntimeBehaviorField(5, PerformanceStat(minimumRange)), - new AetheriaRuntimeBehaviorField(6, PerformanceStat(range)), - new AetheriaRuntimeBehaviorField(16, PerformanceStat(velocity)), - new AetheriaRuntimeBehaviorField(17, PerformanceStat(1)), - new AetheriaRuntimeBehaviorField(19, PerformanceStat(1)) - ]); - var catalog = new AetheriaRuntimeCatalogSnapshot( - [ - CatalogItem("agent-short-weapon", Weapon(10, 5, 100, 80)), - CatalogItem("agent-long-weapon", Weapon(40, 25, 200, 100)) - ], [], []); - var run = new AetheriaRuntimeRunCheckpointCommit - { - GenerationSeed = 123, - RunId = "agent-range-control-smoke", - Zones = [new AetheriaRuntimeZoneSnapshotCommit { ZoneIndex = 0, Entities = [agent, target] }], - AgentTasks = [task] - }; - - return (AetheriaRuntimeAgentScheduler.AssignAndPlan(run, 1, catalog), task); - } - - private static void SchedulerAssignsHighestPriorityCompatibleTask() - { - var agent = Entity(0, 0, "workers"); - agent.AgentTaskCapabilities = [AetheriaRuntimeAgentTaskTypes.Explore]; - var low = AgentTask("low", 1); - var high = AgentTask("high", 99); - var run = new AetheriaRuntimeRunCheckpointCommit - { - RunId = "agent-priority-smoke", - Zones = [new AetheriaRuntimeZoneSnapshotCommit { ZoneIndex = 0, Entities = [agent] }], - AgentTasks = [low, high] - }; - - AetheriaRuntimeAgentScheduler.AssignAndPlan(run, 4); - - RequireEqual(AetheriaRuntimeAgentTaskStatuses.Assigned, high.Status, - "highest priority compatible task must claim the available agent"); - RequireEqual(AetheriaRuntimeAgentTaskStatuses.Queued, low.Status, - "lower priority task must remain queued when no controller remains"); - RequireEqual("high", agent.AssignedAgentTaskId, "agent assignment must point at the selected task"); - } - - private static void SchedulerRequeuesTaskFromDeadAgent() - { - var dead = Entity(0, 0, "workers"); - dead.IsActive = false; - dead.AssignedAgentTaskId = "recover-work"; - var replacement = Entity(1, 0, "workers"); - replacement.AgentTaskCapabilities = [AetheriaRuntimeAgentTaskTypes.Explore]; - var task = new AetheriaRuntimeAgentTaskCommit - { - TaskId = "recover-work", - CorporationKey = "workers", - TaskType = AetheriaRuntimeAgentTaskTypes.Explore, - ZoneIndex = 0, - Status = AetheriaRuntimeAgentTaskStatuses.Assigned, - AssignedEntityIndex = 0, - TargetPositionX = 100, - DeliveredQuantity = 3, - RequestedQuantity = 5 - }; - var run = new AetheriaRuntimeRunCheckpointCommit - { - RunId = "agent-recovery-smoke", - Zones = [new AetheriaRuntimeZoneSnapshotCommit { ZoneIndex = 0, Entities = [dead, replacement] }], - AgentTasks = [task] - }; - - AetheriaRuntimeAgentScheduler.AssignAndPlan(run, 8); - - RequireEqual(AetheriaRuntimeAgentTaskStatuses.Assigned, task.Status, - "work abandoned by an inactive agent must return to the corporation queue and be reassigned"); - RequireEqual(replacement.EntityIndex, task.AssignedEntityIndex, - "compatible active replacement must receive abandoned work"); - RequireEqual(3, task.DeliveredQuantity, - "reassignment must preserve authoritative task progress"); - Require(string.IsNullOrWhiteSpace(dead.AssignedAgentTaskId), - "inactive agent must not retain reservation authority"); - } - - private static void SchedulerCollapsesDuplicateAssignmentMarkers() - { - var first = Entity(0, 0, "workers"); - first.AssignedAgentTaskId = "single-owner"; - var duplicate = Entity(1, 0, "workers"); - duplicate.AssignedAgentTaskId = "single-owner"; - duplicate.HelmForward = 1; - duplicate.TargetEntityIndex = 7; - var task = AgentTask("single-owner", 1); - task.Status = AetheriaRuntimeAgentTaskStatuses.Assigned; - var run = new AetheriaRuntimeRunCheckpointCommit - { - RunId = "agent-single-owner-smoke", - Zones = [new AetheriaRuntimeZoneSnapshotCommit { ZoneIndex = 0, Entities = [first, duplicate] }], - AgentTasks = [task] - }; - - AetheriaRuntimeAgentScheduler.AssignAndPlan(run, 9); - - RequireEqual(1, run.Zones.SelectMany(zone => zone.Entities) - .Count(entity => string.Equals(entity.AssignedAgentTaskId, task.TaskId, StringComparison.Ordinal)), - "one task must have exactly one active carrier after reconciliation"); - RequireEqual(first.EntityIndex, task.AssignedEntityIndex, - "duplicate assignment reconciliation must be deterministic"); - RequireNear(0, duplicate.HelmForward, 0.000001, - "duplicate assignment reconciliation must release the losing scheduler controls"); - RequireEqual(-1, duplicate.TargetEntityIndex, - "duplicate assignment reconciliation must release the losing scheduler target"); - } - - private static void CancellingAgentTaskReleasesSchedulerControls() - { - var agent = Entity(0, 0, "workers"); - agent.AssignedAgentTaskId = "tow-station"; - agent.HelmStrafe = 0.75; - agent.HelmForward = 1; - agent.TargetEntityIndex = 2; - agent.ChildEntityIndices = [2]; - var station = Entity(2, 0, "workers"); - station.Kind = "station"; - var task = new AetheriaRuntimeAgentTaskCommit - { - TaskId = "tow-station", - CorporationKey = "workers", - TaskType = AetheriaRuntimeAgentTaskTypes.Tow, - Status = AetheriaRuntimeAgentTaskStatuses.Assigned, - AssignedEntityIndex = 0, - TargetEntityIndex = 2, - PendingQuantity = 4, - Phase = "delivery" - }; - var run = new AetheriaRuntimeRunCheckpointCommit - { - RunId = "agent-cancel-smoke", - CurrentZoneIndex = 0, - CurrentEntityKey = "", - AgentTasks = [task], - Zones = - [ - new AetheriaRuntimeZoneSnapshotCommit - { - ZoneIndex = 0, - Entities = [agent, Entity(1, 0, "workers"), station] - } - ] - }; - var cancel = AetheriaRuntimeDaemonCommandDocument.Create( - AetheriaRuntimeDaemonCommandKinds.CancelAgentTask, - "commander-smoke", - "starbridge-smoke", - 12, - ""); - cancel.CommandId = "cancel-tow-station"; - cancel.AgentTask = new AetheriaRuntimeAgentTaskCommand { TaskId = task.TaskId }; - - var result = AetheriaRuntimeDaemonOperations.Execute( - run, - [cancel], - new AetheriaRuntimeDaemonOperationContext()); - - Require(result.AppliedCommandIds.Contains(cancel.CommandId, StringComparer.Ordinal), - "task cancellation must commit through the daemon command boundary"); - RequireEqual(AetheriaRuntimeAgentTaskStatuses.Cancelled, task.Status, - "cancelled task must become terminal immediately"); - RequireEqual(AetheriaRuntimeAgentTaskStatuses.Cancelled, task.Phase, - "cancelled task must not retain a counterfeit in-progress phase"); - RequireEqual(-1, task.AssignedEntityIndex, - "cancelled task must release its assigned agent identity"); - RequireEqual(0, task.PendingQuantity, - "cancelled transfer work must not retain pending quantity"); - RequireEqual("", agent.AssignedAgentTaskId, - "cancelled agent must release the durable assignment marker"); - RequireNear(0, agent.HelmStrafe, 0.000001, - "cancelled agent must release scheduler-owned strafe input"); - RequireNear(0, agent.HelmForward, 0.000001, - "cancelled agent must release scheduler-owned forward input"); - RequireEqual(-1, agent.TargetEntityIndex, - "cancelled agent must release scheduler-owned targeting"); - Require(!agent.ChildEntityIndices.Contains(station.EntityIndex), - "cancelling an active tow must release the station at its authoritative world position"); - RequireEqual(0, AetheriaRuntimeAgentScheduler.AssignAndPlan(run, 13).Count, - "cancelled work must not produce another scheduler command on the next frame"); - } - - private static AetheriaRuntimeAgentTaskCommit AgentTask(string id, int priority) => new() - { - TaskId = id, - CorporationKey = "workers", - TaskType = AetheriaRuntimeAgentTaskTypes.Explore, - Priority = priority, - ZoneIndex = 0, - TargetPositionX = 100, - CompletionRadius = 5 - }; - - private static void SchedulerAssignsShortestGalaxyRoute() - { - var distant = Entity(0, 0, "workers"); - distant.EntityId = "worker.route.distant"; - distant.AgentTaskCapabilities = [AetheriaRuntimeAgentTaskTypes.Explore]; - var nearby = Entity(0, 0, "workers"); - nearby.EntityId = "worker.route.nearby"; - nearby.AgentTaskCapabilities = [AetheriaRuntimeAgentTaskTypes.Explore]; - var task = AgentTask("route-choice", 10); - task.ZoneIndex = 2; - var run = new AetheriaRuntimeRunCheckpointCommit - { - RunId = "agent-route-choice-smoke", - Zones = - [ - new AetheriaRuntimeZoneSnapshotCommit { ZoneIndex = 0, PositionX = 0, GravityTerrainRadius = 100, AdjacentZoneIndices = [1], Entities = [distant] }, - new AetheriaRuntimeZoneSnapshotCommit { ZoneIndex = 1, PositionX = 100, GravityTerrainRadius = 100, AdjacentZoneIndices = [0, 2], Entities = Array.Empty() }, - new AetheriaRuntimeZoneSnapshotCommit { ZoneIndex = 2, PositionX = 200, GravityTerrainRadius = 100, AdjacentZoneIndices = [1, 3], Entities = Array.Empty() }, - new AetheriaRuntimeZoneSnapshotCommit { ZoneIndex = 3, PositionX = 300, GravityTerrainRadius = 100, AdjacentZoneIndices = [2], Entities = [nearby] } - ], - AgentTasks = [task] - }; - - AetheriaRuntimeAgentScheduler.AssignAndPlan(run, 1); - - RequireEqual("route-choice", nearby.AssignedAgentTaskId, - "scheduler must reserve the controller with the shortest galaxy route to the task zone"); - Require(string.IsNullOrWhiteSpace(distant.AssignedAgentTaskId), - "longer-route controller must remain available"); - } - - private static void AgentTraversesGalaxyRouteBeforeExecutingTask() - { - var root = Path.Combine(Path.GetTempPath(), "aetheria-agent-route-restart-" + Guid.NewGuid().ToString("N")); - Directory.CreateDirectory(root); - var statePath = Path.Combine(root, "world.cc"); - try - { - var agent = Entity(0, 0, "workers"); - agent.EntityId = "worker.cross-zone.stable"; - agent.AgentTaskCapabilities = [AetheriaRuntimeAgentTaskTypes.Explore]; - var catalog = EquipThrusterBank([agent]); - var task = AgentTask("cross-zone", 10); - task.ZoneIndex = 2; - task.TargetPositionX = 0; - var run = new AetheriaRuntimeRunCheckpointCommit - { - RunId = "agent-route-travel-smoke", - Zones = - [ - new AetheriaRuntimeZoneSnapshotCommit { ZoneIndex = 0, PositionX = 0, GravityTerrainRadius = 100, AdjacentZoneIndices = [1], Entities = [agent] }, - new AetheriaRuntimeZoneSnapshotCommit { ZoneIndex = 1, PositionX = 100, GravityTerrainRadius = 100, AdjacentZoneIndices = [0, 2], Entities = Array.Empty() }, - new AetheriaRuntimeZoneSnapshotCommit { ZoneIndex = 2, PositionX = 200, GravityTerrainRadius = 100, AdjacentZoneIndices = [1], Entities = Array.Empty() } - ], - AgentTasks = [task] - }; - var appliedTravelCommands = 0; - var appliedApproachCommands = 0; - var restarted = false; - var physics = NewPhysics(); - for (var frameId = 0; frameId < 400; frameId++) - { - var tick = AetheriaRuntimeDaemonTickRunner.Tick( - statePath, - run, - new AetheriaRuntimeDaemonTickOptions - { - WorldPhysics = physics, - Catalog = catalog, - FrameId = frameId, - FixedDeltaSeconds = 0.25, - SimulationTimeSeconds = frameId * 0.25, - BuildPublications = false - }); - appliedTravelCommands += tick.OperationResult.AppliedCommandIds.Count(id => id.EndsWith(":travel", StringComparison.Ordinal)); - appliedApproachCommands += tick.OperationResult.AppliedCommandIds.Count(id => id.EndsWith(":travel-approach", StringComparison.Ordinal)); - agent = run.Zones.SelectMany(zone => zone.Entities) - .Single(entity => entity.EntityId == "worker.cross-zone.stable"); - - if (!restarted && string.Equals(agent.WormholeTransition?.Phase, "entering", StringComparison.Ordinal)) - { - var eventCountsBefore = ( - Started: run.GameEvents.Count(value => value.Kind == "wormhole.enter.started"), - Transferred: run.GameEvents.Count(value => value.Kind == "wormhole.transferred"), - Completed: run.GameEvents.Count(value => value.Kind == "wormhole.exit.completed")); - var durableFrame = new AetheriaRuntimeDaemonFrameDocument - { - FrameId = frameId, - FixedDeltaSeconds = 0.25, - SimulationTimeSeconds = frameId * 0.25, - Run = run - }; - using (var node = AetheriaStateNode.OpenAsync(statePath).GetAwaiter().GetResult()) - { - node.MutableDocument( - AetheriaRuntimeVerseRecordKeys.DaemonFrameLatest) - .ReplaceAsync(durableFrame).GetAwaiter().GetResult(); - node.FlushAsync(soft: false).GetAwaiter().GetResult(); - } - using (var node = AetheriaStateNode.OpenAsync(statePath).GetAwaiter().GetResult()) - { - durableFrame = node.MutableDocument( - AetheriaRuntimeVerseRecordKeys.DaemonFrameLatest) - .ReadAsync().GetAwaiter().GetResult() - ?? throw new InvalidOperationException("Durable cross-zone agent frame is missing."); - } - - run = durableFrame.Run; - task = run.AgentTasks.Single(); - agent = run.Zones.SelectMany(zone => zone.Entities) - .Single(entity => entity.EntityId == "worker.cross-zone.stable"); - physics.Dispose(); - physics = NewPhysics(); - RequireEqual(AetheriaRuntimeAgentTaskStatuses.Assigned, task.Status, - "cross-zone restart must preserve the assigned task during wormhole entry"); - RequireEqual(task.TaskId, agent.AssignedAgentTaskId, - "cross-zone restart must preserve the task carrier edge"); - RequireEqual("entering", agent.WormholeTransition?.Phase ?? "", - "cross-zone restart must preserve the exact wormhole phase"); - Require(eventCountsBefore == ( - run.GameEvents.Count(value => value.Kind == "wormhole.enter.started"), - run.GameEvents.Count(value => value.Kind == "wormhole.transferred"), - run.GameEvents.Count(value => value.Kind == "wormhole.exit.completed")), - "CultCache reopen must not replay wormhole chronology before the next daemon tick"); - restarted = true; - } - - if (string.Equals(task.Status, AetheriaRuntimeAgentTaskStatuses.Completed, StringComparison.Ordinal)) - break; - } - - Require(restarted, - "cross-zone task proof must hard-restart while the worker is inside the wormhole timeline"); - Require(appliedApproachCommands > 0, - "agent must approach wormholes through shared movement commands and Ymir before transition"); - RequireEqual(2, appliedTravelCommands, - "agent must traverse each galaxy edge through the shared wormhole command boundary"); - RequireEqual(2, run.GameEvents.Count(value => value.Kind == "wormhole.enter.started"), - "two route edges must publish exactly two retained entry events across restart"); - RequireEqual(2, run.GameEvents.Count(value => value.Kind == "wormhole.transferred"), - "two route edges must publish exactly two retained transfer events across restart"); - RequireEqual(2, run.GameEvents.Count(value => value.Kind == "wormhole.exit.completed"), - "two route edges must publish exactly two retained completion events across restart"); - RequireEqual(1, run.Zones.Sum(zone => zone.Entities.Count), - "cross-zone restart must not clone the durable worker into another zone"); - Require(run.Zones.Single(zone => zone.ZoneIndex == 2).Entities.Count == 1, - "assigned agent must arrive in the task zone without a parallel teleport owner"); - RequireEqual("worker.cross-zone.stable", run.Zones.Single(zone => zone.ZoneIndex == 2).Entities.Single().EntityId, - "zone transfer must preserve stable entity identity while projection indices change"); - RequireEqual(AetheriaRuntimeAgentTaskStatuses.Completed, task.Status, - "agent must execute the task only after arriving in its destination zone"); - } - finally - { - if (Directory.Exists(root)) - Directory.Delete(root, recursive: true); - } - } - - private static void WormholeTraversalUsesPersistedEnterTransferExitPhases() - { - var ship = Entity(0, 75, "player"); - ship.EntityId = "wormhole-traveler"; - var run = new AetheriaRuntimeRunCheckpointCommit - { - RunId = "wormhole-transition-smoke", - GenerationSeed = 77, - CurrentZoneIndex = 0, - CurrentEntityKey = "zone.0.entity.0", - DiscoveredZoneIndices = [0], - Zones = - [ - new AetheriaRuntimeZoneSnapshotCommit - { - ZoneIndex = 0, PositionX = 0, GravityTerrainRadius = 100, - AdjacentZoneIndices = [1], Entities = [ship] - }, - new AetheriaRuntimeZoneSnapshotCommit - { - ZoneIndex = 1, PositionX = 100, GravityTerrainRadius = 100, - AdjacentZoneIndices = [0, 2], Entities = Array.Empty() - }, - new AetheriaRuntimeZoneSnapshotCommit - { - ZoneIndex = 2, PositionX = 200, GravityTerrainRadius = 100, - AdjacentZoneIndices = [1], Entities = Array.Empty() - } - ] - }; - AetheriaRuntimeDaemonCommandDocument Travel(string id) - { - var command = AetheriaRuntimeDaemonCommandDocument.Create( - AetheriaRuntimeDaemonCommandKinds.EnterWormhole, - "pilot", - id, - 0, - run.CurrentEntityKey); - command.CommandId = id; - command.TargetZoneIndex = 1; - return command; - } - var physics = NewPhysics(); - AetheriaRuntimeDaemonTickResult Tick(int frame, params AetheriaRuntimeDaemonCommandDocument[] commands) => - AetheriaRuntimeDaemonTickRunner.Tick( - Path.Combine(Path.GetTempPath(), "aetheria-wormhole-transition-smoke.cc"), - run, - new AetheriaRuntimeDaemonTickOptions - { - WorldPhysics = physics, - FrameId = frame, - FixedDeltaSeconds = 1, - SimulationTimeSeconds = frame + 1, - ObservedCommands = commands, - BuildPublications = false - }); - - var entering = Tick(0, Travel("wormhole-start"), Travel("wormhole-same-batch")); - Require(entering.OperationResult.RejectedCommandIds.Any(id => id.Contains("wormhole-same-batch", StringComparison.Ordinal)), - "one command batch must not admit two competing transition intents for the same entity"); - RequireEqual(0, run.CurrentZoneIndex, - "wormhole command acceptance must not splice the ship into the destination immediately"); - RequireEqual("entering", ship.WormholeTransition?.Phase ?? "", - "daemon must persist the entering phase on the authoritative entity"); - Require(Math.Abs((ship.WormholeTransition?.Progress ?? 0) - 0.25) < 0.0001, - "fossil four-second entry timing must advance from fixed daemon time"); - Require(Math.Abs((ship.WormholeTransition?.VisualDepthOffset ?? 0) + 250) < 0.0001, - "transition must expose the fossil depth lever without making the client own motion"); - var enteringView = AetheriaRuntimeGameDocuments.CurrentEntity(entering.Frame).WormholeTransition; - RequireEqual("entering", enteringView?.Phase ?? "", - "current-entity projection must expose the same transition state to Eve consumers"); - - run = MessagePack.MessagePackSerializer.Deserialize( - MessagePack.MessagePackSerializer.Serialize(run)); - ship = run.Zones.Single(zone => zone.ZoneIndex == 0).Entities.Single(); - physics.Dispose(); - physics = NewPhysics(); - RequireEqual("entering", ship.WormholeTransition?.Phase ?? "", - "daemon restart must retain the exact mid-entry phase instead of repairing it from client state"); - - var duplicate = Tick(1, Travel("wormhole-duplicate")); - Require(duplicate.OperationResult.RejectedCommandIds.Any(id => id.Contains("wormhole-duplicate", StringComparison.Ordinal)), - "an entity already inside a wormhole transition must reject a second traversal owner"); - Tick(2); - Tick(3); - RequireEqual(1, run.CurrentZoneIndex, - "zone ownership must transfer only after the entering phase completes"); - var arrived = run.Zones.Single(zone => zone.ZoneIndex == 1).Entities.Single(); - RequireEqual("exiting", arrived.WormholeTransition?.Phase ?? "", - "transferred ship must persist a distinct exiting phase"); - Require(run.DiscoveredZoneIndices.Contains(2), - "arrival must reveal the destination adjacency using the fossil discovery rule"); - - Tick(4); - Tick(5); - Tick(6); - Tick(7); - RequireEqual("completed", arrived.WormholeTransition?.Phase ?? "", - "the completion frame must preserve a presentation lever before normal physics resumes"); - Require(Math.Abs(Math.Sqrt(arrived.VelocityX * arrived.VelocityX + arrived.VelocityY * arrived.VelocityY) - 20) < 0.0001, - "completed exit must restore the fossil twenty-unit launch velocity"); - Require(Math.Abs(Math.Sqrt(Math.Pow(arrived.PositionX + 75, 2) + Math.Pow(arrived.PositionZ, 2)) - 50) < 0.001, - "completed exit must place the ship fifty units from the destination wormhole"); - Tick(8); - Require(arrived.WormholeTransition == null, - "the tick after completion must release input and Ymir instead of leaving a permanent transition flag"); - RequireEqual(1, run.GameEvents.Count(value => value.Kind == "wormhole.enter.started"), - "entry feedback must have stable exactly-once chronology"); - RequireEqual(1, run.GameEvents.Count(value => value.Kind == "wormhole.transferred"), - "transfer feedback must have stable exactly-once chronology"); - RequireEqual(1, run.GameEvents.Count(value => value.Kind == "wormhole.exit.completed"), - "exit feedback must have stable exactly-once chronology"); - } - - private static void IdleAgentReturnsToCanonicalHomeAndDocks() - { - var worker = Entity(0, 0, "workers"); - worker.EntityId = "worker.return-home"; - worker.HomeEntityId = "station.home"; - worker.AgentTaskCapabilities = [AetheriaRuntimeAgentTaskTypes.Haul]; - var home = Entity(0, 60, "workers"); - home.EntityId = "station.home"; - home.Kind = "station"; - home.DockingBays = [new AetheriaRuntimeLoadoutItemSlotCommit - { - Item = new AetheriaRuntimeLoadoutItemCommit { ItemKey = "station-docking-bay" } - }]; - home.DockingBayAssignments = [-1]; - home.DockingBayContents = [Cargo()]; - var catalog = EquipThrusterBank([worker]); - var run = new AetheriaRuntimeRunCheckpointCommit - { - RunId = "agent-return-home-smoke", - Zones = - [ - new AetheriaRuntimeZoneSnapshotCommit { ZoneIndex = 0, PositionX = 0, GravityTerrainRadius = 100, AdjacentZoneIndices = [1], Entities = [worker] }, - new AetheriaRuntimeZoneSnapshotCommit { ZoneIndex = 1, PositionX = 100, GravityTerrainRadius = 100, AdjacentZoneIndices = [0, 2], Entities = Array.Empty() }, - new AetheriaRuntimeZoneSnapshotCommit { ZoneIndex = 2, PositionX = 200, GravityTerrainRadius = 100, AdjacentZoneIndices = [1], Entities = [home] } - ] - }; - var sawApproach = false; - var travelCount = 0; - var sawDock = false; - var rejectedHomeCommands = new List(); - var minimumHomeDistance = double.PositiveInfinity; - var maximumHomeSpeed = 0.0; - var physics = NewPhysics(); - for (var frame = 0; frame < 500; frame++) - { - var tick = AetheriaRuntimeDaemonTickRunner.Tick( - Path.Combine(Path.GetTempPath(), "aetheria-agent-return-home-smoke.cc"), - run, - new AetheriaRuntimeDaemonTickOptions - { - WorldPhysics = physics, - Catalog = catalog, - FrameId = frame, - FixedDeltaSeconds = 0.25, - SimulationTimeSeconds = frame * 0.25, - BuildPublications = false - }); - sawApproach |= tick.OperationResult.AppliedCommandIds.Any(id => id.Contains(":home-approach", StringComparison.Ordinal)); - travelCount += tick.OperationResult.AppliedCommandIds.Count(id => id.EndsWith(":home-travel", StringComparison.Ordinal)); - sawDock |= tick.OperationResult.AppliedCommandIds.Any(id => id.EndsWith(":home-dock", StringComparison.Ordinal)); - rejectedHomeCommands.AddRange(tick.OperationResult.RejectedCommandIds.Where(id => id.Contains(":home-", StringComparison.Ordinal))); - var currentHomeZone = run.Zones.Single(zone => zone.ZoneIndex == 2); - var currentWorker = currentHomeZone.Entities.FirstOrDefault(entity => entity.EntityId == "worker.return-home"); - var currentHome = currentHomeZone.Entities.FirstOrDefault(entity => entity.EntityId == "station.home"); - if (currentWorker != null && currentHome != null) - { - minimumHomeDistance = Math.Min(minimumHomeDistance, Math.Sqrt( - Math.Pow(currentWorker.PositionX - currentHome.PositionX, 2) + - Math.Pow(currentWorker.PositionZ - currentHome.PositionZ, 2))); - maximumHomeSpeed = Math.Max(maximumHomeSpeed, Math.Sqrt( - currentWorker.VelocityX * currentWorker.VelocityX + - currentWorker.VelocityY * currentWorker.VelocityY)); - } - if (sawDock) - break; - } - - var homeZone = run.Zones.Single(zone => zone.ZoneIndex == 2); - var arrivedWorker = homeZone.Entities.Single(entity => entity.EntityId == "worker.return-home"); - var arrivedHome = homeZone.Entities.Single(entity => entity.EntityId == "station.home"); - Require(sawApproach, "idle worker must approach its route and home through shared movement commands"); - RequireEqual(2, travelCount, "idle worker must traverse the shortest route to its canonical home"); - Require(sawDock, - $"idle worker must dock through the daemon docking command; worker={arrivedWorker.PositionX:0.###},{arrivedWorker.PositionZ:0.###} home={arrivedHome.PositionX:0.###},{arrivedHome.PositionZ:0.###} minDistance={minimumHomeDistance:0.###} maxSpeed={maximumHomeSpeed:0.###} rejected={string.Join(",", rejectedHomeCommands)}"); - Require(arrivedHome.DockingBayAssignments.Contains(arrivedWorker.EntityIndex), - "home docking parentage must be the authoritative completion fact"); - - var settled = AetheriaRuntimeDaemonTickRunner.Tick( - Path.Combine(Path.GetTempPath(), "aetheria-agent-return-home-settled-smoke.cc"), - run, - new AetheriaRuntimeDaemonTickOptions - { - WorldPhysics = physics, Catalog = catalog, FrameId = 501, - FixedDeltaSeconds = 0.25, SimulationTimeSeconds = 125.25, BuildPublications = false - }); - Require(!settled.OperationResult.AppliedCommandIds.Any(id => id.Contains(":home-", StringComparison.Ordinal)), - "docked worker must not emit a perpetual homecoming repair loop"); - } - - private static void ControlledShipDoesNotReceiveAutonomousHelmCommands() - { - var controlled = Entity(0, 100, "workers"); - controlled.EntityId = "controlled-worker"; - controlled.HomeEntityId = "controlled-home"; - controlled.AgentTaskCapabilities = [AetheriaRuntimeAgentTaskTypes.Explore]; - var home = Entity(1, 0, "workers"); - home.EntityId = "controlled-home"; - home.Kind = "station"; - var task = AgentTask("controlled-task", 10); - var run = new AetheriaRuntimeRunCheckpointCommit - { - RunId = "controlled-agent-authority-smoke", - CurrentZoneIndex = 0, - CurrentEntityKey = "zone.0.entity.0", - Zones = [new AetheriaRuntimeZoneSnapshotCommit { ZoneIndex = 0, Entities = [controlled, home] }], - AgentTasks = [task] - }; - - var commands = AetheriaRuntimeAgentScheduler.AssignAndPlan(run, 1); - - RequireEqual(AetheriaRuntimeAgentTaskStatuses.Queued, task.Status, - "manual helm owner must not be conscripted into autonomous corporation work"); - Require(commands.Count == 0, - "controlled ship must emit no task or return-home commands without delegated helm authority"); - } - - private static void AgentClaimsAndCompletesExploreTaskThroughCommands() - { - var agent = Entity(0, 0, "workers"); - agent.AgentTaskCapabilities = [AetheriaRuntimeAgentTaskTypes.Explore]; - var catalog = EquipThrusterBank([agent]); - var run = new AetheriaRuntimeRunCheckpointCommit - { - RunId = "agent-task-smoke", - CurrentZoneIndex = 0, - CurrentEntityKey = "", - Zones = [new AetheriaRuntimeZoneSnapshotCommit { ZoneIndex = 0, Entities = [agent] }] - }; - var issue = AetheriaRuntimeDaemonCommandDocument.Create( - AetheriaRuntimeDaemonCommandKinds.IssueAgentTask, - "commander-smoke", - "starbridge-smoke", - 0, - ""); - issue.CommandId = "issue-explore"; - issue.AgentTask = new AetheriaRuntimeAgentTaskCommand - { - TaskId = "explore-east", - CorporationKey = "workers", - TaskType = AetheriaRuntimeAgentTaskTypes.Explore, - Priority = 50, - ZoneIndex = 0, - TargetPositionX = 50, - TargetPositionZ = 0, - CompletionRadius = 5 - }; - var completed = false; - var physics = NewPhysics(); - for (var frame = 0; frame <= 60; frame++) - { - var tick = AetheriaRuntimeDaemonTickRunner.Tick( - Path.Combine(Path.GetTempPath(), "aetheria-agent-task-smoke.cc"), - run, - new AetheriaRuntimeDaemonTickOptions - { - WorldPhysics = physics, - FrameId = frame, - FixedDeltaSeconds = 0.1, - SimulationTimeSeconds = frame * 0.1, - ObservedCommands = frame == 0 ? [issue] : Array.Empty(), - Catalog = catalog, - BuildPublications = false - }); - if (frame == 0) - { - Require(tick.OperationResult.AppliedCommandIds.Contains("issue-explore"), - "commander task command must enter the normal tick reducer"); - Require(tick.OperationResult.AppliedCommandIds.Any(id => id.Contains(":explore-east:0:move", StringComparison.Ordinal)), - "agent movement must return through the same command receipts"); - } - completed = string.Equals(run.AgentTasks.Single().Status, AetheriaRuntimeAgentTaskStatuses.Completed, StringComparison.Ordinal); - if (completed) - break; - } - - Require(completed, - $"agent must complete an explore task without direct position mutation; position={agent.PositionX:0.###},{agent.PositionZ:0.###} velocity={agent.VelocityX:0.###},{agent.VelocityY:0.###}"); - Require(string.IsNullOrWhiteSpace(agent.AssignedAgentTaskId), "completed task must release the agent"); - Require(agent.PositionX >= 45, "agent must reach the task through repeated movement commands"); - - var frameDocument = new AetheriaRuntimeDaemonFrameDocument { FrameId = 21, Run = run }; - var commander = AetheriaRuntimeDaemonGameSurfaceBuilder.BuildCommander( - frameDocument, - new AetheriaRuntimeDaemonHealthDocument(), - AetheriaRuntimeDaemonCommandBoundaryDocument.Create("agent-task-smoke")); - var taskNode = Flatten(commander.Surface.Root) - .Single(component => string.Equals(component.Id, "aetheria.starbridge.commander.tasks.explore_east", StringComparison.Ordinal)); - RequireEqual(AetheriaRuntimeAgentTaskStatuses.Completed, taskNode.Props["status"], - "commander Eve surface must publish authoritative task status"); - Require(commander.Commands.Any(command => string.Equals(command.Command, "aetheria.daemon.issue_agent_task", StringComparison.Ordinal)), - "commander Eve surface must advertise task issue command"); - var roster = Flatten(commander.Surface.Root) - .Single(component => string.Equals(component.Kind, "agent.roster", StringComparison.Ordinal)); - var worker = Flatten(roster) - .Single(component => string.Equals(component.Kind, "agent.item", StringComparison.Ordinal)); - Require(!string.IsNullOrWhiteSpace(worker.Props["entityId"]), - "commander Eve roster must expose stable provider-owned worker identity"); - Require(worker.Props["capabilities"].Split(',').Contains(AetheriaRuntimeAgentTaskTypes.Explore), - "commander Eve roster must expose provider-owned capability claims"); - RequireEqual("idle", worker.Props["status"], - "commander Eve roster must publish generic status for runtime-independent lowering"); - Require(worker.Props["badges"].Split(',').Contains(AetheriaRuntimeAgentTaskTypes.Explore), - "commander Eve roster must publish capability badges through the generic item contract"); - RequireEqual("", worker.Props["assignedTaskId"], - "completed worker assignment must be visible as released on the Eve roster"); - RequireEqual("false", worker.Props["controlled"], - "commander Eve roster must distinguish autonomous workers from manual helm authority"); - } - - private static void AgentTaskSurvivesCultCacheRestart() - { - var root = Path.Combine(Path.GetTempPath(), "aetheria-agent-restart-" + Guid.NewGuid().ToString("N")); - Directory.CreateDirectory(root); - var statePath = Path.Combine(root, "world.cc"); - try - { - var agent = Entity(0, 0, "workers"); - agent.AgentTaskCapabilities = [AetheriaRuntimeAgentTaskTypes.Explore]; - var catalog = EquipThrusterBank([agent]); - var run = new AetheriaRuntimeRunCheckpointCommit - { - RunId = "agent-task-restart-smoke", - CurrentZoneIndex = 0, - CurrentEntityKey = "", - Zones = [new AetheriaRuntimeZoneSnapshotCommit { ZoneIndex = 0, Entities = [agent] }] - }; - var issue = AetheriaRuntimeDaemonCommandDocument.Create( - AetheriaRuntimeDaemonCommandKinds.IssueAgentTask, - "commander-smoke", - run.RunId, - 0, - ""); - issue.CommandId = "issue-restart-explore"; - issue.AgentTask = new AetheriaRuntimeAgentTaskCommand - { - TaskId = "restart-explore-east", - CorporationKey = "workers", - TaskType = AetheriaRuntimeAgentTaskTypes.Explore, - Priority = 50, - ZoneIndex = 0, - TargetPositionX = 80, - TargetPositionZ = 0, - CompletionRadius = 5 - }; - - var physics = NewPhysics(); - for (var frameId = 0; frameId <= 8; frameId++) - AetheriaRuntimeDaemonTickRunner.Tick( - statePath, - run, - new AetheriaRuntimeDaemonTickOptions - { - WorldPhysics = physics, - FrameId = frameId, - FixedDeltaSeconds = 0.1, - SimulationTimeSeconds = frameId * 0.1, - ObservedCommands = frameId == 0 - ? [issue] - : Array.Empty(), - Catalog = catalog, - BuildPublications = false - }); - - var taskBeforeRestart = run.AgentTasks.Single(); - RequireEqual(AetheriaRuntimeAgentTaskStatuses.Assigned, taskBeforeRestart.Status, - "restart fixture must persist an actively assigned task rather than a queued or completed shell"); - RequireEqual(taskBeforeRestart.TaskId, agent.AssignedAgentTaskId, - "restart fixture must persist the scheduler's single agent assignment"); - Require(agent.PositionX > 0 && agent.PositionX < 75, - "restart fixture must stop during commanded Ymir travel"); - var positionBeforeRestart = agent.PositionX; - var frame = new AetheriaRuntimeDaemonFrameDocument - { - FrameId = 8, - FixedDeltaSeconds = 0.1, - SimulationTimeSeconds = 0.8, - Run = run - }; - using (var node = AetheriaStateNode.OpenAsync(statePath).GetAwaiter().GetResult()) - { - node.MutableDocument( - AetheriaRuntimeVerseRecordKeys.DaemonFrameLatest) - .ReplaceAsync(frame).GetAwaiter().GetResult(); - node.FlushAsync(soft: false).GetAwaiter().GetResult(); - } - - using (var node = AetheriaStateNode.OpenAsync(statePath).GetAwaiter().GetResult()) - { - frame = node.MutableDocument( - AetheriaRuntimeVerseRecordKeys.DaemonFrameLatest) - .ReadAsync().GetAwaiter().GetResult() - ?? throw new InvalidOperationException("Durable agent-task frame is missing."); - } - run = frame.Run; - agent = run.Zones.Single().Entities.Single(); - var restoredTask = run.AgentTasks.Single(); - RequireEqual(AetheriaRuntimeAgentTaskStatuses.Assigned, restoredTask.Status, - "CultCache restart must preserve the active task status"); - RequireEqual(restoredTask.TaskId, agent.AssignedAgentTaskId, - "CultCache restart must preserve the task-to-agent ownership edge"); - RequireNear(positionBeforeRestart, agent.PositionX, 0.000001, - "CultCache restart must preserve the last daemon/Ymir committed pose"); - - physics = NewPhysics(); - var completed = false; - for (var frameId = 9; frameId <= 100; frameId++) - { - AetheriaRuntimeDaemonTickRunner.Tick( - statePath, - run, - new AetheriaRuntimeDaemonTickOptions - { - WorldPhysics = physics, - FrameId = frameId, - FixedDeltaSeconds = 0.1, - SimulationTimeSeconds = frameId * 0.1, - Catalog = catalog, - BuildPublications = false - }); - completed = string.Equals( - run.AgentTasks.Single().Status, - AetheriaRuntimeAgentTaskStatuses.Completed, - StringComparison.Ordinal); - if (completed) - break; - } - - Require(completed && agent.PositionX >= 75, - "restored task must reconstruct Ymir from committed world truth and finish through scheduler movement commands"); - RequireEqual(1, run.AgentTasks.Count, - "restart must not duplicate the durable task when the scheduler resumes"); - RequireEqual("", agent.AssignedAgentTaskId, - "completed restored task must release scheduler control exactly once"); - var commander = AetheriaRuntimeDaemonGameSurfaceBuilder.BuildCommander( - new AetheriaRuntimeDaemonFrameDocument { FrameId = 100, Run = run }, - new AetheriaRuntimeDaemonHealthDocument(), - AetheriaRuntimeDaemonCommandBoundaryDocument.Create("agent-task-restart-smoke")); - var taskNode = Flatten(commander.Surface.Root).Single(node => - node.Id == "aetheria.starbridge.commander.tasks.restart_explore_east"); - RequireEqual(AetheriaRuntimeAgentTaskStatuses.Completed, taskNode.Props["status"], - "generic Eve commander reconnect must observe the restored task's authoritative completion"); - } - finally - { - if (Directory.Exists(root)) - Directory.Delete(root, recursive: true); - } - } - - private static void MultipleActorsUseTheSameMovementLever() - { - var player = Entity(0, 0, "player"); - var agent = Entity(1, 200, "worker"); - var catalog = EquipThrusterBank([player, agent]); - player.TargetEntityIndex = agent.EntityIndex; - var run = new AetheriaRuntimeRunCheckpointCommit - { - RunId = "shared-lever-smoke", - CurrentZoneIndex = 0, - CurrentEntityKey = "zone.0.entity.0", - Zones = [new AetheriaRuntimeZoneSnapshotCommit { ZoneIndex = 0, Entities = [player, agent] }] - }; - var commands = new[] - { - MovementCommand("player-move", "zone.0.entity.0", 1, 0), - MovementCommand("agent-move", "zone.0.entity.1", 0, 1) - }; - var operation = AetheriaRuntimeDaemonOperations.Execute(run, commands); - - RequireEqual(2, operation.Intents.Movements.Count, "movement intent must retain one lever position per actor"); - var physics = NewPhysics(); - for (var step = 0; step < 2; step++) - AetheriaRuntimeDaemonSimulation.Step( - run, - operation.Intents, - 0.1, - AetheriaRuntimeDaemonSimulationSettings.AetheriaDefault, - physics, - catalog, - step + 1); - Require(player.VelocityX > 0 && Math.Abs(player.VelocityY) < Math.Abs(player.VelocityX) * 0.1, - $"player command must drive its actor through the shared movement lever; velocity={player.VelocityX:0.###},{player.VelocityY:0.###}"); - Require(agent.VelocityY > 0 && Math.Abs(agent.VelocityX) < Math.Abs(agent.VelocityY) * 0.1, - $"agent command must drive its actor through the shared movement lever; velocity={agent.VelocityX:0.###},{agent.VelocityY:0.###}"); - - var release = MovementCommand("player-release", "zone.0.entity.0", 0, 0); - release.ScalarValue = 0; - var releaseOperation = AetheriaRuntimeDaemonOperations.Execute(run, [release]); - AetheriaRuntimeDaemonSimulation.Step( - run, - releaseOperation.Intents, - 0.1, - AetheriaRuntimeDaemonSimulationSettings.AetheriaDefault, - physics, - catalog, - 3); - RequireEqual(agent.EntityIndex, player.TargetEntityIndex, - "releasing the movement lever must not steal targeting authority"); - } - - private static void DirectionalThrustersOwnOrdinaryFlightFeedback() - { - var ship = Entity(0, 0, "player"); - var catalog = EquipThrusterBank([ship]); - var run = new AetheriaRuntimeRunCheckpointCommit - { - RunId = "ordinary-thruster-flight-smoke", - CurrentZoneIndex = 0, - CurrentEntityKey = "zone.0.entity.0", - Zones = [new AetheriaRuntimeZoneSnapshotCommit { ZoneIndex = 0, Entities = [ship] }] - }; - var operation = AetheriaRuntimeDaemonOperations.Execute( - run, [MovementCommand("forward-thrust", "zone.0.entity.0", 0, 1)]); - var physics = NewPhysics(); - AetheriaRuntimeDaemonSimulation.Step( - run, operation.Intents, 0.1, - AetheriaRuntimeDaemonSimulationSettings.AetheriaDefault, - physics, catalog, 1); - - var thrusterStates = ship.BehaviorStates - .Where(value => value.BehaviorKind == AetheriaRuntimeBehaviorKinds.Thruster) - .ToArray(); - RequireEqual(8, thrusterStates.Length, - "ordinary hull fixture must fly through its directional thruster bank"); - Require(thrusterStates.Count(value => value.ThrusterAxis > 0.99) == 2 && - thrusterStates.Where(value => value.ThrusterAxis > 0.99) - .All(value => ship.Equipment[value.OwnerIndex].Rotation == "Half"), - "forward helm must fire only the reversed forward-thrust pair when no turn is requested"); - Require(thrusterStates.Where(value => value.ThrusterAxis > 0.99) - .All(value => value.ThrusterThrust > 0 && Math.Abs(value.ThrusterTorque) > 0.5), - "thruster state must expose evaluated thrust and placement-derived torque"); - Require(ship.VelocityY > 0 && Math.Abs(ship.VelocityX) < Math.Abs(ship.VelocityY) * 0.1, - "ordinary thrust must become Ymir-integrated forward velocity"); - Require(ship.Visibility > 1.9, - "successful thruster execution must publish its maximum plume visibility"); - Require(thrusterStates.Where(value => value.ThrusterAxis > 0.99) - .Any(value => AetheriaRuntimeThermalSimulation.EquipmentTemperature( - ship, catalog, value.OwnerIndex) > ship.EquipmentStates - .Single(state => state.EquipmentIndex == value.OwnerIndex).Temperature), - "successful thruster execution must deposit authored heat into daemon equipment cells"); - Require(!ship.BehaviorStates.Any(value => value.BehaviorKind == AetheriaRuntimeBehaviorKinds.AetherDrive), - "ordinary flight must not manufacture an AetherDrive behavior"); - } - - private static void UnpoweredThrustersCannotMoveOrAdvertisePlume() - { - var ship = Entity(0, 0, "player"); - var catalog = EquipThrusterBank([ship]); - var thruster = catalog.Items.Single(value => value.ItemKey == "smoke-directional-thruster"); - thruster.BehaviorPayloads.Single().Fields = thruster.BehaviorPayloads.Single().Fields - .Select(field => field.Key == 4 - ? new AetheriaRuntimeBehaviorField(4, PerformanceStat(10)) - : field) - .ToArray(); - var run = new AetheriaRuntimeRunCheckpointCommit - { - RunId = "unpowered-thruster-smoke", - CurrentZoneIndex = 0, - CurrentEntityKey = "zone.0.entity.0", - Zones = [new AetheriaRuntimeZoneSnapshotCommit { ZoneIndex = 0, Entities = [ship] }] - }; - var operation = AetheriaRuntimeDaemonOperations.Execute( - run, [MovementCommand("unpowered-forward", "zone.0.entity.0", 0, 1)]); - AetheriaRuntimeDaemonSimulation.Step( - run, operation.Intents, 0.1, - AetheriaRuntimeDaemonSimulationSettings.AetheriaDefault, - NewPhysics(), catalog, 1); - - RequireNear(0, ship.VelocityX, 0.000001, - "an unfunded thruster bank must not create lateral velocity"); - RequireNear(0, ship.VelocityY, 0.000001, - "an unfunded thruster bank must not create forward velocity"); - RequireNear(0, ship.Visibility, 0.000001, - "an unfunded thruster bank must not advertise a plume that did not execute"); - } - - private static void SpecializedBehaviorSuccessGatesTrailingWear() - { - var ship = Entity(0, 0, "player"); - var catalog = EquipThrusterBank([ship]); - var thrusterItem = catalog.Items.Single(value => value.ItemKey == "smoke-directional-thruster"); - thrusterItem.BehaviorPayloads = - [ - ThrusterPayload(), - new AetheriaRuntimeBehaviorPayload(1, "Wear", 0, - [new AetheriaRuntimeBehaviorField(1, BoolValue(true))]) - ]; - AetheriaRuntimeThermalSimulation.EnsureTopology(ship, catalog); - AetheriaRuntimeThermalSimulation.EnsureState(ship); - AetheriaRuntimeThermalSimulation.UpdateEquipmentStates(ship, catalog, 0.1); - foreach (var state in ship.EquipmentStates) - state.Wear = 1; - - AetheriaRuntimeEnergySimulation.BeginTick(ship, catalog); - AetheriaRuntimeBehaviorSimulation.Step(0, [ship], [], catalog, 0.1); - AetheriaRuntimeFlightSimulation.Step([ship], [], catalog, 0.1, 1, 1, 0.001, 1); - AetheriaRuntimeBehaviorSimulation.CompleteDeferredChains(ship, catalog, 0.1); - Require(ship.Equipment.All(slot => Math.Abs(slot.Item.Durability - 1) < 0.000001), - "idle thrusters must return false and leave every trailing wear behavior unreachable"); - - ship.HelmForward = 1; - AetheriaRuntimeBehaviorSimulation.Step(0, [ship], [], catalog, 0.1); - AetheriaRuntimeFlightSimulation.Step([ship], [], catalog, 0.1, 1, 1, 0.001, 1); - AetheriaRuntimeBehaviorSimulation.CompleteDeferredChains(ship, catalog, 0.1); - RequireEqual(2, ship.Equipment.Count(slot => slot.Item.Durability < 1), - "only the two successful forward thrusters may resume into trailing wear"); - Require(ship.Equipment.Where(slot => slot.Item.Durability < 1) - .All(slot => Math.Abs(slot.Item.Durability - 0.9) < 0.000001), - "each successful specialized behavior must resume its tail exactly once"); - AetheriaRuntimeBehaviorSimulation.CompleteDeferredChains(ship, catalog, 0.1); - Require(ship.Equipment.Where(slot => slot.Item.Durability < 1) - .All(slot => Math.Abs(slot.Item.Durability - 0.9) < 0.000001), - "replaying deferred completion in the same tick must not apply trailing wear twice"); - } - - private static void RareAetherDriveSpoolsOnlyOnModifiedHullFixture() - { - var ship = Entity(0, 0, "player"); - var catalog = EquipAetherDrive([ship]); - var hull = catalog.FindItem(ship.HullItemKey)!; - var drive = catalog.Items.Single(value => value.ItemKey == "smoke-aether-drive"); - Require(drive.HardpointType == "AetherDrive" && - hull.Hardpoints.Count(value => value.Type == drive.HardpointType) == 1, - "rare AetherDrive fixture must use an explicit modified-hull hardpoint instead of the ordinary hull path"); - var run = new AetheriaRuntimeRunCheckpointCommit - { - RunId = "rare-aether-drive-smoke", - CurrentZoneIndex = 0, - CurrentEntityKey = "zone.0.entity.0", - Zones = [new AetheriaRuntimeZoneSnapshotCommit { ZoneIndex = 0, Entities = [ship] }] - }; - var operation = AetheriaRuntimeDaemonOperations.Execute( - run, [MovementCommand("rare-drive-forward", "zone.0.entity.0", 0, 1)]); - var physics = NewPhysics(); - for (var frame = 1; frame <= 2; frame++) - AetheriaRuntimeDaemonSimulation.Step( - run, operation.Intents, 0.1, - AetheriaRuntimeDaemonSimulationSettings.AetheriaDefault, - physics, catalog, frame); - - var state = ship.BehaviorStates.Single(value => - value.BehaviorKind == AetheriaRuntimeBehaviorKinds.AetherDrive); - Require(state.AetherDriveRpmX > 0 && state.AetherDriveMaximumRpm > 0, - "rare installed drive must persist rotor spool state"); - Require(ship.VelocityY > 0, - "rare installed drive must contribute force only after its rotor has spooled"); - Require(!ship.BehaviorStates.Any(value => value.BehaviorKind == AetheriaRuntimeBehaviorKinds.Thruster), - "isolated AetherDrive proof must not conceal its force behind ordinary thrusters"); - } - - private static void LookDirectionRejectsInvalidVectorsWithoutMutatingTheShip() - { - var player = Entity(0, 0, "player"); - player.DirectionX = 0.6; - player.DirectionY = 0.8; - var run = new AetheriaRuntimeRunCheckpointCommit - { - RunId = "invalid-look-smoke", - CurrentZoneIndex = 0, - CurrentEntityKey = "zone.0.entity.0", - Zones = [new AetheriaRuntimeZoneSnapshotCommit { ZoneIndex = 0, Entities = [player] }] - }; - - AetheriaRuntimeDaemonCommandDocument Look(string id, double x, double z) - { - var command = AetheriaRuntimeDaemonCommandDocument.Create( - AetheriaRuntimeDaemonCommandKinds.SetLookDirection, - "look-smoke", - "look-session", - 0, - "zone.0.entity.0"); - command.CommandId = id; - command.DirectionX = x; - command.PositionZ = z; - return command; - } - - var operation = AetheriaRuntimeDaemonOperations.Execute(run, - [ - Look("zero-look", 0, 0), - Look("nan-look", double.NaN, 1), - Look("infinite-look", 1, double.PositiveInfinity) - ]); - - Require(operation.RejectedCommandIds.SequenceEqual(new[] { "zero-look", "nan-look", "infinite-look" }), - "zero and non-finite look intents must be rejected at the daemon authority boundary"); - RequireNear(0.6, player.DirectionX, 0.000001, - "rejected look intents must not mutate the authoritative ship direction X"); - RequireNear(0.8, player.DirectionY, 0.000001, - "rejected look intents must not mutate the authoritative ship direction Z"); - } - - private static AetheriaRuntimeDaemonCommandDocument MovementCommand( - string commandId, - string actor, - double x, - double y) - { - var command = AetheriaRuntimeDaemonCommandDocument.Create( - AetheriaRuntimeDaemonCommandKinds.SetMoveVector, - "shared-control-smoke", - "shared-control-session", - 0, - actor); - command.CommandId = commandId; - command.DirectionX = x; - command.DirectionY = y; - command.ScalarValue = 1; - return command; - } - - private static void EnergyNetworkSettlesReactorAfterConsumers() - { - var entity = Entity(0, 0, "player"); - entity.Equipment = - [ - new AetheriaRuntimeLoadoutItemSlotCommit { Item = new AetheriaRuntimeLoadoutItemCommit - { ItemKey = "ledger-capacitor", Quality = 1, Durability = 1, Enabled = true } }, - new AetheriaRuntimeLoadoutItemSlotCommit { Item = new AetheriaRuntimeLoadoutItemCommit - { ItemKey = "ledger-reactor", Quality = 1, Durability = 1, Enabled = true } } - ]; - entity.BehaviorStates = - [ - new AetheriaRuntimeBehaviorStateCommit - { - OwnerKind = AetheriaRuntimeBehaviorStateProjector.EquipmentOwnerKind, - OwnerIndex = 0, BehaviorIndex = 0, BehaviorKind = "Capacitor", - CapacitorCharge = 2, CapacitorCapacity = 10, CapacitorEfficiency = 0.5 - }, - new AetheriaRuntimeBehaviorStateCommit - { - OwnerKind = AetheriaRuntimeBehaviorStateProjector.EquipmentOwnerKind, - OwnerIndex = 1, BehaviorIndex = 0, BehaviorKind = "Reactor" - } - ]; - entity.StatGrids = - [ - Grid("hull", 100), Grid("shield", 0), Grid("heat", 280), - Grid("temperature", 280), Grid("thermal-mass", 1), Grid("conductivity", 1) - ]; - var capacitorItem = CatalogItem("ledger-capacitor", CapacitorPayload(10, 0.5)); - capacitorItem.ShapeCells = [new AetheriaRuntimeShapeCell(0, 0)]; - var reactorPayload = new AetheriaRuntimeBehaviorPayload(0, "Reactor", 100, - [ - new AetheriaRuntimeBehaviorField(1, PerformanceStat(4)), - new AetheriaRuntimeBehaviorField(2, PerformanceStat(2)), - new AetheriaRuntimeBehaviorField(3, PerformanceStat(0.5)), - new AetheriaRuntimeBehaviorField(4, PerformanceStat(2)) - ]); - var reactorItem = CatalogItem("ledger-reactor", reactorPayload); - reactorItem.ShapeCells = [new AetheriaRuntimeShapeCell(0, 0)]; - var catalog = new AetheriaRuntimeCatalogSnapshot([capacitorItem, reactorItem], [], []); - - AetheriaRuntimeEnergySimulation.BeginTick(entity, catalog); - Require(AetheriaRuntimeEnergySimulation.TryConsume(entity, catalog, 5), - "online reactor must accept residual demand after capacitors drain"); - var capacitor = entity.BehaviorStates.Single(value => value.BehaviorKind == "Capacitor"); - var reactor = entity.BehaviorStates.Single(value => value.BehaviorKind == "Reactor"); - RequireNear(0, capacitor.CapacitorCharge, 0.000001, - "consumers must drain capacitors before adding reactor draw"); - RequireNear(3, reactor.ReactorDraw, 0.000001, - "residual demand must accumulate on the last-running reactor"); - - AetheriaRuntimeEnergySimulation.SettleReactors(entity, catalog, 1); - RequireNear(1, capacitor.CapacitorCharge, 0.000001, - "baseline reactor surplus must refill non-full capacitors after demand settles"); - RequireNear(0, reactor.ReactorDraw, 0.000001, - "reactor settlement must consume and reset the per-tick demand ledger"); - RequireNear(283.5, GridValue(entity, "temperature", 0), 0.000001, - "capacitor discharge/refill losses and reactor baseline heat must reach canonical item cells"); - var run = new AetheriaRuntimeRunCheckpointCommit - { - RunId = "energy-ledger-smoke", CurrentZoneIndex = 0, CurrentEntityKey = "zone.0.entity.0", - Zones = [new AetheriaRuntimeZoneSnapshotCommit { ZoneIndex = 0, Entities = [entity] }] - }; - var surface = AetheriaRuntimeDaemonGameSurfaceBuilder.Build( - new AetheriaRuntimeDaemonFrameDocument { FrameId = 1, Run = run }, - new AetheriaRuntimeDaemonHealthDocument(), - AetheriaRuntimeDaemonCommandBoundaryDocument.Create("daemon")); - var worldEntity = Flatten(surface.Surface.Root).Single(node => - node.Id == "aetheria.daemon.game.world.entity.0"); - RequireEqual("1", worldEntity.Props["capacitorCharge"], - "Eve must expose the settled canonical capacitor charge"); - RequireEqual("0", worldEntity.Props["reactorDraw"], - "Eve must expose the reset post-settlement reactor ledger"); - } - - private static void RadiatorPumpsHeatBeforeReactorSettlement() - { - var entity = Entity(0, 0, "player"); - entity.HeatsinksEnabled = true; - entity.Equipment = - [ - new AetheriaRuntimeLoadoutItemSlotCommit { Item = new AetheriaRuntimeLoadoutItemCommit - { ItemKey = "radiator-capacitor", Quality = 1, Durability = 1, Enabled = true } }, - new AetheriaRuntimeLoadoutItemSlotCommit { Item = new AetheriaRuntimeLoadoutItemCommit - { ItemKey = "radiator", Quality = 1, Durability = 1, Enabled = true } } - ]; - entity.BehaviorStates = - [ - new AetheriaRuntimeBehaviorStateCommit - { - OwnerKind = AetheriaRuntimeBehaviorStateProjector.EquipmentOwnerKind, - OwnerIndex = 0, BehaviorIndex = 0, BehaviorKind = "Capacitor", CapacitorCharge = 10 - }, - new AetheriaRuntimeBehaviorStateCommit - { - OwnerKind = AetheriaRuntimeBehaviorStateProjector.EquipmentOwnerKind, - OwnerIndex = 1, BehaviorIndex = 0, BehaviorKind = "Radiator", RadiatorTemperature = 280 - } - ]; - entity.StatGrids = - [ - Grid("hull", 100), Grid("shield", 0), Grid("heat", 400), - Grid("temperature", 400), Grid("thermal-mass", 1), Grid("conductivity", 1) - ]; - var capacitor = CatalogItem("radiator-capacitor", CapacitorPayload(10, 1)); - capacitor.ShapeCells = [new AetheriaRuntimeShapeCell(0, 0)]; - var radiatorPayload = new AetheriaRuntimeBehaviorPayload(0, "Radiator", 0, - [ - new AetheriaRuntimeBehaviorField(1, PerformanceStat(0)), - new AetheriaRuntimeBehaviorField(2, PerformanceStat(1)), - new AetheriaRuntimeBehaviorField(3, Number(300)), - new AetheriaRuntimeBehaviorField(4, PerformanceStat(0.5)), - new AetheriaRuntimeBehaviorField(5, PerformanceStat(1)), - new AetheriaRuntimeBehaviorField(6, PerformanceStat(10)) - ]); - var radiator = CatalogItem("radiator", radiatorPayload); - radiator.ShapeCells = [new AetheriaRuntimeShapeCell(0, 0)]; - var catalog = new AetheriaRuntimeCatalogSnapshot([capacitor, radiator], [], []); - - AetheriaRuntimeEnergySimulation.BeginTick(entity, catalog); - AetheriaRuntimeBehaviorSimulation.Step(0, [entity], [], catalog, 1); - AetheriaRuntimeEnergySimulation.StepRadiators(entity, catalog, 1); - - RequireNear(9, entity.BehaviorStates.Single(value => value.BehaviorKind == "Capacitor").CapacitorCharge, - 0.000001, "radiator pump must consume authored energy before reactor settlement"); - RequireNear(300.5, GridValue(entity, "temperature", 0), 0.000001, - "radiator must remove pumped heat and return authored waste heat to its item cells"); - RequireNear(290, entity.BehaviorStates.Single(value => value.BehaviorKind == "Radiator").RadiatorTemperature, - 0.000001, "pumped heat must accumulate in radiator thermal mass before radiation"); - } - - private static void EquipmentThermalPerformanceOwnsShutdownAndWear() - { - var entity = Entity(0, 0, "player"); - entity.ShutdownPerformance = 0.25; - var item = new AetheriaRuntimeLoadoutItemCommit - { ItemKey = "thermal-wear-item", Quality = 0, Durability = 10, Enabled = true }; - entity.Equipment = [new AetheriaRuntimeLoadoutItemSlotCommit { Item = item }]; - entity.StatGrids = - [ - Grid("hull", 100), Grid("shield", 0), Grid("heat", 300), - Grid("temperature", 300), Grid("thermal-mass", 1), Grid("conductivity", 1) - ]; - var wearPayload = new AetheriaRuntimeBehaviorPayload(0, "Wear", 0, - [new AetheriaRuntimeBehaviorField(1, new AetheriaRuntimeBehaviorValue( - "bool", "", 0, true, "", "", [], []))]); - var typed = CatalogItem("thermal-wear-item", wearPayload); - typed.ShapeCells = [new AetheriaRuntimeShapeCell(0, 0)]; - typed.Durability = 10; - typed.ThermalResilience = 2; - typed.MinimumTemperature = 200; - typed.MaximumTemperature = 400; - typed.ThermalPerformanceCurveKeys = - [ - new AetheriaRuntimeCurveKey(0, 1, 0, 0), - new AetheriaRuntimeCurveKey(1, 0, 0, 0) - ]; - var catalog = new AetheriaRuntimeCatalogSnapshot([typed], [], []); - - AetheriaRuntimeThermalSimulation.UpdateEquipmentStates(entity, catalog, 1); - var state = entity.EquipmentStates.Single(); - RequireNear(0.5, state.ThermalPerformance, 0.000001, - "item thermal performance must come from mean occupied-cell temperature and authored curve"); - Require(state.ThermalOnline && state.Online, - "item above shutdown performance with positive durability must remain online"); - AetheriaRuntimeBehaviorSimulation.Step(0, [entity], [], catalog, 1); - RequireNear(10 - state.Wear, item.Durability, 0.000001, - "generic Wear behavior must apply the computed thermal wear potential at its authored cadence"); - - var temperature = entity.StatGrids.Single(value => value.Name == "temperature"); - temperature.Values = [380]; - item.OverrideShutdown = false; - entity.OverrideShutdown = true; - AetheriaRuntimeThermalSimulation.UpdateEquipmentStates(entity, catalog, 1); - state = entity.EquipmentStates.Single(); - Require(!state.ThermalOnline && !state.Online, - "entity override alone must not bypass item thermal shutdown"); - item.OverrideShutdown = true; - AetheriaRuntimeThermalSimulation.UpdateEquipmentStates(entity, catalog, 1); - Require(entity.EquipmentStates.Single().ThermalOnline, - "thermal shutdown bypass must require both entity-wide and item-local override"); - } - - private static void ThermalTopologyComesFromHullAndEquipment() - { - var entity = Entity(0, 0, "player"); - entity.HullItemKey = "thermal-hull"; - entity.Equipment = [new AetheriaRuntimeLoadoutItemSlotCommit - { - X = 0, Y = 0, Item = new AetheriaRuntimeLoadoutItemCommit - { ItemKey = "thermal-gear", Quality = 1, Durability = 1, Enabled = true } - }]; - entity.CargoBays = [new AetheriaRuntimeLoadoutItemSlotCommit - { - X = 1, Y = 0, Item = new AetheriaRuntimeLoadoutItemCommit - { ItemKey = "thermal-cargo-bay", Quality = 1, Durability = 1, Enabled = true } - }]; - var hull = HullCatalogItem("thermal-hull", 2, 1, 0); - hull.Mass = 10; - hull.SpecificHeat = 2; - hull.Conductivity = 4; - hull.InteriorShapeCells = [new AetheriaRuntimeShapeCell(0, 0)]; - var gear = CatalogItem("thermal-gear"); - gear.ShapeCells = [new AetheriaRuntimeShapeCell(0, 0)]; - gear.Mass = 4; - gear.SpecificHeat = 5; - gear.Conductivity = 3; - var cargoBay = CatalogItem("thermal-cargo-bay"); - cargoBay.Category = AetheriaRuntimeItemCategories.CargoBay; - cargoBay.ShapeCells = [new AetheriaRuntimeShapeCell(0, 0)]; - cargoBay.Mass = 6; - cargoBay.SpecificHeat = 2; - cargoBay.Conductivity = 2; - var catalog = new AetheriaRuntimeCatalogSnapshot([hull, gear, cargoBay], [], []); - - AetheriaRuntimeThermalSimulation.EnsureTopology(entity, catalog); - - var mass = entity.StatGrids.Single(value => value.Name == "thermal-mass"); - var conductivity = entity.StatGrids.Single(value => value.Name == "conductivity"); - RequireNear(30, mass.Values[0], 0.000001, - "occupied cell thermal mass must include its proportional equipment mass"); - RequireNear(22, mass.Values[1], 0.000001, - "typed cargo bays must contribute thermal mass at their authored hull cells"); - RequireNear(3, conductivity.Values[0], 0.000001, - "occupied cell conductivity must come from installed equipment"); - RequireNear(280, entity.StatGrids.Single(value => value.Name == "temperature").Values[1], 0.000001, - "authored hull topology must initialize each occupied cell at the fossil temperature"); - } - - private static void ThermalMedicalExposureUsesCockpitTemperature() - { - var entity = Entity(0, 0, "player"); - entity.Equipment = [new AetheriaRuntimeLoadoutItemSlotCommit - { - Item = new AetheriaRuntimeLoadoutItemCommit - { ItemKey = "medical-cockpit", Quality = 1, Durability = 1, Enabled = true } - }]; - entity.StatGrids = - [ - Grid("hull", 100), Grid("temperature", 340), - Grid("thermal-mass", 1), Grid("conductivity", 1) - ]; - var cockpit = CatalogItem("medical-cockpit", - new AetheriaRuntimeBehaviorPayload(0, "Cockpit", 0, [])); - cockpit.ShapeCells = [new AetheriaRuntimeShapeCell(0, 0)]; - var catalog = new AetheriaRuntimeCatalogSnapshot([cockpit], [], []); - var settings = AetheriaRuntimeDaemonSimulationSettings.AetheriaDefault; - - var heat = AetheriaRuntimeThermalMedicalSimulation.Step(entity, catalog, 300, settings); - RequireNear(0.3, entity.Heatstroke, 0.000001, - "cockpit heat must accumulate nonlinear heatstroke at the fossil rate"); - Require(heat.HeatstrokeRiskCrossed, - "heatstroke must emit one upward severe-risk crossing"); - RequireNear(340, Stat(entity, "cockpit-temperature"), 0.000001, - "cockpit cell temperature must become an authoritative Eve-readable fact"); - - entity.StatGrids.Single(value => value.Name == "temperature").Values = [280]; - AetheriaRuntimeThermalMedicalSimulation.Step(entity, catalog, 1, settings); - RequireNear(0.1, entity.Heatstroke, 0.000001, - "heatstroke must recover linearly below the authored threshold"); - - entity.StatGrids.Single(value => value.Name == "temperature").Values = [263]; - var cold = AetheriaRuntimeThermalMedicalSimulation.Step(entity, catalog, 300, settings); - RequireNear(0.3, entity.Hypothermia, 0.000001, - "cockpit cold must accumulate nonlinear hypothermia at the fossil rate"); - Require(cold.HypothermiaRiskCrossed, - "hypothermia must use its own exposure for the intended severe-risk crossing"); - - var run = new AetheriaRuntimeRunCheckpointCommit - { - RunId = "thermal-medical-surface", CurrentZoneIndex = 0, - CurrentEntityKey = "zone.0.entity.0", - Zones = [new AetheriaRuntimeZoneSnapshotCommit { ZoneIndex = 0, Entities = [entity] }] - }; - var surface = AetheriaRuntimeDaemonGameSurfaceBuilder.Build( - new AetheriaRuntimeDaemonFrameDocument { FrameId = 1, Run = run }, - new AetheriaRuntimeDaemonHealthDocument(), - AetheriaRuntimeDaemonCommandBoundaryDocument.Create("daemon")); - var worldEntity = Flatten(surface.Surface.Root).Single(node => - node.Id == "aetheria.daemon.game.world.entity.0"); - RequireEqual("263", worldEntity.Props["cockpitTemperature"], - "Eve must publish canonical cockpit temperature rather than deriving it from effects"); - RequireEqual("true", worldEntity.Props["hypothermiaRisk"], - "Eve must publish the current severe cold-risk state"); - Require(worldEntity.Props.ContainsKey("heatstrokePostWeight") && - worldEntity.Props.ContainsKey("severeHeatstrokeWeight"), - "Eve must publish the exact source weights used by the original heatstroke presentation"); - RequireEqual("5", worldEntity.Props["heatstrokePhasingFrequency"], - "Eve must publish the provider-authored severe heatstroke pulse frequency"); - RequireEqual("1", worldEntity.Props["deathTransitionSeconds"], - "Eve must publish the original thermal death crossfade duration"); - } - - private static void ThermalMedicalDeathUsesOrdinaryDestructionPath() - { - var entity = Entity(0, 0, "player"); - entity.EntityId = "thermal-pilot"; - entity.Equipment = [new AetheriaRuntimeLoadoutItemSlotCommit - { - Item = new AetheriaRuntimeLoadoutItemCommit - { ItemKey = "lethal-cockpit", Quality = 1, Durability = 1, Enabled = true } - }]; - entity.StatGrids = - [ - Grid("hull", 100), Grid("temperature", 700), - Grid("thermal-mass", 1), Grid("conductivity", 1) - ]; - var cockpit = CatalogItem("lethal-cockpit", - new AetheriaRuntimeBehaviorPayload(0, "Cockpit", 0, [])); - cockpit.ShapeCells = [new AetheriaRuntimeShapeCell(0, 0)]; - var catalog = new AetheriaRuntimeCatalogSnapshot([cockpit], [], []); - var zone = new AetheriaRuntimeZoneSnapshotCommit { ZoneIndex = 0, Entities = [entity] }; - var run = new AetheriaRuntimeRunCheckpointCommit - { - RunId = "thermal-death", CurrentZoneIndex = 0, - CurrentEntityKey = "zone.0.entity.0", Zones = [zone] - }; - - AetheriaRuntimeDaemonSimulation.Step(run, new AetheriaRuntimeDaemonIntentState(), 1, - AetheriaRuntimeDaemonSimulationSettings.AetheriaDefault, - NewPhysics(), catalog, frameId: 17); - - RequireEqual("heatstroke", entity.CauseOfDeath, - "lethal cockpit heat must persist the typed cause of death"); - Require(!entity.IsActive && !string.IsNullOrWhiteSpace(entity.DestructionId), - "thermal death must enter the ordinary exactly-once destruction path"); - var death = run.GameEvents.Single(value => value.Kind == "entity.destroyed"); - RequireEqual("heatstroke", death.Reason, - "Eve feedback must carry the thermal cause needed for the original death transition"); - Require(run.GameEvents.Any(value => value.Kind == "pilot.heatstroke.risk"), - "a lethal step crossing the severe threshold must publish risk before death"); - } - - private static void ThermalCellsUseFossilConductionAndRadiation() - { - var entity = Entity(7, 0, "player"); - entity.StatGrids = - [ - Grid(AetheriaRuntimeThermalSimulation.TemperatureGrid, 2, 1, 300, 280), - Grid(AetheriaRuntimeThermalSimulation.ThermalMassGrid, 2, 1, 1, 1), - Grid(AetheriaRuntimeThermalSimulation.ConductivityGrid, 2, 1, 1, 1) - ]; - - AetheriaRuntimeThermalSimulation.AddHeat(entity, 20); - RequireNear(310, GridValue(entity, AetheriaRuntimeThermalSimulation.TemperatureGrid, 0), 0.000001, - "heat energy must be divided across cells and thermal mass"); - RequireNear(290, GridValue(entity, AetheriaRuntimeThermalSimulation.TemperatureGrid, 1), 0.000001, - "heat energy must be divided across cells and thermal mass"); - - AetheriaRuntimeThermalSimulation.Step(entity, 0.1); - var expectedHot = (310 / 0.01 + 290) / 101; - expectedHot -= Math.Pow(expectedHot, 3) * 0.00000001 * 0.1; - var expectedCool = (290 / 0.01 + 310) / 101; - expectedCool -= Math.Pow(expectedCool, 3) * 0.00000001 * 0.1; - RequireNear(expectedHot, GridValue(entity, AetheriaRuntimeThermalSimulation.TemperatureGrid, 0), 0.000001, - "hot cell must follow fossil conduction and radiation"); - RequireNear(expectedCool, GridValue(entity, AetheriaRuntimeThermalSimulation.TemperatureGrid, 1), 0.000001, - "cool cell must follow fossil conduction and radiation"); - RequireNear((expectedHot + expectedCool) / 2, Stat(entity, "heat"), 0.000001, - "legacy heat scalar must be derived from cell temperature"); - - var run = new AetheriaRuntimeRunCheckpointCommit - { - RunId = "thermal-projection-smoke", - CurrentZoneIndex = 0, - CurrentEntityKey = "zone.0.entity.7", - Zones = [new AetheriaRuntimeZoneSnapshotCommit { ZoneIndex = 0, Entities = [entity] }] - }; - var document = AetheriaRuntimeGameDocuments.CurrentEntity(new AetheriaRuntimeDaemonFrameDocument { Run = run }); - RequireNear(Stat(entity, "heat"), document.Hud.MeanTemperature, 0.000001, - "Eve current-entity state must publish mean temperature"); - RequireNear(GridValue(entity, AetheriaRuntimeThermalSimulation.TemperatureGrid, 0), document.Hud.MaximumTemperature, 0.000001, - "Eve current-entity state must publish maximum temperature"); - Require(document.Hud.ThermalVisibility > 0, "Eve current-entity state must publish thermal visibility"); - } - - private static void YmirMovesProjectileAndReportsStableContact() - { - var (run, zone, target) = Scenario(); - using var physics = NewPhysics(); - physics.RetainWorlds(run.RunId, [zone.ZoneIndex]); - physics.Step(run.RunId, 0, 0, zone, zone.Entities, 0.1); - var step = physics.StepPhysicalPayloads(run.RunId, 0, 0, zone, zone.Entities, 0.1); - - RequireEqual("ymir.box3d.retained-session.v1", physics.ImplementationId, "zone physics must identify its retained Ymir implementation"); - RequireEqual(0, step.PhysicalPayloads.Count, "contacted projectile must not survive"); - RequireEqual(1, step.Hits.Count, "Ymir must report one projectile contact"); - RequireEqual(target.EntityIndex, step.Hits[0].TargetEntityIndex, "contact must resolve the daemon entity"); - RequireEqual("aetheria.physical-payload.smoke-projectile", step.Hits[0].PhysicalPayloadBodyId, "physical payload body id must be stable"); - RequireEqual("aetheria.daemon.entity.smoke.entity.2", step.Hits[0].TargetBodyId, "entity body id must be stable"); - } - - private static void PayloadQueryExcludesItsSource() - { - var source = Entity(1, 0, "player"); - var zone = new AetheriaRuntimeZoneSnapshotCommit - { - ZoneIndex = 0, - Entities = [source], - PhysicalPayloads = - [ - new AetheriaRuntimePhysicalPayloadCommit - { - PayloadId = "source-exclusion", - SourceEntityIndex = source.EntityIndex, - PositionX = 0, - PositionZ = 0, - VelocityX = 10, - Radius = 1, - LifetimeSeconds = 5, - Active = true - } - ] - }; - using var physics = NewPhysics(); - physics.RetainWorlds("source-exclusion-smoke", [zone.ZoneIndex]); - physics.Step("source-exclusion-smoke", 1, 0, zone, zone.Entities, 0.1); - var step = physics.StepPhysicalPayloads("source-exclusion-smoke", 1, 0, zone, zone.Entities, 0.1); - - RequireEqual(0, step.Hits.Count, "a payload query must not hit its source body"); - RequireEqual(1, step.PhysicalPayloads.Count, "source exclusion must leave the payload alive"); - } - - private static void PayloadBodiesDoNotCollideWithEachOther() - { - var zone = new AetheriaRuntimeZoneSnapshotCommit - { - ZoneIndex = 0, - Entities = [], - PhysicalPayloads = - [ - new AetheriaRuntimePhysicalPayloadCommit - { - PayloadId = "payload-left", - SourceEntityIndex = -1, - PositionX = -5, - VelocityX = 100, - Radius = 1, - LifetimeSeconds = 5, - Active = true - }, - new AetheriaRuntimePhysicalPayloadCommit - { - PayloadId = "payload-right", - SourceEntityIndex = -1, - PositionX = 5, - VelocityX = -100, - Radius = 1, - LifetimeSeconds = 5, - Active = true - } - ] - }; - using var physics = NewPhysics(); - physics.RetainWorlds("payload-isolation-smoke", [zone.ZoneIndex]); - physics.Step("payload-isolation-smoke", 2, 0, zone, zone.Entities, 0.1); - var step = physics.StepPhysicalPayloads("payload-isolation-smoke", 2, 0, zone, zone.Entities, 0.1); - - RequireEqual(0, step.Hits.Count, "payload bodies must not manufacture world-query hits against each other"); - RequireEqual(2, step.PhysicalPayloads.Count, "payload bodies must survive crossing each other"); - Require(step.PhysicalPayloads.Single(payload => payload.PayloadId == "payload-left").VelocityX > 0, - "payload-to-payload response must not reverse the left payload"); - Require(step.PhysicalPayloads.Single(payload => payload.PayloadId == "payload-right").VelocityX < 0, - "payload-to-payload response must not reverse the right payload"); - } - - private static void OverlappingMineRemainsQueryableAfterArming() - { - var target = Entity(2, 0, "enemy"); - var mine = new AetheriaRuntimePhysicalPayloadCommit - { - PayloadId = "arming-overlap", - PayloadKind = "mine", - SourceEntityIndex = 1, - PositionX = 0, - PositionZ = 0, - Radius = 1, - TriggerRadius = 25, - ActivationDelaySeconds = 1, - LifetimeSeconds = 10, - Stationary = false, - Active = true - }; - var zone = new AetheriaRuntimeZoneSnapshotCommit - { - ZoneIndex = 0, - Entities = [target], - PhysicalPayloads = [mine] - }; - using var physics = NewPhysics(); - physics.RetainWorlds("mine-arming-overlap-smoke", [zone.ZoneIndex]); - physics.Step("mine-arming-overlap-smoke", 1, 0, zone, zone.Entities, 0.5); - var unarmed = physics.StepPhysicalPayloads("mine-arming-overlap-smoke", 1, 0, zone, zone.Entities, 0.5); - RequireEqual(1, unarmed.Hits.Count, "an unarmed overlap may be observed without granting Ymir trigger authority"); - - mine.AgeSeconds = mine.ActivationDelaySeconds; - mine.Radius = mine.TriggerRadius; - zone.PhysicalPayloads = unarmed.PhysicalPayloads; - physics.Step("mine-arming-overlap-smoke", 2, 0, zone, zone.Entities, 0.5); - var armed = physics.StepPhysicalPayloads("mine-arming-overlap-smoke", 2, 0, zone, zone.Entities, 0.5); - RequireEqual(1, armed.Hits.Count, - "a retained mine already overlapping at activation must still yield an eligible Box3D overlap fact"); - RequireEqual(target.EntityIndex, armed.Hits[0].TargetEntityIndex, - "the armed overlap fact must preserve the target identity"); - } - - private static void DaemonSimulationTreatsYmirHitAsPresentationOnly() - { - var (run, _, target) = Scenario(); - - AetheriaRuntimeDaemonSimulation.Step( - run, - new AetheriaRuntimeDaemonIntentState(), - 0.1, - AetheriaRuntimeDaemonSimulationSettings.AetheriaDefault, - NewPhysics()); - - RequireEqual(100.0, Stat(target, "hull"), "presentation projectile contact must not write combat damage"); - RequireEqual(0, run.Zones[0].PhysicalPayloads.Count, "spent projectile must leave daemon state"); - RequireEqual(1, run.GameEvents.Count(value => value.Kind == "physical-payload.contact" && value.SubjectKey == "smoke-projectile"), - "Ymir contact must emit one projectile-identity impact event"); - RequireEqual(0, run.GameEvents.Count(value => value.Kind == "entity.damaged" && value.TargetEntityIndex == target.EntityIndex), - "presentation projectile contact must not manufacture a damage event"); - var feedback = AetheriaRuntimeDaemonGameSurfaceBuilder.BuildReactiveGameplay( - new AetheriaRuntimeDaemonFrameDocument { FrameId = 0, Run = run }); - Require(Flatten(feedback.Surface.Root).Any(node => node.Kind == "feedback.event" && node.Props["eventKind"] == "physical-payload.contact" && node.Props["subjectKey"] == "smoke-projectile"), - "Eve feedback must project authoritative projectile impact identity"); - } - - private static void ReactiveGameplayPublishesOnlyNewEventFacts() - { - var player = Entity(0, 0, "player"); - var run = new AetheriaRuntimeRunCheckpointCommit - { - RunId = "reactive-delta-smoke", - CurrentZoneIndex = 0, - Zones = [new AetheriaRuntimeZoneSnapshotCommit { ZoneIndex = 0, Entities = [player] }], - GameEvents = - [ - new AetheriaRuntimeGameEventCommit { EventId = "old-event", Kind = "old", FrameId = 10, SourceEntityIndex = 0 }, - new AetheriaRuntimeGameEventCommit { EventId = "new-event", Kind = "new", FrameId = 12, SourceEntityIndex = 0 } - ], - ShotReceipts = - [ - new AetheriaRuntimeShotReceiptCommit { ShotId = "old-shot", FrameId = 10, SourceEntityIndex = 0 }, - new AetheriaRuntimeShotReceiptCommit { ShotId = "new-shot", FrameId = 12, SourceEntityIndex = 0 } - ] - }; - run.CurrentEntityKey = run.EntityRecordKey(0, player.EntityIndex); - - var delta = AetheriaRuntimeDaemonGameSurfaceBuilder.BuildReactiveGameplay( - new AetheriaRuntimeDaemonFrameDocument { FrameId = 12, Run = run }, - eventsAfterFrame: 10); - var nodes = Flatten(delta.Surface.Root).ToArray(); - Require(nodes.Count(node => node.Kind == "feedback.event") == 1 && - nodes.Single(node => node.Kind == "feedback.event").Props["eventId"] == "new-event", - "reactive gameplay must publish only event facts newer than the previous delivery frame"); - Require(nodes.Count(node => node.Kind == "shot.receipt") == 1 && - nodes.Single(node => node.Kind == "shot.receipt").Props["shotId"] == "new-shot", - "reactive gameplay must not resend retained shot history in every surface update"); - - var empty = AetheriaRuntimeDaemonGameSurfaceBuilder.BuildReactiveGameplay( - new AetheriaRuntimeDaemonFrameDocument { FrameId = 13, Run = run }, - eventsAfterFrame: 12); - Require(!Flatten(empty.Surface.Root).Any(node => - node.Kind == "feedback.event" || node.Kind == "shot.receipt"), - "a later reactive update must carry current variables without replaying old event arrays"); - } - - private static void ProjectileContactCannotKill() - { - var (run, _, target) = Scenario(); - target.StatGrids.Single(grid => grid.Name == "hull").Values = [5]; - AetheriaRuntimeDaemonSimulation.Step(run, new AetheriaRuntimeDaemonIntentState(), 0.1, - AetheriaRuntimeDaemonSimulationSettings.AetheriaDefault, NewPhysics(), frameId: 9); - Require(target.IsActive && Math.Abs(Stat(target, "hull") - 5) < 0.000001, - "even nominally lethal presentation contact must leave canonical target state untouched"); - RequireEqual(0, run.GameEvents.Count(value => value.Kind == "entity.destroyed" && value.TargetEntityIndex == target.EntityIndex), - "presentation contact must not manufacture a destruction event"); - } - - private static void MissingWorldPhysicsOwnerCannotAdvanceShips() - { - var entity = Entity(0, 0, "player"); - entity.VelocityX = 10; - var run = new AetheriaRuntimeRunCheckpointCommit { CurrentZoneIndex = 0, CurrentEntityKey = "zone.0.entity.0", Zones = [new AetheriaRuntimeZoneSnapshotCommit { ZoneIndex = 0, Entities = [entity] }] }; - try - { - AetheriaRuntimeDaemonTickRunner.Tick(Path.Combine(Path.GetTempPath(), "aetheria-missing-world-physics.cc"), run, - new AetheriaRuntimeDaemonTickOptions { FrameId = 1, FixedDeltaSeconds = 0.1, BuildPublications = false }); - } - catch (InvalidOperationException) - { - RequireNear(0, entity.PositionX, 0.000001, "ship must not advance without Ymir world authority"); - return; - } - throw new InvalidOperationException("daemon advanced a ship without an authoritative world physics owner"); - } - - private static void TractorRampsAndPullsThroughYmirWithoutTeleportingCargo() - { - var ship = Entity(0, 0, "player"); - ship.DirectionX = 0; ship.DirectionY = 1; - ship.LookDirectionX = 1; ship.LookDirectionY = 0; - var pickup = new AetheriaRuntimeDroppedPickupCommit { PickupIndex = 3, PositionX = 60, Item = new AetheriaRuntimeLoadoutItemCommit { ItemKey = "salvage", Quantity = 1 }, LifetimeSeconds = 30 }; - var run = new AetheriaRuntimeRunCheckpointCommit { CurrentZoneIndex = 0, CurrentEntityKey = "zone.0.entity.0", Zones = [new AetheriaRuntimeZoneSnapshotCommit { ZoneIndex = 0, Entities = [ship], DroppedPickups = [pickup] }] }; - var command = AetheriaRuntimeDaemonCommandDocument.Create(AetheriaRuntimeDaemonCommandKinds.SetTractorPower, "pilot", "tractor-smoke", 0, "zone.0.entity.0"); - command.CommandId = "tractor-on"; command.ScalarValue = 1; - AetheriaRuntimeDaemonTickRunner.Tick(Path.Combine(Path.GetTempPath(), "aetheria-tractor-smoke.cc"), run, - new AetheriaRuntimeDaemonTickOptions { WorldPhysics = NewPhysics(), FrameId = 1, FixedDeltaSeconds = 0.25, SimulationTimeSeconds = 0.25, ObservedCommands = [command], BuildPublications = false }); - RequireNear(0.5, ship.TractorPower, 0.000001, "tractor power must use the fossil two-per-second ramp"); - Require(pickup.VelocityX < 0 && pickup.PositionX < 60, - "Ymir must pull a pickup inside the reticle-aligned tractor volume without rotating the hull"); - RequireEqual(1, pickup.Item.Quantity, "tractor force must not consume the pickup item"); - RequireEqual(0, CargoQuantity(ship, "salvage"), "scooping must remain a separate capacity-checked transaction"); - Require(pickup.AgeSeconds > 0 && pickup.AgeSeconds < pickup.LifetimeSeconds, - "daemon-owned pickup lifetime must advance independently of its SoA presentation"); - var release = AetheriaRuntimeDaemonCommandDocument.Create( - AetheriaRuntimeDaemonCommandKinds.SetTractorPower, - "pilot", - "tractor-smoke", - 1, - "zone.0.entity.0"); - release.CommandId = "tractor-off"; - release.ScalarValue = 0; - AetheriaRuntimeDaemonTickRunner.Tick(Path.Combine(Path.GetTempPath(), "aetheria-tractor-release-smoke.cc"), run, - new AetheriaRuntimeDaemonTickOptions { WorldPhysics = NewPhysics(), FrameId = 2, FixedDeltaSeconds = 0.25, SimulationTimeSeconds = 0.5, ObservedCommands = [release], BuildPublications = false }); - RequireNear(0, ship.TractorTargetPower, 0.000001, "released tractor input must set daemon target power to zero"); - RequireNear(0, ship.TractorPower, 0.000001, "daemon must ramp released tractor power back to zero at the fossil rate"); - var inputCapability = AetheriaRuntimeInputCapabilityDocument - .FromFrame(new AetheriaRuntimeDaemonFrameDocument { FrameId = 2, Run = run }) - .ToEveDocument(); - var scoopAction = inputCapability.Actions.Single(action => action.ActionId == "pilot.scoop"); - Require(scoopAction.InputValue?.Model == "button-hold.v1" && - scoopAction.InputValue.PayloadKey == "scalarValue" && - scoopAction.Payload["scalarValue"] == "0", - "tractor input must advertise press/release scalar ownership instead of a one-shot on command"); - ship.TractorPower = 0.5; - ship.TractorTargetPower = 1; - var surface = AetheriaRuntimeDaemonGameSurfaceBuilder.Build( - new AetheriaRuntimeDaemonFrameDocument { FrameId = 1, Run = run }, - new AetheriaRuntimeDaemonHealthDocument(), - AetheriaRuntimeDaemonCommandBoundaryDocument.Create("daemon")); - var beam = Flatten(surface.Surface.Root).Single(node => node.Kind == "beam.presentation"); - Require(beam.Props["sourceEntityId"] == run.EntityRecordKey(0, ship.EntityIndex) && - beam.Props["assetRole"] == "effect.beam.tractor" && - beam.Props["directionMode"] == "source-forward.v1" && - beam.Props["activationActionId"] == "pilot.scoop" && - beam.Props["powerStateSemantic"] == AetheriaRuntimeDaemonSoaColumnKinds.BeamPower && - beam.Props["radius"] == "25" && - beam.Props["maximumDistance"] == "75", - "Eve beam presentation must project the same daemon tractor power and fossil volume without owning force or contact"); - - double PulledVelocity(double power, int frameId) - { - var actor = Entity(0, 0, "player"); - actor.DirectionX = 0; - actor.DirectionY = 1; - actor.LookDirectionX = 1; - actor.LookDirectionY = 0; - actor.TractorPower = power; - actor.TractorTargetPower = power; - var cargo = new AetheriaRuntimeDroppedPickupCommit - { - PickupIndex = 30 + frameId, - PositionX = 60, - Item = new AetheriaRuntimeLoadoutItemCommit { ItemKey = "salvage", Quantity = 1 }, - LifetimeSeconds = 30 - }; - var scenario = new AetheriaRuntimeRunCheckpointCommit - { - RunId = $"tractor-power-{frameId}", - CurrentZoneIndex = 0, - CurrentEntityKey = "zone.0.entity.0", - Zones = [new AetheriaRuntimeZoneSnapshotCommit { ZoneIndex = 0, Entities = [actor], DroppedPickups = [cargo] }] - }; - AetheriaRuntimeDaemonTickRunner.Tick( - Path.Combine(Path.GetTempPath(), $"aetheria-tractor-power-{frameId}.cc"), - scenario, - new AetheriaRuntimeDaemonTickOptions - { - WorldPhysics = NewPhysics(), FrameId = frameId, FixedDeltaSeconds = 0.1, - SimulationTimeSeconds = frameId * 0.1, BuildPublications = false - }); - return cargo.VelocityX; - } - - RequireNear(PulledVelocity(0.02, 20), PulledVelocity(1, 21), 0.000001, - "tractor power above the fossil threshold must gate full traction rather than scale force"); - var pickupAsset = AetheriaRuntimeAssets.ProjectManifest(null).Assets.Single(asset => asset.Ref.AssetKey == "prefab.entity.pickup"); - Require(pickupAsset.Ref.Metadata.TryGetValue("unityAssetPath", out var pickupPath) && pickupPath == "Assets/Prefabs/RPG/Pickups/Tetrahedron.prefab", - "provider asset manifest must advertise the pickup visual used by Eve"); - var assets = AetheriaRuntimeAssets.ProjectManifest(null).Assets; - var playerAsset = assets.Single(asset => asset.Ref.AssetKey == "prefab.entity.player"); - RequireEqual("player", playerAsset.Ref.Metadata["presentationRole"], - "entity prefab fallback must use a specific presentation role instead of the generic world tag"); - var boltEffect = assets.Single(asset => asset.Ref.AssetKey == "prefab.effect.shot.bolt"); - RequireEqual("effect.shot.bolt", boltEffect.Ref.Metadata["presentationRole"], - "provider manifest must bind semantic shot feedback to its graduated EveUnity effect"); - var tractorEffect = assets.Single(asset => asset.Ref.AssetKey == "prefab.effect.beam.tractor"); - RequireEqual("effect.beam.tractor", tractorEffect.Ref.Metadata["presentationRole"], - "provider manifest must bind the stripped fossil tractor prefab to the generic beam role"); - var thermalProfiles = assets.Where(asset => - string.Equals(asset.Ref.Kind, AetheriaRuntimeAssetKinds.VolumeProfile, StringComparison.Ordinal) && - asset.Ref.Metadata.TryGetValue("presentationRole", out var role) && - (role.StartsWith("post.thermal.", StringComparison.Ordinal) || - string.Equals(role, "post.death", StringComparison.Ordinal))).ToArray(); - RequireEqual(5, thermalProfiles.Length, - "provider manifest must advertise every original thermal and death volume profile"); - Require(thermalProfiles.All(asset => - asset.Ref.Metadata.ContainsKey("unityAssetPath") && - asset.Ref.Metadata.ContainsKey("presentationRole")), - "thermal profiles must cross the CDN boundary with exact Unity paths and semantic roles"); - } - - private static void PickupProximityPrecedesYmirCollisionShell() - { - var hull = CatalogItem("proximity-hull"); - hull.HullCapacity = PerformanceStat(1); - var salvage = CatalogItem("proximity-salvage"); - salvage.Volume = 1; - var cargoBay = CatalogItem("proximity-cargo-bay"); - cargoBay.InteriorOccupiedCells = 1; - var catalog = new AetheriaRuntimeCatalogSnapshot([hull, salvage, cargoBay], [], []); - var ship = Entity(0, 0, "player"); - ship.HullItemKey = hull.ItemKey; - ship.CargoBays = - [ - new AetheriaRuntimeLoadoutItemSlotCommit - { - Item = new AetheriaRuntimeLoadoutItemCommit { ItemKey = cargoBay.ItemKey, Quantity = 1 } - } - ]; - ship.CargoContents = [Cargo()]; - var pickup = new AetheriaRuntimeDroppedPickupCommit - { - PickupIndex = 40, - PositionX = AetheriaRuntimeTractorMechanics.CollectionDistance, - Item = new AetheriaRuntimeLoadoutItemCommit { ItemKey = salvage.ItemKey, Quantity = 1 }, - LifetimeSeconds = 30 - }; - var run = new AetheriaRuntimeRunCheckpointCommit - { - RunId = "pickup-proximity-before-contact", - CurrentZoneIndex = 0, - CurrentEntityKey = "zone.0.entity.0", - Zones = - [ - new AetheriaRuntimeZoneSnapshotCommit - { - ZoneIndex = 0, - Entities = [ship], - DroppedPickups = [pickup] - } - ] - }; - - AetheriaRuntimeDaemonTickRunner.Tick( - Path.Combine(Path.GetTempPath(), "aetheria-pickup-before-contact.cc"), - run, - new AetheriaRuntimeDaemonTickOptions - { - Catalog = catalog, - WorldPhysics = NewPhysics(), - FrameId = 1, - FixedDeltaSeconds = 0.02, - SimulationTimeSeconds = 0.02, - BuildPublications = false - }); - - Require(AetheriaRuntimeTractorMechanics.CollectionDistance > 25, - "pickup collection must begin outside the current 20 + 5 Ymir collision shell"); - RequireEqual(1, CargoQuantity(ship, salvage.ItemKey), - "daemon XZ proximity must collect before Ymir contact can become the deciding fact"); - RequireEqual(0, run.Zones[0].DroppedPickups.Count, - "a proximity-collected pickup must leave authoritative world state exactly once"); - } - - private static void CatalogItemIndexPreservesSnapshotLookupSemantics() - { - var first = CatalogItem("Indexed-Item"); - var duplicate = CatalogItem("indexed-item"); - var catalog = new AetheriaRuntimeCatalogSnapshot([first, duplicate], [], []); - - Require(ReferenceEquals(first, catalog.FindItem("INDEXED-ITEM")), - "the derived catalog index must preserve first case-insensitive match semantics"); - - var replacement = CatalogItem("replacement-item"); - catalog.Items = [replacement]; - Require(catalog.FindItem("indexed-item") == null && - ReferenceEquals(replacement, catalog.FindItem("REPLACEMENT-ITEM")), - "replacing snapshot items must invalidate the derived lookup index"); - } - - private static void PickupIsCapacityCheckedExactlyOnceAndExpires() - { - var hull = CatalogItem("pickup-hull"); hull.HullCapacity = PerformanceStat(1); - var salvage = CatalogItem("salvage"); salvage.Volume = 1; - var cargoBay = CatalogItem("pickup-cargo-bay"); cargoBay.InteriorOccupiedCells = 1; - var catalog = new AetheriaRuntimeCatalogSnapshot([hull, salvage, cargoBay], [], []); - var ship = Entity(0, 0, "player"); ship.HullItemKey = hull.ItemKey; ship.CargoContents = [Cargo()]; - ship.CargoBays = [new AetheriaRuntimeLoadoutItemSlotCommit { Item = new AetheriaRuntimeLoadoutItemCommit { ItemKey = cargoBay.ItemKey, Quantity = 1 } }]; - var ally = Entity(1, AetheriaRuntimeTractorMechanics.CollectionDistance, "player"); ally.HullItemKey = hull.ItemKey; - ally.CargoContents = [Cargo()]; - ally.CargoBays = [new AetheriaRuntimeLoadoutItemSlotCommit { Item = new AetheriaRuntimeLoadoutItemCommit { ItemKey = cargoBay.ItemKey, Quantity = 1 } }]; - var zone = new AetheriaRuntimeZoneSnapshotCommit { ZoneIndex = 0, Entities = [ship, ally], DroppedPickups = [new AetheriaRuntimeDroppedPickupCommit { PickupIndex = 7, PositionX = AetheriaRuntimeTractorMechanics.CollectionDistance + 0.001, Item = new AetheriaRuntimeLoadoutItemCommit { ItemKey = salvage.ItemKey, Quantity = 1 }, LifetimeSeconds = 30 }] }; - var run = new AetheriaRuntimeRunCheckpointCommit { CurrentZoneIndex = 0, CurrentEntityKey = "zone.0.entity.0", Zones = [zone] }; - var forbiddenCommand = AetheriaRuntimeDaemonCommandDocument.Create(AetheriaRuntimeDaemonCommandKinds.PickUpLoot, "pilot", "pickup-smoke", 0, "zone.0.entity.0"); - forbiddenCommand.CommandId = "pickup-command-forbidden"; - forbiddenCommand.TargetEntityKey = "zone.0.entity.0"; - var forbidden = AetheriaRuntimeDaemonOperations.Execute(run, [forbiddenCommand], new AetheriaRuntimeDaemonOperationContext { Catalog = catalog }); - Require(forbidden.RejectedCommandIds.Contains(forbiddenCommand.CommandId), - "client pickup commands must not own cargo collection"); - - AetheriaRuntimeDaemonTickRunner.Tick(Path.Combine(Path.GetTempPath(), "aetheria-pickup-outside-proximity.cc"), run, - new AetheriaRuntimeDaemonTickOptions { WorldPhysics = new ScriptedWorldPhysics(), FrameId = 1, FixedDeltaSeconds = 0.1, SimulationTimeSeconds = 0.1, Catalog = catalog, BuildPublications = false }); - RequireEqual(1, zone.DroppedPickups.Count, "pickup outside the daemon collection distance must remain in the world"); - RequireEqual(0, CargoQuantity(ship, salvage.ItemKey), "outside proximity must not mutate cargo"); - - zone.DroppedPickups.Single().PositionX = AetheriaRuntimeTractorMechanics.CollectionDistance; - AetheriaRuntimeDaemonTickRunner.Tick(Path.Combine(Path.GetTempPath(), "aetheria-pickup-proximity-dedup.cc"), run, - new AetheriaRuntimeDaemonTickOptions { WorldPhysics = new ScriptedWorldPhysics(), FrameId = 2, FixedDeltaSeconds = 0.1, SimulationTimeSeconds = 0.2, Catalog = catalog, BuildPublications = false }); - RequireEqual(0, zone.DroppedPickups.Count, "pickup at the 2D collection boundary must be consumed"); - RequireEqual(1, CargoQuantity(ship, salvage.ItemKey), "one pickup identity must commit cargo exactly once"); - RequireEqual(0, CargoQuantity(ally, salvage.ItemKey), - "a closer allied simulation ship must not steal pickup ownership from the current controlled ship"); - RequireEqual(1, run.GameEvents.Count(value => value.Kind == "pickup.collected" && value.PickupIndex == 7), - "one consumed pickup identity must emit one collection event"); - var collectionEvent = run.GameEvents.Single(value => value.Kind == "pickup.collected" && value.PickupIndex == 7); - RequireNear(1, collectionEvent.ScalarValue, 0.000001, - "collection feedback must publish the committed cargo delta"); - RequireNear(0, collectionEvent.AuxiliaryValue, 0.000001, - "collection feedback must publish cargo quantity before the transaction"); - RequireEqual(0, run.PickupContactReceipts.Count, - "proximity collection must not mint obsolete Ymir-contact receipts"); - var collectionFeedback = AetheriaRuntimeDaemonGameSurfaceBuilder.BuildReactiveGameplay( - new AetheriaRuntimeDaemonFrameDocument { FrameId = 3, Run = run }); - Require(Flatten(collectionFeedback.Surface.Root).Any(node => node.Kind == "feedback.event" && - node.Props["eventKind"] == "pickup.collected" && node.Props["pickupIndex"] == "7" && - node.Props["itemKey"] == salvage.ItemKey && node.Props["scalarValue"] == "1" && - node.Props["cargoQuantityBefore"] == "0" && node.Props["cargoQuantityAfter"] == "1"), - "Eve feedback must publish the exact cargo delta committed by daemon proximity"); - - zone.DroppedPickups = [new AetheriaRuntimeDroppedPickupCommit { PickupIndex = 8, PositionX = 100, Item = new AetheriaRuntimeLoadoutItemCommit { ItemKey = salvage.ItemKey, Quantity = 1 }, LifetimeSeconds = 30 }]; - AetheriaRuntimeDaemonTickRunner.Tick(Path.Combine(Path.GetTempPath(), "aetheria-pickup-expiry-smoke.cc"), run, - new AetheriaRuntimeDaemonTickOptions { WorldPhysics = new ScriptedWorldPhysics(), FrameId = 3, FixedDeltaSeconds = 30, SimulationTimeSeconds = 30, BuildPublications = false }); - RequireEqual(0, zone.DroppedPickups.Count, "pickup must expire after the fossil thirty-second lifetime"); - Require(run.GameEvents.Any(value => value.Kind == "pickup.expired" && value.PickupIndex == 8), - "daemon lifetime owner must emit authoritative pickup expiry event"); - } - - private static void TradePurchaseDerivesAcceptanceFromDaemonState() - { - var ore = CatalogItem("trade-ore"); - ore.Price = 40; - ore.Volume = 1; - ore.Stackable = true; - ore.MaxStack = 4; - ore.ShapeCells = [new AetheriaRuntimeShapeCell(0, 0)]; - var cargoBay = CatalogItem("trade-cargo-bay"); - cargoBay.InteriorShapeWidth = 3; - cargoBay.InteriorShapeHeight = 1; - cargoBay.InteriorOccupiedCells = 3; - cargoBay.InteriorShapeCells = - [ - new AetheriaRuntimeShapeCell(0, 0), - new AetheriaRuntimeShapeCell(1, 0), - new AetheriaRuntimeShapeCell(2, 0) - ]; - var catalog = new AetheriaRuntimeCatalogSnapshot([ore, cargoBay], [], []); - var station = Entity(0, 0, "station"); - station.Kind = "station"; - station.CargoContents = [Cargo((ore.ItemKey, 4, 2, 3), (ore.ItemKey, 4, 3, 3))]; - station.DockingBayAssignments = [1]; - station.ChildEntityIndices = [1]; - var ship = Entity(1, 0, "player"); - ship.CargoBays = [new AetheriaRuntimeLoadoutItemSlotCommit - { - Item = new AetheriaRuntimeLoadoutItemCommit { ItemKey = cargoBay.ItemKey, Quantity = 1 } - }]; - ship.CargoContents = [Cargo((ore.ItemKey, 3, 0, 0))]; - var zone = new AetheriaRuntimeZoneSnapshotCommit { ZoneIndex = 0, Entities = [station, ship] }; - var run = new AetheriaRuntimeRunCheckpointCommit - { - RunId = "trade-authority-smoke", - CurrentZoneIndex = 0, - CurrentEntityKey = "zone.0.entity.1", - Credits = 5000, - Zones = [zone] - }; - var command = AetheriaRuntimeDaemonCommandDocument.Create( - AetheriaRuntimeDaemonCommandKinds.TradePurchase, - "pilot", - "trade-authority-smoke", - 1, - run.CurrentEntityKey); - command.TradePurchase.ItemKey = ore.ItemKey; - command.TradePurchase.Quantity = 4; - command.TradePurchase.StationCargoIndex = 0; - command.TradePurchase.TargetCargoIndex = 0; - command.TradePurchase.SourceX = 2; - command.TradePurchase.SourceY = 3; - command.TradePurchase.PurchaseKind = "docked_ship"; - command.TradePurchase.UnitPrice = -999; - command.TradePurchase.TotalPrice = -999; - command.TradePurchase.StationEntityKey = "forged-station"; - command.TradePurchase.TargetEntityKey = "forged-target"; - command.TradePurchase.CreatesDockedShip = true; - - Require(AetheriaRuntimeRunCheckpointCommit.TryParseEntityKey(run.CurrentEntityKey, out _, out _), - "trade smoke current entity key must be daemon-resolvable"); - RequireEqual(0, AetheriaRuntimeCargoCapacityQueries.UnitsThatFit(ship, catalog, ore.ItemKey, 0), - "the old volume-only capacity proxy must expose why it cannot own spatial stack placement"); - var daemonUnitPrice = AetheriaRuntimeDaemonTradeItemQueries.TradeItemValue( - ore, - station.CargoContents[0].Items[0].Item, - catalog.TradeValueSettings).Price; - Require(daemonUnitPrice > 0, "trade smoke daemon catalog price must be positive"); - - var result = AetheriaRuntimeDaemonOperations.Execute( - run, - [command], - new AetheriaRuntimeDaemonOperationContext { Catalog = catalog }); - - Require(result.AppliedCommandIds.Contains(command.CommandId), - "daemon must accept a valid commodity purchase regardless of forged compatibility opinions"); - RequireEqual(5000 - daemonUnitPrice * 4, run.Credits, - "daemon catalog price must own trade credit mutation"); - RequireEqual(7, CargoQuantity(ship, ore.ItemKey), "daemon cargo capacity and placement must own delivery"); - Require(ship.CargoContents.Single().Items - .Where(slot => slot.Item.ItemKey == ore.ItemKey) - .Select(slot => (slot.X, slot.Y, slot.Item.Quantity)) - .SequenceEqual([(0, 0, 4), (1, 0, 3)]), - "a purchase must fill an existing stack before placing the remainder in the first authored free cell"); - RequireEqual(2, zone.Entities.Count, "forged ship-creation opinion must not materialize an entity"); - - AetheriaRuntimeDaemonCommandDocument Contender(string clientId, string commandId) - { - var contender = AetheriaRuntimeDaemonCommandDocument.Create( - AetheriaRuntimeDaemonCommandKinds.TradePurchase, - clientId, - run.RunId, - 2, - run.CurrentEntityKey); - contender.CommandId = commandId; - contender.TradePurchase.ItemKey = ore.ItemKey; - contender.TradePurchase.Quantity = 4; - contender.TradePurchase.StationCargoIndex = 0; - contender.TradePurchase.TargetCargoIndex = 0; - contender.TradePurchase.SourceX = 3; - contender.TradePurchase.SourceY = 3; - return contender; - } - - var firstClient = Contender("pilot-a", "trade-contested-a"); - var secondClient = Contender("pilot-b", "trade-contested-b"); - var creditsBeforeContest = run.Credits; - var contested = AetheriaRuntimeDaemonOperations.Execute( - run, - [firstClient, secondClient], - new AetheriaRuntimeDaemonOperationContext { Catalog = catalog }); - - Require(contested.AppliedCommandIds.SequenceEqual([firstClient.CommandId]), - "the daemon batch reducer must serialize contested stock to exactly one winner"); - Require(contested.RejectedCommandIds.SequenceEqual([secondClient.CommandId]), - "the command observing exhausted stock must be rejected exactly once"); - RequireEqual(AetheriaRuntimeDaemonRejectionReasons.TradeStockUnavailable, - contested.RejectedCommandReasons[secondClient.CommandId], - "contested stock must retain the exact daemon-owned rejection reason"); - RequireEqual(creditsBeforeContest - daemonUnitPrice * 4, run.Credits, - "contested stock must debit the shared run wallet exactly once"); - RequireEqual(11, CargoQuantity(ship, ore.ItemKey), - "contested stock must deliver exactly one winning stack quantity"); - RequireEqual(0, CargoQuantity(station, ore.ItemKey), - "the winning purchase must exhaust authoritative station stock"); - - var frame = AetheriaRuntimeDaemonFrameDocument.Create( - run, "daemon", "trade-contest", 2, 0.04, 0.02); - frame.AppliedCommandIds = contested.AppliedCommandIds; - frame.RejectedCommandIds = contested.RejectedCommandIds; - frame.RejectedCommandReasons = contested.RejectedCommandReasons; - var winnerFact = AetheriaRuntimeCommittedCommandFactDocument.FromAppliedCommand( - frame, firstClient, "aetheria.local"); - var loserFact = AetheriaRuntimeCommittedCommandFactDocument.FromRejectedCommand( - frame, secondClient, "aetheria.local"); - var winnerReceipt = AetheriaRuntimeDaemonReceiptProjector.Project( - winnerFact, AetheriaRuntimeDaemonGameSurfaceBuilder.SurfaceId); - var loserReceipt = AetheriaRuntimeDaemonReceiptProjector.Project( - loserFact, AetheriaRuntimeDaemonGameSurfaceBuilder.SurfaceId); - RequireEqual("reconciled", winnerReceipt.State, - "the winning client must receive an authoritative generic Eve receipt"); - Require(loserReceipt.State == "denied" && - loserReceipt.Message.Contains( - AetheriaRuntimeDaemonRejectionReasons.TradeStockUnavailable, - StringComparison.Ordinal), - "the losing client must receive the exact contested-stock reason through generic Eve"); - - var root = Path.Combine(Path.GetTempPath(), "aetheria-trade-contest-" + Guid.NewGuid().ToString("N")); - Directory.CreateDirectory(root); - var statePath = Path.Combine(root, "world.cc"); - try - { - using (var node = AetheriaStateNode.OpenAsync(statePath).GetAwaiter().GetResult()) - { - node.MutableDocument( - AetheriaRuntimeVerseRecordKeys.DaemonFrameLatest) - .ReplaceAsync(frame).GetAwaiter().GetResult(); - node.MutableDocument( - new CultRecordKey(AetheriaRuntimeCommittedCommandFactDocument.CreateRecordKey(winnerFact.FactId))) - .ReplaceAsync(winnerFact).GetAwaiter().GetResult(); - node.MutableDocument( - new CultRecordKey(AetheriaRuntimeCommittedCommandFactDocument.CreateRecordKey(loserFact.FactId))) - .ReplaceAsync(loserFact).GetAwaiter().GetResult(); - node.FlushAsync(soft: false).GetAwaiter().GetResult(); - } - - using (var node = AetheriaStateNode.OpenAsync(statePath).GetAwaiter().GetResult()) - { - var durableFrame = node.MutableDocument( - AetheriaRuntimeVerseRecordKeys.DaemonFrameLatest) - .ReadAsync().GetAwaiter().GetResult() - ?? throw new InvalidOperationException("Durable contested-trade frame is missing."); - var durableWinner = node.MutableDocument( - new CultRecordKey(AetheriaRuntimeCommittedCommandFactDocument.CreateRecordKey(winnerFact.FactId))) - .ReadAsync().GetAwaiter().GetResult() - ?? throw new InvalidOperationException("Durable winning trade fact is missing."); - var durableLoser = node.MutableDocument( - new CultRecordKey(AetheriaRuntimeCommittedCommandFactDocument.CreateRecordKey(loserFact.FactId))) - .ReadAsync().GetAwaiter().GetResult() - ?? throw new InvalidOperationException("Durable rejected trade fact is missing."); - var durableStation = durableFrame.Run.Zones.Single().Entities.Single(entity => entity.EntityIndex == 0); - var durableShip = durableFrame.Run.Zones.Single().Entities.Single(entity => entity.EntityIndex == 1); - RequireEqual(run.Credits, durableFrame.Run.Credits, - "CultCache reconnect must preserve the single authoritative debit"); - RequireEqual(0, CargoQuantity(durableStation, ore.ItemKey), - "CultCache reconnect must not resurrect contested station stock"); - RequireEqual(11, CargoQuantity(durableShip, ore.ItemKey), - "CultCache reconnect must preserve the winning cargo delivery exactly once"); - RequireEqual("reconciled", AetheriaRuntimeDaemonReceiptProjector.Project( - durableWinner, AetheriaRuntimeDaemonGameSurfaceBuilder.SurfaceId).State, - "the winning generic Eve receipt must survive reconnect"); - RequireEqual(AetheriaRuntimeDaemonRejectionReasons.TradeStockUnavailable, - durableLoser.RejectionReason, - "the losing generic Eve receipt reason must survive reconnect"); - } - } - finally - { - if (Directory.Exists(root)) - Directory.Delete(root, recursive: true); - } - } - - private static void TradeSaleIsDaemonPricedSpatialAndAtomic() - { - var ore = CatalogItem("sale-ore"); - ore.Price = 40; - ore.Stackable = true; - ore.MaxStack = 10; - ore.ShapeWidth = 1; - ore.ShapeHeight = 1; - ore.ShapeCells = [new AetheriaRuntimeShapeCell(0, 0)]; - var cargoBay = CatalogItem("sale-cargo-bay"); - cargoBay.InteriorShapeWidth = 2; - cargoBay.InteriorShapeHeight = 1; - cargoBay.InteriorShapeCells = - [ - new AetheriaRuntimeShapeCell(0, 0), - new AetheriaRuntimeShapeCell(1, 0) - ]; - var catalog = new AetheriaRuntimeCatalogSnapshot([ore, cargoBay], [], []); - - var station = Entity(0, 0, "station"); - station.Kind = "station"; - station.CargoBays = [new AetheriaRuntimeLoadoutItemSlotCommit - { - Item = new AetheriaRuntimeLoadoutItemCommit { ItemKey = cargoBay.ItemKey, Quantity = 1 } - }]; - station.CargoContents = [Cargo((ore.ItemKey, 8, 0, 0))]; - station.DockingBayAssignments = [1]; - - var ship = Entity(1, 0, "player"); - ship.CargoBays = [new AetheriaRuntimeLoadoutItemSlotCommit - { - Item = new AetheriaRuntimeLoadoutItemCommit { ItemKey = cargoBay.ItemKey, Quantity = 1 } - }]; - ship.CargoContents = [new AetheriaRuntimeCargoBayLoadoutCommit - { - Items = [new AetheriaRuntimeLoadoutItemSlotCommit - { - X = 0, - Y = 0, - Item = new AetheriaRuntimeLoadoutItemCommit - { - ItemKey = ore.ItemKey, - Quantity = 5, - Quality = 0.75, - Temperature = 333 - } - }] - }]; - - var run = new AetheriaRuntimeRunCheckpointCommit - { - RunId = "trade-sale-smoke", - CurrentZoneIndex = 0, - CurrentEntityKey = "zone.0.entity.1", - Credits = 100, - Zones = [new AetheriaRuntimeZoneSnapshotCommit { ZoneIndex = 0, Entities = [station, ship] }] - }; - var sale = AetheriaRuntimeDaemonCommandDocument.Create( - AetheriaRuntimeDaemonCommandKinds.TradeSale, - "pilot", - "trade-sale-smoke", - 1, - run.CurrentEntityKey); - sale.TradeSale.ItemKey = ore.ItemKey; - sale.TradeSale.Quantity = 3; - sale.TradeSale.SourceCargoIndex = 0; - sale.TradeSale.SourceX = 0; - sale.TradeSale.SourceY = 0; - - var unitPrice = AetheriaRuntimeDaemonTradeItemQueries.TradeItemValue( - ore, - ship.CargoContents[0].Items[0].Item, - catalog.TradeValueSettings).Price; - var result = AetheriaRuntimeDaemonOperations.Execute( - run, - [sale], - new AetheriaRuntimeDaemonOperationContext { Catalog = catalog }); - - Require(result.AppliedCommandIds.Contains(sale.CommandId), - "valid docked sale must commit through the daemon economy transaction"); - RequireEqual(100 + unitPrice * 3, run.Credits, - "sale payout must be derived from the authoritative source instance and catalog"); - RequireEqual(2, CargoQuantity(ship, ore.ItemKey), - "sale must remove exactly the accepted quantity from seller cargo"); - RequireEqual(11, CargoQuantity(station, ore.ItemKey), - "station stock must receive the exact sold quantity"); - RequireEqual(10, station.CargoContents[0].Items[0].Item.Quantity, - "station stock must fill the first compatible stack before allocating a new cell"); - var allocated = station.CargoContents[0].Items.Single(slot => slot.X == 1 && slot.Y == 0); - RequireEqual(1, allocated.Item.Quantity, - "station stock overflow must use the first free authored cargo cell"); - RequireNear(0.75, allocated.Item.Quality, 0.000001, - "sale placement must preserve source instance quality"); - RequireNear(333, allocated.Item.Temperature, 0.000001, - "sale placement must preserve source instance temperature"); - - station.CargoContents[0].Items[1].Item.Quantity = 10; - var rejectedSale = AetheriaRuntimeDaemonCommandDocument.Create( - AetheriaRuntimeDaemonCommandKinds.TradeSale, - "pilot", - "trade-sale-smoke", - 2, - run.CurrentEntityKey); - rejectedSale.TradeSale.ItemKey = ore.ItemKey; - rejectedSale.TradeSale.Quantity = 1; - rejectedSale.TradeSale.SourceCargoIndex = 0; - rejectedSale.TradeSale.SourceX = 0; - rejectedSale.TradeSale.SourceY = 0; - var creditsBeforeRejection = run.Credits; - result = AetheriaRuntimeDaemonOperations.Execute( - run, - [rejectedSale], - new AetheriaRuntimeDaemonOperationContext { Catalog = catalog }); - - Require(result.RejectedCommandIds.Contains(rejectedSale.CommandId), - "station stock without spatial capacity must reject the sale"); - RequireEqual(AetheriaRuntimeDaemonRejectionReasons.TradeStationNoFit, - result.RejectedCommandReasons[rejectedSale.CommandId], - "full station stock must expose the exact trade rejection reason"); - RequireEqual(creditsBeforeRejection, run.Credits, - "rejected sale must not credit the seller"); - RequireEqual(2, CargoQuantity(ship, ore.ItemKey), - "rejected sale must not remove seller cargo"); - } - - private static void StationBodiesCannotConsumePickups() - { - var hull = CatalogItem("station-filter-hull"); hull.HullCapacity = PerformanceStat(1); - var salvage = CatalogItem("station-filter-salvage"); salvage.Volume = 1; - var cargoBay = CatalogItem("station-filter-cargo-bay"); cargoBay.InteriorOccupiedCells = 1; - var catalog = new AetheriaRuntimeCatalogSnapshot([hull, salvage, cargoBay], [], []); - var station = Entity(0, 0, "player"); - station.Kind = "station"; - station.HullItemKey = hull.ItemKey; - station.CargoBays = [new AetheriaRuntimeLoadoutItemSlotCommit - { - Item = new AetheriaRuntimeLoadoutItemCommit { ItemKey = cargoBay.ItemKey, Quantity = 1 } - }]; - station.CargoContents = [Cargo()]; - var ship = Entity(1, 200, "player"); - var run = new AetheriaRuntimeRunCheckpointCommit - { - CurrentZoneIndex = 0, - CurrentEntityKey = "zone.0.entity.1", - Zones = [new AetheriaRuntimeZoneSnapshotCommit - { - ZoneIndex = 0, - Entities = [station, ship], - DroppedPickups = [new AetheriaRuntimeDroppedPickupCommit - { - PickupIndex = 9, - PositionX = 0, - Item = new AetheriaRuntimeLoadoutItemCommit { ItemKey = salvage.ItemKey, Quantity = 1 }, - LifetimeSeconds = 30 - }] - }] - }; - - using var physics = NewPhysics(); - AetheriaRuntimeDaemonTickRunner.Tick( - Path.Combine(Path.GetTempPath(), "aetheria-pickup-station-filter.cc"), - run, - new AetheriaRuntimeDaemonTickOptions - { - WorldPhysics = physics, - FrameId = 1, - FixedDeltaSeconds = 0.1, - SimulationTimeSeconds = 0.1, - Catalog = catalog, - BuildPublications = false - }); - - RequireEqual(1, run.Zones[0].DroppedPickups.Count, - "station world bodies must not consume ship-scoop pickups"); - RequireEqual(0, CargoQuantity(station, salvage.ItemKey), - "station cargo must remain unchanged because only ships collect by proximity"); - } - - private static void PickupProximityCollectsOrBounces() - { - var hull = CatalogItem("contact-hull"); hull.HullCapacity = PerformanceStat(1); - var salvage = CatalogItem("contact-salvage"); salvage.Volume = 1; - var cargoBay = CatalogItem("contact-cargo-bay"); cargoBay.InteriorOccupiedCells = 1; - var catalog = new AetheriaRuntimeCatalogSnapshot([hull, salvage, cargoBay], [], []); - AetheriaRuntimeRunCheckpointCommit Scenario(bool full) - { - var ship = Entity(0, 0, "player"); ship.HullItemKey = hull.ItemKey; - ship.CargoBays = [new AetheriaRuntimeLoadoutItemSlotCommit { Item = new AetheriaRuntimeLoadoutItemCommit { ItemKey = cargoBay.ItemKey, Quantity = 1 } }]; - ship.CargoContents = [full ? Cargo((salvage.ItemKey, 1, 0, 0)) : Cargo()]; - return new AetheriaRuntimeRunCheckpointCommit { CurrentZoneIndex = 0, CurrentEntityKey = "zone.0.entity.0", Zones = [new AetheriaRuntimeZoneSnapshotCommit { ZoneIndex = 0, Entities = [ship], DroppedPickups = [new AetheriaRuntimeDroppedPickupCommit { PickupIndex = 10, PositionX = 20, Item = new AetheriaRuntimeLoadoutItemCommit { ItemKey = salvage.ItemKey, Quantity = 1 }, LifetimeSeconds = 30 }] }] }; - } - var open = Scenario(false); - using var openPhysics = NewPhysics(); - AetheriaRuntimeDaemonTickRunner.Tick(Path.Combine(Path.GetTempPath(), "aetheria-pickup-proximity-open.cc"), open, - new AetheriaRuntimeDaemonTickOptions { WorldPhysics = openPhysics, FrameId = 1, FixedDeltaSeconds = 0.1, SimulationTimeSeconds = 0.1, Catalog = catalog, BuildPublications = false }); - RequireEqual(0, open.Zones[0].DroppedPickups.Count, "2D ship proximity with capacity must collect pickup automatically"); - RequireEqual(1, CargoQuantity(open.Zones[0].Entities[0], salvage.ItemKey), "proximity collection must commit cargo once"); - RequireEqual(1, open.GameEvents.Count(value => value.Kind == "pickup.collected" && value.PickupIndex == 10), - "proximity collection must emit one stable event"); - RequireEqual(0, open.PickupContactReceipts.Count, - "proximity collection must not depend on a Box3D Begin fact"); - - var full = Scenario(true); - using var fullPhysics = NewPhysics(); - AetheriaRuntimeDaemonTickRunner.Tick(Path.Combine(Path.GetTempPath(), "aetheria-pickup-proximity-full.cc"), full, - new AetheriaRuntimeDaemonTickOptions { WorldPhysics = fullPhysics, FrameId = 1, FixedDeltaSeconds = 0.1, SimulationTimeSeconds = 0.1, Catalog = catalog, BuildPublications = false }); - RequireEqual(1, full.Zones[0].DroppedPickups.Count, "full hold must leave the nearby pickup alive"); - Require(full.Zones[0].DroppedPickups[0].VelocityX > 20, "failed pickup must receive the fossil outward kick"); - var rejectedPickup = full.Zones[0].DroppedPickups[0]; - var rejectedShip = full.Zones[0].Entities[0]; - Require(Math.Sqrt( - Math.Pow(rejectedPickup.PositionX - rejectedShip.PositionX, 2) + - Math.Pow(rejectedPickup.PositionZ - rejectedShip.PositionZ, 2)) > - AetheriaRuntimeTractorMechanics.CollectionDistance, - "capacity rejection must move the pickup outside collection proximity before the next step"); - RequireEqual(1, full.GameEvents.Count(value => value.Kind == "pickup.rejected" && value.PickupIndex == 10), - "capacity rejection must emit one stable event"); - var rejectionEvent = full.GameEvents.Single(value => value.Kind == "pickup.rejected" && value.PickupIndex == 10); - RequireEqual("cargo-capacity", rejectionEvent.Reason, - "capacity rejection feedback must name the provider-owned reason"); - RequireNear(1, rejectionEvent.AuxiliaryValue, 0.000001, - "capacity rejection feedback must preserve the pre-collection cargo quantity"); - RequireEqual(0, full.PickupContactReceipts.Count, - "capacity rejection must not mint obsolete contact receipts"); - AetheriaRuntimeDaemonTickRunner.Tick(Path.Combine(Path.GetTempPath(), "aetheria-pickup-proximity-full-second.cc"), full, - new AetheriaRuntimeDaemonTickOptions { WorldPhysics = fullPhysics, FrameId = 2, FixedDeltaSeconds = 0.1, SimulationTimeSeconds = 0.2, Catalog = catalog, BuildPublications = false }); - RequireEqual(1, full.GameEvents.Count(value => value.Kind == "pickup.rejected" && value.PickupIndex == 10), - "the outward kick must prevent an immediate second rejection event"); - var feedback = AetheriaRuntimeDaemonGameSurfaceBuilder.BuildReactiveGameplay( - new AetheriaRuntimeDaemonFrameDocument { FrameId = 1, Run = full }); - Require(Flatten(feedback.Surface.Root).Any(node => node.Kind == "feedback.event" && - node.Props["eventKind"] == "pickup.rejected" && node.Props["pickupIndex"] == "10" && - node.Props["reason"] == "cargo-capacity" && - node.Props["cargoQuantityBefore"] == "1" && node.Props["cargoQuantityAfter"] == "1"), - "Eve surface must project authoritative pickup feedback chronology"); - } - - private static (AetheriaRuntimeRunCheckpointCommit Run, AetheriaRuntimeZoneSnapshotCommit Zone, AetheriaRuntimeEntitySnapshotCommit Target) Scenario() - { - var source = Entity(1, -100, "player"); - var target = Entity(2, 30, "enemy"); - var zone = new AetheriaRuntimeZoneSnapshotCommit - { - ZoneIndex = 0, - Entities = [source, target], - PhysicalPayloads = - [ - new AetheriaRuntimePhysicalPayloadCommit - { - PayloadId = "smoke-projectile", - SourceEntityIndex = 1, - TargetEntityIndex = 2, - PositionX = 0, - PositionZ = 0, - VelocityX = 100, - VelocityY = 0, - Radius = 1, - ContactMagnitude = 12, - LifetimeSeconds = 5, - Active = true - } - ] - }; - var run = new AetheriaRuntimeRunCheckpointCommit - { - RunId = "ymir-projectile-smoke", - CurrentZoneIndex = 0, - CurrentEntityKey = "zone.0.entity.1", - Zones = [zone] - }; - return (run, zone, target); - } - - private static AetheriaRuntimeEntitySnapshotCommit Entity(int index, double x, string faction) => new() - { - EntityIndex = index, - EntityId = $"smoke.entity.{index}", - Kind = "ship", - FactionKey = faction, - PositionX = x, - PositionZ = 0, - TargetEntityIndex = -1, - IsActive = true, - StatGrids = [Grid("hull", 100), Grid("shield", 0), Grid("heat", 0)] - }; - - private static AetheriaRuntimeEntityStatGridCommit Grid(string name, double value) => new() - { - Name = name, - Width = 1, - Height = 1, - Values = [value] - }; - - private static AetheriaRuntimeEntityStatGridCommit Grid(string name, int width, int height, params double[] values) => new() - { - Name = name, - Width = width, - Height = height, - Values = values - }; - - private static double GridValue(AetheriaRuntimeEntitySnapshotCommit entity, string name, int index) => - entity.StatGrids.Single(grid => string.Equals(grid.Name, name, StringComparison.OrdinalIgnoreCase)).Values[index]; - - private static IEnumerable Flatten(AetheriaRuntimeSurfaceComponent root) - { - yield return root; - foreach (var child in root.Children) - foreach (var descendant in Flatten(child)) - yield return descendant; - } - - private static double Stat(AetheriaRuntimeEntitySnapshotCommit entity, string name) => - entity.StatGrids.Single(grid => string.Equals(grid.Name, name, StringComparison.OrdinalIgnoreCase)).Values[0]; - - private static void RequireEqual(T expected, T actual, string message) - { - if (!EqualityComparer.Default.Equals(expected, actual)) - throw new InvalidOperationException($"{message}. Expected {expected}; actual {actual}."); - } - - private static void Require(bool condition, string message) - { - if (!condition) - throw new InvalidOperationException(message); - } - - private static void RequireNear(double expected, double actual, double tolerance, string message) - { - if (Math.Abs(expected - actual) > tolerance) - throw new InvalidOperationException($"{message}. Expected {expected}; actual {actual}."); - } - - private static void CultCacheRestartPreservesLoadoutAndBehaviorState() - { - var root = Path.Combine(Path.GetTempPath(), "aetheria-loadout-restart-" + Guid.NewGuid().ToString("N")); - Directory.CreateDirectory(root); - var statePath = Path.Combine(root, "world.cc"); - try - { - var controller = CatalogItem("reconnect-controller", - new AetheriaRuntimeBehaviorPayload(0, "Thermotoggle", 0, - [ - new AetheriaRuntimeBehaviorField(1, Number(300)), - new AetheriaRuntimeBehaviorField(2, BoolValue(false)), - new AetheriaRuntimeBehaviorField(3, BoolValue(true)) - ])); - controller.Name = "Reconnect Controller"; - var hull = HullCatalogItem("reconnect-hull", 9, 7, 100); - var cargoBay = CatalogItem("reconnect-cargo-bay"); - cargoBay.InteriorShapeWidth = 6; - cargoBay.InteriorShapeHeight = 4; - var addedModule = CatalogItem("reconnect-added-module"); - addedModule.ShapeWidth = 1; - addedModule.ShapeHeight = 1; - addedModule.OccupiedCells = 1; - addedModule.ShapeCells = [new AetheriaRuntimeShapeCell(0, 0)]; - addedModule.HardpointType = ""; - var catalog = new AetheriaRuntimeCatalogSnapshot( - [hull, cargoBay, controller, addedModule], [], []); - var ship = Entity(0, 0, "player"); - ship.HullItemKey = hull.ItemKey; - ship.Equipment = - [ - new AetheriaRuntimeLoadoutItemSlotCommit - { - X = 3, - Y = 4, - Rotation = "CounterClockwise", - Item = new AetheriaRuntimeLoadoutItemCommit - { - ItemKey = controller.ItemKey, - Quality = 0.73, - Durability = 0.61, - Quantity = 1, - Enabled = true, - OverrideShutdown = true, - Temperature = 417 - } - } - ]; - ship.CargoBays = - [ - new AetheriaRuntimeLoadoutItemSlotCommit - { - Item = new AetheriaRuntimeLoadoutItemCommit - { - ItemKey = cargoBay.ItemKey, - Quantity = 1, - Quality = 1, - Durability = 1, - Enabled = true - } - } - ]; - ship.CargoContents = [Cargo(("reconnect-salvage", 7, 2, 1))]; - ship.WeaponGroups = [new[] { 0 }]; - ship.ActiveWeaponGroups = [true]; - ship.EquipmentStates = - [ - new AetheriaRuntimeEquipmentStateCommit - { - EquipmentIndex = 0, - Temperature = 417, - PreviousTemperature = 410, - ThermalPerformance = 0.8, - DurabilityPerformance = 0.61, - Wear = 0.12, - ThermalOnline = true, - DurabilityOnline = true, - Online = true - } - ]; - ship.BehaviorStates = - [ - new AetheriaRuntimeBehaviorStateCommit - { - OwnerKind = AetheriaRuntimeBehaviorStateProjector.EquipmentOwnerKind, - OwnerIndex = 0, - BehaviorIndex = 0, - BehaviorKind = "Thermotoggle", - ThermotoggleTargetTemperature = 412, - ChainReached = true, - ChainSucceeded = true, - ChainCompleted = true - } - ]; - ship.WeaponStates = - [ - new AetheriaRuntimeWeaponStateCommit - { - OwnerKind = "equipment", - OwnerIndex = 0, - BehaviorIndex = 1, - BehaviorKind = AetheriaRuntimeBehaviorKinds.InstantWeapon, - Ammo = 9, - CooldownProgress = 0.37, - LockProgress = 0.82, - LockTargetEntityIndex = 4, - ShotSequence = 23 - } - ]; - ship.ActiveConsumables = - [ - new AetheriaRuntimeActiveConsumableCommit - { - EffectId = "effect:reconnect-tonic:1", - ItemKey = "reconnect-tonic", - Quality = 0.67, - Duration = 30, - RemainingDuration = 12, - BehaviorStates = - [ - new AetheriaRuntimeConsumableBehaviorStateCommit - { - BehaviorId = "tonic-modifier", - BehaviorIndex = 0, - BehaviorKind = AetheriaRuntimeBehaviorKinds.StatModifier, - ScalarState = 0.42, - StatModifierApplied = true, - StatModifierExecuted = true, - StatModifierTargetStatCount = 3 - } - ] - } - ]; - ship.LoadoutGeneration = new AetheriaRuntimeLoadoutGenerationReceiptCommit - { - Seed = 0xA37E, - SourceZoneIndex = 2, - AvailabilityFactionKey = "player", - PriceExponent = 0.25, - Selections = - [ - new AetheriaRuntimeLoadoutGenerationSelectionCommit - { - Role = "controller", - ItemKey = controller.ItemKey, - ManufacturerKey = "player", - Price = 1200, - ManufacturerDistance = 1, - Allegiance = 0.9 - } - ] - }; - var station = Entity(1, 0, "player"); - station.Kind = "station"; - station.DockingBayAssignments = [ship.EntityIndex]; - station.CargoBays = - [ - new AetheriaRuntimeLoadoutItemSlotCommit - { - Item = new AetheriaRuntimeLoadoutItemCommit - { - ItemKey = cargoBay.ItemKey, - Quantity = 1, - Quality = 1, - Durability = 1, - Enabled = true - } - } - ]; - station.CargoContents = - [ - Cargo((addedModule.ItemKey, 1, 0, 0)) - ]; - var run = new AetheriaRuntimeRunCheckpointCommit - { - RunId = "loadout-restart-smoke", - CurrentZoneIndex = 0, - Zones = [new AetheriaRuntimeZoneSnapshotCommit { ZoneIndex = 0, Entities = [ship, station] }] - }; - run.CurrentEntityKey = run.EntityRecordKey(0, ship.EntityIndex); - var frame = AetheriaRuntimeDaemonFrameDocument.Create( - run, "smoke", "loadout-restart", 41, 4.1, 0.1); - var shipKey = run.EntityRecordKey(0, ship.EntityIndex); - var stationKey = run.EntityRecordKey(0, station.EntityIndex); - var surface = AetheriaRuntimeDaemonGameSurfaceBuilder.Build( - frame, - new AetheriaRuntimeDaemonHealthDocument(), - AetheriaRuntimeDaemonCommandBoundaryDocument.Create("smoke"), - catalog: catalog); - var equipOperation = AetheriaRuntimeDaemonSurfaceCommandCatalog.CommandName( - AetheriaRuntimeDaemonCommandKinds.EquipItem); - Require(surface.Commands.Any(command => command.Command == equipOperation), - "docked Eve surface must advertise the generic equip operation before reconnect"); - var request = new EveSurfaceCommandRequest( - "aetheria", - AetheriaRuntimeDaemonGameSurfaceBuilder.PilotSurfaceId, - CultMesh.OperationInvocation(equipOperation), - CultMesh.OperationPayload( - ("entityId", shipKey), - ("sourceKind", AetheriaRuntimeRefitSourceKinds.Cargo), - ("originEntityKey", stationKey), - ("originIndex", "0"), - ("destinationEntityKey", shipKey), - ("itemKey", addedModule.ItemKey), - ("sourceX", "0"), - ("sourceY", "0"), - ("destinationX", "5"), - ("destinationY", "2"), - ("hasDestinationPosition", "true")), - DateTimeOffset.UtcNow, - "loadout-restart-smoke"); - Require(AetheriaRuntimeDaemonOperationsClient.TryCreateSurfaceCommandDocument( - request, - frame, - ".", - "loadout-restart-smoke", - "refit", - out var equipCommand) && equipCommand != null, - "generic Eve equip request must translate into a typed daemon command"); - var refit = AetheriaRuntimeDaemonOperations.Execute( - run, - [equipCommand!], - new AetheriaRuntimeDaemonOperationContext { Catalog = catalog }); - Require(refit.AppliedCommandIds.Contains(equipCommand!.CommandId, StringComparer.Ordinal), - "generic Eve equip command must commit through the atomic daemon refit transaction: " + - string.Join(",", refit.RejectedCommandReasons.Values)); - Require(ship.Equipment.Any(slot => slot.Item.ItemKey == addedModule.ItemKey && - slot.X == 5 && slot.Y == 2), - "accepted generic refit must place the exact module in daemon world truth"); - RequireEqual(0, station.CargoContents.Single().Items.Count, - "accepted generic refit must remove the exact source item atomically"); - - using (var node = AetheriaStateNode.OpenAsync(statePath).GetAwaiter().GetResult()) - { - node.MutableDocument( - AetheriaRuntimeVerseRecordKeys.DaemonFrameLatest) - .ReplaceAsync(frame).GetAwaiter().GetResult(); - node.FlushAsync(soft: false).GetAwaiter().GetResult(); - } - - using (var node = AetheriaStateNode.OpenAsync(statePath).GetAwaiter().GetResult()) - { - var durable = node.MutableDocument( - AetheriaRuntimeVerseRecordKeys.DaemonFrameLatest) - .ReadAsync().GetAwaiter().GetResult() - ?? throw new InvalidOperationException("Durable loadout restart frame is missing."); - var restored = durable.Run.Zones.Single().Entities.Single(entity => entity.EntityIndex == ship.EntityIndex); - var restoredStation = durable.Run.Zones.Single().Entities.Single(entity => entity.EntityIndex == station.EntityIndex); - var installed = restored.Equipment.Single(slot => slot.Item.ItemKey == controller.ItemKey); - RequireEqual("reconnect-hull", restored.HullItemKey, - "CultCache restart must preserve daemon-owned hull identity"); - Require(installed.X == 3 && installed.Y == 4 && installed.Rotation == "CounterClockwise", - "CultCache restart must preserve exact equipment placement and rotation"); - RequireNear(0.73, installed.Item.Quality, 0.000001, - "CultCache restart must preserve installed item quality"); - RequireNear(0.61, installed.Item.Durability, 0.000001, - "CultCache restart must preserve installed item durability"); - Require(installed.Item.OverrideShutdown && installed.Item.Temperature == 417, - "CultCache restart must preserve per-instance thermal controls"); - RequireEqual(7, restored.CargoContents.Single().Items.Single().Item.Quantity, - "CultCache restart must preserve cargo stack quantity"); - Require(restored.WeaponGroups.Single().SequenceEqual(new[] { 0 }) && - restored.ActiveWeaponGroups.SequenceEqual(new[] { true }), - "CultCache restart must preserve weapon membership and held state"); - RequireNear(0.12, restored.EquipmentStates.Single().Wear, 0.000001, - "CultCache restart must preserve equipment wear state"); - RequireNear(412, restored.BehaviorStates.Single().ThermotoggleTargetTemperature, 0.000001, - "CultCache restart must preserve executable behavior state"); - Require(restored.BehaviorStates.Single().ChainCompleted, - "CultCache restart must preserve specialized chain chronology"); - Require(restored.WeaponStates.Single().ShotSequence == 23 && - restored.WeaponStates.Single().Ammo == 9, - "CultCache restart must preserve weapon chronology and ammunition"); - var consumableState = restored.ActiveConsumables.Single().BehaviorStates.Single(); - Require(consumableState.StatModifierApplied && consumableState.StatModifierExecuted && - consumableState.StatModifierTargetStatCount == 3, - "CultCache restart must preserve active consumable execution state"); - Require(restored.LoadoutGeneration?.Selections.Single().ItemKey == controller.ItemKey, - "CultCache restart must preserve generated-loadout provenance"); - Require(restored.Equipment.Any(slot => slot.Item.ItemKey == addedModule.ItemKey && - slot.X == 5 && slot.Y == 2), - "CultCache restart must preserve the generic Eve refit's accepted placement"); - RequireEqual(0, restoredStation.CargoContents.Single().Items.Count, - "CultCache restart must not resurrect the generic refit's consumed source item"); - - var capability = AetheriaRuntimeInputCapabilityDocument.FromFrame( - durable, catalog: catalog); - RequireEqual("1", capability.Actions.Single(action => - action.ActionId == "weapon-group.0.fire").Payload["currentValue"], - "generic Eve reconnect must observe the daemon-owned held weapon state"); - RequireNear(412, capability.Actions.Single(action => - action.ActionId == "equipment.0.behavior.0").InputValue?.CurrentValue ?? double.NaN, - 0.000001, - "generic Eve reconnect must observe the daemon-owned thermostat target"); - } - } - finally - { - if (Directory.Exists(root)) - Directory.Delete(root, recursive: true); - } - } - - private static void PausedFirstFrameCreatesPersistableYmirWorld() - { - var root = Path.Combine(Path.GetTempPath(), "aetheria-ymir-paused-first-frame-" + Guid.NewGuid().ToString("N")); - Directory.CreateDirectory(root); - try - { - var statePath = Path.Combine(root, "world.cc"); - var run = new AetheriaRuntimeRunCheckpointCommit - { - RunId = "paused-first-frame-smoke", - CurrentZoneIndex = 0, - CurrentEntityKey = "zone.0.entity.0", - Zones = [new AetheriaRuntimeZoneSnapshotCommit - { - ZoneIndex = 0, - Entities = [Entity(0, 0, "player")] - }] - }; - var frame = new AetheriaRuntimeDaemonFrameDocument - { - FrameId = 0, - FixedDeltaSeconds = 0.1, - SimulationTimeSeconds = 0, - SimulationStepsExecuted = 0, - Run = run - }; - using var physics = new AetheriaYmirWorldPhysics(); - using var node = AetheriaStateNode.OpenAsync(statePath).GetAwaiter().GetResult(); - using var persistence = AetheriaYmirPersistenceCoordinator.OpenAsync(node, physics, null) - .GetAwaiter().GetResult(); - - var captures = persistence.Capture(frame); - - RequireEqual(1, captures.Count, - "a paused first frame must still create one persistable Ymir zone world"); - RequireEqual(0L, captures[0].FrameId, - "the paused Ymir persistence capture must belong to the authoritative first frame"); - RequireEqual(-1, captures[0].SimulationStepIndex, - "a paused first frame must retain the no-step persistence cursor"); - } - finally - { - if (Directory.Exists(root)) - Directory.Delete(root, recursive: true); - } - } - - private static void PickupRemovalSurvivesRestartWithoutReplay() - { - var root = Path.Combine(Path.GetTempPath(), "aetheria-ymir-restart-" + Guid.NewGuid().ToString("N")); - Directory.CreateDirectory(root); - var statePath = Path.Combine(root, "world.cc"); - try - { - var hull = CatalogItem("restart-pickup-hull"); hull.HullCapacity = PerformanceStat(1); - var salvage = CatalogItem("restart-pickup-salvage"); salvage.Volume = 1; - var cargoBay = CatalogItem("restart-pickup-cargo-bay"); cargoBay.InteriorOccupiedCells = 1; - var catalog = new AetheriaRuntimeCatalogSnapshot([hull, salvage, cargoBay], [], []); - var ship = Entity(0, 0, "player"); ship.HullItemKey = hull.ItemKey; - ship.CargoBays = [new AetheriaRuntimeLoadoutItemSlotCommit - { - Item = new AetheriaRuntimeLoadoutItemCommit { ItemKey = cargoBay.ItemKey, Quantity = 1 } - }]; - ship.CargoContents = [Cargo()]; - var run = new AetheriaRuntimeRunCheckpointCommit - { - RunId = "pickup-restart-proximity-smoke", - CurrentZoneIndex = 0, - CurrentEntityKey = "zone.0.entity.0", - Zones = [new AetheriaRuntimeZoneSnapshotCommit - { - ZoneIndex = 0, - Entities = [ship], - DroppedPickups = [new AetheriaRuntimeDroppedPickupCommit - { - PickupIndex = 10, - PositionX = 20, - Item = new AetheriaRuntimeLoadoutItemCommit { ItemKey = salvage.ItemKey, Quantity = 1 }, - LifetimeSeconds = 30 - }] - }] - }; - var frame = new AetheriaRuntimeDaemonFrameDocument - { - FrameId = 1, - FixedDeltaSeconds = 0.1, - SimulationTimeSeconds = 0.1, - Run = run - }; - - using (var physics = new AetheriaYmirWorldPhysics()) - { - AetheriaRuntimeDaemonTickRunner.Tick(statePath, run, - new AetheriaRuntimeDaemonTickOptions - { - WorldPhysics = physics, - FrameId = 1, - FixedDeltaSeconds = 0.1, - SimulationTimeSeconds = 0.1, - Catalog = catalog, - BuildPublications = false - }); - RequireEqual(0, run.Zones[0].DroppedPickups.Count, - "pre-restart proximity collection must remove the durable pickup row"); - RequireEqual(1, CargoQuantity(ship, salvage.ItemKey), - "pre-restart proximity collection must commit cargo once"); - RequireEqual(1, run.GameEvents.Count(value => - value.Kind == "pickup.collected" && value.PickupIndex == 10), - "pre-restart proximity collection must emit one stable event"); - RequireEqual(0, run.PickupContactReceipts.Count, - "pre-restart proximity collection must not create contact receipts"); - - frame.Run = run; - using var node = AetheriaStateNode.OpenAsync(statePath).GetAwaiter().GetResult(); - using var persistence = AetheriaYmirPersistenceCoordinator.OpenAsync(node, physics, null) - .GetAwaiter().GetResult(); - var capture = persistence.Capture(frame); - persistence.PersistPrivateAsync(capture).GetAwaiter().GetResult(); - node.MutableDocument(AetheriaRuntimeVerseRecordKeys.DaemonFrameLatest) - .ReplaceAsync(frame).GetAwaiter().GetResult(); - node.FlushAsync(soft: false).GetAwaiter().GetResult(); - persistence.ActivateAsync().GetAwaiter().GetResult(); - Require(!AetheriaDocumentRegistry.DocumentTypes.Contains(typeof(AetheriaYmirResumeDocument)), - "daemon-private Ymir resume types must not enter the public Aetheria registry"); - Require(!node.Cache.AllEntries.Any(value => value is AetheriaYmirResumeDocument or AetheriaYmirJournalChunkDocument), - "public CultMesh cache must not enumerate daemon-private Ymir restart material"); - } - - using (var node = AetheriaStateNode.OpenAsync(statePath).GetAwaiter().GetResult()) - { - var durable = node.MutableDocument(AetheriaRuntimeVerseRecordKeys.DaemonFrameLatest) - .ReadAsync().GetAwaiter().GetResult() - ?? throw new InvalidOperationException("Durable restart smoke frame is missing."); - using var restoredPhysics = new AetheriaYmirWorldPhysics(); - using var persistence = AetheriaYmirPersistenceCoordinator.OpenAsync(node, restoredPhysics, durable) - .GetAwaiter().GetResult(); - AetheriaRuntimeDaemonTickRunner.Tick(statePath, durable.Run, - new AetheriaRuntimeDaemonTickOptions - { - WorldPhysics = restoredPhysics, - FrameId = 2, - FixedDeltaSeconds = 0.1, - SimulationTimeSeconds = 0.2, - Catalog = catalog, - BuildPublications = false - }); - RequireEqual(0, durable.Run.Zones[0].DroppedPickups.Count, - "restart must not resurrect the consumed pickup identity"); - RequireEqual(1, CargoQuantity(durable.Run.Zones[0].Entities[0], salvage.ItemKey), - "restart must not duplicate committed pickup cargo"); - RequireEqual(1, durable.Run.GameEvents.Count(value => - value.Kind == "pickup.collected" && value.PickupIndex == 10), - "restart must not duplicate authoritative pickup collection feedback"); - } - } - finally - { - if (Directory.Exists(root)) - Directory.Delete(root, recursive: true); - } - } - - private static void CombatLockSurvivesPublicFrameRestart() - { - var root = Path.Combine(Path.GetTempPath(), "aetheria-combat-restart-" + Guid.NewGuid().ToString("N")); - Directory.CreateDirectory(root); - var statePath = Path.Combine(root, "world.cc"); - try - { - var source = Entity(0, 0, "player"); - source.DirectionX = 1; - source.LookDirectionX = 1; - source.TargetEntityIndex = 1; - source.Contacts = - [ - new AetheriaRuntimeEntityContactCommit - { TargetEntityIndex = 1, InfoGathered = 1, Hostile = true, Visible = true } - ]; - source.WeaponGroups = [new[] { 0 }]; - source.Equipment = - [ - new AetheriaRuntimeLoadoutItemSlotCommit - { - Item = new AetheriaRuntimeLoadoutItemCommit - { ItemKey = "restart-instant", Quality = 1, Durability = 1, Enabled = true } - } - ]; - var target = Entity(1, 80, "raider"); - var run = new AetheriaRuntimeRunCheckpointCommit - { - RunId = "combat-restart-smoke", - CurrentZoneIndex = 0, - CurrentEntityKey = "zone.0.entity.0", - Zones = [new AetheriaRuntimeZoneSnapshotCommit { ZoneIndex = 0, Entities = [source, target] }] - }; - var payload = new AetheriaRuntimeBehaviorPayload(0, AetheriaRuntimeBehaviorKinds.InstantWeapon, 0, - [ - new AetheriaRuntimeBehaviorField(2, PerformanceStat(20)), - new AetheriaRuntimeBehaviorField(6, PerformanceStat(150)), - new AetheriaRuntimeBehaviorField(15, PerformanceStat(0)), - new AetheriaRuntimeBehaviorField(16, PerformanceStat(200)), - new AetheriaRuntimeBehaviorField(17, PerformanceStat(1)), - new AetheriaRuntimeBehaviorField(19, PerformanceStat(0.5)), - new AetheriaRuntimeBehaviorField(23, PerformanceStat(45)), - new AetheriaRuntimeBehaviorField(24, PerformanceStat(0)), - new AetheriaRuntimeBehaviorField(25, PerformanceStat(1)) - ]); - var catalog = new AetheriaRuntimeCatalogSnapshot([CatalogItem("restart-instant", payload)], [], []); - var fire = new AetheriaRuntimeDaemonIntentState(); - fire.WeaponGroups.Add(new AetheriaRuntimeDaemonWeaponGroupIntent - { - ActorEntityKey = "zone.0.entity.0", WeaponGroup = 0, Fire = true, Active = true - }); - - using (var physics = new AetheriaYmirWorldPhysics()) - AetheriaRuntimeDaemonSimulation.Step(run, fire, 0.1, - AetheriaRuntimeDaemonSimulationSettings.AetheriaDefault, - physics, catalog, 1, 0.1); - var state = source.WeaponStates.Single(value => - value.BehaviorKind == AetheriaRuntimeBehaviorKinds.InstantWeapon); - Require(state.TriggerPending && state.LockProgress > 0 && state.LockProgress < 0.99 && - run.ShotReceipts.Count == 0, - "combat restart fixture must stop with one pending trigger and a partial daemon lock"); - var partialProgress = state.LockProgress; - var frame = new AetheriaRuntimeDaemonFrameDocument - { - FrameId = 1, - FixedDeltaSeconds = 0.1, - SimulationTimeSeconds = 0.1, - Run = run - }; - using (var node = AetheriaStateNode.OpenAsync(statePath).GetAwaiter().GetResult()) - { - node.MutableDocument(AetheriaRuntimeVerseRecordKeys.DaemonFrameLatest) - .ReplaceAsync(frame).GetAwaiter().GetResult(); - node.FlushAsync(soft: false).GetAwaiter().GetResult(); - } - - using (var node = AetheriaStateNode.OpenAsync(statePath).GetAwaiter().GetResult()) - { - var durable = node.MutableDocument(AetheriaRuntimeVerseRecordKeys.DaemonFrameLatest) - .ReadAsync().GetAwaiter().GetResult() - ?? throw new InvalidOperationException("Durable combat restart frame is missing."); - var restoredSource = durable.Run.Zones[0].Entities[0]; - var restoredState = restoredSource.WeaponStates.Single(value => - value.BehaviorKind == AetheriaRuntimeBehaviorKinds.InstantWeapon); - Require(restoredState.TriggerPending && Math.Abs(restoredState.LockProgress - partialProgress) < 0.000001, - "public frame restart must retain the pending trigger and exact partial lock progress"); - using var restoredPhysics = new AetheriaYmirWorldPhysics(); - for (var frameId = 2; frameId < 12 && durable.Run.ShotReceipts.Count == 0; frameId++) - AetheriaRuntimeDaemonSimulation.Step(durable.Run, new AetheriaRuntimeDaemonIntentState(), 0.1, - AetheriaRuntimeDaemonSimulationSettings.AetheriaDefault, - restoredPhysics, catalog, frameId, frameId * 0.1); - RequireEqual(1, durable.Run.ShotReceipts.Count, - "restart must complete one pending instant-weapon shot, not lose or duplicate it"); - RequireEqual(1, durable.Run.GameEvents.Count(value => value.Kind == "weapon.lock.started"), - "restart must not replay the pre-restart lock-start transition"); - RequireEqual(1, durable.Run.GameEvents.Count(value => value.Kind == "weapon.lock.acquired"), - "restart must publish one completed lock transition"); - RequireEqual(1, durable.Run.GameEvents.Count(value => value.Kind == "shot.committed"), - "restart must publish one committed-shot transition"); - } - } - finally - { - if (Directory.Exists(root)) - Directory.Delete(root, recursive: true); - } - } - - private sealed class ScriptedWorldPhysics : IAetheriaRuntimeWorldPhysics - { - public string ImplementationId => "smoke.scripted-world"; - - public void RetainWorlds(string runId, IReadOnlyList zoneIndices) { } - - public AetheriaRuntimeWorldStep Step( - string runId, - long frameId, - int simulationStepIndex, - AetheriaRuntimeZoneSnapshotCommit zone, - IReadOnlyList entities, - double deltaSeconds) => new([], []); - - public AetheriaRuntimePhysicalPayloadStep StepPhysicalPayloads( - string runId, - long frameId, - int simulationStepIndex, - AetheriaRuntimeZoneSnapshotCommit zone, - IReadOnlyList entities, - double deltaSeconds) => new(zone.PhysicalPayloads, []); - } -} diff --git a/Aetheria.State.Daemon/Aetheria.State.Daemon.csproj b/Aetheria.State.Daemon/Aetheria.State.Daemon.csproj deleted file mode 100644 index 0b7f6e8dc..000000000 --- a/Aetheria.State.Daemon/Aetheria.State.Daemon.csproj +++ /dev/null @@ -1,19 +0,0 @@ - - - - - Exe - net10.0 - enable - latest - enable - - - - - - - - - - diff --git a/Aetheria.State.Daemon/AetheriaDaemonGeneratedEntityFactory.cs b/Aetheria.State.Daemon/AetheriaDaemonGeneratedEntityFactory.cs deleted file mode 100644 index f4597059a..000000000 --- a/Aetheria.State.Daemon/AetheriaDaemonGeneratedEntityFactory.cs +++ /dev/null @@ -1,72 +0,0 @@ -using Aetheria.State.Documents; - -internal static class AetheriaDaemonGeneratedEntityFactory -{ - public static AetheriaEntitySnapshot Create( - string name, - string kind, - string factionKey, - AetheriaDaemonLoadout loadout, - double x = 0, - double z = 0, - string orbitKey = "", - int securityLevel = 0, - double securityRadius = 0) - { - var orbital = IsOrbital(kind); - return new AetheriaEntitySnapshot - { - Name = name, - Kind = kind, - Position = new AetheriaVector3 { X = x, Z = z }, - Direction = new AetheriaVector2 { Y = 1 }, - LookDirection = new AetheriaVector2 { Y = 1 }, - Velocity = new AetheriaVector2(), - FactionKey = factionKey, - HullItemKey = loadout.HullItemKey, - LoadoutGeneration = loadout.Receipt, - OrbitKey = orbitKey, - SecurityLevel = securityLevel, - SecurityRadius = securityRadius, - IsActive = true, - HeatsinksEnabled = true, - Visibility = orbital ? 760 : 420, - VisibilitySourceCount = 1, - StatGrids = - [ - StatGrid("hull", orbital ? 420 : 130), - StatGrid("shield", orbital ? 130 : 50), - StatGrid("heat", 0) - ], - Equipment = loadout.Equipment, - CargoBays = loadout.CargoBays, - DockingBays = loadout.DockingBays, - WeaponGroups = loadout.WeaponGroups - .Select(indices => new AetheriaWeaponGroupSnapshot { EquipmentIndices = indices }) - .ToArray(), - CargoContents = loadout.CargoBays - .Select((_, index) => new AetheriaCargoBayLoadout - { - Items = index == 0 ? loadout.Cargo : [] - }) - .ToArray(), - DockingBayContents = loadout.DockingBays - .Select(_ => new AetheriaCargoBayLoadout()) - .ToArray(), - DockingBayAssignments = loadout.DockingBays.Select(_ => -1).ToArray() - }; - } - - private static bool IsOrbital(string kind) => - string.Equals(kind, "station", StringComparison.OrdinalIgnoreCase) || - string.Equals(kind, "turret", StringComparison.OrdinalIgnoreCase) || - string.Equals(kind, "orbital", StringComparison.OrdinalIgnoreCase); - - private static AetheriaEntityStatGrid StatGrid(string name, double value) => new() - { - Name = name, - Width = 1, - Height = 1, - Values = [value] - }; -} diff --git a/Aetheria.State.Daemon/AetheriaDaemonHangarCoordinator.cs b/Aetheria.State.Daemon/AetheriaDaemonHangarCoordinator.cs deleted file mode 100644 index 603b313cf..000000000 --- a/Aetheria.State.Daemon/AetheriaDaemonHangarCoordinator.cs +++ /dev/null @@ -1,196 +0,0 @@ -using Aetheria.State; -using Aetheria.State.Documents; -using GameCult.Aetheria.State.Verse; -using GameCult.Caching; - -public static class AetheriaDaemonHangarCoordinator -{ - public const string LocalPlayerKey = "player:local"; - public const string StarterShipId = "ship:local:vanguard"; - public const string StarterLoadoutName = "Vanguard One"; - - public static async Task EnsureAsync( - AetheriaStateNode node, - AetheriaRuntimeCatalogSnapshot catalog, - string now) - { - var existing = await node.MutableDocument(AetheriaStateNode.HangarKey) - .ReadAsync().ConfigureAwait(false); - if (existing != null) - return; - - var factionKey = (catalog.Corporations ?? []) - .Select(value => value.CorporationKey) - .FirstOrDefault(value => !string.IsNullOrWhiteSpace(value)) - ?? throw new InvalidDataException("Hangar creation requires one typed corporation."); - var generator = new AetheriaDaemonLoadoutGenerator( - catalog, - AetheriaDaemonZoneGenerator.GenerationSeed, - 0, - new Dictionary(StringComparer.Ordinal) { [factionKey] = 0 }, - new Dictionary> { [0] = [] }, - isPrelude: true); - var generated = generator.Build("ship", factionKey); - var loadoutCommit = new AetheriaRuntimeLoadoutTemplateCommit - { - Name = StarterLoadoutName, - OwnerPlayerKey = LocalPlayerKey, - RootEntity = ToRuntimeLoadout(generated, factionKey) - }; - var loadout = AetheriaRuntimeStateMapper.ToLoadoutTemplate(loadoutCommit, now); - var loadoutKey = AetheriaRuntimeStateMapper.LoadoutKey(loadout.Name); - var installed = generated.Equipment - .Concat(generated.CargoBays) - .Concat(generated.DockingBays) - .Select(value => value.ItemKey) - .ToHashSet(StringComparer.Ordinal); - var inventory = catalog.EquipmentItems - .Where(value => !string.IsNullOrWhiteSpace(value.ItemKey) && !installed.Contains(value.ItemKey)) - .OrderBy(value => value.Price) - .ThenBy(value => value.ItemKey, StringComparer.Ordinal) - .Take(24) - .Select(value => new AetheriaHangarItemStack { ItemKey = value.ItemKey, Quantity = 1 }) - .ToArray(); - - await node.MutableDocument(loadoutKey).ReplaceAsync(loadout).ConfigureAwait(false); - await node.MutableDocument(AetheriaStateNode.HangarKey).ReplaceAsync(new AetheriaHangarState - { - HangarId = "local", - PlayerKey = LocalPlayerKey, - Revision = 1, - Ships = - [ - new AetheriaHangarShip - { - ShipId = StarterShipId, - HullItemKey = generated.HullItemKey, - LoadoutTemplateKey = loadoutKey.ToString(), - Status = AetheriaHangarShipStatuses.Available - } - ], - Inventory = inventory, - LoadoutTemplateKeys = [loadoutKey.ToString()], - UpdatedAtUtc = now - }).ConfigureAwait(false); - await node.FlushAsync().ConfigureAwait(false); - } - - public static async Task LaunchTerminusAsync( - AetheriaStateNode node, - AetheriaRuntimeCatalogSnapshot catalog, - string requestId, - string shipId, - long expectedRevision, - string now) - { - var hangar = await node.MutableDocument(AetheriaStateNode.HangarKey) - .ReadAsync().ConfigureAwait(false) - ?? throw new InvalidOperationException("The canonical Hangar document does not exist."); - var ship = (hangar.Ships ?? []).SingleOrDefault(value => string.Equals(value.ShipId, shipId, StringComparison.Ordinal)); - var receipt = await AetheriaHangar.AdmitAsync(node, new AetheriaDeploymentRequest - { - RequestId = requestId, - PlayerKey = hangar.PlayerKey, - Mode = AetheriaGameModes.Terminus, - ShipId = shipId, - LoadoutTemplateKey = ship?.LoadoutTemplateKey ?? "", - ExpectedHangarRevision = expectedRevision, - ModePolicyId = AetheriaModePolicies.TerminusLocal - }, now).ConfigureAwait(false); - if (!receipt.Accepted) - return receipt; - - await AetheriaDaemonZoneGenerator.WritePlayableRunAsync( - node, - catalog, - now, - AetheriaDaemonTerminusScenarios.Standard, - receipt).ConfigureAwait(false); - return receipt; - } - - public static async Task CanContinueTerminusAsync( - AetheriaStateNode node, - string shipId, - string deploymentId) - { - var hangar = await node.MutableDocument(AetheriaStateNode.HangarKey) - .ReadAsync().ConfigureAwait(false); - var ship = (hangar?.Ships ?? []).SingleOrDefault(value => - string.Equals(value.ShipId, shipId, StringComparison.Ordinal) && - string.Equals(value.ActiveDeploymentId, deploymentId, StringComparison.Ordinal) && - string.Equals(value.Status, AetheriaHangarShipStatuses.Deployed, StringComparison.Ordinal)); - if (ship == null) - return false; - var deployment = (hangar!.Deployments ?? []).SingleOrDefault(value => - value.Accepted && - string.Equals(value.DeploymentId, deploymentId, StringComparison.Ordinal) && - string.Equals(value.Mode, AetheriaGameModes.Terminus, StringComparison.Ordinal)); - if (deployment == null) - return false; - var settings = await node.MutableDocument(AetheriaStateNode.PlayerSettingsKey) - .ReadAsync().ConfigureAwait(false); - return !string.IsNullOrWhiteSpace(settings?.ActiveRunKey); - } - - private static AetheriaRuntimeEntityLoadoutCommit ToRuntimeLoadout( - AetheriaDaemonLoadout source, - string factionKey) => new() - { - Name = StarterLoadoutName, - Kind = "ship", - FactionKey = factionKey, - Hull = new AetheriaRuntimeLoadoutItemCommit - { - ItemKey = source.HullItemKey, - Quality = 1, - Durability = 1, - Quantity = 1, - Enabled = true - }, - Equipment = source.Equipment.Select(ToRuntimeSlot).ToArray(), - CargoBays = source.CargoBays.Select(ToRuntimeSlot).ToArray(), - DockingBays = source.DockingBays.Select(ToRuntimeSlot).ToArray(), - CargoContents = source.CargoBays.Select((_, index) => new AetheriaRuntimeCargoBayLoadoutCommit - { - Items = index == 0 ? source.Cargo.Select(ToRuntimeSlot).ToArray() : [] - }).ToArray(), - DockingBayContents = source.DockingBays.Select(_ => new AetheriaRuntimeCargoBayLoadoutCommit()).ToArray(), - DockingBayAssignments = source.DockingBays.Select(_ => -1).ToArray(), - WeaponGroups = source.WeaponGroups.Select(group => (IReadOnlyList)group.ToArray()).ToArray() - }; - - private static AetheriaRuntimeLoadoutItemSlotCommit ToRuntimeSlot(AetheriaEntityItemSlot slot) => new() - { - X = slot.Position?.X ?? 0, - Y = slot.Position?.Y ?? 0, - Rotation = slot.Rotation, - Item = new AetheriaRuntimeLoadoutItemCommit - { - ItemKey = slot.ItemKey, - Quality = slot.Quality, - Durability = slot.Durability, - Quantity = slot.Quantity, - Enabled = slot.Enabled, - OverrideShutdown = slot.OverrideShutdown, - Temperature = slot.Temperature - } - }; - - private static AetheriaRuntimeLoadoutItemSlotCommit ToRuntimeSlot(AetheriaLoadoutItemSlot slot) => new() - { - X = slot.Position?.X ?? 0, - Y = slot.Position?.Y ?? 0, - Rotation = slot.Rotation, - Item = new AetheriaRuntimeLoadoutItemCommit - { - ItemKey = slot.Item?.ItemKey ?? "", - Quality = slot.Item?.Quality ?? 1, - Durability = slot.Item?.Durability ?? 1, - Quantity = slot.Item?.Quantity ?? 1, - Enabled = slot.Item?.Enabled ?? true, - OverrideShutdown = slot.Item?.OverrideShutdown ?? false, - Temperature = slot.Item?.Temperature ?? 0 - } - }; -} diff --git a/Aetheria.State.Daemon/AetheriaDaemonLoadoutGenerator.cs b/Aetheria.State.Daemon/AetheriaDaemonLoadoutGenerator.cs deleted file mode 100644 index 8b55c89ac..000000000 --- a/Aetheria.State.Daemon/AetheriaDaemonLoadoutGenerator.cs +++ /dev/null @@ -1,448 +0,0 @@ -using Aetheria.State.Documents; -using GameCult.Aetheria.State.Verse; -using Random = CultMath.Random; - -public sealed class AetheriaDaemonLoadoutGenerator -{ - private const double RareGenerationWeight = 0.05; - private readonly AetheriaRuntimeCatalogSnapshot _catalog; - private Random _random; - private readonly uint _seed; - private readonly int _zoneIndex; - private readonly IReadOnlyDictionary _homeZones; - private readonly IReadOnlyDictionary> _adjacency; - private readonly double _priceExponent; - private readonly bool _isPrelude; - - public AetheriaDaemonLoadoutGenerator( - AetheriaRuntimeCatalogSnapshot catalog, - uint seed, - int zoneIndex, - IReadOnlyDictionary homeZones, - IReadOnlyDictionary> adjacency, - double priceExponent = 0.5, - bool isPrelude = false) - { - _catalog = catalog ?? throw new ArgumentNullException(nameof(catalog)); - _seed = seed == 0 ? 1u : seed; - _random = new Random(_seed); - _zoneIndex = zoneIndex; - _homeZones = homeZones ?? throw new ArgumentNullException(nameof(homeZones)); - _adjacency = adjacency ?? throw new ArgumentNullException(nameof(adjacency)); - _priceExponent = priceExponent; - _isPrelude = isPrelude; - } - - public AetheriaDaemonLoadout Build( - string entityKind, - string availabilityFactionKey, - string hullItemKey = "") - { - var hullType = string.Equals(entityKind, "station", StringComparison.OrdinalIgnoreCase) ? "Station" : "Ship"; - var hull = Pick(availabilityFactionKey, 0, item => - string.Equals(item.Category, AetheriaRuntimeItemCategories.Hull, StringComparison.Ordinal) && - string.Equals(item.HullType, hullType, StringComparison.Ordinal) && - (string.IsNullOrWhiteSpace(hullItemKey) || string.Equals(item.ItemKey, hullItemKey, StringComparison.Ordinal))) - ?? throw new InvalidOperationException($"No available {hullType} hull for faction {availabilityFactionKey}."); - - var occupied = new HashSet<(int X, int Y)>(); - foreach (var hardpoint in hull.Hardpoints ?? Array.Empty()) - foreach (var cell in hardpoint.ShapeCells ?? Array.Empty()) - occupied.Add((hardpoint.PositionX + cell.X, hardpoint.PositionY + cell.Y)); - var slots = new List(); - var hasDedicatedDockingMounts = (hull.Hardpoints ?? Array.Empty()) - .Any(hardpoint => string.Equals(hardpoint.Type, "DockingBay", StringComparison.Ordinal)); - if (string.Equals(entityKind, "station", StringComparison.OrdinalIgnoreCase) && !hasDedicatedDockingMounts) - AddFreeSpaceItem(hull, availabilityFactionKey, occupied, slots, 2, - item => string.Equals(item.Category, AetheriaRuntimeItemCategories.DockingBay, StringComparison.Ordinal) && - !string.Equals(item.HardpointType, "DockingBay", StringComparison.Ordinal)); - - var previous = new List(); - foreach (var hardpoint in (hull.Hardpoints ?? Array.Empty()) - .OrderByDescending(value => value.OccupiedCells)) - { - var controllerKind = string.Equals(hardpoint.Type, "ControlModule", StringComparison.Ordinal) - ? string.Equals(entityKind, "ship", StringComparison.OrdinalIgnoreCase) ? "Cockpit" : "TurretController" - : ""; - var item = previous.FirstOrDefault(candidate => FitsHardpoint(candidate, hardpoint)); - item ??= PickHardpoint(availabilityFactionKey, hardpoint, candidate => - (IsGear(candidate) || IsDockingBay(candidate)) && FitsHardpoint(candidate, hardpoint) && - (controllerKind.Length == 0 || HasBehavior(candidate, controllerKind))); - if (item == null) - { - if (controllerKind.Length > 0) - throw new InvalidOperationException($"No available {controllerKind} fits {hardpoint.Type}."); - continue; - } - - var rotation = ParseRotation(hardpoint.Rotation); - slots.Add(Slot(hardpoint.PositionX, hardpoint.PositionY, item.ItemKey, rotation)); - Reserve(occupied, item, hardpoint.PositionX, hardpoint.PositionY, rotation); - previous.Add(item); - } - - var cargoBay = AddFreeSpaceItem(hull, availabilityFactionKey, occupied, slots, 3, item => - string.Equals(item.Category, AetheriaRuntimeItemCategories.CargoBay, StringComparison.Ordinal) && - item.InteriorOccupiedCells >= 8); - AddFreeSpaceItem(hull, availabilityFactionKey, occupied, slots, 2, item => - IsGear(item) && HasBehavior(item, "Reactor")); - AddFreeSpaceItem(hull, availabilityFactionKey, occupied, slots, 2, item => - IsGear(item) && HasBehavior(item, "Capacitor")); - - var cargo = PackCargo(cargoBay, availabilityFactionKey, - string.Equals(entityKind, "station", StringComparison.OrdinalIgnoreCase)); - var equipment = slots - .Where(value => !IsBay(_catalog.FindItem(value.ItemKey))) - .ToArray(); - var cargoBays = slots - .Where(value => string.Equals( - _catalog.FindItem(value.ItemKey)?.Category, - AetheriaRuntimeItemCategories.CargoBay, - StringComparison.Ordinal)) - .ToArray(); - var dockingBays = slots - .Where(value => string.Equals( - _catalog.FindItem(value.ItemKey)?.Category, - AetheriaRuntimeItemCategories.DockingBay, - StringComparison.Ordinal)) - .ToArray(); - var selectedKeys = new[] { (Role: "hull", Key: hull.ItemKey) } - .Concat(equipment.Select(value => (Role: "equipment", Key: value.ItemKey))) - .Concat(cargoBays.Select(value => (Role: "cargo-bay", Key: value.ItemKey))) - .Concat(dockingBays.Select(value => (Role: "docking-bay", Key: value.ItemKey))) - .Concat(cargo.Select(value => (Role: "cargo", Key: value.Item.ItemKey))); - var receipt = new AetheriaLoadoutGenerationReceipt - { - Seed = _seed, - SourceZoneIndex = _zoneIndex, - AvailabilityFactionKey = availabilityFactionKey, - PriceExponent = _priceExponent, - Selections = selectedKeys.Select(value => Selection(value.Role, value.Key, availabilityFactionKey)).ToArray() - }; - var defaultWeaponGroup = equipment - .Select((slot, index) => (slot, index)) - .Where(value => IsWeapon(_catalog.FindItem(value.slot.ItemKey))) - .Select(value => value.index) - .ToArray(); - var weaponGroups = defaultWeaponGroup.Length == 0 - ? Array.Empty() - : new[] { defaultWeaponGroup }; - return new AetheriaDaemonLoadout( - hull.ItemKey, - equipment, - cargoBays, - dockingBays, - cargo, - weaponGroups, - receipt); - } - - private AetheriaLoadoutGenerationSelection Selection(string role, string itemKey, string factionKey) - { - var item = _catalog.FindItem(itemKey) ?? throw new InvalidOperationException($"Generated item {itemKey} left the catalog."); - var faction = _catalog.FindCorporation(factionKey); - var allegiance = string.Equals(item.ManufacturerKey, factionKey, StringComparison.Ordinal) - ? 1 - : (faction?.Allegiances ?? Array.Empty()) - .FirstOrDefault(value => value.CorporationKey == item.ManufacturerKey)?.Weight ?? 0; - return new AetheriaLoadoutGenerationSelection - { - Role = role, - ItemKey = item.ItemKey, - ManufacturerKey = item.ManufacturerKey, - Price = item.Price, - ManufacturerDistance = _homeZones.TryGetValue(item.ManufacturerKey, out var home) ? Distance(_zoneIndex, home) : 1, - Allegiance = allegiance - }; - } - - private AetheriaRuntimeCatalogItem AddFreeSpaceItem( - AetheriaRuntimeCatalogItem hull, - string factionKey, - HashSet<(int X, int Y)> occupied, - List slots, - double sizeExponent, - Func filter) - { - var candidates = Available(factionKey) - .Where(filter) - .Select(item => (Item: item, Fit: FindFit(hull, item, occupied))) - .Where(value => value.Fit.HasValue) - .ToArray(); - var selected = PickWeighted(candidates, sizeExponent, factionKey, - value => value.Item); - if (selected.Item == null || !selected.Fit.HasValue) - throw new InvalidOperationException("No available mandatory equipment fits the remaining hull space."); - var fit = selected.Fit.Value; - slots.Add(Slot(fit.X, fit.Y, selected.Item.ItemKey, fit.Rotation)); - Reserve(occupied, selected.Item, fit.X, fit.Y, fit.Rotation); - return selected.Item; - } - - private AetheriaRuntimeCatalogItem? TryAddFreeSpaceItem( - AetheriaRuntimeCatalogItem hull, - string factionKey, - HashSet<(int X, int Y)> occupied, - List slots, - double sizeExponent, - Func filter) - { - var candidates = Available(factionKey) - .Where(filter) - .Select(item => (Item: item, Fit: FindFit(hull, item, occupied))) - .Where(value => value.Fit.HasValue) - .ToArray(); - var selected = PickWeighted(candidates, sizeExponent, factionKey, value => value.Item); - if (selected.Item == null || !selected.Fit.HasValue) - return null; - var fit = selected.Fit.Value; - slots.Add(Slot(fit.X, fit.Y, selected.Item.ItemKey, fit.Rotation)); - Reserve(occupied, selected.Item, fit.X, fit.Y, fit.Rotation); - return selected.Item; - } - - private AetheriaLoadoutItemSlot[] PackCargo( - AetheriaRuntimeCatalogItem cargoBay, - string factionKey, - bool includeStationInventory) - { - var candidates = new List(); - if (includeStationInventory) - { - var pool = Available(factionKey) - .Where(item => !string.Equals(item.Category, AetheriaRuntimeItemCategories.CargoBay, StringComparison.Ordinal) && - !string.Equals(item.Category, AetheriaRuntimeItemCategories.DockingBay, StringComparison.Ordinal) && - (!string.Equals(item.Category, AetheriaRuntimeItemCategories.Hull, StringComparison.Ordinal) || - string.Equals(item.HullType, "Ship", StringComparison.Ordinal))) - .ToList(); - for (var draw = 0; draw < 16 && pool.Count > 0; draw++) - { - var selected = PickWeighted(pool, 1, factionKey, item => item); - if (selected == null) break; - candidates.Add(selected); - pool.Remove(selected); - } - } - - var interiorCells = Cells(cargoBay.InteriorShapeCells); - var width = Math.Max(1, cargoBay.InteriorShapeWidth); - var height = Math.Max(1, cargoBay.InteriorShapeHeight); - var occupied = new HashSet<(int X, int Y)>(); - var packed = new List(); - foreach (var item in candidates.OrderByDescending(value => value.OccupiedCells)) - { - var fit = FindFit(interiorCells, width, height, item, occupied); - if (!fit.HasValue) continue; - Reserve(occupied, item, fit.Value.X, fit.Value.Y, fit.Value.Rotation); - packed.Add(CargoSlot(fit.Value.X, fit.Value.Y, item.ItemKey, fit.Value.Rotation)); - } - return packed.ToArray(); - } - - private AetheriaRuntimeCatalogItem? Pick( - string factionKey, - double sizeExponent, - Func filter) - { - return PickWeighted(Available(factionKey).Where(filter).ToArray(), sizeExponent, factionKey, item => item); - } - - private AetheriaRuntimeCatalogItem? PickHardpoint( - string factionKey, - AetheriaRuntimeHardpoint hardpoint, - Func filter) - { - var candidates = Available(factionKey).Where(filter).ToArray(); - if (candidates.Length == 0) - return null; - var largestFootprint = candidates.Max(item => item.OccupiedCells); - return PickWeighted( - candidates.Where(item => item.OccupiedCells == largestFootprint).ToArray(), - 2, - factionKey, - item => item); - } - - private T PickWeighted( - IReadOnlyList values, - double sizeExponent, - string factionKey, - Func itemOf) - { - if (values.Count == 0) return default!; - var weights = values.Select(value => Weight(itemOf(value), factionKey, sizeExponent)).ToArray(); - var total = weights.Sum(); - if (total <= 0) return default!; - var cursor = _random.NextFloat() * total; - for (var i = 0; i < values.Count; i++) - { - cursor -= weights[i]; - if (cursor <= 0) return values[i]; - } - return values[^1]; - } - - private IEnumerable Available(string factionKey) - { - var faction = _catalog.Corporations.FirstOrDefault(value => - string.Equals(value.CorporationKey, factionKey, StringComparison.Ordinal)); - if (faction == null) yield break; - var allegiances = (faction.Allegiances ?? Array.Empty()) - .ToDictionary(value => value.CorporationKey, value => value.Weight, StringComparer.Ordinal); - foreach (var item in _catalog.EquipmentItems) - { - if (item.Price <= 0 || string.IsNullOrWhiteSpace(item.ManufacturerKey)) - continue; - if (!_isPrelude && - (!_homeZones.ContainsKey(item.ManufacturerKey) || !allegiances.ContainsKey(item.ManufacturerKey))) - continue; - yield return item; - } - } - - private double Weight(AetheriaRuntimeCatalogItem item, string factionKey, double sizeExponent) - { - var faction = _catalog.Corporations.First(value => value.CorporationKey == factionKey); - double allegiance; - if (string.Equals(item.ManufacturerKey, factionKey, StringComparison.Ordinal)) - { - allegiance = 1; - } - else - { - var authoredWeight = (faction.Allegiances ?? Array.Empty()) - .FirstOrDefault(value => value.CorporationKey == item.ManufacturerKey)?.Weight ?? 0; - var distance = _homeZones.TryGetValue(item.ManufacturerKey, out var home) - ? Distance(_zoneIndex, home) - : 1; - allegiance = authoredWeight / Math.Max(1, distance); - } - return allegiance * Math.Pow(Math.Max(1, item.OccupiedCells), sizeExponent) / - Math.Pow(item.Price, _priceExponent) * RarityWeight(item); - } - - private static double RarityWeight(AetheriaRuntimeCatalogItem item) => - (item.Tags ?? Array.Empty()).Contains("rarity:rare", StringComparer.Ordinal) - ? RareGenerationWeight - : 1; - - private int Distance(int start, int target) - { - if (start == target) return 1; - var visited = new HashSet { start }; - var queue = new Queue<(int Zone, int Distance)>(); - queue.Enqueue((start, 0)); - while (queue.Count > 0) - { - var current = queue.Dequeue(); - foreach (var adjacent in _adjacency.TryGetValue(current.Zone, out var values) ? values : Array.Empty()) - { - if (!visited.Add(adjacent)) continue; - if (adjacent == target) return current.Distance + 1; - queue.Enqueue((adjacent, current.Distance + 1)); - } - } - return 1; - } - - private static bool FitsHardpoint(AetheriaRuntimeCatalogItem item, AetheriaRuntimeHardpoint hardpoint) - { - if (!string.Equals(item.HardpointType, hardpoint.Type, StringComparison.Ordinal)) - return false; - var target = Cells(hardpoint.ShapeCells); - return RotatedCells(item, ParseRotation(hardpoint.Rotation)).All(target.Contains); - } - - private static (int X, int Y, int Rotation)? FindFit( - AetheriaRuntimeCatalogItem hull, - AetheriaRuntimeCatalogItem item, - HashSet<(int X, int Y)> occupied) - { - return FindFit(Cells(hull.ShapeCells), Math.Max(1, hull.ShapeWidth), Math.Max(1, hull.ShapeHeight), item, occupied); - } - - private static (int X, int Y, int Rotation)? FindFit( - HashSet<(int X, int Y)> targetCells, - int width, - int height, - AetheriaRuntimeCatalogItem item, - HashSet<(int X, int Y)> occupied) - { - for (var rotation = 0; rotation < 4; rotation++) - { - var cells = RotatedCells(item, rotation).ToArray(); - for (var y = 0; y < height; y++) - for (var x = 0; x < width; x++) - { - if (cells.All(cell => targetCells.Contains((cell.X + x, cell.Y + y)) && - !occupied.Contains((cell.X + x, cell.Y + y)))) - return (x, y, rotation); - } - } - return null; - } - - private static void Reserve(HashSet<(int X, int Y)> occupied, AetheriaRuntimeCatalogItem item, int x, int y, int rotation) - { - foreach (var cell in RotatedCells(item, rotation)) occupied.Add((cell.X + x, cell.Y + y)); - } - - private static HashSet<(int X, int Y)> Cells(IReadOnlyList? cells) => - (cells ?? Array.Empty()).Select(value => (value.X, value.Y)).ToHashSet(); - - private static IEnumerable<(int X, int Y)> RotatedCells(AetheriaRuntimeCatalogItem item, int rotation) - => AetheriaRuntimeEquipmentGridGeometry.RotatedCells(item, rotation); - - private static int ParseRotation(string value) => - AetheriaRuntimeEquipmentGridGeometry.ParseRotation(value); - - private static string RotationName(int rotation) => - AetheriaRuntimeEquipmentGridGeometry.RotationName(rotation); - - private static bool HasBehavior(AetheriaRuntimeCatalogItem item, string kind) => - (item.BehaviorKinds ?? Array.Empty()).Contains(kind, StringComparer.Ordinal); - - private static bool IsGear(AetheriaRuntimeCatalogItem item) => - string.Equals(item.Category, AetheriaRuntimeItemCategories.Gear, StringComparison.Ordinal) || - string.Equals(item.Category, AetheriaRuntimeItemCategories.Weapon, StringComparison.Ordinal); - - private static bool IsWeapon(AetheriaRuntimeCatalogItem? item) => - item != null && - (string.Equals(item.Category, AetheriaRuntimeItemCategories.Weapon, StringComparison.Ordinal) || - (item.BehaviorKinds ?? Array.Empty()).Any(kind => - kind.Contains("Weapon", StringComparison.Ordinal))); - - private static bool IsBay(AetheriaRuntimeCatalogItem? item) => - item != null && - (string.Equals(item.Category, AetheriaRuntimeItemCategories.CargoBay, StringComparison.Ordinal) || - string.Equals(item.Category, AetheriaRuntimeItemCategories.DockingBay, StringComparison.Ordinal)); - - private static bool IsDockingBay(AetheriaRuntimeCatalogItem item) => - string.Equals(item.Category, AetheriaRuntimeItemCategories.DockingBay, StringComparison.Ordinal); - - private static AetheriaEntityItemSlot Slot(int x, int y, string itemKey, int rotation = 0) => new() - { - Position = new AetheriaGridCoord { X = x, Y = y }, ItemKey = itemKey, - Quality = 1, Durability = 1, Quantity = 1, Enabled = true, - Rotation = RotationName(rotation) - }; - - private static AetheriaLoadoutItemSlot CargoSlot(int index, string itemKey) => - CargoSlot(index % 4, index / 4, itemKey); - - private static AetheriaLoadoutItemSlot CargoSlot(int x, int y, string itemKey, int rotation = 0) => new() - { - Position = new AetheriaGridCoord { X = x, Y = y }, - Item = new AetheriaLoadoutItem { ItemKey = itemKey, Quality = 1, Durability = 1, Quantity = 1, Enabled = true }, - Rotation = RotationName(rotation) - }; -} - -public sealed record AetheriaDaemonLoadout( - string HullItemKey, - AetheriaEntityItemSlot[] Equipment, - AetheriaEntityItemSlot[] CargoBays, - AetheriaEntityItemSlot[] DockingBays, - AetheriaLoadoutItemSlot[] Cargo, - int[][] WeaponGroups, - AetheriaLoadoutGenerationReceipt Receipt); diff --git a/Aetheria.State.Daemon/AetheriaDaemonNativeCatalog.cs b/Aetheria.State.Daemon/AetheriaDaemonNativeCatalog.cs deleted file mode 100644 index d439fd186..000000000 --- a/Aetheria.State.Daemon/AetheriaDaemonNativeCatalog.cs +++ /dev/null @@ -1,232 +0,0 @@ -using Aetheria.State; -using Aetheria.State.Documents; -using MessagePack; -using System.Security.Cryptography; - -internal static class AetheriaDaemonNativeCatalog -{ - private const string NativeCatalogRevisionTag = "native-catalog-revision:1"; - private const string ZenithHullLegacyId = "82efc0a5-1ba5-4ff3-a281-b2e6e247521d"; - private const string MediumDockingBayLegacyId = "3e930a2c-ac72-4385-98aa-1c5b0b90db46"; - private const string DockyardHullLegacyId = "ca098005-8cc8-47f4-be99-7bc842805359"; - private const string DockyardBayLegacyId = "8ec30f8d-8536-48b4-bd64-65f29f229895"; - - public static string DockyardHullItemKey => ItemKey(DockyardHullLegacyId); - public static string DockyardBayItemKey => ItemKey(DockyardBayLegacyId); - - public static async Task EnsureAsync(AetheriaStateNode node) - { - ArgumentNullException.ThrowIfNull(node); - - var zenith = await node.MutableDocument( - AetheriaCatalogKeys.ItemDefinitionFromLegacyId(ZenithHullLegacyId)) - .ReadAsync() - .ConfigureAwait(false) - ?? throw new InvalidDataException("The daemon-native dockyard requires the canonical Zenith station hull."); - var mediumBay = await node.MutableDocument( - AetheriaCatalogKeys.ItemDefinitionFromLegacyId(MediumDockingBayLegacyId)) - .ReadAsync() - .ConfigureAwait(false) - ?? throw new InvalidDataException("The daemon-native dockyard requires the canonical medium docking bay."); - - var expectedHull = CreateDockyardHull(zenith); - var expectedBay = CreateDockyardBay(mediumBay); - var hullDocument = node.MutableDocument( - AetheriaCatalogKeys.ItemDefinitionFromLegacyId(DockyardHullLegacyId)); - var bayDocument = node.MutableDocument( - AetheriaCatalogKeys.ItemDefinitionFromLegacyId(DockyardBayLegacyId)); - var existingHull = await hullDocument.ReadAsync().ConfigureAwait(false); - var existingBay = await bayDocument.ReadAsync().ConfigureAwait(false); - var hullCurrent = HasGenerationTags(existingHull, expectedHull.Tags); - var bayCurrent = HasGenerationTags(existingBay, expectedBay.Tags); - - if (!hullCurrent) - await hullDocument.ReplaceAsync(expectedHull).ConfigureAwait(false); - if (!bayCurrent) - await bayDocument.ReplaceAsync(expectedBay).ConfigureAwait(false); - - return !hullCurrent || !bayCurrent; - } - - private static AetheriaItemDefinition CreateDockyardHull(AetheriaItemDefinition source) - { - const int width = 24; - const int height = 24; - var hardpoints = Enumerable.Range(0, 6) - .SelectMany(index => new[] - { - Hardpoint("DockingBay", index * 4, 0, 4, 4, source.HullArmor), - Hardpoint("DockingBay", index * 4, 20, 4, 4, source.HullArmor) - }) - .Concat(new[] - { - Hardpoint("Reactor", 10, 10, 4, 4, source.HullArmor), - Hardpoint("Radiator", 8, 10, 2, 2, source.HullArmor), - Hardpoint("Radiator", 14, 10, 2, 2, source.HullArmor), - Hardpoint("Radiator", 10, 14, 2, 2, source.HullArmor), - Hardpoint("Radiator", 12, 14, 2, 2, source.HullArmor), - Hardpoint("Sensors", 11, 16, 2, 2, source.HullArmor) - }) - .ToArray(); - var hullCells = RectangleCells(0, 0, width, height); - var interiorCells = RectangleCells(1, 1, width - 2, height - 2); - - var hull = Copy(source, DockyardHullLegacyId, "Zenith Dockyard"); - hull.Description = "A high-capacity Zenith station variant built around twelve dedicated docking berths."; - hull.Mass = source.Mass * 4.5; - hull.Volume = hullCells.Length; - hull.Price = checked(source.Price * 5); - hull.ShapeWidth = width; - hull.ShapeHeight = height; - hull.OccupiedCells = hullCells.Length; - hull.ShapeCells = hullCells; - hull.InteriorShapeWidth = width; - hull.InteriorShapeHeight = height; - hull.InteriorOccupiedCells = interiorCells.Length; - hull.InteriorShapeCells = interiorCells; - hull.Hardpoints = hardpoints; - hull.HullDrag = source.HullDrag * 1.75; - hull.Tags = Tags( - source.Tags, - "origin:aetheria-daemon", - NativeCatalogRevisionTag, - SourceFingerprintTag(source), - "station-role:dockyard", - "docking-capacity:12", - $"presentation-hull-item:{ItemKey(ZenithHullLegacyId)}"); - return hull; - } - - private static AetheriaItemDefinition CreateDockyardBay(AetheriaItemDefinition source) - { - var bay = Copy(source, DockyardBayLegacyId, "Dockyard Berth"); - bay.Description = "A medium docking bay packaged for a dedicated station docking hardpoint."; - bay.HardpointType = "DockingBay"; - bay.Price = checked(source.Price * 2); - bay.Tags = Tags( - source.Tags, - "origin:aetheria-daemon", - NativeCatalogRevisionTag, - SourceFingerprintTag(source), - "station-role:dockyard"); - return bay; - } - - private static bool HasGenerationTags(AetheriaItemDefinition? existing, IEnumerable expectedTags) - { - if (existing == null) - return false; - - var expectedGenerationTags = expectedTags.Where(tag => - tag.StartsWith("native-catalog-revision:", StringComparison.Ordinal) || - tag.StartsWith("native-source-sha256:", StringComparison.Ordinal)); - return expectedGenerationTags.All(tag => existing.Tags.Contains(tag, StringComparer.Ordinal)); - } - - private static string SourceFingerprintTag(AetheriaItemDefinition source) - { - var payload = MessagePackSerializer.Serialize(source); - return "native-source-sha256:" + Convert.ToHexString(SHA256.HashData(payload)).ToLowerInvariant(); - } - - private static AetheriaItemDefinition Copy(AetheriaItemDefinition source, string legacyId, string name) => new() - { - Name = name, - Category = source.Category, - LegacyId = legacyId, - Description = source.Description, - Mass = source.Mass, - Volume = source.Volume, - Tags = (source.Tags ?? []).ToArray(), - ManufacturerLegacyId = source.ManufacturerLegacyId, - Price = source.Price, - ShapeWidth = source.ShapeWidth, - ShapeHeight = source.ShapeHeight, - OccupiedCells = source.OccupiedCells, - HardpointType = source.HardpointType, - HullType = source.HullType, - BehaviorKinds = (source.BehaviorKinds ?? []).ToArray(), - BehaviorCount = source.BehaviorCount, - MaxStack = source.MaxStack, - Stackable = source.Stackable, - Duration = source.Duration, - Durability = source.Durability, - WeaponRange = source.WeaponRange, - WeaponCaliber = source.WeaponCaliber, - WeaponType = source.WeaponType, - WeaponFireTypes = source.WeaponFireTypes, - WeaponModifiers = source.WeaponModifiers, - ShapeCells = Clone(source.ShapeCells), - InteriorShapeWidth = source.InteriorShapeWidth, - InteriorShapeHeight = source.InteriorShapeHeight, - InteriorOccupiedCells = source.InteriorOccupiedCells, - InteriorShapeCells = Clone(source.InteriorShapeCells), - Hardpoints = (source.Hardpoints ?? []).Select(Clone).ToArray(), - BehaviorPayloads = source.BehaviorPayloads ?? [], - MinimumTemperature = source.MinimumTemperature, - MaximumTemperature = source.MaximumTemperature, - ThermalPerformanceCurveKeys = source.ThermalPerformanceCurveKeys ?? [], - HullPrefab = source.HullPrefab, - SimpleCommodityCategory = source.SimpleCommodityCategory, - CompoundCommodityCategory = source.CompoundCommodityCategory, - SpecificHeat = source.SpecificHeat, - Conductivity = source.Conductivity, - HullGridOffset = source.HullGridOffset, - HullArmor = source.HullArmor, - HullDrag = source.HullDrag, - HullCanTow = source.HullCanTow, - DockingMaxSizeX = source.DockingMaxSizeX, - DockingMaxSizeY = source.DockingMaxSizeY, - ActionBarIcon = source.ActionBarIcon, - ThermalResilience = source.ThermalResilience, - AudioStats = source.AudioStats ?? [], - EffectivenessCurveKeys = source.EffectivenessCurveKeys ?? [] - }; - - private static AetheriaItemHardpoint Clone(AetheriaItemHardpoint source) => new() - { - Type = source.Type, - PositionX = source.PositionX, - PositionY = source.PositionY, - ShapeWidth = source.ShapeWidth, - ShapeHeight = source.ShapeHeight, - OccupiedCells = source.OccupiedCells, - ShapeCells = Clone(source.ShapeCells), - Transform = source.Transform, - Rotation = source.Rotation, - Armor = source.Armor - }; - - private static AetheriaItemHardpoint Hardpoint( - string type, - int x, - int y, - int width, - int height, - double armor) => new() - { - Type = type, - PositionX = x, - PositionY = y, - ShapeWidth = width, - ShapeHeight = height, - OccupiedCells = width * height, - ShapeCells = RectangleCells(0, 0, width, height), - Rotation = "None", - Armor = armor - }; - - private static AetheriaShapeCell[] RectangleCells(int x, int y, int width, int height) => - Enumerable.Range(y, height) - .SelectMany(row => Enumerable.Range(x, width) - .Select(column => new AetheriaShapeCell { X = column, Y = row })) - .ToArray(); - - private static AetheriaShapeCell[] Clone(IEnumerable? cells) => - (cells ?? []).Select(cell => new AetheriaShapeCell { X = cell.X, Y = cell.Y }).ToArray(); - - private static string[] Tags(IEnumerable? tags, params string[] added) => - (tags ?? []).Concat(added).Distinct(StringComparer.Ordinal).ToArray(); - - private static string ItemKey(string legacyId) => $"aetheria.item_definition:legacy:{legacyId}"; -} diff --git a/Aetheria.State.Daemon/AetheriaDaemonRegularRunWriter.cs b/Aetheria.State.Daemon/AetheriaDaemonRegularRunWriter.cs deleted file mode 100644 index d6b1889a6..000000000 --- a/Aetheria.State.Daemon/AetheriaDaemonRegularRunWriter.cs +++ /dev/null @@ -1,134 +0,0 @@ -using Aetheria.State; -using Aetheria.State.Documents; -using GameCult.Aetheria.State.Verse; -using GameCult.Caching; - -public static class AetheriaDaemonRegularRunWriter -{ - public static async Task WriteAsync( - AetheriaStateNode node, - AetheriaRuntimeCatalogSnapshot catalog, - string now, - uint generationSeed, - AetheriaDaemonRegularTopologySettings? topologySettings = null) - { - if (node == null) throw new ArgumentNullException(nameof(node)); - if (catalog == null) throw new ArgumentNullException(nameof(catalog)); - if (generationSeed == 0) throw new ArgumentOutOfRangeException(nameof(generationSeed)); - - var factions = AetheriaDaemonRegularTopologyGenerator.ResolveFossilFactions(catalog); - var world = AetheriaDaemonTutorialWorldGenerator.GenerateRegular( - factions, generationSeed, topologySettings); - var materialized = AetheriaDaemonTutorialWorldMaterializer.Materialize( - world, factions, catalog, isPrelude: false, identityPrefix: "regular"); - var runId = $"sector-{generationSeed:x8}"; - var runKey = RunKey(runId); - var corporationIndices = (catalog.Corporations ?? Array.Empty()) - .Select((corporation, index) => (corporation.CorporationKey, index)) - .Where(value => !string.IsNullOrWhiteSpace(value.CorporationKey)) - .ToDictionary(value => value.CorporationKey, value => value.index, StringComparer.Ordinal); - var zoneKeys = materialized.Topology.Zones - .OrderBy(zone => zone.ZoneIndex) - .Select(zone => ZoneKey(runId, zone.ZoneIndex)) - .ToArray(); - var playerEntityKey = EntityKey(runId, materialized.PlayerZoneIndex, materialized.PlayerEntityIndex); - - foreach (var zone in materialized.Zones.Values.OrderBy(zone => zone.Topology.ZoneIndex)) - { - var entityKeys = Enumerable.Range(0, zone.Entities.Length) - .Select(index => EntityKey(runId, zone.Topology.ZoneIndex, index)) - .ToArray(); - await node.MutableDocument(new CultRecordKey(ZoneKey(runId, zone.Topology.ZoneIndex))) - .ReplaceAsync(new AetheriaZoneState - { - Name = zone.Topology.Name, - Position = new AetheriaVector2 { X = zone.Topology.X, Y = zone.Topology.Y }, - AdjacentZoneIndices = zone.Topology.AdjacentZoneIndices.ToArray(), - FactionIndices = zone.Topology.FactionKeys.Select(key => corporationIndices[key]).ToArray(), - OwnerFactionIndex = string.IsNullOrWhiteSpace(zone.Topology.OwnerFactionKey) - ? -1 - : corporationIndices[zone.Topology.OwnerFactionKey], - EntityKeys = entityKeys, - Orbits = zone.Orbits, - Bodies = zone.CelestialPlan.Bodies, - GravityTerrainRadius = zone.CelestialPlan.Radius, - GravityTerrainDepth = zone.CelestialPlan.GravityTerrainDepth, - GravityTerrainDepthExponent = zone.CelestialPlan.GravityTerrainDepthExponent, - GravityTerrainBoundaryFog = zone.CelestialPlan.GravityTerrainBoundaryFog, - GravityTerrainWaveFrequency = 1, - DroppedPickups = [], - NextPickupIndex = 0 - }).ConfigureAwait(false); - for (var entityIndex = 0; entityIndex < zone.Entities.Length; entityIndex++) - { - await node.MutableDocument(new CultRecordKey(entityKeys[entityIndex])) - .ReplaceAsync(zone.Entities[entityIndex]) - .ConfigureAwait(false); - } - } - - await node.MutableDocument(new CultRecordKey(runKey)).ReplaceAsync(new AetheriaRunState - { - RunId = runId, - IsTutorial = false, - GameMode = AetheriaGameSessionState.AetheriaMode, - EntranceZoneIndex = materialized.Topology.EntranceZoneIndex, - ExitZoneIndex = materialized.Topology.ExitZoneIndex, - CurrentZoneIndex = materialized.PlayerZoneIndex, - DiscoveredZoneIndices = materialized.Topology.DiscoveredZoneIndices.ToArray(), - ZoneKeys = zoneKeys, - FactionRelationships = materialized.Topology.HomeZoneByFactionKey.Keys - .Select(key => new AetheriaFactionRelationshipState - { - FactionKey = key, - Relationship = "Neutral", - Standing = 0 - }) - .ToArray(), - HomeZones = materialized.Topology.HomeZoneByFactionKey - .OrderBy(pair => pair.Key, StringComparer.Ordinal) - .Select(pair => new AetheriaFactionZoneState - { - FactionIndex = corporationIndices[pair.Key], - ZoneIndex = pair.Value - }) - .ToArray(), - BossZones = materialized.Topology.BossZoneByFactionKey - .OrderBy(pair => pair.Key, StringComparer.Ordinal) - .Select(pair => new AetheriaFactionZoneState - { - FactionIndex = corporationIndices[pair.Key], - ZoneIndex = pair.Value - }) - .ToArray(), - GenerationSeed = generationSeed, - CurrentEntityKey = playerEntityKey, - LifecyclePhase = AetheriaRuntimeRunLifecycle.Active, - TerminalFrameId = -1, - AgentTasks = materialized.Zones.Values - .OrderBy(zone => zone.Topology.ZoneIndex) - .SelectMany(zone => zone.AgentTasks) - .ToArray(), - UpdatedAtUtc = now - }).ConfigureAwait(false); - - var settings = await node.MutableDocument(AetheriaStateNode.PlayerSettingsKey) - .ReadAsync().ConfigureAwait(false) ?? new AetheriaPlayerSettings(); - settings.ActiveRunKey = runKey; - settings.PlayerName = string.IsNullOrWhiteSpace(settings.PlayerName) ? "Pilot" : settings.PlayerName; - settings.LastUpdatedAtUtc = now; - await node.MutableDocument(AetheriaStateNode.PlayerSettingsKey) - .ReplaceAsync(settings).ConfigureAwait(false); - await node.FlushAsync().ConfigureAwait(false); - return new AetheriaDaemonWrittenRun(runId, runKey, playerEntityKey, false) - { - SessionMode = AetheriaGameSessionState.AetheriaMode - }; - } - - public static string RunKey(string runId) => $"global:aetheria.run_state.{runId}.v1"; - public static string ZoneKey(string runId, int zoneIndex) => - $"global:aetheria.zone_state.{runId}.{zoneIndex}.v1"; - public static string EntityKey(string runId, int zoneIndex, int entityIndex) => - $"global:aetheria.run_state.{runId}.zone.{zoneIndex}.entity.{entityIndex}.v1"; -} diff --git a/Aetheria.State.Daemon/AetheriaDaemonRegularTopologyGenerator.cs b/Aetheria.State.Daemon/AetheriaDaemonRegularTopologyGenerator.cs deleted file mode 100644 index 26669231d..000000000 --- a/Aetheria.State.Daemon/AetheriaDaemonRegularTopologyGenerator.cs +++ /dev/null @@ -1,258 +0,0 @@ -public sealed record AetheriaDaemonRegularTopologySettings( - int ZoneCount, - float LinkDensity, - int MegaCount, - int BossCount) -{ - public static AetheriaDaemonRegularTopologySettings Fossil { get; } = - new(196, 0.5f, 12, 3); -} - -/// -/// Daemon-owned port of the fossil non-tutorial Galaxy constructor. It owns -/// regular-sector corporation selection, entrance/exit, boss chokepoints, -/// full-radius influence, discovery, and names. Zone contents consume this -/// graph and cannot revise it. -/// -public static class AetheriaDaemonRegularTopologyGenerator -{ - public static AetheriaDaemonTutorialTopology GenerateFossil( - IReadOnlyList availableFactions, - uint seed, - AetheriaDaemonRegularTopologySettings? settings = null) - { - settings ??= AetheriaDaemonRegularTopologySettings.Fossil; - if (settings.ZoneCount < 3) - throw new ArgumentOutOfRangeException(nameof(settings), "Regular topology requires at least three zones."); - if (settings.MegaCount < 1) - throw new ArgumentOutOfRangeException(nameof(settings), "Regular topology requires at least one corporation."); - if (availableFactions == null || availableFactions.Count == 0) - throw new InvalidDataException("Regular topology requires typed corporations."); - - var random = new CultMath.Random(seed); - var factions = availableFactions - .OrderBy(_ => random.NextFloat()) - .Take(settings.MegaCount) - .ToArray(); - var points = AetheriaDaemonTutorialTopologyGenerator.GeneratePoints( - settings.ZoneCount, ref random, MainCloudDensity); - var links = AetheriaDaemonTutorialTopologyGenerator.DelaunayLinks(points); - AetheriaDaemonTutorialTopologyGenerator.PruneLinks( - points, links, settings.LinkDensity, MainCloudDensity); - var distances = AetheriaDaemonTutorialTopologyGenerator.BuildDistances(points.Length, links); - - var exit = AetheriaDaemonTutorialTopologyGenerator.MaxZone(points.Length, zone => - Enumerable.Range(0, points.Length).Sum(other => distances[zone, other])); - var entrance = AetheriaDaemonTutorialTopologyGenerator.MaxZone(points.Length, zone => - distances[exit, zone]); - var bossZones = PlaceBosses( - factions, settings.BossCount, entrance, exit, links, distances); - var homes = PlaceHomes(factions, entrance, exit, bossZones, distances); - - var zoneFactions = new string[points.Length][]; - var owners = new string[points.Length]; - for (var zoneIndex = 0; zoneIndex < points.Length; zoneIndex++) - { - zoneFactions[zoneIndex] = factions - .Where(faction => distances[zoneIndex, homes[faction.CorporationKey]] <= faction.InfluenceDistance) - .Select(faction => faction.CorporationKey) - .ToArray(); - owners[zoneIndex] = factions - .Where(faction => distances[zoneIndex, homes[faction.CorporationKey]] <= faction.InfluenceDistance) - .OrderBy(faction => distances[zoneIndex, homes[faction.CorporationKey]]) - .ThenBy(faction => Array.IndexOf(factions, faction)) - .Select(faction => faction.CorporationKey) - .FirstOrDefault() ?? ""; - } - - var discovered = new[] { entrance } - .Concat(AetheriaDaemonTutorialTopologyGenerator.Neighbors(entrance, links)) - .Distinct() - .OrderBy(index => index) - .ToArray(); - var nameSeed = (uint)random.NextInt(1, int.MaxValue); - var zoneNames = AetheriaDaemonTutorialTopologyGenerator.GenerateNames( - factions, owners, nameSeed, order: 4); - var zones = Enumerable.Range(0, points.Length) - .Select(index => new AetheriaDaemonTutorialZoneTopology( - index, - zoneNames[index], - points[index].X, - points[index].Y, - AetheriaDaemonTutorialTopologyGenerator.Neighbors(index, links).OrderBy(value => value).ToArray(), - zoneFactions[index], - owners[index])) - .ToArray(); - return new AetheriaDaemonTutorialTopology( - seed, 91.29439f, entrance, discovered, homes, zones) - { - ExitZoneIndex = exit, - BossZoneByFactionKey = bossZones - }; - } - - public static IReadOnlyList ResolveFossilFactions( - GameCult.Aetheria.State.Verse.AetheriaRuntimeCatalogSnapshot catalog) - { - if (catalog == null) throw new ArgumentNullException(nameof(catalog)); - return (catalog.Corporations ?? Array.Empty()) - .Where(corporation => corporation != null && !string.IsNullOrWhiteSpace(corporation.CorporationKey)) - .Where(corporation => !string.IsNullOrWhiteSpace(corporation.GeonameFileKey)) - .Select(corporation => - { - var names = catalog.FindNameFile(corporation.GeonameFileKey); - if (names == null || names.Names.Count == 0) - throw new InvalidDataException( - $"Regular-sector corporation '{corporation.Name}' has no typed geoname corpus."); - return new AetheriaDaemonTutorialFactionInput( - corporation.CorporationKey, - corporation.Name ?? "", - corporation.ShortName ?? "", - corporation.InfluenceDistance) - { - TrainingNames = names.Names, - BossHullItemKey = corporation.BossHullItemKey ?? "" - }; - }) - .ToArray(); - } - - public static float MainCloudDensity(float x, float y) - { - var point = new CultMath.float2(x + 91.29439f, y + 91.29439f); - var frequency = 1f; - var amplitude = 0.5f; - var sum = 0f; - for (var octave = 0; octave < 10; octave++) - { - var noise = MathF.Abs(CultMath.math.simplex_noise(point * frequency)); - sum += (octave < 4 ? 1f - noise : noise) * amplitude; - frequency *= 2f; - amplitude *= 0.7f; - } - return MathF.Pow(sum + 0.3f, 10f) * 0.01f; - } - - private static Dictionary PlaceBosses( - IReadOnlyList factions, - int bossCount, - int entrance, - int exit, - IReadOnlySet links, - int[,] distances) - { - var path = ShortestPath(entrance, exit, links); - var chokepoints = path - .Where(zone => AetheriaDaemonTutorialTopologyGenerator.Neighbors(zone, links).Count() > 2) - .Where(zone => !ConnectedWithoutZone(entrance, exit, zone, links, distances.GetLength(0))) - .ToArray(); - var bossZones = new Dictionary(StringComparer.Ordinal); - foreach (var faction in factions - .Where(value => !string.IsNullOrWhiteSpace(value.BossHullItemKey)) - .Take(Math.Max(0, bossCount))) - { - var candidates = chokepoints.Length == 0 ? path : chokepoints; - bossZones[faction.CorporationKey] = AetheriaDaemonTutorialTopologyGenerator.MaxZone(candidates, zone => - { - var score = (double)distances[exit, zone] * distances[entrance, zone]; - foreach (var occupied in bossZones.Values) - score *= distances[occupied, zone]; - return score; - }); - } - return bossZones; - } - - private static Dictionary PlaceHomes( - IReadOnlyList factions, - int entrance, - int exit, - IReadOnlyDictionary bossZones, - int[,] distances) - { - var homes = new Dictionary(StringComparer.Ordinal); - foreach (var faction in factions.Where(value => bossZones.ContainsKey(value.CorporationKey))) - { - var candidates = Enumerable.Range(0, distances.GetLength(0)) - .Where(zone => distances[zone, bossZones[faction.CorporationKey]] <= faction.InfluenceDistance); - homes[faction.CorporationKey] = AetheriaDaemonTutorialTopologyGenerator.MaxZone(candidates, zone => - { - var score = (double)AetheriaDaemonTutorialTopologyGenerator.RegionSize( - zone, faction.InfluenceDistance, distances); - foreach (var occupied in homes.Values) - score *= Math.Sqrt(distances[occupied, zone]); - return score; - }); - } - foreach (var faction in factions.Where(value => !homes.ContainsKey(value.CorporationKey))) - { - homes[faction.CorporationKey] = AetheriaDaemonTutorialTopologyGenerator.MaxZone( - distances.GetLength(0), zone => - { - var score = Math.Pow( - AetheriaDaemonTutorialTopologyGenerator.RegionSize( - zone, faction.InfluenceDistance, distances), homes.Count); - score *= distances[exit, zone] * distances[entrance, zone]; - foreach (var occupied in homes.Values) - score *= Math.Sqrt(distances[occupied, zone]); - foreach (var boss in bossZones.Values) - score *= Math.Sqrt(distances[boss, zone]); - return score; - }); - } - return homes; - } - - private static int[] ShortestPath( - int source, - int target, - IReadOnlySet links) - { - var queue = new Queue(); - var parents = new Dictionary(); - queue.Enqueue(source); - parents[source] = -1; - while (queue.Count > 0) - { - var current = queue.Dequeue(); - if (current == target) break; - foreach (var neighbor in AetheriaDaemonTutorialTopologyGenerator.Neighbors(current, links).OrderBy(value => value)) - { - if (parents.ContainsKey(neighbor)) continue; - parents[neighbor] = current; - queue.Enqueue(neighbor); - } - } - var path = new List(); - for (var cursor = target; cursor >= 0; cursor = parents[cursor]) - path.Add(cursor); - path.Reverse(); - return path.ToArray(); - } - - private static bool ConnectedWithoutZone( - int source, - int target, - int ignored, - IReadOnlySet links, - int zoneCount) - { - if (source == ignored || target == ignored) return false; - var queue = new Queue(); - var visited = new bool[zoneCount]; - queue.Enqueue(source); - visited[source] = true; - while (queue.Count > 0) - { - var current = queue.Dequeue(); - foreach (var neighbor in AetheriaDaemonTutorialTopologyGenerator.Neighbors(current, links)) - { - if (neighbor == ignored || visited[neighbor]) continue; - if (neighbor == target) return true; - visited[neighbor] = true; - queue.Enqueue(neighbor); - } - } - return false; - } -} diff --git a/Aetheria.State.Daemon/AetheriaDaemonRunFactory.cs b/Aetheria.State.Daemon/AetheriaDaemonRunFactory.cs deleted file mode 100644 index db8e69d7a..000000000 --- a/Aetheria.State.Daemon/AetheriaDaemonRunFactory.cs +++ /dev/null @@ -1,39 +0,0 @@ -using Aetheria.State; -using Aetheria.State.Documents; -using GameCult.Aetheria.State.Verse; - -public static class AetheriaDaemonRunFactory -{ - public static async Task WriteAsync( - AetheriaStateNode node, - AetheriaRuntimeCatalogSnapshot catalog, - string now, - string generationIdentity, - AetheriaDaemonRegularTopologySettings? regularTopologySettings = null) - { - if (node == null) throw new ArgumentNullException(nameof(node)); - if (catalog == null) throw new ArgumentNullException(nameof(catalog)); - var settings = await node.MutableDocument(AetheriaStateNode.PlayerSettingsKey) - .ReadAsync().ConfigureAwait(false) ?? new AetheriaPlayerSettings(); - if (!settings.TutorialPassed) - return await AetheriaDaemonTutorialRunWriter.WriteAsync(node, catalog, now).ConfigureAwait(false); - - return await AetheriaDaemonRegularRunWriter.WriteAsync( - node, - catalog, - now, - StableSeed(generationIdentity), - regularTopologySettings).ConfigureAwait(false); - } - - public static uint StableSeed(string value) - { - var hash = 2166136261u; - foreach (var character in value ?? "") - { - hash ^= character; - hash *= 16777619u; - } - return hash == 0 ? 1u : hash; - } -} diff --git a/Aetheria.State.Daemon/AetheriaDaemonTutorialRunWriter.cs b/Aetheria.State.Daemon/AetheriaDaemonTutorialRunWriter.cs deleted file mode 100644 index 18defa535..000000000 --- a/Aetheria.State.Daemon/AetheriaDaemonTutorialRunWriter.cs +++ /dev/null @@ -1,134 +0,0 @@ -using Aetheria.State; -using Aetheria.State.Documents; -using GameCult.Aetheria.State.Verse; -using GameCult.Caching; - -public sealed record AetheriaDaemonWrittenRun( - string RunId, - string RunKey, - string CurrentEntityKey, - bool IsTutorial) -{ - public string SessionMode { get; init; } = AetheriaGameSessionState.AetheriaMode; -} - -public static class AetheriaDaemonTutorialRunWriter -{ - public const string RunId = "tutorial"; - public const uint GenerationSeed = 0xA37E_2026u; - - public static async Task WriteAsync( - AetheriaStateNode node, - AetheriaRuntimeCatalogSnapshot catalog, - string now) - { - var factions = AetheriaDaemonTutorialTopologyGenerator.ResolveFossilFactions(catalog); - var world = AetheriaDaemonTutorialWorldGenerator.Generate(factions, GenerationSeed); - var materialized = AetheriaDaemonTutorialWorldMaterializer.Materialize(world, factions, catalog); - var runKey = RunKey(); - var zoneKeys = materialized.Topology.Zones - .OrderBy(zone => zone.ZoneIndex) - .Select(zone => ZoneKey(zone.ZoneIndex)) - .ToArray(); - var corporationIndices = (catalog.Corporations ?? Array.Empty()) - .Select((corporation, index) => (corporation.CorporationKey, index)) - .Where(value => !string.IsNullOrWhiteSpace(value.CorporationKey)) - .ToDictionary(value => value.CorporationKey, value => value.index, StringComparer.Ordinal); - var playerEntityKey = EntityKey(materialized.PlayerZoneIndex, materialized.PlayerEntityIndex); - - foreach (var zone in materialized.Zones.Values.OrderBy(zone => zone.Topology.ZoneIndex)) - { - var entityKeys = Enumerable.Range(0, zone.Entities.Length) - .Select(index => EntityKey(zone.Topology.ZoneIndex, index)) - .ToArray(); - await node.MutableDocument(new CultRecordKey(ZoneKey(zone.Topology.ZoneIndex))) - .ReplaceAsync(new AetheriaZoneState - { - Name = zone.Topology.Name, - Position = new AetheriaVector2 { X = zone.Topology.X, Y = zone.Topology.Y }, - AdjacentZoneIndices = zone.Topology.AdjacentZoneIndices.ToArray(), - FactionIndices = zone.Topology.FactionKeys - .Select(key => corporationIndices[key]) - .ToArray(), - OwnerFactionIndex = string.IsNullOrWhiteSpace(zone.Topology.OwnerFactionKey) - ? -1 - : corporationIndices[zone.Topology.OwnerFactionKey], - EntityKeys = entityKeys, - Orbits = zone.Orbits, - Bodies = zone.CelestialPlan.Bodies, - GravityTerrainRadius = zone.CelestialPlan.Radius, - GravityTerrainDepth = zone.CelestialPlan.GravityTerrainDepth, - GravityTerrainDepthExponent = zone.CelestialPlan.GravityTerrainDepthExponent, - GravityTerrainBoundaryFog = zone.CelestialPlan.GravityTerrainBoundaryFog, - GravityTerrainWaveFrequency = 1, - DroppedPickups = [], - NextPickupIndex = 0 - }).ConfigureAwait(false); - for (var entityIndex = 0; entityIndex < zone.Entities.Length; entityIndex++) - { - await node.MutableDocument(new CultRecordKey(entityKeys[entityIndex])) - .ReplaceAsync(zone.Entities[entityIndex]) - .ConfigureAwait(false); - } - } - - await node.MutableDocument(new CultRecordKey(runKey)).ReplaceAsync(new AetheriaRunState - { - RunId = RunId, - IsTutorial = true, - EntranceZoneIndex = materialized.Topology.EntranceZoneIndex, - ExitZoneIndex = -1, - CurrentZoneIndex = materialized.PlayerZoneIndex, - DiscoveredZoneIndices = materialized.Topology.DiscoveredZoneIndices.ToArray(), - ZoneKeys = zoneKeys, - FactionRelationships = factions.Select(faction => new AetheriaFactionRelationshipState - { - FactionKey = faction.CorporationKey, - Relationship = "Neutral", - Standing = 0 - }).ToArray(), - HomeZones = materialized.Topology.HomeZoneByFactionKey - .OrderBy(pair => pair.Key, StringComparer.Ordinal) - .Select(pair => new AetheriaFactionZoneState - { - FactionIndex = corporationIndices[pair.Key], - ZoneIndex = pair.Value - }) - .ToArray(), - BossZones = materialized.Topology.BossZoneByFactionKey - .OrderBy(pair => pair.Key, StringComparer.Ordinal) - .Select(pair => new AetheriaFactionZoneState - { - FactionIndex = corporationIndices[pair.Key], - ZoneIndex = pair.Value - }) - .ToArray(), - GenerationSeed = GenerationSeed, - CurrentEntityKey = playerEntityKey, - LifecyclePhase = AetheriaRuntimeRunLifecycle.Active, - TerminalFrameId = -1, - GameMode = AetheriaGameSessionState.AetheriaMode, - AgentTasks = materialized.Zones.Values - .OrderBy(zone => zone.Topology.ZoneIndex) - .SelectMany(zone => zone.AgentTasks) - .ToArray(), - UpdatedAtUtc = now - }).ConfigureAwait(false); - - var settings = await node.MutableDocument(AetheriaStateNode.PlayerSettingsKey) - .ReadAsync().ConfigureAwait(false) ?? new AetheriaPlayerSettings(); - settings.ActiveRunKey = runKey; - settings.PlayerName = string.IsNullOrWhiteSpace(settings.PlayerName) ? "Pilot" : settings.PlayerName; - settings.LastUpdatedAtUtc = now; - await node.MutableDocument(AetheriaStateNode.PlayerSettingsKey) - .ReplaceAsync(settings) - .ConfigureAwait(false); - await node.FlushAsync().ConfigureAwait(false); - return new AetheriaDaemonWrittenRun(RunId, runKey, playerEntityKey, true); - } - - public static string RunKey() => $"global:aetheria.run_state.{RunId}.v1"; - public static string ZoneKey(int zoneIndex) => $"global:aetheria.zone_state.{RunId}.{zoneIndex}.v1"; - public static string EntityKey(int zoneIndex, int entityIndex) => - $"global:aetheria.run_state.{RunId}.zone.{zoneIndex}.entity.{entityIndex}.v1"; -} diff --git a/Aetheria.State.Daemon/AetheriaDaemonTutorialTopologyGenerator.cs b/Aetheria.State.Daemon/AetheriaDaemonTutorialTopologyGenerator.cs deleted file mode 100644 index 8e35243ec..000000000 --- a/Aetheria.State.Daemon/AetheriaDaemonTutorialTopologyGenerator.cs +++ /dev/null @@ -1,725 +0,0 @@ -using GameCult.Aetheria.State.Verse; - -public sealed record AetheriaDaemonTutorialFactionInput( - string CorporationKey, - string Name, - string ShortName, - int InfluenceDistance) -{ - public IReadOnlyList TrainingNames { get; init; } = Array.Empty(); - public string BossHullItemKey { get; init; } = ""; -} - -public sealed record AetheriaDaemonTutorialTopologySettings( - int ZoneCount, - float LinkDensity, - string ProtagonistFaction, - string AntagonistFaction, - string BufferFaction, - IReadOnlyList NeutralFactions, - string QuestFaction) -{ - public static AetheriaDaemonTutorialTopologySettings Fossil { get; } = new( - 64, - 0.5f, - "Miss", - "Zhe", - "Luc", - new[] { "Aero", "Finch" }, - "Adras"); -} - -public sealed record AetheriaDaemonTutorialZoneTopology( - int ZoneIndex, - string Name, - float X, - float Y, - IReadOnlyList AdjacentZoneIndices, - IReadOnlyList FactionKeys, - string OwnerFactionKey); - -public sealed record AetheriaDaemonTutorialTopology( - uint GenerationSeed, - float NoisePosition, - int EntranceZoneIndex, - IReadOnlyList DiscoveredZoneIndices, - IReadOnlyDictionary HomeZoneByFactionKey, - IReadOnlyList Zones) -{ - public int ExitZoneIndex { get; init; } = -1; - public IReadOnlyDictionary BossZoneByFactionKey { get; init; } = - new Dictionary(StringComparer.Ordinal); -} - -/// -/// Daemon-owned migration of the fossil tutorial galaxy topology. This organ -/// deliberately stops before zone contents: materialization consumes this -/// result, but cannot change its graph, faction placement, or discovery truth. -/// -public static class AetheriaDaemonTutorialTopologyGenerator -{ - internal const int CandidateMultiplier = 8; - - public static AetheriaDaemonTutorialTopology GenerateFossil( - IReadOnlyList factions, - uint seed, - AetheriaDaemonTutorialTopologySettings? settings = null) - { - var noiseRandom = new CultMath.Random(seed ^ 0x74A7_91D3u); - float noisePosition; - var attempts = 0; - do - { - if (++attempts > 1_000_000) - throw new InvalidOperationException("Tutorial cloud field did not produce an admissible entrance region."); - noisePosition = noiseRandom.NextFloat(0, 1000); - } while (TutorialCloudDensity(0.5f, 0.5f, noisePosition) < 0.5f); - - return Generate( - factions, - seed, - (x, y) => TutorialCloudDensity(x, y, noisePosition), - settings) with - { - NoisePosition = noisePosition - }; - } - - public static float TutorialCloudDensity(float x, float y, float noisePosition) - { - var point = new CultMath.float2(x + noisePosition, y + noisePosition); - var frequency = 0.1f; - var amplitude = 0.5f; - var sum = 0f; - for (var octave = 0; octave < 10; octave++) - { - var noise = MathF.Abs(CultMath.math.simplex_noise(point * frequency)); - sum += (octave < 4 ? 1f - noise : noise) * amplitude; - frequency *= 2f; - amplitude *= 0.7f; - } - return MathF.Pow(sum + 0.3f, 10f) * 0.01f; - } - - public static IReadOnlyList ResolveFossilFactions( - AetheriaRuntimeCatalogSnapshot catalog, - AetheriaDaemonTutorialTopologySettings? settings = null) - { - settings ??= AetheriaDaemonTutorialTopologySettings.Fossil; - var requested = new[] - { - settings.ProtagonistFaction, - settings.AntagonistFaction, - settings.BufferFaction - } - .Concat(settings.NeutralFactions) - .Append(settings.QuestFaction) - .ToArray(); - - return requested.Select(name => - { - var matches = (catalog.Corporations ?? Array.Empty()) - .Where(value => MatchesAuthoredSelector(value, name)) - .ToArray(); - if (matches.Length == 0) - throw new InvalidDataException($"Tutorial generation requires the authored faction '{name}'."); - if (matches.Length > 1) - throw new InvalidDataException( - $"Tutorial faction selector '{name}' is ambiguous: {string.Join(", ", matches.Select(value => value.Name))}."); - var match = matches[0]; - if (string.IsNullOrWhiteSpace(match.CorporationKey)) - throw new InvalidDataException($"Tutorial faction '{name}' has no stable corporation key."); - var nameFile = catalog.FindNameFile(match.GeonameFileKey); - if (nameFile == null || nameFile.Names.Count == 0) - throw new InvalidDataException($"Tutorial faction '{name}' has no typed geoname corpus."); - return new AetheriaDaemonTutorialFactionInput( - match.CorporationKey, - match.Name ?? "", - match.ShortName ?? "", - match.InfluenceDistance) - { - TrainingNames = nameFile.Names - }; - }).ToArray(); - } - - private static bool MatchesAuthoredSelector(AetheriaRuntimeCorporation corporation, string selector) - { - return string.Equals(corporation.CorporationKey, selector, StringComparison.OrdinalIgnoreCase) || - StartsWithSelector(corporation.ShortName, selector) || - StartsWithSelector(corporation.Name, selector); - } - - private static bool StartsWithSelector(string? value, string selector) => - !string.IsNullOrWhiteSpace(value) && - value.StartsWith(selector, StringComparison.OrdinalIgnoreCase); - - public static AetheriaDaemonTutorialTopology Generate( - IReadOnlyList factions, - uint seed, - Func cloudDensity, - AetheriaDaemonTutorialTopologySettings? settings = null) - { - settings ??= AetheriaDaemonTutorialTopologySettings.Fossil; - if (settings.ZoneCount < 3) - throw new ArgumentOutOfRangeException(nameof(settings), "Tutorial topology requires at least three zones."); - if (cloudDensity == null) - throw new ArgumentNullException(nameof(cloudDensity)); - - var roles = ResolveRoles(factions, settings); - var random = new CultMath.Random(seed); - var points = GeneratePoints(settings.ZoneCount, ref random, cloudDensity); - var links = DelaunayLinks(points); - PruneLinks(points, links, settings.LinkDensity, cloudDensity); - var distances = BuildDistances(points.Length, links); - - var home = PlaceFactions(points.Length, links, distances, roles); - var influence = roles.All.ToDictionary( - faction => faction.CorporationKey, - faction => Math.Max(0, (faction.InfluenceDistance + 1) / 2), - StringComparer.Ordinal); - var zoneFactions = new string[points.Length][]; - var owners = new string[points.Length]; - for (var zoneIndex = 0; zoneIndex < points.Length; zoneIndex++) - { - zoneFactions[zoneIndex] = roles.All - .Where(faction => distances[zoneIndex, home[faction.CorporationKey]] <= influence[faction.CorporationKey]) - .Select(faction => faction.CorporationKey) - .ToArray(); - owners[zoneIndex] = roles.All - .Where(faction => distances[zoneIndex, home[faction.CorporationKey]] <= influence[faction.CorporationKey]) - .OrderBy(faction => distances[zoneIndex, home[faction.CorporationKey]]) - .ThenBy(faction => Array.IndexOf(roles.All, faction)) - .Select(faction => faction.CorporationKey) - .FirstOrDefault() ?? ""; - } - - var entranceCandidates = Enumerable.Range(0, points.Length) - .Where(index => owners[index].Length == 0) - .ToArray(); - if (entranceCandidates.Length == 0) - throw new InvalidOperationException("Tutorial topology produced no unowned entrance zone."); - var entrance = entranceCandidates - .OrderBy(index => distances[index, home[roles.Protagonist.CorporationKey]]) - .ThenBy(index => index) - .First(); - var discovered = new[] { entrance } - .Concat(Neighbors(entrance, links)) - .Distinct() - .OrderBy(index => index) - .ToArray(); - - var nameSeed = (uint)random.NextInt(1, int.MaxValue); - var zoneNames = GenerateNames(roles.All, owners, nameSeed); - var zones = Enumerable.Range(0, points.Length) - .Select(index => new AetheriaDaemonTutorialZoneTopology( - index, - zoneNames[index], - points[index].X, - points[index].Y, - Neighbors(index, links).OrderBy(value => value).ToArray(), - zoneFactions[index], - owners[index])) - .ToArray(); - return new AetheriaDaemonTutorialTopology(seed, float.NaN, entrance, discovered, home, zones); - } - - private static TutorialRoles ResolveRoles( - IReadOnlyList factions, - AetheriaDaemonTutorialTopologySettings settings) - { - AetheriaDaemonTutorialFactionInput Resolve(string name) - { - var matches = factions.Where(value => - string.Equals(value.CorporationKey, name, StringComparison.OrdinalIgnoreCase) || - StartsWithSelector(value.ShortName, name) || - StartsWithSelector(value.Name, name)) - .ToArray(); - if (matches.Length == 0) - throw new InvalidDataException($"Tutorial topology is missing faction '{name}'."); - if (matches.Length > 1) - throw new InvalidDataException( - $"Tutorial topology faction selector '{name}' is ambiguous: {string.Join(", ", matches.Select(value => value.Name))}."); - return matches[0]; - } - - var protagonist = Resolve(settings.ProtagonistFaction); - var antagonist = Resolve(settings.AntagonistFaction); - var buffer = Resolve(settings.BufferFaction); - var neutrals = settings.NeutralFactions.Select(Resolve).ToArray(); - var quest = Resolve(settings.QuestFaction); - var all = new[] { protagonist, antagonist, buffer } - .Concat(neutrals) - .Append(quest) - .DistinctBy(value => value.CorporationKey, StringComparer.Ordinal) - .ToArray(); - if (all.Length != 4 + neutrals.Length) - throw new InvalidDataException("Tutorial faction roles must resolve to distinct corporations."); - return new TutorialRoles(protagonist, antagonist, buffer, neutrals, quest, all); - } - - internal static string[] GenerateNames( - IReadOnlyList factions, - IReadOnlyList ownerKeys, - uint seed, - int order = 3) - { - var generators = new Dictionary(StringComparer.Ordinal); - foreach (var faction in factions) - { - if (faction.TrainingNames.Count == 0) - throw new InvalidDataException($"Tutorial faction '{faction.ShortName}' has no geoname training rows."); - generators[faction.CorporationKey] = new TutorialMarkovNameGenerator( - seed, - faction.TrainingNames, - order: order, - minimumLength: 5, - maximumLength: 10); - } - - var catalogRandom = new CultMath.Random(seed); - var used = new HashSet(StringComparer.Ordinal); - var output = new string[ownerKeys.Count]; - for (var zoneIndex = 0; zoneIndex < ownerKeys.Count; zoneIndex++) - { - var owner = ownerKeys[zoneIndex]; - if (owner.Length == 0) - { - string candidate; - do candidate = $"EAC-{catalogRandom.NextInt(9999)}"; - while (!used.Add(candidate)); - output[zoneIndex] = candidate; - continue; - } - - output[zoneIndex] = generators[owner].Next(used); - } - return output; - } - - internal static Point[] GeneratePoints( - int count, - ref CultMath.Random random, - Func density) - { - var candidates = new Point[count * CandidateMultiplier]; - var accepted = 0; - var accumulator = 0f; - var attempts = 0; - while (accepted < candidates.Length) - { - if (++attempts > candidates.Length * 1_000_000) - throw new InvalidOperationException("Tutorial density did not admit enough topology samples."); - var point = new Point(random.NextFloat(), random.NextFloat()); - var dx = point.X - 0.5f; - var dy = point.Y - 0.5f; - var envelope = Saturate((0.2f - (dx * dx + dy * dy)) * 4f); - var sampleDensity = Saturate(density(point.X, point.Y)); - accumulator += sampleDensity * sampleDensity * envelope; - if (accumulator <= 0.5f) - continue; - accumulator = 0; - candidates[accepted++] = point; - } - - var dMax = 2f * MathF.Sqrt((1f / count) / (2f * MathF.Sqrt(3f))); - var dMaxSquared = dMax * dMax; - var weights = new float[candidates.Length]; - var densities = candidates.Select(point => density(point.X, point.Y)).ToArray(); - var active = Enumerable.Repeat(true, candidates.Length).ToArray(); - for (var i = 0; i < candidates.Length; i++) - for (var j = 0; j < candidates.Length; j++) - { - var distanceSquared = DistanceSquared(candidates[i], candidates[j]); - if (distanceSquared > dMaxSquared) - continue; - weights[i] += Weight(distanceSquared, dMax, densities[i]); - } - - for (var remaining = candidates.Length; remaining > count; remaining--) - { - var eliminated = Enumerable.Range(0, candidates.Length) - .Where(index => active[index]) - .OrderByDescending(index => weights[index]) - .ThenBy(index => index) - .First(); - active[eliminated] = false; - for (var neighbor = 0; neighbor < candidates.Length; neighbor++) - { - if (!active[neighbor]) - continue; - var distanceSquared = DistanceSquared(candidates[neighbor], candidates[eliminated]); - if (distanceSquared <= dMaxSquared) - weights[neighbor] -= Weight(distanceSquared, dMax, densities[neighbor]); - } - } - - return Enumerable.Range(0, candidates.Length) - .Where(index => active[index]) - .Select(index => candidates[index]) - .ToArray(); - } - - private static float Weight(float distanceSquared, float dMax, float density) - { - var distance = MathF.Sqrt(distanceSquared); - return MathF.Pow(1f - distance / dMax, 5f + 6f * density); - } - - internal static HashSet DelaunayLinks(IReadOnlyList points) - { - var vertices = points.Concat(new[] - { - new Point(-16, -8), - new Point(16, -8), - new Point(0, 16) - }).ToArray(); - var superA = points.Count; - var superB = points.Count + 1; - var superC = points.Count + 2; - var triangles = new List { Orient(vertices, new Triangle(superA, superB, superC)) }; - - for (var pointIndex = 0; pointIndex < points.Count; pointIndex++) - { - var bad = triangles.Where(triangle => ContainsInCircumcircle(vertices, triangle, vertices[pointIndex])).ToArray(); - var boundary = bad - .SelectMany(TriangleEdges) - .GroupBy(edge => edge) - .Where(group => group.Count() == 1) - .Select(group => group.Key) - .ToArray(); - foreach (var triangle in bad) - triangles.Remove(triangle); - triangles.AddRange(boundary.Select(edge => Orient(vertices, new Triangle(edge.A, edge.B, pointIndex)))); - } - - return triangles - .Where(triangle => triangle.A < points.Count && triangle.B < points.Count && triangle.C < points.Count) - .SelectMany(TriangleEdges) - .ToHashSet(); - } - - private static Triangle Orient(IReadOnlyList points, Triangle triangle) - { - return Cross(points[triangle.A], points[triangle.B], points[triangle.C]) >= 0 - ? triangle - : new Triangle(triangle.B, triangle.A, triangle.C); - } - - private static bool ContainsInCircumcircle(IReadOnlyList points, Triangle triangle, Point point) - { - var a = points[triangle.A]; - var b = points[triangle.B]; - var c = points[triangle.C]; - var ax = a.X - point.X; - var ay = a.Y - point.Y; - var bx = b.X - point.X; - var by = b.Y - point.Y; - var cx = c.X - point.X; - var cy = c.Y - point.Y; - var determinant = (ax * ax + ay * ay) * (bx * cy - cx * by) - - (bx * bx + by * by) * (ax * cy - cx * ay) - + (cx * cx + cy * cy) * (ax * by - bx * ay); - return determinant > 1e-7f; - } - - private static IEnumerable TriangleEdges(Triangle triangle) - { - yield return new Edge(triangle.A, triangle.B); - yield return new Edge(triangle.B, triangle.C); - yield return new Edge(triangle.C, triangle.A); - } - - internal static void PruneLinks( - IReadOnlyList points, - HashSet links, - float linkDensity, - Func density) - { - var target = (int)MathF.Ceiling(Saturate(linkDensity) * links.Count); - while (links.Count > target) - { - var degrees = Enumerable.Range(0, points.Count) - .Select(index => links.Count(edge => edge.Contains(index))) - .ToArray(); - var removable = links - .Where(edge => IsConnectedWithout(points.Count, links, edge.A, edge.B, edge)) - .Select(edge => new { Edge = edge, Weight = LinkWeight(points, edge, degrees, density) }) - .OrderByDescending(value => value.Weight) - .ThenBy(value => value.Edge.A) - .ThenBy(value => value.Edge.B) - .FirstOrDefault(); - if (removable == null) - break; - links.Remove(removable.Edge); - } - } - - private static float LinkWeight( - IReadOnlyList points, - Edge edge, - IReadOnlyList degrees, - Func density) - { - var a = points[edge.A]; - var b = points[edge.B]; - var midpointDensity = Saturate(density((a.X + b.X) * 0.5f, (a.Y + b.Y) * 0.5f)); - var densityFactor = midpointDensity <= 0 ? float.PositiveInfinity : 1f / midpointDensity; - return densityFactor * DistanceSquared(a, b) * (degrees[edge.A] - 1) * (degrees[edge.B] - 1); - } - - private static Dictionary PlaceFactions( - int zoneCount, - HashSet links, - int[,] distances, - TutorialRoles roles) - { - var home = new Dictionary(StringComparer.Ordinal); - var influence = roles.All.ToDictionary( - faction => faction.CorporationKey, - faction => Math.Max(0, (faction.InfluenceDistance + 1) / 2), - StringComparer.Ordinal); - - home[roles.Protagonist.CorporationKey] = MaxZone(zoneCount, zone => - RegionSize(zone, influence[roles.Protagonist.CorporationKey], distances)); - home[roles.Antagonist.CorporationKey] = MaxZone(zoneCount, zone => - RegionSize(zone, influence[roles.Antagonist.CorporationKey], distances) * - distances[zone, home[roles.Protagonist.CorporationKey]]); - home[roles.Protagonist.CorporationKey] = MaxZone(zoneCount, zone => - RegionSize(zone, influence[roles.Protagonist.CorporationKey], distances) * - Math.Sqrt(distances[zone, home[roles.Antagonist.CorporationKey]])); - - var minimumDifference = Enumerable.Range(0, zoneCount) - .Min(zone => Math.Abs( - distances[zone, home[roles.Antagonist.CorporationKey]] - - distances[zone, home[roles.Protagonist.CorporationKey]])); - home[roles.Buffer.CorporationKey] = MaxZone( - Enumerable.Range(0, zoneCount).Where(zone => Math.Abs( - distances[zone, home[roles.Antagonist.CorporationKey]] - - distances[zone, home[roles.Protagonist.CorporationKey]]) == minimumDifference), - zone => RegionSize(zone, influence[roles.Buffer.CorporationKey], distances)); - - foreach (var neutral in roles.Neutrals) - { - home[neutral.CorporationKey] = MaxZone(zoneCount, zone => - { - var score = (double)RegionSize(zone, influence[neutral.CorporationKey], distances); - foreach (var occupied in home.Values) - score *= Math.Sqrt(distances[zone, occupied]); - return score; - }); - } - - var present = BuildPresence(zoneCount, roles.All.Where(value => value != roles.Quest), home, influence, distances); - var preferredQuestZones = Enumerable.Range(0, zoneCount) - .Where(zone => present[zone].Contains(roles.Antagonist.CorporationKey) && present[zone].Contains(roles.Buffer.CorporationKey)) - .ToArray(); - if (preferredQuestZones.Length > 0) - { - home[roles.Quest.CorporationKey] = MaxZone(preferredQuestZones, zone => - distances[zone, home[roles.Antagonist.CorporationKey]] * - RegionSize(zone, influence[roles.Quest.CorporationKey], distances)); - } - else - { - var antagonistZones = Enumerable.Range(0, zoneCount) - .Where(zone => present[zone].Contains(roles.Antagonist.CorporationKey)); - home[roles.Quest.CorporationKey] = antagonistZones - .OrderBy(zone => distances[zone, home[roles.Buffer.CorporationKey]]) - .ThenBy(zone => zone) - .First(); - } - - return home; - } - - private static HashSet[] BuildPresence( - int zoneCount, - IEnumerable factions, - IReadOnlyDictionary home, - IReadOnlyDictionary influence, - int[,] distances) - { - return Enumerable.Range(0, zoneCount) - .Select(zone => factions - .Where(faction => distances[zone, home[faction.CorporationKey]] <= influence[faction.CorporationKey]) - .Select(faction => faction.CorporationKey) - .ToHashSet(StringComparer.Ordinal)) - .ToArray(); - } - - internal static int[,] BuildDistances(int zoneCount, IReadOnlySet links) - { - var output = new int[zoneCount, zoneCount]; - for (var source = 0; source < zoneCount; source++) - { - var queue = new Queue(); - var visited = new bool[zoneCount]; - queue.Enqueue(source); - visited[source] = true; - while (queue.Count > 0) - { - var current = queue.Dequeue(); - foreach (var neighbor in Neighbors(current, links)) - { - if (visited[neighbor]) - continue; - visited[neighbor] = true; - output[source, neighbor] = output[source, current] + 1; - queue.Enqueue(neighbor); - } - } - if (visited.Any(value => !value)) - throw new InvalidOperationException("Tutorial topology graph is disconnected."); - } - return output; - } - - internal static bool IsConnectedWithout( - int zoneCount, - IReadOnlySet links, - int source, - int target, - Edge ignored) - { - var queue = new Queue(); - var visited = new bool[zoneCount]; - queue.Enqueue(source); - visited[source] = true; - while (queue.Count > 0) - { - var current = queue.Dequeue(); - foreach (var edge in links) - { - if (edge == ignored || !edge.Contains(current)) - continue; - var neighbor = edge.Other(current); - if (neighbor == target) - return true; - if (visited[neighbor]) - continue; - visited[neighbor] = true; - queue.Enqueue(neighbor); - } - } - return false; - } - - internal static IEnumerable Neighbors(int zone, IEnumerable links) - { - return links.Where(edge => edge.Contains(zone)).Select(edge => edge.Other(zone)); - } - - internal static int RegionSize(int zone, int maxDistance, int[,] distances) - { - return Enumerable.Range(0, distances.GetLength(0)).Count(other => distances[zone, other] <= maxDistance); - } - - internal static int MaxZone(int zoneCount, Func score) => - MaxZone(Enumerable.Range(0, zoneCount), score); - - internal static int MaxZone(IEnumerable zones, Func score) => - zones.Select(zone => new { Zone = zone, Score = score(zone) }) - .OrderByDescending(value => value.Score) - .ThenBy(value => value.Zone) - .First().Zone; - - private static float DistanceSquared(Point a, Point b) - { - var x = a.X - b.X; - var y = a.Y - b.Y; - return x * x + y * y; - } - - private static float Cross(Point a, Point b, Point c) => - (b.X - a.X) * (c.Y - a.Y) - (b.Y - a.Y) * (c.X - a.X); - - private static float Saturate(float value) => value < 0 ? 0 : value > 1 ? 1 : value; - - internal readonly record struct Point(float X, float Y); - private readonly record struct Triangle(int A, int B, int C); - internal readonly record struct Edge - { - public Edge(int a, int b) - { - A = Math.Min(a, b); - B = Math.Max(a, b); - } - - public int A { get; } - public int B { get; } - public bool Contains(int value) => A == value || B == value; - public int Other(int value) => A == value ? B : B == value ? A : throw new ArgumentOutOfRangeException(nameof(value)); - } - - private sealed record TutorialRoles( - AetheriaDaemonTutorialFactionInput Protagonist, - AetheriaDaemonTutorialFactionInput Antagonist, - AetheriaDaemonTutorialFactionInput Buffer, - IReadOnlyList Neutrals, - AetheriaDaemonTutorialFactionInput Quest, - AetheriaDaemonTutorialFactionInput[] All); - - private sealed class TutorialMarkovNameGenerator - { - private readonly Dictionary> _chains = new(StringComparer.Ordinal); - private readonly List _samples = []; - private readonly int _order; - private readonly int _minimumLength; - private readonly int _maximumLength; - private CultMath.Random _random; - - public TutorialMarkovNameGenerator( - uint seed, - IEnumerable sampleNames, - int order, - int minimumLength, - int maximumLength) - { - _random = new CultMath.Random(seed); - _order = Math.Max(1, order); - _minimumLength = Math.Max(1, minimumLength); - _maximumLength = maximumLength; - var names = new HashSet(StringComparer.Ordinal); - foreach (var line in sampleNames) - foreach (var word in (line ?? "").ToUpperInvariant().Split(' ', ',', '.', '"')) - if (word.Length >= _minimumLength) names.Add(word); - - foreach (var line in names) - foreach (var word in line.Split(' ', '\'', '(', ')')) - { - var lower = word.Trim().ToLowerInvariant(); - if (lower.Length >= _order + 1) _samples.Add(lower + "|"); - } - foreach (var word in _samples) - for (var letter = 0; letter < word.Length - _order; letter++) - { - var token = word.Substring(letter, _order); - if (!_chains.TryGetValue(token, out var row)) - _chains[token] = row = []; - row.Add(word[letter + _order]); - } - if (_samples.Count == 0) - throw new InvalidDataException("Tutorial geoname corpus contains no usable Markov samples."); - } - - public string Next(ISet used) - { - for (var attempt = 0; attempt < 1_000_000; attempt++) - { - var sample = _samples[_random.NextInt(0, _samples.Count)]; - var value = sample.Substring(0, _order); - while (_chains.TryGetValue(value.Substring(value.Length - _order, _order), out var row)) - { - var next = row[_random.NextInt(0, row.Count)]; - if (next == '|') break; - value += next; - } - value = char.ToUpperInvariant(value[0]) + value.Substring(1); - if (value.Length >= _minimumLength && value.Length <= _maximumLength && used.Add(value)) - return value; - } - throw new InvalidOperationException("Tutorial Markov corpus could not produce another unique zone name."); - } - } -} diff --git a/Aetheria.State.Daemon/AetheriaDaemonTutorialWorldMaterializer.cs b/Aetheria.State.Daemon/AetheriaDaemonTutorialWorldMaterializer.cs deleted file mode 100644 index 047fdfa72..000000000 --- a/Aetheria.State.Daemon/AetheriaDaemonTutorialWorldMaterializer.cs +++ /dev/null @@ -1,276 +0,0 @@ -using Aetheria.State.Documents; -using GameCult.Aetheria.State.Verse; - -public sealed record AetheriaDaemonMaterializedTutorialZone( - AetheriaDaemonTutorialZoneTopology Topology, - AetheriaDaemonGeneratedZonePlan CelestialPlan, - string NearestFactionKey, - int FactionPresence, - AetheriaOrbitSnapshot[] Orbits, - AetheriaEntitySnapshot[] Entities, - AetheriaAgentTaskState[] AgentTasks, - int PlayerEntityIndex); - -public sealed record AetheriaDaemonMaterializedTutorialWorld( - AetheriaDaemonTutorialTopology Topology, - IReadOnlyDictionary Zones, - int PlayerZoneIndex, - int PlayerEntityIndex); - -/// -/// Daemon-owned port of the fossil ZoneGenerator population pass. Celestial -/// generation hands its exact remaining random state to this organ, so adding -/// presentation work cannot perturb station, turret, or enemy placement. -/// -public static class AetheriaDaemonTutorialWorldMaterializer -{ - public static AetheriaDaemonMaterializedTutorialWorld Materialize( - AetheriaDaemonTutorialWorldPlan world, - IReadOnlyList factions, - AetheriaRuntimeCatalogSnapshot catalog, - bool isPrelude = true, - string identityPrefix = "tutorial") - { - if (world == null) throw new ArgumentNullException(nameof(world)); - if (catalog == null) throw new ArgumentNullException(nameof(catalog)); - var homes = world.Topology.HomeZoneByFactionKey; - factions = homes.Keys - .Select(key => factions.First(faction => - string.Equals(faction.CorporationKey, key, StringComparison.Ordinal))) - .ToArray(); - var adjacency = world.Topology.Zones.ToDictionary( - zone => zone.ZoneIndex, - zone => zone.AdjacentZoneIndices, - EqualityComparer.Default); - var distances = factions.ToDictionary( - faction => faction.CorporationKey, - faction => DistancesFrom(homes[faction.CorporationKey], adjacency), - StringComparer.Ordinal); - var zones = new Dictionary(); - var playerEntityIndex = -1; - - foreach (var topology in world.Topology.Zones.OrderBy(zone => zone.ZoneIndex)) - { - var celestial = world.Zones[topology.ZoneIndex]; - var random = new CultMath.Random(celestial.PostBodyRandomState); - var spawnRandom = new CultMath.Random(celestial.PostBodyRandomState ^ 0x51A7_10C5u); - var nearest = factions - .OrderBy(faction => distances[faction.CorporationKey][topology.ZoneIndex]) - .First(); - var homeDistance = distances[nearest.CorporationKey][topology.ZoneIndex]; - var influence = isPrelude - ? Math.Max(0, (nearest.InfluenceDistance + 1) / 2) - : Math.Max(0, nearest.InfluenceDistance); - var factionPresence = influence - homeDistance + 1; - var stationCount = (int)(random.NextFloat() * (factionPresence + 1)); - var orbits = celestial.Orbits.ToList(); - var entities = new List(); - var agentTasks = new List(); - var generators = new Dictionary(StringComparer.Ordinal); - - AetheriaDaemonLoadoutGenerator Generator(string factionKey) - { - if (!generators.TryGetValue(factionKey, out var generator)) - { - generator = new AetheriaDaemonLoadoutGenerator( - catalog, - (uint)random.NextInt(1, int.MaxValue), - topology.ZoneIndex, - homes, - adjacency, - isPrelude: isPrelude); - generators.Add(factionKey, generator); - } - return generator; - } - - var potential = celestial.Orbits - .Where(orbit => !string.IsNullOrWhiteSpace(orbit.ParentOrbitKey)) - .Where(orbit => !celestial.Orbits.Any(sibling => - !ReferenceEquals(sibling, orbit) && - string.Equals(sibling.ParentOrbitKey, orbit.ParentOrbitKey, StringComparison.Ordinal) && - Math.Abs(sibling.Distance - orbit.Distance) < 0.1)) - .OrderBy(orbit => orbit.Distance) - .ToArray(); - var selected = potential.Skip(potential.Length / 2).Take(Math.Max(0, stationCount)).ToArray(); - for (var stationIndex = 0; stationIndex < selected.Length; stationIndex++) - { - var baseOrbit = selected[stationIndex]; - var stationOrbit = new AetheriaOrbitSnapshot - { - OrbitKey = $"{identityPrefix}.zone.{topology.ZoneIndex}.station.{stationIndex}.orbit", - ParentOrbitKey = baseOrbit.ParentOrbitKey, - Distance = baseOrbit.Distance, - Phase = baseOrbit.Phase + MathF.PI / 3 * Sign(random.NextFloat() - 0.5f), - Period = baseOrbit.Distance, - FixedPosition = new AetheriaVector2() - }; - orbits.Add(stationOrbit); - var security = (int)((1f - MathF.Pow(random.NextFloat(), factionPresence / 2f)) * 3f); - entities.Add(AetheriaDaemonGeneratedEntityFactory.Create( - $"{topology.Name} Station {stationIndex + 1}", - "station", - nearest.CorporationKey, - Generator(nearest.CorporationKey).Build("station", nearest.CorporationKey), - orbitKey: stationOrbit.OrbitKey, - securityLevel: security, - securityRadius: celestial.Radius)); - - for (var turretIndex = 0; turretIndex < 2; turretIndex++) - { - var distanceMultiplier = turretIndex / 2; - if (turretIndex % 2 == 0) distanceMultiplier = -distanceMultiplier; - var turretOrbit = new AetheriaOrbitSnapshot - { - OrbitKey = $"{identityPrefix}.zone.{topology.ZoneIndex}.station.{stationIndex}.turret.{turretIndex}.orbit", - ParentOrbitKey = stationOrbit.ParentOrbitKey, - Distance = stationOrbit.Distance, - Phase = stationOrbit.Phase + 20f * distanceMultiplier / stationOrbit.Distance, - Period = stationOrbit.Distance, - FixedPosition = new AetheriaVector2() - }; - orbits.Add(turretOrbit); - entities.Add(AetheriaDaemonGeneratedEntityFactory.Create( - $"{topology.Name} Turret {stationIndex + 1}-{turretIndex + 1}", - "turret", - nearest.CorporationKey, - Generator(nearest.CorporationKey).Build("turret", nearest.CorporationKey), - orbitKey: turretOrbit.OrbitKey, - securityLevel: security, - securityRadius: celestial.Radius)); - } - } - - var enemyCount = (int)(random.NextFloat() * factionPresence * 2) + stationCount; - for (var enemyIndex = 0; enemyIndex < Math.Max(0, enemyCount); enemyIndex++) - { - var halfRadius = celestial.Radius * 0.5; - var enemy = AetheriaDaemonGeneratedEntityFactory.Create( - $"{nearest.ShortName} Ship {enemyIndex + 1}", - "ship", - nearest.CorporationKey, - Generator(nearest.CorporationKey).Build("ship", nearest.CorporationKey), - spawnRandom.NextFloat((float)-halfRadius, (float)halfRadius), - spawnRandom.NextFloat((float)-halfRadius, (float)halfRadius)); - var entityIndex = entities.Count; - var taskId = $"{identityPrefix}.zone.{topology.ZoneIndex}.entity.{entityIndex}.patrol"; - enemy.AgentTaskCapabilities = - [ - AetheriaRuntimeAgentTaskTypes.Attack, - AetheriaRuntimeAgentTaskTypes.Defend, - AetheriaRuntimeAgentTaskTypes.Explore, - AetheriaRuntimeAgentTaskTypes.Patrol - ]; - enemy.AssignedAgentTaskId = taskId; - agentTasks.Add(new AetheriaAgentTaskState - { - TaskId = taskId, - CorporationKey = nearest.CorporationKey, - TaskType = AetheriaRuntimeAgentTaskTypes.Patrol, - Priority = 0, - ZoneIndex = topology.ZoneIndex, - Status = AetheriaRuntimeAgentTaskStatuses.Assigned, - AssignedEntityIndex = entityIndex, - CompletionRadius = 10, - TargetOrbitKeys = orbits - .OrderBy(_ => spawnRandom.NextFloat()) - .Take(4) - .Select(orbit => orbit.OrbitKey) - .ToArray() - }); - entities.Add(enemy); - } - - var localPlayerIndex = -1; - if (topology.ZoneIndex == world.Topology.EntranceZoneIndex) - { - var protagonist = factions[0]; - var starter = new AetheriaDaemonLoadoutGenerator( - catalog, - world.Topology.GenerationSeed ^ 0x50A7_EA11u, - topology.ZoneIndex, - homes, - adjacency, - isPrelude: isPrelude).Build("ship", protagonist.CorporationKey); - localPlayerIndex = entities.Count; - entities.Add(AetheriaDaemonGeneratedEntityFactory.Create( - "Pilot", - "ship", - protagonist.CorporationKey, - starter)); - playerEntityIndex = localPlayerIndex; - } - - ApplyOrbitalPositions(orbits, entities); - - zones.Add(topology.ZoneIndex, new AetheriaDaemonMaterializedTutorialZone( - topology, - celestial, - nearest.CorporationKey, - factionPresence, - orbits.ToArray(), - entities.ToArray(), - agentTasks.ToArray(), - localPlayerIndex)); - } - - if (playerEntityIndex < 0) - throw new InvalidOperationException("Tutorial materialization produced no player entry ship."); - return new AetheriaDaemonMaterializedTutorialWorld( - world.Topology, - zones, - world.Topology.EntranceZoneIndex, - playerEntityIndex); - } - - private static void ApplyOrbitalPositions( - IReadOnlyList orbits, - IReadOnlyList entities) - { - var runtimeZone = new AetheriaRuntimeZoneSnapshotCommit - { - Orbits = orbits.Select(orbit => new AetheriaRuntimeOrbitSnapshotCommit - { - OrbitKey = orbit.OrbitKey, - ParentOrbitKey = orbit.ParentOrbitKey, - Distance = orbit.Distance, - Phase = orbit.Phase, - Period = orbit.Period, - FixedPositionX = orbit.FixedPosition?.X ?? 0, - FixedPositionY = orbit.FixedPosition?.Y ?? 0 - }).ToArray() - }; - var positions = AetheriaRuntimeOrbitQueries.BuildPositions(runtimeZone); - foreach (var entity in entities.Where(entity => !string.IsNullOrWhiteSpace(entity.OrbitKey))) - { - if (!positions.TryGetValue(entity.OrbitKey, out var position)) - throw new InvalidDataException( - $"Generated orbital entity '{entity.Name}' references missing orbit '{entity.OrbitKey}'."); - entity.Position.X = position.x; - entity.Position.Z = position.z; - } - } - - private static IReadOnlyDictionary DistancesFrom( - int start, - IReadOnlyDictionary> adjacency) - { - var result = adjacency.Keys.ToDictionary(index => index, _ => int.MaxValue); - var queue = new Queue(); - result[start] = 0; - queue.Enqueue(start); - while (queue.Count > 0) - { - var current = queue.Dequeue(); - foreach (var neighbor in adjacency[current]) - { - if (result[neighbor] <= result[current] + 1) continue; - result[neighbor] = result[current] + 1; - queue.Enqueue(neighbor); - } - } - return result; - } - - private static int Sign(float value) => value < 0 ? -1 : value > 0 ? 1 : 0; -} diff --git a/Aetheria.State.Daemon/AetheriaDaemonZoneBodyGenerator.cs b/Aetheria.State.Daemon/AetheriaDaemonZoneBodyGenerator.cs deleted file mode 100644 index 32ab3ef5c..000000000 --- a/Aetheria.State.Daemon/AetheriaDaemonZoneBodyGenerator.cs +++ /dev/null @@ -1,508 +0,0 @@ -using Aetheria.State.Documents; - -public sealed record AetheriaDaemonGeneratedZonePlan( - uint GenerationSeed, - uint PostBodyRandomState, - double Radius, - double Mass, - double HierarchyMass, - int EmptyOrbitCount, - AetheriaOrbitSnapshot[] Orbits, - AetheriaBodySnapshot[] Bodies) -{ - public double GravityTerrainDepth { get; init; } = 64; - public double GravityTerrainDepthExponent { get; init; } = 2; - public double GravityTerrainBoundaryFog { get; init; } -} - -public sealed record AetheriaDaemonTutorialWorldPlan( - AetheriaDaemonTutorialTopology Topology, - IReadOnlyDictionary Zones); - -public static class AetheriaDaemonTutorialWorldGenerator -{ - public static AetheriaDaemonTutorialWorldPlan Generate( - IReadOnlyList factions, - uint seed, - AetheriaDaemonTutorialTopologySettings? settings = null) - { - var topology = AetheriaDaemonTutorialTopologyGenerator.GenerateFossil(factions, seed, settings); - var zones = topology.Zones.ToDictionary( - zone => zone.ZoneIndex, - zone => AetheriaDaemonZoneBodyGenerator.Generate( - seed, - zone, - AetheriaDaemonTutorialTopologyGenerator.TutorialCloudDensity( - zone.X, - zone.Y, - topology.NoisePosition))); - return new AetheriaDaemonTutorialWorldPlan(topology, zones); - } - - public static AetheriaDaemonTutorialWorldPlan GenerateRegular( - IReadOnlyList factions, - uint seed, - AetheriaDaemonRegularTopologySettings? settings = null) - { - var topology = AetheriaDaemonRegularTopologyGenerator.GenerateFossil(factions, seed, settings); - var zones = topology.Zones.ToDictionary( - zone => zone.ZoneIndex, - zone => AetheriaDaemonZoneBodyGenerator.Generate( - seed, - zone, - AetheriaDaemonRegularTopologyGenerator.MainCloudDensity(zone.X, zone.Y), - "regular")); - return new AetheriaDaemonTutorialWorldPlan(topology, zones); - } -} - -/// -/// Daemon-owned port of the fossil ZoneGenerator celestial hierarchy. Entity -/// population consumes this plan later; it cannot regenerate bodies or orbits. -/// -public static class AetheriaDaemonZoneBodyGenerator -{ - private const int MaximumPlacementSamples = 32; - - public static AetheriaDaemonGeneratedZonePlan Generate( - uint galaxySeed, - AetheriaDaemonTutorialZoneTopology zone, - float cloudDensity, - string identityPrefix = "tutorial") - { - var density = Saturate(cloudDensity / 2f); - var radius = ExponentialLerp(density, 1.5f, 1000, 10000); - var mass = ExponentialLerp(density, 1.5f, 10000, 500000); - var targetSubzoneCount = ExponentialLerp(density, 1.5f, 0, 8); - var zoneSeed = StableZoneSeed(galaxySeed, zone); - var random = new CultMath.Random(zoneSeed); - var planets = new List(); - - if (targetSubzoneCount > 1) - { - var boundary = new Circle(new CultMath.float2(0, 0), radius * 0.9f); - var occupied = new List(); - var start = random.NextFloat(radius * 0.25f, radius * 0.5f) * random.NextFloat2Direction(); - occupied.Add(new Circle(start, -boundary.DistanceTo(start))); - - var samples = 0; - while (occupied.Count < targetSubzoneCount && samples < MaximumPlacementSamples) - { - samples = 0; - for (var i = 0; i < MaximumPlacementSamples; i++) - { - var point = random.NextFloat2(-radius, radius); - var tangentRadius = MathF.Min( - -boundary.DistanceTo(point), - occupied.Min(circle => circle.DistanceTo(point))); - if (tangentRadius > 0) - { - occupied.Add(new Circle(point, tangentRadius)); - break; - } - samples++; - } - } - - var totalArea = occupied.Sum(circle => circle.Area); - foreach (var circle in occupied) - planets.AddRange(GenerateSystem( - ref random, - circle.Area / totalArea * mass, - circle.Radius, - circle.Center)); - } - else - { - planets.AddRange(GenerateSystem(ref random, mass, radius, new CultMath.float2(0, 0))); - } - - var orbitKeys = planets.Select((_, index) => $"{identityPrefix}.zone.{zone.ZoneIndex}.orbit.{index}").ToArray(); - var nodeIndices = planets.Select((node, index) => (node, index)).ToDictionary(value => value.node, value => value.index); - var orbits = planets.Select((planet, index) => new AetheriaOrbitSnapshot - { - OrbitKey = orbitKeys[index], - ParentOrbitKey = planet.Parent == null ? "" : orbitKeys[nodeIndices[planet.Parent]], - FixedPosition = new AetheriaVector2 { X = planet.FixedPosition.x, Y = planet.FixedPosition.y }, - Distance = planet.Distance, - Phase = planet.Phase, - Period = planet.Distance - }).ToArray(); - - var bodies = new List(); - for (var nodeIndex = 0; nodeIndex < planets.Count; nodeIndex++) - { - var planet = planets[nodeIndex]; - if (planet.Empty) - continue; - bodies.Add(ProjectBody( - identityPrefix, zone.ZoneIndex, bodies.Count, planet, orbits[nodeIndex], ref random)); - } - - return new AetheriaDaemonGeneratedZonePlan( - zoneSeed, - random.state, - radius, - mass, - planets.Sum(planet => (double)planet.Mass), - planets.Count(planet => planet.Empty), - orbits, - bodies.ToArray()); - } - - private static PlanetNode[] GenerateSystem( - ref CultMath.Random random, - float mass, - float radius, - CultMath.float2 fixedPosition) - { - var root = new PlanetNode - { - Mass = mass, - ChildDistanceMaximum = radius * 0.75f, - ChildDistanceMinimum = PlanetSafetyRadius(mass), - FixedPosition = fixedPosition - }; - var rosette = random.NextFloat() < 0.1f; - if (rosette) - { - root.ExpandRosette(ref random, (int)(random.NextFloat(1, 5) + random.NextFloat(1, 5))); - root.ExpandSolar( - ref random, - (int)(random.NextFloat(1, 3) * random.NextFloat(1, 2)), - 0.6f, 0.8f, 1.25f, 1.75f, 1, 0.1f); - var averageChildMass = root.Children.Sum(value => value.Mass) / root.Children.Count; - foreach (var planet in root.Children.Where(value => value.Mass > 1000).ToArray()) - { - var scale = planet.Mass / averageChildMass; - planet.ExpandSolar( - ref random, - (int)(random.NextFloat(1, 3 * scale) + random.NextFloat(1, 3 * scale)), - 0.75f, 2.5f, 1 + scale * 0.25f, 1.05f + scale * 0.5f, - random.NextFloat() * random.NextFloat() * 10 + 1, - 0.5f); - } - } - else - { - root.ExpandSolar( - ref random, - random.NextInt(5, 15), - 0.75f, 2.5f, 1.1f, 1.25f, - random.NextFloat() * random.NextFloat() * 10 + 1, - 0.25f); - } - - var expanded = new HashSet(); - var binaries = new HashSet(); - for (var pass = 0; pass < 5; pass++) - { - var candidates = root.All() - .Where(planet => planet != root && - (!rosette || planet.Parent != root) && - planet.Mass > 100 && - !expanded.Contains(planet)) - .ToArray(); - foreach (var planet in candidates) - { - if (random.NextFloat() >= 0.25f) - continue; - if (random.NextFloat() < 0.1f) - { - planet.ExpandRosette(ref random, 2); - binaries.UnionWith(planet.Children); - } - else - { - planet.ExpandSolar( - ref random, - planet.Mass < 1000 ? random.NextInt(1, 3) : random.NextInt(2, 6), - 0.75f, 1.5f, 1.05f, 1.25f, 1, 0.15f); - } - expanded.Add(planet); - } - } - - var beltCandidates = root.All() - .Where(planet => planet != root && - (!rosette || planet.Parent != root) && - planet.Mass < 500 && - !binaries.Contains(planet) && - planet.Children.Count == 0) - .Reverse() - .ToArray(); - foreach (var planet in beltCandidates) - { - if (random.NextFloat() < 0.25f && planet.Parent!.Children.All(sibling => !sibling.Belt)) - planet.Belt = true; - } - - var all = root.All().ToArray(); - var totalMass = all.Sum(planet => planet.Mass); - foreach (var planet in all) - planet.Mass = planet.Mass / totalMass * mass; - return all; - } - - private static AetheriaBodySnapshot ProjectBody( - string identityPrefix, - int zoneIndex, - int bodyIndex, - PlanetNode planet, - AetheriaOrbitSnapshot orbit, - ref CultMath.Random random) - { - var kind = planet.Belt ? "asteroid_belt" : - planet.Mass > 5000 ? "sun" : - planet.Mass > 1000 ? "gas_giant" : - planet.Mass > 100 ? "planet" : "planetoid"; - var body = new AetheriaBodySnapshot - { - BodyKey = $"{identityPrefix}.zone.{zoneIndex}.body.{bodyIndex}", - Kind = kind, - Name = $"Z{zoneIndex:D2}-{bodyIndex:D3}", - OrbitKey = orbit.OrbitKey, - Mass = planet.Mass, - Resources = [], - BodyRadiusMultiplier = 1, - GravityRadiusMultiplier = 1, - GravityDepthMultiplier = 1, - GravityDepthExponent = 2, - GravityInfluenceCenterX = double.NaN, - GravityInfluenceCenterZ = double.NaN, - GravityInfluenceRadius = 500 * Math.Pow(planet.Mass, 0.25), - GravityWellDepth = 30 * Math.Pow(planet.Mass, 0.175), - GravityWaveRadius = 500 * Math.Pow(planet.Mass, 0.25), - GravityWaveDepth = 0.5 * Math.Pow(planet.Mass, 0.3), - GravityWaveSpeed = 0.025, - Asteroids = [], - GasGiantVisual = new AetheriaGasGiantVisualState(), - SunVisual = new AetheriaSunVisualState() - }; - - if (kind == "asteroid_belt") - { - var count = Math.Max(0, (int)(MathF.Pow(planet.Mass * planet.Distance, 0.5f) + 11)); - var asteroids = new AetheriaAsteroidSnapshot[count]; - for (var asteroidIndex = 0; asteroidIndex < count; asteroidIndex++) - { - asteroids[asteroidIndex] = new AetheriaAsteroidSnapshot - { - Distance = planet.Distance + random.NextFloat() * (random.NextFloat() - 0.5f) * - (5 * MathF.Pow(planet.Distance, 0.666f) + 50), - Phase = random.NextFloat(), - Size = random.NextFloat(), - RotationSpeed = ExponentialLerp(random.NextFloat(), 2, 0.1f, 0.5f), - MiningAccumulators = [] - }; - } - body.Asteroids = asteroids; - } - else if (kind is "gas_giant" or "sun") - { - var colors = new List(); - if (kind == "sun") - { - var primary = random.NextFloat(); - var secondary = Fraction(primary + 1 + 0.33f * (random.NextFloat() > 0.5f ? 1 : -1)); - colors.Add(Color(Hsv(primary, 0.85f, 0.5f), 0)); - colors.Add(Color(Hsv(secondary, 0.85f, 1), 1)); - var fog = Hsv(primary, 0.55f, 1); - var light = Hsv(primary, 0.5f, 1); - body.SunVisual = new AetheriaSunVisualState - { - FogTintColor = Vector(fog), - LightColor = Vector(light), - LightRadiusMultiplier = 1 - }; - } - else - { - var primary = random.NextFloat(); - var right = Fraction(primary + 0.25f); - var left = Fraction(primary + 0.75f); - var bandCount = (int)(ExponentialLerp(random.NextFloat(), 2, 5, 8) + 0.5f); - for (var band = 0; band < bandCount; band++) - { - var time = (float)band / (bandCount - 1); - var hue = random.NextFloat() > 0.25f ? primary : random.NextFloat() > 0.5f ? right : left; - colors.Add(Color(Hsv( - hue, - ExponentialLerp(random.NextFloat(), 0.5f, 0, 0.8f), - ExponentialLerp(random.NextFloat(), 0.5f, 0, 0.8f)), time)); - } - } - body.GasGiantVisual = new AetheriaGasGiantVisualState - { - FirstOffsetDomainRotationSpeed = kind == "sun" ? 5 : 0, - FirstOffsetRotationSpeed = 5, - SecondOffsetDomainRotationSpeed = -25, - SecondOffsetRotationSpeed = 10, - AlbedoRotationSpeed = -3, - Colors = colors.ToArray(), - MaterialOverrides = [] - }; - } - return body; - } - - private static uint StableZoneSeed(uint galaxySeed, AetheriaDaemonTutorialZoneTopology zone) - { - var hash = 2166136261u ^ galaxySeed; - foreach (var value in System.Text.Encoding.UTF8.GetBytes(zone.Name ?? "")) - hash = (hash ^ value) * 16777619u; - hash = (hash ^ unchecked((uint)BitConverter.SingleToInt32Bits(zone.X))) * 16777619u; - hash = (hash ^ unchecked((uint)BitConverter.SingleToInt32Bits(zone.Y))) * 16777619u; - hash = (hash ^ unchecked((uint)zone.ZoneIndex)) * 16777619u; - return hash == 0 ? 0x6E62_4EB7u : hash; - } - - private static float PlanetSafetyRadius(float mass) => 2.5f * MathF.Pow(mass, 0.25f); - private static float ExponentialLerp(float value, float exponent, float minimum, float maximum) => - minimum + MathF.Pow(Saturate(value), exponent) * (maximum - minimum); - private static float Saturate(float value) => value < 0 ? 0 : value > 1 ? 1 : value; - private static float Fraction(float value) => value - MathF.Floor(value); - - private static CultMath.float3 Hsv(float hue, float saturation, float value) - { - hue = Fraction(hue); - var sector = hue * 6; - var index = (int)MathF.Floor(sector); - var fraction = sector - index; - var p = value * (1 - saturation); - var q = value * (1 - saturation * fraction); - var t = value * (1 - saturation * (1 - fraction)); - return index switch - { - 0 => new CultMath.float3(value, t, p), - 1 => new CultMath.float3(q, value, p), - 2 => new CultMath.float3(p, value, t), - 3 => new CultMath.float3(p, q, value), - 4 => new CultMath.float3(t, p, value), - _ => new CultMath.float3(value, p, q) - }; - } - - private static AetheriaColor Color(CultMath.float3 value, float time) => new() - { - X = value.x, - Y = value.y, - Z = value.z, - W = time - }; - - private static AetheriaVector3 Vector(CultMath.float3 value) => new() - { - X = value.x, - Y = value.y, - Z = value.z - }; - - private sealed record Circle(CultMath.float2 Center, float Radius) - { - public float Area => MathF.PI * Radius * Radius; - public float DistanceTo(CultMath.float2 point) => CultMath.math.length(point - Center) - Radius; - } - - private sealed class PlanetNode - { - public float Distance; - public float Phase; - public float Mass; - public float ChildDistanceMinimum; - public float ChildDistanceMaximum; - public bool Empty; - public bool Belt; - public List Children { get; } = []; - public PlanetNode? Parent; - public CultMath.float2 FixedPosition; - - public IEnumerable All() => new[] { this }.Concat(Children.SelectMany(child => child.All())); - - public void ExpandRosette(ref CultMath.Random random, int vertices) - { - Empty = true; - var sharedMass = Mass / vertices * 2; - var proportion = vertices % 2 == 0 ? random.NextFloat(0.5f, 0.95f) : 0.5f; - var distance = (ChildDistanceMinimum + ChildDistanceMaximum) / 2; - var p0 = new CultMath.float2(0, distance); - var angle = 1f / vertices * MathF.PI * 2; - var p1 = new CultMath.float2(MathF.Cos(angle), MathF.Sin(angle)) * distance; - var neighborDistance = CultMath.math.length(p0 - p1); - var p0ChildDistance = (neighborDistance * proportion - PlanetSafetyRadius(sharedMass * (1 - proportion))) * 0.75f; - var p1ChildDistance = (neighborDistance * (1 - proportion) - PlanetSafetyRadius(sharedMass * proportion)) * 0.75f; - for (var index = 0; index < vertices; index++) - { - var child = new PlanetNode - { - Parent = this, - Mass = sharedMass * (index % 2 == 0 ? proportion : 1 - proportion), - Distance = distance, - Phase = (float)index / vertices, - ChildDistanceMaximum = index % 2 == 0 ? p0ChildDistance : p1ChildDistance - }; - child.ChildDistanceMinimum = PlanetSafetyRadius(child.Mass) * 2; - Children.Add(child); - } - ChildDistanceMinimum = distance + p0ChildDistance; - } - - public void ExpandSolar( - ref CultMath.Random random, - int count, - float massMultiplierMinimum, - float massMultiplierMaximum, - float distanceMultiplierMinimum, - float distanceMultiplierMaximum, - float jupiterJump, - float massFraction) - { - if (count == 0 || ChildDistanceMaximum < ChildDistanceMinimum) - return; - var masses = new float[count]; - var distances = new float[count]; - float massTotal = distances[0] = masses[0] = 1; - for (var index = 1; index < count; index++) - { - massTotal += masses[index] = masses[index - 1] * - random.NextFloat(massMultiplierMinimum, massMultiplierMaximum) * - (count / 2 == index ? jupiterJump : 1); - distances[index] = distances[index - 1] * - random.NextFloat(distanceMultiplierMinimum, distanceMultiplierMaximum); - } - for (var index = 0; index < count; index++) - masses[index] *= random.NextFloat(0.1f, 1); - for (var index = 0; index < count; index++) - masses[index] = masses[index] / massTotal * Mass * massFraction; - if (count > 1) - { - var original = distances.ToArray(); - for (var index = 0; index < count; index++) - distances[index] = ChildDistanceMinimum + - (ChildDistanceMaximum - ChildDistanceMinimum) * - ((original[index] - original[0]) / (original[^1] - original[0])) + - PlanetSafetyRadius(masses[index]); - } - for (var index = 0; index < count; index++) - { - if (masses[index] <= 1) - continue; - var child = new PlanetNode - { - Parent = this, - Mass = masses[index], - Distance = distances[index], - Phase = random.NextFloat() - }; - child.ChildDistanceMinimum = PlanetSafetyRadius(child.Mass) * 2; - child.ChildDistanceMaximum = MathF.Min( - index == 0 ? child.Distance - ChildDistanceMinimum : child.Distance - distances[index - 1], - index < count - 1 ? distances[index + 1] - child.Distance : float.PositiveInfinity); - if (float.IsNaN(child.Distance)) - throw new NotFiniteNumberException("Planet created with NaN distance."); - if (child.ChildDistanceMaximum > child.ChildDistanceMinimum) - Children.Add(child); - } - } - } -} diff --git a/Aetheria.State.Daemon/AetheriaDaemonZoneGenerator.cs b/Aetheria.State.Daemon/AetheriaDaemonZoneGenerator.cs deleted file mode 100644 index e53be573a..000000000 --- a/Aetheria.State.Daemon/AetheriaDaemonZoneGenerator.cs +++ /dev/null @@ -1,595 +0,0 @@ -using Aetheria.State; -using Aetheria.State.Documents; -using GameCult.Aetheria.State.Verse; -using GameCult.Caching; - -internal static class AetheriaDaemonTerminusScenarios -{ - public const string Standard = "standard"; - public const string ReleasedClientProof = "released-client-proof"; - public const string CargoCapacityRejectionProof = "cargo-capacity-rejection-proof"; - - public static string Parse(string? value) - { - var scenario = string.IsNullOrWhiteSpace(value) ? Standard : value.Trim().ToLowerInvariant(); - return scenario switch - { - Standard => Standard, - ReleasedClientProof => ReleasedClientProof, - CargoCapacityRejectionProof => CargoCapacityRejectionProof, - _ => throw new ArgumentException( - $"Unknown Terminus scenario '{value}'. Expected {Standard}, {ReleasedClientProof}, or {CargoCapacityRejectionProof}.") - }; - } -} - -internal static class AetheriaDaemonZoneGenerator -{ - public const string RunId = "local-terminus"; - public const uint GenerationSeed = 0xA37E_2026u; - - public static async Task WritePlayableRunAsync( - AetheriaStateNode node, - AetheriaRuntimeCatalogSnapshot catalog, - string now, - string scenario, - AetheriaDeploymentReceipt? deployment = null) - { - scenario = AetheriaDaemonTerminusScenarios.Parse(scenario); - if (deployment != null && (!deployment.Accepted || !string.Equals(deployment.Mode, AetheriaGameModes.Terminus, StringComparison.Ordinal))) - throw new InvalidOperationException("A product Terminus run requires an accepted Terminus deployment."); - var runId = deployment == null - ? RunIdFor(scenario) - : $"terminus-{StableToken(deployment.DeploymentId)}"; - var generationSeed = deployment == null - ? GenerationSeed - : AetheriaDaemonRunFactory.StableSeed(deployment.DeploymentId); - var runKey = new CultRecordKey($"global:aetheria.run_state.{runId}.v1"); - var zoneKey = new CultRecordKey($"global:aetheria.zone_state.{runId}.0.v1"); - var corporationKeys = (catalog.Corporations ?? Array.Empty()) - .Select(value => value.CorporationKey) - .Where(value => !string.IsNullOrWhiteSpace(value)) - .ToArray(); - if (corporationKeys.Length == 0) - { - throw new InvalidDataException( - "Terminus generation requires at least one typed corporation with a stable corporation key."); - } - var homeZones = corporationKeys.ToDictionary(value => value, _ => 0, StringComparer.Ordinal); - var availabilityFactions = new Dictionary(StringComparer.Ordinal) - { - ["player"] = corporationKeys.ElementAtOrDefault(0) ?? "", - ["raider"] = corporationKeys.ElementAtOrDefault(1) ?? corporationKeys.ElementAtOrDefault(0) ?? "", - ["neutral"] = corporationKeys.ElementAtOrDefault(2) ?? corporationKeys.ElementAtOrDefault(0) ?? "" - }; - var rootRandom = new CultMath.Random(generationSeed); - var adjacency = new Dictionary> { [0] = Array.Empty() }; - var loadouts = availabilityFactions.Values - .Where(value => !string.IsNullOrWhiteSpace(value)) - .Distinct(StringComparer.Ordinal) - .OrderBy(value => value, StringComparer.Ordinal) - .ToDictionary( - value => value, - _ => new AetheriaDaemonLoadoutGenerator( - catalog, - (uint)rootRandom.NextInt(1, int.MaxValue), - 0, - homeZones, - adjacency), - StringComparer.Ordinal); - var entities = GenerateEntities(loadouts, availabilityFactions, catalog, scenario, runId); - if (deployment != null) - ApplyDeployment(entities[1], deployment.Loadout); - var entityKeys = Enumerable.Range(0, entities.Length) - .Select(index => EntityKeyForRun(runId, 0, index)) - .ToArray(); - - var settings = await node.MutableDocument(AetheriaStateNode.PlayerSettingsKey) - .ReadAsync() - .ConfigureAwait(false) ?? new AetheriaPlayerSettings(); - settings.ActiveRunKey = runKey.ToString(); - settings.PlayerName = string.IsNullOrWhiteSpace(settings.PlayerName) ? "Terminus Pilot" : settings.PlayerName; - settings.LastUpdatedAtUtc = now; - await node.MutableDocument(AetheriaStateNode.PlayerSettingsKey) - .ReplaceAsync(settings) - .ConfigureAwait(false); - - await node.MutableDocument(runKey).ReplaceAsync(new AetheriaRunState - { - RunId = runId, - EntranceZoneIndex = 0, - ExitZoneIndex = 0, - CurrentZoneIndex = 0, - DiscoveredZoneIndices = [0], - ZoneKeys = [zoneKey.ToString()], - GenerationSeed = generationSeed, - CurrentEntityKey = entityKeys[1], - LifecyclePhase = AetheriaRuntimeRunLifecycle.Active, - TerminalFrameId = -1, - GameMode = AetheriaGameSessionState.TerminusMode, - UpdatedAtUtc = now - }).ConfigureAwait(false); - - await node.MutableDocument(zoneKey).ReplaceAsync(new AetheriaZoneState - { - Name = "Daemon Generated Terminus", - Position = Vec2(0, 0), - EntityKeys = entityKeys, - FactionIndices = [0, 1, 2], - OwnerFactionIndex = 0, - Orbits = GenerateOrbits(), - Bodies = GenerateBodies(), - GravityTerrainRadius = 1900, - GravityTerrainDepth = 7, - GravityTerrainDepthExponent = 1.18, - GravityTerrainBoundaryFog = 0.25, - GravityTerrainWaveFrequency = 0.55, - NextPickupIndex = 0, - DroppedPickups = Array.Empty() - }).ConfigureAwait(false); - - for (var i = 0; i < entities.Length; i++) - { - await node.MutableDocument(new CultRecordKey(entityKeys[i])) - .ReplaceAsync(entities[i]) - .ConfigureAwait(false); - } - - await node.FlushAsync().ConfigureAwait(false); - } - - public static string RunIdFor(string scenario) - { - scenario = AetheriaDaemonTerminusScenarios.Parse(scenario); - return string.Equals(scenario, AetheriaDaemonTerminusScenarios.Standard, StringComparison.Ordinal) - ? RunId - : $"{RunId}-{scenario}"; - } - - public static string EntityKey(string scenario, int zoneIndex, int entityIndex) - { - return $"global:aetheria.run_state.{RunIdFor(scenario)}.zone.{zoneIndex}.entity.{entityIndex}.v1"; - } - - private static string EntityKeyForRun(string runId, int zoneIndex, int entityIndex) => - $"global:aetheria.run_state.{runId}.zone.{zoneIndex}.entity.{entityIndex}.v1"; - - private static AetheriaOrbitSnapshot[] GenerateOrbits() - { - var bodies = BodyPlans(); - return bodies.Select(body => new AetheriaOrbitSnapshot - { - OrbitKey = $"{body.Key}.orbit", - ParentOrbitKey = body.ParentKey.Length == 0 ? "" : $"{body.ParentKey}.orbit", - Distance = body.Distance, - Phase = body.Phase, - FixedPosition = Vec2(body.X, body.Z) - }).ToArray(); - } - - private static AetheriaBodySnapshot[] GenerateBodies() - { - return BodyPlans().Select(body => new AetheriaBodySnapshot - { - BodyKey = body.Key, - Kind = body.Kind, - Name = body.Name, - OrbitKey = $"{body.Key}.orbit", - Mass = body.Mass, - BodyRadiusMultiplier = body.BodyRadius, - GravityRadiusMultiplier = 1, - GravityDepthMultiplier = 1, - GravityDepthExponent = body.DepthExponent, - GravityInfluenceCenterX = body.X, - GravityInfluenceCenterZ = body.Z, - GravityInfluenceRadius = body.GravityRadius, - GravityWellDepth = body.GravityDepth, - GravityWaveRadius = body.WaveRadius, - GravityWaveDepth = body.WaveDepth, - GravityWaveSpeed = body.WaveSpeed, - Asteroids = body.Kind == "asteroid_belt" ? Asteroids(body.Distance) : [], - GasGiantVisual = GasGiantVisual(body), - SunVisual = SunVisual(body) - }).ToArray(); - } - - private static BodyPlan[] BodyPlans() - { - return - [ - Body("local.sun", "", "sun", "Terminus", 0, 0, 1600, 2.8, 980, 92, 3.0, 480, 11, 1.8), - Body("local.inner", "local.sun", "planet", "Cairn", 250, 0.45, 110, 0.78, 230, 20, 2.2, 92, 3, 1.0), - Body("local.blue", "local.sun", "planet", "Blueglass", 440, 2.45, 260, 1.18, 360, 36, 2.55, 150, 5, 1.1), - Body("local.blue.moon", "local.blue", "moon", "Latch", 520, 2.78, 70, 0.58, 170, 15, 2.8, 70, 2.2, 1.7), - Body("local.ember", "local.sun", "planet", "Emberhook", 640, 3.78, 220, 1.05, 330, 31, 2.25, 128, 4.2, 1.3), - Body("local.ember.moon", "local.ember", "moon", "Ashwake", 735, 3.45, 86, 0.62, 185, 16, 2.7, 72, 2.4, 1.45), - Body("local.belt", "local.sun", "asteroid_belt", "Cinder Belt", 760, -0.28, 140, 1.0, 310, 24, 2.0, 122, 4, 0.8), - Body("local.green", "local.sun", "planet", "Marrow", 930, 1.28, 180, 0.96, 300, 26, 2.35, 112, 3.6, 1.0), - Body("local.outer", "local.sun", "gas_giant", "Vesper", 1120, 0.58, 420, 1.55, 500, 48, 2.4, 230, 7, 0.72), - Body("local.outer.moon.a", "local.outer", "moon", "Iris", 1240, 0.88, 92, 0.66, 195, 17, 2.75, 78, 2.5, 1.2), - Body("local.outer.moon.b", "local.outer", "moon", "Hush", 1025, 0.28, 76, 0.55, 180, 15, 2.9, 70, 2.1, 1.5), - Body("local.deep", "local.sun", "gas_giant", "Nacre", 1420, 5.72, 340, 1.36, 440, 41, 2.55, 200, 5.8, 0.95) - ]; - } - - private static BodyPlan Body( - string key, - string parentKey, - string kind, - string name, - double distance, - double phase, - double mass, - double bodyRadius, - double gravityRadius, - double gravityDepth, - double depthExponent, - double waveRadius, - double waveDepth, - double waveSpeed) - { - if (gravityDepth <= 0) - throw new ArgumentOutOfRangeException(nameof(gravityDepth), "Gravity depth is an unsigned well magnitude; Ymir positive radial strength attracts."); - - return new BodyPlan( - key, - parentKey, - kind, - name, - distance, - phase, - Math.Cos(phase) * distance, - Math.Sin(phase) * distance, - mass, - bodyRadius, - gravityRadius, - gravityDepth, - depthExponent, - waveRadius, - waveDepth, - waveSpeed); - } - - private static AetheriaEntitySnapshot[] GenerateEntities( - IReadOnlyDictionary loadouts, - IReadOnlyDictionary availabilityFactions, - AetheriaRuntimeCatalogSnapshot catalog, - string scenario, - string runId) - { - var keys = Enumerable.Range(0, 12) - .Select(index => EntityKeyForRun(runId, 0, index)) - .ToArray(); - - var entities = new[] - { - Entity(loadouts, availabilityFactions, "Anchor Station", "station", -50, -30, 0, 0, "player", 760, keys[6], [keys[1], keys[2], keys[3], keys[4], keys[6], keys[7], keys[11]], 2, 900, AetheriaDaemonNativeCatalog.DockyardHullItemKey), - Entity(loadouts, availabilityFactions, "Vanguard One", "ship", -40, -30, 0, 0, "player", 540, keys[6], [keys[0], keys[2], keys[4], keys[6], keys[7]]), - Entity(loadouts, availabilityFactions, "Wing Two", "ship", 145, 125, -5, 7, "player", 450, keys[6], [keys[0], keys[1], keys[6], keys[8]]), - Entity(loadouts, availabilityFactions, "Torch Three", "ship", -235, 210, 8, -4, "player", 500, keys[7], [keys[0], keys[1], keys[7]]), - Entity(loadouts, availabilityFactions, "Foundry Tug", "ship", 330, -155, -2, 3, "player", 390, keys[6], [keys[0], keys[1], keys[6], keys[10]]), - Entity(loadouts, availabilityFactions, "Derelict Relay", "station", -390, 270, 0, 0, "neutral", 160, "", [keys[0], keys[3]]), - Entity(loadouts, availabilityFactions, "Ash Raider", "ship", 20, -30, -5, -2, "raider", 320, "", [keys[0], keys[1], keys[2], keys[4]]), - Entity(loadouts, availabilityFactions, "Cinder Knife", "ship", 585, -115, -7, 1, "raider", 280, "", [keys[0], keys[3]]), - Entity(loadouts, availabilityFactions, "Blackwake", "ship", 690, 300, -4, -3, "raider", 230, "", [keys[2]]), - Entity(loadouts, availabilityFactions, "Vesper Sloop", "ship", 960, 620, -3, -5, "raider", 250, "", [keys[4]]), - Entity(loadouts, availabilityFactions, "Survey Skiff", "ship", -610, -365, 3, 5, "neutral", 210, "", [keys[4], keys[5]]), - Entity(loadouts, availabilityFactions, "Lagrange Beacon", "station", 105, 510, 0, 0, "neutral", 190, "", [keys[0], keys[1], keys[6]]) - }; - foreach (var index in new[] { 1, 2, 3, 4 }) - entities[index].HomeEntityKey = keys[0]; - if (!string.Equals(scenario, AetheriaDaemonTerminusScenarios.Standard, StringComparison.Ordinal)) - ApplyReleasedClientProofScenario(entities, catalog, scenario); - entities[2].AgentTaskCapabilities = ["attack", "defend", "explore"]; - entities[3].AgentTaskCapabilities = ["attack", "defend"]; - entities[4].AgentTaskCapabilities = ["mine", "haul", "tow", "explore"]; - return entities; - } - - private static void ApplyReleasedClientProofScenario( - AetheriaEntitySnapshot[] entities, - AetheriaRuntimeCatalogSnapshot catalog, - string scenario) - { - // Start the proof pilot at rest on the station's authored center. The proof session owns - // its paused initial clock, so client connection latency cannot alter this starting state. - entities[1].Position.X = entities[0].Position.X; - entities[1].Position.Z = entities[0].Position.Z; - entities[1].Velocity = Vec2(0, 0); - var playerFactionKey = entities[1].FactionKey; - for (var index = 0; index < entities.Length; index++) - if (index != 6) - entities[index].FactionKey = playerFactionKey; - entities[1].CargoContents = [new AetheriaCargoBayLoadout()]; - entities[6].StatGrids = - [ - StatGrid("hull", 1), - StatGrid("shield", 0), - StatGrid("heat", 0) - ]; - var salvage = entities[6].Equipment.FirstOrDefault() - ?? throw new InvalidOperationException("The starter raider loadout produced no canonical salvage item."); - var generation = entities[6].LoadoutGeneration - ?? throw new InvalidOperationException("The starter raider loadout produced no generation receipt."); - var selections = generation.Selections ?? Array.Empty(); - var salvageSelection = selections.First(selection => - string.Equals(selection.Role, "equipment", StringComparison.Ordinal) && - string.Equals(selection.ItemKey, salvage.ItemKey, StringComparison.Ordinal)); - entities[6].CargoContents = - [ - new AetheriaCargoBayLoadout - { - Items = - [ - new AetheriaLoadoutItemSlot - { - Item = new AetheriaLoadoutItem - { - ItemKey = salvage.ItemKey, - Quality = salvage.Quality, - Durability = salvage.Durability, - Quantity = 1, - Enabled = true - } - } - ] - } - ]; - entities[6].Equipment = Array.Empty(); - entities[6].WeaponGroups = Array.Empty(); - generation.Selections = selections - .Where(selection => string.Equals(selection.Role, "hull", StringComparison.Ordinal)) - .Append(new AetheriaLoadoutGenerationSelection - { - Role = "cargo", - ItemKey = salvageSelection.ItemKey, - ManufacturerKey = salvageSelection.ManufacturerKey, - Price = salvageSelection.Price, - ManufacturerDistance = salvageSelection.ManufacturerDistance, - Allegiance = salvageSelection.Allegiance - }) - .ToArray(); - - if (string.Equals(scenario, AetheriaDaemonTerminusScenarios.CargoCapacityRejectionProof, StringComparison.Ordinal)) - FillCargoBays(entities[1], salvage.ItemKey, catalog); - } - - private static void ApplyDeployment( - AetheriaEntitySnapshot entity, - AetheriaRuntimeEntityLoadoutCommit loadout) - { - entity.Name = string.IsNullOrWhiteSpace(loadout.Name) ? entity.Name : loadout.Name; - entity.Kind = string.IsNullOrWhiteSpace(loadout.Kind) ? entity.Kind : loadout.Kind; - entity.FactionKey = string.IsNullOrWhiteSpace(loadout.FactionKey) ? entity.FactionKey : loadout.FactionKey; - entity.HullItemKey = loadout.Hull?.ItemKey ?? entity.HullItemKey; - entity.Equipment = (loadout.Equipment ?? []).Select(ToEntitySlot).ToArray(); - entity.CargoBays = (loadout.CargoBays ?? []).Select(ToEntitySlot).ToArray(); - entity.DockingBays = (loadout.DockingBays ?? []).Select(ToEntitySlot).ToArray(); - entity.CargoContents = (loadout.CargoContents ?? []).Select(ToCargoBay).ToArray(); - entity.DockingBayContents = (loadout.DockingBayContents ?? []).Select(ToCargoBay).ToArray(); - entity.DockingBayAssignments = (loadout.DockingBayAssignments ?? []).ToArray(); - entity.WeaponGroups = (loadout.WeaponGroups ?? []) - .Select(group => new AetheriaWeaponGroupSnapshot - { - EquipmentIndices = (group ?? []).ToArray() - }) - .ToArray(); - } - - private static AetheriaEntityItemSlot ToEntitySlot(AetheriaRuntimeLoadoutItemSlotCommit slot) => new() - { - Position = new AetheriaGridCoord { X = slot.X, Y = slot.Y }, - Rotation = slot.Rotation ?? "None", - ItemKey = slot.Item?.ItemKey ?? "", - Quality = slot.Item?.Quality ?? 1, - Durability = slot.Item?.Durability ?? 1, - Quantity = slot.Item?.Quantity ?? 1, - Enabled = slot.Item?.Enabled ?? true, - OverrideShutdown = slot.Item?.OverrideShutdown ?? false, - Temperature = slot.Item?.Temperature ?? 0 - }; - - private static AetheriaCargoBayLoadout ToCargoBay(AetheriaRuntimeCargoBayLoadoutCommit bay) => new() - { - Items = (bay.Items ?? []).Select(slot => new AetheriaLoadoutItemSlot - { - Position = new AetheriaGridCoord { X = slot.X, Y = slot.Y }, - Rotation = slot.Rotation ?? "None", - Item = new AetheriaLoadoutItem - { - ItemKey = slot.Item?.ItemKey ?? "", - Quality = slot.Item?.Quality ?? 1, - Durability = slot.Item?.Durability ?? 1, - Quantity = slot.Item?.Quantity ?? 1, - Enabled = slot.Item?.Enabled ?? true, - OverrideShutdown = slot.Item?.OverrideShutdown ?? false, - Temperature = slot.Item?.Temperature ?? 0 - } - }).ToArray() - }; - - private static string StableToken(string value) => - new string((value ?? "").Select(character => char.IsLetterOrDigit(character) ? char.ToLowerInvariant(character) : '-').ToArray()).Trim('-'); - - private static void FillCargoBays( - AetheriaEntitySnapshot entity, - string itemKey, - AetheriaRuntimeCatalogSnapshot catalog) - { - var item = catalog.FindItem(itemKey) - ?? throw new InvalidOperationException($"The cargo-capacity proof item '{itemKey}' is absent from the typed catalog."); - if (item.Volume <= 0) - throw new InvalidOperationException($"The cargo-capacity proof item '{itemKey}' must have positive volume."); - - var cargoBays = (entity.CargoBays ?? Array.Empty()) - .Select(slot => catalog.FindItem(slot.ItemKey)) - .Where(candidate => candidate != null && candidate.InteriorOccupiedCells > 0) - .ToArray(); - if (cargoBays.Length == 0) - throw new InvalidOperationException("The cargo-capacity proof ship has no generated cargo bay."); - - entity.CargoContents = cargoBays.Select((bay, index) => - { - var quantity = (int)Math.Floor(bay!.InteriorOccupiedCells / item.Volume); - if (quantity <= 0) - throw new InvalidOperationException($"Cargo bay {index} cannot contain one '{itemKey}' proof item."); - return new AetheriaCargoBayLoadout - { - Items = - [ - new AetheriaLoadoutItemSlot - { - Item = new AetheriaLoadoutItem - { - ItemKey = itemKey, - Quantity = quantity, - Quality = 1, - Durability = 1, - Enabled = true - } - } - ] - }; - }).ToArray(); - } - - private static AetheriaEntitySnapshot Entity( - IReadOnlyDictionary loadouts, - IReadOnlyDictionary availabilityFactions, - string name, - string kind, - double x, - double z, - double vx, - double vy, - string faction, - double visibility, - string target, - string[] contactKeys, - int securityLevel = 0, - double securityRadius = 0, - string hullItemKey = "") - { - var availabilityFaction = availabilityFactions[faction]; - var loadout = loadouts[availabilityFaction].Build(kind, availabilityFaction, hullItemKey); - var entity = AetheriaDaemonGeneratedEntityFactory.Create( - name, - kind, - availabilityFaction, - loadout, - x, - z, - securityLevel: securityLevel, - securityRadius: securityRadius); - entity.Direction = Vec2(vx == 0 && vy == 0 ? 0 : vx, vx == 0 && vy == 0 ? 1 : vy); - entity.LookDirection = Vec2(vx == 0 && vy == 0 ? 0 : vx, vx == 0 && vy == 0 ? 1 : vy); - entity.Velocity = Vec2(vx, vy); - entity.Visibility = visibility; - entity.TargetEntityKey = target; - entity.Contacts = contactKeys.Select(key => new AetheriaEntityContactSnapshot - { - TargetEntityKey = key, - InfoGathered = 1 - }).ToArray(); - if (string.Equals(faction, "raider", StringComparison.OrdinalIgnoreCase)) - entity.StatGrids[0].Values[0] = 85; - return entity; - } - - private static AetheriaGasGiantVisualState GasGiantVisual(BodyPlan body) - { - if (body.Kind != "gas_giant" && body.Kind != "sun") - return new AetheriaGasGiantVisualState(); - - return new AetheriaGasGiantVisualState - { - FirstOffsetDomainRotationSpeed = body.Kind == "sun" ? 5 : 0, - FirstOffsetRotationSpeed = 5, - SecondOffsetRotationSpeed = 10, - SecondOffsetDomainRotationSpeed = -25, - AlbedoRotationSpeed = -3, - WaveRadiusMultiplier = 1, - WaveDepthMultiplier = 1, - Colors = - [ - Color(0.85, 0.32, 0.18, 0), - Color(0.18, 0.54, 0.92, 0.5), - Color(0.92, 0.78, 0.38, 1) - ] - }; - } - - private static AetheriaSunVisualState SunVisual(BodyPlan body) - { - return body.Kind == "sun" - ? new AetheriaSunVisualState - { - LightColor = Vec3(1, 0.86, 0.58), - FogTintColor = Vec3(0.32, 0.52, 1), - LightRadiusMultiplier = 1.8 - } - : new AetheriaSunVisualState(); - } - - private static AetheriaAsteroidSnapshot[] Asteroids(double orbitDistance) - { - return Enumerable.Range(0, 18) - .Select(index => - { - var phase = index * Math.Tau / 18.0; - return new AetheriaAsteroidSnapshot - { - Distance = orbitDistance + Math.Sin(phase * 3.0) * 42.0, - Phase = phase, - Size = 0.65 + (index % 5) * 0.24, - RotationSpeed = (index % 2 == 0 ? 1 : -1) * (0.04 + index * 0.006) - }; - }) - .ToArray(); - } - - private static AetheriaEntityStatGrid StatGrid(string name, double value) - { - return new AetheriaEntityStatGrid - { - Name = name, - Width = 1, - Height = 1, - Values = [value] - }; - } - - private static AetheriaColor Color(double x, double y, double z, double w) - { - return new AetheriaColor { X = x, Y = y, Z = z, W = w }; - } - - private static AetheriaVector2 Vec2(double x, double y) - { - return new AetheriaVector2 { X = x, Y = y }; - } - - private static AetheriaVector3 Vec3(double x, double y, double z) - { - return new AetheriaVector3 { X = x, Y = y, Z = z }; - } - - private sealed record BodyPlan( - string Key, - string ParentKey, - string Kind, - string Name, - double Distance, - double Phase, - double X, - double Z, - double Mass, - double BodyRadius, - double GravityRadius, - double GravityDepth, - double DepthExponent, - double WaveRadius, - double WaveDepth, - double WaveSpeed); - -} diff --git a/Aetheria.State.Daemon/AetheriaYmirPersistence.cs b/Aetheria.State.Daemon/AetheriaYmirPersistence.cs deleted file mode 100644 index a526e9813..000000000 --- a/Aetheria.State.Daemon/AetheriaYmirPersistence.cs +++ /dev/null @@ -1,11 +0,0 @@ -using Ymir.Core; - -namespace Aetheria.State.Daemon; - -public sealed record AetheriaYmirZonePersistenceCapture( - string RunId, - int ZoneIndex, - long FrameId, - int SimulationStepIndex, - YmirSessionPersistenceCapture World, - YmirSessionPersistenceCapture? Payload); diff --git a/Aetheria.State.Daemon/AetheriaYmirPersistenceCoordinator.cs b/Aetheria.State.Daemon/AetheriaYmirPersistenceCoordinator.cs deleted file mode 100644 index 728615916..000000000 --- a/Aetheria.State.Daemon/AetheriaYmirPersistenceCoordinator.cs +++ /dev/null @@ -1,398 +0,0 @@ -using System.Globalization; -using System.Diagnostics; -using System.Security.Cryptography; -using System.Text; -using Aetheria.State.Documents; -using GameCult.Aetheria.State.Verse; -using GameCult.Caching; -using GameCult.Caching.MessagePack; -using GameCult.Mesh; -using Ymir.Core; - -namespace Aetheria.State.Daemon; - -/// -/// Owns daemon-private Ymir restart material. The public Aetheria frame remains the -/// sole durable commit marker; private chunks and resumes are never advertised. -/// -public sealed class AetheriaYmirPersistenceCoordinator : IDisposable -{ - private const string WorldChannel = "world"; - private const string PayloadChannel = "payloads"; - private readonly CultCache _cache; - private readonly AetheriaYmirWorldPhysics _physics; - private readonly Dictionary _cursors = new(); - private bool _activated; - - private AetheriaYmirPersistenceCoordinator( - CultCache cache, - AetheriaYmirWorldPhysics physics, - bool activated) - { - _cache = cache; - _physics = physics; - _activated = activated; - } - - public static string PrivateStatePath(string publicStatePath) => publicStatePath + ".ymir.cc"; - - public static async Task OpenAsync( - AetheriaStateNode node, - AetheriaYmirWorldPhysics physics, - AetheriaRuntimeDaemonFrameDocument? frame) - { - var trace = string.Equals( - Environment.GetEnvironmentVariable("AETHERIA_TRACE_STARTUP_PHASES"), - "1", - StringComparison.Ordinal); - var phase = Stopwatch.StartNew(); - void Trace(string name) - { - if (trace) - Console.WriteLine($"Aetheria Ymir restore phase {name} took {phase.Elapsed.TotalMilliseconds:0.###}ms."); - phase.Restart(); - } - ArgumentNullException.ThrowIfNull(node); - ArgumentNullException.ThrowIfNull(physics); - var registry = CultMesh.CreateCultCacheDocumentRegistry( - typeof(AetheriaYmirPersistenceMarkerDocument), - typeof(AetheriaYmirJournalChunkDocument), - typeof(AetheriaYmirResumeDocument)); - var ownedSchemaIds = registry.AllDescriptors - .Select(descriptor => descriptor.SchemaId) - .ToHashSet(StringComparer.Ordinal); - var currentResumeKeys = frame?.Run == null || string.IsNullOrWhiteSpace(frame.Run.RunId) - ? new HashSet(StringComparer.Ordinal) - : (frame.Run.Zones ?? Array.Empty()) - .Where(zone => zone != null) - .Select(zone => ResumeRecordKey(frame.Run.RunId, zone.ZoneIndex, frame.FrameId)) - .ToHashSet(StringComparer.Ordinal); - var cache = await CultCacheMessagePack.OpenAsync( - PrivateStatePath(node.StatePath), - new CultCacheOpenOptions - { - Registry = registry, - PullOnOpen = true, - UseDirectoryStore = true, - DirectoryStoreHydrationFilter = record => - ownedSchemaIds.Contains(record.SchemaId) && - (string.Equals(record.Key, AetheriaYmirPersistenceMarkerDocument.RecordKey, StringComparison.Ordinal) || - currentResumeKeys.Contains(record.Key)), - FlushOnDispose = true, - StoreFlushOnDispose = true - }).ConfigureAwait(false); - Trace("open-private-cache"); - try - { - var activated = cache.Get( - new CultRecordKey(AetheriaYmirPersistenceMarkerDocument.RecordKey)) != null; - var coordinator = new AetheriaYmirPersistenceCoordinator(cache, physics, activated); - if (frame?.Run == null || string.IsNullOrWhiteSpace(frame.Run.RunId)) - return coordinator; - - var zones = (frame.Run.Zones ?? Array.Empty()) - .Where(zone => zone != null) - .OrderBy(zone => zone.ZoneIndex) - .ToArray(); - var resumes = zones - .Select(zone => cache.Get( - new CultRecordKey(ResumeRecordKey(frame.Run.RunId, zone.ZoneIndex, frame.FrameId)))) - .ToArray(); - Trace("resolve-resumes"); - if (resumes.All(value => value == null) && !activated) - return coordinator; - if (resumes.Any(value => value == null)) - throw new InvalidOperationException( - $"Aetheria frame {frame.FrameId} has incomplete daemon-private Ymir resume state."); - - var journalRanges = zones.Zip(resumes, (zone, resume) => - JournalRanges(frame.Run.RunId, zone.ZoneIndex, resume!)) - .SelectMany(value => value) - .ToArray(); - await cache.PullBackingStoreRecordsAsync(metadata => - journalRanges.Any(range => range.Contains(metadata.Key))).ConfigureAwait(false); - Trace("hydrate-journals"); - - foreach (var pair in zones.Zip(resumes, (zone, resume) => (zone, resume: resume!))) - coordinator.RestoreZone(frame.Run.RunId, frame.FrameId, pair.zone.ZoneIndex, pair.resume); - Trace("restore-zones"); - return coordinator; - } - catch - { - cache.Dispose(); - throw; - } - } - - public IReadOnlyList Capture( - AetheriaRuntimeDaemonFrameDocument frame) - { - ArgumentNullException.ThrowIfNull(frame); - var run = frame.Run ?? throw new InvalidOperationException("Ymir persistence requires an authoritative run."); - if (string.IsNullOrWhiteSpace(run.RunId)) - throw new InvalidOperationException("Ymir persistence requires a stable run id."); - var zones = (run.Zones ?? Array.Empty()) - .Where(zone => zone != null) - .OrderBy(zone => zone.ZoneIndex) - .ToArray(); - foreach (var zone in zones) - _physics.SynchronizePersistenceFrame( - run.RunId, - frame.FrameId, - zone, - zone.Entities ?? Array.Empty()); - return zones - .Select(zone => - { - var world = CursorFor(run.RunId, zone.ZoneIndex, WorldChannel); - var payload = CursorFor(run.RunId, zone.ZoneIndex, PayloadChannel); - return _physics.CapturePersistence( - run.RunId, - zone.ZoneIndex, - frame.FrameId, - world.Generation, - world.EntryCount, - payload.Generation, - payload.EntryCount); - }) - .ToArray(); - } - - public async Task PersistPrivateAsync(IReadOnlyList captures) - { - ArgumentNullException.ThrowIfNull(captures); - foreach (var capture in captures) - { - await PutChunkAsync(capture, WorldChannel, capture.World.JournalChunk).ConfigureAwait(false); - if (capture.Payload != null) - await PutChunkAsync(capture, PayloadChannel, capture.Payload.JournalChunk).ConfigureAwait(false); - } - await _cache.FlushAsync(soft: false).ConfigureAwait(false); - - foreach (var capture in captures) - { - var world = capture.World.ResumeDescriptor; - var payload = capture.Payload?.ResumeDescriptor; - var resume = new AetheriaYmirResumeDocument - { - RunId = capture.RunId, - ZoneIndex = capture.ZoneIndex, - FrameId = capture.FrameId, - SimulationStepIndex = capture.SimulationStepIndex, - WorldSessionGeneration = world.SessionGeneration, - WorldJournalEntryCount = world.JournalEntryCount, - WorldDescriptorPayload = YmirSessionCheckpointCodec.Encode(world), - PayloadSessionGeneration = payload?.SessionGeneration ?? "", - PayloadJournalEntryCount = payload?.JournalEntryCount ?? 0, - PayloadDescriptorPayload = payload == null ? [] : YmirSessionCheckpointCodec.Encode(payload) - }; - await PutImmutableAsync( - new CultRecordKey(ResumeRecordKey(capture.RunId, capture.ZoneIndex, capture.FrameId)), - resume, - Equivalent).ConfigureAwait(false); - } - await _cache.FlushAsync(soft: false).ConfigureAwait(false); - - foreach (var capture in captures) - { - Remember(capture.RunId, capture.ZoneIndex, WorldChannel, capture.World.ResumeDescriptor); - if (capture.Payload != null) - Remember(capture.RunId, capture.ZoneIndex, PayloadChannel, capture.Payload.ResumeDescriptor); - } - } - - /// - /// Activates fail-closed restart only after the matching public frame has been hard-flushed. - /// - public async Task ActivateAsync() - { - if (_activated) - return; - await _cache.UpsertAsync( - new AetheriaYmirPersistenceMarkerDocument - { - ActivatedAtUtc = DateTimeOffset.UtcNow.ToString("O", CultureInfo.InvariantCulture) - }, - new CultRecordHandle( - new CultRecordKey(AetheriaYmirPersistenceMarkerDocument.RecordKey))).ConfigureAwait(false); - await _cache.FlushAsync(soft: false).ConfigureAwait(false); - _activated = true; - } - - public static string ResumeRecordKey(string runId, int zoneIndex, long frameId) => - $"private:aetheria.ymir.resume.v1:{RunKey(runId)}:{zoneIndex}:{frameId:D20}"; - - public void Dispose() => _cache.Dispose(); - - private async Task PutChunkAsync( - AetheriaYmirZonePersistenceCapture capture, - string channel, - YmirSessionJournalChunk? chunk) - { - if (chunk == null) - return; - var document = new AetheriaYmirJournalChunkDocument - { - RunId = capture.RunId, - ZoneIndex = capture.ZoneIndex, - Channel = channel, - SessionGeneration = chunk.SessionGeneration, - FirstEntryIndex = chunk.FirstEntryIndex, - EntryCount = chunk.Entries.Count, - Payload = YmirSessionCheckpointCodec.Encode(chunk) - }; - await PutImmutableAsync( - new CultRecordKey(JournalRecordKey( - capture.RunId, - capture.ZoneIndex, - channel, - chunk.SessionGeneration, - chunk.FirstEntryIndex)), - document, - Equivalent).ConfigureAwait(false); - } - - private async Task PutImmutableAsync( - CultRecordKey key, - T document, - Func equivalent) - where T : class - { - var existing = _cache.Get(key); - if (existing != null) - { - if (!equivalent(existing, document)) - throw new InvalidOperationException($"Immutable Ymir persistence record {key.Value} conflicts with durable state."); - return; - } - await _cache.UpsertAsync(document, new CultRecordHandle(key)).ConfigureAwait(false); - } - - private void RestoreZone(string runId, long frameId, int zoneIndex, AetheriaYmirResumeDocument resume) - { - if (!string.Equals(resume.RunId, runId, StringComparison.Ordinal) || - resume.ZoneIndex != zoneIndex || resume.FrameId != frameId || - resume.WorldDescriptorPayload.Length == 0) - throw new InvalidOperationException("Aetheria private Ymir resume identity does not match the durable frame."); - var world = YmirSessionCheckpointCodec.DecodeResumeDescriptor(resume.WorldDescriptorPayload); - var payload = resume.PayloadDescriptorPayload.Length == 0 - ? null - : YmirSessionCheckpointCodec.DecodeResumeDescriptor(resume.PayloadDescriptorPayload); - if (!string.Equals(world.SessionGeneration, resume.WorldSessionGeneration, StringComparison.Ordinal) || - world.JournalEntryCount != resume.WorldJournalEntryCount || - !string.Equals(payload?.SessionGeneration ?? "", resume.PayloadSessionGeneration, StringComparison.Ordinal) || - (payload?.JournalEntryCount ?? 0) != resume.PayloadJournalEntryCount) - throw new InvalidOperationException("Aetheria private Ymir resume metadata disagrees with its checksummed payload."); - - var worldChunks = Chunks(runId, zoneIndex, WorldChannel, world.SessionGeneration, world.JournalEntryCount); - var payloadChunks = payload == null - ? Array.Empty() - : Chunks(runId, zoneIndex, PayloadChannel, payload.SessionGeneration, payload.JournalEntryCount); - _physics.RestorePersistence( - runId, zoneIndex, frameId, resume.SimulationStepIndex, - world, worldChunks, payload, payloadChunks); - Remember(runId, zoneIndex, WorldChannel, world); - if (payload != null) - Remember(runId, zoneIndex, PayloadChannel, payload); - } - - private YmirSessionJournalChunk[] Chunks( - string runId, - int zoneIndex, - string channel, - string generation, - long entryCount) => - _cache.GetAll() - .Where(value => value != null && - string.Equals(value.RunId, runId, StringComparison.Ordinal) && - value.ZoneIndex == zoneIndex && - string.Equals(value.Channel, channel, StringComparison.Ordinal) && - string.Equals(value.SessionGeneration, generation, StringComparison.Ordinal) && - value.FirstEntryIndex < entryCount) - .OrderBy(value => value.FirstEntryIndex) - .Select(value => - { - var chunk = YmirSessionCheckpointCodec.DecodeJournalChunk(value.Payload); - if (chunk.FirstEntryIndex != value.FirstEntryIndex || chunk.Entries.Count != value.EntryCount) - throw new InvalidOperationException("Aetheria Ymir journal metadata disagrees with its checksummed payload."); - return chunk; - }) - .ToArray(); - - private Cursor CursorFor(string runId, int zoneIndex, string channel) => - _cursors.TryGetValue(new CursorKey(runId, zoneIndex, channel), out var cursor) - ? cursor - : new Cursor("", 0); - - private void Remember( - string runId, - int zoneIndex, - string channel, - YmirSessionResumeDescriptor descriptor) => - _cursors[new CursorKey(runId, zoneIndex, channel)] = - new Cursor(descriptor.SessionGeneration, descriptor.JournalEntryCount); - - private static bool Equivalent(AetheriaYmirJournalChunkDocument left, AetheriaYmirJournalChunkDocument right) => - left.RunId == right.RunId && left.ZoneIndex == right.ZoneIndex && left.Channel == right.Channel && - left.SessionGeneration == right.SessionGeneration && left.FirstEntryIndex == right.FirstEntryIndex && - left.EntryCount == right.EntryCount && left.Payload.AsSpan().SequenceEqual(right.Payload); - - private static bool Equivalent(AetheriaYmirResumeDocument left, AetheriaYmirResumeDocument right) => - left.RunId == right.RunId && left.ZoneIndex == right.ZoneIndex && left.FrameId == right.FrameId && - left.SimulationStepIndex == right.SimulationStepIndex && - left.WorldSessionGeneration == right.WorldSessionGeneration && - left.WorldJournalEntryCount == right.WorldJournalEntryCount && - left.WorldDescriptorPayload.AsSpan().SequenceEqual(right.WorldDescriptorPayload) && - left.PayloadSessionGeneration == right.PayloadSessionGeneration && - left.PayloadJournalEntryCount == right.PayloadJournalEntryCount && - left.PayloadDescriptorPayload.AsSpan().SequenceEqual(right.PayloadDescriptorPayload); - - private static string JournalRecordKey( - string runId, - int zoneIndex, - string channel, - string generation, - long firstEntryIndex) => - $"private:aetheria.ymir.journal.v1:{RunKey(runId)}:{zoneIndex}:{channel}:{generation}:{firstEntryIndex:D20}"; - - private static JournalRange[] JournalRanges( - string runId, - int zoneIndex, - AetheriaYmirResumeDocument resume) - { - var world = YmirSessionCheckpointCodec.DecodeResumeDescriptor(resume.WorldDescriptorPayload); - var ranges = new List - { - new(JournalRecordPrefix(runId, zoneIndex, WorldChannel, world.SessionGeneration), world.JournalEntryCount) - }; - if (resume.PayloadDescriptorPayload.Length > 0) - { - var payload = YmirSessionCheckpointCodec.DecodeResumeDescriptor(resume.PayloadDescriptorPayload); - ranges.Add(new JournalRange( - JournalRecordPrefix(runId, zoneIndex, PayloadChannel, payload.SessionGeneration), - payload.JournalEntryCount)); - } - return ranges.ToArray(); - } - - private static string JournalRecordPrefix( - string runId, - int zoneIndex, - string channel, - string generation) => - $"private:aetheria.ymir.journal.v1:{RunKey(runId)}:{zoneIndex}:{channel}:{generation}:"; - - private static string RunKey(string runId) => - Convert.ToHexString(SHA256.HashData(Encoding.UTF8.GetBytes(runId))).ToLowerInvariant(); - - private readonly record struct CursorKey(string RunId, int ZoneIndex, string Channel); - private readonly record struct Cursor(string Generation, long EntryCount); - private readonly record struct JournalRange(string Prefix, long EntryCount) - { - public bool Contains(string key) => - key.StartsWith(Prefix, StringComparison.Ordinal) && - long.TryParse(key.AsSpan(Prefix.Length), NumberStyles.None, CultureInfo.InvariantCulture, out var firstEntryIndex) && - firstEntryIndex < EntryCount; - } -} diff --git a/Aetheria.State.Daemon/AetheriaYmirPersistenceDocuments.cs b/Aetheria.State.Daemon/AetheriaYmirPersistenceDocuments.cs deleted file mode 100644 index cfe59524b..000000000 --- a/Aetheria.State.Daemon/AetheriaYmirPersistenceDocuments.cs +++ /dev/null @@ -1,43 +0,0 @@ -using GameCult.Caching; -using MessagePack; - -namespace Aetheria.State.Daemon; - -[CultDocument("gamecult.aetheria.ymir_persistence_marker", "gamecult.aetheria.ymir_persistence_marker.v1")] -[MessagePackObject] -public sealed class AetheriaYmirPersistenceMarkerDocument -{ - public const string RecordKey = "private:aetheria.ymir.persistence.v1"; - - [Key(0)] public string FormatId { get; set; } = "gamecult.aetheria.ymir.persistence.v1"; - [Key(1)] public string ActivatedAtUtc { get; set; } = ""; -} - -[CultDocument("gamecult.aetheria.ymir_journal_chunk", "gamecult.aetheria.ymir_journal_chunk.v1")] -[MessagePackObject] -public sealed class AetheriaYmirJournalChunkDocument -{ - [Key(0)] public string RunId { get; set; } = ""; - [Key(1)] public int ZoneIndex { get; set; } - [Key(2)] public string Channel { get; set; } = ""; - [Key(3)] public string SessionGeneration { get; set; } = ""; - [Key(4)] public long FirstEntryIndex { get; set; } - [Key(5)] public int EntryCount { get; set; } - [Key(6)] public byte[] Payload { get; set; } = []; -} - -[CultDocument("gamecult.aetheria.ymir_resume", "gamecult.aetheria.ymir_resume.v1")] -[MessagePackObject] -public sealed class AetheriaYmirResumeDocument -{ - [Key(0)] public string RunId { get; set; } = ""; - [Key(1)] public int ZoneIndex { get; set; } - [Key(2)] public long FrameId { get; set; } - [Key(3)] public int SimulationStepIndex { get; set; } - [Key(4)] public string WorldSessionGeneration { get; set; } = ""; - [Key(5)] public long WorldJournalEntryCount { get; set; } - [Key(6)] public byte[] WorldDescriptorPayload { get; set; } = []; - [Key(7)] public string PayloadSessionGeneration { get; set; } = ""; - [Key(8)] public long PayloadJournalEntryCount { get; set; } - [Key(9)] public byte[] PayloadDescriptorPayload { get; set; } = []; -} diff --git a/Aetheria.State.Daemon/AetheriaYmirWorldPhysics.cs b/Aetheria.State.Daemon/AetheriaYmirWorldPhysics.cs deleted file mode 100644 index d657757d1..000000000 --- a/Aetheria.State.Daemon/AetheriaYmirWorldPhysics.cs +++ /dev/null @@ -1,630 +0,0 @@ -using GameCult.Aetheria.State.Verse; -using Ymir.Core; - -namespace Aetheria.State.Daemon; - -public sealed class AetheriaYmirWorldPhysics : IAetheriaRuntimeWorldPhysics, IDisposable -{ - private const long PersistenceRolloverJournalEntries = 64; - private const string EntityPrefix = "aetheria.daemon.entity."; - private const string PickupPrefix = "aetheria.daemon.pickup."; - private const string PhysicalPayloadPrefix = "aetheria.physical-payload."; - private const ulong WorldEntityCollisionCategory = 1UL; - private const ulong ShipCollisionCategory = 2UL; - private const ulong PickupCollisionCategory = 4UL; - private readonly Dictionary _sessions = new(); - - public string ImplementationId => "ymir.box3d.retained-session.v1"; - - public void SynchronizePersistenceFrame( - string runId, - long frameId, - AetheriaRuntimeZoneSnapshotCommit zone, - IReadOnlyList entities) - { - ArgumentNullException.ThrowIfNull(zone); - if (string.IsNullOrWhiteSpace(runId)) - throw new ArgumentException("A retained Ymir world requires a run id.", nameof(runId)); - - var key = new WorldKey(runId, zone.ZoneIndex); - if (_sessions.TryGetValue(key, out var existing) && existing.LastFrameId == frameId) - return; - if (existing != null && frameId < existing.LastFrameId) - throw new InvalidOperationException( - $"Ymir session '{existing.WorldSession.Info.SessionId}' cannot synchronize backward from " + - $"frame {existing.LastFrameId} to {frameId}."); - - var desiredBodies = BuildBodies(ActiveEntities(entities), ActivePickups(zone)); - var state = existing ?? CreateSession(key, zone, desiredBodies); - if (existing == null) - _sessions.Add(key, state); - else - { - var commandOrdinal = 0; - Reconcile( - state.WorldSession, - desiredBodies, - frameId, - state.LastSimulationStepIndex, - ref commandOrdinal); - } - - state.LastFrameId = frameId; - state.LastResult = null; - if (state.PayloadSession != null) - { - state.LastPayloadFrameId = frameId; - state.LastPayloadSimulationStepIndex = state.LastSimulationStepIndex; - state.LastPayloadResult = null; - } - } - - public AetheriaYmirZonePersistenceCapture CapturePersistence( - string runId, - int zoneIndex, - long frameId, - string persistedWorldSessionGeneration, - long persistedWorldJournalEntryCount, - string persistedPayloadSessionGeneration, - long persistedPayloadJournalEntryCount) - { - if (!_sessions.TryGetValue(new WorldKey(runId, zoneIndex), out var state) || - state.LastFrameId != frameId) - throw new InvalidOperationException( - $"Cannot persist Ymir world for run '{runId}' zone {zoneIndex} at frame {frameId}."); - if (state.PayloadSession != null && - (state.LastPayloadFrameId != frameId || state.LastPayloadSimulationStepIndex != state.LastSimulationStepIndex)) - throw new InvalidOperationException( - $"Cannot persist Ymir payload world for run '{runId}' zone {zoneIndex} at frame {frameId}."); - - TryRollPersistenceGeneration(state, payload: false); - if (state.PayloadSession != null) - TryRollPersistenceGeneration(state, payload: true); - - return new AetheriaYmirZonePersistenceCapture( - runId, - zoneIndex, - frameId, - state.LastSimulationStepIndex, - state.WorldSession.CapturePersistence( - string.Equals(persistedWorldSessionGeneration, state.WorldSession.Info.SessionGeneration, StringComparison.Ordinal) - ? persistedWorldJournalEntryCount - : 0), - state.PayloadSession?.CapturePersistence( - string.Equals(persistedPayloadSessionGeneration, state.PayloadSession.Info.SessionGeneration, StringComparison.Ordinal) - ? persistedPayloadJournalEntryCount - : 0)); - } - - public void RestorePersistence( - string runId, - int zoneIndex, - long frameId, - int simulationStepIndex, - YmirSessionResumeDescriptor worldDescriptor, - IReadOnlyList worldChunks, - YmirSessionResumeDescriptor? payloadDescriptor, - IReadOnlyList payloadChunks) - { - var key = new WorldKey(runId, zoneIndex); - if (_sessions.ContainsKey(key)) - throw new InvalidOperationException($"Ymir world for run '{runId}' zone {zoneIndex} is already live."); - var sessionId = $"aetheria.run.{runId}.zone.{zoneIndex}"; - if (!string.Equals(worldDescriptor.SessionId, sessionId + ".world", StringComparison.Ordinal) || - (payloadDescriptor != null && - !string.Equals(payloadDescriptor.SessionId, sessionId + ".payloads", StringComparison.Ordinal))) - throw new InvalidOperationException("Aetheria Ymir resume belongs to another run or zone."); - if (payloadDescriptor == null && payloadChunks.Count > 0) - throw new InvalidOperationException("Aetheria Ymir resume has payload journal chunks without a descriptor."); - - var world = YmirSession.Restore(worldDescriptor, worldChunks); - YmirSession? payload = null; - try - { - payload = payloadDescriptor == null ? null : YmirSession.Restore(payloadDescriptor, payloadChunks); - _sessions.Add(key, new SessionState(world) - { - PayloadSession = payload, - LastFrameId = frameId, - LastSimulationStepIndex = simulationStepIndex, - LastPayloadFrameId = payload == null ? -1 : frameId, - LastPayloadSimulationStepIndex = payload == null ? -1 : simulationStepIndex - }); - } - catch - { - payload?.Dispose(); - world.Dispose(); - throw; - } - } - - public void RetainWorlds(string runId, IReadOnlyList zoneIndices) - { - var retainedZones = (zoneIndices ?? Array.Empty()).ToHashSet(); - foreach (var key in _sessions.Keys - .Where(key => !string.Equals(key.RunId, runId, StringComparison.Ordinal) || - !retainedZones.Contains(key.ZoneIndex)) - .ToArray()) - { - _sessions[key].WorldSession.Dispose(); - _sessions[key].PayloadSession?.Dispose(); - _sessions.Remove(key); - } - } - - public AetheriaRuntimeWorldStep Step( - string runId, - long frameId, - int simulationStepIndex, - AetheriaRuntimeZoneSnapshotCommit zone, - IReadOnlyList entities, - double deltaSeconds) - { - ArgumentNullException.ThrowIfNull(zone); - if (string.IsNullOrWhiteSpace(runId)) - throw new ArgumentException("A retained Ymir world requires a run id.", nameof(runId)); - if (deltaSeconds <= 0) - throw new ArgumentOutOfRangeException(nameof(deltaSeconds)); - - var key = new WorldKey(runId, zone.ZoneIndex); - if (_sessions.TryGetValue(key, out var existing) && - existing.LastFrameId == frameId && existing.LastSimulationStepIndex == simulationStepIndex && - existing.LastResult != null) - return existing.LastResult; - if (existing != null && - (frameId < existing.LastFrameId || - (frameId == existing.LastFrameId && simulationStepIndex < existing.LastSimulationStepIndex))) - throw new InvalidOperationException( - $"Ymir session '{existing.WorldSession.Info.SessionId}' cannot step backward from " + - $"frame/step {existing.LastFrameId}/{existing.LastSimulationStepIndex} to {frameId}/{simulationStepIndex}."); - - var active = ActiveEntities(entities); - var pickups = ActivePickups(zone); - var desiredBodies = BuildBodies(active, pickups); - var state = existing ?? CreateSession(key, zone, desiredBodies); - if (existing == null) - _sessions.Add(key, state); - - var commandOrdinal = 0; - Reconcile(state.WorldSession, desiredBodies, frameId, simulationStepIndex, ref commandOrdinal); - ApplyTractorForces(state.WorldSession, active, pickups, frameId, simulationStepIndex, ref commandOrdinal); - - var step = state.WorldSession.Step(new YmirStepSessionCommand( - Header(state.WorldSession, frameId, simulationStepIndex, "step", "world", ref commandOrdinal), - (float)deltaSeconds, - GravityFields(zone))); - var result = Lower(step, active); - state.LastFrameId = frameId; - state.LastSimulationStepIndex = simulationStepIndex; - state.LastResult = result; - return result; - } - - public AetheriaRuntimePhysicalPayloadStep StepPhysicalPayloads( - string runId, - long frameId, - int simulationStepIndex, - AetheriaRuntimeZoneSnapshotCommit zone, - IReadOnlyList entities, - double deltaSeconds) - { - ArgumentNullException.ThrowIfNull(zone); - if (!_sessions.TryGetValue(new WorldKey(runId, zone.ZoneIndex), out var state) || - state.LastFrameId != frameId || state.LastSimulationStepIndex != simulationStepIndex) - throw new InvalidOperationException( - $"Payload phase requires the retained world phase for run '{runId}' zone {zone.ZoneIndex} " + - $"at frame/step {frameId}/{simulationStepIndex}."); - if (state.LastPayloadFrameId == frameId && state.LastPayloadSimulationStepIndex == simulationStepIndex && - state.LastPayloadResult != null) - return state.LastPayloadResult; - if (deltaSeconds <= 0) - throw new ArgumentOutOfRangeException(nameof(deltaSeconds)); - - var payloadSession = state.PayloadSession ??= YmirSession.Create(new YmirSessionCreateRequest( - $"aetheria.run.{runId}.zone.{zone.ZoneIndex}.payloads", - [], - (float)zone.SimulationTimeSeconds)); - var payloads = (zone.PhysicalPayloads ?? Array.Empty()) - .Where(payload => payload != null && payload.Active) - .OrderBy(payload => payload.PayloadId, StringComparer.Ordinal) - .ToArray(); - var desiredBodies = payloads.Select(PayloadBody).ToArray(); - var commandOrdinal = 0; - Reconcile(payloadSession, desiredBodies, frameId, simulationStepIndex, ref commandOrdinal); - var starts = payloadSession.Snapshot().Bodies.ToDictionary(body => body.Id, StringComparer.Ordinal); - var stepped = payloadSession.Step(new YmirStepSessionCommand( - Header(payloadSession, frameId, simulationStepIndex, "step", "payloads", ref commandOrdinal), - (float)deltaSeconds, - [])); - var moved = stepped.World.Bodies.ToDictionary(body => body.Id, StringComparer.Ordinal); - var activeEntities = ActiveEntities(entities); - var entitiesByBodyId = activeEntities.ToDictionary(BodyId, StringComparer.Ordinal); - var survivors = new List(); - var hits = new List(); - - foreach (var payload in payloads) - { - var payloadBodyId = PhysicalPayloadPrefix + payload.PayloadId; - var start = starts[payloadBodyId]; - var body = moved[payloadBodyId]; - payload.PositionX = body.Position.X; - payload.PositionZ = body.Position.Y; - payload.VelocityX = body.Velocity.X; - payload.VelocityY = body.Velocity.Y; - - var candidates = activeEntities - .Where(entity => entity.EntityIndex != payload.SourceEntityIndex) - .Select(BodyId) - .OrderBy(id => id, StringComparer.Ordinal) - .ToArray(); - var dx = body.Position.X - start.Position.X; - var dz = body.Position.Y - start.Position.Y; - var distance = MathF.Sqrt(dx * dx + dz * dz); - var queryId = $"aetheria:frame:{frameId}:step:{simulationStepIndex}:payload:{payload.PayloadId}"; - CircleCastHit? castHit = null; - CircleOverlapHit? overlapHit = null; - if (distance > 1.0e-6f) - { - var cast = state.WorldSession.CastCircle(new YmirSessionCircleCastQuery( - queryId + ":cast", - state.WorldSession.Info.Revision, - start.Position, - new Vec2(dx, dz), - distance, - (float)payload.Radius, - candidates)); - RequireQuery(cast.Error, cast.QueryId); - castHit = cast.Hits.FirstOrDefault(); - queryId = cast.QueryId; - } - else - { - var overlap = state.WorldSession.OverlapCircle(new YmirSessionCircleOverlapQuery( - queryId + ":overlap", - state.WorldSession.Info.Revision, - body.Position, - (float)payload.Radius, - candidates)); - RequireQuery(overlap.Error, overlap.QueryId); - overlapHit = overlap.Hits.FirstOrDefault(); - queryId = overlap.QueryId; - } - - var targetBodyId = castHit?.BodyId ?? overlapHit?.BodyId; - if (targetBodyId == null || !entitiesByBodyId.TryGetValue(targetBodyId, out var target)) - { - survivors.Add(payload); - continue; - } - - var point = castHit?.Point ?? overlapHit!.Point; - var normal = castHit?.Normal ?? overlapHit!.Normal; - hits.Add(new AetheriaRuntimePhysicalPayloadHit - { - Payload = payload, - QueryId = queryId, - PhysicalPayloadBodyId = payloadBodyId, - TargetEntityIndex = target.EntityIndex, - TargetBodyId = targetBodyId, - PointX = point.X, - PointZ = point.Y, - NormalX = normal.X, - NormalZ = normal.Y - }); - - if (string.Equals(payload.PayloadKind, "mine", StringComparison.Ordinal)) - { - payload.Stationary = true; - payload.VelocityX = 0; - payload.VelocityY = 0; - if (!body.IsStatic) - RequireAccepted(payloadSession.SetVelocity(new YmirSetBodyVelocityCommand( - Header(payloadSession, frameId, simulationStepIndex, "stop", payload.PayloadId, ref commandOrdinal), - payloadBodyId, - Vec2.Zero, - 0))); - survivors.Add(payload); - } - else - { - RequireAccepted(payloadSession.Remove(new YmirRemoveBodyCommand( - Header(payloadSession, frameId, simulationStepIndex, "remove-hit", payload.PayloadId, ref commandOrdinal), - payloadBodyId))); - } - } - - var result = new AetheriaRuntimePhysicalPayloadStep(survivors, hits); - state.LastPayloadFrameId = frameId; - state.LastPayloadSimulationStepIndex = simulationStepIndex; - state.LastPayloadResult = result; - return result; - } - - public void Dispose() - { - foreach (var state in _sessions.Values) - { - state.WorldSession.Dispose(); - state.PayloadSession?.Dispose(); - } - _sessions.Clear(); - } - - private static SessionState CreateSession( - WorldKey key, - AetheriaRuntimeZoneSnapshotCommit zone, - IReadOnlyList bodies) - { - var sessionId = $"aetheria.run.{key.RunId}.zone.{key.ZoneIndex}"; - return new SessionState(YmirSession.Create(new YmirSessionCreateRequest( - sessionId + ".world", - bodies, - (float)zone.SimulationTimeSeconds))); - } - - private static void Reconcile( - YmirSession session, - IReadOnlyList desiredBodies, - long frameId, - int simulationStepIndex, - ref int commandOrdinal) - { - var desired = desiredBodies.ToDictionary(body => body.Id, StringComparer.Ordinal); - var current = session.Snapshot().Bodies.ToDictionary(body => body.Id, StringComparer.Ordinal); - - foreach (var body in current.Values.Where(body => !desired.ContainsKey(body.Id)).OrderBy(body => body.Id, StringComparer.Ordinal)) - RequireAccepted(session.Remove(new YmirRemoveBodyCommand( - Header(session, frameId, simulationStepIndex, "remove", body.Id, ref commandOrdinal), body.Id))); - - foreach (var body in desired.Values.OrderBy(body => body.Id, StringComparer.Ordinal)) - { - if (!current.TryGetValue(body.Id, out var present)) - { - RequireAccepted(session.Spawn(new YmirSpawnBodyCommand( - Header(session, frameId, simulationStepIndex, "spawn", body.Id, ref commandOrdinal), body))); - continue; - } - - if (present.IsBullet != body.IsBullet || - present.ParticipatesInFields != body.ParticipatesInFields || - present.CollisionCategoryBits != body.CollisionCategoryBits || - present.CollisionMaskBits != body.CollisionMaskBits || - present.CollisionGroupIndex != body.CollisionGroupIndex) - { - RequireAccepted(session.Remove(new YmirRemoveBodyCommand( - Header(session, frameId, simulationStepIndex, "replace-profile", body.Id, ref commandOrdinal), body.Id))); - RequireAccepted(session.Spawn(new YmirSpawnBodyCommand( - Header(session, frameId, simulationStepIndex, "respawn-profile", body.Id, ref commandOrdinal), body))); - continue; - } - - if (present.Radius != body.Radius || present.Mass != body.Mass || - present.IsStatic != body.IsStatic || present.IsKinematic != body.IsKinematic || - present.Restitution != body.Restitution) - RequireAccepted(session.Configure(new YmirConfigureBodyCommand( - Header(session, frameId, simulationStepIndex, "configure", body.Id, ref commandOrdinal), - body.Id, body.Radius, body.Mass, body.IsStatic, body.Restitution, body.IsKinematic))); - - if (present.Position != body.Position || present.Direction != body.Direction) - RequireAccepted(session.Teleport(new YmirTeleportBodyCommand( - Header(session, frameId, simulationStepIndex, "pose", body.Id, ref commandOrdinal), - body.Id, body.Position, body.Direction ?? new Vec2(0, 1)))); - - if (!body.IsStatic && (present.Velocity != body.Velocity || present.AngularVelocity != body.AngularVelocity)) - RequireAccepted(session.SetVelocity(new YmirSetBodyVelocityCommand( - Header(session, frameId, simulationStepIndex, "velocity", body.Id, ref commandOrdinal), - body.Id, body.Velocity, body.AngularVelocity))); - } - } - - private static void ApplyTractorForces( - YmirSession session, - IReadOnlyList active, - IReadOnlyList pickups, - long frameId, - int simulationStepIndex, - ref int commandOrdinal) - { - foreach (var actor in active.Where(entity => - entity.TractorPower > AetheriaRuntimeTractorMechanics.ActivationThreshold)) - { - var forward = Normalize(actor.LookDirectionX, actor.LookDirectionY, 0, 1); - foreach (var pickup in pickups) - { - var dx = pickup.PositionX - actor.PositionX; - var dz = pickup.PositionZ - actor.PositionZ; - var along = dx * forward.X + dz * forward.Y; - var lateral = Math.Abs(dx * forward.Y - dz * forward.X); - var distance = Math.Sqrt(dx * dx + dz * dz); - if (along < 0 || along > AetheriaRuntimeTractorMechanics.Distance || - lateral > AetheriaRuntimeTractorMechanics.Radius || distance <= 0.001) - continue; - - var force = AetheriaRuntimeTractorMechanics.Traction; - var pickupId = PickupPrefix + pickup.PickupIndex; - RequireAccepted(session.ApplyForce(new YmirApplyForceCommand( - Header(session, frameId, simulationStepIndex, "tractor", $"{actor.EntityIndex}:{pickup.PickupIndex}", ref commandOrdinal), - pickupId, - new Vec2((float)(-dx / distance * force), (float)(-dz / distance * force))))); - } - } - } - - private static AetheriaRuntimeWorldStep Lower( - YmirSessionStepResult step, - IReadOnlyList active) - { - var bodies = step.World.Bodies; - var entitiesByBodyId = active.ToDictionary(BodyId, StringComparer.Ordinal); - return new AetheriaRuntimeWorldStep( - bodies.Where(body => body.Id.StartsWith(EntityPrefix, StringComparison.Ordinal)) - .Select(body => new AetheriaRuntimeWorldBodyStep - { - EntityIndex = entitiesByBodyId[body.Id].EntityIndex, - PositionX = body.Position.X, - PositionZ = body.Position.Y, - VelocityX = body.Velocity.X, - VelocityY = body.Velocity.Y, - DirectionX = body.Direction?.X ?? 0, - DirectionY = body.Direction?.Y ?? 1 - }).ToArray(), - bodies.Where(body => body.Id.StartsWith(PickupPrefix, StringComparison.Ordinal)) - .Select(body => new AetheriaRuntimeWorldPickupStep - { - PickupIndex = ParseIndex(body.Id, PickupPrefix), - PositionX = body.Position.X, - PositionZ = body.Position.Y, - VelocityX = body.Velocity.X, - VelocityZ = body.Velocity.Y - }) - .ToArray()); - } - - private static IReadOnlyList ActiveEntities( - IReadOnlyList entities) - { - var source = entities ?? Array.Empty(); - var attached = source.SelectMany(entity => entity.ChildEntityIndices ?? Array.Empty()).ToHashSet(); - return source.Where(entity => entity.IsActive && entity.WormholeTransition == null && - !attached.Contains(entity.EntityIndex)).ToArray(); - } - - private static IReadOnlyList ActivePickups( - AetheriaRuntimeZoneSnapshotCommit zone) => - (zone.DroppedPickups ?? Array.Empty()) - .Where(pickup => pickup != null && pickup.AgeSeconds < pickup.LifetimeSeconds) - .ToArray(); - - private static IReadOnlyList BuildBodies( - IReadOnlyList active, - IReadOnlyList pickups) => - active.Select(entity => - { - if (string.IsNullOrWhiteSpace(entity.EntityId)) - throw new InvalidOperationException( - $"Entity index {entity.EntityIndex} has no stable runtime identity for retained Ymir ownership."); - var isStatic = string.Equals(entity.Kind, "station", StringComparison.OrdinalIgnoreCase); - var isShip = string.Equals(entity.Kind, "ship", StringComparison.OrdinalIgnoreCase); - var direction = Normalize(entity.DirectionX, entity.DirectionY, 0, 1); - return new PhysicsBody( - BodyId(entity), - new Vec2((float)entity.PositionX, (float)entity.PositionZ), - isStatic ? new Vec2(0, 0) : new Vec2((float)entity.VelocityX, (float)entity.VelocityY), - isStatic ? 48 : 20, - 1, - IsStatic: isStatic, - Restitution: 0.2f, - Direction: new Vec2((float)direction.X, (float)direction.Y), - CollisionCategoryBits: isShip ? ShipCollisionCategory : WorldEntityCollisionCategory, - CollisionMaskBits: isShip ? ulong.MaxValue : ulong.MaxValue & ~PickupCollisionCategory, - // The fossil moves entities directly and has no rigidbody collision response - // between ships/stations. Keep their Ymir bodies for fields and queries, but - // do not let invented circle radii hold a ship outside the 25-unit dock rule. - // Pickups retain group zero so Ymir can resolve their physical motion against ships. - // The daemon independently owns collection through post-step XZ proximity. - CollisionGroupIndex: -1); - }).Concat(pickups.Select(pickup => new PhysicsBody( - PickupPrefix + pickup.PickupIndex, - new Vec2((float)pickup.PositionX, (float)pickup.PositionZ), - new Vec2((float)pickup.VelocityX, (float)pickup.VelocityZ), - 5, - 1, - Restitution: 0.2f, - CollisionCategoryBits: PickupCollisionCategory, - CollisionMaskBits: ShipCollisionCategory))) - .ToArray(); - - private static string BodyId(AetheriaRuntimeEntitySnapshotCommit entity) => EntityPrefix + entity.EntityId; - - private static PhysicsBody PayloadBody(AetheriaRuntimePhysicalPayloadCommit payload) => new( - PhysicalPayloadPrefix + payload.PayloadId, - new Vec2((float)payload.PositionX, (float)payload.PositionZ), - payload.Stationary ? Vec2.Zero : new Vec2((float)payload.VelocityX, (float)payload.VelocityY), - (float)payload.Radius, - 1, - IsStatic: payload.Stationary, - Restitution: 0, - Direction: new Vec2(0, 1), - IsBullet: !payload.Stationary, - ParticipatesInFields: false, - CollisionCategoryBits: 2, - CollisionMaskBits: 0); - - private static IReadOnlyList GravityFields(AetheriaRuntimeZoneSnapshotCommit zone) => - AetheriaRuntimeDaemonRenderQueries.QueryBodyPoses(zone) - .Select(pose => (Pose: pose, Body: (zone.Bodies ?? Array.Empty()) - .FirstOrDefault(body => body != null && body.BodyKey == pose.BodyKey))) - .Where(pair => pair.Body != null && pair.Body.GravityInfluenceRadius > 0 && pair.Body.GravityWellDepth != 0) - .Select(pair => new RadialField( - "aetheria.daemon.gravity." + pair.Pose.BodyKey, - new Vec2((float)pair.Pose.CenterX, (float)pair.Pose.CenterZ), - (float)pair.Body!.GravityWellDepth, - (float)pair.Body.GravityInfluenceRadius)) - .ToArray(); - - private static YmirCommandHeader Header( - YmirSession session, - long frameId, - int simulationStepIndex, - string kind, - string subject, - ref int ordinal) => - new($"aetheria:frame:{frameId}:step:{simulationStepIndex}:{ordinal++}:{kind}:{subject}", session.Info.Revision); - - private static void RequireAccepted(YmirCommandReceipt receipt) - { - if (receipt.Outcome != YmirCommandOutcome.Accepted) - throw new InvalidOperationException( - $"Ymir rejected Aetheria command '{receipt.CommandId}' with {receipt.Error}."); - } - - private static void RequireQuery(YmirQueryError error, string queryId) - { - if (error != YmirQueryError.None) - throw new InvalidOperationException($"Ymir rejected Aetheria query '{queryId}' with {error}."); - } - - private static int ParseIndex(string id, string prefix) => - id.StartsWith(prefix, StringComparison.Ordinal) && - int.TryParse(id.AsSpan(prefix.Length), out var value) - ? value - : -1; - - private static void TryRollPersistenceGeneration(SessionState state, bool payload) - { - var current = payload ? state.PayloadSession : state.WorldSession; - if (current == null || current.PersistenceJournalEntryCount < PersistenceRolloverJournalEntries || - !current.TryReplaceWithCompactedPersistenceBaseline(out var replacement)) - return; - - if (payload) - state.PayloadSession = replacement!; - else - state.WorldSession = replacement!; - } - - private static (double X, double Y) Normalize( - double x, - double y, - double fallbackX, - double fallbackY) - { - var length = Math.Sqrt(x * x + y * y); - return length <= 0.001 ? (fallbackX, fallbackY) : (x / length, y / length); - } - - private sealed class SessionState(YmirSession worldSession) - { - public YmirSession WorldSession { get; set; } = worldSession; - public YmirSession? PayloadSession { get; set; } - public long LastFrameId { get; set; } = -1; - public int LastSimulationStepIndex { get; set; } = -1; - public AetheriaRuntimeWorldStep? LastResult { get; set; } - public long LastPayloadFrameId { get; set; } = -1; - public int LastPayloadSimulationStepIndex { get; set; } = -1; - public AetheriaRuntimePhysicalPayloadStep? LastPayloadResult { get; set; } - } - - private readonly record struct WorldKey(string RunId, int ZoneIndex); -} diff --git a/Aetheria.State.Daemon/Program.cs b/Aetheria.State.Daemon/Program.cs deleted file mode 100644 index 42c89bd56..000000000 --- a/Aetheria.State.Daemon/Program.cs +++ /dev/null @@ -1,4360 +0,0 @@ -using Aetheria.State; -using Aetheria.State.Documents; -using Aetheria.State.Daemon; -using GameCult.Aetheria.State.Verse; -using GameCult.Caching; -using GameCult.Eve.Surface; -using GameCult.Mesh; -using GameCult.Mesh.Quic; -using GameCult.Networking; -using MessagePack; -using System.Globalization; -using System.Diagnostics; -using System.Net; -using System.Net.Sockets; -using System.Collections.Concurrent; -using System.Security.Cryptography; -using System.Security.Cryptography.X509Certificates; - -var options = AetheriaDaemonHostOptions.Parse(args); -var startedAtUtc = DateTimeOffset.UtcNow.ToString("O"); -var startup = Stopwatch.StartNew(); -var startupPhase = Stopwatch.StartNew(); -var traceStartupPhases = string.Equals( - Environment.GetEnvironmentVariable("AETHERIA_TRACE_STARTUP_PHASES"), - "1", - StringComparison.Ordinal); -void TraceStartup(string name) -{ - if (traceStartupPhases) - Console.WriteLine( - $"Aetheria startup phase {name} took {startupPhase.Elapsed.TotalMilliseconds:0.###}ms " + - $"(total {startup.Elapsed.TotalMilliseconds:0.###}ms)."); - startupPhase.Restart(); -} -var traceClientTransport = string.Equals( - Environment.GetEnvironmentVariable("AETHERIA_TRACE_CLIENT_TRANSPORT"), - "1", - StringComparison.Ordinal); - -Console.WriteLine($"Aetheria Verse daemon starting: {options.StatePath}"); -Console.WriteLine($"Aetheria Verse daemon direct peers: {options.PeerCultMeshEndpoints.Count} " + - $"[{string.Join(", ", options.PeerCultMeshEndpoints)}]"); -if (options.EnableOdinAnnouncements) - Console.WriteLine($"Aetheria Odin announcement target: {options.OdinCultMeshUri}"); - -await using var node = await AetheriaStateNode.OpenAsync( - options.StatePath, - runtimeId: options.DaemonId, - startServer: true, - enableDurableShardLogs: false, - hydrationProfile: AetheriaStateHydrationProfile.DaemonBoot).ConfigureAwait(false); -TraceStartup("open-state-node"); -using var discoveryHost = new AetheriaVerseDiscoveryHost(node); -var latestFrame = await node.MutableDocument(AetheriaRuntimeVerseRecordKeys.DaemonFrameLatest).ReadAsync().ConfigureAwait(false); -TraceStartup("latest-frame"); -var unityBundles = BuildUnityBundleArtifactSet(options); -TraceStartup("provider-asset-bundles"); -await EnsureWorldDocumentAsync(node).ConfigureAwait(false); -TraceStartup("world-document"); -var persistedRuntimeCatalog = await node.Database - .GetAsync(AetheriaStateNode.RuntimeCatalogKey) - .ConfigureAwait(false); -var tradePolicyChanged = await EnsureTradeValuePolicyAsync(node, startedAtUtc).ConfigureAwait(false); -TraceStartup("trade-policy"); -var nativeCatalogChanged = await AetheriaDaemonNativeCatalog.EnsureAsync(node).ConfigureAwait(false); -TraceStartup("native-catalog"); -await node.FlushAsync().ConfigureAwait(false); -TraceStartup("initial-flush"); -if (persistedRuntimeCatalog == null || - string.IsNullOrWhiteSpace(persistedRuntimeCatalog.NameCorpusRecordKey) || - tradePolicyChanged || - nativeCatalogChanged) - await node.RefreshRuntimeCatalogAsync().ConfigureAwait(false); -TraceStartup("runtime-catalog"); -await AetheriaDaemonHangarCoordinator.EnsureAsync(node, node.RuntimeCatalog().Latest(), startedAtUtc).ConfigureAwait(false); -TraceStartup("hangar"); -var verseHost = await EnsureVerseHostSettingsAsync(node, options, startedAtUtc).ConfigureAwait(false); -TraceStartup("verse-host"); -await EnsureVerseAuthorityPolicyAsync(node, options).ConfigureAwait(false); -TraceStartup("authority-policy"); -discoveryHost.Update(verseHost); -await PublishRuntimeSessionAsync(node, options, startedAtUtc, "starting").ConfigureAwait(false); -TraceStartup("runtime-session"); -await PublishStateSurfacesAsync(node, options, startedAtUtc).ConfigureAwait(false); -TraceStartup("state-surfaces"); -var ingressState = new AetheriaDaemonIngressState(); -await RefreshControlPlaneInputsAsync(node, ingressState).ConfigureAwait(false); -await using var cultMeshClientHost = await StartClientCultMeshHostAsync( - node, options, unityBundles, () => latestFrame).ConfigureAwait(false); -TraceStartup("client-host"); -Console.WriteLine($"Aetheria client CultMesh endpoint: {cultMeshClientHost.ControlEndpoint}"); -Console.WriteLine($"Aetheria client CDN endpoint: {cultMeshClientHost.ContentEndpoint}"); -Console.WriteLine($"Aetheria client realtime endpoint: {cultMeshClientHost.RealtimeEndpoint}"); -Console.WriteLine($"Aetheria daemon transport-ready in {startup.Elapsed.TotalMilliseconds:0.###}ms."); -using var clientSubscriptions = new CultNetDatabaseSubscriptionServer(cultMeshClientHost.Control, node.Database); -var playableWorldDemand = new AetheriaPlayableWorldDemandState(); -clientSubscriptions.DemandChanged += playableWorldDemand.Observe; -var managedViewportDemand = new AetheriaManagedViewportDemandState(); -clientSubscriptions.DemandChanged += managedViewportDemand.Observe; -if (traceClientTransport) - clientSubscriptions.DemandChanged += demand => Console.WriteLine( - $"CultMesh state demand consumer={demand.ConsumerRuntimeId} subscription={demand.SubscriptionId} " + - $"active={demand.Active} sameMachine={demand.SameMachine} " + - $"records=[{string.Join(",", demand.RecordKeys)}] schemas=[{string.Join(",", demand.SchemaIds)}] " + - $"bodies=[{string.Join(",", demand.BodyIds)}] transports=[{string.Join(",", demand.SupportedBodyTransports)}]"); -using var bodyDemand = new CultMeshBodyDemandTracker(clientSubscriptions); -using var soaPublisher = new AetheriaRuntimeDaemonSoaFramePublisher( - DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(), - bodyDemand); -var stopped = new TaskCompletionSource(); -var hangarActivationRequested = false; -Console.CancelKeyPress += (_, eventArgs) => -{ - eventArgs.Cancel = true; - stopped.TrySetResult(null); -}; - -while (!stopped.Task.IsCompleted) -{ - if (!options.Once && !options.UseTerminusFixture) - { - await PublishRuntimeSessionAsync(node, options, startedAtUtc, "ready").ConfigureAwait(false); - Console.WriteLine("Aetheria daemon ready; waiting for a client to load or generate a world."); - while (!stopped.Task.IsCompleted) - { - ThrowIfClientHostFaulted(cultMeshClientHost); - hangarActivationRequested |= await AcceptCoreEveInvocationsAsync(node, options, latestFrame).ConfigureAwait(false); - await AcceptEveCommandsAsync(node, options, ingressState).ConfigureAwait(false); - if (hangarActivationRequested && playableWorldDemand.IsActive && - (HasPlayableRun(latestFrame?.Run) || - HasPlayableRun(await ReadRuntimeRunCheckpointAsync(node, options.RenderSettings).ConfigureAwait(false)))) - break; - - var completed = await Task.WhenAny(stopped.Task, Task.Delay(options.TickInterval)).ConfigureAwait(false); - if (completed == stopped.Task) - break; - } - - if (stopped.Task.IsCompleted) - break; - } - else - { - await EnsurePlayableRunDocumentsAsync(node, options, startedAtUtc, latestFrame).ConfigureAwait(false); - TraceStartup("playable-run"); - } - - latestFrame = await node.MutableDocument(AetheriaRuntimeVerseRecordKeys.DaemonFrameLatest) - .ReadAsync().ConfigureAwait(false); - using var worldPhysics = new AetheriaYmirWorldPhysics(); - await EnsureGameSessionAsync(node, options, startedAtUtc, latestFrame).ConfigureAwait(false); - TraceStartup("game-session"); - using var physicsPersistence = await AetheriaYmirPersistenceCoordinator.OpenAsync( - node, - worldPhysics, - latestFrame).ConfigureAwait(false); - TraceStartup("ymir-persistence"); - var nextApiPublicationUtc = DateTimeOffset.UtcNow; - var hotState = new AetheriaHotEntityPublicationState(); - var reactiveSurfaceState = new AetheriaReactiveSurfacePublicationState(); - var publishRestoredFrame = !options.Once && - latestFrame?.Run != null && - HasPlayableRun(latestFrame.Run) && - HasPersistedClientBootstrap(node, latestFrame); - AetheriaRuntimeDaemonTickResult? initialPublication = null; - AetheriaPreparedPublication? initialPrepared = null; - Task publicationTask; - if (publishRestoredFrame) - { - await RefreshControlPlaneInputsAsync(node, ingressState).ConfigureAwait(false); - TraceStartup("restored-client-state"); - } - else - { - var firstTick = await TickAsync( - node, options, unityBundles, worldPhysics, latestFrame, ingressState, buildPublications: false).ConfigureAwait(false); - TraceStartup("first-tick"); - latestFrame = firstTick.Frame; - initialPrepared = PreparePublication( - node.StatePath, - options, - firstTick, - ingressState, - physicsPersistence); - initialPublication = initialPrepared.Publication; - } - if (!publishRestoredFrame) - { - await PublishClientGameplayDocumentsAsync( - node, - options, - unityBundles, - initialPublication!, - ingressState.Catalog ?? throw new InvalidOperationException("Aetheria runtime catalog was not initialized."), - reactiveSurfaceState, - publishTopology: true).ConfigureAwait(false); - } - TraceStartup("client-bootstrap"); - ThrowIfClientHostFaulted(cultMeshClientHost); - await PublishHotEntityStateAsync( - node, soaPublisher, hotState, latestFrame!, ingressState.Catalog, cultMeshClientHost).ConfigureAwait(false); - TraceStartup("hot-entity-state"); - if (publishRestoredFrame) - { - publicationTask = Task.CompletedTask; - } - else - { - publicationTask = Task.Run(() => PersistPreparedDocumentsAsync( - node, - options, - physicsPersistence, - initialPrepared!, - initialTopology: true)); - } - nextApiPublicationUtc = DateTimeOffset.UtcNow.Add(options.ApiPublicationInterval); - Console.WriteLine($"Aetheria Verse daemon published frame {latestFrame!.FrameId}."); - Console.WriteLine($"Aetheria daemon playable-world-ready in {startup.Elapsed.TotalMilliseconds:0.###}ms."); - - if (options.Once) - { - await publicationTask.ConfigureAwait(false); - await PublishRuntimeSessionAsync(node, options, startedAtUtc, "completed").ConfigureAwait(false); - return; - } - await PublishRuntimeSessionAsync(node, options, startedAtUtc, "running").ConfigureAwait(false); - Console.WriteLine("Aetheria Verse daemon is running. Press Ctrl+C to stop."); - - var nextTickUtc = DateTimeOffset.UtcNow.Add(options.TickInterval); - while (!stopped.Task.IsCompleted && (options.UseTerminusFixture || playableWorldDemand.IsActive)) - { - ThrowIfClientHostFaulted(cultMeshClientHost); - var delay = nextTickUtc - DateTimeOffset.UtcNow; - if (delay > TimeSpan.Zero) - { - var completed = await Task.WhenAny(stopped.Task, Task.Delay(delay)).ConfigureAwait(false); - if (completed == stopped.Task) - break; - } - - var buildPublications = DateTimeOffset.UtcNow >= nextApiPublicationUtc; - var tick = await TickAsync(node, options, unityBundles, worldPhysics, latestFrame, ingressState, buildPublications: false).ConfigureAwait(false); - ThrowIfClientHostFaulted(cultMeshClientHost); - latestFrame = tick.Frame; - await PublishHotEntityStateAsync( - node, soaPublisher, hotState, tick.Frame, ingressState.Catalog, cultMeshClientHost).ConfigureAwait(false); - nextTickUtc += options.TickInterval; - if (nextTickUtc < DateTimeOffset.UtcNow - options.TickInterval) - nextTickUtc = DateTimeOffset.UtcNow; - if (buildPublications && publicationTask.IsCompleted) - { - if (publicationTask.IsFaulted) - await publicationTask.ConfigureAwait(false); - var publication = PreparePublication( - node.StatePath, - options, - tick, - ingressState, - physicsPersistence); - publicationTask = Task.Run(() => PersistPreparedDocumentsAsync( - node, - options, - physicsPersistence, - publication, - initialTopology: false, - unityBundles: unityBundles, - reactiveSurfaceState: reactiveSurfaceState)); - await PublishDemandedManagedViewportsAsync(node, options, tick.Frame, managedViewportDemand).ConfigureAwait(false); - nextApiPublicationUtc = DateTimeOffset.UtcNow.Add(options.ApiPublicationInterval); - } - if (tick.Frame.FrameId % (traceClientTransport ? 10 : 120) == 0) - { - Console.WriteLine( - $"Aetheria Verse daemon published frame {tick.Frame.FrameId} at {tick.Frame.SimulationTimeSeconds:0.00}s; " + - $"control peers={cultMeshClientHost.Control.PeerCount} " + - $"quic peers={cultMeshClientHost.Realtime.ConnectionCount} " + - $"cdn chunks={cultMeshClientHost.Content.ChunkRequestsServed}."); - } - } - - await publicationTask.ConfigureAwait(false); - if (!stopped.Task.IsCompleted && !options.UseTerminusFixture) - { - var checkpointRun = latestFrame?.Run ?? throw new InvalidOperationException("Aetheria cannot checkpoint an inactive playable session without its run."); - var checkpointTick = new AetheriaRuntimeDaemonTickResult( - checkpointRun, - new AetheriaRuntimeDaemonOperationResult(checkpointRun, [], []), - latestFrame!); - var checkpoint = PreparePublication( - node.StatePath, - options, - checkpointTick, - ingressState, - physicsPersistence); - await PersistPreparedDocumentsAsync( - node, - options, - physicsPersistence, - checkpoint, - initialTopology: false, - unityBundles: unityBundles, - reactiveSurfaceState: reactiveSurfaceState).ConfigureAwait(false); - Console.WriteLine($"Aetheria daemon saved frame {latestFrame!.FrameId} after the playable client disconnected."); - } -} - -await PublishRuntimeSessionAsync(node, options, startedAtUtc, "stopped").ConfigureAwait(false); -Console.WriteLine("Aetheria Verse daemon stopping."); - -static void ThrowIfClientHostFaulted(AetheriaClientCultMeshHost host) -{ - if (host.Control.BackgroundFailure.IsCompleted) - throw new InvalidOperationException( - "Aetheria client CultMesh TCP control host faulted.", - host.Control.BackgroundFailure.GetAwaiter().GetResult()); - if (host.Realtime.BackgroundFailure.IsCompleted) - throw new InvalidOperationException( - "Aetheria client CultMesh QUIC realtime host faulted.", - host.Realtime.BackgroundFailure.GetAwaiter().GetResult()); -} - -static async Task TickAsync( - AetheriaStateNode node, - AetheriaDaemonHostOptions options, - AetheriaUnityBundleArtifactSet unityBundles, - IAetheriaRuntimeWorldPhysics worldPhysics, - AetheriaRuntimeDaemonFrameDocument? currentFrame, - AetheriaDaemonIngressState ingressState, - bool buildPublications) -{ - var traceTickPhases = string.Equals( - Environment.GetEnvironmentVariable("AETHERIA_TRACE_TICK_PHASES"), - "1", - StringComparison.Ordinal); - var phase = Stopwatch.StartNew(); - void TracePhase(string name) - { - if (traceTickPhases && phase.ElapsedMilliseconds >= 20) - Console.WriteLine($"Aetheria tick phase {name} took {phase.ElapsedMilliseconds}ms."); - phase.Restart(); - } - - await AcceptCoreEveInvocationsAsync(node, options, currentFrame).ConfigureAwait(false); - TracePhase("core-ingress"); - await AcceptEveCommandsAsync(node, options, ingressState).ConfigureAwait(false); - TracePhase("provider-ingress"); - if (!ingressState.ControlPlaneInitialized || buildPublications) - await RefreshControlPlaneInputsAsync(node, ingressState).ConfigureAwait(false); - - var fixedDeltaSeconds = currentFrame?.FixedDeltaSeconds > 0 - ? currentFrame.FixedDeltaSeconds - : options.FixedDeltaSeconds; - var nextFrameId = (currentFrame?.FrameId ?? -1) + 1; - var sessionId = string.IsNullOrWhiteSpace(currentFrame?.SessionId) - ? options.SessionId - : currentFrame.SessionId; - var run = HasPlayableRun(currentFrame?.Run) - ? currentFrame!.Run - : await ReadRuntimeRunCheckpointAsync(node, options.RenderSettings).ConfigureAwait(false) ?? new AetheriaRuntimeRunCheckpointCommit(); - ApplyDaemonRenderSettings(run, options.RenderSettings); - - var loadoutTemplates = ingressState.LoadoutTemplates; - var observedCommands = node.Documents() - .OrderBy(command => command.IssuedAtUtc ?? "", StringComparer.Ordinal) - .ThenBy(command => command.CommandId ?? "", StringComparer.Ordinal) - .ToArray(); - var accountedCommandIds = new HashSet( - currentFrame?.AccountedCommandIds ?? Array.Empty(), - StringComparer.Ordinal); - var pendingObservedCommands = observedCommands - .Where(command => command != null && !accountedCommandIds.Contains(command.CommandId ?? "")) - .ToArray(); - var policyRejectedCommandIds = new List(); - var authorityPolicy = ingressState.AuthorityPolicy; - var starbridgeScenario = ingressState.StarbridgeScenario; - var starbridgeSession = ingressState.StarbridgeSession; - var authorityLeases = ingressState.AuthorityLeases; - var authorizedCommands = AetheriaRuntimeAuthorityRouter.AuthorizedCommands( - pendingObservedCommands, - authorityPolicy, - authorityLeases, - options.DaemonId, - policyRejectedCommandIds); - var terminus = string.Equals(ingressState.GameMode, AetheriaGameSessionState.TerminusMode, StringComparison.Ordinal); - var simulationClockCommands = authorizedCommands - .Where(command => command.Kind == AetheriaRuntimeDaemonCommandKinds.SetSimulationRate || - command.Kind == AetheriaRuntimeDaemonCommandKinds.AdvanceSimulationStep) - .ToArray(); - if (!terminus && simulationClockCommands.Length > 0) - { - policyRejectedCommandIds.AddRange(simulationClockCommands.Select(command => command.CommandId)); - authorizedCommands = authorizedCommands - .Where(command => command.Kind != AetheriaRuntimeDaemonCommandKinds.SetSimulationRate && - command.Kind != AetheriaRuntimeDaemonCommandKinds.AdvanceSimulationStep) - .ToArray(); - } - else if (simulationClockCommands.Length > 0) - { - await ApplySimulationClockCommandsAsync(node, ingressState, simulationClockCommands).ConfigureAwait(false); - } - var simulationStepCount = terminus ? ingressState.TakeTerminusSimulationSteps() : 1; - var advanceSimulation = simulationStepCount > 0 && AetheriaRuntimeRunLifecycle.IsActive(run); - if (terminus && !advanceSimulation && AetheriaRuntimeRunLifecycle.IsActive(run)) - { - authorizedCommands = authorizedCommands - .Where(command => !AetheriaRuntimeDaemonOperations.RequiresSimulationStep(command.Kind)) - .ToArray(); - } - var simulationTimeSeconds = (currentFrame?.SimulationTimeSeconds ?? 0) + - ((advanceSimulation ? simulationStepCount : 0) * fixedDeltaSeconds); - TracePhase("tick-inputs"); - - var result = AetheriaRuntimeDaemonTickRunner.Tick( - node.StatePath, - run, - new AetheriaRuntimeDaemonTickOptions - { - DaemonId = options.DaemonId, - SessionId = sessionId, - VerseId = options.VerseId, - CultMeshAddress = options.CultMeshAddress, - FrameId = nextFrameId, - SimulationTimeSeconds = simulationTimeSeconds, - FixedDeltaSeconds = fixedDeltaSeconds, - ObservedCommands = authorizedCommands, - AccountedCommandIds = accountedCommandIds.ToArray(), - PreRejectedCommandIds = policyRejectedCommandIds, - CumulativeAppliedCommandIds = currentFrame?.CumulativeAppliedCommandIds ?? currentFrame?.AppliedCommandIds ?? Array.Empty(), - CumulativeRejectedCommandIds = currentFrame?.CumulativeRejectedCommandIds ?? currentFrame?.RejectedCommandIds ?? Array.Empty(), - Catalog = ingressState.Catalog, - RenderSettings = options.RenderSettings, - SimulationSettings = options.SimulationSettings, - WorldPhysics = worldPhysics, - AdvanceSimulation = advanceSimulation, - SimulationStepCount = simulationStepCount, - StopCompressedSimulationOnAttention = terminus && simulationStepCount > 1, - StarbridgeScenario = starbridgeScenario, - StarbridgeSession = starbridgeSession, - BuildPublications = buildPublications, - OperationContext = new AetheriaRuntimeDaemonOperationContext - { - LoadoutTemplates = loadoutTemplates - } - }); - if (terminus && result.AttentionInterruption != null) - await PauseTerminusForAttentionAsync(node, ingressState).ConfigureAwait(false); - result.Frame.GameMode = ingressState.GameMode; - result.Frame.RequestedSimulationRate = ingressState.RequestedSimulationRate; - result.Frame.EffectiveSimulationRate = ingressState.SimulationRate; - result.Frame.SimulationStepsExecuted = result.SimulationStepsExecuted; - result.Frame.AttentionCauseKind = result.AttentionInterruption?.CauseKind ?? ""; - TracePhase("simulation"); - if (string.Equals(Environment.GetEnvironmentVariable("AETHERIA_TRACE_EVE_SNAPSHOTS"), "1", StringComparison.Ordinal) && - result.Frame.AppliedCommandIds.Count > 0) - { - foreach (var entity in (result.Frame.Run?.Zones ?? Array.Empty()) - .SelectMany(zone => zone.Entities ?? Array.Empty()) - .Where(entity => string.Equals(entity.FactionKey, "player", StringComparison.OrdinalIgnoreCase))) - { - Console.WriteLine( - $"Eve command result entity={entity.EntityIndex} position={entity.PositionX},{entity.PositionZ} " + - $"velocity={entity.VelocityX},{entity.VelocityY}"); - } - } - result.Frame.CumulativeImportedFactIds = - currentFrame?.CumulativeImportedFactIds ?? Array.Empty(); - result.Frame.CumulativeRejectedImportedFactIds = - currentFrame?.CumulativeRejectedImportedFactIds ?? Array.Empty(); - - await PublishCommittedCommandFactsAsync( - node, - options, - result.Frame, - pendingObservedCommands, - authorizedCommands, - policyRejectedCommandIds).ConfigureAwait(false); - TracePhase("command-facts"); - - if (options.PeerCultMeshEndpoints.Count > 0) - { - result = await ImportRemoteCommittedFactsAsync( - node, - options, - worldPhysics, - result.Frame, - result, - authorityPolicy, - authorityLeases).ConfigureAwait(false); - TracePhase("peer-facts"); - } - - if (buildPublications) - { - await PublishDurableDaemonDocumentsAsync(node, result).ConfigureAwait(false); - await PublishClientGameplayDocumentsAsync( - node, - options, - unityBundles, - result, - ingressState.Catalog ?? throw new InvalidOperationException("Aetheria runtime catalog was not initialized."), - reactiveSurfaceState: null, - publishTopology: true).ConfigureAwait(false); - await PublishSecondaryTopologyDocumentsAsync(node, options, result).ConfigureAwait(false); - TracePhase("api-publication"); - await PublishStateSurfacesAsync(node, options, result.Frame.PublishedAtUtc, publishHangar: false).ConfigureAwait(false); - TracePhase("state-surfaces"); - await PublishOdinSurfaceAnnouncementsAsync(node, options, result.Frame.PublishedAtUtc).ConfigureAwait(false); - TracePhase("odin-announcements"); - } - - return result; -} - -static AetheriaPreparedPublication PreparePublication( - string statePath, - AetheriaDaemonHostOptions options, - AetheriaRuntimeDaemonTickResult tick, - AetheriaDaemonIngressState ingressState, - AetheriaYmirPersistenceCoordinator physicsPersistence) -{ - var physics = physicsPersistence.Capture(tick.Frame); - var frameBytes = MessagePackSerializer.Serialize(tick.Frame); - var frame = MessagePackSerializer.Deserialize(frameBytes); - var operation = new AetheriaRuntimeDaemonOperationResult( - frame.Run ?? new AetheriaRuntimeRunCheckpointCommit(), - tick.OperationResult.AppliedCommandIds.ToArray(), - tick.OperationResult.RejectedCommandIds.ToArray(), - rejectedCommandReasons: tick.OperationResult.RejectedCommandReasons); - var publication = AetheriaRuntimeDaemonTickRunner.BuildPublications( - statePath, - operation, - frame, - new AetheriaRuntimeDaemonTickOptions - { - DaemonId = options.DaemonId, - VerseId = options.VerseId, - CultMeshAddress = options.CultMeshAddress, - Catalog = ingressState.Catalog, - StarbridgeScenario = ingressState.StarbridgeScenario, - StarbridgeSession = ingressState.StarbridgeSession, - RenderSettings = options.RenderSettings, - SimulationSettings = options.SimulationSettings - }, - frame.AccountedCommandIds?.Count ?? 0); - return new AetheriaPreparedPublication( - publication, - physics, - ingressState.Catalog ?? throw new InvalidOperationException("Aetheria runtime catalog was not initialized.")); -} - -static bool HasPersistedClientBootstrap( - AetheriaStateNode node, - AetheriaRuntimeDaemonFrameDocument frame) -{ - var input = node.Cache.Get(AetheriaRuntimeVerseRecordKeys.PilotInputCapability); - var game = node.Cache.Get(AetheriaRuntimeVerseRecordKeys.DaemonGameSurface); - var reactive = node.Cache.Get(AetheriaRuntimeVerseRecordKeys.DaemonGameReactiveSurface); - var map = node.Cache.Get(AetheriaRuntimeVerseRecordKeys.MapMenuSurface); - return node.Cache.Get(AetheriaRuntimeVerseRecordKeys.EveProviderAdvertisement) != null && - node.Cache.Get(AetheriaRuntimeVerseRecordKeys.EveAssetCatalog) != null && - input?.Version == frame.FrameId && - game?.Version == frame.FrameId && - HasCurrentEntityBodyContract(game.Surface.Root, requirePointer: true) && - reactive?.Version == frame.FrameId && - HasCurrentEntityBodyContract(reactive.Surface.Root, requirePointer: false) && - map?.Version == frame.FrameId; -} - -static bool HasCurrentEntityBodyContract(EveSurfaceComponent component, bool requirePointer) -{ - var hasPointer = !string.IsNullOrWhiteSpace(component.GetProp("entityViewPointerId")); - if (hasPointer && !string.Equals( - component.GetProp("entityBodyId"), - AetheriaRuntimeDaemonSoaFramePublisher.BodyId, - StringComparison.Ordinal)) - return false; - var children = component.Children ?? Array.Empty(); - var childHasPointer = false; - foreach (var child in children) - { - if (!HasCurrentEntityBodyContract(child, requirePointer: false)) - return false; - childHasPointer |= ContainsEntityViewPointer(child); - } - return !requirePointer || hasPointer || childHasPointer; -} - -static bool ContainsEntityViewPointer(EveSurfaceComponent component) => - !string.IsNullOrWhiteSpace(component.GetProp("entityViewPointerId")) || - (component.Children ?? Array.Empty()).Any(ContainsEntityViewPointer); - -static async Task PersistPreparedDocumentsAsync( - AetheriaStateNode node, - AetheriaDaemonHostOptions options, - AetheriaYmirPersistenceCoordinator physicsPersistence, - AetheriaPreparedPublication prepared, - bool initialTopology, - AetheriaUnityBundleArtifactSet? unityBundles = null, - AetheriaReactiveSurfacePublicationState? reactiveSurfaceState = null) -{ - await physicsPersistence.PersistPrivateAsync(prepared.Physics).ConfigureAwait(false); - await PublishDurableDaemonDocumentsAsync(node, prepared.Publication).ConfigureAwait(false); - if (initialTopology) - { - await PublishSecondaryTopologyDocumentsAsync(node, options, prepared.Publication).ConfigureAwait(false); - await PublishStateSurfacesAsync(node, options, prepared.Publication.Frame.PublishedAtUtc, publishHangar: false).ConfigureAwait(false); - await PublishOdinSurfaceAnnouncementsAsync(node, options, prepared.Publication.Frame.PublishedAtUtc).ConfigureAwait(false); - } - else - { - if (unityBundles == null) - throw new InvalidOperationException("Periodic publication requires the provider asset set."); - await PublishClientGameplayDocumentsAsync( - node, - options, - unityBundles, - prepared.Publication, - prepared.Catalog, - reactiveSurfaceState, - publishTopology: false).ConfigureAwait(false); - } - await node.FlushAsync(soft: false).ConfigureAwait(false); - await physicsPersistence.ActivateAsync().ConfigureAwait(false); -} - -static async Task RefreshControlPlaneInputsAsync( - AetheriaStateNode node, - AetheriaDaemonIngressState ingressState) -{ - var gameSession = await node.MutableDocument(AetheriaStateNode.GameSessionStateKey) - .ReadAsync().ConfigureAwait(false); - ingressState.GameMode = gameSession?.Mode ?? ""; - ingressState.RequestedSimulationRate = gameSession?.SimulationRate ?? 0; - ingressState.SimulationRate = gameSession?.EffectiveSimulationRate ?? gameSession?.SimulationRate ?? 0; - ingressState.LoadoutTemplates = node.Cache - .GetAll() - .Select(ToLoadoutTemplateCommit) - .ToArray(); - ingressState.Catalog = node.RuntimeCatalog().Latest(); - ingressState.AuthorityPolicy = await node - .MutableDocument(AetheriaRuntimeVerseRecordKeys.VerseAuthorityPolicy) - .ReadAsync().ConfigureAwait(false); - ingressState.StarbridgeScenario = await node - .MutableDocument(AetheriaRuntimeVerseRecordKeys.StarbridgeScenarioLatest) - .ReadAsync().ConfigureAwait(false); - ingressState.StarbridgeSession = await node - .MutableDocument(AetheriaRuntimeVerseRecordKeys.StarbridgeSessionLatest) - .ReadAsync().ConfigureAwait(false); - ingressState.AuthorityLeases = node.Documents().ToArray(); - ingressState.ControlPlaneInitialized = true; -} - -static async Task ApplySimulationClockCommandsAsync( - AetheriaStateNode node, - AetheriaDaemonIngressState ingressState, - IReadOnlyList commands) -{ - var session = await node.MutableDocument(AetheriaStateNode.GameSessionStateKey) - .ReadAsync().ConfigureAwait(false) ?? new AetheriaGameSessionState(); - foreach (var command in commands.OrderBy(command => command.IssuedAtUtc, StringComparer.Ordinal)) - { - if (command.Kind == AetheriaRuntimeDaemonCommandKinds.AdvanceSimulationStep) - { - ingressState.SimulationStepAccumulator += 1; - continue; - } - if (AetheriaRuntimeDaemonOperations.IsSupportedSimulationRate(command.ScalarValue)) - { - session.SimulationRate = command.ScalarValue; - session.EffectiveSimulationRate = command.ScalarValue; - } - } - session.UpdatedAtUtc = DateTimeOffset.UtcNow.ToString("O"); - await node.MutableDocument(AetheriaStateNode.GameSessionStateKey) - .ReplaceAsync(session).ConfigureAwait(false); - ingressState.RequestedSimulationRate = session.SimulationRate; - ingressState.SimulationRate = session.EffectiveSimulationRate ?? session.SimulationRate; -} - -static async Task PauseTerminusForAttentionAsync( - AetheriaStateNode node, - AetheriaDaemonIngressState ingressState) -{ - var session = await node.MutableDocument(AetheriaStateNode.GameSessionStateKey) - .ReadAsync().ConfigureAwait(false) ?? new AetheriaGameSessionState(); - session.EffectiveSimulationRate = 0; - session.UpdatedAtUtc = DateTimeOffset.UtcNow.ToString("O"); - await node.MutableDocument(AetheriaStateNode.GameSessionStateKey) - .ReplaceAsync(session).ConfigureAwait(false); - ingressState.SimulationRate = 0; - ingressState.SimulationStepAccumulator = 0; -} - -static async Task PublishCommittedCommandFactsAsync( - AetheriaStateNode node, - AetheriaDaemonHostOptions options, - AetheriaRuntimeDaemonFrameDocument frame, - IReadOnlyList pendingObservedCommands, - IReadOnlyList authorizedCommands, - IReadOnlyList policyRejectedCommandIds) -{ - var byCommandId = (pendingObservedCommands ?? Array.Empty()) - .Where(command => command != null && !string.IsNullOrWhiteSpace(command.CommandId)) - .ToDictionary(command => command.CommandId, StringComparer.Ordinal); - - foreach (var commandId in frame.AppliedCommandIds ?? Array.Empty()) - { - if (string.IsNullOrWhiteSpace(commandId) || !byCommandId.TryGetValue(commandId, out var command)) - continue; - - await PublishCommittedFactAsync( - node, - AetheriaRuntimeCommittedCommandFactDocument.FromAppliedCommand( - frame, - command, - options.VerseId)).ConfigureAwait(false); - } - - foreach (var commandId in frame.RejectedCommandIds ?? Array.Empty()) - { - if (string.IsNullOrWhiteSpace(commandId) || - !byCommandId.TryGetValue(commandId, out var command)) - continue; - - await PublishCommittedFactAsync( - node, - AetheriaRuntimeCommittedCommandFactDocument.FromRejectedCommand( - frame, - command, - options.VerseId)).ConfigureAwait(false); - } -} - -static async Task PublishCommittedFactAsync( - AetheriaStateNode node, - AetheriaRuntimeCommittedCommandFactDocument fact) -{ - await node.PutCommittedCommandFactAsync(fact).ConfigureAwait(false); - var receipt = AetheriaRuntimeDaemonReceiptProjector.Project( - fact, - AetheriaRuntimeDaemonGameSurfaceBuilder.SurfaceId); - await node.Database.PutAsync(AetheriaRuntimeVerseRecordKeys.EveReceiptForCommand(receipt.CommandId), receipt) - .ConfigureAwait(false); - if (string.Equals(Environment.GetEnvironmentVariable("AETHERIA_TRACE_EVE_SNAPSHOTS"), "1", StringComparison.Ordinal)) - Console.WriteLine($"Eve command receipt command={receipt.CommandId} state={receipt.State} provider={receipt.ProviderId}"); -} - -static async Task ImportRemoteCommittedFactsAsync( - AetheriaStateNode node, - AetheriaDaemonHostOptions options, - IAetheriaRuntimeWorldPhysics worldPhysics, - AetheriaRuntimeDaemonFrameDocument frame, - AetheriaRuntimeDaemonTickResult currentResult, - AetheriaRuntimeVerseAuthorityPolicyDocument? authorityPolicy, - IReadOnlyList authorityLeases) -{ - var remoteFacts = new List(); - foreach (var endpoint in options.PeerCultMeshEndpoints) - { - try - { - var endpointFacts = await AetheriaVerseReplica - .FetchScopedDocumentsAsync( - endpoint, - schemaIds: [AetheriaRuntimeDaemonSchemas.CommittedCommandFact], - connectTimeout: options.PeerSyncTimeout, - responseTimeout: options.PeerSyncTimeout).ConfigureAwait(false); - remoteFacts.AddRange(endpointFacts); - } - catch (Exception ex) when ( - ex is IOException || - ex is SocketException || - ex is TimeoutException || - ex is InvalidOperationException) - { - Console.WriteLine($"Aetheria direct peer fact sync skipped for {endpoint}: {ex.GetType().Name}: {ex.Message}"); - } - } - - var facts = remoteFacts - .Where(fact => fact != null) - .Where(fact => string.Equals(fact.VerseId, options.VerseId, StringComparison.Ordinal)) - .Where(fact => !string.Equals(fact.SourceDaemonId, options.DaemonId, StringComparison.Ordinal)) - .GroupBy(fact => fact.FactId ?? "", StringComparer.Ordinal) - .Where(group => !string.IsNullOrWhiteSpace(group.Key)) - .Select(group => group.First()) - .ToArray(); - if (facts.Length == 0) - return currentResult; - - var importedFactIds = (frame.CumulativeImportedFactIds ?? Array.Empty()) - .Concat(frame.CumulativeRejectedImportedFactIds ?? Array.Empty()) - .Distinct(StringComparer.Ordinal) - .ToArray(); - var import = AetheriaRuntimeCommittedFactImporter.ImportIntoFrame( - node.StatePath, - frame, - facts, - authorityPolicy, - authorityLeases, - options.DaemonId, - options.DaemonId, - frame.SessionId, - options.VerseId, - worldPhysics, - importedFactIds, - node.RuntimeCatalog().Latest()); - - var importedFrame = import.Frame; - importedFrame.ImportedFactIds = import.AcceptedFactIds; - importedFrame.RejectedImportedFactIds = import.RejectedFactIds; - importedFrame.DuplicateImportedFactIds = import.DuplicateFactIds; - importedFrame.CumulativeImportedFactIds = (frame.CumulativeImportedFactIds ?? Array.Empty()) - .Concat(import.AcceptedFactIds) - .Distinct(StringComparer.Ordinal) - .ToArray(); - importedFrame.CumulativeRejectedImportedFactIds = (frame.CumulativeRejectedImportedFactIds ?? Array.Empty()) - .Concat(import.RejectedFactIds) - .Distinct(StringComparer.Ordinal) - .ToArray(); - importedFrame.CumulativeAppliedCommandIds = (frame.CumulativeAppliedCommandIds ?? Array.Empty()) - .Concat(importedFrame.CumulativeAppliedCommandIds ?? Array.Empty()) - .Distinct(StringComparer.Ordinal) - .ToArray(); - importedFrame.CumulativeRejectedCommandIds = (frame.CumulativeRejectedCommandIds ?? Array.Empty()) - .Concat(importedFrame.CumulativeRejectedCommandIds ?? Array.Empty()) - .Distinct(StringComparer.Ordinal) - .ToArray(); - - if (import.AcceptedFactIds.Count > 0 || import.RejectedFactIds.Count > 0) - { - Console.WriteLine( - $"Aetheria imported direct peer facts: accepted={import.AcceptedFactIds.Count}, " + - $"rejected={import.RejectedFactIds.Count}, duplicates={import.DuplicateFactIds.Count}."); - } - - return import.Tick; -} - -static async Task StartClientCultMeshHostAsync( - AetheriaStateNode node, - AetheriaDaemonHostOptions options, - AetheriaUnityBundleArtifactSet unityBundles, - Func latestFrame) -{ - var traceStartup = string.Equals( - Environment.GetEnvironmentVariable("AETHERIA_TRACE_STARTUP_PHASES"), - "1", - StringComparison.Ordinal); - var startupPhase = Stopwatch.StartNew(); - void Trace(string phase) - { - if (traceStartup) - Console.WriteLine($"Aetheria client-host phase {phase} took {startupPhase.Elapsed.TotalMilliseconds:0.###}ms."); - startupPhase.Restart(); - } - - var bundleCdnManifests = BuildBundleCdnManifestIndex(unityBundles); - Trace("cdn-documents"); - var server = new TcpFramedCultNetSchemaServer(new TcpListener( - ParseBindAddress(options.ClientCultMeshHost), - options.ClientCultMeshPort)); - var content = new CultMeshTcpContentServer(new TcpListener( - ParseBindAddress(options.ClientCultMeshHost), - options.ClientCultMeshContentPort), unityBundles.ResolveChunk); - Trace("tcp-listeners"); - var realtimeCertificate = LoadOrCreateRealtimeCertificate( - options.ClientCultMeshAdvertiseHost, - options.ClientCultMeshCertificatePath); - Trace("realtime-certificate"); - CultMeshQuicRealtimeServer realtime; - try - { - realtime = await CultMeshQuicRealtimeServer.ListenAsync(new CultMeshQuicRealtimeServerOptions - { - ListenEndPoint = new IPEndPoint( - ParseBindAddress(options.ClientCultMeshHost), - options.ClientCultMeshQuicPort), - ServerCertificate = realtimeCertificate - }).ConfigureAwait(false); - Trace("quic-listen"); - } - catch - { - realtimeCertificate.Dispose(); - content.Dispose(); - server.Dispose(); - throw; - } - var advertisedEndpoint = $"cultnet+tcp://{options.ClientCultMeshAdvertiseHost}:{server.LocalEndPoint.Port}"; - var advertisedContentEndpoint = - $"{CultMeshTcpContentTransportConnector.Scheme}://{options.ClientCultMeshAdvertiseHost}:{content.LocalEndPoint.Port}"; - var certificatePin = Convert.ToHexString(SHA256.HashData(realtimeCertificate.RawData)); - var advertisedRealtimeEndpoint = - $"{CultMeshQuicRealtimeTransportConnector.Scheme}://{options.ClientCultMeshAdvertiseHost}:{realtime.LocalEndPoint.Port}" + - $"?cert-sha256={certificatePin}"; - server.PeerFailed += (endpoint, error) => Console.Error.WriteLine( - $"Aetheria client CultMesh peer {endpoint} failed: {error.GetType().Name}: {error.Message}"); - server.OnCultNet((request, peer) => - { - var descriptor = new CultMeshVerseDescriptor( - options.VerseId, - "Aetheria", - CultMeshVerseAuthorityModel.OperatorCluster, - new CultMeshVerseCompatibility( - "cultmesh.v0", - CultMeshVerseDescriptor.ComputeRulesHash("aetheria", "runtime-world.v1")), - discoveryEndpoints: new[] { advertisedEndpoint, advertisedContentEndpoint, advertisedRealtimeEndpoint }, - authorityRuntimeIds: new[] { options.DaemonId }, - description: "Aetheria provider Verse"); - peer.SendCultNet(new CultMeshVerseCatalogResponseMessage - { - MessageId = string.IsNullOrWhiteSpace(request.MessageId) ? Guid.NewGuid().ToString("N") : request.MessageId, - Verses = new[] { descriptor.ToMessage() } - }); - return Task.CompletedTask; - }); - server.OnCultNet(async (request, peer) => - { - try - { - var response = node.Database.Documents.CreateRawSnapshotResponse( - node.Cache, - string.IsNullOrWhiteSpace(request.MessageId) ? Guid.NewGuid().ToString("N") : request.MessageId, - request); - if (TryGetScopedEveSurfaceRequest(request, out var scopedRecordKey, out var scopedSurfaceKind)) - { - if (!response.Documents.Any(document => - string.Equals(document.RecordKey, scopedRecordKey, StringComparison.Ordinal))) - { - await InjectEveSurfaceSnapshotAsync( - node, options, request, response, scopedRecordKey, scopedSurfaceKind).ConfigureAwait(false); - } - peer.SendCultNet(response); - return; - } - var frame = latestFrame(); - var hasFrame = frame != null; - if (hasFrame && frame != null && SnapshotWants( - request, - AetheriaRuntimeDaemonSchemas.Frame, - "daemon:aetheria.frame.latest.v1")) - { - var framePut = node.Database.Documents.CreateRawDocumentPutMessage( - response.MessageId, - new CultRecordHandle( - new CultRecordKey("daemon:aetheria.frame.latest.v1")), - frame, - new CultNetDocumentMessageOptions - { - SourceRuntimeId = options.DaemonId, - SourceRole = "aetheria-daemon" - }); - response.Documents = response.Documents - .Where(document => !string.Equals(document.SchemaId, framePut.Document.SchemaId, StringComparison.Ordinal) || - !string.Equals(document.RecordKey, framePut.Document.RecordKey, StringComparison.Ordinal)) - .Concat(new[] { framePut.Document }) - .ToArray(); - } - - var health = await node.MutableDocument(AetheriaRuntimeVerseRecordKeys.DaemonHealth).ReadAsync().ConfigureAwait(false); - if (health != null && SnapshotWants( - request, - AetheriaRuntimeDaemonSchemas.Health, - AetheriaRuntimeVerseRecordKeys.DaemonHealth.ToString())) - { - var healthPut = node.Database.Documents.CreateRawDocumentPutMessage( - response.MessageId, - new CultRecordHandle( - AetheriaRuntimeVerseRecordKeys.DaemonHealth), - health, - new CultNetDocumentMessageOptions - { - SourceRuntimeId = options.DaemonId, - SourceRole = "aetheria-daemon" - }); - response.Documents = response.Documents - .Where(document => !string.Equals(document.SchemaId, healthPut.Document.SchemaId, StringComparison.Ordinal) || - !string.Equals(document.RecordKey, healthPut.Document.RecordKey, StringComparison.Ordinal)) - .Concat(new[] { healthPut.Document }) - .ToArray(); - } - - var authorityPolicy = await node.MutableDocument(AetheriaRuntimeVerseRecordKeys.VerseAuthorityPolicy).ReadAsync().ConfigureAwait(false); - if (authorityPolicy != null && SnapshotWants( - request, - AetheriaRuntimeDaemonSchemas.VerseAuthorityPolicy, - AetheriaRuntimeVerseAuthorityPolicyDocument.DocumentKey)) - { - var authorityPut = node.Database.Documents.CreateRawDocumentPutMessage( - response.MessageId, - new CultRecordHandle( - new CultRecordKey(AetheriaRuntimeVerseAuthorityPolicyDocument.DocumentKey)), - authorityPolicy, - new CultNetDocumentMessageOptions - { - SourceRuntimeId = options.DaemonId, - SourceRole = "aetheria-daemon" - }); - response.Documents = response.Documents - .Where(document => !string.Equals(document.SchemaId, authorityPut.Document.SchemaId, StringComparison.Ordinal) || - !string.Equals(document.RecordKey, authorityPut.Document.RecordKey, StringComparison.Ordinal)) - .Concat(new[] { authorityPut.Document }) - .ToArray(); - } - - var starbridgeScenario = await node.MutableDocument(AetheriaRuntimeVerseRecordKeys.StarbridgeScenarioLatest).ReadAsync().ConfigureAwait(false); - if (starbridgeScenario != null && SnapshotWants( - request, - AetheriaRuntimeDaemonSchemas.StarbridgeScenario, - AetheriaRuntimeVerseRecordKeys.StarbridgeScenarioLatest.ToString())) - { - var scenarioPut = node.Database.Documents.CreateRawDocumentPutMessage( - response.MessageId, - new CultRecordHandle( - AetheriaRuntimeVerseRecordKeys.StarbridgeScenarioLatest), - starbridgeScenario, - new CultNetDocumentMessageOptions - { - SourceRuntimeId = options.DaemonId, - SourceRole = "aetheria-daemon" - }); - response.Documents = response.Documents - .Where(document => !string.Equals(document.SchemaId, scenarioPut.Document.SchemaId, StringComparison.Ordinal) || - !string.Equals(document.RecordKey, scenarioPut.Document.RecordKey, StringComparison.Ordinal)) - .Concat(new[] { scenarioPut.Document }) - .ToArray(); - } - - await InjectEveSurfaceSnapshotAsync( - node, - options, - request, - response, - AetheriaRuntimeVerseRecordKeys.DaemonGameSurface.ToString(), - "game").ConfigureAwait(false); - await InjectEveSurfaceSnapshotAsync( - node, - options, - request, - response, - AetheriaRuntimeVerseRecordKeys.DaemonGameTuiSurface.ToString(), - "game-tui").ConfigureAwait(false); - await InjectEveSurfaceSnapshotAsync( - node, - options, - request, - response, - AetheriaRuntimeVerseRecordKeys.DaemonEditorSurface.ToString(), - "editor").ConfigureAwait(false); - await InjectEveSurfaceSnapshotAsync( - node, - options, - request, - response, - AetheriaRuntimeVerseRecordKeys.DaemonEditorTuiSurface.ToString(), - "editor-tui").ConfigureAwait(false); - await InjectEveSurfaceSnapshotAsync( - node, - options, - request, - response, - AetheriaRuntimeVerseRecordKeys.MainMenuSurface.ToString(), - "main-menu").ConfigureAwait(false); - await InjectEveSurfaceSnapshotAsync( - node, - options, - request, - response, - AetheriaRuntimeVerseRecordKeys.MainMenuSettingsSurface.ToString(), - "main-menu-settings").ConfigureAwait(false); - await InjectEveSurfaceSnapshotAsync( - node, - options, - request, - response, - AetheriaRuntimeVerseRecordKeys.MainMenuInputSettingsSurface.ToString(), - "main-menu-input-settings").ConfigureAwait(false); - await InjectEveSurfaceSnapshotAsync( - node, - options, - request, - response, - AetheriaRuntimeVerseRecordKeys.MainMenuPlayerSettingsSurface.ToString(), - "main-menu-player-settings").ConfigureAwait(false); - await InjectEveSurfaceSnapshotAsync( - node, - options, - request, - response, - AetheriaRuntimeVerseRecordKeys.MainMenuVerseSettingsSurface.ToString(), - "main-menu-verse-settings").ConfigureAwait(false); - await InjectEveSurfaceSnapshotAsync( - node, - options, - request, - response, - AetheriaRuntimeVerseRecordKeys.InventoryPanelSurface.ToString(), - "inventory-panel").ConfigureAwait(false); - await InjectEveSurfaceSnapshotAsync( - node, - options, - request, - response, - AetheriaRuntimeVerseRecordKeys.InventoryDropdownSurface.ToString(), - "inventory-dropdown").ConfigureAwait(false); - await InjectEveSurfaceSnapshotAsync( - node, - options, - request, - response, - AetheriaRuntimeVerseRecordKeys.MapMenuSurface.ToString(), - "map-menu").ConfigureAwait(false); - await InjectEveSurfaceSnapshotAsync( - node, - options, - request, - response, - AetheriaRuntimeVerseRecordKeys.TradeMenuSurface.ToString(), - "trade-menu").ConfigureAwait(false); - - var starbridgeSession = await node.MutableDocument(AetheriaRuntimeVerseRecordKeys.StarbridgeSessionSummary).ReadAsync().ConfigureAwait(false); - if (starbridgeSession != null && - SnapshotWants( - request, - AetheriaRuntimeDaemonSchemas.StarbridgeSessionSummary, - AetheriaRuntimeVerseRecordKeys.StarbridgeSessionSummary.ToString())) - { - var starbridgePut = node.Database.Documents.CreateRawDocumentPutMessage( - response.MessageId, - new CultRecordHandle( - AetheriaRuntimeVerseRecordKeys.StarbridgeSessionSummary), - starbridgeSession, - new CultNetDocumentMessageOptions - { - SourceRuntimeId = options.DaemonId, - SourceRole = "aetheria-daemon" - }); - response.Documents = response.Documents - .Where(document => !string.Equals(document.SchemaId, starbridgePut.Document.SchemaId, StringComparison.Ordinal) || - !string.Equals(document.RecordKey, starbridgePut.Document.RecordKey, StringComparison.Ordinal)) - .Concat(new[] { starbridgePut.Document }) - .ToArray(); - } - - var assetManifest = await node.MutableDocument(AetheriaRuntimeVerseRecordKeys.DaemonAssetManifest).ReadAsync().ConfigureAwait(false); - if (assetManifest != null && - SnapshotWants( - request, - AetheriaRuntimeDaemonSchemas.AssetManifest, - AetheriaRuntimeVerseRecordKeys.DaemonAssetManifest.ToString())) - { - var assetManifestPut = node.Database.Documents.CreateRawDocumentPutMessage( - response.MessageId, - new CultRecordHandle( - AetheriaRuntimeVerseRecordKeys.DaemonAssetManifest), - assetManifest, - new CultNetDocumentMessageOptions - { - SourceRuntimeId = options.DaemonId, - SourceRole = "aetheria-daemon" - }); - response.Documents = response.Documents - .Where(document => !string.Equals(document.SchemaId, assetManifestPut.Document.SchemaId, StringComparison.Ordinal) || - !string.Equals(document.RecordKey, assetManifestPut.Document.RecordKey, StringComparison.Ordinal)) - .Concat(new[] { assetManifestPut.Document }) - .ToArray(); - } - - var factPuts = node.Documents() - .OrderBy(fact => fact.CommittedAtUtc ?? "", StringComparer.Ordinal) - .ThenBy(fact => fact.FactId ?? "", StringComparer.Ordinal) - .Select(fact => new - { - Fact = fact, - RecordKey = AetheriaRuntimeCommittedCommandFactDocument.CreateRecordKey(fact.FactId) - }) - .Where(item => SnapshotWants( - request, - AetheriaRuntimeDaemonSchemas.CommittedCommandFact, - item.RecordKey)) - .Select(item => node.Database.Documents.CreateRawDocumentPutMessage( - response.MessageId, - new CultRecordHandle( - new CultRecordKey(item.RecordKey)), - item.Fact, - new CultNetDocumentMessageOptions - { - SourceRuntimeId = options.DaemonId, - SourceRole = "aetheria-daemon" - }).Document) - .ToArray(); - if (factPuts.Length > 0) - { - var factKeys = factPuts - .Select(document => document.RecordKey) - .ToHashSet(StringComparer.Ordinal); - response.Documents = response.Documents - .Where(document => !string.Equals(document.SchemaId, AetheriaRuntimeDaemonSchemas.CommittedCommandFact, StringComparison.Ordinal) || - !factKeys.Contains(document.RecordKey)) - .Concat(factPuts) - .ToArray(); - } - - if (hasFrame && frame != null && TryGetGameViewportRequest(request, out var viewportRecordKey, out var viewportSchemaId, out var viewport)) - { - if (string.Equals(viewportSchemaId, AetheriaRuntimeDaemonSchemas.ObjectsViewport, StringComparison.Ordinal)) - { - var viewportPut = node.Database.Documents.CreateRawDocumentPutMessage( - response.MessageId, - new CultRecordHandle( - new CultRecordKey(viewportRecordKey)), - AetheriaRuntimeGameDocuments.ObjectsViewport(frame, viewport), - new CultNetDocumentMessageOptions - { - SourceRuntimeId = options.DaemonId, - SourceRole = "aetheria-daemon" - }); - response.Documents = response.Documents - .Where(document => !string.Equals(document.SchemaId, viewportPut.Document.SchemaId, StringComparison.Ordinal) || - !string.Equals(document.RecordKey, viewportPut.Document.RecordKey, StringComparison.Ordinal)) - .Concat(new[] { viewportPut.Document }) - .ToArray(); - } - else if (string.Equals(viewportSchemaId, AetheriaRuntimeDaemonSchemas.GravityViewport, StringComparison.Ordinal)) - { - var viewportPut = node.Database.Documents.CreateRawDocumentPutMessage( - response.MessageId, - new CultRecordHandle( - new CultRecordKey(viewportRecordKey)), - AetheriaRuntimeGameDocuments.GravityViewport(frame, viewport), - new CultNetDocumentMessageOptions - { - SourceRuntimeId = options.DaemonId, - SourceRole = "aetheria-daemon" - }); - response.Documents = response.Documents - .Where(document => !string.Equals(document.SchemaId, viewportPut.Document.SchemaId, StringComparison.Ordinal) || - !string.Equals(document.RecordKey, viewportPut.Document.RecordKey, StringComparison.Ordinal)) - .Concat(new[] { viewportPut.Document }) - .ToArray(); - } - else if (string.Equals(viewportSchemaId, AetheriaRuntimeDaemonSchemas.RenderSplatsViewport, StringComparison.Ordinal)) - { - var viewportPut = node.Database.Documents.CreateRawDocumentPutMessage( - response.MessageId, - new CultRecordHandle( - new CultRecordKey(viewportRecordKey)), - AetheriaRuntimeGameDocuments.RenderSplatsViewport(frame, viewport), - new CultNetDocumentMessageOptions - { - SourceRuntimeId = options.DaemonId, - SourceRole = "aetheria-daemon" - }); - response.Documents = response.Documents - .Where(document => !string.Equals(document.SchemaId, viewportPut.Document.SchemaId, StringComparison.Ordinal) || - !string.Equals(document.RecordKey, viewportPut.Document.RecordKey, StringComparison.Ordinal)) - .Concat(new[] { viewportPut.Document }) - .ToArray(); - } - else - { - var viewportPut = node.Database.Documents.CreateRawDocumentPutMessage( - response.MessageId, - new CultRecordHandle( - new CultRecordKey(viewportRecordKey)), - AetheriaRuntimeGameDocuments.Viewport(frame, viewport), - new CultNetDocumentMessageOptions - { - SourceRuntimeId = options.DaemonId, - SourceRole = "aetheria-daemon" - }); - response.Documents = response.Documents - .Where(document => !string.Equals(document.SchemaId, viewportPut.Document.SchemaId, StringComparison.Ordinal) || - !string.Equals(document.RecordKey, viewportPut.Document.RecordKey, StringComparison.Ordinal)) - .Concat(new[] { viewportPut.Document }) - .ToArray(); - } - } - - if (hasFrame && frame != null && TryGetIndexedGameDocumentRequest(request, out var indexedRecordKey, out var indexedSchemaId, out var indexedEntityIndex)) - { - if (string.Equals(indexedSchemaId, AetheriaRuntimeDaemonSchemas.SelectedObject, StringComparison.Ordinal)) - { - var selectedObjectPut = node.Database.Documents.CreateRawDocumentPutMessage( - response.MessageId, - new CultRecordHandle( - new CultRecordKey(indexedRecordKey)), - AetheriaRuntimeGameDocuments.SelectedObject(frame, indexedEntityIndex), - new CultNetDocumentMessageOptions - { - SourceRuntimeId = options.DaemonId, - SourceRole = "aetheria-daemon" - }); - response.Documents = response.Documents - .Where(document => !string.Equals(document.SchemaId, selectedObjectPut.Document.SchemaId, StringComparison.Ordinal) || - !string.Equals(document.RecordKey, selectedObjectPut.Document.RecordKey, StringComparison.Ordinal)) - .Concat(new[] { selectedObjectPut.Document }) - .ToArray(); - } - else if (string.Equals(indexedSchemaId, AetheriaRuntimeDaemonSchemas.Inventory, StringComparison.Ordinal)) - { - var inventoryPut = node.Database.Documents.CreateRawDocumentPutMessage( - response.MessageId, - new CultRecordHandle( - new CultRecordKey(indexedRecordKey)), - AetheriaRuntimeGameDocuments.Inventory(frame, indexedEntityIndex), - new CultNetDocumentMessageOptions - { - SourceRuntimeId = options.DaemonId, - SourceRole = "aetheria-daemon" - }); - response.Documents = response.Documents - .Where(document => !string.Equals(document.SchemaId, inventoryPut.Document.SchemaId, StringComparison.Ordinal) || - !string.Equals(document.RecordKey, inventoryPut.Document.RecordKey, StringComparison.Ordinal)) - .Concat(new[] { inventoryPut.Document }) - .ToArray(); - } - } - - InjectCultMeshCdnManifestSnapshots( - node, - options.DaemonId, - bundleCdnManifests, - request, - response); - peer.SendCultNet(response); - } - catch (Exception ex) - { - peer.SendCultNet(new CultNetErrorMessage - { - Error = $"Aetheria snapshot response failed: {ex.GetType().Name}: {ex.Message}" - }); - } - }); - server.OnCultNet(async (message, _) => - { - if (string.Equals(message.Document.SchemaId, AetheriaRuntimeDaemonSchemas.Command, StringComparison.Ordinal)) - { - var command = MessagePackSerializer.Deserialize( - message.Document.Payload); - await node.SubmitDaemonCommandAsync(command).ConfigureAwait(false); - return; - } - - await node.Database.ApplyPutAsync(message).ConfigureAwait(false); - }); - return new AetheriaClientCultMeshHost( - server, - content, - realtime, - realtimeCertificate, - advertisedEndpoint, - advertisedContentEndpoint, - advertisedRealtimeEndpoint); -} - -static X509Certificate2 CreateRealtimeCertificate(string advertisedHost) -{ - using var key = RSA.Create(2048); - var request = new CertificateRequest( - $"CN={advertisedHost}", - key, - HashAlgorithmName.SHA256, - RSASignaturePadding.Pkcs1); - request.CertificateExtensions.Add(new X509BasicConstraintsExtension(false, false, 0, false)); - request.CertificateExtensions.Add(new X509KeyUsageExtension(X509KeyUsageFlags.DigitalSignature, false)); - request.CertificateExtensions.Add(new X509EnhancedKeyUsageExtension( - new OidCollection { new("1.3.6.1.5.5.7.3.1") }, - false)); - var names = new SubjectAlternativeNameBuilder(); - if (IPAddress.TryParse(advertisedHost, out var address)) names.AddIpAddress(address); - else names.AddDnsName(advertisedHost); - request.CertificateExtensions.Add(names.Build()); - request.CertificateExtensions.Add(new X509SubjectKeyIdentifierExtension(request.PublicKey, false)); - using var generated = request.CreateSelfSigned( - DateTimeOffset.UtcNow.AddMinutes(-1), - DateTimeOffset.UtcNow.AddDays(7)); - // System.Net.Quic uses Schannel on Windows; Schannel server credentials reject ephemeral private keys. - return X509CertificateLoader.LoadPkcs12( - generated.Export(X509ContentType.Pfx), - null, - X509KeyStorageFlags.Exportable); -} - -static X509Certificate2 LoadOrCreateRealtimeCertificate(string advertisedHost, string certificatePath) -{ - if (string.IsNullOrWhiteSpace(certificatePath)) - throw new ArgumentException("Realtime certificate path must be non-empty.", nameof(certificatePath)); - - if (File.Exists(certificatePath)) - { - try - { - var existing = X509CertificateLoader.LoadPkcs12FromFile( - certificatePath, - null, - X509KeyStorageFlags.DefaultKeySet); - if (existing.HasPrivateKey && - existing.NotAfter.ToUniversalTime() > DateTime.UtcNow.AddDays(1) && - string.Equals( - existing.GetNameInfo(X509NameType.DnsName, forIssuer: false), - advertisedHost, - StringComparison.OrdinalIgnoreCase)) - return existing; - existing.Dispose(); - } - catch (CryptographicException) - { - // Replace an unreadable or obsolete development certificate below. - } - } - - var generated = CreateRealtimeCertificate(advertisedHost); - Directory.CreateDirectory(Path.GetDirectoryName(certificatePath) ?? "."); - var temporaryPath = certificatePath + ".tmp-" + Guid.NewGuid().ToString("N"); - try - { - File.WriteAllBytes(temporaryPath, generated.Export(X509ContentType.Pfx)); - File.Move(temporaryPath, certificatePath, overwrite: true); - } - finally - { - if (File.Exists(temporaryPath)) File.Delete(temporaryPath); - } - return generated; -} - -static void InjectCultMeshCdnManifestSnapshots( - AetheriaStateNode node, - string daemonId, - IReadOnlyDictionary bundleCdnManifests, - CultNetSnapshotRequestMessage request, - CultNetSnapshotResponseRawMessage response) -{ - var recordKeys = request.RecordKeys ?? Array.Empty(); - if (recordKeys.Length == 0) - return; - - var documents = new List(); - foreach (var recordKey in recordKeys.Distinct(StringComparer.Ordinal)) - { - if (bundleCdnManifests.TryGetValue(recordKey, out var manifest)) - { - var bundleDocument = node.Database.Documents.CreateRawDocumentPutMessage( - $"aetheria-cdn:{recordKey}", - new CultRecordHandle(new CultRecordKey(recordKey)), - manifest, - new CultNetDocumentMessageOptions - { - SourceRuntimeId = daemonId, - SourceRole = "aetheria-cultmesh-cdn", - Tags = ["aetheria", "cultmesh-cdn", "asset-bundle"] - }).Document; - if (string.Equals(Environment.GetEnvironmentVariable("AETHERIA_TRACE_EVE_SNAPSHOTS"), "1", StringComparison.Ordinal)) - Console.WriteLine($"Eve CDN snapshot schema={bundleDocument.SchemaId} record={recordKey}"); - documents.Add(bundleDocument); - } - } - - if (documents.Count == 0) - return; - - var keys = documents.Select(document => document.RecordKey).ToHashSet(StringComparer.Ordinal); - response.Documents = response.Documents - .Where(document => !keys.Contains(document.RecordKey)) - .Concat(documents) - .ToArray(); -} - -static IReadOnlyDictionary BuildBundleCdnManifestIndex( - AetheriaUnityBundleArtifactSet unityBundles) -{ - var manifests = new Dictionary(StringComparer.Ordinal); - foreach (var bundle in unityBundles.Bundles) - { - var artifact = bundle.Artifact; - manifests[artifact.ManifestKey.ToString()] = artifact.Manifest; - } - return manifests; -} - -static async Task InjectEveSurfaceSnapshotAsync( - AetheriaStateNode node, - AetheriaDaemonHostOptions options, - CultNetSnapshotRequestMessage request, - CultNetSnapshotResponseRawMessage response, - string recordKey, - string surfaceKind) -{ - const string EveSurfaceSchema = "gamecult.eve.surface.v1"; - var trace = string.Equals(Environment.GetEnvironmentVariable("AETHERIA_TRACE_EVE_SNAPSHOTS"), "1", StringComparison.Ordinal); - if (!SnapshotWants(request, EveSurfaceSchema, recordKey)) - return; - - var surfaceState = await ReadEveSurfacePublicationAsync(node, surfaceKind).ConfigureAwait(false); - if (trace) - { - Console.WriteLine( - $"Eve snapshot request kind={surfaceKind} schemas=[{string.Join(",", request.SchemaIds ?? Array.Empty())}] " + - $"records=[{string.Join(",", request.RecordKeys ?? Array.Empty())}] surface={(surfaceState == null ? "missing" : surfaceState.Surface.Id)} " + - $"playerPosition={(surfaceState == null ? "missing" : FindControllablePosition(surfaceState.Surface.Root))}"); - } - if (surfaceState == null) - return; - - var surfacePut = node.Database.Documents.CreateRawDocumentPutMessage( - response.MessageId, - new CultRecordHandle(new CultRecordKey(recordKey)), - surfaceState, - new CultNetDocumentMessageOptions - { - SourceRuntimeId = options.DaemonId, - SourceRole = "aetheria-daemon" - }); - response.Documents = response.Documents - .Where(document => !string.Equals(document.RecordKey, surfacePut.Document.RecordKey, StringComparison.Ordinal)) - .Concat(new[] { surfacePut.Document }) - .ToArray(); - if (trace) - Console.WriteLine($"Eve snapshot response schema={surfacePut.Document.SchemaId} record={surfacePut.Document.RecordKey}"); -} - -static string FindControllablePosition(EveSurfaceComponent component) -{ - if (component.Props.TryGetValue("controllable", out var controllable) && - string.Equals(controllable, "true", StringComparison.OrdinalIgnoreCase)) - return component.GetProp("position", "missing"); - foreach (var child in component.Children) - { - var position = FindControllablePosition(child); - if (!string.Equals(position, "missing", StringComparison.Ordinal)) - return position; - } - return "missing"; -} - -static bool TryGetScopedEveSurfaceRequest( - CultNetSnapshotRequestMessage request, - out string recordKey, - out string surfaceKind) -{ - recordKey = ""; - surfaceKind = ""; - if (request.RecordKeys is not { Length: 1 }) - return false; - if (request.SchemaIds is { Length: > 0 } && - !request.SchemaIds.Contains(EveSurfaceDocument.SchemaId, StringComparer.Ordinal)) - return false; - - recordKey = request.RecordKeys[0]; - surfaceKind = recordKey switch - { - var value when value == AetheriaRuntimeVerseRecordKeys.DaemonGameSurface.ToString() => "game", - var value when value == AetheriaRuntimeVerseRecordKeys.DaemonGameTuiSurface.ToString() => "game-tui", - var value when value == AetheriaRuntimeVerseRecordKeys.DaemonEditorSurface.ToString() => "editor", - var value when value == AetheriaRuntimeVerseRecordKeys.DaemonEditorTuiSurface.ToString() => "editor-tui", - var value when value == AetheriaRuntimeVerseRecordKeys.MainMenuSurface.ToString() => "main-menu", - var value when value == AetheriaRuntimeVerseRecordKeys.MainMenuSettingsSurface.ToString() => "main-menu-settings", - var value when value == AetheriaRuntimeVerseRecordKeys.MainMenuInputSettingsSurface.ToString() => "main-menu-input-settings", - var value when value == AetheriaRuntimeVerseRecordKeys.MainMenuPlayerSettingsSurface.ToString() => "main-menu-player-settings", - var value when value == AetheriaRuntimeVerseRecordKeys.MainMenuVerseSettingsSurface.ToString() => "main-menu-verse-settings", - var value when value == AetheriaRuntimeVerseRecordKeys.InventoryPanelSurface.ToString() => "inventory-panel", - var value when value == AetheriaRuntimeVerseRecordKeys.InventoryDropdownSurface.ToString() => "inventory-dropdown", - var value when value == AetheriaRuntimeVerseRecordKeys.MapMenuSurface.ToString() => "map-menu", - var value when value == AetheriaRuntimeVerseRecordKeys.TradeMenuSurface.ToString() => "trade-menu", - _ => "" - }; - return surfaceKind.Length > 0; -} - -static Task ReadEveSurfacePublicationAsync(AetheriaStateNode node, string surfaceKind) -{ - var key = surfaceKind switch - { - "game" => AetheriaRuntimeVerseRecordKeys.DaemonGameSurface, - "game-tui" => AetheriaRuntimeVerseRecordKeys.DaemonGameTuiSurface, - "editor" => AetheriaRuntimeVerseRecordKeys.DaemonEditorSurface, - "editor-tui" => AetheriaRuntimeVerseRecordKeys.DaemonEditorTuiSurface, - "main-menu" => AetheriaRuntimeVerseRecordKeys.MainMenuSurface, - "main-menu-settings" => AetheriaRuntimeVerseRecordKeys.MainMenuSettingsSurface, - "main-menu-input-settings" => AetheriaRuntimeVerseRecordKeys.MainMenuInputSettingsSurface, - "main-menu-player-settings" => AetheriaRuntimeVerseRecordKeys.MainMenuPlayerSettingsSurface, - "main-menu-verse-settings" => AetheriaRuntimeVerseRecordKeys.MainMenuVerseSettingsSurface, - "inventory-panel" => AetheriaRuntimeVerseRecordKeys.InventoryPanelSurface, - "inventory-dropdown" => AetheriaRuntimeVerseRecordKeys.InventoryDropdownSurface, - "map-menu" => AetheriaRuntimeVerseRecordKeys.MapMenuSurface, - "trade-menu" => AetheriaRuntimeVerseRecordKeys.TradeMenuSurface, - _ => throw new ArgumentOutOfRangeException(nameof(surfaceKind), surfaceKind, "Unknown Eve surface publication.") - }; - return ReadPortableSurfaceAsync(node, key); -} - -static async Task ReadPortableSurfaceAsync(AetheriaStateNode node, CultRecordKey key) -{ - var portableSurface = await node.MutableDocument(key).ReadAsync().ConfigureAwait(false); - if (portableSurface != null) - return portableSurface; - var providerSurface = await node.MutableDocument(key).ReadAsync().ConfigureAwait(false); - return providerSurface == null ? null : AetheriaRuntimeSurfaceDocuments.ToEveSurfaceDocument(providerSurface); -} - -static IPAddress ParseBindAddress(string host) -{ - if (string.IsNullOrWhiteSpace(host) || - string.Equals(host, "localhost", StringComparison.OrdinalIgnoreCase)) - { - return IPAddress.Loopback; - } - - if (string.Equals(host, "*", StringComparison.Ordinal) || - string.Equals(host, "0.0.0.0", StringComparison.Ordinal) || - string.Equals(host, "any", StringComparison.OrdinalIgnoreCase)) - { - return IPAddress.Any; - } - - return IPAddress.Parse(host); -} - -static bool SnapshotWants( - CultNetSnapshotRequestMessage request, - string schemaId, - string recordKey) -{ - var schemaIds = request.SchemaIds ?? Array.Empty(); - var recordKeys = request.RecordKeys ?? Array.Empty(); - var schemaMatches = schemaIds.Length == 0 || schemaIds.Contains(schemaId, StringComparer.Ordinal); - var recordMatches = recordKeys.Length == 0 || recordKeys.Contains(recordKey, StringComparer.Ordinal); - return schemaMatches && recordMatches; -} - -static bool TryGetGameViewportRequest( - CultNetSnapshotRequestMessage request, - out string recordKey, - out string schemaId, - out AetheriaRuntimeViewportBounds viewport) -{ - const string prefix = "daemon:aetheria.game.viewport.v1;"; - recordKey = ""; - schemaId = ""; - viewport = new AetheriaRuntimeViewportBounds(); - - var schemaIds = request.SchemaIds ?? Array.Empty(); - var allowedSchemas = new[] - { - AetheriaRuntimeDaemonSchemas.GameViewport, - AetheriaRuntimeDaemonSchemas.ObjectsViewport, - AetheriaRuntimeDaemonSchemas.GravityViewport, - AetheriaRuntimeDaemonSchemas.RenderSplatsViewport - }; - if (schemaIds.Length > 0 && !schemaIds.Any(candidate => allowedSchemas.Contains(candidate, StringComparer.Ordinal))) - return false; - - foreach (var candidate in request.RecordKeys ?? Array.Empty()) - { - if (candidate.StartsWith(prefix, StringComparison.Ordinal)) - { - var values = candidate - .Substring(prefix.Length) - .Split(';', StringSplitOptions.RemoveEmptyEntries) - .Select(part => part.Split(new[] { '=' }, 2)) - .Where(parts => parts.Length == 2) - .ToDictionary(parts => parts[0], parts => parts[1], StringComparer.Ordinal); - if (!TryGetDouble(values, "minX", out var minX) || - !TryGetDouble(values, "minY", out var minY) || - !TryGetDouble(values, "maxX", out var maxX) || - !TryGetDouble(values, "maxY", out var maxY)) - { - continue; - } - - recordKey = candidate; - schemaId = schemaIds.FirstOrDefault(known => allowedSchemas.Contains(known, StringComparer.Ordinal)) - ?? AetheriaRuntimeDaemonSchemas.GameViewport; - viewport = new AetheriaRuntimeViewportBounds - { - MinX = Math.Min(minX, maxX), - MinY = Math.Min(minY, maxY), - MaxX = Math.Max(minX, maxX), - MaxY = Math.Max(minY, maxY) - }; - return true; - } - - if (TryGetManagedViewportRequest(candidate, out var managedSchemaId, out viewport)) - { - if (schemaIds.Length > 0 && !schemaIds.Contains(managedSchemaId, StringComparer.Ordinal)) - continue; - - recordKey = candidate; - schemaId = managedSchemaId; - return true; - } - } - - return false; -} - -static bool TryGetManagedViewportRequest( - string recordKey, - out string schemaId, - out AetheriaRuntimeViewportBounds viewport) -{ - schemaId = ""; - viewport = new AetheriaRuntimeViewportBounds(); - var prefix = ""; - if (recordKey.StartsWith("aetheria.viewport.map.", StringComparison.Ordinal)) - { - prefix = "aetheria.viewport.map."; - schemaId = AetheriaRuntimeDaemonSchemas.GameViewport; - } - else if (recordKey.StartsWith("aetheria.viewport.objects.", StringComparison.Ordinal)) - { - prefix = "aetheria.viewport.objects."; - schemaId = AetheriaRuntimeDaemonSchemas.ObjectsViewport; - } - else if (recordKey.StartsWith("aetheria.viewport.gravity.", StringComparison.Ordinal)) - { - prefix = "aetheria.viewport.gravity."; - schemaId = AetheriaRuntimeDaemonSchemas.GravityViewport; - } - else if (recordKey.StartsWith("aetheria.viewport.render_splats.", StringComparison.Ordinal)) - { - prefix = "aetheria.viewport.render_splats."; - schemaId = AetheriaRuntimeDaemonSchemas.RenderSplatsViewport; - } - else - { - return false; - } - - var parts = recordKey.Substring(prefix.Length).Split('.'); - if (parts.Length != 4 || - !TryParseViewportToken(parts[0], out var minX) || - !TryParseViewportToken(parts[1], out var minY) || - !TryParseViewportToken(parts[2], out var maxX) || - !TryParseViewportToken(parts[3], out var maxY)) - { - return false; - } - - viewport = new AetheriaRuntimeViewportBounds - { - MinX = Math.Min(minX, maxX), - MinY = Math.Min(minY, maxY), - MaxX = Math.Max(minX, maxX), - MaxY = Math.Max(minY, maxY) - }; - return true; -} - -static bool TryGetIndexedGameDocumentRequest( - CultNetSnapshotRequestMessage request, - out string recordKey, - out string schemaId, - out int entityIndex) -{ - recordKey = ""; - schemaId = ""; - entityIndex = -1; - var schemaIds = request.SchemaIds ?? Array.Empty(); - var allowedSchemas = new[] - { - AetheriaRuntimeDaemonSchemas.SelectedObject, - AetheriaRuntimeDaemonSchemas.Inventory - }; - if (schemaIds.Length > 0 && !schemaIds.Any(candidate => allowedSchemas.Contains(candidate, StringComparer.Ordinal))) - return false; - - foreach (var candidate in request.RecordKeys ?? Array.Empty()) - { - var candidateSchemaId = ""; - var prefix = ""; - if (candidate.StartsWith("aetheria.object.selected.", StringComparison.Ordinal)) - { - prefix = "aetheria.object.selected."; - candidateSchemaId = AetheriaRuntimeDaemonSchemas.SelectedObject; - } - else if (candidate.StartsWith("aetheria.inventory.", StringComparison.Ordinal)) - { - prefix = "aetheria.inventory."; - candidateSchemaId = AetheriaRuntimeDaemonSchemas.Inventory; - } - else - { - continue; - } - - if (schemaIds.Length > 0 && !schemaIds.Contains(candidateSchemaId, StringComparer.Ordinal)) - continue; - if (!int.TryParse(candidate.Substring(prefix.Length), NumberStyles.Integer, CultureInfo.InvariantCulture, out var parsedEntityIndex)) - continue; - - recordKey = candidate; - schemaId = candidateSchemaId; - entityIndex = parsedEntityIndex; - return true; - } - - return false; -} - -static bool TryParseViewportToken(string token, out double value) -{ - return double.TryParse( - (token ?? "").Replace('n', '-').Replace('p', '.'), - NumberStyles.Float, - CultureInfo.InvariantCulture, - out value); -} - -static bool TryGetDouble( - IReadOnlyDictionary values, - string key, - out double value) -{ - value = 0; - return values.TryGetValue(key, out var text) && - double.TryParse(text, NumberStyles.Float, CultureInfo.InvariantCulture, out value); -} - -static async Task PublishDemandedManagedViewportsAsync( - AetheriaStateNode node, - AetheriaDaemonHostOptions options, - AetheriaRuntimeDaemonFrameDocument frame, - AetheriaManagedViewportDemandState demand) -{ - foreach (var request in demand.Snapshot()) - { - if (!TryGetGameViewportRequest( - new CultNetSnapshotRequestMessage - { - RecordKeys = request.RecordKeys.ToArray(), - SchemaIds = request.SchemaIds.ToArray() - }, - out var recordKey, - out var schemaId, - out var viewport)) - { - continue; - } - - CultNetDocumentPutRawMessage put; - var messageId = $"aetheria-managed-viewport:{frame.FrameId}:{recordKey}"; - var messageOptions = new CultNetDocumentMessageOptions - { - SourceRuntimeId = options.DaemonId, - SourceRole = "aetheria-daemon" - }; - if (string.Equals(schemaId, AetheriaRuntimeDaemonSchemas.RenderSplatsViewport, StringComparison.Ordinal)) - { - put = node.Database.Documents.CreateRawDocumentPutMessage( - messageId, - new CultRecordHandle(new CultRecordKey(recordKey)), - AetheriaRuntimeGameDocuments.RenderSplatsViewport(frame, viewport), - messageOptions); - } - else if (string.Equals(schemaId, AetheriaRuntimeDaemonSchemas.GravityViewport, StringComparison.Ordinal)) - { - put = node.Database.Documents.CreateRawDocumentPutMessage( - messageId, - new CultRecordHandle(new CultRecordKey(recordKey)), - AetheriaRuntimeGameDocuments.GravityViewport(frame, viewport), - messageOptions); - } - else if (string.Equals(schemaId, AetheriaRuntimeDaemonSchemas.ObjectsViewport, StringComparison.Ordinal)) - { - put = node.Database.Documents.CreateRawDocumentPutMessage( - messageId, - new CultRecordHandle(new CultRecordKey(recordKey)), - AetheriaRuntimeGameDocuments.ObjectsViewport(frame, viewport), - messageOptions); - } - else - { - put = node.Database.Documents.CreateRawDocumentPutMessage( - messageId, - new CultRecordHandle(new CultRecordKey(recordKey)), - AetheriaRuntimeGameDocuments.Viewport(frame, viewport), - messageOptions); - } - - await node.Database.ApplyPutAsync(put).ConfigureAwait(false); - } -} - -static async Task PublishHotEntityStateAsync( - AetheriaStateNode node, - AetheriaRuntimeDaemonSoaFramePublisher publisher, - AetheriaHotEntityPublicationState state, - AetheriaRuntimeDaemonFrameDocument frame, - AetheriaRuntimeCatalogSnapshot? catalog, - AetheriaClientCultMeshHost clientHost) -{ - var hasRealtimeConsumers = clientHost.Realtime.ConnectionCount > 0; - using var hotFrame = publisher.BuildCurrentZoneEntities( - frame, - catalog, - realtimeDemand: hasRealtimeConsumers); - if (hotFrame == null) - return; - - var publication = await publisher.PublishAsync( - hotFrame, - includeRealtimePayload: hasRealtimeConsumers).ConfigureAwait(false); - var layoutChanged = !state.Matches(publication.View); - if (layoutChanged) - { - if (layoutChanged && string.Equals(Environment.GetEnvironmentVariable("AETHERIA_TRACE_CLIENT_TRANSPORT"), "1", StringComparison.Ordinal)) - Console.WriteLine( - $"CultMesh entity layout changed sequence={publication.View.Sequence} bytes={MessagePackSerializer.Serialize(publication.View).Length} " + - $"identities={publication.View.Identities.Length} representations=[{string.Join(",", publication.Body.Representations.Select(value => value.TransportKind))}]"); - await node.MutableDocument( - CultMeshBodyPublicationDocument.CreateLatestRecordKey(publication.Body.BodyId)) - .ReplaceAsync(publication.Body) - .ConfigureAwait(false); - } - if (layoutChanged) - { - await node.MutableDocument(AetheriaRuntimeVerseRecordKeys.EveEntitySoaViewLatest) - .ReplaceAsync(publication.View) - .ConfigureAwait(false); - state.Set(publication.View); - } - if (!publication.RealtimePayload.IsEmpty) - { - await clientHost.Realtime.BroadcastAsync(new CultMeshRealtimeFrame - { - ChannelId = "aetheria.entities", - SchemaId = AetheriaRuntimeDaemonSoaFramePublisher.BodySchemaId, - BodyId = AetheriaRuntimeDaemonSoaFramePublisher.BodyId, - ProducerEpoch = publication.Body.ProducerEpoch, - Sequence = publication.Body.Sequence, - Delivery = CultMeshRealtimeDelivery.LatestOnly, - Payload = publication.RealtimePayload - }).ConfigureAwait(false); - } -} - -static async Task PublishDurableDaemonDocumentsAsync( - AetheriaStateNode node, - AetheriaRuntimeDaemonTickResult result) -{ - await node.MutableDocument(AetheriaRuntimeVerseRecordKeys.DaemonFrameLatest) - .ReplaceAsync(result.Frame) - .ConfigureAwait(false); - await node.MutableDocument(AetheriaRuntimeVerseRecordKeys.ZoneRenderLatest) - .ReplaceAsync(AetheriaRuntimeGameDocuments.ZoneRender(result.Frame)) - .ConfigureAwait(false); - if (result.Health != null) - await node.MutableDocument(AetheriaRuntimeVerseRecordKeys.DaemonHealth) - .ReplaceAsync(result.Health) - .ConfigureAwait(false); - if (result.CommandBoundary != null) - await node.MutableDocument(AetheriaRuntimeVerseRecordKeys.DaemonCommandBoundary) - .ReplaceAsync(result.CommandBoundary) - .ConfigureAwait(false); - if (result.StarbridgeSessionSummary != null) - await node.MutableDocument(AetheriaRuntimeVerseRecordKeys.StarbridgeSessionSummary) - .ReplaceAsync(result.StarbridgeSessionSummary) - .ConfigureAwait(false); -} - -static async Task PublishClientGameplayDocumentsAsync( - AetheriaStateNode node, - AetheriaDaemonHostOptions options, - AetheriaUnityBundleArtifactSet unityBundles, - AetheriaRuntimeDaemonTickResult result, - AetheriaRuntimeCatalogSnapshot inputCatalog, - AetheriaReactiveSurfacePublicationState? reactiveSurfaceState, - bool publishTopology) -{ - var trace = publishTopology && string.Equals( - Environment.GetEnvironmentVariable("AETHERIA_TRACE_STARTUP_PHASES"), - "1", - StringComparison.Ordinal); - var phase = Stopwatch.StartNew(); - void TraceClientDocumentPhase(string name) - { - if (trace) - Console.WriteLine($"Aetheria client-bootstrap phase {name} took {phase.Elapsed.TotalMilliseconds:0.###}ms."); - phase.Restart(); - } - - if (publishTopology && result.ProviderAdvertisement != null) - await node.MutableDocument(AetheriaRuntimeVerseRecordKeys.DaemonProviderAdvertisement) - .ReplaceAsync(result.ProviderAdvertisement) - .ConfigureAwait(false); - if (publishTopology) - await node.MutableDocument(AetheriaRuntimeVerseRecordKeys.EveProviderAdvertisement) - .ReplaceAsync(BuildCoreProviderAdvertisement(options, result.Frame.PublishedAtUtc)) - .ConfigureAwait(false); - TraceClientDocumentPhase("provider-advertisements"); - if (publishTopology && result.AssetManifest != null) - { - await node.MutableDocument(AetheriaRuntimeVerseRecordKeys.DaemonAssetManifest) - .ReplaceAsync(result.AssetManifest) - .ConfigureAwait(false); - await node.MutableDocument(AetheriaRuntimeVerseRecordKeys.EveAssetCatalog) - .ReplaceAsync(BuildCoreAssetCatalog(unityBundles, result.AssetManifest)) - .ConfigureAwait(false); - } - TraceClientDocumentPhase("asset-catalog"); - var gameSession = await node.MutableDocument(AetheriaStateNode.GameSessionStateKey) - .ReadAsync().ConfigureAwait(false); - await node.MutableDocument(AetheriaRuntimeVerseRecordKeys.PilotInputCapability) - .ReplaceAsync(AetheriaRuntimeInputCapabilityDocument.FromFrame( - result.Frame, - string.Equals(gameSession?.Mode, AetheriaGameSessionState.TerminusMode, StringComparison.Ordinal), - inputCatalog).ToEveDocument()) - .ConfigureAwait(false); - TraceClientDocumentPhase("input-capability"); - var mainMenuState = await node.MutableDocument(AetheriaStateNode.MainMenuStateKey) - .ReadAsync() - .ConfigureAwait(false); - var hasActivePilot = !string.IsNullOrWhiteSpace(result.Frame.Run?.CurrentEntityKey) && - result.Frame.Run.Zones?.Any(zone => - zone != null && - zone.ZoneIndex == result.Frame.Run.CurrentZoneIndex && - (zone.Entities?.Count ?? 0) > 0) == true; - var activeMainMenuSurfaceId = string.IsNullOrWhiteSpace(mainMenuState?.ActiveSurfaceId) - ? (hasActivePilot ? "" : AetheriaRuntimeMainMenuCommands.RootSurfaceId) - : mainMenuState.ActiveSurfaceId; - var reactiveGameSurface = AetheriaRuntimeSurfaceDocuments.ToPortableSurface( - AetheriaRuntimeDaemonGameSurfaceBuilder.BuildReactiveGameplay( - result.Frame, - reactiveSurfaceState?.LastPublishedFrame ?? -1)); - if (reactiveSurfaceState?.Matches(reactiveGameSurface.Version) != true) - { - await node.MutableDocument(AetheriaRuntimeVerseRecordKeys.DaemonGameReactiveSurface) - .ReplaceAsync(reactiveGameSurface) - .ConfigureAwait(false); - reactiveSurfaceState?.Set(reactiveGameSurface.Version); - } - TraceClientDocumentPhase("reactive-surface"); - if (publishTopology) - { - var gameSurface = AetheriaRuntimeDaemonGameSurfaceBuilder.Build( - result.Frame, - result.Health ?? new AetheriaRuntimeDaemonHealthDocument(), - result.CommandBoundary ?? AetheriaRuntimeDaemonCommandBoundaryDocument.Create(options.DaemonId), - activeMainMenuSurfaceId, - inputCatalog); - var portableGameSurface = AetheriaRuntimeSurfaceDocuments.ToPortableSurface(gameSurface); - if (string.Equals(Environment.GetEnvironmentVariable("AETHERIA_TRACE_CLIENT_TRANSPORT"), "1", StringComparison.Ordinal)) - Console.WriteLine($"Eve game topology bytes={MessagePackSerializer.Serialize(portableGameSurface).Length}"); - await node.MutableDocument(AetheriaRuntimeVerseRecordKeys.DaemonGameSurface) - .ReplaceAsync(portableGameSurface) - .ConfigureAwait(false); - await node.MutableDocument(AetheriaRuntimeVerseRecordKeys.HangarSurface) - .ReplaceAsync(ReidentifySurface(portableGameSurface, AetheriaRuntimeHangarCommands.SurfaceId, "Terminus")) - .ConfigureAwait(false); - await PublishDaemonSectorMapSurfaceAsync(node, result.Frame, inputCatalog).ConfigureAwait(false); - } - TraceClientDocumentPhase("game-topology"); -} - -static async Task PublishSecondaryTopologyDocumentsAsync( - AetheriaStateNode node, - AetheriaDaemonHostOptions options, - AetheriaRuntimeDaemonTickResult result) -{ - var commanderSurface = AetheriaRuntimeDaemonGameSurfaceBuilder.BuildCommander( - result.Frame, - result.Health ?? new AetheriaRuntimeDaemonHealthDocument(), - result.CommandBoundary ?? AetheriaRuntimeDaemonCommandBoundaryDocument.Create(options.DaemonId), - result.StarbridgeSessionSummary); - await node.MutableDocument(AetheriaRuntimeVerseRecordKeys.StarbridgeCommanderSurface) - .ReplaceAsync(AetheriaRuntimeSurfaceDocuments.ToPortableSurface(commanderSurface)) - .ConfigureAwait(false); - if (result.GameTuiSurface != null) - await node.MutableDocument(AetheriaRuntimeVerseRecordKeys.DaemonGameTuiSurface) - .ReplaceAsync(AetheriaRuntimeSurfaceDocuments.ToPortableSurface(result.GameTuiSurface)) - .ConfigureAwait(false); - if (result.EditorSurface != null) - await node.MutableDocument(AetheriaRuntimeVerseRecordKeys.DaemonEditorSurface) - .ReplaceAsync(AetheriaRuntimeSurfaceDocuments.ToPortableSurface(result.EditorSurface)) - .ConfigureAwait(false); - if (result.EditorTuiSurface != null) - await node.MutableDocument(AetheriaRuntimeVerseRecordKeys.DaemonEditorTuiSurface) - .ReplaceAsync(AetheriaRuntimeSurfaceDocuments.ToPortableSurface(result.EditorTuiSurface)) - .ConfigureAwait(false); - - await PublishDaemonMenuSurfacesAsync(node, options, result.Frame).ConfigureAwait(false); -} - -static async Task PublishDaemonSectorMapSurfaceAsync( - AetheriaStateNode node, - AetheriaRuntimeDaemonFrameDocument frame, - AetheriaRuntimeCatalogSnapshot catalog) -{ - if (frame == null) - return; - - var updatedAtUtc = string.IsNullOrWhiteSpace(frame.PublishedAtUtc) - ? DateTimeOffset.UtcNow.ToString("O") - : frame.PublishedAtUtc; - var surface = AetheriaRuntimeSectorMapSurfaceBuilder.Build( - AetheriaRuntimeGameDocuments.SectorMap(frame), - updatedAtUtc, - frame.FrameId, - catalog); - await node.MutableDocument(AetheriaRuntimeVerseRecordKeys.MapMenuSurface) - .ReplaceAsync(AetheriaRuntimeSurfaceDocuments.ToPortableSurface(surface)) - .ConfigureAwait(false); -} - -static EveProviderAdvertisementDocument BuildCoreProviderAdvertisement( - AetheriaDaemonHostOptions options, - string updatedAtUtc) -{ - var interaction = new EveWorldInteractionAdvertisement( - "provider-authored-world-surface", - new[] { AetheriaRuntimeDaemonSchemas.Frame }, - "aetheria.daemon.commands", - AetheriaRuntimeVerseRecordKeys.EveCommandRecordPrefix, - EveCommandReceiptDocument.SchemaId, - AetheriaRuntimeVerseRecordKeys.EveReceiptRecordPrefix, - AetheriaRuntimeVerseRecordKeys.EveAssetCatalog.ToString(), - new[] { "unity-scene", "web-reference", "electron-shell", "tui" }, - "provider-owns-world-state-command-acceptance-and-receipts"); - var mapInteraction = new EveWorldInteractionAdvertisement( - "provider-authored-map-surface", - new[] { AetheriaRuntimeDaemonSchemas.SectorMap }, - "", - "", - "", - "", - AetheriaRuntimeVerseRecordKeys.EveAssetCatalog.ToString(), - new[] { "unity-scene", "web-reference", "electron-shell" }, - "provider-owns-topology-discovery-landmarks-influence-and-assets"); - return new EveProviderAdvertisementDocument( - AetheriaRuntimeProviderIdentity.ProviderId, - options.DaemonId, - options.VerseId, - "Aetheria Daemon", - "game.daemon", - options.CultMeshAddress, - updatedAtUtc, - new GameCult.Eve.Surface.EveProviderFreshness("fresh", updatedAtUtc, 5000), - new[] - { - EveSurfaceDocument.SchemaId, - EveSurfaceCommandRequest.SchemaId, - EveCommandReceiptDocument.SchemaId, - EveAssetCatalogDocument.SchemaId - }, - Array.Empty(), - new[] - { - new EveAdvertisedSurface( - AetheriaRuntimeDaemonGameSurfaceBuilder.PilotSurfaceId, - EveSurfaceDocument.SchemaId, - AetheriaRuntimeVerseRecordKeys.DaemonGameSurface.ToString(), - "cultmesh-record", - "active", - "interactive-world", - interaction), - new EveAdvertisedSurface( - AetheriaRuntimeDaemonGameSurfaceBuilder.CommanderSurfaceId, - EveSurfaceDocument.SchemaId, - AetheriaRuntimeVerseRecordKeys.StarbridgeCommanderSurface.ToString(), - "cultmesh-record", - "active", - "interactive-world", - interaction), - new EveAdvertisedSurface( - AetheriaRuntimeSectorMapSurfaceBuilder.SurfaceId, - EveSurfaceDocument.SchemaId, - AetheriaRuntimeVerseRecordKeys.MapMenuSurface.ToString(), - "cultmesh-record", - "active", - "graph", - mapInteraction), - new EveAdvertisedSurface( - AetheriaRuntimeHangarCommands.SurfaceId, - EveSurfaceDocument.SchemaId, - AetheriaRuntimeVerseRecordKeys.HangarSurface.ToString(), - "cultmesh-record", - "active", - "interactive-world", - interaction) - }, - Array.Empty(), - new[] { AetheriaRuntimeDaemonSoaFramePublisher.ProducerId }); -} - -static async Task PublishDaemonMenuSurfacesAsync( - AetheriaStateNode node, - AetheriaDaemonHostOptions options, - AetheriaRuntimeDaemonFrameDocument frame) -{ - if (frame == null) - return; - - var updatedAtUtc = string.IsNullOrWhiteSpace(frame.PublishedAtUtc) - ? DateTimeOffset.UtcNow.ToString("O") - : frame.PublishedAtUtc; - var catalog = node.RuntimeCatalog().Latest(); - var assetManifest = AetheriaRuntimeAssets.ProjectManifest( - catalog, - frame.Run?.RunId ?? frame.SessionId, - "cultmesh://aetheria/assets"); - var loadoutTemplates = node.Cache.GetAll() - .Select(ToLoadoutTemplateSnapshot) - .ToArray(); - var playerSettings = await ReadRuntimePlayerSettingsAsync(node).ConfigureAwait(false); - var currentEntity = AetheriaRuntimeGameDocuments.CurrentEntity(frame); - var stationRefit = AetheriaRuntimeGameDocuments.StationRefit(frame, loadoutTemplates, catalog); - var dropdownRequest = new AetheriaRuntimeInventoryDropdownSurfaceRequest - { - CurrentView = "Current Entity", - DisplayedEntityKey = currentEntity.EntityKey, - DisplayedCargoEntityKey = currentEntity.EntityKey, - DisplayedCargoIndex = 0, - CanSaveLoadout = !string.IsNullOrWhiteSpace(currentEntity.EntityKey) - }; - var inventoryRequest = new AetheriaRuntimeInventoryPanelSurfaceRequest - { - ViewTitle = string.IsNullOrWhiteSpace(currentEntity.Entity?.DisplayName) - ? "Current Entity Inventory" - : currentEntity.Entity.DisplayName, - DisplayedEntityKey = currentEntity.EntityKey, - DisplayedEntityIndex = currentEntity.EntityIndex, - DisplayedCargoEntityKey = currentEntity.EntityKey, - DisplayedCargoEntityIndex = currentEntity.EntityIndex, - DisplayedCargoIndex = 0 - }; - var inventory = currentEntity.EntityIndex < 0 - ? new AetheriaRuntimeInventoryDocument() - : AetheriaRuntimeGameDocuments.Inventory(frame, currentEntity.EntityIndex); - - var verseHost = await EnsureVerseHostSettingsAsync(node, options, updatedAtUtc) - .ConfigureAwait(false); - var stateBoot = AetheriaRuntimeStateBoot.Inspect( - new DirectoryInfo(Path.GetDirectoryName(node.StatePath) ?? "."), - node.StatePath); - var mainMenu = AetheriaRuntimeMainMenuSurfaceBuilder.BuildRoot( - stateBoot, - frame, - AetheriaRuntimeVerseHostSettingsDocument.FromSnapshot(new AetheriaRuntimeVerseHostSettingsSnapshot( - verseHost.ServiceId, - verseHost.VerseId, - verseHost.RootVerse, - verseHost.CanonicalService, - verseHost.LocatedService, - verseHost.CultMeshAddress, - verseHost.Title, - verseHost.Visibility, - verseHost.LastUpdatedAtUtc)), - playerSettings, - canOpenRuntimeInputScreen: true, - inGame: true, - updatedAtUtc); - var mainMenuSettings = AetheriaRuntimeMainMenuSurfaceBuilder.BuildSettings(updatedAtUtc); - var mainMenuInputSettings = AetheriaRuntimeMainMenuSurfaceBuilder.BuildInputSettings( - stateBoot, - playerSettings, - canOpenRuntimeInputScreen: true, - inGame: true, - updatedAtUtc); - var mainMenuPlayerSettings = AetheriaRuntimeMainMenuSurfaceBuilder.BuildPlayerSettings( - playerSettings, - updatedAtUtc); - var mainMenuVerseSettings = AetheriaRuntimeMainMenuSurfaceBuilder.BuildVerseSettings( - AetheriaRuntimeClientTargetSurfaceBuilder.Build( - stateBoot, - AetheriaRuntimeVerseHostSettingsDocument.FromSnapshot(new AetheriaRuntimeVerseHostSettingsSnapshot( - verseHost.ServiceId, - verseHost.VerseId, - verseHost.RootVerse, - verseHost.CanonicalService, - verseHost.LocatedService, - verseHost.CultMeshAddress, - verseHost.Title, - verseHost.Visibility, - verseHost.LastUpdatedAtUtc)), - updatedAtUtc)); - var inventoryPanel = AetheriaRuntimeInventoryPanelSurfaceBuilder.BuildFromDocuments( - currentEntity, - stationRefit, - inventory, - catalog, - playerSettings, - inventoryRequest, - AetheriaRuntimeVerseRecordKeys.InventoryDropdownSurface.ToString(), - updatedAtUtc); - var inventoryDropdown = AetheriaRuntimeInventoryDropdownSurfaceBuilder.BuildFromDocuments( - stationRefit, - dropdownRequest, - updatedAtUtc); - var tradeMenu = BuildTradeMenuSurface(stationRefit, catalog, updatedAtUtc, frame.FrameId); - - await node.MutableDocument(AetheriaRuntimeVerseRecordKeys.MainMenuSurface) - .ReplaceAsync(AetheriaRuntimeSurfaceDocuments.ToPortableSurface(mainMenu)) - .ConfigureAwait(false); - await node.MutableDocument(AetheriaRuntimeVerseRecordKeys.MainMenuSettingsSurface) - .ReplaceAsync(AetheriaRuntimeSurfaceDocuments.ToPortableSurface(mainMenuSettings)) - .ConfigureAwait(false); - await node.MutableDocument(AetheriaRuntimeVerseRecordKeys.MainMenuInputSettingsSurface) - .ReplaceAsync(AetheriaRuntimeSurfaceDocuments.ToPortableSurface(mainMenuInputSettings)) - .ConfigureAwait(false); - await node.MutableDocument(AetheriaRuntimeVerseRecordKeys.MainMenuPlayerSettingsSurface) - .ReplaceAsync(AetheriaRuntimeSurfaceDocuments.ToPortableSurface(mainMenuPlayerSettings)) - .ConfigureAwait(false); - await node.MutableDocument(AetheriaRuntimeVerseRecordKeys.MainMenuVerseSettingsSurface) - .ReplaceAsync(AetheriaRuntimeSurfaceDocuments.ToPortableSurface(mainMenuVerseSettings)) - .ConfigureAwait(false); - await node.MutableDocument(AetheriaRuntimeVerseRecordKeys.InventoryPanelSurface) - .ReplaceAsync(AetheriaRuntimeSurfaceDocuments.ToPortableSurface(inventoryPanel)) - .ConfigureAwait(false); - await node.MutableDocument(AetheriaRuntimeVerseRecordKeys.InventoryDropdownSurface) - .ReplaceAsync(AetheriaRuntimeSurfaceDocuments.ToPortableSurface(inventoryDropdown)) - .ConfigureAwait(false); - await node.MutableDocument(AetheriaRuntimeVerseRecordKeys.TradeMenuSurface) - .ReplaceAsync(AetheriaRuntimeSurfaceDocuments.ToPortableSurface(tradeMenu)) - .ConfigureAwait(false); - await node.MutableDocument(AetheriaRuntimeVerseRecordKeys.DaemonAssetManifest) - .ReplaceAsync(assetManifest) - .ConfigureAwait(false); -} - -static async Task ReadRuntimePlayerSettingsAsync(AetheriaStateNode node) -{ - var settings = await node.MutableDocument(AetheriaStateNode.PlayerSettingsKey) - .ReadAsync() - .ConfigureAwait(false); - settings ??= new AetheriaPlayerSettings(); - return new AetheriaRuntimePlayerSettingsDocument - { - PlayerName = settings.PlayerName ?? "", - TutorialPassed = settings.TutorialPassed, - TemperatureUnit = settings.Gameplay?.TemperatureUnit ?? "", - SignificantDigits = settings.Gameplay?.SignificantDigits ?? 3, - DefaultShutdownPerformance = settings.Gameplay?.DefaultShutdownPerformance ?? 0, - NebulaQuality = settings.Graphics?.NebulaQuality ?? "", - ShowAsteroidsInMinimap = settings.Graphics?.ShowAsteroidsInMinimap ?? false, - BindingOverrides = Array.Empty(), - ActionBarInputs = settings.Input?.ActionBarInputs ?? Array.Empty() - }; -} - -static AetheriaRuntimeSurfaceDocument BuildTradeMenuSurface( - AetheriaRuntimeStationRefitDocument stationRefit, - AetheriaRuntimeCatalogSnapshot catalog, - string updatedAtUtc, - long version) -{ - stationRefit ??= new AetheriaRuntimeStationRefitDocument(); - var rows = (stationRefit.StationStock ?? Array.Empty()) - .Take(12) - .Select((item, index) => - { - var typedItem = catalog?.FindItem(item.ItemKey); - return SurfaceLeaf( - $"aetheria.trade.menu.stock.{index}", - "row", - ("item", typedItem?.Name ?? item.ItemKey), - ("key", item.ItemKey ?? ""), - ("qty", item.Quantity.ToString(CultureInfo.InvariantCulture)), - ("price", item.Price.ToString("N0", CultureInfo.InvariantCulture)), - ("owned", item.OwnedQuantity.ToString(CultureInfo.InvariantCulture)), - ("afford", item.CanAfford ? "yes" : "no")); - }) - .DefaultIfEmpty(SurfaceLeaf( - "aetheria.trade.menu.stock.empty", - "text", - ("value", "No station stock is available in the current daemon frame."))) - .ToArray(); - - return new AetheriaRuntimeSurfaceDocument( - providerId: AetheriaRuntimeProviderIdentity.ProviderId, - providerKind: "trade.menu", - title: "Trade Menu", - version: version, - updatedAtUtc: updatedAtUtc ?? "", - surface: new AetheriaRuntimeSurfaceTree( - "aetheria.trade.menu", - SurfaceNode( - "aetheria.trade.menu.root", - "surface", - Array.Empty<(string Key, string Value)>(), - SurfaceNode( - "aetheria.trade.menu.summary", - "card", - new[] { ("title", "Station Trade") }, - SurfaceLeaf("aetheria.trade.menu.summary.docked", "metric", ("label", "Docked"), ("value", stationRefit.IsDocked ? "yes" : "no")), - SurfaceLeaf("aetheria.trade.menu.summary.station", "metric", ("label", "Station"), ("value", stationRefit.DockParent?.DisplayName ?? stationRefit.DockParentEntityKey ?? "")), - SurfaceLeaf("aetheria.trade.menu.summary.credits", "metric", ("label", "Credits"), ("value", stationRefit.Credits.ToString("N0", CultureInfo.InvariantCulture)))), - SurfaceNode( - "aetheria.trade.menu.stock", - "card", - new[] { ("title", "Station Stock") }, - rows)), - Array.Empty()), - commands: Array.Empty()); -} - -static AetheriaRuntimeSurfaceComponent SurfaceLeaf( - string id, - string kind, - params (string Key, string Value)[] props) -{ - return SurfaceNode(id, kind, props, Array.Empty()); -} - -static AetheriaRuntimeSurfaceComponent SurfaceNode( - string id, - string kind, - (string Key, string Value)[] props, - params AetheriaRuntimeSurfaceComponent[] children) -{ - return new AetheriaRuntimeSurfaceComponent( - id, - kind, - props.ToDictionary(prop => prop.Key, prop => prop.Value), - children); -} - -static async Task AcceptCoreEveInvocationsAsync( - AetheriaStateNode node, - AetheriaDaemonHostOptions options, - AetheriaRuntimeDaemonFrameDocument? currentFrame) -{ - var activatedSession = false; - var accounted = new HashSet( - currentFrame?.AccountedCommandIds ?? Array.Empty(), - StringComparer.Ordinal); - var receipted = node.Documents() - .Where(receipt => !string.IsNullOrWhiteSpace(receipt.CommandId)) - .Select(receipt => receipt.CommandId) - .ToHashSet(StringComparer.Ordinal); - var submitted = node.Documents() - .Where(command => command != null && !string.IsNullOrWhiteSpace(command.CommandId)) - .Select(command => command.CommandId) - .ToHashSet(StringComparer.Ordinal); - foreach (var request in node.Documents() - .Where(request => request != null && !string.IsNullOrWhiteSpace(request.CommandId)) - .OrderBy(request => request.IssuedAt)) - { - if (accounted.Contains(request.CommandId) || receipted.Contains(request.CommandId) || submitted.Contains(request.CommandId)) - continue; - - if (string.Equals(request.SurfaceId, AetheriaRuntimeHangarCommands.SurfaceId, StringComparison.Ordinal)) - { - activatedSession |= await AcceptHangarInvocationAsync(node, options, request).ConfigureAwait(false); - receipted.Add(request.CommandId); - continue; - } - - if (AetheriaRuntimeDaemonOperationsClient.TryCreateSurfaceCommandDocument( - request, - currentFrame, - node.StatePath, - request.ClientId, - currentFrame?.SessionId ?? options.SessionId, - out var command) && command != null) - { - command.CommandId = request.CommandId; - command.ClientId = request.ClientId; - command.AuthorRuntimeId = request.ClientId; - await node.SubmitDaemonCommandAsync(command).ConfigureAwait(false); - submitted.Add(command.CommandId); - continue; - } - - var denied = new EveCommandReceiptDocument( - $"receipt:{request.CommandId}:denied", - request.CommandId, - request.Command, - "denied", - "Aetheria", - options.DaemonId, - request.ProviderId, - request.SurfaceId, - "Command is not advertised by the Aetheria daemon surface.", - DateTimeOffset.UtcNow.ToString("O"), - Math.Max(currentFrame?.FrameId ?? 0, 0)); - await node.Database.PutAsync(AetheriaRuntimeVerseRecordKeys.EveReceiptForCommand(denied.CommandId), denied) - .ConfigureAwait(false); - } - return activatedSession; -} - -static EveSurfaceDocument ReidentifySurface(EveSurfaceDocument source, string surfaceId, string title) => - new( - source.Type, - source.Schema, - source.ProviderId, - source.ProviderKind, - title, - source.Version, - source.UpdatedAtUtc, - new EveSurfaceTree(surfaceId, source.Surface.Root, source.Surface.Styles), - source.Commands); - -static async Task AcceptHangarInvocationAsync( - AetheriaStateNode node, - AetheriaDaemonHostOptions options, - EveSurfaceCommandRequest request) -{ - var now = DateTimeOffset.UtcNow.ToString("O"); - var command = request.Command ?? ""; - var accepted = false; - var diagnostic = ""; - var activatesSession = false; - try - { - var shipId = Payload(request, "shipId"); - var expectedRevision = PayloadLong(request, "expectedHangarRevision", -1); - switch (command) - { - case AetheriaRuntimeHangarCommands.SelectShip: - case AetheriaRuntimeHangarCommands.SelectTerminus: - case AetheriaRuntimeHangarCommands.EditLoadout: - accepted = true; - break; - case AetheriaRuntimeHangarCommands.SelectStarbridge: - case AetheriaRuntimeHangarCommands.SelectArena: - diagnostic = "This mode has no session bootstrap yet."; - break; - case AetheriaRuntimeHangarCommands.EquipItem: - { - var result = await AetheriaHangar.EquipAsync( - node, - shipId, - Payload(request, "itemKey"), - expectedRevision, - node.RuntimeCatalog().Latest(), - now).ConfigureAwait(false); - accepted = result.Accepted; - diagnostic = result.Diagnostic; - if (accepted) await PublishStateSurfacesAsync(node, options, now).ConfigureAwait(false); - break; - } - case AetheriaRuntimeHangarCommands.RemoveItem: - { - var result = await AetheriaHangar.RemoveAsync( - node, - shipId, - (int)PayloadLong(request, "equipmentIndex", -1), - expectedRevision, - now).ConfigureAwait(false); - accepted = result.Accepted; - diagnostic = result.Diagnostic; - if (accepted) await PublishStateSurfacesAsync(node, options, now).ConfigureAwait(false); - break; - } - case AetheriaRuntimeHangarCommands.Launch: - { - var receipt = await AetheriaDaemonHangarCoordinator.LaunchTerminusAsync( - node, - await node.RuntimeCatalogForGenerationAsync().ConfigureAwait(false), - request.CommandId, - shipId, - expectedRevision, - now).ConfigureAwait(false); - accepted = receipt.Accepted; - diagnostic = receipt.Diagnostic; - if (accepted) - { - await ActivateTerminusSessionAsync(node, options, request.CommandId, now).ConfigureAwait(false); - activatesSession = true; - } - break; - } - case AetheriaRuntimeHangarCommands.Continue: - { - accepted = await AetheriaDaemonHangarCoordinator.CanContinueTerminusAsync( - node, - shipId, - Payload(request, "deploymentId")).ConfigureAwait(false); - diagnostic = accepted ? "" : "No resumable Terminus deployment exists for this ship."; - if (accepted) - { - await ActivateTerminusSessionAsync(node, options, request.CommandId, now).ConfigureAwait(false); - activatesSession = true; - } - break; - } - default: - diagnostic = "Command is not advertised by the Hangar surface."; - break; - } - } - catch (Exception error) - { - accepted = false; - diagnostic = error.Message; - } - - var receiptDocument = new EveCommandReceiptDocument( - $"receipt:{request.CommandId}:{(accepted ? "applied" : "denied")}", - request.CommandId, - command, - accepted ? "applied" : "denied", - "Aetheria Hangar", - options.DaemonId, - request.ProviderId, - request.SurfaceId, - diagnostic, - now, - 0); - await node.Database.PutAsync(AetheriaRuntimeVerseRecordKeys.EveReceiptForCommand(request.CommandId), receiptDocument) - .ConfigureAwait(false); - await node.FlushAsync().ConfigureAwait(false); - return activatesSession; -} - -static async Task ActivateTerminusSessionAsync( - AetheriaStateNode node, - AetheriaDaemonHostOptions options, - string commandId, - string now) -{ - var run = await ReadRuntimeRunCheckpointAsync(node, options.RenderSettings).ConfigureAwait(false) - ?? throw new InvalidOperationException("Terminus activation requires a canonical run checkpoint."); - await node.MutableDocument(AetheriaStateNode.GameSessionStateKey) - .ReplaceAsync(new AetheriaGameSessionState - { - Mode = AetheriaGameSessionState.TerminusMode, - SessionId = options.SessionId, - RunId = run.RunId, - ControlledEntityKey = run.CurrentEntityKey, - EntrySurfaceId = AetheriaRuntimeHangarCommands.SurfaceId, - SimulationRate = 1, - EffectiveSimulationRate = 1, - LastStartCommandId = commandId, - UpdatedAtUtc = now - }).ConfigureAwait(false); -} - -static string Payload(EveSurfaceCommandRequest request, string key) => - request.PayloadFields.TryGetValue(key, out var value) ? value ?? "" : ""; - -static long PayloadLong(EveSurfaceCommandRequest request, string key, long fallback) => - long.TryParse(Payload(request, key), NumberStyles.Integer, CultureInfo.InvariantCulture, out var value) - ? value - : fallback; - -static async Task AcceptEveCommandsAsync( - AetheriaStateNode node, - AetheriaDaemonHostOptions options, - AetheriaDaemonIngressState ingressState) -{ - var commandCountBefore = node.Documents().Count; - if (commandCountBefore == ingressState.ObservedEveCommandCount) - return; - ingressState.ObservedEveCommandCount = commandCountBefore; - var now = DateTimeOffset.UtcNow.ToString("O"); - try - { - var existingStatus = await node.MutableDocument(AetheriaStateNode.EveCommandAcceptanceStatusKey).ReadAsync().ConfigureAwait(false); - var report = await AetheriaEveCommandBridge.AcceptObservedAsync( - node, - existingStatus?.AccountedCommandIds) - .ConfigureAwait(false); - await node.MutableDocument(AetheriaStateNode.EveCommandAcceptanceStatusKey) - .ReplaceAsync(new AetheriaEveCommandAcceptanceStatus - { - RuntimeId = options.DaemonId, - StatePath = node.StatePath, - LastPollAtUtc = now, - LastAcceptedAtUtc = report.AcceptedCommandIds.Length > 0 ? now : "", - ObservedBeforeAccept = commandCountBefore, - CommandsAccepted = report.AcceptedCommandIds.Length, - CommandsRejected = report.RejectedCommands, - AppliedCatalogRefreshes = report.AcceptedCatalogRefreshes, - AppliedOperationsRefreshes = report.AcceptedOperationsRefreshes, - AppliedPlayerSettingsCommands = report.AcceptedPlayerSettingsCommands, - AppliedInputSettingsCommands = report.AcceptedInputSettingsCommands, - AppliedLoadoutTemplateCommands = report.AcceptedLoadoutTemplateCommands, - AppliedVerseHostCommands = report.AcceptedVerseHostCommands, - AppliedMainMenuCommands = report.AcceptedMainMenuCommands, - AccountedCommandIds = report.AccountedCommandIds, - LastRejectedCommand = report.LastRejectedCommand, - LastRejectedReason = report.LastRejectedReason, - ConsecutiveFailures = 0, - Status = report.RejectedCommands > 0 ? "rejected" : "ok" - }) - .ConfigureAwait(false); - } - catch (Exception ex) - { - var existing = await node.MutableDocument(AetheriaStateNode.EveCommandAcceptanceStatusKey).ReadAsync().ConfigureAwait(false); - await node.MutableDocument(AetheriaStateNode.EveCommandAcceptanceStatusKey) - .ReplaceAsync(new AetheriaEveCommandAcceptanceStatus - { - RuntimeId = options.DaemonId, - StatePath = node.StatePath, - LastPollAtUtc = now, - LastAcceptedAtUtc = existing?.LastAcceptedAtUtc ?? "", - ObservedBeforeAccept = commandCountBefore, - AccountedCommandIds = existing?.AccountedCommandIds ?? [], - ConsecutiveFailures = (existing?.ConsecutiveFailures ?? 0) + 1, - LastError = ex.ToString(), - Status = "error" - }) - .ConfigureAwait(false); - throw; - } -} - -static async Task PublishStateSurfacesAsync( - AetheriaStateNode node, - AetheriaDaemonHostOptions options, - string updatedAtUtc, - bool publishHangar = true) -{ - var verseHost = await EnsureVerseHostSettingsAsync(node, options, updatedAtUtc).ConfigureAwait(false); - var eveStatus = await node.MutableDocument(AetheriaStateNode.EveCommandAcceptanceStatusKey).ReadAsync().ConfigureAwait(false); - var runtimeSession = await node.MutableDocument(AetheriaStateNode.RuntimeSessionKey(options.DaemonId)).ReadAsync().ConfigureAwait(false); - var playerSettings = await node.MutableDocument(AetheriaStateNode.PlayerSettingsKey).ReadAsync().ConfigureAwait(false) ?? new AetheriaPlayerSettings(); - var playerSettingsUpdatedAt = string.IsNullOrWhiteSpace(playerSettings.LastUpdatedAtUtc) - ? updatedAtUtc - : playerSettings.LastUpdatedAtUtc; - var hangar = await node.MutableDocument(AetheriaStateNode.HangarKey) - .ReadAsync().ConfigureAwait(false); - AetheriaLoadoutTemplate? hangarLoadout = null; - var selectedHangarShip = hangar?.Ships?.FirstOrDefault(); - if (!string.IsNullOrWhiteSpace(selectedHangarShip?.LoadoutTemplateKey)) - hangarLoadout = await node.MutableDocument(new(selectedHangarShip.LoadoutTemplateKey)) - .ReadAsync().ConfigureAwait(false); - - await node.MutableDocument(AetheriaStateNode.OperationsSurfaceKey) - .ReplaceAsync(AetheriaEveSurfaceDocuments.BuildOperationsSurface(eveStatus, verseHost, runtimeSession)) - .ConfigureAwait(false); - await node.MutableDocument(AetheriaStateNode.PlayerSettingsSurfaceKey) - .ReplaceAsync(AetheriaEveSurfaceDocuments.BuildPlayerSettingsSurface(playerSettings, playerSettingsUpdatedAt)) - .ConfigureAwait(false); - await node.MutableDocument(AetheriaStateNode.ProviderAdvertisementSurfaceKey) - .ReplaceAsync(AetheriaEveSurfaceDocuments.BuildProviderAdvertisement(verseHost, node.StatePath, updatedAtUtc)) - .ConfigureAwait(false); - if (publishHangar && hangar != null) - await node.MutableDocument(AetheriaRuntimeVerseRecordKeys.HangarSurface) - .ReplaceAsync(AetheriaRuntimeSurfaceDocuments.ToPortableSurface( - AetheriaRuntimeHangarSurfaceBuilder.Build( - hangar, - selectedHangarShip?.ShipId ?? "", - AetheriaGameModes.Terminus, - updatedAtUtc, - Math.Max(1, hangar.Revision), - hangarLoadout == null ? null : AetheriaRuntimeStateMapper.ToRuntimeLoadoutTemplate(hangarLoadout), - node.RuntimeCatalog().Latest()))) - .ConfigureAwait(false); - await node.FlushAsync().ConfigureAwait(false); -} - -static async Task PublishOdinSurfaceAnnouncementsAsync( - AetheriaStateNode node, - AetheriaDaemonHostOptions options, - string updatedAtUtc) -{ - if (!options.EnableOdinAnnouncements || string.IsNullOrWhiteSpace(options.OdinCultMeshUri)) - return; - - var surfaces = new[] - { - ("aetheria.operations", "Aetheria Operations", AetheriaStateNode.OperationsSurfaceKey), - ("aetheria.player_settings", "Aetheria Player Settings", AetheriaStateNode.PlayerSettingsSurfaceKey), - ("aetheria.daemon.game", "Aetheria Daemon Game", AetheriaRuntimeVerseRecordKeys.DaemonGameSurface), - ("aetheria.daemon.game.tui", "Aetheria Daemon Game TUI", AetheriaRuntimeVerseRecordKeys.DaemonGameTuiSurface), - ("aetheria.daemon.editor", "Aetheria Daemon Editor", AetheriaRuntimeVerseRecordKeys.DaemonEditorSurface), - ("aetheria.daemon.editor.tui", "Aetheria Daemon Editor TUI", AetheriaRuntimeVerseRecordKeys.DaemonEditorTuiSurface), - ("aetheria.main_menu.root", "Main Menu", AetheriaRuntimeVerseRecordKeys.MainMenuSurface), - ("aetheria.main_menu.settings", "Main Menu Settings", AetheriaRuntimeVerseRecordKeys.MainMenuSettingsSurface), - ("aetheria.main_menu.input_settings", "Main Menu Input Settings", AetheriaRuntimeVerseRecordKeys.MainMenuInputSettingsSurface), - ("aetheria.main_menu.player_settings", "Main Menu Player Settings", AetheriaRuntimeVerseRecordKeys.MainMenuPlayerSettingsSurface), - ("aetheria.main_menu.verse_settings", "Main Menu Verse Settings", AetheriaRuntimeVerseRecordKeys.MainMenuVerseSettingsSurface), - ("aetheria.inventory.panel", "Inventory Panel", AetheriaRuntimeVerseRecordKeys.InventoryPanelSurface), - ("aetheria.inventory.panel.dropdown", "Inventory Dropdown", AetheriaRuntimeVerseRecordKeys.InventoryDropdownSurface), - (AetheriaRuntimeSectorMapSurfaceBuilder.SurfaceId, "Sector Map", AetheriaRuntimeVerseRecordKeys.MapMenuSurface), - ("aetheria.trade.menu", "Trade Menu", AetheriaRuntimeVerseRecordKeys.TradeMenuSurface), - (AetheriaRuntimeHangarCommands.SurfaceId, "Aetheria Hangar", AetheriaRuntimeVerseRecordKeys.HangarSurface) - }; - - var documents = new List(); - var providerAdvertisement = await node.MutableDocument(AetheriaStateNode.ProviderAdvertisementSurfaceKey) - .ReadAsync() - .ConfigureAwait(false); - var assetManifest = await node.MutableDocument(AetheriaRuntimeVerseRecordKeys.DaemonAssetManifest) - .ReadAsync() - .ConfigureAwait(false); - if (assetManifest != null) - { - documents.Add(CreateOdinRawPut( - "gamecult.aetheria.asset_manifest.v1", - AetheriaRuntimeVerseRecordKeys.DaemonAssetManifest.ToString(), - assetManifest)); - } - foreach (var (providerId, title, recordKey) in surfaces) - { - var surface = await node.MutableDocument(recordKey) - .ReadAsync() - .ConfigureAwait(false); - if (surface?.Surface?.Root == null) - continue; - - if (providerAdvertisement != null) - { - documents.Add(CreateOdinRawPut( - EveProviderAdvertisementDocument.SchemaId, - providerId, - BuildOdinProviderAdvertisement(providerId, title, providerAdvertisement, updatedAtUtc))); - } - documents.Add(CreateOdinRawPut( - "gamecult.eve.surface_state.v1", - providerId, - BuildOdinSurfaceState(providerId, title, surface, updatedAtUtc, assetManifest))); - } - - if (documents.Count == 0) - return; - - try - { - await PublishOdinRawPutsAsync(options, options.OdinCultMeshUri, documents).ConfigureAwait(false); - } - catch (Exception ex) when ( - ex is IOException || - ex is SocketException || - ex is TimeoutException || - ex is InvalidOperationException) - { - Console.WriteLine($"Aetheria Odin announcement skipped for {options.OdinCultMeshUri}: {ex.GetType().Name}: {ex.Message}"); - } -} - -static EveAssetCatalogDocument BuildCoreAssetCatalog( - AetheriaUnityBundleArtifactSet unityBundles, - AetheriaRuntimeAssetManifestDocument source) -{ - var variants = unityBundles.Bundles; - - var assets = (source.Assets ?? Array.Empty()) - .Where(entry => entry?.Ref != null && - (entry.Ref.Metadata.TryGetValue("unityAssetPath", out _) || - entry.Ref.Metadata.TryGetValue("resourcesPath", out _))) - .Select(entry => - { - var bundleName = AetheriaRuntimeAssets.ResolveUnityBundleName(entry); - var entryVariants = variants - .Where(bundle => string.Equals(bundle.BundleName, bundleName, StringComparison.Ordinal)) - .Select(bundle => - { - var dependencyUris = bundle.Dependencies.Select(dependency => variants.Single(candidate => - string.Equals(candidate.Platform, bundle.Platform, StringComparison.Ordinal) && - string.Equals(candidate.BundleName, dependency, StringComparison.Ordinal)).Artifact.ManifestKey.Value) - .ToArray(); - var unityAssetPath = ResolveUnityBundleAssetKey(entry, bundle.Assets); - return new EveAssetVariant( - "unity-scene", - bundle.Platform, - "unity-assetbundle", - bundle.Artifact.ManifestKey.Value, - $"sha256:{bundle.Artifact.Manifest.ContentHash}", - bundle.Artifact.Manifest.SizeBytes, - unityAssetPath, - new Dictionary( - entry.Ref.Metadata - .Where(pair => pair.Key.StartsWith("unity.", StringComparison.Ordinal)) - .Append(new KeyValuePair("unity.bundleName", bundle.BundleName)) - .Append(new KeyValuePair("unity.bundleDependencyUris", string.Join(";", dependencyUris))) - .Append(new KeyValuePair("renderChannel.map.unityLayer", "14")), - StringComparer.Ordinal)); - }) - .ToArray(); - if (entryVariants.Length == 0) - throw new InvalidOperationException( - $"No built Unity bundle named '{bundleName}' contains advertised asset '{entry.Ref.AssetKey}'."); - return new EveAssetCatalogEntry( - entry.Ref.AssetKey, - entry.Ref.Kind, - entryVariants, - new Dictionary(StringComparer.Ordinal) - { - ["mimeType"] = entry.Ref.MimeType ?? "", - ["presentationRole"] = entry.Ref.Metadata.TryGetValue("presentationRole", out var role) ? role : "" - }); - }) - .OrderBy(entry => entry.AssetRef, StringComparer.Ordinal) - .ToArray(); - return new EveAssetCatalogDocument( - AetheriaRuntimeProviderIdentity.ProviderId, - AetheriaRuntimeVerseRecordKeys.EveAssetCatalog.ToString(), - AssetCatalogVersion(source.PublishedAtUtc), - source.PublishedAtUtc, - assets); -} - -static long AssetCatalogVersion(string publishedAtUtc) -{ - return DateTimeOffset.TryParse( - publishedAtUtc, - CultureInfo.InvariantCulture, - DateTimeStyles.RoundtripKind, - out var publishedAt) - ? publishedAt.UtcDateTime.Ticks - : DateTimeOffset.UtcNow.UtcDateTime.Ticks; -} - -static string ResolveUnityBundleAssetKey( - AetheriaRuntimeAssetManifestEntry entry, - IReadOnlyList bundleAssets) -{ - if (entry.Ref.Metadata.TryGetValue("bundleAssetPath", out var bundleAssetPath)) - return bundleAssetPath; - if (entry.Ref.Metadata.TryGetValue("unityAssetPath", out var explicitPath)) - return explicitPath; - if (!entry.Ref.Metadata.TryGetValue("resourcesPath", out var resourcesPath)) - throw new InvalidOperationException($"Asset '{entry.Ref.AssetKey}' has no Unity asset path."); - - var expected = $"Assets/Resources/{resourcesPath}".Replace('\\', '/'); - var resolved = bundleAssets.FirstOrDefault(path => string.Equals( - Path.ChangeExtension(path, null)?.Replace('\\', '/'), - expected, - StringComparison.OrdinalIgnoreCase)); - return resolved ?? throw new InvalidOperationException( - $"Bundle asset for '{entry.Ref.AssetKey}' was not found below '{expected}'."); -} - -static (IReadOnlyList Assets, IReadOnlyList Dependencies) ReadUnityBundleSidecar(string bundlePath) -{ - var manifestPath = bundlePath + ".manifest"; - if (!File.Exists(manifestPath)) - throw new FileNotFoundException("Unity bundle sidecar manifest is missing.", manifestPath); - var assets = new List(); - var dependencies = new List(); - var section = ""; - foreach (var line in File.ReadLines(manifestPath)) - { - var trimmed = line.Trim(); - if (trimmed == "Assets:") { section = "assets"; continue; } - if (trimmed.StartsWith("Dependencies:", StringComparison.Ordinal)) - { - section = "dependencies"; - if (trimmed == "Dependencies: []") section = ""; - continue; - } - if (!trimmed.StartsWith("- ", StringComparison.Ordinal)) continue; - if (section == "assets") assets.Add(trimmed.Substring(2)); - else if (section == "dependencies") - dependencies.Add(Path.GetFileName(trimmed.Substring(2).Replace('\\', '/'))); - } - return (assets, dependencies); -} - -static IReadOnlyList<(string Path, string Platform, string BundleName)> FindAssetBundles(AetheriaDaemonHostOptions options) -{ - return Directory.Exists(options.AssetBundleRoot) - ? Directory.GetFiles(options.AssetBundleRoot, "aetheria-*", SearchOption.AllDirectories) - .Where(path => !path.EndsWith(".manifest", StringComparison.OrdinalIgnoreCase)) - .OrderBy(path => path, StringComparer.Ordinal) - .Select(path => ( - path, - new DirectoryInfo(Path.GetDirectoryName(path)!).Name, - Path.GetFileName(path))) - .ToArray() - : Array.Empty<(string, string, string)>(); -} - -static AetheriaUnityBundleArtifactSet BuildUnityBundleArtifactSet(AetheriaDaemonHostOptions options) -{ - var bundles = FindAssetBundles(options) - .Select(bundle => - { - var sidecar = ReadUnityBundleSidecar(bundle.Path); - return new AetheriaUnityBundleArtifact( - bundle.Path, - bundle.Platform, - bundle.BundleName, - sidecar.Assets, - sidecar.Dependencies, - PackAssetBundle(bundle.Path, bundle.Platform, bundle.BundleName)); - }) - .ToArray(); - foreach (var bundle in bundles) - foreach (var dependency in bundle.Dependencies) - { - if (!bundles.Any(candidate => - string.Equals(candidate.Platform, bundle.Platform, StringComparison.Ordinal) && - string.Equals(candidate.BundleName, dependency, StringComparison.Ordinal))) - throw new InvalidOperationException( - $"Unity bundle '{bundle.BundleName}' depends on missing bundle '{dependency}' for {bundle.Platform}."); - } - return new AetheriaUnityBundleArtifactSet(bundles); -} - -static CultMeshCdnArtifact PackAssetBundle(string path, string platform, string bundleName) -{ - return CultMeshCdn.PackArtifact( - $"aetheria/bundles/{platform}/{bundleName}", - File.ReadAllBytes(path), - new CultMeshCdnPackOptions - { - Kind = CultMeshCdnArtifactKinds.Asset, - Version = "1", - MimeType = "application/vnd.unity.assetbundle", - Tags = ["aetheria", "unity-scene", platform, bundleName] - }); -} - -static CultNetDocumentPutRawMessage CreateOdinRawPut( - string schemaId, - string recordKey, - T payload) -{ - var now = DateTimeOffset.UtcNow.ToString("O"); - return new CultNetDocumentPutRawMessage - { - MessageId = $"aetheria-odin-put:{recordKey}:{Guid.NewGuid():N}", - Document = new CultNetRawDocumentRecord - { - SchemaId = schemaId, - RecordKey = recordKey, - StoredAt = now, - PayloadEncoding = "messagepack", - Payload = MessagePackSerializer.Serialize(payload), - SourceRuntimeId = "aetheria-daemon", - SourceRole = "aetheria-daemon", - Tags = ["aetheria", "eve", "odin"] - } - }; -} - -static EveProviderAdvertisementDocument BuildOdinProviderAdvertisement( - string providerId, - string title, - EveProviderAdvertisementDocument canonical, - string updatedAtUtc) -{ - var surfaces = canonical.Surfaces - .Where(surface => string.Equals(surface.SurfaceId, providerId, StringComparison.Ordinal)) - .ToArray(); - var commands = canonical.Commands - .Where(command => string.IsNullOrWhiteSpace(command.SurfaceId) || - string.Equals(command.SurfaceId, providerId, StringComparison.Ordinal)) - .ToArray(); - return new EveProviderAdvertisementDocument( - providerId, - canonical.ServiceId, - canonical.VerseId, - title, - canonical.Kind, - canonical.CultMeshAddress, - string.IsNullOrWhiteSpace(updatedAtUtc) ? canonical.UpdatedAtUtc : updatedAtUtc, - canonical.Freshness, - canonical.Schemas, - canonical.Witnesses, - surfaces, - commands); -} - -static object[] BuildOdinSurfaceState( - string providerId, - string title, - EveSurfaceDocument document, - string updatedAtUtc, - AetheriaRuntimeAssetManifestDocument? assetManifest) -{ - var surface = new Dictionary(StringComparer.Ordinal) - { - ["schema"] = "gamecult.eve.surface.v1", - ["id"] = document.Surface.Id, - ["title"] = string.IsNullOrWhiteSpace(document.Title) ? title : document.Title, - ["root"] = ToOdinSurfaceComponent(document.Surface.Root), - ["assetManifestDocumentId"] = AetheriaRuntimeVerseRecordKeys.DaemonAssetManifest.ToString(), - ["assetManifestSchemaId"] = AetheriaRuntimeDaemonSchemas.AssetManifest, - ["assets"] = ToOdinAssetRefs(assetManifest) - }; - return - [ - providerId, - title, - document.Version, - string.IsNullOrWhiteSpace(document.UpdatedAtUtc) - ? (string.IsNullOrWhiteSpace(updatedAtUtc) ? DateTimeOffset.UtcNow.ToString("O") : updatedAtUtc) - : document.UpdatedAtUtc, - surface - ]; -} - -static object[] ToOdinAssetRefs(AetheriaRuntimeAssetManifestDocument? assetManifest) -{ - return assetManifest?.Assets? - .Where(entry => !string.IsNullOrWhiteSpace(entry?.Ref?.AssetKey)) - .Select(entry => new Dictionary(StringComparer.Ordinal) - { - ["assetKey"] = entry.Ref.AssetKey, - ["kind"] = entry.Ref.Kind, - ["uri"] = entry.Ref.Uri, - ["transport"] = entry.Ref.Transport, - ["mimeType"] = entry.Ref.MimeType, - ["contentHash"] = entry.Ref.ContentHash, - ["tags"] = entry.Tags?.ToArray() ?? Array.Empty() - }) - .Cast() - .ToArray() - ?? Array.Empty(); -} - -static Dictionary ToOdinSurfaceComponent(EveSurfaceComponent component) -{ - var result = new Dictionary(StringComparer.Ordinal) - { - ["id"] = component.Id, - ["kind"] = component.Kind, - ["props"] = component.Props.ToDictionary(prop => prop.Key, prop => (object?)prop.Value, StringComparer.Ordinal), - ["children"] = component.Children.Select(ToOdinSurfaceComponent).ToArray() - }; - if (component.Layout.Count > 0) - { - result["layout"] = component.Layout.ToDictionary(prop => prop.Key, prop => (object?)prop.Value, StringComparer.Ordinal); - } - - if (component.Style.Count > 0) - { - result["style"] = component.Style.ToDictionary(prop => prop.Key, prop => (object?)prop.Value, StringComparer.Ordinal); - } - - if (component.EmbeddedDocuments.Count > 0) - { - result["embeddedDocuments"] = component.EmbeddedDocuments - .Select(document => new Dictionary(StringComparer.Ordinal) - { - ["slotId"] = document.SlotId, - ["documentId"] = document.DocumentId, - ["schemaId"] = document.SchemaId, - ["presentationKind"] = document.PresentationKind - }) - .ToArray(); - } - - return result; -} - -static async Task PublishOdinRawPutsAsync( - AetheriaDaemonHostOptions options, - string cultMeshUri, - IReadOnlyList documents) -{ - var endpoint = CultMesh.ResolveRudpEndpoint(cultMeshUri); - using var client = CultMesh.ConnectRudpClient( - $"{options.DaemonId}-odin-publisher", - 0x0d1d0002, - endpoint, - new CultMeshRudpClientOptions - { - ConnectPayload = System.Text.Encoding.UTF8.GetBytes("aetheria-odin-surface-announcer"), - ConnectTimeout = TimeSpan.FromMilliseconds(500), - PollInterval = TimeSpan.FromMilliseconds(5), - SocketOptions = new CultMeshRudpSocketOptions - { - BindHost = "0.0.0.0", - BindPort = 0, - TransportId = "aetheria-odin-surface-announcer", - MaxFragmentBytes = 1200, - MaxPendingReliablePackets = 512, - ResendDelayMs = 25 - } - }); - - foreach (var document in documents) - { - client.SendSchemaMessage(document); - var drainUntil = DateTimeOffset.UtcNow.AddMilliseconds(40); - while (DateTimeOffset.UtcNow < drainUntil) - { - _ = client.ReceiveOnce(); - client.PollResends(); - await Task.Delay(5).ConfigureAwait(false); - } - } - - var deadline = DateTimeOffset.UtcNow.AddMilliseconds(500); - while (DateTimeOffset.UtcNow < deadline) - { - _ = client.ReceiveOnce(); - client.PollResends(); - await Task.Delay(10).ConfigureAwait(false); - } -} - -static async Task PublishRuntimeSessionAsync( - AetheriaStateNode node, - AetheriaDaemonHostOptions options, - string startedAtUtc, - string status) -{ - var now = DateTimeOffset.UtcNow.ToString("O"); - await node.MutableDocument(AetheriaStateNode.RuntimeSessionKey(options.DaemonId)) - .ReplaceAsync(new AetheriaRuntimeSession - { - RuntimeId = options.DaemonId, - Role = "verse-daemon", - StartedAtUtc = startedAtUtc, - LastSeenAtUtc = now, - Status = status - }) - .ConfigureAwait(false); - await PublishStateSurfacesAsync( - node, - options, - now, - publishHangar: !string.Equals(status, "running", StringComparison.Ordinal) && - !string.Equals(status, "completed", StringComparison.Ordinal)).ConfigureAwait(false); -} - -static async Task EnsureWorldDocumentAsync(AetheriaStateNode node) -{ - var now = DateTimeOffset.UtcNow.ToString("O"); - var world = node.MutableDocument(AetheriaStateNode.WorldKey); - var existing = await world.ReadAsync().ConfigureAwait(false); - if (existing != null) - { - return; - } - - await world.ReplaceAsync(new AetheriaWorldState - { - Name = "Aetheria", - WorldId = "aetheria", - SchemaEpoch = 1, - CreatedAtUtc = now, - UpdatedAtUtc = now - }).ConfigureAwait(false); -} - -static async Task EnsureTradeValuePolicyAsync(AetheriaStateNode node, string now) -{ - var tradeValuePolicy = node.MutableDocument(AetheriaStateNode.TradeValuePolicyKey); - var existing = await tradeValuePolicy.ReadAsync().ConfigureAwait(false); - if (existing != null) - return false; - - await tradeValuePolicy.ReplaceAsync( - AetheriaRuntimeStateMapper.ToTradeValuePolicy( - AetheriaRuntimeTradeValueSettings.Default, - now)).ConfigureAwait(false); - return true; -} - -static async Task EnsurePlayableRunDocumentsAsync( - AetheriaStateNode node, - AetheriaDaemonHostOptions options, - string now, - AetheriaRuntimeDaemonFrameDocument? latestFrame) -{ - if (options.UseTerminusFixture) - { - await AetheriaDaemonZoneGenerator.WritePlayableRunAsync( - node, node.RuntimeCatalog().Latest(), now, options.TerminusScenario).ConfigureAwait(false); - return; - } - - if (HasPlayableRun(latestFrame?.Run)) - return; - - var settings = await node.MutableDocument(AetheriaStateNode.PlayerSettingsKey).ReadAsync().ConfigureAwait(false); - if (!string.IsNullOrWhiteSpace(settings?.ActiveRunKey)) - { - var existingRun = await ReadRuntimeRunCheckpointAsync(node, options.RenderSettings).ConfigureAwait(false); - if (HasPlayableRun(existingRun)) - return; - } - - await AetheriaDaemonRunFactory.WriteAsync( - node, - await node.RuntimeCatalogForGenerationAsync().ConfigureAwait(false), - now, - now).ConfigureAwait(false); -} - -static async Task EnsureGameSessionAsync( - AetheriaStateNode node, - AetheriaDaemonHostOptions options, - string now, - AetheriaRuntimeDaemonFrameDocument? latestFrame) -{ - var run = !options.UseTerminusFixture && HasPlayableRun(latestFrame?.Run) - ? latestFrame!.Run - : await ReadRuntimeRunCheckpointAsync(node, options.RenderSettings).ConfigureAwait(false) - ?? throw new InvalidDataException("Aetheria game session requires an authoritative frame or active bootstrap run."); - var expectedMode = string.IsNullOrWhiteSpace(run.GameMode) - ? run.IsTutorial ? AetheriaGameSessionState.AetheriaMode : AetheriaGameSessionState.TerminusMode - : run.GameMode; - var initialSimulationRate = options.UseTerminusFixture ? 0 : 1; - var existing = await node.MutableDocument(AetheriaStateNode.GameSessionStateKey) - .ReadAsync().ConfigureAwait(false); - if (existing != null && - string.Equals(existing.Mode, expectedMode, StringComparison.Ordinal) && - string.Equals(existing.RunId, run.RunId, StringComparison.Ordinal) && - string.Equals(existing.ControlledEntityKey, run.CurrentEntityKey, StringComparison.Ordinal) && - Math.Abs(existing.SimulationRate - initialSimulationRate) < 0.000001 && - Math.Abs((existing.EffectiveSimulationRate ?? existing.SimulationRate) - initialSimulationRate) < 0.000001) - return; - - await node.MutableDocument(AetheriaStateNode.GameSessionStateKey) - .ReplaceAsync(new AetheriaGameSessionState - { - Mode = expectedMode, - SessionId = options.SessionId, - RunId = run.RunId, - ControlledEntityKey = run.CurrentEntityKey, - EntrySurfaceId = AetheriaRuntimeDaemonGameSurfaceBuilder.PilotSurfaceId, - SimulationRate = initialSimulationRate, - EffectiveSimulationRate = initialSimulationRate, - UpdatedAtUtc = now - }).ConfigureAwait(false); - await node.FlushAsync().ConfigureAwait(false); -} - -static async Task EnsureVerseAuthorityPolicyAsync( - AetheriaStateNode node, - AetheriaDaemonHostOptions options) -{ - var existing = await node.MutableDocument(AetheriaRuntimeVerseRecordKeys.VerseAuthorityPolicy).ReadAsync().ConfigureAwait(false); - if (existing != null && string.Equals(existing.Schema, AetheriaRuntimeVerseAuthoritySchemas.Policy, StringComparison.Ordinal)) - return; - - var policy = AetheriaRuntimeVerseAuthorityPolicyDocument.TrustedCoop(options.VerseId, options.DaemonId); - await node.MutableDocument(AetheriaRuntimeVerseRecordKeys.VerseAuthorityPolicy) - .ReplaceAsync(policy) - .ConfigureAwait(false); -} - -static async Task ReadRuntimeRunCheckpointAsync( - AetheriaStateNode node, - AetheriaRuntimeDaemonRenderSettings renderSettings) -{ - var settings = await node.MutableDocument(AetheriaStateNode.PlayerSettingsKey).ReadAsync().ConfigureAwait(false); - if (string.IsNullOrWhiteSpace(settings?.ActiveRunKey)) - return null; - - var run = await ReadDurableBootstrapDocumentAsync( - node, - new CultRecordKey(settings.ActiveRunKey)).ConfigureAwait(false); - if (run == null) - return null; - - var catalog = node.RuntimeCatalog().Latest() ?? new AetheriaRuntimeCatalogSnapshot(); - var zones = new List(); - var zoneKeys = run.ZoneKeys ?? Array.Empty(); - for (var zoneIndex = 0; zoneIndex < zoneKeys.Length; zoneIndex++) - { - var zone = await ReadDurableBootstrapDocumentAsync( - node, - new CultRecordKey(zoneKeys[zoneIndex])).ConfigureAwait(false); - if (zone == null) - continue; - - zones.Add(await ToRuntimeZoneAsync(node, zone, zoneIndex, renderSettings, catalog).ConfigureAwait(false)); - } - - return new AetheriaRuntimeRunCheckpointCommit - { - RunId = run.RunId ?? "", - IsTutorial = run.IsTutorial, - GameMode = string.IsNullOrWhiteSpace(run.GameMode) - ? run.IsTutorial ? AetheriaGameSessionState.AetheriaMode : AetheriaGameSessionState.TerminusMode - : run.GameMode, - EntranceZoneIndex = run.EntranceZoneIndex, - ExitZoneIndex = run.ExitZoneIndex, - CurrentZoneIndex = run.CurrentZoneIndex, - DiscoveredZoneIndices = (run.DiscoveredZoneIndices ?? Array.Empty()).ToArray(), - Zones = zones, - FactionRelationships = (run.FactionRelationships ?? Array.Empty()) - .Select(relationship => new AetheriaRuntimeFactionRelationshipCommit - { - Relationship = relationship.Relationship ?? "", - Standing = relationship.Standing, - FactionKey = relationship.FactionKey ?? "" - }) - .ToArray(), - HomeZones = (run.HomeZones ?? Array.Empty()) - .Select(entry => new AetheriaRuntimeFactionZoneCommit - { - FactionIndex = entry.FactionIndex, - ZoneIndex = entry.ZoneIndex - }) - .ToArray(), - BossZones = (run.BossZones ?? Array.Empty()) - .Select(entry => new AetheriaRuntimeFactionZoneCommit - { - FactionIndex = entry.FactionIndex, - ZoneIndex = entry.ZoneIndex - }) - .ToArray(), - GenerationSeed = run.GenerationSeed, - CurrentEntityKey = run.CurrentEntityKey ?? "", - LifecyclePhase = string.IsNullOrWhiteSpace(run.LifecyclePhase) - ? AetheriaRuntimeRunLifecycle.Active - : run.LifecyclePhase, - TerminalReason = run.TerminalReason ?? "", - TerminalFrameId = run.TerminalFrameId, - AgentTasks = (run.AgentTasks ?? Array.Empty()) - .Select(task => new AetheriaRuntimeAgentTaskCommit - { - TaskId = task.TaskId ?? "", - CorporationKey = task.CorporationKey ?? "", - TaskType = task.TaskType ?? "", - Priority = task.Priority, - ZoneIndex = task.ZoneIndex, - Status = task.Status ?? AetheriaRuntimeAgentTaskStatuses.Queued, - AssignedEntityIndex = task.AssignedEntityIndex, - CompletionRadius = task.CompletionRadius, - TargetOrbitKeys = (task.TargetOrbitKeys ?? Array.Empty()).ToArray(), - CircuitIndex = task.CircuitIndex - }) - .ToArray(), - Credits = 1000000 - }; -} - -static async Task ToRuntimeZoneAsync( - AetheriaStateNode node, - AetheriaZoneState zone, - int zoneIndex, - AetheriaRuntimeDaemonRenderSettings renderSettings, - AetheriaRuntimeCatalogSnapshot catalog) -{ - var entityKeys = zone.EntityKeys ?? Array.Empty(); - var entityIndices = entityKeys - .Select((key, index) => new { key, index }) - .Where(pair => !string.IsNullOrWhiteSpace(pair.key)) - .ToDictionary(pair => pair.key, pair => pair.index, StringComparer.Ordinal); - var entities = new List(); - for (var entityIndex = 0; entityIndex < entityKeys.Length; entityIndex++) - { - var entity = await ReadDurableBootstrapDocumentAsync( - node, - new CultRecordKey(entityKeys[entityIndex])).ConfigureAwait(false); - if (entity != null) - entities.Add(ToRuntimeEntity(entityKeys[entityIndex], entity, entityIndex, entityIndices, catalog)); - } - - return new AetheriaRuntimeZoneSnapshotCommit - { - ZoneIndex = zoneIndex, - Name = zone.Name ?? "", - PositionX = zone.Position?.X ?? 0, - PositionY = zone.Position?.Y ?? 0, - AdjacentZoneIndices = (zone.AdjacentZoneIndices ?? Array.Empty()).ToArray(), - FactionIndices = (zone.FactionIndices ?? Array.Empty()).ToArray(), - OwnerFactionIndex = zone.OwnerFactionIndex, - Entities = entities, - Orbits = (zone.Orbits ?? Array.Empty()).Select(ToRuntimeOrbit).ToArray(), - Bodies = (zone.Bodies ?? Array.Empty()).Select(body => ToRuntimeBody(body, entityIndices, renderSettings)).ToArray(), - DroppedPickups = (zone.DroppedPickups ?? Array.Empty()).Select(ToRuntimePickup).ToArray(), - GravityTerrainRadius = zone.GravityTerrainRadius, - GravityTerrainDepth = zone.GravityTerrainDepth, - GravityTerrainDepthExponent = zone.GravityTerrainDepthExponent, - GravityTerrainBoundaryFog = zone.GravityTerrainBoundaryFog, - GravityTerrainWaveFrequency = zone.GravityTerrainWaveFrequency, - SimulationTimeSeconds = zone.SimulationTimeSeconds, - NextPickupIndex = Math.Max( - zone.NextPickupIndex, - (zone.DroppedPickups ?? Array.Empty()) - .Select(pickup => pickup?.PickupIndex ?? -1) - .DefaultIfEmpty(-1) - .Max() + 1) - }; -} - -static async Task ReadDurableBootstrapDocumentAsync( - AetheriaStateNode node, - CultRecordKey key) - where TDocument : class -{ - if (node.Cache.Get(key) == null) - { - await node.Cache.PullBackingStoreRecordsAsync(metadata => - string.Equals(metadata.Key, key.ToString(), StringComparison.Ordinal)).ConfigureAwait(false); - } - - return await node.MutableDocument(key).ReadAsync().ConfigureAwait(false); -} - -static AetheriaRuntimeEntitySnapshotCommit ToRuntimeEntity( - string entityKey, - AetheriaEntitySnapshot entity, - int entityIndex, - IReadOnlyDictionary entityIndices, - AetheriaRuntimeCatalogSnapshot catalog) -{ - var equipment = ToEntitySlotCommits(entity.Equipment); - var runtimeEntity = new AetheriaRuntimeEntitySnapshotCommit - { - EntityId = entityKey ?? "", - HomeEntityId = entity.HomeEntityKey ?? "", - AgentTaskCapabilities = (entity.AgentTaskCapabilities ?? Array.Empty()).ToArray(), - AssignedAgentTaskId = entity.AssignedAgentTaskId ?? "", - EntityIndex = entityIndex, - Name = entity.Name ?? "", - Kind = entity.Kind ?? "", - PositionX = entity.Position?.X ?? 0, - PositionY = entity.Position?.Y ?? 0, - PositionZ = entity.Position?.Z ?? 0, - DirectionX = entity.Direction?.X ?? 0, - DirectionY = entity.Direction?.Y ?? 1, - LookDirectionX = entity.LookDirection?.X ?? entity.Direction?.X ?? 0, - LookDirectionY = entity.LookDirection?.Y ?? entity.Direction?.Y ?? 1, - HelmStrafe = entity.HelmInput?.X ?? 0, - HelmForward = entity.HelmInput?.Y ?? 0, - OrbitKey = entity.OrbitKey ?? "", - SecurityLevel = entity.SecurityLevel, - SecurityRadius = entity.SecurityRadius, - FactionKey = entity.FactionKey ?? "", - HullItemKey = entity.HullItemKey ?? "", - Equipment = equipment, - CargoBays = ToEntitySlotCommits(entity.CargoBays), - DockingBays = ToEntitySlotCommits(entity.DockingBays), - ChildEntityIndices = (entity.ChildEntityKeys ?? Array.Empty()) - .Select(key => entityIndices.TryGetValue(key, out var index) ? index : -1) - .Where(index => index >= 0) - .ToArray(), - WeaponGroups = (entity.WeaponGroups ?? Array.Empty()) - .Select(group => (IReadOnlyList)(group.EquipmentIndices ?? Array.Empty()).ToArray()) - .ToArray(), - StatGrids = (entity.StatGrids ?? Array.Empty()) - .Select(grid => new AetheriaRuntimeEntityStatGridCommit - { - Name = grid.Name ?? "", - Width = grid.Width, - Height = grid.Height, - Values = (grid.Values ?? Array.Empty()).ToArray() - }) - .ToArray(), - VelocityX = entity.Velocity?.X ?? 0, - VelocityY = entity.Velocity?.Y ?? 0, - TargetEntityIndex = entityIndices.TryGetValue(entity.TargetEntityKey ?? "", out var targetIndex) ? targetIndex : -1, - IsActive = entity.IsActive, - HeatsinksEnabled = entity.HeatsinksEnabled, - OverrideShutdown = entity.OverrideShutdown, - TractorPower = entity.TractorPower, - Heatstroke = entity.Heatstroke, - Hypothermia = entity.Hypothermia, - ActiveConsumables = (entity.ActiveConsumables ?? Array.Empty()) - .Select(consumable => new AetheriaRuntimeActiveConsumableCommit - { - EffectId = consumable.EffectId ?? "", - ItemKey = consumable.ItemKey ?? "", - Quality = consumable.Quality, - RemainingDuration = consumable.RemainingDuration, - Duration = consumable.Duration, - BehaviorStates = (consumable.BehaviorStates ?? Array.Empty()) - .Select(state => new AetheriaRuntimeConsumableBehaviorStateCommit - { - BehaviorIndex = state.BehaviorIndex, - BehaviorKind = state.BehaviorKind ?? "", - ScalarState = state.ScalarState, - BehaviorId = state.BehaviorId ?? "" - }) - .ToArray() - }) - .ToArray(), - BehaviorProgress = (entity.BehaviorProgress ?? Array.Empty()) - .Select(progress => new AetheriaRuntimeBehaviorProgressCommit - { - OwnerKind = progress.OwnerKind ?? "", - OwnerIndex = progress.OwnerIndex, - BehaviorIndex = progress.BehaviorIndex, - BehaviorKind = progress.BehaviorKind ?? "", - Progress = progress.Progress - }) - .ToArray(), - WeaponStates = Array.Empty(), - BehaviorStates = AetheriaRuntimeBehaviorStateProjector.CreateEquipmentBehaviorStates(equipment, catalog), - CargoContents = ToCargoBayCommits(entity.CargoContents), - DockingBayContents = ToCargoBayCommits(entity.DockingBayContents), - DockingBayAssignments = (entity.DockingBayAssignments ?? Array.Empty()).ToArray(), - Visibility = entity.Visibility, - VisibilitySourceCount = entity.VisibilitySourceCount, - FogFieldEmitters = (entity.FogFieldEmitters ?? Array.Empty()) - .Select(emitter => new AetheriaRuntimeFogFieldEmitterCommit - { - Enabled = emitter.Enabled, - Radius = emitter.Radius, - Density = emitter.Density, - OffsetX = emitter.OffsetX, - OffsetZ = emitter.OffsetZ, - FalloffExponent = emitter.FalloffExponent - }) - .ToArray(), - Contacts = (entity.Contacts ?? Array.Empty()) - .Select(contact => ToRuntimeContact(contact, entityIndices)) - .Where(contact => contact.TargetEntityIndex >= 0) - .ToArray(), - LoadoutGeneration = entity.LoadoutGeneration == null ? null : new AetheriaRuntimeLoadoutGenerationReceiptCommit - { - Seed = entity.LoadoutGeneration.Seed, - SourceZoneIndex = entity.LoadoutGeneration.SourceZoneIndex, - AvailabilityFactionKey = entity.LoadoutGeneration.AvailabilityFactionKey ?? "", - PriceExponent = entity.LoadoutGeneration.PriceExponent, - Selections = (entity.LoadoutGeneration.Selections ?? Array.Empty()) - .Select(value => new AetheriaRuntimeLoadoutGenerationSelectionCommit - { - Role = value.Role ?? "", - ItemKey = value.ItemKey ?? "", - ManufacturerKey = value.ManufacturerKey ?? "", - Price = value.Price, - ManufacturerDistance = value.ManufacturerDistance, - Allegiance = value.Allegiance - }).ToArray() - } - }; - AetheriaRuntimeBehaviorStateProjector.EnsureEquipmentBehaviorStates(runtimeEntity, catalog); - return runtimeEntity; -} - -static AetheriaRuntimeEntityContactCommit ToRuntimeContact( - AetheriaEntityContactSnapshot contact, - IReadOnlyDictionary entityIndices) -{ - return new AetheriaRuntimeEntityContactCommit - { - TargetEntityIndex = entityIndices.TryGetValue(contact.TargetEntityKey ?? "", out var index) ? index : -1, - InfoGathered = contact.InfoGathered, - Hostile = contact.Hostile, - Visible = contact.Visible - }; -} - -static AetheriaRuntimeOrbitSnapshotCommit ToRuntimeOrbit(AetheriaOrbitSnapshot orbit) -{ - return new AetheriaRuntimeOrbitSnapshotCommit - { - OrbitKey = orbit.OrbitKey ?? "", - ParentOrbitKey = orbit.ParentOrbitKey ?? "", - Distance = orbit.Distance, - Phase = orbit.Phase, - Period = orbit.Period, - FixedPositionX = orbit.FixedPosition?.X ?? 0, - FixedPositionY = orbit.FixedPosition?.Y ?? 0 - }; -} - -static AetheriaRuntimeBodySnapshotCommit ToRuntimeBody( - AetheriaBodySnapshot body, - IReadOnlyDictionary entityIndices, - AetheriaRuntimeDaemonRenderSettings renderSettings) -{ - return new AetheriaRuntimeBodySnapshotCommit - { - BodyKey = body.BodyKey ?? "", - Kind = body.Kind ?? "", - Name = body.Name ?? "", - OrbitKey = body.OrbitKey ?? "", - Mass = body.Mass, - Resources = (body.Resources ?? Array.Empty()) - .Select(resource => new AetheriaRuntimeBodyResourceCommit - { - ItemKey = resource.ItemKey ?? "", - Amount = resource.Amount - }) - .ToArray(), - BodyRadiusMultiplier = body.BodyRadiusMultiplier, - GravityRadiusMultiplier = body.GravityRadiusMultiplier, - GravityDepthMultiplier = body.GravityDepthMultiplier, - GravityDepthExponent = body.GravityDepthExponent, - Asteroids = (body.Asteroids ?? Array.Empty()) - .Select(asteroid => new AetheriaRuntimeAsteroidCommit - { - Distance = asteroid.Distance, - Phase = asteroid.Phase, - Size = asteroid.Size, - RotationSpeed = asteroid.RotationSpeed, - Damage = asteroid.Damage, - RespawnTimer = asteroid.RespawnTimer, - MiningAccumulators = (asteroid.MiningAccumulators ?? Array.Empty()) - .Select(accumulator => new AetheriaRuntimeAsteroidMiningAccumulatorCommit - { - MinerEntityIndex = entityIndices.TryGetValue(accumulator.MinerEntityKey ?? "", out var index) ? index : -1, - Amount = accumulator.Amount - }) - .ToArray() - }) - .ToArray(), - GasGiantVisual = new AetheriaRuntimeGasGiantVisualCommit - { - FirstOffsetDomainRotationSpeed = body.GasGiantVisual?.FirstOffsetDomainRotationSpeed ?? 1, - FirstOffsetRotationSpeed = body.GasGiantVisual?.FirstOffsetRotationSpeed ?? 1, - SecondOffsetDomainRotationSpeed = body.GasGiantVisual?.SecondOffsetDomainRotationSpeed ?? 1, - SecondOffsetRotationSpeed = body.GasGiantVisual?.SecondOffsetRotationSpeed ?? 1, - AlbedoRotationSpeed = body.GasGiantVisual?.AlbedoRotationSpeed ?? 1, - WaveRadiusMultiplier = body.GasGiantVisual?.WaveRadiusMultiplier ?? 1, - WaveDepthMultiplier = body.GasGiantVisual?.WaveDepthMultiplier ?? 1, - WaveDepthExponent = body.GasGiantVisual?.WaveDepthExponent ?? 8, - WaveSpeedMultiplier = body.GasGiantVisual?.WaveSpeedMultiplier ?? 8, - MaterialOverrides = (body.GasGiantVisual?.MaterialOverrides ?? Array.Empty()).ToArray(), - Colors = (body.GasGiantVisual?.Colors ?? Array.Empty()) - .Select(color => new AetheriaRuntimeColorCommit { X = color.X, Y = color.Y, Z = color.Z, W = color.W }) - .ToArray() - }, - SunVisual = new AetheriaRuntimeSunVisualCommit - { - LightColorX = body.SunVisual?.LightColor?.X ?? 0, - LightColorY = body.SunVisual?.LightColor?.Y ?? 0, - LightColorZ = body.SunVisual?.LightColor?.Z ?? 0, - FogTintColorX = body.SunVisual?.FogTintColor?.X ?? 0, - FogTintColorY = body.SunVisual?.FogTintColor?.Y ?? 0, - FogTintColorZ = body.SunVisual?.FogTintColor?.Z ?? 0, - LightRadiusMultiplier = body.SunVisual?.LightRadiusMultiplier ?? 1 - }, - GravityInfluenceCenterX = body.GravityInfluenceCenterX, - GravityInfluenceCenterZ = body.GravityInfluenceCenterZ, - GravityInfluenceRadius = body.GravityInfluenceRadius, - GravityWellDepth = body.GravityWellDepth, - GravityWaveRadius = body.GravityWaveRadius, - GravityWaveDepth = body.GravityWaveDepth, - GravityWaveSpeed = body.GravityWaveSpeed, - GravityWaveFrequency = renderSettings.ResolveGravityWaveFrequency(body.Mass), - IconSize = renderSettings.ResolveBodyIconSize(body.Mass) - }; -} - -static void ApplyDaemonRenderSettings( - AetheriaRuntimeRunCheckpointCommit run, - AetheriaRuntimeDaemonRenderSettings renderSettings) -{ - foreach (var zone in run.Zones ?? Array.Empty()) - { - foreach (var body in zone.Bodies ?? Array.Empty()) - { - body.IconSize = renderSettings.ResolveBodyIconSize(body.Mass); - body.GravityWaveFrequency = renderSettings.ResolveGravityWaveFrequency(body.Mass); - } - } -} - -static AetheriaRuntimeDroppedPickupCommit ToRuntimePickup(AetheriaDroppedPickupSnapshot pickup) -{ - return new AetheriaRuntimeDroppedPickupCommit - { - PickupIndex = pickup.PickupIndex, - PositionX = pickup.Position?.X ?? 0, - PositionY = pickup.Position?.Y ?? 0, - PositionZ = pickup.Position?.Z ?? 0, - VelocityX = pickup.Velocity?.X ?? 0, - VelocityY = pickup.Velocity?.Y ?? 0, - VelocityZ = pickup.Velocity?.Z ?? 0, - Item = ToLoadoutItemCommit(pickup.Item) - }; -} - -static bool HasPlayableRun(AetheriaRuntimeRunCheckpointCommit? run) -{ - return (run?.Zones ?? Array.Empty()) - .Any(zone => (zone.Entities ?? Array.Empty()).Count > 0); -} - -static AetheriaRuntimeLoadoutItemSlotCommit[] ToEntitySlotCommits( - IReadOnlyList? slots) -{ - return (slots ?? Array.Empty()) - .Select(slot => new AetheriaRuntimeLoadoutItemSlotCommit - { - X = slot.Position?.X ?? 0, - Y = slot.Position?.Y ?? 0, - Rotation = slot.Rotation ?? "None", - Item = new AetheriaRuntimeLoadoutItemCommit - { - ItemKey = slot.ItemKey ?? "", - Quality = slot.Quality, - Durability = slot.Durability, - Quantity = slot.Quantity, - Enabled = slot.Enabled, - OverrideShutdown = slot.OverrideShutdown, - Temperature = slot.Temperature - } - }) - .ToArray(); -} - -static async Task EnsureVerseHostSettingsAsync( - AetheriaStateNode node, - AetheriaDaemonHostOptions options, - string now) -{ - var existing = await node.MutableDocument(AetheriaStateNode.VerseHostSettingsKey).ReadAsync().ConfigureAwait(false); - var normalized = AetheriaVerseHostSettingsNormalizer.Normalize(existing); - normalized.ServiceId = options.DaemonId; - normalized.VerseId = options.VerseId; - normalized.CultMeshAddress = options.CultMeshAddress; - - if (existing == null || - string.IsNullOrWhiteSpace(existing.LastUpdatedAtUtc) || - !AetheriaVerseHostSettingsNormalizer.Equivalent(existing, normalized)) - { - normalized.LastUpdatedAtUtc = now; - await node.MutableDocument(AetheriaStateNode.VerseHostSettingsKey) - .ReplaceAsync(normalized) - .ConfigureAwait(false); - } - - return normalized; -} - -static AetheriaRuntimeLoadoutTemplateCommit ToLoadoutTemplateCommit(AetheriaLoadoutTemplate template) -{ - return new AetheriaRuntimeLoadoutTemplateCommit - { - Name = template.Name ?? "", - OwnerPlayerKey = template.OwnerPlayerKey ?? "", - RootEntity = ToEntityLoadoutCommit(template.RootEntity) - }; -} - -static AetheriaRuntimeEntityLoadoutCommit ToEntityLoadoutCommit(AetheriaEntityLoadout? entity) -{ - entity ??= new AetheriaEntityLoadout(); - return new AetheriaRuntimeEntityLoadoutCommit - { - Name = entity.Name ?? "", - Kind = entity.Kind ?? "", - FactionKey = entity.FactionKey ?? "", - Hull = ToLoadoutItemCommit(entity.Hull), - Equipment = ToSlotCommits(entity.Equipment), - CargoBays = ToSlotCommits(entity.CargoBays), - DockingBays = ToSlotCommits(entity.DockingBays), - CargoContents = ToCargoBayCommits(entity.CargoContents), - DockingBayContents = ToCargoBayCommits(entity.DockingBayContents), - DockingBayAssignments = (entity.DockingBayAssignments ?? Array.Empty()).ToArray(), - WeaponGroups = (entity.WeaponGroups ?? Array.Empty>()) - .Select(group => (group ?? Array.Empty()).ToArray()) - .ToArray(), - Children = (entity.Children ?? Array.Empty()) - .Select(ToEntityLoadoutCommit) - .ToArray() - }; -} - -static AetheriaRuntimeLoadoutItemSlotCommit[] ToSlotCommits( - IReadOnlyList? slots) -{ - return (slots ?? Array.Empty()) - .Select(slot => new AetheriaRuntimeLoadoutItemSlotCommit - { - X = slot.Position?.X ?? 0, - Y = slot.Position?.Y ?? 0, - Rotation = slot.Rotation ?? "None", - Item = ToLoadoutItemCommit(slot.Item) - }) - .ToArray(); -} - -static AetheriaRuntimeCargoBayLoadoutCommit[] ToCargoBayCommits( - IReadOnlyList? cargoBays) -{ - return (cargoBays ?? Array.Empty()) - .Select(cargoBay => new AetheriaRuntimeCargoBayLoadoutCommit - { - Items = ToSlotCommits(cargoBay.Items) - }) - .ToArray(); -} - -static AetheriaRuntimeLoadoutItemCommit ToLoadoutItemCommit(AetheriaLoadoutItem? item) -{ - return new AetheriaRuntimeLoadoutItemCommit - { - ItemKey = item?.ItemKey ?? "", - Quality = item?.Quality ?? 0, - Durability = item?.Durability ?? 0, - Quantity = item?.Quantity ?? 0, - Enabled = item?.Enabled ?? false, - OverrideShutdown = item?.OverrideShutdown ?? false - }; -} - -static AetheriaRuntimeLoadoutTemplateSnapshot ToLoadoutTemplateSnapshot(AetheriaLoadoutTemplate template) -{ - return new AetheriaRuntimeLoadoutTemplateSnapshot( - template.Name ?? "", - template.OwnerPlayerKey ?? "", - ToEntityLoadoutSnapshot(template.RootEntity), - template.CreatedAtUtc ?? "", - template.UpdatedAtUtc ?? ""); -} - -static AetheriaRuntimeEntityLoadoutSnapshot ToEntityLoadoutSnapshot(AetheriaEntityLoadout? entity) -{ - entity ??= new AetheriaEntityLoadout(); - return new AetheriaRuntimeEntityLoadoutSnapshot( - entity.Name ?? "", - entity.Kind ?? "", - entity.FactionKey ?? "", - ToLoadoutItemSnapshot(entity.Hull), - ToSlotSnapshots(entity.Equipment), - ToSlotSnapshots(entity.CargoBays), - ToSlotSnapshots(entity.DockingBays), - ToCargoSnapshots(entity.CargoContents), - ToCargoSnapshots(entity.DockingBayContents), - (entity.DockingBayAssignments ?? []).ToArray(), - (entity.WeaponGroups ?? []).Select(group => (IReadOnlyList)group.ToArray()).ToArray(), - (entity.Children ?? []).Select(ToEntityLoadoutSnapshot).ToArray()); -} - -static AetheriaRuntimeLoadoutItemSnapshot ToLoadoutItemSnapshot(AetheriaLoadoutItem? item) -{ - item ??= new AetheriaLoadoutItem(); - return new AetheriaRuntimeLoadoutItemSnapshot( - item.ItemKey ?? "", - item.Quality, - item.Durability, - item.Quantity, - item.Enabled, - item.OverrideShutdown, - item.Temperature); -} - -static AetheriaRuntimeLoadoutItemSlotSnapshot[] ToSlotSnapshots(IEnumerable? slots) -{ - return (slots ?? []) - .Select(slot => new AetheriaRuntimeLoadoutItemSlotSnapshot( - slot.Position?.X ?? 0, - slot.Position?.Y ?? 0, - ToLoadoutItemSnapshot(slot.Item), - slot.Rotation ?? "None")) - .ToArray(); -} - -static AetheriaRuntimeCargoBayLoadoutSnapshot[] ToCargoSnapshots(IEnumerable? bays) -{ - return (bays ?? []) - .Select(bay => new AetheriaRuntimeCargoBayLoadoutSnapshot(ToSlotSnapshots(bay.Items))) - .ToArray(); -} - -internal sealed record AetheriaPreparedPublication( - AetheriaRuntimeDaemonTickResult Publication, - IReadOnlyList Physics, - AetheriaRuntimeCatalogSnapshot Catalog); - -internal sealed class AetheriaPlayableWorldDemandState -{ - private readonly ConcurrentDictionary _active = new(StringComparer.Ordinal); - - public bool IsActive => !_active.IsEmpty; - - public void Observe(CultNetDatabaseSubscriptionDemand demand) - { - var key = demand.ConsumerRuntimeId + "\u001f" + demand.SubscriptionId; - if (!RequestsPlayableWorld(demand)) - { - _active.TryRemove(key, out _); - return; - } - - _active[key] = 0; - } - - private static bool RequestsPlayableWorld(CultNetDatabaseSubscriptionDemand demand) => - demand.Active && - (demand.RecordKeys.Any(key => - string.Equals(key, AetheriaRuntimeVerseRecordKeys.DaemonFrameLatest.ToString(), StringComparison.Ordinal) || - string.Equals(key, AetheriaRuntimeVerseRecordKeys.DaemonGameSurface.ToString(), StringComparison.Ordinal) || - string.Equals(key, AetheriaRuntimeVerseRecordKeys.DaemonGameReactiveSurface.ToString(), StringComparison.Ordinal) || - string.Equals(key, AetheriaRuntimeVerseRecordKeys.HangarSurface.ToString(), StringComparison.Ordinal)) || - demand.SchemaIds.Contains(AetheriaRuntimeDaemonSchemas.Frame, StringComparer.Ordinal) || - demand.BodyIds.Contains(AetheriaRuntimeDaemonSoaFramePublisher.BodyId, StringComparer.Ordinal)); -} - -internal sealed class AetheriaManagedViewportDemandState -{ - private readonly ConcurrentDictionary _active = - new(StringComparer.Ordinal); - - public void Observe(CultNetDatabaseSubscriptionDemand demand) - { - var key = demand.ConsumerRuntimeId + "\u001f" + demand.SubscriptionId; - if (!demand.Active) - { - _active.TryRemove(key, out _); - return; - } - - if (demand.RecordKeys.Count == 0) - return; - _active[key] = new AetheriaManagedViewportDemand( - demand.RecordKeys.ToArray(), - demand.SchemaIds.ToArray()); - } - - public IReadOnlyList Snapshot() => - _active.Values - .GroupBy( - demand => string.Join("\u001f", demand.RecordKeys) + "\u001e" + string.Join("\u001f", demand.SchemaIds), - StringComparer.Ordinal) - .Select(group => group.First()) - .ToArray(); -} - -internal sealed class AetheriaManagedViewportDemand -{ - public AetheriaManagedViewportDemand( - IReadOnlyList recordKeys, - IReadOnlyList schemaIds) - { - RecordKeys = recordKeys; - SchemaIds = schemaIds; - } - - public IReadOnlyList RecordKeys { get; } - public IReadOnlyList SchemaIds { get; } -} - -internal sealed class AetheriaReactiveSurfacePublicationState -{ - private long? _version; - - public long LastPublishedFrame => _version ?? -1; - - public bool Matches(long version) => _version == version; - - public void Set(long version) => _version = version; -} - -internal sealed class AetheriaHotEntityPublicationState -{ - private EveEntitySoaViewDocument? _layout; - - public void Set(EveEntitySoaViewDocument layout) => _layout = layout; - - public bool Matches(EveEntitySoaViewDocument next) - { - var current = _layout; - if (current == null || current.ProviderId != next.ProviderId || - current.BodySchemaId != next.BodySchemaId || current.LayoutVersion != next.LayoutVersion || - current.ProducerEpoch != next.ProducerEpoch || current.Capacity != next.Capacity || - current.Buffers.Length != next.Buffers.Length || - current.Columns.Length != next.Columns.Length || current.DirtyRanges.Length != next.DirtyRanges.Length || - current.RenderGroups.Length != next.RenderGroups.Length || current.Identities.Length != next.Identities.Length) - return false; - - for (var i = 0; i < current.Buffers.Length; i++) - { - var a = current.Buffers[i]; - var b = next.Buffers[i]; - if (a.BufferId != b.BufferId || a.ByteOffset != b.ByteOffset || a.ByteLength != b.ByteLength) - return false; - } - for (var i = 0; i < current.Columns.Length; i++) - { - var a = current.Columns[i]; - var b = next.Columns[i]; - if (a.ColumnId != b.ColumnId || a.Semantic != b.Semantic || a.BufferId != b.BufferId || - a.ScalarType != b.ScalarType || a.ByteOffset != b.ByteOffset || - a.ElementStride != b.ElementStride || a.ElementCount != b.ElementCount || - a.Unit != b.Unit || a.CoordinateSpace != b.CoordinateSpace) - return false; - } - for (var i = 0; i < current.DirtyRanges.Length; i++) - { - var a = current.DirtyRanges[i]; - var b = next.DirtyRanges[i]; - if (a.ColumnId != b.ColumnId || a.StartIndex != b.StartIndex || a.Count != b.Count) - return false; - } - for (var i = 0; i < current.RenderGroups.Length; i++) - { - var a = current.RenderGroups[i]; - var b = next.RenderGroups[i]; - if (a.GroupId != b.GroupId || a.MeshAssetRef != b.MeshAssetRef || - a.MaterialAssetRef != b.MaterialAssetRef || a.SubMeshIndex != b.SubMeshIndex || - a.Layer != b.Layer || a.InstanceCount != b.InstanceCount || a.DefaultScale != b.DefaultScale || - a.BoundsCenterX != b.BoundsCenterX || a.BoundsCenterY != b.BoundsCenterY || - a.BoundsCenterZ != b.BoundsCenterZ || a.BoundsSizeX != b.BoundsSizeX || - a.BoundsSizeY != b.BoundsSizeY || a.BoundsSizeZ != b.BoundsSizeZ || - a.ShadowMode != b.ShadowMode || a.ReceiveShadows != b.ReceiveShadows || a.Lod != b.Lod) - return false; - } - for (var i = 0; i < current.Identities.Length; i++) - { - var a = current.Identities[i]; - var b = next.Identities[i]; - if (a.Index != b.Index || a.EntityId != b.EntityId || a.EntityKind != b.EntityKind || - a.Label != b.Label || a.Faction != b.Faction || a.Selectable != b.Selectable || - a.Controllable != b.Controllable || a.AssetRef != b.AssetRef) - return false; - } - return true; - } -} - -internal sealed class AetheriaDaemonIngressState -{ - public int ObservedEveCommandCount { get; set; } = -1; - public bool ControlPlaneInitialized { get; set; } - public string GameMode { get; set; } = ""; - public double RequestedSimulationRate { get; set; } - public double SimulationRate { get; set; } - public double SimulationStepAccumulator { get; set; } - public AetheriaRuntimeLoadoutTemplateCommit[] LoadoutTemplates { get; set; } = []; - public AetheriaRuntimeCatalogSnapshot? Catalog { get; set; } - public AetheriaRuntimeVerseAuthorityPolicyDocument? AuthorityPolicy { get; set; } - public AetheriaRuntimeStarbridgeScenarioDocument? StarbridgeScenario { get; set; } - public AetheriaRuntimeStarbridgeSessionDocument? StarbridgeSession { get; set; } - public AetheriaRuntimeAuthorityLeaseDocument[] AuthorityLeases { get; set; } = []; - - public int TakeTerminusSimulationSteps() - { - SimulationStepAccumulator += Math.Max(0, SimulationRate); - var steps = (int)Math.Floor(SimulationStepAccumulator); - SimulationStepAccumulator -= steps; - return steps; - } -} - -internal sealed class AetheriaUnityBundleArtifactSet -{ - private readonly IReadOnlyDictionary _chunks; - - public AetheriaUnityBundleArtifactSet(IReadOnlyList bundles) - { - Bundles = bundles ?? throw new ArgumentNullException(nameof(bundles)); - var chunks = new Dictionary(StringComparer.Ordinal); - foreach (var chunk in bundles.SelectMany(bundle => bundle.Artifact.Chunks)) - chunks.TryAdd(CultMeshCdnArtifactChunk.CreateRecordKey(chunk).ToString(), chunk); - _chunks = chunks; - } - - public IReadOnlyList Bundles { get; } - - public CultMeshCdnArtifactChunk? ResolveChunk(string hash) - { - var key = CultMeshCdnArtifactChunk.CreateRecordKey(hash).ToString(); - return _chunks.TryGetValue(key, out var chunk) ? chunk : null; - } -} - -internal sealed class AetheriaUnityBundleArtifact -{ - public AetheriaUnityBundleArtifact( - string path, - string platform, - string bundleName, - IReadOnlyList assets, - IReadOnlyList dependencies, - CultMeshCdnArtifact artifact) - { - Path = path; - Platform = platform; - BundleName = bundleName; - Assets = assets; - Dependencies = dependencies; - Artifact = artifact; - } - - public string Path { get; } - public string Platform { get; } - public string BundleName { get; } - public IReadOnlyList Assets { get; } - public IReadOnlyList Dependencies { get; } - public CultMeshCdnArtifact Artifact { get; } -} - -internal sealed class AetheriaClientCultMeshHost : IAsyncDisposable -{ - public AetheriaClientCultMeshHost( - TcpFramedCultNetSchemaServer control, - CultMeshTcpContentServer content, - CultMeshQuicRealtimeServer realtime, - X509Certificate2 realtimeCertificate, - string controlEndpoint, - string contentEndpoint, - string realtimeEndpoint) - { - Control = control ?? throw new ArgumentNullException(nameof(control)); - Content = content ?? throw new ArgumentNullException(nameof(content)); - Realtime = realtime ?? throw new ArgumentNullException(nameof(realtime)); - RealtimeCertificate = realtimeCertificate ?? throw new ArgumentNullException(nameof(realtimeCertificate)); - ControlEndpoint = controlEndpoint ?? throw new ArgumentNullException(nameof(controlEndpoint)); - ContentEndpoint = contentEndpoint ?? throw new ArgumentNullException(nameof(contentEndpoint)); - RealtimeEndpoint = realtimeEndpoint ?? throw new ArgumentNullException(nameof(realtimeEndpoint)); - } - - public TcpFramedCultNetSchemaServer Control { get; } - public CultMeshTcpContentServer Content { get; } - public CultMeshQuicRealtimeServer Realtime { get; } - private X509Certificate2 RealtimeCertificate { get; } - public string ControlEndpoint { get; } - public string ContentEndpoint { get; } - public string RealtimeEndpoint { get; } - - public async ValueTask DisposeAsync() - { - await Realtime.DisposeAsync().ConfigureAwait(false); - RealtimeCertificate.Dispose(); - Content.Dispose(); - Control.Dispose(); - } -} - -internal sealed class AetheriaDaemonHostOptions -{ - public string StatePath { get; init; } = ""; - public string DaemonId { get; init; } = "aetheria-daemon"; - public string SessionId { get; init; } = "local"; - public string VerseId { get; init; } = "aetheria.local"; - public string CultMeshAddress { get; init; } = "cultmesh://aetheria.local/eve/providers/aetheria.daemon"; - public string ClientCultMeshHost { get; init; } = "127.0.0.1"; - public string ClientCultMeshAdvertiseHost { get; init; } = "127.0.0.1"; - public int ClientCultMeshPort { get; init; } = 3076; - public int ClientCultMeshContentPort { get; init; } - public int ClientCultMeshQuicPort { get; init; } - public string ClientCultMeshCertificatePath { get; init; } = ""; - public string AetheriaResourcesRoot { get; init; } = ""; - public string AssetBundleRoot { get; init; } = ""; - public IReadOnlyList PeerCultMeshEndpoints { get; init; } = Array.Empty(); - public TimeSpan PeerSyncTimeout { get; init; } = TimeSpan.FromMilliseconds(250); - public string OdinCultMeshUri { get; init; } = ""; - public bool EnableOdinAnnouncements { get; init; } - public TimeSpan TickInterval { get; init; } = TimeSpan.FromMilliseconds(20); - public TimeSpan ApiPublicationInterval { get; init; } = TimeSpan.FromSeconds(1); - public double FixedDeltaSeconds { get; init; } = 0.02; - public AetheriaRuntimeDaemonRenderSettings RenderSettings { get; init; } = - AetheriaRuntimeDaemonRenderSettings.AetheriaDefault; - public AetheriaRuntimeDaemonSimulationSettings SimulationSettings { get; init; } = - AetheriaRuntimeDaemonSimulationSettings.AetheriaDefault; - public string TerminusScenario { get; init; } = AetheriaDaemonTerminusScenarios.Standard; - public bool UseTerminusFixture { get; init; } - public bool Once { get; init; } - - public static AetheriaDaemonHostOptions Parse(IReadOnlyList args) - { - var root = ReadOption(args, "--root"); - if (string.IsNullOrWhiteSpace(root)) - root = Path.GetFullPath(Path.Combine(AppContext.BaseDirectory, "..", "..", "..", "..")); - - var state = ReadOption(args, "--state"); - var intervalMs = ReadPositiveInt(args, "--tick-interval-ms") ?? 20; - var fixedDeltaMs = ReadPositiveInt(args, "--fixed-delta-ms") ?? 20; - var daemonId = ReadOption(args, "--daemon-id"); - var verseId = ReadOption(args, "--verse-id"); - var cultMeshAddress = ReadOption(args, "--cultmesh-address"); - var clientCultMeshHost = ReadOption(args, "--client-cultmesh-host"); - var clientCultMeshAdvertiseHost = ReadOption(args, "--client-cultmesh-advertise-host"); - var clientCultMeshPort = ReadNonNegativeInt(args, "--client-cultmesh-port") ?? 3076; - var clientCultMeshContentPort = ReadNonNegativeInt(args, "--client-cultmesh-content-port") ?? 0; - var clientCultMeshQuicPort = ReadNonNegativeInt(args, "--client-cultmesh-quic-port") ?? 0; - var clientCultMeshCertificatePath = ReadOption(args, "--client-cultmesh-certificate-path"); - var aetheriaResourcesRoot = ReadOption(args, "--aetheria-resources-root"); - var assetBundleRoot = ReadOption(args, "--asset-bundle-root"); - RejectRemovedOption(args, "--rts-cultmesh-port", "--client-cultmesh-port"); - var peerCultMeshEndpoints = ReadOptions(args, "--peer-cultmesh-endpoint"); - var peerSyncTimeoutMs = ReadPositiveInt(args, "--peer-sync-timeout-ms") ?? 250; - RejectRemovedOption(args, "--odin-cultmesh-rudp", "--odin-cultmesh-uri"); - RejectRemovedOption(args, "--odin-cultnet-rudp", "--odin-cultmesh-uri"); - var odinCultMeshUri = ReadOption(args, "--odin-cultmesh-uri"); - var noOdinAnnouncements = HasFlag(args, "--no-odin-announcements"); - var apiPublicationIntervalMs = ReadPositiveInt(args, "--api-publication-interval-ms") ?? 1000; - var sessionId = ReadOption(args, "--session-id"); - var requestedTerminusScenario = ReadOption(args, "--terminus-scenario"); - var terminusScenario = AetheriaDaemonTerminusScenarios.Parse(requestedTerminusScenario); - - var resolvedStatePath = string.IsNullOrWhiteSpace(state) - ? AetheriaStatePaths.ResolveDefaultStatePath(root) - : Path.GetFullPath(state); - var certificateHost = string.IsNullOrWhiteSpace(clientCultMeshAdvertiseHost) - ? (string.IsNullOrWhiteSpace(clientCultMeshHost) || clientCultMeshHost == "0.0.0.0" || clientCultMeshHost == "*" ? "127.0.0.1" : clientCultMeshHost) - : clientCultMeshAdvertiseHost; - var certificateHostToken = Convert.ToHexString(SHA256.HashData( - System.Text.Encoding.UTF8.GetBytes(certificateHost))).Substring(0, 12).ToLowerInvariant(); - - return new AetheriaDaemonHostOptions - { - StatePath = resolvedStatePath, - DaemonId = string.IsNullOrWhiteSpace(daemonId) ? "aetheria-daemon" : daemonId, - SessionId = string.IsNullOrWhiteSpace(sessionId) ? "local" : sessionId, - VerseId = string.IsNullOrWhiteSpace(verseId) ? "aetheria.local" : verseId, - CultMeshAddress = string.IsNullOrWhiteSpace(cultMeshAddress) - ? "cultmesh://aetheria.local/eve/providers/aetheria.daemon" - : cultMeshAddress, - ClientCultMeshHost = string.IsNullOrWhiteSpace(clientCultMeshHost) ? "127.0.0.1" : clientCultMeshHost, - ClientCultMeshAdvertiseHost = string.IsNullOrWhiteSpace(clientCultMeshAdvertiseHost) - ? (string.IsNullOrWhiteSpace(clientCultMeshHost) || clientCultMeshHost == "0.0.0.0" || clientCultMeshHost == "*" ? "127.0.0.1" : clientCultMeshHost) - : clientCultMeshAdvertiseHost, - ClientCultMeshPort = clientCultMeshPort, - ClientCultMeshContentPort = clientCultMeshContentPort, - ClientCultMeshQuicPort = clientCultMeshQuicPort, - ClientCultMeshCertificatePath = string.IsNullOrWhiteSpace(clientCultMeshCertificatePath) - ? resolvedStatePath + $".client-quic-{certificateHostToken}.pfx" - : Path.GetFullPath(clientCultMeshCertificatePath), - AetheriaResourcesRoot = string.IsNullOrWhiteSpace(aetheriaResourcesRoot) - ? Path.GetFullPath(Path.Combine(root, "Assets", "Resources")) - : Path.GetFullPath(aetheriaResourcesRoot), - AssetBundleRoot = string.IsNullOrWhiteSpace(assetBundleRoot) - ? Path.GetFullPath(Path.Combine(root, "Build", "EveAssets")) - : Path.GetFullPath(assetBundleRoot), - PeerCultMeshEndpoints = peerCultMeshEndpoints, - PeerSyncTimeout = TimeSpan.FromMilliseconds(peerSyncTimeoutMs), - OdinCultMeshUri = noOdinAnnouncements ? "" : odinCultMeshUri, - EnableOdinAnnouncements = !noOdinAnnouncements && !string.IsNullOrWhiteSpace(odinCultMeshUri), - TickInterval = TimeSpan.FromMilliseconds(intervalMs), - ApiPublicationInterval = TimeSpan.FromMilliseconds(apiPublicationIntervalMs), - FixedDeltaSeconds = fixedDeltaMs / 1000.0, - TerminusScenario = terminusScenario, - UseTerminusFixture = !string.IsNullOrWhiteSpace(requestedTerminusScenario), - Once = HasFlag(args, "--once") - }; - } - - private static string ReadOption(IReadOnlyList args, string name) - { - for (var i = 0; i < args.Count - 1; i++) - { - if (string.Equals(args[i], name, StringComparison.OrdinalIgnoreCase)) - return args[i + 1]; - } - - return ""; - } - - private static IReadOnlyList ReadOptions(IReadOnlyList args, string name) - { - var values = new List(); - for (var i = 0; i < args.Count - 1; i++) - { - if (string.Equals(args[i], name, StringComparison.OrdinalIgnoreCase) && - !string.IsNullOrWhiteSpace(args[i + 1])) - { - values.Add(args[i + 1].Trim()); - } - } - - return values - .Distinct(StringComparer.Ordinal) - .ToArray(); - } - - private static void RejectRemovedOption(IReadOnlyList args, string removed, string replacement) - { - if (args.Any(arg => string.Equals(arg, removed, StringComparison.OrdinalIgnoreCase))) - { - throw new InvalidOperationException($"{removed} was removed. Use {replacement} with a cultmesh:// Odin route."); - } - } - - private static int? ReadPositiveInt(IReadOnlyList args, string name) - { - return int.TryParse(ReadOption(args, name), out var value) && value > 0 - ? value - : null; - } - - private static int? ReadNonNegativeInt(IReadOnlyList args, string name) - { - return int.TryParse(ReadOption(args, name), out var value) && value >= 0 - ? value - : null; - } - - private static bool HasFlag(IReadOnlyList args, string name) - { - return args.Any(arg => string.Equals(arg, name, StringComparison.OrdinalIgnoreCase)); - } -} diff --git a/Aetheria.State.Daemon/Properties/AssemblyInfo.cs b/Aetheria.State.Daemon/Properties/AssemblyInfo.cs deleted file mode 100644 index 2f3bbef3f..000000000 --- a/Aetheria.State.Daemon/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,3 +0,0 @@ -using System.Runtime.CompilerServices; - -[assembly: InternalsVisibleTo("Aetheria.State.Daemon.Smoke")] diff --git a/Aetheria.State.Freeze/Aetheria.State.Freeze.csproj b/Aetheria.State.Freeze/Aetheria.State.Freeze.csproj deleted file mode 100644 index 1e5102696..000000000 --- a/Aetheria.State.Freeze/Aetheria.State.Freeze.csproj +++ /dev/null @@ -1,15 +0,0 @@ - - - Exe - net10.0 - enable - latest - enable - - - - - - - - diff --git a/Aetheria.State.Freeze/Program.cs b/Aetheria.State.Freeze/Program.cs deleted file mode 100644 index 249ea460d..000000000 --- a/Aetheria.State.Freeze/Program.cs +++ /dev/null @@ -1,1374 +0,0 @@ -using System.Diagnostics; -using System.Text.Json; -using System.Text.Json.Serialization; -using Aetheria.State; -using GameCult.Geometry; -using GameCult.Aetheria.State.Verse; -using MessagePack; - -var options = FreezeOptions.Parse(args); -Directory.CreateDirectory(options.OutputDirectory); -Directory.CreateDirectory(Path.GetDirectoryName(options.StatePath) ?? "."); - -await ExportFixtureSetAsync( - options, - "baseline", - options.StatePath, - options.OutputDirectory, - Array.Empty()) - .ConfigureAwait(false); - -if (options.IncludeCommandBatches) -{ - var commandStateDirectory = Path.Combine(options.OutputDirectory, ".state"); - await ExportFixtureSetAsync( - options, - "command-batch-pilot", - Path.Combine(commandStateDirectory, "command-batch-pilot.cc"), - Path.Combine(options.OutputDirectory, "command-batch-pilot"), - BuildPilotCommands()) - .ConfigureAwait(false); - - await ExportFixtureSetAsync( - options, - "command-batch-interaction", - Path.Combine(commandStateDirectory, "command-batch-interaction.cc"), - Path.Combine(options.OutputDirectory, "command-batch-interaction"), - BuildInteractionCommands()) - .ConfigureAwait(false); - - await ExportFixtureSetAsync( - options, - "command-batch-refit", - Path.Combine(commandStateDirectory, "command-batch-refit.cc"), - Path.Combine(options.OutputDirectory, "command-batch-refit"), - BuildRefitCommands()) - .ConfigureAwait(false); -} - -ExportAuthorityDecisionFixtures(Path.Combine(options.OutputDirectory, "authority-decisions")); -ExportYmirQueryFixtures(Path.Combine(options.OutputDirectory, "ymir-queries")); - -Console.WriteLine($"Wrote Aetheria freeze fixtures to {options.OutputDirectory}"); - -static async Task ExportFixtureSetAsync( - FreezeOptions options, - string scenario, - string statePath, - string outputDirectory, - IReadOnlyList commands) -{ - Directory.CreateDirectory(outputDirectory); - Directory.CreateDirectory(Path.GetDirectoryName(statePath) ?? "."); - if (options.CleanState) - DeleteStateFamily(statePath); - - if (commands.Count > 0) - await SeedCommandsAsync(statePath, commands).ConfigureAwait(false); - - if (options.RunDaemon) - await RunDaemonOnceAsync(options, statePath, scenario).ConfigureAwait(false); - - var manifest = new FreezeManifest - { - Scenario = scenario, - GeneratedAtUtc = DateTimeOffset.UtcNow.ToString("O"), - Root = options.Root, - StatePath = statePath, - OutputDirectory = outputDirectory, - RanDaemon = options.RunDaemon, - SeededCommandCount = commands.Count - }; - - if (commands.Count > 0) - WriteArtifact(outputDirectory, manifest, "commands.seeded", AetheriaRuntimeDaemonSchemas.Command + "[]", commands); - - await using var node = await AetheriaStateNode - .OpenAsync(statePath, runtimeId: "aetheria-freeze", startServer: false) - .ConfigureAwait(false); - - var frame = await node.MutableDocument( - AetheriaRuntimeVerseRecordKeys.DaemonFrameLatest) - .ReadAsync() - .ConfigureAwait(false); - if (frame == null) - throw new InvalidOperationException("Freeze export cannot continue because the daemon latest frame is missing."); - WriteArtifact(outputDirectory, manifest, "daemon-frame.latest", AetheriaRuntimeDaemonSchemas.Frame, frame); - - if (await TryReadDocumentAsync(node, AetheriaRuntimeVerseRecordKeys.DaemonHealth).ConfigureAwait(false) is { } health) - WriteArtifact(outputDirectory, manifest, "daemon-health.latest", AetheriaRuntimeDaemonSchemas.Health, health); - - if (await TryReadDocumentAsync(node, AetheriaRuntimeVerseRecordKeys.DaemonProviderAdvertisement).ConfigureAwait(false) is { } provider) - WriteArtifact(outputDirectory, manifest, "daemon-provider.latest", AetheriaRuntimeDaemonSchemas.ProviderAdvertisement, provider); - - if (await TryReadDocumentAsync(node, AetheriaRuntimeVerseRecordKeys.DaemonCommandBoundary).ConfigureAwait(false) is { } commandBoundary) - WriteArtifact(outputDirectory, manifest, "daemon-command-boundary.latest", AetheriaRuntimeDaemonSchemas.CommandBoundary, commandBoundary); - - if (await TryReadDocumentAsync(node, AetheriaRuntimeVerseRecordKeys.VerseAuthorityPolicy).ConfigureAwait(false) is { } authorityPolicy) - WriteArtifact(outputDirectory, manifest, "verse-authority-policy.latest", AetheriaRuntimeVerseAuthoritySchemas.Policy, authorityPolicy); - - if (await TryReadDocumentAsync(node, AetheriaRuntimeVerseRecordKeys.StarbridgeSessionSummary).ConfigureAwait(false) is { } starbridgeSummary) - WriteArtifact(outputDirectory, manifest, "starbridge-session-summary.latest", AetheriaRuntimeDaemonSchemas.StarbridgeSessionSummary, starbridgeSummary); - - if (await TryReadDocumentAsync(node, AetheriaRuntimeVerseRecordKeys.DaemonSoaViewLatest).ConfigureAwait(false) is { } soaView) - WriteArtifact(outputDirectory, manifest, "daemon-soa-view.latest", AetheriaRuntimeDaemonSchemas.SoaView, soaView); - - var viewport = new AetheriaRuntimeViewportBounds - { - MinX = options.ViewportMinX, - MinY = options.ViewportMinY, - MaxX = options.ViewportMaxX, - MaxY = options.ViewportMaxY - }; - WriteArtifact(outputDirectory, manifest, "document.game-viewport", AetheriaRuntimeDaemonSchemas.GameViewport, AetheriaRuntimeGameDocuments.Viewport(frame, viewport)); - WriteArtifact(outputDirectory, manifest, "document.objects-viewport", AetheriaRuntimeDaemonSchemas.ObjectsViewport, AetheriaRuntimeGameDocuments.ObjectsViewport(frame, viewport)); - WriteArtifact(outputDirectory, manifest, "document.gravity-viewport", AetheriaRuntimeDaemonSchemas.GravityViewport, AetheriaRuntimeGameDocuments.GravityViewport(frame, viewport)); - WriteArtifact(outputDirectory, manifest, "document.current-zone", AetheriaRuntimeDaemonSchemas.CurrentZone, AetheriaRuntimeGameDocuments.CurrentZone(frame)); - WriteArtifact(outputDirectory, manifest, "document.current-entity", AetheriaRuntimeDaemonSchemas.CurrentEntity, AetheriaRuntimeGameDocuments.CurrentEntity(frame)); - WriteArtifact(outputDirectory, manifest, "document.sector-map", AetheriaRuntimeDaemonSchemas.SectorMap, AetheriaRuntimeGameDocuments.SectorMap(frame)); - WriteArtifact(outputDirectory, manifest, "document.zone-render", AetheriaRuntimeDaemonSchemas.ZoneRender, AetheriaRuntimeGameDocuments.ZoneRender(frame)); - - var selectedEntityIndex = ResolveSelectedEntityIndex(frame); - WriteArtifact(outputDirectory, manifest, "document.selected-object", AetheriaRuntimeDaemonSchemas.SelectedObject, AetheriaRuntimeGameDocuments.SelectedObject(frame, selectedEntityIndex)); - WriteArtifact(outputDirectory, manifest, "document.inventory", AetheriaRuntimeDaemonSchemas.Inventory, AetheriaRuntimeGameDocuments.Inventory(frame, selectedEntityIndex)); - - WriteJson(Path.Combine(outputDirectory, "manifest.json"), manifest); -} - -static void ExportAuthorityDecisionFixtures(string outputDirectory) -{ - Directory.CreateDirectory(outputDirectory); - var fixtures = BuildAuthorityDecisionFixtures(); - var manifest = new FreezeManifest - { - Scenario = "authority-decisions", - GeneratedAtUtc = DateTimeOffset.UtcNow.ToString("O"), - OutputDirectory = outputDirectory, - RanDaemon = false, - SeededCommandCount = fixtures.Cases.Length - }; - WriteArtifact(outputDirectory, manifest, "authority-decisions", "gamecult.aetheria.freeze.authority_decisions.v1", fixtures); - WriteJson(Path.Combine(outputDirectory, "manifest.json"), manifest); -} - -static Task TryReadDocumentAsync(AetheriaStateNode node, GameCult.Caching.CultRecordKey recordKey) - where T : class -{ - return TryReadAsync(() => node.MutableDocument(recordKey).ReadAsync()); -} - -static async Task TryReadAsync(Func> read) - where T : class -{ - try - { - return await read().ConfigureAwait(false); - } - catch (KeyNotFoundException) - { - return null; - } -} - -static void ExportYmirQueryFixtures(string outputDirectory) -{ - Directory.CreateDirectory(outputDirectory); - var fixtures = BuildYmirQueryFixtures(); - var manifest = new FreezeManifest - { - Scenario = "ymir-queries", - GeneratedAtUtc = DateTimeOffset.UtcNow.ToString("O"), - OutputDirectory = outputDirectory, - RanDaemon = false, - SeededCommandCount = fixtures.Cases.Length - }; - WriteArtifact(outputDirectory, manifest, "ymir-queries", "gamecult.aetheria.freeze.ymir_queries.v1", fixtures); - WriteJson(Path.Combine(outputDirectory, "manifest.json"), manifest); -} - -static async Task SeedCommandsAsync(string statePath, IReadOnlyList commands) -{ - await using var node = await AetheriaStateNode - .OpenAsync(statePath, "aetheria-freeze-command-seeder", enableDurableShardLogs: false) - .ConfigureAwait(false); - foreach (var command in commands) - await node.SubmitDaemonCommandAsync(command).ConfigureAwait(false); - await node.FlushAsync().ConfigureAwait(false); -} - -static async Task RunDaemonOnceAsync(FreezeOptions options, string statePath, string scenario) -{ - var daemonProject = Path.Combine(options.Root, "Aetheria.State.Daemon", "Aetheria.State.Daemon.csproj"); - if (!File.Exists(daemonProject)) - throw new FileNotFoundException("Could not find daemon project.", daemonProject); - - var daemonArgs = string.Join( - " ", - new[] - { - "run", - "--project", - Quote(daemonProject), - "--", - "--once", - "--root", - Quote(options.Root), - "--state", - Quote(statePath), - "--daemon-id", - $"aetheria-freeze-{scenario}", - "--session-id", - scenario, - "--client-cultmesh-port", - "0" - }); - - var start = new ProcessStartInfo("dotnet", daemonArgs) - { - WorkingDirectory = options.Root, - RedirectStandardOutput = true, - RedirectStandardError = true, - UseShellExecute = false - }; - - using var process = Process.Start(start) ?? throw new InvalidOperationException("Failed to start daemon process."); - var stdout = process.StandardOutput.ReadToEndAsync(); - var stderr = process.StandardError.ReadToEndAsync(); - await process.WaitForExitAsync().ConfigureAwait(false); - - if (process.ExitCode != 0) - { - throw new InvalidOperationException( - $"Aetheria daemon fixture run failed with exit code {process.ExitCode}.\nSTDOUT:\n{await stdout.ConfigureAwait(false)}\nSTDERR:\n{await stderr.ConfigureAwait(false)}"); - } -} - -static void DeleteStateFamily(string statePath) -{ - var directory = Path.GetDirectoryName(statePath); - var fileName = Path.GetFileName(statePath); - if (string.IsNullOrWhiteSpace(directory) || string.IsNullOrWhiteSpace(fileName) || !Directory.Exists(directory)) - return; - - foreach (var path in Directory.EnumerateFileSystemEntries(directory, fileName + "*")) - { - if (Directory.Exists(path)) - Directory.Delete(path, recursive: true); - else - File.Delete(path); - } -} - -static IReadOnlyList BuildPilotCommands() -{ - const string actor = "global:aetheria.run_state.local-starbridge.zone.0.entity.1.v1"; - const string target = "global:aetheria.run_state.local-starbridge.zone.0.entity.4.v1"; - return new[] - { - Command("pilot-001-set-target", AetheriaRuntimeDaemonCommandKinds.SetTarget, actor, command => - { - command.TargetEntityKey = target; - }), - Command("pilot-002-move", AetheriaRuntimeDaemonCommandKinds.SetMoveVector, actor, command => - { - command.DirectionX = 1; - command.DirectionY = 0.25; - command.ScalarValue = 1; - }), - Command("pilot-003-look", AetheriaRuntimeDaemonCommandKinds.SetLookDirection, actor, command => - { - command.DirectionX = 0.25; - command.DirectionY = 1; - }), - Command("pilot-004-tractor", AetheriaRuntimeDaemonCommandKinds.SetTractorPower, actor, command => - { - command.ScalarValue = 0.5; - }), - Command("pilot-005-sensor-ping", AetheriaRuntimeDaemonCommandKinds.SensorPing, actor), - Command("pilot-006-toggle-shield", AetheriaRuntimeDaemonCommandKinds.ToggleShieldEnabled, actor), - Command("pilot-007-fire-weapon-group", AetheriaRuntimeDaemonCommandKinds.FireWeaponGroup, actor, command => - { - command.WeaponGroup = 0; - }) - }; -} - -static IReadOnlyList BuildInteractionCommands() -{ - const string actor = "global:aetheria.run_state.local-starbridge.zone.0.entity.1.v1"; - const string station = "global:aetheria.run_state.local-starbridge.zone.0.entity.0.v1"; - return new[] - { - Command("interaction-001-dock-nearest", AetheriaRuntimeDaemonCommandKinds.DockNearest, actor), - Command("interaction-002-dock", AetheriaRuntimeDaemonCommandKinds.Dock, actor, command => - { - command.TargetEntityKey = station; - }), - Command("interaction-003-undock", AetheriaRuntimeDaemonCommandKinds.Undock, actor), - Command("interaction-004-interact", AetheriaRuntimeDaemonCommandKinds.Interact, actor), - Command("interaction-005-tow", AetheriaRuntimeDaemonCommandKinds.TowToStation, actor, command => - { - command.TargetEntityKey = station; - }) - }; -} - -static IReadOnlyList BuildRefitCommands() -{ - const string actor = "global:aetheria.run_state.local-starbridge.zone.0.entity.1.v1"; - const string station = "global:aetheria.run_state.local-starbridge.zone.0.entity.0.v1"; - return new[] - { - Command("refit-001-rename", AetheriaRuntimeDaemonCommandKinds.SetEntityName, actor, command => - { - command.TargetEntityKey = actor; - command.TextValue = "Vanguard Freeze"; - }), - Command("refit-002-transfer-cargo", AetheriaRuntimeDaemonCommandKinds.TransferCargoItem, actor, command => - { - command.TargetEntityKey = actor; - command.TextValue = "repair-parts"; - command.ScalarValue = 1; - command.CargoTransfer.OriginEntityKey = station; - command.CargoTransfer.OriginCargoIndex = 0; - command.CargoTransfer.DestinationEntityKey = actor; - command.CargoTransfer.DestinationCargoIndex = 0; - command.CargoTransfer.SourceX = 0; - command.CargoTransfer.SourceY = 0; - command.CargoTransfer.DestinationX = 2; - command.CargoTransfer.DestinationY = 0; - command.CargoTransfer.HasDestinationPosition = true; - }), - Command("refit-003-trade-purchase", AetheriaRuntimeDaemonCommandKinds.TradePurchase, actor, command => - { - command.TargetEntityKey = actor; - command.EquipmentIndex = 0; - command.PositionX = 1; - command.PositionY = 0; - command.ScalarValue = 25; - command.TextValue = "reactor-fuel"; - command.TradePurchase.ItemKey = "reactor-fuel"; - command.TradePurchase.Quantity = 1; - command.TradePurchase.StationCargoIndex = 0; - command.TradePurchase.TargetCargoIndex = 0; - command.TradePurchase.SourceX = 1; - command.TradePurchase.SourceY = 0; - }), - Command("refit-004-equip-from-cargo", AetheriaRuntimeDaemonCommandKinds.EquipItem, actor, command => - { - command.TargetEntityKey = actor; - command.PositionX = 1; - command.PositionY = 0; - command.TextValue = "field-rations"; - command.EquipmentTransfer.SourceKind = "cargo"; - command.EquipmentTransfer.OriginEntityKey = actor; - command.EquipmentTransfer.OriginIndex = 0; - command.EquipmentTransfer.DestinationEntityKey = actor; - command.EquipmentTransfer.SourceX = 1; - command.EquipmentTransfer.SourceY = 0; - command.EquipmentTransfer.DestinationX = 1; - command.EquipmentTransfer.DestinationY = 0; - command.EquipmentTransfer.HasDestinationPosition = true; - }), - Command("refit-005-store-equipped", AetheriaRuntimeDaemonCommandKinds.StoreItem, actor, command => - { - command.TargetEntityKey = actor; - command.TextValue = "sensor-array"; - command.StoreItem.OriginEntityKey = actor; - command.StoreItem.SourceEquipmentIndex = 0; - command.StoreItem.DestinationEntityKey = actor; - command.StoreItem.DestinationCargoIndex = 0; - command.StoreItem.DestinationX = 3; - command.StoreItem.DestinationY = 0; - command.StoreItem.HasDestinationPosition = true; - }), - Command("refit-006-set-docked-current-ship", AetheriaRuntimeDaemonCommandKinds.SetDockedCurrentShip, actor, command => - { - command.TargetEntityKey = actor; - }), - Command("refit-007-pick-up-loot", AetheriaRuntimeDaemonCommandKinds.PickUpLoot, actor, command => - { - command.TargetEntityKey = actor; - command.TextValue = "loose-salvage"; - command.ScalarValue = 2; - command.PositionX = -40; - command.PositionY = 0; - command.PositionZ = -35; - }), - Command("refit-008-restore-loadout", AetheriaRuntimeDaemonCommandKinds.RestoreLoadout, actor, command => - { - command.TargetEntityKey = actor; - command.LoadoutRestore.DockedEntityKey = actor; - command.LoadoutRestore.TemplateName = "freeze-template"; - command.LoadoutRestore.Price = 0; - }) - }; -} - -static AuthorityDecisionFixtureSet BuildAuthorityDecisionFixtures() -{ - const string verseId = "aetheria.freeze"; - const string host = "aetheria-daemon"; - const string otherRuntime = "raven-client"; - const string delegatedRuntime = "starbridge-commander"; - const string actor = "global:aetheria.run_state.local-starbridge.zone.0.entity.1.v1"; - const string raider = "global:aetheria.run_state.local-starbridge.zone.0.entity.4.v1"; - var validLease = new AetheriaRuntimeAuthorityLeaseDocument - { - LeaseId = "lease-commander-vanguard-movement", - VerseId = verseId, - RuntimeId = delegatedRuntime, - SubjectPrefix = actor, - ClaimKinds = new[] { AetheriaRuntimeClaimKinds.Movement }, - ValidFromUtc = "2026-01-01T00:00:00.0000000Z", - ExpiresAtUtc = "2100-01-01T00:00:00.0000000Z", - Scope = "nearby-interest" - }; - - var cases = new[] - { - AuthorityCase( - "trusted-coop-allows-delegated-movement", - AetheriaRuntimeVerseAuthorityPolicyDocument.TrustedCoop(verseId, host), - Array.Empty(), - AuthorityCommand("auth-001-delegated-move", AetheriaRuntimeDaemonCommandKinds.SetMoveVector, actor, delegatedRuntime), - host), - AuthorityCase( - "host-authoritative-allows-host", - HostPolicy(verseId, host), - Array.Empty(), - AuthorityCommand("auth-002-host-move", AetheriaRuntimeDaemonCommandKinds.SetMoveVector, actor, host), - host), - AuthorityCase( - "host-authoritative-rejects-delegated-runtime", - HostPolicy(verseId, host), - Array.Empty(), - AuthorityCommand("auth-003-delegated-move", AetheriaRuntimeDaemonCommandKinds.SetMoveVector, actor, delegatedRuntime), - host), - AuthorityCase( - "delegated-runtime-allows-commander-targeting", - DelegatedPolicy(verseId, host, actor, AetheriaRuntimeClaimKinds.Targeting, delegatedRuntime), - Array.Empty(), - AuthorityCommand("auth-004-commander-target", AetheriaRuntimeDaemonCommandKinds.SetTarget, actor, delegatedRuntime, command => command.TargetEntityKey = raider), - host), - AuthorityCase( - "delegated-runtime-rejects-other-targeting", - DelegatedPolicy(verseId, host, actor, AetheriaRuntimeClaimKinds.Targeting, delegatedRuntime), - Array.Empty(), - AuthorityCommand("auth-005-other-target", AetheriaRuntimeDaemonCommandKinds.SetTarget, actor, otherRuntime, command => command.TargetEntityKey = raider), - host), - AuthorityCase( - "interest-lease-allows-delegated-movement", - LeasePolicy(verseId, host, actor, AetheriaRuntimeClaimKinds.Movement), - new[] { validLease }, - AuthorityCommand("auth-006-delegated-lease-move", AetheriaRuntimeDaemonCommandKinds.SetMoveVector, actor, delegatedRuntime), - host), - AuthorityCase( - "interest-lease-rejects-delegated-without-lease", - LeasePolicy(verseId, host, actor, AetheriaRuntimeClaimKinds.Movement), - Array.Empty(), - AuthorityCommand("auth-007-delegated-no-lease-move", AetheriaRuntimeDaemonCommandKinds.SetMoveVector, actor, delegatedRuntime), - host), - AuthorityCase( - "owning-runtime-is-not-implemented", - OwningPolicy(verseId, host, actor), - Array.Empty(), - AuthorityCommand("auth-008-owning-mode-move", AetheriaRuntimeDaemonCommandKinds.SetMoveVector, actor, otherRuntime), - host) - }; - - return new AuthorityDecisionFixtureSet - { - Schema = "gamecult.aetheria.freeze.authority_decisions.v1", - GeneratedAtUtc = DateTimeOffset.UtcNow.ToString("O"), - Cases = cases - }; -} - -static YmirQueryFixtureSet BuildYmirQueryFixtures() -{ - var stepBody = YmirBody("ship", 1, 2, 1); - stepBody.velocity = YmirVec2Of(3, -4); - var stepRequest = new YmirStepRequest - { - deltaTime = 0.5f, - world = YmirWorldOf(stepBody) - }; - - var radialBody = YmirBody("ship", 5, 0, 1); - var radialRequest = new YmirStepRequest - { - deltaTime = 1.0f, - world = YmirWorldOf(radialBody) - }; - radialRequest.world.fields = new[] - { - new YmirRadialField - { - id = "push", - position = YmirVec2Of(0, 0), - strength = 10, - radius = 10 - } - }; - - var mover = YmirBody("mover", 0, 0, 1); - mover.velocity = YmirVec2Of(2, 0); - mover.mass = 1; - var wall = YmirBody("wall", 1.5f, 0, 1); - wall.isStatic = true; - var contactRequest = new YmirStepRequest - { - deltaTime = 0.1f, - world = YmirWorldOf(mover, wall) - }; - - var overlapSphereRequest = new YmirSphereOverlapRequest - { - center = YmirVec3Of(0, 0, 0), - radius = 3, - bodies = new[] - { - YmirSphereBody("far", 4, 0, 0, 2), - YmirSphereBody("near", 1, 0, 0, 0.25f), - YmirSphereBody("miss", 10, 0, 0, 1) - } - }; - - var overlapCircleRequest = new YmirCircleOverlapRequest - { - center = YmirVec2Of(0, 0), - radius = 3, - world = YmirWorldOf( - YmirBody("far", 4, 0, 2), - YmirBody("near", 1, 0, 0.25f), - YmirBody("miss", 10, 0, 1)) - }; - - var castSphereRequest = new YmirSphereCastRequest - { - origin = YmirVec3Of(0, 0, 0), - direction = YmirVec3Of(2, 0, 0), - distance = 10, - radius = 1, - bodies = new[] { YmirSphereBody("target", 4, 0, 0, 1) } - }; - - var castCircleRequest = new YmirCircleCastRequest - { - origin = YmirVec2Of(0, 0), - direction = YmirVec2Of(1, 0), - distance = 10, - radius = 1, - world = YmirWorldOf( - YmirBody("far", 8, 0, 1), - YmirBody("near", 4, 0, 1), - YmirBody("miss", 4, 4, 1)) - }; - - var invalidCastCircleRequest = new YmirCircleCastRequest - { - origin = YmirVec2Of(0, 0), - direction = YmirVec2Of(0, 0), - distance = 1, - radius = 1, - world = YmirWorldOf(YmirBody("target", 0, 0, 1)) - }; - - return new YmirQueryFixtureSet - { - Schema = "gamecult.aetheria.freeze.ymir_queries.v1", - GeneratedAtUtc = DateTimeOffset.UtcNow.ToString("O"), - Cases = new[] - { - YmirStepCase("step-integrates-dynamic-body", "Dynamic bodies integrate velocity without mutating the input world.", stepRequest, YmirPhysicsQueries.Step(stepRequest)), - YmirStepCase("step-applies-radial-field", "Radial fields apply linear falloff acceleration before integration.", radialRequest, YmirPhysicsQueries.Step(radialRequest)), - YmirStepCase("step-reports-contact-and-separates", "Dynamic/static body overlap produces a contact and separates the dynamic body.", contactRequest, YmirPhysicsQueries.Step(contactRequest)), - YmirSphereOverlapCase("overlap-sphere-sorts-surface-distance", "Sphere overlap hits sort by surface distance, then id.", overlapSphereRequest, YmirPhysicsQueries.OverlapSphere(overlapSphereRequest)), - YmirCircleOverlapCase("overlap-circle-sorts-surface-distance", "Circle overlap hits sort by surface distance, then id.", overlapCircleRequest, YmirPhysicsQueries.OverlapCircle(overlapCircleRequest)), - YmirSphereCastCase("cast-sphere-impact-point-normal", "Sphere casts report impact distance, point, and normal.", castSphereRequest, YmirPhysicsQueries.CastSphere(castSphereRequest)), - YmirCircleCastCase("cast-circle-sorts-impact-distance", "Circle cast hits sort by impact distance, then id.", castCircleRequest, YmirPhysicsQueries.CastCircle(castCircleRequest)), - YmirCircleCastCase("cast-circle-invalid-direction-skips", "Invalid cast direction returns no hits.", invalidCastCircleRequest, YmirPhysicsQueries.CastCircle(invalidCastCircleRequest)) - } - }; -} - -static YmirQueryFixtureCase YmirStepCase( - string caseId, - string intent, - YmirStepRequest request, - YmirStepResult result) -{ - return new YmirQueryFixtureCase - { - CaseId = caseId, - QueryKind = "step", - Intent = intent, - Request = new YmirQueryFixturePayload - { - DeltaTime = request.deltaTime, - World = ToFixtureWorld(request.world) - }, - Result = new YmirQueryFixturePayload - { - World = ToFixtureWorld(result.world), - Contacts = ToFixtureContacts(result.contacts) - } - }; -} - -static YmirQueryFixtureCase YmirSphereOverlapCase( - string caseId, - string intent, - YmirSphereOverlapRequest request, - YmirSphereOverlapResult result) -{ - return new YmirQueryFixtureCase - { - CaseId = caseId, - QueryKind = "overlap-sphere", - Intent = intent, - Request = new YmirQueryFixturePayload - { - Center3 = ToFixtureVec3(request.center), - Radius = request.radius, - SphereBodies = ToFixtureSphereBodies(request.bodies) - }, - Result = new YmirQueryFixturePayload - { - SphereOverlapHits = ToFixtureSphereOverlapHits(result.hits) - } - }; -} - -static YmirQueryFixtureCase YmirCircleOverlapCase( - string caseId, - string intent, - YmirCircleOverlapRequest request, - YmirCircleOverlapResult result) -{ - return new YmirQueryFixtureCase - { - CaseId = caseId, - QueryKind = "overlap-circle", - Intent = intent, - Request = new YmirQueryFixturePayload - { - Center2 = ToFixtureVec2(request.center), - Radius = request.radius, - World = ToFixtureWorld(request.world) - }, - Result = new YmirQueryFixturePayload - { - CircleOverlapHits = ToFixtureCircleOverlapHits(result.hits) - } - }; -} - -static YmirQueryFixtureCase YmirSphereCastCase( - string caseId, - string intent, - YmirSphereCastRequest request, - YmirSphereCastResult result) -{ - return new YmirQueryFixtureCase - { - CaseId = caseId, - QueryKind = "cast-sphere", - Intent = intent, - Request = new YmirQueryFixturePayload - { - Origin3 = ToFixtureVec3(request.origin), - Direction3 = ToFixtureVec3(request.direction), - Distance = request.distance, - Radius = request.radius, - SphereBodies = ToFixtureSphereBodies(request.bodies) - }, - Result = new YmirQueryFixturePayload - { - SphereCastHits = ToFixtureSphereCastHits(result.hits) - } - }; -} - -static YmirQueryFixtureCase YmirCircleCastCase( - string caseId, - string intent, - YmirCircleCastRequest request, - YmirCircleCastResult result) -{ - return new YmirQueryFixtureCase - { - CaseId = caseId, - QueryKind = "cast-circle", - Intent = intent, - Request = new YmirQueryFixturePayload - { - Origin2 = ToFixtureVec2(request.origin), - Direction2 = ToFixtureVec2(request.direction), - Distance = request.distance, - Radius = request.radius, - World = ToFixtureWorld(request.world) - }, - Result = new YmirQueryFixturePayload - { - CircleCastHits = ToFixtureCircleCastHits(result.hits) - } - }; -} - -static YmirFixtureWorld ToFixtureWorld(YmirWorld world) -{ - return new YmirFixtureWorld - { - Time = world?.time ?? 0, - Bodies = ToFixtureBodies(world?.bodies), - Fields = ToFixtureFields(world?.fields) - }; -} - -static YmirFixtureBody[] ToFixtureBodies(IReadOnlyList? bodies) -{ - return (bodies ?? Array.Empty()) - .Select(body => new YmirFixtureBody - { - Id = body?.id ?? "", - Position = ToFixtureVec2(body?.position ?? default), - Velocity = ToFixtureVec2(body?.velocity ?? default), - Direction = ToFixtureVec2(body?.direction ?? default), - AngularVelocity = body?.angularVelocity ?? 0, - Torque = body?.torque ?? 0, - MomentOfInertia = body?.momentOfInertia ?? 0, - Radius = body?.radius ?? 0, - Mass = body?.mass ?? 0, - IsStatic = body?.isStatic ?? false, - Restitution = body?.restitution ?? 0 - }) - .ToArray(); -} - -static YmirFixtureSphereBody[] ToFixtureSphereBodies(IReadOnlyList? bodies) -{ - return (bodies ?? Array.Empty()) - .Select(body => new YmirFixtureSphereBody - { - Id = body?.id ?? "", - Position = ToFixtureVec3(body?.position ?? default), - Radius = body?.radius ?? 0 - }) - .ToArray(); -} - -static YmirFixtureField[] ToFixtureFields(IReadOnlyList? fields) -{ - return (fields ?? Array.Empty()) - .Select(field => new YmirFixtureField - { - Id = field?.id ?? "", - Position = ToFixtureVec2(field?.position ?? default), - Strength = field?.strength ?? 0, - Radius = field?.radius ?? 0 - }) - .ToArray(); -} - -static YmirFixtureContact[] ToFixtureContacts(IReadOnlyList? contacts) -{ - return (contacts ?? Array.Empty()) - .Select(contact => new YmirFixtureContact - { - BodyA = contact?.bodyA ?? "", - BodyB = contact?.bodyB ?? "", - Point = ToFixtureVec2(contact?.point ?? default), - Normal = ToFixtureVec2(contact?.normal ?? default), - Penetration = contact?.penetration ?? 0, - RelativeSpeed = contact?.relativeSpeed ?? 0 - }) - .ToArray(); -} - -static YmirFixtureCircleOverlapHit[] ToFixtureCircleOverlapHits(IReadOnlyList? hits) -{ - return (hits ?? Array.Empty()) - .Select(hit => new YmirFixtureCircleOverlapHit - { - BodyId = hit?.bodyId ?? "", - Point = ToFixtureVec2(hit?.point ?? default), - Normal = ToFixtureVec2(hit?.normal ?? default), - Penetration = hit?.penetration ?? 0, - Distance = hit?.distance ?? 0 - }) - .ToArray(); -} - -static YmirFixtureSphereOverlapHit[] ToFixtureSphereOverlapHits(IReadOnlyList? hits) -{ - return (hits ?? Array.Empty()) - .Select(hit => new YmirFixtureSphereOverlapHit - { - BodyId = hit?.bodyId ?? "", - Point = ToFixtureVec3(hit?.point ?? default), - Normal = ToFixtureVec3(hit?.normal ?? default), - Penetration = hit?.penetration ?? 0, - Distance = hit?.distance ?? 0 - }) - .ToArray(); -} - -static YmirFixtureCircleCastHit[] ToFixtureCircleCastHits(IReadOnlyList? hits) -{ - return (hits ?? Array.Empty()) - .Select(hit => new YmirFixtureCircleCastHit - { - BodyId = hit?.bodyId ?? "", - Point = ToFixtureVec2(hit?.point ?? default), - Normal = ToFixtureVec2(hit?.normal ?? default), - Distance = hit?.distance ?? 0 - }) - .ToArray(); -} - -static YmirFixtureSphereCastHit[] ToFixtureSphereCastHits(IReadOnlyList? hits) -{ - return (hits ?? Array.Empty()) - .Select(hit => new YmirFixtureSphereCastHit - { - BodyId = hit?.bodyId ?? "", - Point = ToFixtureVec3(hit?.point ?? default), - Normal = ToFixtureVec3(hit?.normal ?? default), - Distance = hit?.distance ?? 0 - }) - .ToArray(); -} - -static CultVec2 ToFixtureVec2(YmirVec2 value) -{ - return new CultVec2(value.x, value.y); -} - -static CultVec3 ToFixtureVec3(YmirVec3 value) -{ - return new CultVec3(value.x, value.y, value.z); -} - -static YmirWorld YmirWorldOf(params YmirPhysicsBody[] bodies) -{ - return new YmirWorld - { - time = 0, - bodies = bodies, - fields = Array.Empty() - }; -} - -static YmirPhysicsBody YmirBody(string id, float x, float y, float radius) -{ - return new YmirPhysicsBody - { - id = id, - position = YmirVec2Of(x, y), - direction = YmirVec2Of(1, 0), - radius = radius, - mass = 1, - momentOfInertia = 1 - }; -} - -static YmirSphereQueryBody YmirSphereBody(string id, float x, float y, float z, float radius) -{ - return new YmirSphereQueryBody - { - id = id, - position = YmirVec3Of(x, y, z), - radius = radius - }; -} - -static YmirVec2 YmirVec2Of(float x, float y) -{ - return new YmirVec2 { x = x, y = y }; -} - -static YmirVec3 YmirVec3Of(float x, float y, float z) -{ - return new YmirVec3 { x = x, y = y, z = z }; -} - -static AuthorityDecisionCase AuthorityCase( - string caseId, - AetheriaRuntimeVerseAuthorityPolicyDocument policy, - IReadOnlyList leases, - AetheriaRuntimeDaemonCommandDocument command, - string localRuntimeId) -{ - var decision = AetheriaRuntimeAuthorityRouter.Authorize(command, policy, leases, localRuntimeId); - return new AuthorityDecisionCase - { - CaseId = caseId, - LocalRuntimeId = localRuntimeId, - Policy = policy, - Leases = leases.ToArray(), - Command = command, - Decision = new AuthorityDecisionSnapshot - { - Authorized = decision.Authorized, - Reason = decision.Reason, - Mode = decision.Mode, - SubjectKey = decision.SubjectKey, - ClaimKind = decision.ClaimKind, - AuthorRuntimeId = decision.AuthorRuntimeId, - RuleId = decision.RuleId - } - }; -} - -static AetheriaRuntimeVerseAuthorityPolicyDocument HostPolicy(string verseId, string hostRuntimeId) -{ - return new AetheriaRuntimeVerseAuthorityPolicyDocument - { - VerseId = verseId, - PolicyId = "aetheria.freeze.host-authoritative.v1", - HostRuntimeId = hostRuntimeId, - DefaultMode = AetheriaRuntimeAuthorityModes.HostAuthoritative, - DeploymentMode = AetheriaRuntimeVerseDeploymentModes.DedicatedDaemon, - UpdatedAtUtc = "2026-01-01T00:00:00.0000000Z", - Rules = new[] - { - new AetheriaRuntimeAuthorityRule - { - RuleId = "freeze.host.default", - SubjectPrefix = "*", - ClaimKinds = new[] { AetheriaRuntimeClaimKinds.Any }, - Mode = AetheriaRuntimeAuthorityModes.HostAuthoritative - } - } - }; -} - -static AetheriaRuntimeVerseAuthorityPolicyDocument DelegatedPolicy( - string verseId, - string hostRuntimeId, - string subjectPrefix, - string claimKind, - string runtimeId) -{ - return new AetheriaRuntimeVerseAuthorityPolicyDocument - { - VerseId = verseId, - PolicyId = "aetheria.freeze.delegated.v1", - HostRuntimeId = hostRuntimeId, - DefaultMode = AetheriaRuntimeAuthorityModes.HostAuthoritative, - DeploymentMode = AetheriaRuntimeVerseDeploymentModes.DistributedTrusted, - UpdatedAtUtc = "2026-01-01T00:00:00.0000000Z", - Rules = new[] - { - new AetheriaRuntimeAuthorityRule - { - RuleId = "freeze.delegated.subject-claim", - SubjectPrefix = subjectPrefix, - ClaimKinds = new[] { claimKind }, - Mode = AetheriaRuntimeAuthorityModes.DelegatedRuntime, - RuntimeIds = new[] { runtimeId }, - Priority = 100 - }, - new AetheriaRuntimeAuthorityRule - { - RuleId = "freeze.host.default", - SubjectPrefix = "*", - ClaimKinds = new[] { AetheriaRuntimeClaimKinds.Any }, - Mode = AetheriaRuntimeAuthorityModes.HostAuthoritative - } - } - }; -} - -static AetheriaRuntimeVerseAuthorityPolicyDocument LeasePolicy( - string verseId, - string hostRuntimeId, - string subjectPrefix, - string claimKind) -{ - return new AetheriaRuntimeVerseAuthorityPolicyDocument - { - VerseId = verseId, - PolicyId = "aetheria.freeze.interest-lease.v1", - HostRuntimeId = hostRuntimeId, - DefaultMode = AetheriaRuntimeAuthorityModes.HostAuthoritative, - DeploymentMode = AetheriaRuntimeVerseDeploymentModes.DistributedTrusted, - UpdatedAtUtc = "2026-01-01T00:00:00.0000000Z", - Rules = new[] - { - new AetheriaRuntimeAuthorityRule - { - RuleId = "freeze.lease.subject-claim", - SubjectPrefix = subjectPrefix, - ClaimKinds = new[] { claimKind }, - Mode = AetheriaRuntimeAuthorityModes.InterestLease, - LeaseScope = "nearby-interest", - Priority = 100 - }, - new AetheriaRuntimeAuthorityRule - { - RuleId = "freeze.host.default", - SubjectPrefix = "*", - ClaimKinds = new[] { AetheriaRuntimeClaimKinds.Any }, - Mode = AetheriaRuntimeAuthorityModes.HostAuthoritative - } - } - }; -} - -static AetheriaRuntimeVerseAuthorityPolicyDocument OwningPolicy(string verseId, string hostRuntimeId, string subjectPrefix) -{ - return new AetheriaRuntimeVerseAuthorityPolicyDocument - { - VerseId = verseId, - PolicyId = "aetheria.freeze.owning-runtime.v1", - HostRuntimeId = hostRuntimeId, - DefaultMode = AetheriaRuntimeAuthorityModes.HostAuthoritative, - DeploymentMode = AetheriaRuntimeVerseDeploymentModes.DistributedTrusted, - UpdatedAtUtc = "2026-01-01T00:00:00.0000000Z", - Rules = new[] - { - new AetheriaRuntimeAuthorityRule - { - RuleId = "freeze.owning.subject", - SubjectPrefix = subjectPrefix, - ClaimKinds = new[] { AetheriaRuntimeClaimKinds.Movement }, - Mode = AetheriaRuntimeAuthorityModes.OwningRuntime, - Priority = 100 - } - } - }; -} - -static AetheriaRuntimeDaemonCommandDocument AuthorityCommand( - string commandId, - AetheriaRuntimeDaemonCommandKinds kind, - string actor, - string authorRuntimeId, - Action? configure = null) -{ - var command = Command(commandId, kind, actor, configure); - command.AuthorRuntimeId = authorRuntimeId; - command.ClientId = authorRuntimeId; - return command; -} - -static AetheriaRuntimeDaemonCommandDocument Command( - string commandId, - AetheriaRuntimeDaemonCommandKinds kind, - string actor, - Action? configure = null) -{ - var command = AetheriaRuntimeDaemonCommandDocument.Create( - kind, - "aetheria-freeze-fixture", - "freeze", - observedFrameId: -1, - actorEntityKey: actor); - command.CommandId = commandId; - command.IssuedAtUtc = "2026-01-01T00:00:00.0000000Z"; - configure?.Invoke(command); - return command; -} - -static int ResolveSelectedEntityIndex(AetheriaRuntimeDaemonFrameDocument frame) -{ - var contextRun = frame.Run ?? new AetheriaRuntimeRunCheckpointCommit(); - var current = contextRun.CurrentEntityKey ?? ""; - var marker = ".entity."; - var markerIndex = current.LastIndexOf(marker, StringComparison.Ordinal); - if (markerIndex >= 0) - { - var start = markerIndex + marker.Length; - var end = current.IndexOf('.', start); - var token = end < 0 ? current[start..] : current[start..end]; - if (int.TryParse(token, out var parsed)) - return parsed; - } - - var currentZone = (contextRun.Zones ?? Array.Empty()) - .FirstOrDefault(zone => zone != null && zone.ZoneIndex == contextRun.CurrentZoneIndex) ?? - (contextRun.Zones ?? Array.Empty()).FirstOrDefault(); - return (currentZone?.Entities ?? Array.Empty()) - .FirstOrDefault()?.EntityIndex ?? -1; -} - -static void WriteArtifact(string outputDirectory, FreezeManifest manifest, string name, string schemaId, T document) -{ - var jsonFile = name + ".json"; - var msgpackFile = name + ".msgpack"; - WriteJson(Path.Combine(outputDirectory, jsonFile), document); - File.WriteAllBytes(Path.Combine(outputDirectory, msgpackFile), MessagePackSerializer.Serialize(document)); - manifest.Artifacts.Add(new FreezeArtifact - { - Name = name, - SchemaId = schemaId, - Json = jsonFile, - MessagePack = msgpackFile, - DocumentType = typeof(T).FullName ?? typeof(T).Name - }); -} - -static void WriteJson(string path, T value) -{ - var json = JsonSerializer.Serialize(value, FreezeJson.Options); - File.WriteAllText(path, json); -} - -static string Quote(string value) -{ - return "\"" + value.Replace("\"", "\\\"", StringComparison.Ordinal) + "\""; -} - -internal sealed class FreezeManifest -{ - public string Scenario { get; set; } = ""; - public string GeneratedAtUtc { get; set; } = ""; - public string Root { get; set; } = ""; - public string StatePath { get; set; } = ""; - public string OutputDirectory { get; set; } = ""; - public bool RanDaemon { get; set; } - public int SeededCommandCount { get; set; } - public List Artifacts { get; } = new(); -} - -internal sealed class FreezeArtifact -{ - public string Name { get; set; } = ""; - public string SchemaId { get; set; } = ""; - public string DocumentType { get; set; } = ""; - public string Json { get; set; } = ""; - public string MessagePack { get; set; } = ""; -} - -[MessagePackObject] -internal sealed class YmirQueryFixtureSet -{ - [Key(0)] public string Schema { get; set; } = ""; - [Key(1)] public string GeneratedAtUtc { get; set; } = ""; - [Key(2)] public YmirQueryFixtureCase[] Cases { get; set; } = Array.Empty(); -} - -[MessagePackObject] -internal sealed class YmirQueryFixtureCase -{ - [Key(0)] public string CaseId { get; set; } = ""; - [Key(1)] public string QueryKind { get; set; } = ""; - [Key(2)] public string Intent { get; set; } = ""; - [Key(3)] public YmirQueryFixturePayload Request { get; set; } = new(); - [Key(4)] public YmirQueryFixturePayload Result { get; set; } = new(); -} - -[MessagePackObject] -internal sealed class YmirQueryFixturePayload -{ - [Key(0)] public float? DeltaTime { get; set; } - [Key(1)] public float? Radius { get; set; } - [Key(2)] public float? Distance { get; set; } - [Key(3)] public CultVec2? Center2 { get; set; } - [Key(4)] public CultVec3? Center3 { get; set; } - [Key(5)] public CultVec2? Origin2 { get; set; } - [Key(6)] public CultVec3? Origin3 { get; set; } - [Key(7)] public CultVec2? Direction2 { get; set; } - [Key(8)] public CultVec3? Direction3 { get; set; } - [Key(9)] public YmirFixtureWorld? World { get; set; } - [Key(10)] public YmirFixtureSphereBody[] SphereBodies { get; set; } = Array.Empty(); - [Key(11)] public YmirFixtureCircleOverlapHit[] CircleOverlapHits { get; set; } = Array.Empty(); - [Key(12)] public YmirFixtureSphereOverlapHit[] SphereOverlapHits { get; set; } = Array.Empty(); - [Key(13)] public YmirFixtureCircleCastHit[] CircleCastHits { get; set; } = Array.Empty(); - [Key(14)] public YmirFixtureSphereCastHit[] SphereCastHits { get; set; } = Array.Empty(); - [Key(15)] public YmirFixtureContact[] Contacts { get; set; } = Array.Empty(); -} - -[MessagePackObject] -internal sealed class YmirFixtureWorld -{ - [Key(0)] public float Time { get; set; } - [Key(1)] public YmirFixtureBody[] Bodies { get; set; } = Array.Empty(); - [Key(2)] public YmirFixtureField[] Fields { get; set; } = Array.Empty(); -} - -[MessagePackObject] -internal sealed class YmirFixtureBody -{ - [Key(0)] public string Id { get; set; } = ""; - [Key(1)] public CultVec2 Position { get; set; } - [Key(2)] public CultVec2 Velocity { get; set; } - [Key(3)] public CultVec2 Direction { get; set; } - [Key(4)] public float AngularVelocity { get; set; } - [Key(5)] public float Torque { get; set; } - [Key(6)] public float MomentOfInertia { get; set; } - [Key(7)] public float Radius { get; set; } - [Key(8)] public float Mass { get; set; } - [Key(9)] public bool IsStatic { get; set; } - [Key(10)] public float Restitution { get; set; } -} - -[MessagePackObject] -internal sealed class YmirFixtureSphereBody -{ - [Key(0)] public string Id { get; set; } = ""; - [Key(1)] public CultVec3 Position { get; set; } - [Key(2)] public float Radius { get; set; } -} - -[MessagePackObject] -internal sealed class YmirFixtureField -{ - [Key(0)] public string Id { get; set; } = ""; - [Key(1)] public CultVec2 Position { get; set; } - [Key(2)] public float Strength { get; set; } - [Key(3)] public float Radius { get; set; } -} - -[MessagePackObject] -internal sealed class YmirFixtureContact -{ - [Key(0)] public string BodyA { get; set; } = ""; - [Key(1)] public string BodyB { get; set; } = ""; - [Key(2)] public CultVec2 Point { get; set; } - [Key(3)] public CultVec2 Normal { get; set; } - [Key(4)] public float Penetration { get; set; } - [Key(5)] public float RelativeSpeed { get; set; } -} - -[MessagePackObject] -internal sealed class YmirFixtureCircleOverlapHit -{ - [Key(0)] public string BodyId { get; set; } = ""; - [Key(1)] public CultVec2 Point { get; set; } - [Key(2)] public CultVec2 Normal { get; set; } - [Key(3)] public float Penetration { get; set; } - [Key(4)] public float Distance { get; set; } -} - -[MessagePackObject] -internal sealed class YmirFixtureSphereOverlapHit -{ - [Key(0)] public string BodyId { get; set; } = ""; - [Key(1)] public CultVec3 Point { get; set; } - [Key(2)] public CultVec3 Normal { get; set; } - [Key(3)] public float Penetration { get; set; } - [Key(4)] public float Distance { get; set; } -} - -[MessagePackObject] -internal sealed class YmirFixtureCircleCastHit -{ - [Key(0)] public string BodyId { get; set; } = ""; - [Key(1)] public CultVec2 Point { get; set; } - [Key(2)] public CultVec2 Normal { get; set; } - [Key(3)] public float Distance { get; set; } -} - -[MessagePackObject] -internal sealed class YmirFixtureSphereCastHit -{ - [Key(0)] public string BodyId { get; set; } = ""; - [Key(1)] public CultVec3 Point { get; set; } - [Key(2)] public CultVec3 Normal { get; set; } - [Key(3)] public float Distance { get; set; } -} - -[MessagePackObject] -internal sealed class AuthorityDecisionFixtureSet -{ - [Key(0)] public string Schema { get; set; } = ""; - [Key(1)] public string GeneratedAtUtc { get; set; } = ""; - [Key(2)] public AuthorityDecisionCase[] Cases { get; set; } = Array.Empty(); -} - -[MessagePackObject] -internal sealed class AuthorityDecisionCase -{ - [Key(0)] public string CaseId { get; set; } = ""; - [Key(1)] public string LocalRuntimeId { get; set; } = ""; - [Key(2)] public AetheriaRuntimeVerseAuthorityPolicyDocument Policy { get; set; } = new(); - [Key(3)] public AetheriaRuntimeAuthorityLeaseDocument[] Leases { get; set; } = Array.Empty(); - [Key(4)] public AetheriaRuntimeDaemonCommandDocument Command { get; set; } = new(); - [Key(5)] public AuthorityDecisionSnapshot Decision { get; set; } = new(); -} - -[MessagePackObject] -internal sealed class AuthorityDecisionSnapshot -{ - [Key(0)] public bool Authorized { get; set; } - [Key(1)] public string Reason { get; set; } = ""; - [Key(2)] public string Mode { get; set; } = ""; - [Key(3)] public string SubjectKey { get; set; } = ""; - [Key(4)] public string ClaimKind { get; set; } = ""; - [Key(5)] public string AuthorRuntimeId { get; set; } = ""; - [Key(6)] public string RuleId { get; set; } = ""; -} - -internal static class FreezeJson -{ - public static readonly JsonSerializerOptions Options = new() - { - WriteIndented = true, - DefaultIgnoreCondition = JsonIgnoreCondition.Never, - Converters = { new JsonStringEnumConverter() } - }; -} - -internal sealed class FreezeOptions -{ - public string Root { get; init; } = ""; - public string StatePath { get; init; } = ""; - public string OutputDirectory { get; init; } = ""; - public bool RunDaemon { get; init; } = true; - public bool CleanState { get; init; } = true; - public bool IncludeCommandBatches { get; init; } = true; - public double ViewportMinX { get; init; } = -1000; - public double ViewportMinY { get; init; } = -1000; - public double ViewportMaxX { get; init; } = 1000; - public double ViewportMaxY { get; init; } = 1000; - - public static FreezeOptions Parse(IReadOnlyList args) - { - var root = FullPath(ReadOption(args, "--root"), Directory.GetCurrentDirectory()); - var defaultState = Path.Combine(root, "obj", "freeze", "aetheria-freeze-state.cc"); - var defaultOutput = Path.Combine(root, "Aetheria.State", "fixtures", "rust-rebuild-freeze"); - return new FreezeOptions - { - Root = root, - StatePath = FullPath(ReadOption(args, "--state"), defaultState), - OutputDirectory = FullPath(ReadOption(args, "--out"), defaultOutput), - RunDaemon = !HasFlag(args, "--no-run-daemon"), - CleanState = !HasFlag(args, "--no-clean-state"), - IncludeCommandBatches = !HasFlag(args, "--no-command-batches"), - ViewportMinX = ReadDouble(args, "--viewport-min-x") ?? -1000, - ViewportMinY = ReadDouble(args, "--viewport-min-y") ?? -1000, - ViewportMaxX = ReadDouble(args, "--viewport-max-x") ?? 1000, - ViewportMaxY = ReadDouble(args, "--viewport-max-y") ?? 1000 - }; - } - - private static string FullPath(string value, string fallback) - { - return Path.GetFullPath(string.IsNullOrWhiteSpace(value) ? fallback : value); - } - - private static string ReadOption(IReadOnlyList args, string name) - { - for (var i = 0; i < args.Count - 1; i++) - { - if (string.Equals(args[i], name, StringComparison.OrdinalIgnoreCase)) - return args[i + 1]; - } - - return ""; - } - - private static double? ReadDouble(IReadOnlyList args, string name) - { - return double.TryParse(ReadOption(args, name), out var value) ? value : null; - } - - private static bool HasFlag(IReadOnlyList args, string name) - { - return args.Any(arg => string.Equals(arg, name, StringComparison.OrdinalIgnoreCase)); - } -} diff --git a/Aetheria.State.Import/Aetheria.State.Import.csproj b/Aetheria.State.Import/Aetheria.State.Import.csproj deleted file mode 100644 index 8bc18d366..000000000 --- a/Aetheria.State.Import/Aetheria.State.Import.csproj +++ /dev/null @@ -1,18 +0,0 @@ - - - - - Exe - net10.0 - enable - latest - enable - - - - - - - - - diff --git a/Aetheria.State.Import/Program.cs b/Aetheria.State.Import/Program.cs deleted file mode 100644 index df9bc2683..000000000 --- a/Aetheria.State.Import/Program.cs +++ /dev/null @@ -1,1560 +0,0 @@ -using System.Security.Cryptography; -using Aetheria.State; -using Aetheria.State.Documents; -using Aetheria.State.Migration; -using GameCult.Aetheria.State.Verse; -using MessagePack; - -var root = args.Length > 0 ? Path.GetFullPath(args[0]) : Directory.GetCurrentDirectory(); -var statePath = args.Length > 1 - ? Path.GetFullPath(args[1]) - : AetheriaStatePaths.ResolveDefaultStatePath(root); -var sourceRoot = Path.GetRelativePath(Directory.GetCurrentDirectory(), root).Replace('\\', '/'); -var outputStatePath = Path.GetRelativePath(root, statePath).Replace('\\', '/'); - -var gameData = Path.Combine(root, "GameData"); -var catalogPath = Path.Combine(gameData, "AetherDB.msgpack"); -var supplementalCatalogPath = Path.Combine(gameData, "Legacy", "AetherDB.2021-03-05.msgpack"); -var recoveredHullCatalogPath = Path.Combine(gameData, "Legacy", "AetherDB.2021-04-14.msgpack"); -var nameFilesRoot = Path.Combine(gameData, "NameFile"); -var capturedAtUtc = DateTimeOffset.UtcNow.ToString("O"); - -var catalog = CaptureFile(root, catalogPath); -var supplementalCatalog = CaptureFile(root, supplementalCatalogPath); -var recoveredHullCatalog = CaptureFile(root, recoveredHullCatalogPath); -var nameFiles = Directory.Exists(nameFilesRoot) - ? Directory.EnumerateFiles(nameFilesRoot, "*.msgpack", SearchOption.TopDirectoryOnly) - .OrderBy(path => path, StringComparer.OrdinalIgnoreCase) - .Select(path => CaptureFile(root, path)) - .ToArray() - : []; -var currentEntries = LegacyCatalogReader.Read(catalogPath); -var currentLegacyIds = currentEntries.Select(entry => entry.Summary.LegacyId).ToHashSet(StringComparer.Ordinal); -var supplementalMineEntries = LegacyCatalogReader.Read(supplementalCatalogPath) - .Where(entry => entry.ItemDefinition != null && IsMineItem(entry.ItemDefinition)) - .Where(entry => !currentLegacyIds.Contains(entry.Summary.LegacyId)) - .ToArray(); -var recoveredGameplayEntries = LegacyCatalogReader.Read( - recoveredHullCatalogPath, - LegacyCatalogLayout.April2021) - .Where(entry => entry.ItemDefinition != null && IsRecoveredGameplayItem(entry.ItemDefinition)) - .Where(entry => !currentLegacyIds.Contains(entry.Summary.LegacyId)) - .ToArray(); -var entries = currentEntries - .Concat(supplementalMineEntries) - .Concat(recoveredGameplayEntries) - .ToArray(); -var nameFileEntries = Directory.Exists(nameFilesRoot) - ? Directory.EnumerateFiles(nameFilesRoot, "*.msgpack", SearchOption.TopDirectoryOnly) - .OrderBy(path => path, StringComparer.OrdinalIgnoreCase) - .Select(LegacyCatalogReader.ReadSingle) - .ToArray() - : []; -var itemDefinitions = entries - .Where(entry => entry.ItemDefinition != null) - .Select(entry => ProjectCatalogRarity(ProjectStationSensorMount(ProjectDeployableWeapon(entry.ItemDefinition!)))) - .ToArray(); -var corporations = entries - .Where(entry => entry.Corporation != null) - .Select(entry => entry.Corporation!) - .ToArray(); -var parsedNameFiles = entries.Concat(nameFileEntries) - .Where(entry => entry.NameFile != null) - .Select(entry => entry.NameFile!) - .ToArray(); - -static AetheriaItemDefinition ProjectDeployableWeapon(AetheriaItemDefinition item) -{ - if (!IsMineItem(item)) - return item; - - item.WeaponType = "Mine"; - item.BehaviorPayloads = (item.BehaviorPayloads ?? []) - .Select(behavior => string.Equals(behavior.Kind, "InstantWeapon", StringComparison.Ordinal) - ? new AetheriaBehaviorPayload - { - UnionKey = behavior.UnionKey, - Kind = "DeployableWeapon", - Group = behavior.Group, - BehaviorId = behavior.BehaviorId, - Fields = behavior.Fields - .Where(field => field.Key < 26 || field.Key > 30) - .Concat([ - NumberBehaviorField(26, 2), - NumberBehaviorField(27, 30), - PerformanceBehaviorField(28, 25), - NumberBehaviorField(29, 2), - PerformanceBehaviorField(30, 25) - ]) - .OrderBy(field => field.Key) - .ToArray() - } - : behavior) - .ToArray(); - item.BehaviorKinds = item.BehaviorPayloads - .Select(behavior => behavior.Kind) - .Distinct(StringComparer.Ordinal) - .OrderBy(kind => kind, StringComparer.Ordinal) - .ToArray(); - item.BehaviorCount = item.BehaviorPayloads.Length; - item.Tags = (item.Tags ?? []) - .Where(tag => !string.Equals(tag, "behavior:InstantWeapon", StringComparison.Ordinal)) - .Concat(["behavior:DeployableWeapon"]) - .Distinct(StringComparer.Ordinal) - .ToArray(); - return item; -} - -static AetheriaItemDefinition ProjectStationSensorMount(AetheriaItemDefinition item) -{ - const string stationHullLegacyId = "82efc0a5-1ba5-4ff3-a281-b2e6e247521d"; - if (!string.Equals(item.LegacyId, stationHullLegacyId, StringComparison.OrdinalIgnoreCase) || - (item.Hardpoints ?? []).Any(hardpoint => string.Equals(hardpoint.Type, "Sensors", StringComparison.Ordinal))) - { - return item; - } - - const int positionX = 5; - const int positionY = 1; - var mountCells = new[] - { - new AetheriaShapeCell { X = 0, Y = 0 }, - new AetheriaShapeCell { X = 1, Y = 0 }, - new AetheriaShapeCell { X = 0, Y = 1 }, - new AetheriaShapeCell { X = 1, Y = 1 } - }; - var hullCells = (item.ShapeCells ?? []) - .Select(cell => (cell.X, cell.Y)) - .ToHashSet(); - if (mountCells.Any(cell => !hullCells.Contains((positionX + cell.X, positionY + cell.Y)))) - { - throw new InvalidDataException("Recovered station sensor mount does not fit the decoded station hull shape."); - } - - item.Hardpoints = (item.Hardpoints ?? []) - .Append(new AetheriaItemHardpoint - { - Type = "Sensors", - PositionX = positionX, - PositionY = positionY, - ShapeWidth = 2, - ShapeHeight = 2, - OccupiedCells = 4, - ShapeCells = mountCells, - Rotation = "None" - }) - .ToArray(); - return item; -} - -static bool IsMineItem(AetheriaItemDefinition item) -{ - var hasMineEffect = (item.BehaviorPayloads ?? []).Any(behavior => - string.Equals(behavior.Kind, "InstantWeapon", StringComparison.Ordinal) && - behavior.Fields.Any(field => field.Key == 8 && - field.Value.StringValue.EndsWith("/Mine Launcher.prefab", StringComparison.OrdinalIgnoreCase))); - return string.Equals(item.WeaponType, "Mine", StringComparison.Ordinal) || hasMineEffect; -} - -static bool IsRecoveredGameplayItem(AetheriaItemDefinition item) -{ - return string.Equals(item.LegacyId, "c4b20032-8cd3-4206-807f-97b296797425", StringComparison.OrdinalIgnoreCase) || - string.Equals(item.LegacyId, "bb5d9f73-612f-4060-8fb4-f72d6b93b616", StringComparison.OrdinalIgnoreCase) || - string.Equals(item.HardpointType, "Thruster", StringComparison.Ordinal); -} - -static AetheriaItemDefinition ProjectCatalogRarity(AetheriaItemDefinition item) -{ - var tags = (item.Tags ?? []).AsEnumerable(); - if (IsRecoveredGameplayItem(item)) - tags = tags.Append("provenance:recovered:2021-04-14"); - var isRareHull = string.Equals(item.Name, "LonginusX", StringComparison.Ordinal) && - string.Equals(item.HullType, "Ship", StringComparison.Ordinal); - var isRareDrive = (item.BehaviorKinds ?? []).Contains("AetherDrive", StringComparer.Ordinal); - if (isRareHull || isRareDrive) - tags = tags.Append("rarity:rare"); - item.Tags = tags - .Distinct(StringComparer.Ordinal) - .ToArray(); - return item; -} - -static AetheriaBehaviorField NumberBehaviorField(int key, double value) => new() -{ - Key = key, - Value = new AetheriaBehaviorValue { Kind = "number", NumberValue = value } -}; - -static AetheriaBehaviorField PerformanceBehaviorField(int key, double value) => new() -{ - Key = key, - Value = new AetheriaBehaviorValue - { - Kind = "performance-stat", - Children = - [ - new AetheriaBehaviorValue { Kind = "number", NumberValue = value }, - new AetheriaBehaviorValue { Kind = "number", NumberValue = value }, - new AetheriaBehaviorValue { Kind = "number" }, - new AetheriaBehaviorValue { Kind = "number" }, - new AetheriaBehaviorValue { Kind = "number" } - ] - } -}; - -if (args.Any(argument => string.Equals(argument, "--merge-mine", StringComparison.Ordinal))) -{ - var mine = itemDefinitions.SingleOrDefault(item => string.Equals(item.WeaponType, "Mine", StringComparison.Ordinal)) - ?? throw new InvalidDataException("Supplemental catalog did not contain the recovered Mine Launcher item."); - await using var mergeNode = await AetheriaStateNode.OpenAsync(statePath, "aetheria-mine-catalog-merge"); - await mergeNode.MutableDocument(AetheriaCatalogKeys.ItemDefinitionFromLegacyId(mine.LegacyId)) - .ReplaceAsync(mine); - await mergeNode.FlushAsync(); - await mergeNode.RefreshRuntimeCatalogAsync(); - var merged = await mergeNode.RuntimeCatalog().LatestAsync().ConfigureAwait(false); - var mergedMine = merged.FindItem(mine.ItemKey); - if (mergedMine == null || !mergedMine.BehaviorPayloads.Any(behavior => - string.Equals(behavior.Kind, "DeployableWeapon", StringComparison.Ordinal))) - throw new InvalidDataException("Mine Launcher merge did not become visible through the runtime catalog."); - Console.WriteLine($"Merged Mine Launcher into typed state: {statePath}"); - Console.WriteLine($"Item: {mine.ItemKey}"); - return; -} - -if (args.Any(argument => string.Equals(argument, "--merge-station-sensor", StringComparison.Ordinal))) -{ - const string stationHullLegacyId = "82efc0a5-1ba5-4ff3-a281-b2e6e247521d"; - var stationHull = itemDefinitions.Single(item => - string.Equals(item.LegacyId, stationHullLegacyId, StringComparison.OrdinalIgnoreCase)); - await using var mergeNode = await AetheriaStateNode.OpenAsync(statePath, "aetheria-station-sensor-catalog-merge"); - await mergeNode.MutableDocument(AetheriaCatalogKeys.ItemDefinitionFromLegacyId(stationHull.LegacyId)) - .ReplaceAsync(stationHull); - await mergeNode.FlushAsync(); - await mergeNode.RefreshRuntimeCatalogAsync(); - var merged = await mergeNode.RuntimeCatalog().LatestAsync().ConfigureAwait(false); - var mergedHull = merged.FindItem(stationHull.ItemKey); - if (mergedHull == null || !(mergedHull.Hardpoints ?? []).Any(hardpoint => - string.Equals(hardpoint.Type, "Sensors", StringComparison.Ordinal) && - hardpoint.OccupiedCells == 4)) - { - throw new InvalidDataException("Station sensor hardpoint merge did not become visible through the runtime catalog."); - } - Console.WriteLine($"Merged station sensor hardpoint into typed state: {statePath}"); - Console.WriteLine($"Hull: {stationHull.ItemKey}"); - return; -} - -ResetMaterializedStateOutput(statePath); - -await using var node = await AetheriaStateNode.OpenAsync( - statePath, - "aetheria-legacy-catalog-import", - useDirectoryStore: true); - -await node.MutableDocument(AetheriaStateNode.LegacyCatalogQuarantineKey) - .ReplaceAsync(new AetheriaLegacyCatalogQuarantine -{ - RootPath = sourceRoot, - CapturedAtUtc = capturedAtUtc, - CatalogFile = catalog.RelativePath, - CatalogFingerprint = catalog.Fingerprint, - CatalogBytes = catalog.Bytes, - NameFiles = nameFiles, - SupplementalCatalogFiles = new[] { supplementalCatalog, recoveredHullCatalog } - .Where(file => file.Bytes > 0) - .ToArray(), - Notes = - [ - "This document quarantines legacy catalog file facts and records the bounded raw payload mapping pass. It does not grant old MessagePack files runtime state authority.", - "AetherDB.msgpack and NameFile/*.msgpack remain migration inputs until Unity runtime reads typed Aetheria catalog documents directly.", - "The 2021-03-05 supplemental catalog contributes only the missing Mine Launcher item and remains quarantined provenance, not runtime authority.", - "The 2021-04-14 catalog contributes only the missing common Longinus and Djinni hull rows plus missing Thruster equipment required to outfit their authored mounts. It preserves the post-drive catalog's distinct Longinus/LonginusX identities after the later modular database lost the common rows." - ] -}); - -await node.MutableDocument(AetheriaStateNode.MigrationLedgerKey) - .ReplaceAsync(new AetheriaMigrationLedger -{ - Source = LegacyMigrationBoundary.LegacyGameDataFile, - SourceFingerprint = catalog.Fingerprint, - LastMigrationAtUtc = capturedAtUtc, - Counts = - [ - new AetheriaMigrationCount - { - DocumentType = "aetheria.legacy_catalog_quarantine.v1", - Count = 1 - }, - new AetheriaMigrationCount - { - DocumentType = "legacy.catalog_file", - Count = catalog.Bytes > 0 ? 1 : 0 - }, - new AetheriaMigrationCount - { - DocumentType = "legacy.name_file", - Count = nameFiles.Length - }, - new AetheriaMigrationCount - { - DocumentType = "aetheria.item_definition.v1", - Count = itemDefinitions.Length - }, - new AetheriaMigrationCount - { - DocumentType = "aetheria.corporation.v2", - Count = corporations.Length - }, - new AetheriaMigrationCount - { - DocumentType = "aetheria.name_file.v2", - Count = parsedNameFiles.Length - } - ], - LegacyCatalogEntries = entries.Select(entry => entry.Summary).ToArray(), - Notes = - [ - "Legacy catalog quarantine captured file fingerprints and mapped stable old MessagePack union payload fields into typed CultCache documents.", - "The mapper intentionally reads only stable scalar/catalog fields. Runtime object graphs, behavior payloads, and Unity-specific shapes remain legacy until dedicated typed documents exist.", - "Recovered common Longinus and Djinni hulls and their missing Thruster equipment come from the fingerprinted 2021-04-14 catalog; LonginusX and AetherDrive items are explicitly tagged as rare generation candidates.", - $"State path: {outputStatePath}" - ] -}); - -foreach (var item in itemDefinitions) -{ - await node.MutableDocument(AetheriaCatalogKeys.ItemDefinitionFromLegacyId(item.LegacyId)) - .ReplaceAsync(item); -} - -foreach (var corporation in corporations) -{ - await node.MutableDocument(AetheriaCatalogKeys.CorporationFromLegacyId(corporation.LegacyId)) - .ReplaceAsync(corporation); -} - -foreach (var nameFile in parsedNameFiles) -{ - await node.MutableDocument(AetheriaCatalogKeys.NameFileFromLegacyId(nameFile.LegacyId)) - .ReplaceAsync(nameFile); -} - -await node.FlushAsync(); -var runtimeCatalog = await node.RefreshRuntimeCatalogAsync().ConfigureAwait(false); -var importedCorporationKeys = runtimeCatalog.Corporations - .Select(corporation => corporation.CorporationKey) - .Where(key => !string.IsNullOrWhiteSpace(key)) - .Distinct(StringComparer.Ordinal) - .ToArray(); -if (importedCorporationKeys.Length != corporations.Length) -{ - throw new InvalidDataException( - $"Runtime catalog projected {importedCorporationKeys.Length} corporation keys from {corporations.Length} typed corporation documents."); -} -var runtimeMines = runtimeCatalog.Items - .Where(item => string.Equals(item.WeaponType, "Mine", StringComparison.Ordinal)) - .ToArray(); -if (runtimeMines.Any(item => !item.BehaviorPayloads.Any(behavior => - string.Equals(behavior.Kind, "DeployableWeapon", StringComparison.Ordinal) && - HasNumber(behavior, 26, 2) && - HasNumber(behavior, 27, 30) && - HasPerformanceStat(behavior, 28, 25) && - HasNumber(behavior, 29, 2) && - HasPerformanceStat(behavior, 30, 25)))) -{ - var diagnostic = string.Join("; ", runtimeMines.Select(item => - $"{item.Name} [{string.Join(",", item.BehaviorPayloads.Select(behavior => behavior.Kind + ":" + string.Join("/", behavior.Fields.Select(field => field.Key))))}]")); - throw new InvalidDataException($"Mine catalog import did not preserve the canonical deployable lifecycle: {diagnostic}"); -} -await node.MutableDocument(AetheriaStateNode.TradeValuePolicyKey) - .ReplaceAsync(AetheriaRuntimeStateMapper.ToTradeValuePolicy( - runtimeCatalog.TradeValueSettings, - capturedAtUtc)); -await node.FlushAsync(); -await node.RefreshRuntimeCatalogAsync().ConfigureAwait(false); -await node.CatalogSurface().LatestAsync().ConfigureAwait(false); - -await node.FlushAsync(); - -await using (var reopenedNode = await AetheriaStateNode.OpenAsync( - statePath, - "aetheria-legacy-catalog-import-reopen-proof", - useDirectoryStore: true)) -{ - var reopenedCatalog = await reopenedNode.RuntimeCatalog().LatestAsync().ConfigureAwait(false); - var reopenedCorporationKeys = reopenedCatalog.Corporations - .Select(corporation => corporation.CorporationKey) - .Where(key => !string.IsNullOrWhiteSpace(key)) - .Distinct(StringComparer.Ordinal) - .ToArray(); - if (reopenedCorporationKeys.Length != importedCorporationKeys.Length) - { - throw new InvalidDataException( - $"Reopened runtime catalog projected {reopenedCorporationKeys.Length} corporation keys after the importer flushed {importedCorporationKeys.Length}."); - } -} - -Console.WriteLine($"Aetheria legacy catalog mapped into typed state: {statePath}"); -Console.WriteLine($"Catalog: {catalog.RelativePath} {catalog.Bytes} bytes {catalog.Fingerprint}"); -Console.WriteLine($"Name files: {nameFiles.Length}"); -Console.WriteLine($"Mapped items: {itemDefinitions.Length}"); -Console.WriteLine($"Mapped factions: {corporations.Length}"); -Console.WriteLine($"Runtime corporation keys: {string.Join(", ", importedCorporationKeys)}"); -Console.WriteLine($"Mapped name files: {parsedNameFiles.Length}"); -Console.WriteLine($"Mapped mine weapons: {string.Join(", ", runtimeMines.Select(item => item.Name + " (" + item.ItemKey + ")"))}"); - -static bool HasNumber(AetheriaRuntimeBehaviorPayload behavior, int key, double expected) => - behavior.Fields.Any(field => field.Key == key && Math.Abs(field.Value.NumberValue - expected) < 0.000001); - -static bool HasPerformanceStat(AetheriaRuntimeBehaviorPayload behavior, int key, double expected) -{ - var field = behavior.Fields.FirstOrDefault(candidate => candidate.Key == key); - return field?.Value.Children.Count >= 2 && - Math.Abs(field.Value.Children[0].NumberValue - expected) < 0.000001 && - Math.Abs(field.Value.Children[1].NumberValue - expected) < 0.000001; -} - -static AetheriaLegacyCatalogFile CaptureFile(string root, string path) -{ - if (!File.Exists(path)) - { - return new AetheriaLegacyCatalogFile - { - RelativePath = Path.GetRelativePath(root, path).Replace('\\', '/'), - Fingerprint = "missing", - Bytes = 0 - }; - } - - using var stream = File.OpenRead(path); - var hash = SHA256.HashData(stream); - return new AetheriaLegacyCatalogFile - { - RelativePath = Path.GetRelativePath(root, path).Replace('\\', '/'), - Fingerprint = Convert.ToHexString(hash).ToLowerInvariant(), - Bytes = stream.Length - }; -} - -static void ResetMaterializedStateOutput(string statePath) -{ - DeleteFileIfExists(statePath); - DeleteDirectoryIfExists($"{statePath}.records"); - - var meshDirectory = Path.ChangeExtension(statePath, ".cultmesh"); - if (!string.Equals(meshDirectory, statePath, StringComparison.OrdinalIgnoreCase)) - { - DeleteDirectoryIfExists(meshDirectory); - } -} - -static void DeleteFileIfExists(string path) -{ - if (File.Exists(path)) - { - File.Delete(path); - } -} - -static void DeleteDirectoryIfExists(string path) -{ - if (Directory.Exists(path)) - { - Directory.Delete(path, recursive: true); - } -} - -internal enum LegacyCatalogLayout -{ - Current, - April2021 -} - -internal static class LegacyCatalogReader -{ - private static readonly Dictionary UnionNames = new() - { - [0] = "SimpleCommodityData", - [1] = "CompoundCommodityData", - [2] = "GearData", - [3] = "HullData", - [8] = "GalaxyMapLayerData", - [9] = "NameFile", - [11] = "PlayerData", - [13] = "Faction", - [15] = "OrbitData", - [16] = "BodyData", - [17] = "PersonalityAttribute", - [25] = "AsteroidBeltData", - [26] = "GasGiantData", - [27] = "SunData", - [28] = "PlanetData", - [29] = "CargoBayData", - [30] = "DockingBayData", - [31] = "WeaponItemData" - }; - - private static readonly Dictionary ItemCategories = new() - { - [0] = "simple-commodity", - [1] = "compound-commodity", - [2] = "gear", - [3] = "hull", - [29] = "cargo-bay", - [30] = "docking-bay", - [31] = "weapon" - }; - - private static readonly HashSet ItemUnionKeys = [0, 1, 2, 3, 29, 30, 31]; - private static readonly Dictionary HardpointTypes = CreateEnumMap([ - "Hull", - "Tool", - "Thermal", - "Thruster", - "WarpDrive", - "Reactor", - "Radiator", - "Shield", - "Sensors", - "Energy", - "Ballistic", - "Launcher", - "ControlModule", - "AetherDrive" - ]); - private static readonly Dictionary ItemRotations = CreateEnumMap([ - "None", - "CounterClockwise", - "Reversed", - "Clockwise" - ]); - private static readonly Dictionary HullTypes = CreateEnumMap(["Ship", "Station", "Turret"]); - private static readonly Dictionary SimpleCommodityCategories = CreateEnumMap([ - "Minerals", - "Metals", - "Alloys", - "Compounds", - "Organics", - "Ammo", - "Consumer" - ]); - private static readonly Dictionary CompoundCommodityCategories = CreateEnumMap([ - "Wearables", - "Consumables", - "Luxuries", - "Tools", - "Manufacturing", - "Assemblies" - ]); - private static readonly Dictionary WeaponRanges = CreateEnumMap(["Melee", "Short", "Medium", "Long"]); - private static readonly Dictionary WeaponCalibers = CreateEnumMap(["Small", "Medium", "Large", "ExtraLarge"]); - private static readonly Dictionary WeaponTypes = CreateEnumMap([ - "ElectromagneticallyPropelled", - "ExplosivelyPropelled", - "Laser", - "Electrostatic", - "ParticleProjection", - "Missile", - "MicroMissile", - "SplitMissile", - "Mine", - "Jet" - ]); - private static readonly Dictionary WeaponFireTypeFlags = new() - { - [1 << 0] = "Direct", - [1 << 1] = "Guided", - [1 << 2] = "Seeking", - [1 << 3] = "Continuous", - [1 << 4] = "Charged" - }; - private static readonly Dictionary WeaponModifierFlags = new() - { - [1 << 0] = "Airburst", - [1 << 1] = "Incendiary", - [1 << 2] = "ArmorPenetrating", - [1 << 3] = "NegativeEntropy", - [1 << 4] = "RapidFire", - [1 << 5] = "Burst", - [1 << 6] = "Cluster" - }; - private static readonly Dictionary BehaviorUnionNames = new() - { - [0] = "GuidedWeapon", - [1] = "Launcher", - [2] = "Reactor", - [3] = "Radiator", - [4] = "StatModifier", - [5] = "Sensor", - [6] = "Reflector", - [7] = "Shield", - [8] = "Thruster", - [9] = "Wear", - [10] = "VelocityConversion", - [11] = "VelocityLimit", - [12] = "AetherDrive", - [15] = "Cooldown", - [16] = "Heat", - [18] = "ItemUsage", - [20] = "Switch", - [21] = "Trigger", - [22] = "Visibility", - [23] = "Thermotoggle", - [24] = "EnergyDraw", - [26] = "MiningTool", - [28] = "ResourceScanner", - [31] = "Capacitor", - [32] = "Cockpit", - [33] = "HeatStorage", - [34] = "TurretController", - [35] = "InstantWeapon", - [36] = "ConstantWeapon", - [37] = "ChargedWeapon", - [38] = "AutoWeapon" - }; - - public static IReadOnlyList Read( - string catalogPath, - LegacyCatalogLayout layout = LegacyCatalogLayout.Current) - { - if (!File.Exists(catalogPath)) - { - return []; - } - - var bytes = File.ReadAllBytes(catalogPath); - var reader = new MessagePackReader(bytes); - var count = reader.ReadArrayHeader(); - var entries = new List(count); - for (var i = 0; i < count; i++) - { - entries.Add(ReadEntry(ref reader, layout)); - } - - return entries; - } - - public static LegacyCatalogEntry ReadSingle(string path) - { - var bytes = File.ReadAllBytes(path); - var reader = new MessagePackReader(bytes); - return ReadEntry(ref reader, LegacyCatalogLayout.Current); - } - - private static LegacyCatalogEntry ReadEntry( - ref MessagePackReader reader, - LegacyCatalogLayout layout) - { - var unionHeader = reader.ReadArrayHeader(); - if (unionHeader != 2) - { - throw new InvalidDataException($"Unexpected legacy catalog union header length: {unionHeader}."); - } - - var unionKey = reader.ReadInt32(); - var unionName = UnionNames.TryGetValue(unionKey, out var knownName) ? knownName : $"union:{unionKey}"; - var payload = ReadArrayPayload(ref reader); - var legacyId = GetGuid(payload, 0); - var name = GetString(payload, 1); - var description = GetString(payload, 2); - var shape = ReadShape(payload, 5); - var interiorShape = ReadInteriorShape(unionKey, payload, shape); - var dockingMaxSize = unionKey == 30 ? ReadPoint(payload, 25) : PointFacts.Empty; - var hardpoints = unionKey == 3 - ? ReadHardpoints(payload, layout == LegacyCatalogLayout.April2021 ? 20 : 23) - : []; - var behaviorKey = unionKey == 31 || unionKey is 2 or 3 or 29 or 30 ? 11 : 10; - var behaviorPayloads = ReadBehaviorPayloads(payload, behaviorKey); - var itemCategory = GetItemCategory(unionKey); - var isConsumable = itemCategory == "consumable"; - var behaviorKinds = behaviorPayloads - .Select(behavior => behavior.Kind) - .Where(kind => !string.IsNullOrWhiteSpace(kind)) - .Distinct(StringComparer.OrdinalIgnoreCase) - .OrderBy(kind => kind, StringComparer.OrdinalIgnoreCase) - .ToArray(); - var summary = new AetheriaLegacyCatalogEntrySummary - { - LegacyId = legacyId, - UnionKey = unionKey, - DocumentKind = unionName, - Name = name, - Note = "Mapped from stable legacy MessagePack array fields." - }; - - return new LegacyCatalogEntry( - summary, - ItemUnionKeys.Contains(unionKey) - ? new AetheriaItemDefinition - { - Name = name, - Category = itemCategory, - LegacyId = legacyId, - Description = description, - Mass = GetDouble(payload, 4), - SpecificHeat = GetPositiveDoubleOrDefault(payload, 6, 1), - Conductivity = GetPositiveDoubleOrDefault(payload, 7, 1), - Volume = shape.OccupiedCells, - ManufacturerLegacyId = GetOptionalGuid(payload, 3), - Price = GetInt(payload, 8), - ShapeWidth = shape.Width, - ShapeHeight = shape.Height, - OccupiedCells = shape.OccupiedCells, - ShapeCells = shape.Cells, - InteriorShapeWidth = interiorShape.Width, - InteriorShapeHeight = interiorShape.Height, - InteriorOccupiedCells = interiorShape.OccupiedCells, - InteriorShapeCells = interiorShape.Cells, - Hardpoints = hardpoints, - BehaviorPayloads = behaviorPayloads, - HardpointType = GetHardpointType(unionKey, payload, layout), - HullType = unionKey == 3 - ? GetEnumName(payload, layout == LegacyCatalogLayout.April2021 ? 22 : 25, HullTypes) - : "", - BehaviorKinds = behaviorKinds, - BehaviorCount = behaviorPayloads.Length, - MaxStack = unionKey == 0 ? GetInt(payload, 9) : 0, - Stackable = isConsumable && GetBool(payload, 11), - Duration = isConsumable ? GetDouble(payload, 12) : 0, - Durability = unionKey is 2 or 3 or 29 or 30 or 31 ? GetDouble(payload, 12) : 0, - MinimumTemperature = unionKey is 2 or 3 or 29 or 30 or 31 ? GetDouble(payload, 13) : 0, - MaximumTemperature = unionKey is 2 or 3 or 29 or 30 or 31 ? GetDouble(payload, 14) : 0, - ThermalPerformanceCurveKeys = unionKey is 2 or 3 or 29 or 30 or 31 ? ReadCurveKeys(payload, 17) : [], - ThermalResilience = unionKey is 2 or 3 or 29 or 30 or 31 ? GetPositiveDoubleOrDefault(payload, 18, 1) : 1, - AudioStats = layout == LegacyCatalogLayout.Current && unionKey is 2 or 3 or 29 or 30 or 31 - ? ReadAudioStats(payload, 22) - : [], - EffectivenessCurveKeys = isConsumable ? ReadCurveKeys(payload, 14) : [], - HullPrefab = unionKey == 3 - ? GetString(payload, layout == LegacyCatalogLayout.April2021 ? 21 : 24) - : "", - HullGridOffset = unionKey == 3 - ? GetDouble(payload, layout == LegacyCatalogLayout.April2021 ? 23 : 26) - : 0, - HullArmor = unionKey == 3 - ? GetDouble(payload, layout == LegacyCatalogLayout.April2021 ? 24 : 27) - : 0, - HullDrag = unionKey == 3 - ? GetDouble(payload, layout == LegacyCatalogLayout.April2021 ? 25 : 28) - : 0, - HullCanTow = layout == LegacyCatalogLayout.Current && unionKey == 3 && GetBool(payload, 29), - DockingMaxSizeX = dockingMaxSize.X, - DockingMaxSizeY = dockingMaxSize.Y, - ActionBarIcon = layout == LegacyCatalogLayout.Current && unionKey is 2 or 3 or 29 or 30 or 31 - ? GetString(payload, 20) - : "", - SimpleCommodityCategory = unionKey == 0 ? GetEnumName(payload, 10, SimpleCommodityCategories) : "", - CompoundCommodityCategory = unionKey == 1 ? GetEnumName(payload, 11, CompoundCommodityCategories) : "", - WeaponRange = unionKey == 31 ? GetEnumName(payload, 24, WeaponRanges) : "", - WeaponCaliber = unionKey == 31 ? GetEnumName(payload, 25, WeaponCalibers) : "", - WeaponType = unionKey == 31 ? GetEnumName(payload, 26, WeaponTypes) : "", - WeaponFireTypes = unionKey == 31 ? GetFlags(payload, 27, WeaponFireTypeFlags) : "", - WeaponModifiers = unionKey == 31 ? GetFlags(payload, 28, WeaponModifierFlags) : "", - Tags = [unionName, "legacy-catalog", .. behaviorKinds.Select(kind => $"behavior:{kind}")] - } - : null, - unionKey == 13 - ? new AetheriaCorporation - { - Name = name, - LegacyId = legacyId, - ShortName = GetString(payload, 2), - Description = GetString(payload, 3), - GeonameFileLegacyId = GetOptionalGuid(payload, 9), - BossHullLegacyId = GetOptionalGuid(payload, 10), - InfluenceDistance = GetInt(payload, 11), - AllegianceCount = GetMapCount(payload, 12), - OverworldMusic = GetUInt(payload, 13), - CombatMusic = GetUInt(payload, 14), - BossMusic = GetUInt(payload, 15), - Allegiances = ReadAllegiances(payload, 12) - } - : null, - unionKey == 9 - ? new AetheriaNameFile - { - Name = name, - LegacyId = legacyId, - NameCount = GetArrayLength(payload, 2), - SampleNames = GetStringArraySample(payload, 2, 8), - Names = GetStringArray(payload, 2) - } - : null); - } - - private static Dictionary ReadArrayPayload(ref MessagePackReader reader) - { - var length = reader.ReadArrayHeader(); - var values = new Dictionary(length); - for (var index = 0; index < length; index++) - { - values[index] = ReadValue(ref reader); - } - - return values; - } - - private static object? ReadValue(ref MessagePackReader reader) - { - if (reader.NextMessagePackType == MessagePackType.Nil) - { - reader.ReadNil(); - return null; - } - - return reader.NextMessagePackType switch - { - MessagePackType.Integer => ReadInteger(ref reader), - MessagePackType.Boolean => reader.ReadBoolean(), - MessagePackType.Float => reader.ReadDouble(), - MessagePackType.String => reader.ReadString(), - MessagePackType.Binary => ReadBinary(ref reader), - MessagePackType.Array => ReadNestedArray(ref reader), - MessagePackType.Map => ReadNestedMap(ref reader), - _ => SkipValue(ref reader) - }; - } - - private static long ReadInteger(ref MessagePackReader reader) - { - return reader.NextCode >= MessagePackCode.MinNegativeFixInt || reader.NextCode == MessagePackCode.Int8 || - reader.NextCode == MessagePackCode.Int16 || reader.NextCode == MessagePackCode.Int32 || - reader.NextCode == MessagePackCode.Int64 - ? reader.ReadInt64() - : (long) reader.ReadUInt64(); - } - - private static object?[] ReadNestedArray(ref MessagePackReader reader) - { - var length = reader.ReadArrayHeader(); - var values = new object?[length]; - for (var index = 0; index < length; index++) - { - values[index] = ReadValue(ref reader); - } - - return values; - } - - private static byte[]? ReadBinary(ref MessagePackReader reader) - { - var bytes = reader.ReadBytes(); - if (bytes == null) - { - return null; - } - - var result = new byte[bytes.Value.Length]; - var offset = 0; - foreach (var segment in bytes.Value) - { - segment.Span.CopyTo(result.AsSpan(offset)); - offset += segment.Length; - } - - return result; - } - - private static Dictionary ReadNestedMap(ref MessagePackReader reader) - { - var length = reader.ReadMapHeader(); - var values = new Dictionary(length); - for (var index = 0; index < length; index++) - { - var key = ReadValue(ref reader); - values[FormatMapKey(key)] = ReadValue(ref reader); - } - - return values; - } - - private static string FormatMapKey(object? key) - { - return key switch - { - byte[] bytes when bytes.Length == 16 => new Guid(bytes).ToString("D"), - string text => text, - _ => key?.ToString() ?? "" - }; - } - - private static object? SkipValue(ref MessagePackReader reader) - { - reader.Skip(); - return null; - } - - private static string GetGuid(IReadOnlyDictionary payload, int key) - { - return payload.TryGetValue(key, out var value) && value is byte[] bytes && bytes.Length == 16 - ? new Guid(bytes).ToString("D") - : ""; - } - - private static string GetOptionalGuid(IReadOnlyDictionary payload, int key) - { - var legacyId = GetGuid(payload, key); - return Guid.TryParse(legacyId, out var guid) && guid == Guid.Empty ? "" : legacyId; - } - - private static string GetString(IReadOnlyDictionary payload, int key) - { - return payload.TryGetValue(key, out var value) ? value as string ?? "" : ""; - } - - private static double GetDouble(IReadOnlyDictionary payload, int key) - { - if (!payload.TryGetValue(key, out var value) || value == null) - { - return 0; - } - - return value switch - { - double doubleValue => doubleValue, - float floatValue => floatValue, - long integerValue => integerValue, - int integerValue => integerValue, - _ => 0 - }; - } - - private static double GetPositiveDoubleOrDefault(IReadOnlyDictionary payload, int key, double fallback) - { - var value = GetDouble(payload, key); - return value > 0 ? value : fallback; - } - - private static int GetInt(IReadOnlyDictionary payload, int key) - { - if (!payload.TryGetValue(key, out var value) || value == null) - { - return 0; - } - - return value switch - { - long integerValue => checked((int) integerValue), - int integerValue => integerValue, - double doubleValue => checked((int) doubleValue), - float floatValue => checked((int) floatValue), - _ => 0 - }; - } - - private static bool GetBool(IReadOnlyDictionary payload, int key) - { - return payload.TryGetValue(key, out var value) && value is true; - } - - private static uint GetUInt(IReadOnlyDictionary payload, int key) - { - if (!payload.TryGetValue(key, out var value) || value == null) - { - return 0; - } - - return value switch - { - long integerValue when integerValue >= 0 => checked((uint) integerValue), - int integerValue when integerValue >= 0 => checked((uint) integerValue), - double doubleValue when doubleValue >= 0 => checked((uint) doubleValue), - float floatValue when floatValue >= 0 => checked((uint) floatValue), - _ => 0 - }; - } - - private static int GetMapCount(IReadOnlyDictionary payload, int key) - { - return payload.TryGetValue(key, out var value) && value is Dictionary map ? map.Count : 0; - } - - private static AetheriaCorporationAllegiance[] ReadAllegiances(IReadOnlyDictionary payload, int key) - { - if (!payload.TryGetValue(key, out var value) || value is not Dictionary map) - { - return []; - } - - return map - .Select(entry => new AetheriaCorporationAllegiance - { - CorporationLegacyId = NormalizeLegacyId(entry.Key), - Weight = ReadDoubleValue(entry.Value) - }) - .Where(allegiance => !string.IsNullOrWhiteSpace(allegiance.CorporationLegacyId)) - .ToArray(); - } - - private static string NormalizeLegacyId(string legacyId) - { - return Guid.TryParse(legacyId, out var parsed) && parsed != Guid.Empty - ? parsed.ToString("D") - : ""; - } - - private static int GetArrayLength(IReadOnlyDictionary payload, int key) - { - return payload.TryGetValue(key, out var value) && value is object?[] array ? array.Length : 0; - } - - private static string GetHardpointType( - int unionKey, - IReadOnlyDictionary payload, - LegacyCatalogLayout layout) - { - return unionKey switch - { - 3 => "Hull", - 29 or 30 => "Tool", - 2 or 31 => GetEnumName( - payload, - layout == LegacyCatalogLayout.April2021 ? 20 : 23, - HardpointTypes), - _ => "" - }; - } - - private static string GetItemCategory(int unionKey) - { - return ItemCategories.TryGetValue(unionKey, out var category) - ? category - : $"legacy-union:{unionKey}"; - } - - private static string GetEnumName( - IReadOnlyDictionary payload, - int key, - IReadOnlyDictionary names) - { - var value = GetInt(payload, key); - return names.TryGetValue(value, out var name) ? name : ""; - } - - private static string GetFlags( - IReadOnlyDictionary payload, - int key, - IReadOnlyDictionary names) - { - var value = GetInt(payload, key); - if (value == 0) - { - return "None"; - } - - var flags = names - .Where(entry => (value & entry.Key) == entry.Key) - .Select(entry => entry.Value) - .ToArray(); - return flags.Length == 0 ? "" : string.Join("|", flags); - } - - private static AetheriaItemHardpoint[] ReadHardpoints(IReadOnlyDictionary payload, int key) - { - if (!payload.TryGetValue(key, out var value) || value is not object?[] hardpoints) - { - return []; - } - - return hardpoints - .Select(ReadHardpoint) - .Where(hardpoint => hardpoint != null) - .Cast() - .ToArray(); - } - - private static AetheriaItemHardpoint? ReadHardpoint(object? value) - { - if (value is not object?[] fields) - { - return null; - } - - var position = ReadPoint(fields.ElementAtOrDefault(1)); - var shape = ReadShapeValue(fields.ElementAtOrDefault(2)); - return new AetheriaItemHardpoint - { - Type = ReadEnumName(fields.ElementAtOrDefault(0), HardpointTypes), - PositionX = position.X, - PositionY = position.Y, - ShapeWidth = shape.Width, - ShapeHeight = shape.Height, - OccupiedCells = shape.OccupiedCells, - ShapeCells = shape.Cells, - Transform = ReadStringValue(fields.ElementAtOrDefault(3)), - Rotation = ReadEnumName(fields.ElementAtOrDefault(4), ItemRotations), - Armor = ReadDoubleValue(fields.ElementAtOrDefault(5)) - }; - } - - private static AetheriaCurveKey[] ReadCurveKeys(IReadOnlyDictionary payload, int key) - { - if (!payload.TryGetValue(key, out var value)) - { - return []; - } - - return ExtractCurveKeyTuples(value) - .Select(tuple => new AetheriaCurveKey - { - Time = ReadDoubleValue(tuple.ElementAtOrDefault(0)), - Value = ReadDoubleValue(tuple.ElementAtOrDefault(1)), - InTangent = ReadDoubleValue(tuple.ElementAtOrDefault(2)), - OutTangent = ReadDoubleValue(tuple.ElementAtOrDefault(3)) - }) - .OrderBy(curveKey => curveKey.Time) - .ToArray(); - } - - private static AetheriaItemAudioStat[] ReadAudioStats(IReadOnlyDictionary payload, int key) - { - if (!payload.TryGetValue(key, out var value) || value is not object?[] audioStats) - { - return []; - } - - return audioStats - .Select(ReadAudioStat) - .Where(audioStat => audioStat != null) - .Cast() - .ToArray(); - } - - private static AetheriaItemAudioStat? ReadAudioStat(object? value) - { - if (value is not object?[] fields) - { - return null; - } - - return new AetheriaItemAudioStat - { - Parameter = ReadUIntValue(fields.ElementAtOrDefault(0)), - Stat = ReadPerformanceStat(fields.ElementAtOrDefault(1)) - }; - } - - private static AetheriaItemPerformanceStat ReadPerformanceStat(object? value) - { - if (value is not object?[] fields) - { - return new AetheriaItemPerformanceStat(); - } - - return new AetheriaItemPerformanceStat - { - Min = ReadDoubleValue(fields.ElementAtOrDefault(0)), - Max = ReadDoubleValue(fields.ElementAtOrDefault(1)), - HeatExponentMultiplier = ReadDoubleValue(fields.ElementAtOrDefault(2)), - DurabilityExponentMultiplier = ReadDoubleValue(fields.ElementAtOrDefault(3)), - QualityExponent = ReadDoubleValue(fields.ElementAtOrDefault(4)) - }; - } - - private static IEnumerable ExtractCurveKeyTuples(object? value) - { - if (value is not object?[] values) - { - return []; - } - - if (LooksLikeCurveKey(values)) - { - return [values]; - } - - var directKeys = values - .OfType() - .Where(LooksLikeCurveKey) - .ToArray(); - if (directKeys.Length > 0) - { - return directKeys; - } - - return values - .OfType() - .SelectMany(ExtractCurveKeyTuples) - .ToArray(); - } - - private static bool LooksLikeCurveKey(object?[] values) - { - return values.Length >= 4 && - IsNumber(values[0]) && - IsNumber(values[1]) && - IsNumber(values[2]) && - IsNumber(values[3]); - } - - private static bool IsNumber(object? value) - { - return value is byte or sbyte or short or ushort or int or uint or long or ulong or float or double or decimal; - } - - private static AetheriaBehaviorPayload[] ReadBehaviorPayloads(IReadOnlyDictionary payload, int key) - { - if (!payload.TryGetValue(key, out var value) || value is not object?[] behaviors) - { - return []; - } - - var result = behaviors - .Select(ReadBehaviorPayload) - .Where(behavior => behavior != null) - .Cast() - .ToArray(); - for (var index = 0; index < result.Length; index++) - if (string.IsNullOrWhiteSpace(result[index].BehaviorId)) - result[index].BehaviorId = $"behavior:{index}:{result[index].Kind}"; - return result; - } - - private static AetheriaBehaviorPayload? ReadBehaviorPayload(object? value) - { - if (value is not object?[] union || union.Length < 2) - { - return null; - } - - var unionKey = union[0] switch - { - long longValue => checked((int) longValue), - int intValue => intValue, - _ => -1 - }; - var fields = union[1] is object?[] payload - ? payload - .Select((fieldValue, index) => new AetheriaBehaviorField - { - Key = index, - Value = ReadBehaviorValue(fieldValue) - }) - .ToArray() - : []; - return new AetheriaBehaviorPayload - { - UnionKey = unionKey, - Kind = BehaviorUnionNames.TryGetValue(unionKey, out var name) ? name : $"behavior:{unionKey}", - Group = fields.FirstOrDefault(field => field.Key == 0)?.Value.Kind == "number" - ? checked((int) fields[0].Value.NumberValue) - : 0, - Fields = fields - }; - } - - private static AetheriaBehaviorValue ReadBehaviorValue(object? value) - { - if (value == null) - { - return new AetheriaBehaviorValue { Kind = "nil" }; - } - - return value switch - { - bool boolValue => new AetheriaBehaviorValue { Kind = "bool", BoolValue = boolValue }, - string stringValue => new AetheriaBehaviorValue { Kind = "string", StringValue = stringValue }, - byte[] bytes when bytes.Length == 16 => ReadLegacyBehaviorId(bytes), - byte[] bytes => new AetheriaBehaviorValue - { - Kind = "binary", - StringValue = Convert.ToHexString(bytes).ToLowerInvariant() - }, - object?[] array => new AetheriaBehaviorValue - { - Kind = "array", - Children = array.Select(ReadBehaviorValue).ToArray() - }, - Dictionary map => new AetheriaBehaviorValue - { - Kind = "map", - MapEntries = map - .OrderBy(entry => entry.Key, StringComparer.Ordinal) - .Select(entry => new AetheriaBehaviorMapEntry - { - Key = entry.Key, - Value = ReadBehaviorValue(entry.Value) - }) - .ToArray() - }, - double doubleValue => new AetheriaBehaviorValue { Kind = "number", NumberValue = doubleValue }, - float floatValue => new AetheriaBehaviorValue { Kind = "number", NumberValue = floatValue }, - long integerValue => new AetheriaBehaviorValue { Kind = "number", NumberValue = integerValue }, - int integerValue => new AetheriaBehaviorValue { Kind = "number", NumberValue = integerValue }, - _ => new AetheriaBehaviorValue - { - Kind = "unknown", - StringValue = value.ToString() ?? "" - } - }; - } - - private static AetheriaBehaviorValue ReadLegacyBehaviorId(byte[] bytes) - { - var legacyId = new Guid(bytes); - return new AetheriaBehaviorValue - { - Kind = "legacy-id", - LegacyIdValue = legacyId.ToString("D"), - ItemKeyValue = ProjectLegacyItemKey(legacyId) - }; - } - - private static string ProjectLegacyItemKey(Guid legacyItemId) - { - return legacyItemId == Guid.Empty ? "" : $"aetheria.item_definition:legacy:{legacyItemId:D}"; - } - - private static Dictionary CreateEnumMap(string[] names) - { - return names - .Select((name, index) => new { name, index }) - .ToDictionary(entry => entry.index, entry => entry.name); - } - - private static string[] GetStringArraySample(IReadOnlyDictionary payload, int key, int max) - { - return payload.TryGetValue(key, out var value) && value is object?[] array - ? array.OfType().Take(max).ToArray() - : []; - } - - private static string[] GetStringArray(IReadOnlyDictionary payload, int key) - { - return payload.TryGetValue(key, out var value) && value is object?[] array - ? array.OfType().ToArray() - : []; - } - - private static ShapeFacts ReadShape(IReadOnlyDictionary payload, int key) - { - if (!payload.TryGetValue(key, out var value) || value == null) - { - return ShapeFacts.Empty; - } - - return ReadShapeValue(value); - } - - private static ShapeFacts ReadInteriorShape( - int unionKey, - IReadOnlyDictionary payload, - ShapeFacts itemShape) - { - return unionKey switch - { - 3 => itemShape.Shrink(), - 29 or 30 => ReadShape(payload, 24), - _ => ShapeFacts.Empty - }; - } - - private static ShapeFacts ReadShapeValue(object? value) - { - if (value == null) - { - return ShapeFacts.Empty; - } - - var matrix = UnwrapShapeMatrix(value); - if (matrix is not object?[] columns) - { - return ShapeFacts.Empty; - } - - if (TryReadRectangularShape(columns, out var rectangularShape)) - { - return rectangularShape; - } - - var width = columns.Length; - var height = 0; - var cellsByRow = new List(); - for (var x = 0; x < columns.Length; x++) - { - var column = columns[x]; - if (column is not object?[] cells) - { - if (column is bool occupiedCell) - { - height = Math.Max(height, 1); - if (occupiedCell) - { - cellsByRow.Add(new AetheriaShapeCell { X = x, Y = 0 }); - } - } - - continue; - } - - height = Math.Max(height, cells.Length); - for (var y = 0; y < cells.Length; y++) - { - if (cells[y] is true) - { - cellsByRow.Add(new AetheriaShapeCell { X = x, Y = y }); - } - } - } - - return new ShapeFacts( - width, - height, - cellsByRow - .OrderBy(cell => cell.Y) - .ThenBy(cell => cell.X) - .ToArray()); - } - - private static bool TryReadRectangularShape(object?[] envelope, out ShapeFacts shape) - { - shape = ShapeFacts.Empty; - if (envelope.Length != 3 || - envelope[2] is not Array flattened) - { - return false; - } - - var width = ReadIntValue(envelope[0]); - var height = ReadIntValue(envelope[1]); - if (width <= 0 || height <= 0 || flattened.Length != width * height) - { - return false; - } - - var cells = new List(); - for (var x = 0; x < width; x++) - for (var y = 0; y < height; y++) - { - if (flattened.GetValue(x * height + y) is true) - { - cells.Add(new AetheriaShapeCell { X = x, Y = y }); - } - } - - shape = new ShapeFacts(width, height, cells - .OrderBy(cell => cell.Y) - .ThenBy(cell => cell.X) - .ToArray()); - return true; - } - - private static object? UnwrapShapeMatrix(object? value) - { - return value is object?[] shapeObject && shapeObject.Length == 1 ? shapeObject[0] : value; - } - - private static PointFacts ReadPoint(object? value) - { - if (value is not object?[] coordinates) - { - return PointFacts.Empty; - } - - return new PointFacts( - ReadIntValue(coordinates.ElementAtOrDefault(0)), - ReadIntValue(coordinates.ElementAtOrDefault(1))); - } - - private static PointFacts ReadPoint(IReadOnlyDictionary payload, int key) - { - return payload.TryGetValue(key, out var value) - ? ReadPoint(value) - : PointFacts.Empty; - } - - private static string ReadEnumName(object? value, IReadOnlyDictionary names) - { - var index = ReadIntValue(value); - return names.TryGetValue(index, out var name) ? name : ""; - } - - private static int ReadIntValue(object? value) - { - return value switch - { - long integerValue => checked((int) integerValue), - int integerValue => integerValue, - double doubleValue => checked((int) doubleValue), - float floatValue => checked((int) floatValue), - _ => 0 - }; - } - - private static uint ReadUIntValue(object? value) - { - return value switch - { - long integerValue when integerValue >= 0 => checked((uint)integerValue), - int integerValue when integerValue >= 0 => checked((uint)integerValue), - double doubleValue when doubleValue >= 0 => checked((uint)doubleValue), - float floatValue when floatValue >= 0 => checked((uint)floatValue), - _ => 0 - }; - } - - private static double ReadDoubleValue(object? value) - { - return value switch - { - double doubleValue => doubleValue, - float floatValue => floatValue, - long integerValue => integerValue, - int integerValue => integerValue, - _ => 0 - }; - } - - private static string ReadStringValue(object? value) - { - return value as string ?? ""; - } -} - -internal sealed record LegacyCatalogEntry( - AetheriaLegacyCatalogEntrySummary Summary, - AetheriaItemDefinition? ItemDefinition, - AetheriaCorporation? Corporation, - AetheriaNameFile? NameFile); - -internal readonly record struct ShapeFacts(int Width, int Height, AetheriaShapeCell[] Cells) -{ - public static ShapeFacts Empty { get; } = new(0, 0, []); - - public int OccupiedCells => Cells.Length; - - public ShapeFacts Shrink() - { - if (Width == 0 || Height == 0 || Cells.Length == 0) - { - return Empty; - } - - var occupied = Cells - .Select(cell => (cell.X, cell.Y)) - .ToHashSet(); - var interior = Cells - .Where(cell => - IsOccupied(occupied, cell.X - 1, cell.Y - 1) && - IsOccupied(occupied, cell.X, cell.Y - 1) && - IsOccupied(occupied, cell.X + 1, cell.Y - 1) && - IsOccupied(occupied, cell.X - 1, cell.Y) && - IsOccupied(occupied, cell.X + 1, cell.Y) && - IsOccupied(occupied, cell.X - 1, cell.Y + 1) && - IsOccupied(occupied, cell.X, cell.Y + 1) && - IsOccupied(occupied, cell.X + 1, cell.Y + 1)) - .Select(cell => new AetheriaShapeCell { X = cell.X, Y = cell.Y }) - .ToArray(); - return new ShapeFacts(Width, Height, interior); - } - - private static bool IsOccupied(IReadOnlySet<(int X, int Y)> occupied, int x, int y) - { - return occupied.Contains((x, y)); - } -} - -internal readonly record struct PointFacts(int X, int Y) -{ - public static PointFacts Empty { get; } = new(0, 0); -} diff --git a/Aetheria.State.Replica/Aetheria.State.Replica.csproj b/Aetheria.State.Replica/Aetheria.State.Replica.csproj deleted file mode 100644 index 85c9ae99e..000000000 --- a/Aetheria.State.Replica/Aetheria.State.Replica.csproj +++ /dev/null @@ -1,14 +0,0 @@ - - - Exe - net10.0 - enable - enable - latest - Replica sync worker for pulling daemon-owned Aetheria Verse state into a local cache-only .cc witness. - - - - - - diff --git a/Aetheria.State.Replica/Program.cs b/Aetheria.State.Replica/Program.cs deleted file mode 100644 index 30feca8fe..000000000 --- a/Aetheria.State.Replica/Program.cs +++ /dev/null @@ -1,151 +0,0 @@ -using Aetheria.State; - -return await ProgramMainAsync(args).ConfigureAwait(false); - -static async Task ProgramMainAsync(string[] args) -{ - if (args.Length == 0) - { - PrintUsage(); - return 1; - } - - var mode = args[0].Trim(); - var options = ParseOptions(args.Skip(1)); - var endpoint = RequireOption(options, "endpoint"); - var runtimeId = ReadOption(options, "runtime-id", "aetheria-verse-replica"); - var replicaPath = ResolveReplicaPath(options); - SeedBaselineReplica(options, replicaPath); - - switch (mode) - { - case "sync": - { - var appliedSequence = await AetheriaVerseReplica - .SyncSnapshotAsync(replicaPath, endpoint, runtimeId) - .ConfigureAwait(false); - Console.WriteLine($"Synced Aetheria replica to {replicaPath} from {endpoint} at shard sequence {appliedSequence}."); - return 0; - } - case "follow": - { - var pollSeconds = ReadDoubleOption(options, "poll-seconds", 1d); - using var cancellation = new CancellationTokenSource(); - Console.CancelKeyPress += (_, eventArgs) => - { - eventArgs.Cancel = true; - cancellation.Cancel(); - }; - - Console.WriteLine( - $"Following Aetheria replica at {replicaPath} from {endpoint} every {pollSeconds:0.###} seconds. Press Ctrl+C to stop."); - await AetheriaVerseReplica - .RunReplicaAsync(replicaPath, endpoint, TimeSpan.FromSeconds(pollSeconds), cancellation.Token, runtimeId) - .ConfigureAwait(false); - return 0; - } - default: - Console.Error.WriteLine($"Unknown mode '{mode}'."); - PrintUsage(); - return 1; - } -} - -static string ResolveReplicaPath(IReadOnlyDictionary options) -{ - var explicitPath = ReadOption(options, "replica", ""); - if (!string.IsNullOrWhiteSpace(explicitPath)) - return Path.GetFullPath(explicitPath); - - var gameDataRoot = RequireOption(options, "game-data-root"); - var verseId = RequireOption(options, "verse-id"); - return Path.Combine( - Path.GetFullPath(gameDataRoot), - "Verses", - $"{SanitizeVerseId(verseId)}.cc"); -} - -static void SeedBaselineReplica(IReadOnlyDictionary options, string replicaPath) -{ - var baselinePath = ReadOption(options, "baseline-state", ""); - if (string.IsNullOrWhiteSpace(baselinePath) || - File.Exists(replicaPath) || - !File.Exists(baselinePath)) - { - return; - } - - Directory.CreateDirectory(Path.GetDirectoryName(replicaPath) ?? "."); - File.Copy(baselinePath, replicaPath, overwrite: false); -} - -static string SanitizeVerseId(string verseId) -{ - var safeVerseId = string.IsNullOrWhiteSpace(verseId) - ? "unknown-verse" - : new string((verseId ?? "") - .Select(ch => char.IsLetterOrDigit(ch) || ch == '-' || ch == '_' || ch == '.' - ? ch - : '-') - .ToArray()) - .Trim('-'); - - return string.IsNullOrWhiteSpace(safeVerseId) - ? "unknown-verse" - : safeVerseId; -} - -static Dictionary ParseOptions(IEnumerable args) -{ - var options = new Dictionary(StringComparer.OrdinalIgnoreCase); - string? pendingKey = null; - foreach (var arg in args) - { - if (arg.StartsWith("--", StringComparison.Ordinal)) - { - pendingKey = arg[2..]; - options[pendingKey] = "true"; - continue; - } - - if (!string.IsNullOrWhiteSpace(pendingKey)) - { - options[pendingKey] = arg; - pendingKey = null; - } - } - - return options; -} - -static string RequireOption(IReadOnlyDictionary options, string key) -{ - var value = ReadOption(options, key, ""); - if (!string.IsNullOrWhiteSpace(value)) - return value; - - throw new InvalidOperationException($"Missing required option --{key}."); -} - -static string ReadOption(IReadOnlyDictionary options, string key, string fallback) -{ - return options.TryGetValue(key, out var value) && !string.IsNullOrWhiteSpace(value) - ? value - : fallback; -} - -static double ReadDoubleOption(IReadOnlyDictionary options, string key, double fallback) -{ - var value = ReadOption(options, key, ""); - return double.TryParse(value, out var parsed) && parsed > 0 - ? parsed - : fallback; -} - -static void PrintUsage() -{ - Console.WriteLine("Usage:"); - Console.WriteLine(" sync --endpoint cultnet://host:3075 --replica path-to-replica.cc [--runtime-id aetheria-verse-replica]"); - Console.WriteLine(" sync --endpoint cultnet://host:3075 --game-data-root GameData --verse-id verse.id [--runtime-id aetheria-verse-replica]"); - Console.WriteLine(" follow --endpoint cultnet://host:3075 --replica path-to-replica.cc [--poll-seconds 1] [--runtime-id aetheria-verse-replica]"); -} diff --git a/Aetheria.State.Smoke/Aetheria.State.Smoke.csproj b/Aetheria.State.Smoke/Aetheria.State.Smoke.csproj deleted file mode 100644 index 794ccfebf..000000000 --- a/Aetheria.State.Smoke/Aetheria.State.Smoke.csproj +++ /dev/null @@ -1,17 +0,0 @@ - - - - - Exe - net10.0 - enable - latest - enable - - - - - - - - diff --git a/Aetheria.State.Smoke/Program.cs b/Aetheria.State.Smoke/Program.cs deleted file mode 100644 index cf8897af2..000000000 --- a/Aetheria.State.Smoke/Program.cs +++ /dev/null @@ -1,1460 +0,0 @@ -using Aetheria.State; -using Aetheria.State.Documents; -using Aetheria.State.Migration; -using System.Buffers.Binary; -using GameCult.Caching; -using GameCult.Aetheria.State.Verse; -using GameCult.Mesh; -using GameCult.Networking; -using GameCult.Eve.Surface; -using EveProviderAdvertisementState = GameCult.Eve.Surface.EveProviderAdvertisementDocument; -using EveSurfaceDocument = GameCult.Eve.Surface.EveSurfaceDocument; - -var root = args.Length > 0 ? args[0] : Directory.GetCurrentDirectory(); -var stateDirectory = Path.Combine(Path.GetTempPath(), "aetheria-state-smoke", Guid.NewGuid().ToString("N")); -var statePath = Path.Combine(stateDirectory, "aetheria-world.cc"); -var now = DateTimeOffset.UtcNow.ToString("O"); -var itemLegacyId = "smoke:aether-drive"; -var factionLegacyId = "smoke:faction"; -var nameFileLegacyId = "smoke:name-file"; -var runKey = new CultRecordKey("run:smoke"); -var loadoutKey = new CultRecordKey("loadout:smoke:aether-runner"); -var zoneKey = new CultRecordKey("zone:smoke:0"); -var entityKey = new CultRecordKey("entity:smoke:runner"); -var itemKey = AetheriaCatalogKeys.ItemDefinitionFromLegacyId(itemLegacyId); -var factionKey = AetheriaCatalogKeys.CorporationFromLegacyId(factionLegacyId); -var nameFileKey = AetheriaCatalogKeys.NameFileFromLegacyId(nameFileLegacyId); - -await using (var node = await AetheriaStateNode.OpenAsync(statePath, "aetheria-state-smoke")) -{ - await node.MutableDocument(AetheriaStateNode.WorldKey).ReplaceAsync(new AetheriaWorldState - { - Name = "Aetheria", - WorldId = "aetheria", - SchemaEpoch = 1, - CreatedAtUtc = now, - UpdatedAtUtc = now - }); - - await node.MutableDocument(itemKey) - .ReplaceAsync(new AetheriaItemDefinition - { - Name = "Smoke Aether Drive", - Category = "ship-module", - LegacyId = itemLegacyId, - Description = "Typed CultCache smoke document for the rebuild spine.", - Mass = 12.5, - Volume = 4.0, - Tags = ["smoke", "state-spine"] - }); - - await node.MutableDocument(AetheriaStateNode.MigrationLedgerKey).ReplaceAsync(new AetheriaMigrationLedger - { - Source = LegacyMigrationBoundary.LegacyGameDataFile, - SourceFingerprint = "smoke", - LastMigrationAtUtc = now, - Counts = - [ - new AetheriaMigrationCount - { - DocumentType = "aetheria.item_definition.v1", - Count = 1 - } - ], - Notes = ["Smoke proves the new state owner can write, flush, reopen, and read without old JSON/Rethink authority."] - }); - - await node.MutableDocument(AetheriaStateNode.LegacyCatalogQuarantineKey).ReplaceAsync(new AetheriaLegacyCatalogQuarantine - { - RootPath = root, - CapturedAtUtc = now, - CatalogFile = LegacyMigrationBoundary.LegacyGameDataFile, - CatalogFingerprint = "smoke", - CatalogBytes = 12, - NameFiles = - [ - new AetheriaLegacyCatalogFile - { - RelativePath = "GameData/NameFile/smoke.msgpack", - Fingerprint = "smoke-name-file", - Bytes = 4 - } - ], - Notes = ["Smoke proves legacy catalog quarantine state is typed and durable."] - }); - - await node.MutableDocument(factionKey).ReplaceAsync(new AetheriaCorporation - { - Name = "Smoke Faction", - LegacyId = factionLegacyId, - Description = "Typed faction/corporation document for legacy catalog migration smoke." - }); - - await node.MutableDocument(nameFileKey).ReplaceAsync(new AetheriaNameFile - { - Name = "Smoke Names", - LegacyId = nameFileLegacyId, - NameCount = 2, - SampleNames = ["Ada", "Grace"] - }); - - await node.FlushAsync(); - await node.RefreshRuntimeCatalogAsync(); - var runtimeCatalog = await node.RuntimeCatalog().LatestAsync().ConfigureAwait(false); - await node.MutableDocument(AetheriaStateNode.CatalogSurfaceKey) - .ReplaceAsync(AetheriaEveSurfaceDocuments.BuildCatalogSurface(runtimeCatalog, now)); - - var verseHostSettings = AetheriaVerseHostSettingsNormalizer.Normalize(new AetheriaVerseHostSettings - { - LastUpdatedAtUtc = now - }); - await node.MutableDocument(AetheriaStateNode.VerseHostSettingsKey).ReplaceAsync(verseHostSettings); - await node.MutableDocument(AetheriaStateNode.OperationsSurfaceKey) - .ReplaceAsync(AetheriaEveSurfaceDocuments.BuildOperationsSurface(verseHostSettings: verseHostSettings)); - var providerAdvertisement = AetheriaEveSurfaceDocuments.BuildProviderAdvertisement(verseHostSettings, statePath, now); - var pilotAdvertisement = providerAdvertisement.Surfaces.Single(surface => - surface.SurfaceId == AetheriaRuntimeDaemonGameSurfaceBuilder.PilotSurfaceId); - if (pilotAdvertisement.WorldInteraction?.AssetManifestRecordRef != - AetheriaRuntimeVerseRecordKeys.EveAssetCatalog.ToString()) - { - throw new InvalidOperationException("Pilot surface does not advertise its provider-owned asset catalog."); - } - await node.MutableDocument(AetheriaStateNode.ProviderAdvertisementSurfaceKey) - .ReplaceAsync(providerAdvertisement); - var daemonProvider = AetheriaRuntimeDaemonProviderAdvertisementDocument.Create( - statePath, - "smoke-daemon", - "aetheria.local", - "cultmesh://aetheria.local/eve/providers/aetheria.daemon"); - var daemonHealth = new AetheriaRuntimeDaemonHealthDocument - { - DaemonId = "smoke-daemon", - VerseId = "aetheria.local", - PublishedAtUtc = now, - StatePath = statePath, - FrameId = 7, - ObservedCommandCount = 1, - AppliedCommandCount = 2, - RejectedCommandCount = 0, - Status = "healthy", - CommandBoundaryPath = AetheriaRuntimeStateBoundary.GetDaemonCommandBoundaryPath(statePath) - }; - var daemonCommandBoundary = AetheriaRuntimeDaemonCommandBoundaryDocument.Create("smoke-daemon"); - var daemonFrame = AetheriaRuntimeDaemonFrameDocument.Create( - new AetheriaRuntimeRunCheckpointCommit - { - RunId = "smoke-run", - CurrentZoneIndex = 0, - CurrentEntityKey = entityKey.ToString() - }, - "smoke-daemon", - "smoke-session", - 7, - 0.14, - 0.02); - var daemonGameSurface = AetheriaRuntimeDaemonGameSurfaceBuilder.Build( - daemonFrame, - daemonHealth, - daemonCommandBoundary); - var playableWorld = daemonGameSurface.Surface.Root.Children.Single(component => - component.Id == "aetheria.daemon.game.world"); - var expectedPlayableWorldProps = new Dictionary(StringComparer.Ordinal) - { - ["cameraRig"] = "perspective.entity-forward-follow.v1", - ["cameraTargetEntityId"] = entityKey.ToString(), - ["cameraDistance"] = "30", - ["cameraVerticalFieldOfViewDegrees"] = "60", - ["cameraTargetScreenX"] = "0.64", - ["cameraTargetScreenY"] = "0.19", - ["cameraPositionDamping"] = "0", - ["cameraNearClipPlane"] = "1", - ["cameraFarClipPlane"] = "4096", - ["ambientLightColor"] = "0.2,0.2,0.2", - ["ambientLightIntensity"] = "1.46" - }; - var playableWorldMismatches = expectedPlayableWorldProps - .Where(expected => - !playableWorld.Props.TryGetValue(expected.Key, out var actual) || - !string.Equals(actual, expected.Value, StringComparison.Ordinal)) - .Select(expected => - $"{expected.Key}: expected '{expected.Value}', actual '{(playableWorld.Props.TryGetValue(expected.Key, out var actual) ? actual : "")}'") - .ToArray(); - if (playableWorldMismatches.Length > 0) - { - throw new InvalidOperationException( - $"Playable world did not publish the native camera and environment contract: {string.Join("; ", playableWorldMismatches)}"); - } - var gravityFog = playableWorld.Children.Single(component => - component.Id == "aetheria.daemon.game.world.gravity-fog"); - var stardust = playableWorld.Children.Single(component => - component.Id == "aetheria.daemon.game.world.stardust"); - if (!gravityFog.Props["features"].Split(';').Contains("flow.value3d", StringComparer.Ordinal) || - !stardust.Props["features"].Split(';').Contains("flow.value3d", StringComparer.Ordinal)) - { - throw new InvalidOperationException("Aetheria's default render settings did not select smooth Value3D flow."); - } - - daemonFrame.RenderSettings = new AetheriaRuntimeDaemonRenderSettings( - default, - default, - default, - 0, - 0, - 0, - 0, - 0, - 0, - useValue3DFlow: false); - var triangleSurface = AetheriaRuntimeDaemonGameSurfaceBuilder.Build( - daemonFrame, - daemonHealth, - daemonCommandBoundary); - var triangleWorld = triangleSurface.Surface.Root.Children.Single(component => - component.Id == "aetheria.daemon.game.world"); - if (triangleWorld.Children - .Where(component => component.Id is "aetheria.daemon.game.world.gravity-fog" or "aetheria.daemon.game.world.stardust") - .Any(component => component.Props["features"].Split(';').Contains("flow.value3d", StringComparer.Ordinal))) - { - throw new InvalidOperationException("Triangle flow remained opted into the Value3D feature keyword."); - } - daemonFrame.RenderSettings = AetheriaRuntimeDaemonRenderSettings.AetheriaDefault; - - var renderAssets = AetheriaRuntimeAssets.ProjectManifest(null); - if (!renderAssets.Assets.Any(asset => - asset.Ref.AssetKey == "shader.environment.gravity-fog" && - asset.Ref.Metadata.TryGetValue("unity.volume.feature.flow.value3d.keyword", out var keyword) && - keyword == "FLOW_VALUE3D") || - !renderAssets.Assets.Any(asset => - asset.Ref.AssetKey == "compute.environment.stardust" && - asset.Ref.Metadata.TryGetValue("unity.particles.feature.flow.value3d.keyword", out var keyword) && - keyword == "FLOW_VALUE3D")) - { - throw new InvalidOperationException("Aetheria's asset catalog did not map smooth flow to FLOW_VALUE3D."); - } - await node.MutableDocument(AetheriaRuntimeVerseRecordKeys.DaemonProviderAdvertisement) - .ReplaceAsync(daemonProvider); - await node.MutableDocument(AetheriaRuntimeVerseRecordKeys.DaemonHealth) - .ReplaceAsync(daemonHealth); - await node.MutableDocument(AetheriaRuntimeVerseRecordKeys.DaemonCommandBoundary) - .ReplaceAsync(daemonCommandBoundary); - await node.MutableDocument(AetheriaRuntimeVerseRecordKeys.DaemonFrameLatest) - .ReplaceAsync(daemonFrame); - await node.FlushAsync(); - using var daemonCommandClient = await AetheriaClient.OpenAsync( - statePath, - "aetheria-state-smoke-command-client", - "smoke-session", - startServer: false, - pullOnOpen: true); - daemonCommandClient.Control.SensorPing(); - - await using (var commandVerifyNode = await AetheriaStateNode.OpenAsync( - statePath, - "aetheria-state-smoke-command-check")) - { - if (commandVerifyNode.Documents() - .OrderBy(command => command.IssuedAtUtc ?? "", StringComparer.Ordinal) - .ThenBy(command => command.CommandId ?? "", StringComparer.Ordinal) - .All(command => - command.Kind != AetheriaRuntimeDaemonCommandKinds.SensorPing || - command.ClientId != "aetheria-state-smoke-command-client")) - { - throw new InvalidOperationException("AetheriaClient control submission did not appear as a typed daemon state record."); - } - } - await node.MutableDocument(AetheriaRuntimeVerseRecordKeys.DaemonGameSurface) - .ReplaceAsync(AetheriaRuntimeSurfaceDocuments.ToPortableSurface(daemonGameSurface)); - await node.MutableDocument(AetheriaStateNode.RuntimeSessionKey("smoke-runtime")).ReplaceAsync(new AetheriaRuntimeSession - { - RuntimeId = "smoke-runtime", - Role = "state-smoke", - StartedAtUtc = now, - LastSeenAtUtc = now, - Status = "running" - }); - await node.MutableDocument(AetheriaStateNode.OperationsSurfaceKey).ReplaceAsync(AetheriaEveSurfaceDocuments.BuildOperationsSurface( - verseHostSettings: verseHostSettings, - runtimeSession: await node.MutableDocument(AetheriaStateNode.RuntimeSessionKey("smoke-runtime")).ReadAsync())); - - await node.MutableDocument(loadoutKey).ReplaceAsync(new AetheriaLoadoutTemplate - { - Name = "Smoke Aether Runner", - OwnerPlayerKey = "player:smoke", - CreatedAtUtc = now, - UpdatedAtUtc = now, - RootEntity = new AetheriaEntityLoadout - { - Name = "Smoke Aether Runner", - Kind = "ship", - FactionKey = factionKey.ToString(), - Hull = new AetheriaLoadoutItem - { - ItemKey = itemKey.ToString(), - Quality = 1.0, - Durability = 1.0 - }, - Equipment = - [ - new AetheriaLoadoutItemSlot - { - Position = new AetheriaGridCoord { X = 0, Y = 0 }, - Rotation = "Clockwise", - Item = new AetheriaLoadoutItem - { - ItemKey = itemKey.ToString(), - Quality = 0.95, - Durability = 0.8, - Enabled = false, - OverrideShutdown = true - } - } - ], - WeaponGroups = [[0]] - } - }); - - await node.MutableDocument(AetheriaStateNode.HangarKey).ReplaceAsync(new AetheriaHangarState - { - HangarId = "smoke", - PlayerKey = "player:smoke", - Revision = 7, - Ships = - [ - new AetheriaHangarShip - { - ShipId = "ship:smoke:aether-runner", - HullItemKey = itemKey.ToString(), - LoadoutTemplateKey = loadoutKey.ToString() - }, - new AetheriaHangarShip - { - ShipId = "ship:smoke:starbridge", - HullItemKey = itemKey.ToString(), - LoadoutTemplateKey = loadoutKey.ToString() - }, - new AetheriaHangarShip - { - ShipId = "ship:smoke:arena", - HullItemKey = itemKey.ToString(), - LoadoutTemplateKey = loadoutKey.ToString() - } - ], - LoadoutTemplateKeys = [loadoutKey.ToString()], - UpdatedAtUtc = now - }); - var deploymentRequest = new AetheriaDeploymentRequest - { - RequestId = "request:smoke:terminus", - PlayerKey = "player:smoke", - Mode = AetheriaGameModes.Terminus, - ShipId = "ship:smoke:aether-runner", - LoadoutTemplateKey = loadoutKey.ToString(), - ExpectedHangarRevision = 7, - ModePolicyId = AetheriaModePolicies.TerminusLocal - }; - var deployment = await AetheriaHangar.AdmitAsync(node, deploymentRequest, now); - var duplicateDeployment = await AetheriaHangar.AdmitAsync(node, deploymentRequest, now); - if (!deployment.Accepted || - deployment.DeploymentId != duplicateDeployment.DeploymentId || - deployment.HangarRevision != 8) - throw new InvalidOperationException("Hangar deployment admission was not accepted exactly once."); - - foreach (var (mode, shipId, revision, policy) in new[] - { - (AetheriaGameModes.Starbridge, "ship:smoke:starbridge", 8L, AetheriaModePolicies.StarbridgeMixed), - (AetheriaGameModes.Arena, "ship:smoke:arena", 9L, AetheriaModePolicies.ArenaServer) - }) - { - var modeDeployment = await AetheriaHangar.AdmitAsync(node, new AetheriaDeploymentRequest - { - RequestId = $"request:smoke:{mode}", - PlayerKey = "player:smoke", - Mode = mode, - ShipId = shipId, - LoadoutTemplateKey = loadoutKey.ToString(), - ExpectedHangarRevision = revision, - ModePolicyId = policy - }, now); - if (!modeDeployment.Accepted || modeDeployment.Mode != mode) - throw new InvalidOperationException($"Hangar rejected the shared {mode} deployment boundary."); - } - - var staleDeployment = AetheriaHangar.Admit( - await node.MutableDocument(AetheriaStateNode.HangarKey).ReadAsync() - ?? throw new InvalidOperationException("Hangar disappeared during admission smoke."), - new AetheriaDeploymentRequest - { - RequestId = "request:smoke:stale", - PlayerKey = "player:smoke", - Mode = AetheriaGameModes.Arena, - ShipId = "ship:smoke:aether-runner", - LoadoutTemplateKey = loadoutKey.ToString(), - ExpectedHangarRevision = 7, - ModePolicyId = AetheriaModePolicies.ArenaServer - }, - await node.MutableDocument(loadoutKey).ReadAsync(), - now); - if (staleDeployment.Accepted || staleDeployment.Diagnostic != "hangar revision mismatch") - throw new InvalidOperationException("Stale Hangar deployment bypassed revision admission."); - - await node.MutableDocument(entityKey).ReplaceAsync(new AetheriaEntitySnapshot - { - Name = "Smoke Aether Runner", - Kind = "ship", - Position = new AetheriaVector3 { X = 12.5, Y = 0.0, Z = -3.25 }, - Direction = new AetheriaVector2 { X = 0.0, Y = 1.0 }, - LookDirection = new AetheriaVector2 { X = 0.8, Y = 0.6 }, - HelmInput = new AetheriaVector2 { X = -0.25, Y = 0.75 }, - FactionKey = factionKey.ToString(), - HullItemKey = itemKey.ToString(), - Equipment = - [ - new AetheriaEntityItemSlot - { - Position = new AetheriaGridCoord { X = 0, Y = 0 }, - Rotation = "CounterClockwise", - ItemKey = itemKey.ToString(), - Quality = 0.9, - Durability = 0.8, - Quantity = 1, - Enabled = false, - OverrideShutdown = true - } - ], - CargoContents = - [ - new AetheriaCargoBayLoadout - { - Items = - [ - new AetheriaLoadoutItemSlot - { - Position = new AetheriaGridCoord { X = 2, Y = 3 }, - Rotation = "Half", - Item = new AetheriaLoadoutItem - { - ItemKey = itemKey.ToString(), - Quantity = 7 - } - } - ] - } - ], - DockingBayAssignments = [-1], - Visibility = 12.75, - VisibilitySourceCount = 3, - Contacts = - [ - new AetheriaEntityContactSnapshot - { - TargetEntityKey = entityKey.ToString(), - InfoGathered = 0.85, - Hostile = true, - Visible = true - } - ], - WeaponGroups = - [ - new AetheriaWeaponGroupSnapshot - { - EquipmentIndices = [0] - } - ], - WeaponStates = - [ - new AetheriaWeaponStateSnapshot - { - OwnerKind = "equipment", - OwnerIndex = 0, - BehaviorIndex = 1, - BehaviorKind = "LockWeapon", - Ammo = 1, - LockProgress = 0.65, - LockTargetEntityKey = entityKey.ToString() - } - ], - BehaviorStates = - [ - new AetheriaBehaviorStateSnapshot - { - OwnerKind = "equipment", - OwnerIndex = 0, - BehaviorIndex = 0, - BehaviorKind = "AetherDrive", - AetherDriveAxisX = 0.5, - AetherDriveAxisY = -0.25, - AetherDriveAxisZ = 0.75, - AetherDriveThrustX = 12.0, - AetherDriveThrustY = 6.0, - AetherDriveThrustZ = 3.0, - AetherDriveRpmX = 1200.0, - AetherDriveRpmY = 900.0, - AetherDriveRpmZ = 450.0, - AetherDriveMaximumRpm = 2400.0, - AetherDriveThrustDirectionX = 1.5, - AetherDriveThrustDirectionY = -0.5 - }, - new AetheriaBehaviorStateSnapshot - { - OwnerKind = "equipment", - OwnerIndex = 0, - BehaviorIndex = 1, - BehaviorKind = "ResourceScanner", - ResourceScannerTargetBodyKey = "aetheria.body:smoke:body", - ResourceScannerAsteroidIndex = 2, - ResourceScannerScanTime = 1.25, - ResourceScannerRange = 500.0, - ResourceScannerMinimumDensity = 0.2, - ResourceScannerScanDuration = 3.5 - }, - new AetheriaBehaviorStateSnapshot - { - OwnerKind = "equipment", - OwnerIndex = 0, - BehaviorIndex = 2, - BehaviorKind = "MiningTool", - MiningToolAsteroidBeltKey = "aetheria.body:smoke:body", - MiningToolAsteroidIndex = 3, - MiningToolRange = 275.0 - }, - new AetheriaBehaviorStateSnapshot - { - OwnerKind = "equipment", - OwnerIndex = 0, - BehaviorIndex = 3, - BehaviorKind = "Thruster", - ThrusterAxis = 0.8, - ThrusterThrust = 125.0, - ThrusterTorque = -0.4 - }, - new AetheriaBehaviorStateSnapshot - { - OwnerKind = "equipment", - OwnerIndex = 0, - BehaviorIndex = 4, - BehaviorKind = "Shield", - ShieldEfficiency = 0.7, - ShieldEnergyUsage = 1.4 - }, - new AetheriaBehaviorStateSnapshot - { - OwnerKind = "equipment", - OwnerIndex = 0, - BehaviorIndex = 5, - BehaviorKind = "VelocityLimit", - VelocityLimit = 42.0 - }, - new AetheriaBehaviorStateSnapshot - { - OwnerKind = "equipment", - OwnerIndex = 0, - BehaviorIndex = 6, - BehaviorKind = "Thermotoggle", - ThermotoggleTargetTemperature = 315.5 - }, - new AetheriaBehaviorStateSnapshot - { - OwnerKind = "equipment", - OwnerIndex = 0, - BehaviorIndex = 7, - BehaviorKind = "Switch", - SwitchActivated = true - }, - new AetheriaBehaviorStateSnapshot - { - OwnerKind = "equipment", - OwnerIndex = 0, - BehaviorIndex = 8, - BehaviorKind = "Trigger", - TriggerPulled = true - }, - new AetheriaBehaviorStateSnapshot - { - OwnerKind = "equipment", - OwnerIndex = 0, - BehaviorIndex = 9, - BehaviorKind = "StatModifier", - StatModifierApplied = true, - StatModifierExecuted = true, - StatModifierTargetStatCount = 2 - }, - new AetheriaBehaviorStateSnapshot - { - OwnerKind = "equipment", - OwnerIndex = 0, - BehaviorIndex = 10, - BehaviorKind = "TurretController", - TurretControllerWeaponCount = 2, - TurretControllerShotSpeed = 80.0, - TurretControllerPredictShots = true - } - ], - StatGrids = - [ - new AetheriaEntityStatGrid - { - Name = "armor", - Width = 2, - Height = 2, - Values = [1.0, 0.9, 0.8, 0.7] - } - ] - }); - - await node.MutableDocument(zoneKey).ReplaceAsync(new AetheriaZoneState - { - Name = "Smoke Zone", - Position = new AetheriaVector2 { X = 4.0, Y = 8.0 }, - AdjacentZoneIndices = [1], - FactionIndices = [0], - OwnerFactionIndex = 0, - EntityKeys = [entityKey.ToString()], - Orbits = - [ - new AetheriaOrbitSnapshot - { - OrbitKey = "smoke:orbit", - ParentOrbitKey = "smoke:parent-orbit", - Distance = 100, - Phase = 0.25, - FixedPosition = new AetheriaVector2 { X = 5, Y = -6 } - } - ], - Bodies = - [ - new AetheriaBodySnapshot - { - BodyKey = "smoke:body", - Kind = "asteroid_belt", - Name = "Smoke Belt", - OrbitKey = "smoke:orbit", - Mass = 42, - Resources = - [ - new AetheriaBodyResource - { - ItemKey = itemKey.ToString(), - Amount = 3.5 - } - ], - Asteroids = - [ - new AetheriaAsteroidSnapshot - { - Distance = 7, - Phase = 0.75, - Size = 2, - RotationSpeed = 0.5, - Damage = 1.25, - RespawnTimer = 6.5, - MiningAccumulators = - [ - new AetheriaAsteroidMiningAccumulatorSnapshot - { - MinerEntityKey = entityKey.ToString(), - Amount = 0.75 - } - ] - } - ] - } - ] - }); - - await node.MutableDocument(runKey).ReplaceAsync(new AetheriaRunState - { - RunId = "smoke", - IsTutorial = false, - EntranceZoneIndex = 0, - ExitZoneIndex = 1, - CurrentZoneIndex = 0, - CurrentEntityKey = entityKey.ToString(), - GenerationSeed = 424242, - DiscoveredZoneIndices = [0], - ZoneKeys = [zoneKey.ToString()], - UpdatedAtUtc = now - }); - - await node.MutableDocument(AetheriaStateNode.PlayerSettingsKey).ReplaceAsync(new AetheriaPlayerSettings - { - ActiveRunKey = runKey.ToString(), - LastUpdatedAtUtc = now, - PlayerName = "Smoke Pilot", - TutorialPassed = true, - StoryFileHashes = - [ - new AetheriaStoryFileHash - { - StoryPath = "Narrative/smoke.ink", - Hash = "sha256:smoke" - } - ], - Gameplay = new AetheriaPlayerGameplaySettings - { - TemperatureUnit = "Celsius", - SignificantDigits = 4 - }, - Graphics = new AetheriaPlayerGraphicsSettings - { - NebulaQuality = "High", - ShowAsteroidsInMinimap = true - }, - Input = new AetheriaPlayerInputSettings - { - BindingOverrides = - [ - new AetheriaInputBindingOverride - { - ActionName = "Thrust", - BindingIndex = 0, - BindingPath = "/w" - } - ], - ActionBarInputs = ["/1", "/leftButton"] - } - }); - await node.MutableDocument(AetheriaStateNode.PlayerSettingsSurfaceKey) - .ReplaceAsync(AetheriaEveSurfaceDocuments.BuildPlayerSettingsSurface( - await node.MutableDocument(AetheriaStateNode.PlayerSettingsKey).ReadAsync(), - now)); - - using var eveCommandClient = await AetheriaClient.OpenAsync( - statePath, - "aetheria-state-smoke-eve-command-client", - startServer: false, - pullOnOpen: true); - await eveCommandClient.Ui.InputSettingsAsync( - AetheriaRuntimeEveCommandKind.SetBindingOverride, - new AetheriaRuntimeInputSettingsCommandBody - { - ActionName = "Thrust", - BindingIndex = 0, - InputSystemPath = "/w", - Enabled = true - }, - "aetheria-state-smoke-eve-command-client"); - await using (var commandVerifyNode = await AetheriaStateNode.OpenAsync( - statePath, - "aetheria-state-smoke-eve-command-check")) - { - if (commandVerifyNode.Documents() - .Select(AetheriaRuntimeEveCommandClient.NormalizeDocument) - .OrderBy(command => command.IssuedAtUtc ?? "", StringComparer.Ordinal) - .ThenBy(command => command.CommandId ?? "", StringComparer.Ordinal) - .All(command => - command.Kind != AetheriaRuntimeEveCommandKind.SetBindingOverride || - command.ClientId != "aetheria-state-smoke-eve-command-client")) - { - throw new InvalidOperationException("AetheriaClient UI submission did not appear as a typed Eve state record."); - } - } - await node.SubmitEveCommandAsync(AetheriaRuntimeEveCommandClient.ToDocument( - AetheriaRuntimeEveCommands.SubmitCatalogCommand( - statePath, - AetheriaRuntimeEveCommandKind.CatalogRefresh, - "aetheria-state-smoke"))); - await node.SubmitEveCommandAsync(AetheriaRuntimeEveCommandClient.ToDocument( - AetheriaRuntimeEveCommands.SubmitPlayerSettingsCommand( - statePath, - AetheriaRuntimeEveCommandKind.IncrementSignificantDigits, - new AetheriaRuntimePlayerSettingsCommandBody(), - "aetheria-state-smoke"))); - await node.SubmitEveCommandAsync(AetheriaRuntimeEveCommandClient.ToDocument( - AetheriaRuntimeEveCommands.SubmitPlayerSettingsCommand( - statePath, - AetheriaRuntimeEveCommandKind.ToggleShowAsteroidsInMinimap, - new AetheriaRuntimePlayerSettingsCommandBody(), - "aetheria-state-smoke"))); - await node.SubmitEveCommandAsync(new AetheriaRuntimeEveCommandDocument - { - Schema = AetheriaRuntimeEveCommandDocument.SchemaId, - CommandId = Guid.NewGuid().ToString("N"), - ProviderId = "aetheria", - SurfaceId = AetheriaRuntimeCatalogCommands.SurfaceId, - Command = "aetheria.catalog.unknown", - Kind = AetheriaRuntimeEveCommandKind.Unknown, - IssuedAtUtc = DateTime.UtcNow.ToString("O"), - ClientId = "aetheria-state-smoke" - }); - var eveCommandReport = await AetheriaEveCommandBridge.AcceptObservedAsync(node); - var eveCommandStatus = new AetheriaEveCommandAcceptanceStatus - { - RuntimeId = "smoke-runtime", - StatePath = statePath, - LastPollAtUtc = now, - LastAcceptedAtUtc = now, - ObservedBeforeAccept = 4, - CommandsAccepted = eveCommandReport.AcceptedCommandIds.Length, - CommandsRejected = eveCommandReport.RejectedCommands, - AppliedCatalogRefreshes = eveCommandReport.AcceptedCatalogRefreshes, - AppliedOperationsRefreshes = eveCommandReport.AcceptedOperationsRefreshes, - AppliedPlayerSettingsCommands = eveCommandReport.AcceptedPlayerSettingsCommands, - AccountedCommandIds = eveCommandReport.AccountedCommandIds, - LastRejectedCommand = eveCommandReport.LastRejectedCommand, - LastRejectedReason = eveCommandReport.LastRejectedReason, - Status = eveCommandReport.RejectedCommands > 0 ? "rejected" : "ok" - }; - await node.MutableDocument(AetheriaStateNode.EveCommandAcceptanceStatusKey).ReplaceAsync(eveCommandStatus); - await node.MutableDocument(AetheriaStateNode.OperationsSurfaceKey).ReplaceAsync(AetheriaEveSurfaceDocuments.BuildOperationsSurface( - eveCommandStatus, - verseHostSettings: await node.MutableDocument(AetheriaStateNode.VerseHostSettingsKey).ReadAsync(), - runtimeSession: await node.MutableDocument(AetheriaStateNode.RuntimeSessionKey("smoke-runtime")).ReadAsync())); - - await node.FlushAsync(); -} -await using (var bootNode = await AetheriaStateNode.OpenAsync( - statePath, - "aetheria-state-smoke-daemon-boot", - hydrationProfile: AetheriaStateHydrationProfile.DaemonBoot)) -{ - if (bootNode.Cache.Get(AetheriaRuntimeVerseRecordKeys.DaemonFrameLatest) == null) - throw new InvalidOperationException("Daemon boot did not hydrate the authoritative restart frame."); - if (bootNode.Cache.Get(runKey) != null || - bootNode.Cache.Get(zoneKey) != null || - bootNode.Cache.Get(entityKey) != null) - { - throw new InvalidOperationException("Daemon boot eagerly hydrated bootstrap run, zone, or entity records."); - } - - await bootNode.Cache.PullBackingStoreRecordsAsync(metadata => - string.Equals(metadata.Key, runKey.ToString(), StringComparison.Ordinal) || - string.Equals(metadata.Key, zoneKey.ToString(), StringComparison.Ordinal) || - string.Equals(metadata.Key, entityKey.ToString(), StringComparison.Ordinal)); - if (bootNode.Cache.Get(runKey)?.RunId != "smoke" || - bootNode.Cache.Get(zoneKey) == null || - bootNode.Cache.Get(entityKey) == null) - { - throw new InvalidOperationException("Cold bootstrap records could not be pulled explicitly for frame recovery."); - } -} -await using (var reopened = await AetheriaStateNode.OpenAsync(statePath, "aetheria-state-smoke-reopen")) -{ - var world = await reopened.MutableDocument(AetheriaStateNode.WorldKey).ReadAsync(); - var item = await reopened.MutableDocument(AetheriaCatalogKeys.ItemDefinitionFromLegacyId(itemLegacyId)).ReadAsync(); - var faction = await reopened.MutableDocument(AetheriaCatalogKeys.CorporationFromLegacyId(factionLegacyId)).ReadAsync(); - var nameFile = await reopened.MutableDocument(AetheriaCatalogKeys.NameFileFromLegacyId(nameFileLegacyId)).ReadAsync(); - var quarantine = await reopened.MutableDocument(AetheriaStateNode.LegacyCatalogQuarantineKey).ReadAsync(); - var catalogSurface = await reopened.MutableDocument(AetheriaStateNode.CatalogSurfaceKey).ReadAsync(); - var eveCommandStatus = await reopened.MutableDocument(AetheriaStateNode.EveCommandAcceptanceStatusKey).ReadAsync(); - var operationsSurface = await reopened.MutableDocument(AetheriaStateNode.OperationsSurfaceKey).ReadAsync(); - var playerSettingsSurface = await reopened.MutableDocument(AetheriaStateNode.PlayerSettingsSurfaceKey).ReadAsync(); - var advertisement = await reopened.MutableDocument(AetheriaStateNode.ProviderAdvertisementSurfaceKey).ReadAsync(); - var daemonProvider = await reopened.MutableDocument(AetheriaRuntimeVerseRecordKeys.DaemonProviderAdvertisement) - .ReadAsync(); - var daemonHealth = await reopened.MutableDocument(AetheriaRuntimeVerseRecordKeys.DaemonHealth) - .ReadAsync(); - var daemonCommandBoundary = await reopened.MutableDocument(AetheriaRuntimeVerseRecordKeys.DaemonCommandBoundary) - .ReadAsync(); - var daemonFrame = await reopened.MutableDocument(AetheriaRuntimeVerseRecordKeys.DaemonFrameLatest) - .ReadAsync(); - var daemonGameSurface = await reopened.MutableDocument(AetheriaRuntimeVerseRecordKeys.DaemonGameSurface) - .ReadAsync(); - var runtimeSession = await reopened.MutableDocument(AetheriaStateNode.RuntimeSessionKey("smoke-runtime")).ReadAsync(); - var playerSettings = await reopened.MutableDocument(AetheriaStateNode.PlayerSettingsKey).ReadAsync(); - var loadout = await reopened.MutableDocument(loadoutKey).ReadAsync(); - var hangar = await reopened.MutableDocument(AetheriaStateNode.HangarKey).ReadAsync(); - var runState = await reopened.MutableDocument(runKey).ReadAsync(); - var zoneState = await reopened.MutableDocument(zoneKey).ReadAsync(); - var entitySnapshot = await reopened.MutableDocument(entityKey).ReadAsync(); - - if (world?.WorldId != "aetheria") - { - throw new InvalidOperationException("World state did not survive flush/reopen."); - } - - if (hangar?.Revision != 10 || - hangar.Deployments.Length != 3 || - hangar.Deployments.Select(value => value.Mode).Distinct().Count() != 3 || - hangar.Deployments.Any(value => !value.Accepted) || - hangar.Ships.Any(value => value.Status != AetheriaHangarShipStatuses.Deployed)) - throw new InvalidOperationException("Canonical Hangar deployment did not survive flush/reopen."); - - var hangarSurface = AetheriaRuntimeHangarSurfaceBuilder.Build( - hangar, - "ship:smoke:aether-runner", - AetheriaGameModes.Arena, - now); - var hangarComponents = Flatten(hangarSurface.Surface.Root).ToArray(); - if (hangarSurface.Surface.Id != AetheriaRuntimeHangarCommands.SurfaceId || - !hangarSurface.Commands.Any(command => command.Command == AetheriaRuntimeHangarCommands.Launch) || - !hangarComponents.Any(component => component.Id == "aetheria.hangar.preview.slot") || - !hangarComponents.Any(component => component.Props.TryGetValue("targetSurfaceId", out var target) && - target == AetheriaRuntimeInventoryPanelSurfaceBuilder.SurfaceId) || - hangarComponents.Count(component => component.Id.StartsWith("aetheria.hangar.bay.", StringComparison.Ordinal)) != 3) - throw new InvalidOperationException("Hangar surface did not expose preview, ship bays, loadout editor, and launch boundary."); - - if (item?.Name != "Smoke Aether Drive") - { - throw new InvalidOperationException("Item definition did not survive flush/reopen."); - } - - if (faction?.LegacyId != factionLegacyId) - { - throw new InvalidOperationException("Faction/corporation document did not survive flush/reopen."); - } - - if (nameFile?.NameCount != 2) - { - throw new InvalidOperationException("Name file document did not survive flush/reopen."); - } - - if (quarantine?.CatalogFingerprint != "smoke" || quarantine.NameFiles.Length != 1) - { - throw new InvalidOperationException("Legacy catalog quarantine did not survive flush/reopen."); - } - - if (catalogSurface == null || - catalogSurface.Surface.Id != AetheriaEveSurfaceDocuments.CatalogSurfaceId) - { - throw new InvalidOperationException("Eve catalog surface did not survive flush/reopen."); - } - - if (eveCommandStatus?.CommandsAccepted != 3 || - eveCommandStatus.CommandsRejected != 1 || - eveCommandStatus.AppliedCatalogRefreshes != 1 || - eveCommandStatus.AppliedPlayerSettingsCommands != 2 || - !eveCommandStatus.LastRejectedReason.Contains("Unknown typed Eve command kind", StringComparison.Ordinal) || - operationsSurface?.Surface.Id != AetheriaEveSurfaceDocuments.OperationsSurfaceId || - !operationsSurface.Surface.Root.Children.Any(child => child.Id == "aetheria.operations.eveCommandAcceptance") || - operationsSurface.Surface.Root.Children.Any(child => child.Id == "aetheria.operations.commitDrain") || - !operationsSurface.Surface.Root.Children.Any(child => child.Id == "aetheria.operations.runtimeSession")) - { - throw new InvalidOperationException( - "Eve request acceptance status or operations surface did not survive flush/reopen: " + - $"accepted={eveCommandStatus?.CommandsAccepted}, " + - $"rejected={eveCommandStatus?.CommandsRejected}, " + - $"catalog={eveCommandStatus?.AppliedCatalogRefreshes}, " + - $"settings={eveCommandStatus?.AppliedPlayerSettingsCommands}, " + - $"reason='{eveCommandStatus?.LastRejectedReason}', " + - $"surface='{operationsSurface?.Surface.Id}', " + - $"children=[{string.Join(", ", operationsSurface?.Surface.Root.Children.Select(child => child.Id) ?? [])}]."); - } - - if (advertisement?.ProviderId != AetheriaEveSurfaceDocuments.ProviderId || - advertisement.Surfaces.Count < 7 || - !advertisement.Surfaces.Any(surface => surface.SurfaceId == AetheriaEveSurfaceDocuments.CatalogSurfaceId) || - !advertisement.Surfaces.Any(surface => surface.SurfaceId == AetheriaEveSurfaceDocuments.OperationsSurfaceId) || - !advertisement.Surfaces.Any(surface => surface.SurfaceId == AetheriaEveSurfaceDocuments.PlayerSettingsSurfaceId) || - !advertisement.Surfaces.Any(surface => - surface.SurfaceId == AetheriaRuntimeDaemonGameSurfaceBuilder.SurfaceId && - surface.RecordRef == AetheriaEveSurfaceDocuments.DaemonGameSurfaceKey) || - !advertisement.Surfaces.Any(surface => - surface.SurfaceId == AetheriaRuntimeDaemonGameSurfaceBuilder.TuiSurfaceId && - surface.RecordRef == AetheriaEveSurfaceDocuments.DaemonGameTuiSurfaceKey) || - !advertisement.Surfaces.Any(surface => - surface.SurfaceId == AetheriaRuntimeDaemonEditorSurfaceBuilder.SurfaceId && - surface.RecordRef == AetheriaEveSurfaceDocuments.DaemonEditorSurfaceKey) || - !advertisement.Surfaces.Any(surface => - surface.SurfaceId == AetheriaRuntimeDaemonEditorSurfaceBuilder.TuiSurfaceId && - surface.RecordRef == AetheriaEveSurfaceDocuments.DaemonEditorTuiSurfaceKey) || - !advertisement.Schemas.Contains("aetheria.runtime_session.v1") || - !advertisement.Schemas.Contains(AetheriaRuntimeDaemonSchemas.ProviderAdvertisement) || - !advertisement.Schemas.Contains(AetheriaRuntimeDaemonSchemas.Frame) || - !advertisement.Schemas.Contains(AetheriaRuntimeDaemonSchemas.SoaView) || - !advertisement.Schemas.Contains(AetheriaRuntimeDaemonSchemas.Health) || - !advertisement.Schemas.Contains(AetheriaRuntimeDaemonSchemas.CommandBoundary) || - !advertisement.Schemas.Contains(AetheriaRuntimeDaemonSchemas.GameSurface) || - !advertisement.Schemas.Contains(AetheriaRuntimeDaemonSchemas.EditorSurface) || - !advertisement.Witnesses.Any(witness => - witness.Kind == "cultcache" && - witness.Reference == statePath) || - !advertisement.Witnesses.Any(witness => - witness.Kind == "cultmesh-record" && - witness.Reference == AetheriaRuntimeVerseRecordKeys.DaemonProviderAdvertisement.ToString()) || - !advertisement.Witnesses.Any(witness => - witness.Kind == "cultmesh-record" && - witness.Reference == AetheriaRuntimeVerseRecordKeys.DaemonCommandBoundary.ToString()) || - !advertisement.Commands.Any(command => - command.Command == "aetheria.daemon.commands" && - command.Transport == "cultmesh") || - !advertisement.Commands.Any(command => command.Command == "aetheria.player_settings.graphics.show_asteroids.toggle") || - !advertisement.Schemas.Contains(AetheriaEveCommandBridge.CommandSchema)) - { - throw new InvalidOperationException("Aetheria Eve provider advertisement did not survive flush/reopen."); - } - - if (daemonProvider?.DaemonId != "smoke-daemon" || - daemonHealth?.FrameId != 7 || - daemonCommandBoundary?.BoundaryId != "aetheria.daemon.commands" || - !daemonCommandBoundary.Commands.Any(command => command.Kind == AetheriaRuntimeDaemonCommandKinds.FireWeaponGroup) || - daemonFrame?.FrameId != 7 || - daemonFrame.SessionId != "smoke-session" || - daemonGameSurface?.Surface.Id != AetheriaRuntimeDaemonGameSurfaceBuilder.SurfaceId || - daemonGameSurface.Surface.Root.Id != "aetheria.daemon.game.root") - { - throw new InvalidOperationException("Daemon Verse API documents did not survive flush/reopen as typed CultCache records."); - } - - var reopenedPlayableWorld = daemonGameSurface.Surface.Root.Children.Single(component => - component.Id == "aetheria.daemon.game.world"); - var expectedReopenedPlayableWorldProps = new Dictionary(StringComparer.Ordinal) - { - ["cameraRig"] = "perspective.entity-forward-follow.v1", - ["cameraTargetEntityId"] = entityKey.ToString(), - ["cameraDistance"] = "30", - ["cameraVerticalFieldOfViewDegrees"] = "60", - ["cameraTargetScreenX"] = "0.64", - ["cameraTargetScreenY"] = "0.19", - ["cameraPositionDamping"] = "0", - ["cameraNearClipPlane"] = "1", - ["cameraFarClipPlane"] = "4096", - ["ambientLightColor"] = "0.2,0.2,0.2", - ["ambientLightIntensity"] = "1.46" - }; - if (expectedReopenedPlayableWorldProps.Any(expected => - !reopenedPlayableWorld.Props.TryGetValue(expected.Key, out var actual) || - !string.Equals(actual, expected.Value, StringComparison.Ordinal))) - { - throw new InvalidOperationException("Playable world camera and environment contract did not survive flush/reopen."); - } - - if (runtimeSession?.RuntimeId != "smoke-runtime" || - runtimeSession.Role != "state-smoke" || - runtimeSession.Status != "running") - { - throw new InvalidOperationException("Runtime session document did not survive flush/reopen."); - } - - if (playerSettings?.ActiveRunKey != runKey.ToString() || - playerSettings.PlayerName != "Smoke Pilot" || - !playerSettings.TutorialPassed || - playerSettings.StoryFileHashes.Length != 1 || - playerSettings.Gameplay.SignificantDigits != 5 || - playerSettings.Graphics.NebulaQuality != "High" || - playerSettings.Graphics.ShowAsteroidsInMinimap || - playerSettings.Input.BindingOverrides.Length != 1 || - playerSettings.Input.ActionBarInputs.Length != 2) - { - throw new InvalidOperationException("Player settings did not survive flush/reopen."); - } - - if (playerSettingsSurface?.Surface.Id != AetheriaEveSurfaceDocuments.PlayerSettingsSurfaceId || - !playerSettingsSurface.Surface.Root.Children.Any(child => child.Id == "aetheria.playerSettings.gameplay") || - !playerSettingsSurface.Surface.Root.Children.Any(child => child.Id == "aetheria.playerSettings.graphics")) - { - throw new InvalidOperationException("Player settings Eve surface did not survive flush/reopen."); - } - - if (loadout?.RootEntity.Hull.ItemKey != AetheriaCatalogKeys.ItemDefinitionFromLegacyId(itemLegacyId).ToString() || - loadout.RootEntity.Equipment.Length != 1 || - loadout.RootEntity.Equipment[0].Rotation != "Clockwise" || - loadout.RootEntity.Equipment[0].Item.Enabled || - !loadout.RootEntity.Equipment[0].Item.OverrideShutdown || - loadout.RootEntity.WeaponGroups.Length != 1) - { - throw new InvalidOperationException("Loadout template did not survive flush/reopen."); - } - - if (runState?.RunId != "smoke" || - runState.GenerationSeed != 424242 || - runState.CurrentEntityKey != entityKey.ToString() || - runState.ZoneKeys.Length != 1) - { - throw new InvalidOperationException("Run state did not survive flush/reopen."); - } - - if (zoneState?.EntityKeys.Length != 1 || - zoneState.EntityKeys[0] != entityKey.ToString() || - zoneState.Position.X != 4.0 || - zoneState.OwnerFactionIndex != 0 || - zoneState.Orbits.Length != 1 || - zoneState.Orbits[0].OrbitKey != "smoke:orbit" || - zoneState.Bodies.Length != 1 || - zoneState.Bodies[0].Kind != "asteroid_belt" || - zoneState.Bodies[0].Resources.Length != 1 || - zoneState.Bodies[0].Asteroids.Length != 1 || - zoneState.Bodies[0].Asteroids[0].Damage != 1.25 || - zoneState.Bodies[0].Asteroids[0].RespawnTimer != 6.5 || - zoneState.Bodies[0].Asteroids[0].MiningAccumulators.Length != 1 || - zoneState.Bodies[0].Asteroids[0].MiningAccumulators[0].MinerEntityKey != entityKey.ToString() || - zoneState.Bodies[0].Asteroids[0].MiningAccumulators[0].Amount != 0.75) - { - throw new InvalidOperationException("Zone state did not survive flush/reopen."); - } - - if (entitySnapshot?.Kind != "ship" || - entitySnapshot.LookDirection.X != 0.8 || - entitySnapshot.LookDirection.Y != 0.6 || - entitySnapshot.HelmInput.X != -0.25 || - entitySnapshot.HelmInput.Y != 0.75 || - entitySnapshot.Equipment.Length != 1 || - entitySnapshot.Equipment[0].Quality != 0.9 || - entitySnapshot.Equipment[0].Durability != 0.8 || - entitySnapshot.Equipment[0].Quantity != 1 || - entitySnapshot.Equipment[0].Rotation != "CounterClockwise" || - entitySnapshot.Equipment[0].Enabled || - !entitySnapshot.Equipment[0].OverrideShutdown || - entitySnapshot.CargoContents.Length != 1 || - entitySnapshot.CargoContents[0].Items[0].Item.Quantity != 7 || - entitySnapshot.CargoContents[0].Items[0].Rotation != "Half" || - entitySnapshot.DockingBayAssignments.Length != 1 || - entitySnapshot.DockingBayAssignments[0] != -1 || - entitySnapshot.Visibility != 12.75 || - entitySnapshot.VisibilitySourceCount != 3 || - entitySnapshot.Contacts.Length != 1 || - entitySnapshot.Contacts[0].TargetEntityKey != entityKey.ToString() || - entitySnapshot.Contacts[0].InfoGathered != 0.85 || - !entitySnapshot.Contacts[0].Hostile || - !entitySnapshot.Contacts[0].Visible || - entitySnapshot.WeaponGroups.Length != 1 || - entitySnapshot.WeaponStates.Length != 1 || - entitySnapshot.WeaponStates[0].BehaviorKind != "LockWeapon" || - entitySnapshot.WeaponStates[0].LockProgress != 0.65 || - entitySnapshot.WeaponStates[0].LockTargetEntityKey != entityKey.ToString() || - entitySnapshot.BehaviorStates.Length != 11 || - entitySnapshot.BehaviorStates[0].BehaviorKind != "AetherDrive" || - entitySnapshot.BehaviorStates[0].AetherDriveAxisX != 0.5 || - entitySnapshot.BehaviorStates[0].AetherDriveRpmY != 900.0 || - entitySnapshot.BehaviorStates[0].AetherDriveMaximumRpm != 2400.0 || - entitySnapshot.BehaviorStates[1].BehaviorKind != "ResourceScanner" || - entitySnapshot.BehaviorStates[1].ResourceScannerTargetBodyKey != "aetheria.body:smoke:body" || - entitySnapshot.BehaviorStates[1].ResourceScannerAsteroidIndex != 2 || - entitySnapshot.BehaviorStates[1].ResourceScannerScanTime != 1.25 || - entitySnapshot.BehaviorStates[1].ResourceScannerScanDuration != 3.5 || - entitySnapshot.BehaviorStates[2].BehaviorKind != "MiningTool" || - entitySnapshot.BehaviorStates[2].MiningToolAsteroidBeltKey != "aetheria.body:smoke:body" || - entitySnapshot.BehaviorStates[2].MiningToolAsteroidIndex != 3 || - entitySnapshot.BehaviorStates[2].MiningToolRange != 275.0 || - entitySnapshot.BehaviorStates[3].BehaviorKind != "Thruster" || - entitySnapshot.BehaviorStates[3].ThrusterAxis != 0.8 || - entitySnapshot.BehaviorStates[3].ThrusterThrust != 125.0 || - entitySnapshot.BehaviorStates[3].ThrusterTorque != -0.4 || - entitySnapshot.BehaviorStates[4].BehaviorKind != "Shield" || - entitySnapshot.BehaviorStates[4].ShieldEfficiency != 0.7 || - entitySnapshot.BehaviorStates[4].ShieldEnergyUsage != 1.4 || - entitySnapshot.BehaviorStates[5].BehaviorKind != "VelocityLimit" || - entitySnapshot.BehaviorStates[5].VelocityLimit != 42.0 || - entitySnapshot.BehaviorStates[6].BehaviorKind != "Thermotoggle" || - entitySnapshot.BehaviorStates[6].ThermotoggleTargetTemperature != 315.5 || - entitySnapshot.BehaviorStates[7].BehaviorKind != "Switch" || - !entitySnapshot.BehaviorStates[7].SwitchActivated || - entitySnapshot.BehaviorStates[8].BehaviorKind != "Trigger" || - !entitySnapshot.BehaviorStates[8].TriggerPulled || - entitySnapshot.BehaviorStates[9].BehaviorKind != "StatModifier" || - !entitySnapshot.BehaviorStates[9].StatModifierApplied || - !entitySnapshot.BehaviorStates[9].StatModifierExecuted || - entitySnapshot.BehaviorStates[9].StatModifierTargetStatCount != 2 || - entitySnapshot.BehaviorStates[10].BehaviorKind != "TurretController" || - entitySnapshot.BehaviorStates[10].TurretControllerWeaponCount != 2 || - entitySnapshot.BehaviorStates[10].TurretControllerShotSpeed != 80.0 || - !entitySnapshot.BehaviorStates[10].TurretControllerPredictShots || - entitySnapshot.StatGrids.Length != 1 || - entitySnapshot.StatGrids[0].Values.Length != 4) - { - throw new InvalidOperationException("Entity snapshot did not survive flush/reopen."); - } -} - -var capabilityRun = new AetheriaRuntimeRunCheckpointCommit -{ - RunId = "capability-smoke", - CurrentZoneIndex = 0, - CurrentEntityKey = AetheriaRuntimeRunCheckpointCommit.EntityRecordKey("capability-smoke", 0, 7), - Zones = - [ - new AetheriaRuntimeZoneSnapshotCommit - { - ZoneIndex = 0, - Entities = - [ - new AetheriaRuntimeEntitySnapshotCommit - { - EntityIndex = 7, - EntityId = AetheriaRuntimeRunCheckpointCommit.EntityRecordKey("capability-smoke", 0, 7), - WeaponGroups = [new[] { 2 }] - } - ] - } - ] -}; -var capability = AetheriaRuntimeInputCapabilityDocument.FromFrame(new AetheriaRuntimeDaemonFrameDocument -{ - FrameId = 12, - Run = capabilityRun -}).ToEveDocument(); -var advertisedActionIds = capability.Actions.Select(action => action.ActionId).ToHashSet(StringComparer.Ordinal); -if (!advertisedActionIds.Contains("weapon-group.0.fire") || - capability.DefaultProfiles.SelectMany(profile => profile.Bindings) - .Any(binding => !advertisedActionIds.Contains(binding.ActionId))) -{ - throw new InvalidOperationException("Portable Eve input capability did not preserve weapon groups and valid default bindings."); -} - -await ProveDirectSoaPublicationPipeline(); - -Console.WriteLine($"Aetheria typed state smoke passed: {statePath}"); - -static async Task ProveDirectSoaPublicationPipeline() -{ - using var cache = new CultCache(); - var frame = new AetheriaRuntimeDaemonFrameDocument - { - DaemonId = "aetheria-daemon", - SessionId = "soa-pipeline-smoke", - FrameId = 41, - SimulationTimeSeconds = 4, - Run = new AetheriaRuntimeRunCheckpointCommit - { - RunId = "soa-pipeline-smoke", - CurrentZoneIndex = 0, - CurrentEntityKey = AetheriaRuntimeRunCheckpointCommit.EntityRecordKey("soa-pipeline-smoke", 0, 12), - Zones = - [ - new AetheriaRuntimeZoneSnapshotCommit - { - ZoneIndex = 0, - Entities = - [ - new AetheriaRuntimeEntitySnapshotCommit - { - EntityIndex = 12, - EntityId = "ship:soa-witness", - Name = "SoA Witness", - Kind = "ship", - FactionKey = "faction:smoke", - PositionX = 1.25, - PositionY = -2.5, - PositionZ = 7.75, - IsActive = true - } - ], - DroppedPickups = - [ - new AetheriaRuntimeDroppedPickupCommit - { - PickupIndex = 3, - PositionX = 4, - PositionY = 5, - PositionZ = 6 - } - ], - PhysicalPayloads = - [ - new AetheriaRuntimePhysicalPayloadCommit - { - PayloadId = "mine:soa-witness", - PayloadKind = "mine", - FactionKey = "faction:smoke", - PositionX = 9, - PositionZ = 11, - Radius = 2, - Active = true, - Stationary = true - } - ], - Bodies = - [ - new AetheriaRuntimeBodySnapshotCommit - { - BodyKey = "body:soa-sun", - Name = "SoA Sun", - Kind = "sun", - Mass = 100, - BodyRadiusMultiplier = 1, - GravityInfluenceCenterX = 20, - GravityInfluenceCenterZ = 30 - }, - new AetheriaRuntimeBodySnapshotCommit - { - BodyKey = "body:soa-belt", - Name = "SoA Belt", - Kind = "asteroid_belt", - GravityInfluenceCenterX = 40, - GravityInfluenceCenterZ = 50, - Asteroids = - [ - new AetheriaRuntimeAsteroidCommit - { - Distance = 5, - Phase = 0, - Size = 2, - RotationSpeed = 0.25 - } - ] - } - ] - } - ] - } - }; - - using var publisher = new AetheriaRuntimeDaemonSoaFramePublisher(producerEpoch: 9); - var built = publisher.BuildCurrentZoneEntities(frame); - var payloadIdentity = built.View.Identities.SingleOrDefault(identity => - string.Equals(identity.EntityId, "soa-pipeline-smoke:zone:0:physical-payload:mine:soa-witness", StringComparison.Ordinal)); - var pickupIdentity = built.View.Identities.Single(identity => - string.Equals(identity.EntityId, "pickup:0:3", StringComparison.Ordinal)); - var sunIdentity = built.View.Identities.Single(identity => - string.Equals(identity.Kind, "celestial.sun", StringComparison.Ordinal)); - var asteroidIdentity = built.View.Identities.Single(identity => - string.Equals(identity.Kind, "celestial.asteroid", StringComparison.Ordinal)); - if (payloadIdentity == null || !string.Equals(payloadIdentity.AssetRef, "prefab.entity.mine", StringComparison.Ordinal) || - !string.Equals(payloadIdentity.Kind, "physical-payload", StringComparison.Ordinal) || payloadIdentity.Selectable || - payloadIdentity.EntityIndex >= -1 || pickupIdentity.EntityIndex >= -1 || - payloadIdentity.EntityIndex == pickupIdentity.EntityIndex || - sunIdentity.AssetRef != "prefab.body.sun" || asteroidIdentity.AssetRef != "prefab.body.asteroid" || - sunIdentity.Selectable || asteroidIdentity.Selectable || - new[] { payloadIdentity.EntityIndex, pickupIdentity.EntityIndex, sunIdentity.EntityIndex, asteroidIdentity.EntityIndex } - .Distinct().Count() != 4 || - built.View.Columns.Any(column => column.ElementCount != 5)) - throw new InvalidOperationException("Physical payload did not enter the authoritative SoA generation with its provider asset identity."); - if (cache.Get(AetheriaRuntimeVerseRecordKeys.EveEntitySoaViewLatest) != null) - throw new InvalidOperationException("SoA view became visible before its body publication."); - - var published = await publisher.PublishAsync(built, includeRealtimePayload: true); - if (cache.Get(AetheriaRuntimeVerseRecordKeys.EveEntitySoaViewLatest) != null) - throw new InvalidOperationException("Building a CultMesh body publication made the Eve view visible before commit."); - if (cache.GetAll().Any() || cache.GetAll().Any()) - throw new InvalidOperationException("Hot SoA body bytes were retained as snapshot-addressable CDN state."); - - frame.FrameId++; - var nextBuilt = publisher.BuildCurrentZoneEntities(frame); - var nextPayloadIdentity = nextBuilt.View.Identities.Single(identity => identity.EntityId == payloadIdentity.EntityId); - var nextPickupIdentity = nextBuilt.View.Identities.Single(identity => identity.EntityId == pickupIdentity.EntityId); - var nextSunIdentity = nextBuilt.View.Identities.Single(identity => identity.EntityId == sunIdentity.EntityId); - var nextAsteroidIdentity = nextBuilt.View.Identities.Single(identity => identity.EntityId == asteroidIdentity.EntityId); - if (nextPayloadIdentity.EntityIndex != payloadIdentity.EntityIndex || - nextPickupIdentity.EntityIndex != pickupIdentity.EntityIndex || - nextSunIdentity.EntityIndex != sunIdentity.EntityIndex || - nextAsteroidIdentity.EntityIndex != asteroidIdentity.EntityIndex) - throw new InvalidOperationException("Synthetic SoA identity changed across immutable generations in one producer epoch."); - var next = await publisher.PublishAsync(nextBuilt); - if (published.Body.RecordKey.Equals(next.Body.RecordKey)) - throw new InvalidOperationException("Distinct SoA generations collided on one CultMesh body publication key."); - - await cache.UpsertAsync( - published.Body, - new CultRecordHandle(published.Body.RecordKey)); - await cache.UpsertAsync( - next.Body, - new CultRecordHandle(next.Body.RecordKey)); - await cache.UpsertAsync( - next.Body, - new CultRecordHandle( - CultMeshBodyPublicationDocument.CreateLatestRecordKey(next.Body.BodyId))); - if (cache.Get(AetheriaRuntimeVerseRecordKeys.EveEntitySoaViewLatest) != null) - throw new InvalidOperationException("Publishing the body record alone made the Eve view visible."); - await cache.UpsertAsync( - published.View, - new CultRecordHandle(AetheriaRuntimeVerseRecordKeys.EveEntitySoaViewLatest)); - - var bodyHandle = new CultMeshBodyPublicationHandle( - published.View.Buffers[0].BufferId, - published.View.ProducerEpoch, - published.View.Sequence); - var body = cache.Get(bodyHandle.RecordKey) - ?? throw new InvalidOperationException("Typed CultMesh body publication did not roundtrip through CultCache."); - bodyHandle.Validate(body); - var latest = cache.Get( - CultMeshBodyPublicationDocument.CreateLatestRecordKey(body.BodyId)); - if (latest == null || latest.Sequence != next.Body.Sequence || body.Sequence == latest.Sequence) - throw new InvalidOperationException("The view generation was substituted by the latest CultMesh body publication."); - var view = cache.Get(AetheriaRuntimeVerseRecordKeys.EveEntitySoaViewLatest) - ?? throw new InvalidOperationException("Typed Eve SoA view did not become visible after body publication."); - var localRepresentation = body.Representations.SingleOrDefault(candidate => - candidate.TransportKind == CultMeshBodyTransportKind.SharedMemory); - if (localRepresentation == null || published.RealtimePayload.IsEmpty || - body.Representations.Any(candidate => candidate.TransportKind == CultMeshBodyTransportKind.Network) || - body.Representations.Any(candidate => candidate.TransportKind == CultMeshBodyTransportKind.SharedFileMapping)) - throw new InvalidOperationException("Direct SoA publication did not expose shared memory plus an explicit realtime payload."); - - var request = new CultMeshBodyValidationRequest - { - BodyId = body.BodyId, - SchemaId = body.SchemaId, - LayoutVersion = body.LayoutVersion, - ProducerEpoch = body.ProducerEpoch, - Sequence = body.Sequence, - Capacity = body.Capacity, - AccessMode = CultMeshBodyAccessMode.ReadOnly, - NowUtc = DateTimeOffset.UtcNow - }; - using var localLease = new CultMeshBodyPublicationResolver(new CultMeshBodyTransportService( - new ICultMeshBodyTransportAdapter[] - { - new CultMeshSharedMemoryBodyAdapter() - }, - (producerId, _) => producerId == AetheriaRuntimeDaemonSoaFramePublisher.ProducerId)) - .ResolveReadOnly(body, request); - - var localEntityIndex = ReadInt32(view, localLease, "entity.index", 0); - var networkEntityIndex = ReadInt32Bytes(view, published.RealtimePayload.Span, "entity.index", 0); - var localPosition = ReadFloat3(view, localLease, "transform.position", 0); - var networkPosition = ReadFloat3Bytes(view, published.RealtimePayload.Span, "transform.position", 0); - var localPayloadEntityIndex = ReadInt32(view, localLease, "entity.index", 2); - var networkPayloadEntityIndex = ReadInt32Bytes(view, published.RealtimePayload.Span, "entity.index", 2); - var localPayloadPosition = ReadFloat3(view, localLease, "transform.position", 2); - var networkPayloadPosition = ReadFloat3Bytes(view, published.RealtimePayload.Span, "transform.position", 2); - var localSunPosition = ReadFloat3(view, localLease, "transform.position", 3); - var networkSunPosition = ReadFloat3Bytes(view, published.RealtimePayload.Span, "transform.position", 3); - var localAsteroidPosition = ReadFloat3(view, localLease, "transform.position", 4); - var networkAsteroidPosition = ReadFloat3Bytes(view, published.RealtimePayload.Span, "transform.position", 4); - var identity = view.Identities.Single(candidate => candidate.Index == localEntityIndex); - if (localLease.TransportKind != CultMeshBodyTransportKind.SharedMemory || - localEntityIndex != 12 || networkEntityIndex != localEntityIndex || - localPosition != (1.25f, -2.5f, 7.75f) || networkPosition != localPosition || - localPayloadEntityIndex != payloadIdentity.EntityIndex || - networkPayloadEntityIndex != localPayloadEntityIndex || - localPayloadPosition != (9f, 0f, 11f) || networkPayloadPosition != localPayloadPosition || - localSunPosition.X != 20f || localSunPosition.Z != 30f || networkSunPosition != localSunPosition || - localAsteroidPosition.X != 45f || localAsteroidPosition.Z != 50f || - networkAsteroidPosition != localAsteroidPosition || - identity.EntityId != "ship:soa-witness" || identity.Label != "SoA Witness") - throw new InvalidOperationException("Shared-memory and realtime SoA payloads were not logically equivalent."); - - frame.Run.Zones[0].PhysicalPayloads[0].Active = false; - var withoutPayload = publisher.BuildCurrentZoneEntities(frame); - if (withoutPayload.View.Identities.Any(candidate => candidate.EntityId == payloadIdentity.EntityId) || - withoutPayload.View.Identities.Single(candidate => candidate.EntityId == pickupIdentity.EntityId).EntityIndex != pickupIdentity.EntityIndex) - throw new InvalidOperationException("Inactive physical payload remained in SoA or disturbed a surviving synthetic identity."); -} - -static IEnumerable Flatten(AetheriaRuntimeSurfaceComponent component) -{ - yield return component; - foreach (var child in component.Children ?? Array.Empty()) - foreach (var descendant in Flatten(child)) - yield return descendant; -} - -static int ReadInt32(EveEntitySoaViewDocument view, ICultMeshBodyReadLease lease, string semantic, int row) -{ - var column = view.Columns.Single(candidate => candidate.Semantic == semantic); - var buffer = view.Buffers.Single(candidate => candidate.BufferId == column.BufferId); - return lease.ReadInt32(buffer.ByteOffset + column.ByteOffset + (long)row * column.ElementStride); -} - -static int ReadInt32Bytes(EveEntitySoaViewDocument view, ReadOnlySpan body, string semantic, int row) -{ - var column = view.Columns.Single(candidate => candidate.Semantic == semantic); - var buffer = view.Buffers.Single(candidate => candidate.BufferId == column.BufferId); - var offset = checked((int)(buffer.ByteOffset + column.ByteOffset + (long)row * column.ElementStride)); - return BinaryPrimitives.ReadInt32LittleEndian(body.Slice(offset, sizeof(int))); -} - -static (float X, float Y, float Z) ReadFloat3( - EveEntitySoaViewDocument view, - ICultMeshBodyReadLease lease, - string semantic, - int row) -{ - var column = view.Columns.Single(candidate => candidate.Semantic == semantic); - var buffer = view.Buffers.Single(candidate => candidate.BufferId == column.BufferId); - var offset = buffer.ByteOffset + column.ByteOffset + (long)row * column.ElementStride; - return (lease.ReadSingle(offset), lease.ReadSingle(offset + 4), lease.ReadSingle(offset + 8)); -} - -static (float X, float Y, float Z) ReadFloat3Bytes( - EveEntitySoaViewDocument view, - ReadOnlySpan body, - string semantic, - int row) -{ - var column = view.Columns.Single(candidate => candidate.Semantic == semantic); - var buffer = view.Buffers.Single(candidate => candidate.BufferId == column.BufferId); - var offset = checked((int)(buffer.ByteOffset + column.ByteOffset + (long)row * column.ElementStride)); - return ( - BitConverter.Int32BitsToSingle(BinaryPrimitives.ReadInt32LittleEndian(body.Slice(offset, sizeof(float)))), - BitConverter.Int32BitsToSingle(BinaryPrimitives.ReadInt32LittleEndian(body.Slice(offset + 4, sizeof(float)))), - BitConverter.Int32BitsToSingle(BinaryPrimitives.ReadInt32LittleEndian(body.Slice(offset + 8, sizeof(float))))); -} diff --git a/Aetheria.State.Unity.Smoke/Aetheria.State.Unity.Smoke.csproj b/Aetheria.State.Unity.Smoke/Aetheria.State.Unity.Smoke.csproj deleted file mode 100644 index de6a3899a..000000000 --- a/Aetheria.State.Unity.Smoke/Aetheria.State.Unity.Smoke.csproj +++ /dev/null @@ -1,14 +0,0 @@ - - - Exe - net10.0 - enable - latest - enable - - - - - - - diff --git a/Aetheria.State.Unity.Smoke/Program.cs b/Aetheria.State.Unity.Smoke/Program.cs deleted file mode 100644 index 7e71243c2..000000000 --- a/Aetheria.State.Unity.Smoke/Program.cs +++ /dev/null @@ -1,281 +0,0 @@ -extern alias PackageUnity; - -using Aetheria.State; -using Aetheria.State.Unity; -using PackageUnity::GameCult.Aetheria.State.Verse; - -var root = args.Length > 0 ? Path.GetFullPath(args[0]) : Directory.GetCurrentDirectory(); -var statePath = args.Length > 1 - ? Path.GetFullPath(args[1]) - : AetheriaStatePaths.ResolveDefaultStatePath(root); - -await using var client = await AetheriaRuntimeCatalogClient.OpenAsync(statePath); -var catalog = client.ReadCatalog(); -var packageCatalog = AetheriaRuntimeCatalogStore.OpenReadOnly(statePath); -var surface = client.ReadCatalogSurface(); -var commitSmokeDirectory = Path.Combine(Path.GetTempPath(), "aetheria-state-unity-smoke", Guid.NewGuid().ToString("N")); -var commitSmokeStatePath = Path.Combine(commitSmokeDirectory, "aetheria-world.cc"); -Directory.CreateDirectory(commitSmokeDirectory); - -if (catalog.Items.Count != 115) -{ - throw new InvalidOperationException($"Expected 115 runtime catalog items, found {catalog.Items.Count}."); -} - -if (packageCatalog.Items.Count != catalog.Items.Count || - packageCatalog.Corporations.Count != catalog.Corporations.Count || - packageCatalog.NameFiles.Count != catalog.NameFiles.Count) -{ - throw new InvalidOperationException( - $"Package catalog store mismatch: {packageCatalog.Items.Count}/{packageCatalog.Corporations.Count}/{packageCatalog.NameFiles.Count} " + - $"!= {catalog.Items.Count}/{catalog.Corporations.Count}/{catalog.NameFiles.Count}."); -} - -if (catalog.TradeItems.Count == 0) -{ - throw new InvalidOperationException("Runtime catalog has no trade items."); -} - -if (catalog.EquipmentItems.Count == 0) -{ - throw new InvalidOperationException("Runtime catalog has no equipment items."); -} - -var shaped = catalog.Items.FirstOrDefault(item => item.ShapeCells.Count > 0) - ?? throw new InvalidOperationException("Runtime catalog has no typed shape masks."); -if (string.IsNullOrWhiteSpace(shaped.ItemKey)) -{ - throw new InvalidOperationException($"Runtime shape sample has no typed item key: {shaped.Name}."); -} - -var packageShaped = packageCatalog.FindItem(shaped.ItemKey); -if (packageShaped == null || - packageShaped.Name != shaped.Name || - packageShaped.ShapeCells.Count != shaped.ShapeCells.Count) -{ - throw new InvalidOperationException($"Package catalog store did not read the expected typed item payload for {shaped.ItemKey}."); -} - -if (shaped.ShapeCells.Count != shaped.OccupiedCells) -{ - throw new InvalidOperationException( - $"Runtime shape mask count mismatch for {shaped.Name}: cells={shaped.ShapeCells.Count}, occupied={shaped.OccupiedCells}."); -} - -if (shaped.ShapeCells.Any(cell => cell.X < 0 || cell.Y < 0 || cell.X >= shaped.ShapeWidth || cell.Y >= shaped.ShapeHeight)) -{ - throw new InvalidOperationException($"Runtime shape mask has out-of-bounds cells for {shaped.Name}."); -} - -var interior = catalog.Items.FirstOrDefault(item => item.InteriorShapeCells.Count > 0) - ?? throw new InvalidOperationException("Runtime catalog has no typed interior shape masks."); -if (interior.InteriorShapeCells.Count != interior.InteriorOccupiedCells) -{ - throw new InvalidOperationException( - $"Runtime interior shape mask count mismatch for {interior.Name}: cells={interior.InteriorShapeCells.Count}, occupied={interior.InteriorOccupiedCells}."); -} - -var hardpointHost = catalog.Items.FirstOrDefault(item => item.Hardpoints.Count > 0) - ?? throw new InvalidOperationException("Runtime catalog has no typed hardpoints."); -var hardpoint = hardpointHost.Hardpoints.First(); -if (hardpoint.ShapeCells.Count != hardpoint.OccupiedCells) -{ - throw new InvalidOperationException( - $"Runtime hardpoint shape count mismatch for {hardpointHost.Name}: cells={hardpoint.ShapeCells.Count}, occupied={hardpoint.OccupiedCells}."); -} - -var behaviorKind = catalog.Items.SelectMany(item => item.BehaviorKinds).FirstOrDefault() - ?? throw new InvalidOperationException("Runtime catalog has no behavior kind fingerprints."); -if (!catalog.FindItemsByBehavior(behaviorKind).Any()) -{ - throw new InvalidOperationException($"Runtime catalog behavior lookup failed for {behaviorKind}."); -} - -var behaviorHost = catalog.Items.FirstOrDefault(item => item.BehaviorPayloads.Count > 0) - ?? throw new InvalidOperationException("Runtime catalog has no typed behavior payloads."); -if (!behaviorHost.BehaviorPayloads.All(payload => behaviorHost.BehaviorKinds.Contains(payload.Kind))) -{ - throw new InvalidOperationException( - $"Runtime behavior payload kind index mismatch for {behaviorHost.Name}."); -} - -var behaviorPayload = behaviorHost.BehaviorPayloads.First(); -if (behaviorPayload.Fields.Count == 0) -{ - throw new InvalidOperationException($"Runtime behavior payload has no fields for {behaviorHost.Name}."); -} - -var behaviorItemRefsMissingItemKeys = packageCatalog.Items - .SelectMany(item => item.BehaviorPayloads) - .Sum(CountRequiredBehaviorItemRefsMissingItemKeys); -if (behaviorItemRefsMissingItemKeys > 0) -{ - throw new InvalidOperationException( - $"Runtime behavior payload item refs missing item-key projections: {behaviorItemRefsMissingItemKeys}."); -} - -var equipment = catalog.EquipmentItems.First(); -if (!catalog.FindItemsByHardpoint(equipment.HardpointType).Any()) -{ - throw new InvalidOperationException($"Runtime catalog hardpoint lookup failed for {equipment.HardpointType}."); -} - -var manufactured = catalog.Items.FirstOrDefault(item => !string.IsNullOrWhiteSpace(item.ManufacturerKey)) - ?? throw new InvalidOperationException("Runtime catalog has no manufactured item."); -if (string.IsNullOrWhiteSpace(manufactured.ManufacturerKey) || - catalog.FindCorporation(manufactured.ManufacturerKey) == null || - catalog.GetManufacturer(manufactured) == null) -{ - throw new InvalidOperationException($"Runtime catalog typed manufacturer lookup failed for {manufactured.Name}."); -} - -var packageCorporation = packageCatalog.Corporations.FirstOrDefault(corporation => !string.IsNullOrWhiteSpace(corporation.GeonameFileKey)); -if (packageCorporation == null) -{ - throw new InvalidOperationException("Package catalog store did not read any corporation name-file links."); -} - -var packageNameFile = packageCatalog.GetNameFile(packageCorporation); -if (string.IsNullOrWhiteSpace(packageCorporation.GeonameFileKey) || - packageCatalog.FindNameFile(packageCorporation.GeonameFileKey) == null || - packageNameFile == null || - string.IsNullOrWhiteSpace(packageNameFile.NameFileKey) || - packageNameFile.Names.Count == 0 || - packageNameFile.Names.Count != packageNameFile.NameCount) -{ - throw new InvalidOperationException("Package catalog store did not read typed corporation/name-file links with full names."); -} - -if (packageCorporation.Allegiances.Count == 0 || packageCorporation.Allegiances.Count != packageCorporation.AllegianceCount) -{ - throw new InvalidOperationException("Package catalog store did not read corporation allegiance edges."); -} - -if (surface?.Schema != "gamecult.eve.surface.v1" || - surface.Surface.Id != AetheriaEveSurfaceDocuments.CatalogSurfaceId) -{ - throw new InvalidOperationException("Runtime catalog client did not read the typed Eve surface."); -} - -if (surface == null || - surface.Schema != "gamecult.eve.surface.v1" || - surface.ProviderId != "aetheria" || - surface.Surface.Root.Kind != "surface" || - surface.Surface.Root.Children.Length == 0 || - surface.Commands.All(command => command.Command != AetheriaRuntimeCatalogCommands.Refresh)) -{ - throw new InvalidOperationException("Unity runtime catalog client did not read the typed Eve surface contract from the managed document."); -} - -try -{ - var eveCommand = AetheriaRuntimeEveCommands.SubmitCatalogCommand( - commitSmokeStatePath, - AetheriaRuntimeEveCommandKind.CatalogRefresh, - "aetheria-state-unity-smoke"); - await using var eveCommandNode = await AetheriaStateNode.OpenAsync( - commitSmokeStatePath, - "aetheria-unity-runtime-eve-command-smoke"); - await eveCommandNode.SubmitEveCommandAsync(new GameCult.Aetheria.State.Verse.AetheriaRuntimeEveCommandDocument - { - Schema = eveCommand.Schema, - CommandId = eveCommand.CommandId, - ProviderId = eveCommand.ProviderId, - SurfaceId = eveCommand.SurfaceId, - Command = eveCommand.Command, - Kind = (GameCult.Aetheria.State.Verse.AetheriaRuntimeEveCommandKind)(int)eveCommand.Kind, - IssuedAtUtc = eveCommand.IssuedAtUtc, - ClientId = eveCommand.ClientId, - PlayerSettings = new GameCult.Aetheria.State.Verse.AetheriaRuntimePlayerSettingsCommandBody - { - PlayerName = eveCommand.PlayerSettings.PlayerName - }, - InputSettings = new GameCult.Aetheria.State.Verse.AetheriaRuntimeInputSettingsCommandBody - { - ActionName = eveCommand.InputSettings.ActionName, - BindingIndex = eveCommand.InputSettings.BindingIndex, - InputSystemPath = eveCommand.InputSettings.InputSystemPath, - Enabled = eveCommand.InputSettings.Enabled - } - }); - await eveCommandNode.FlushAsync(); - await using var eveCommandCheckNode = await AetheriaStateNode.OpenAsync( - commitSmokeStatePath, - "aetheria-unity-runtime-eve-command-check"); - var eveSubmitted = eveCommandCheckNode.Documents() - .Select(AetheriaRuntimeEveCommandClient.NormalizeDocument) - .OrderBy(command => command.IssuedAtUtc ?? "", StringComparer.Ordinal) - .ThenBy(command => command.CommandId ?? "", StringComparer.Ordinal) - .ToArray(); - if (eveSubmitted.Length != 1 || - eveSubmitted[0].Schema != AetheriaRuntimeEveCommandClient.CommandSchema || - eveSubmitted[0].CommandId != eveCommand.CommandId || - eveSubmitted[0].ProviderId != "aetheria" || - eveSubmitted[0].SurfaceId != AetheriaRuntimeCatalogCommands.SurfaceId || - eveSubmitted[0].Command != AetheriaRuntimeCatalogCommands.Refresh || - (int)eveSubmitted[0].Kind != (int)AetheriaRuntimeEveCommandKind.CatalogRefresh || - eveSubmitted[0].PlayerSettings == null || - eveSubmitted[0].InputSettings == null) - { - var submitted = eveSubmitted.Length > 0 ? eveSubmitted[0] : null; - throw new InvalidOperationException( - "Runtime Eve command state record did not preserve typed command envelopes separately from state commits. " + - $"Count={eveSubmitted.Length}, " + - $"Schema={submitted?.Schema ?? ""}, " + - $"CommandId={submitted?.CommandId ?? ""} expected {eveCommand.CommandId}, " + - $"ProviderId={submitted?.ProviderId ?? ""}, " + - $"SurfaceId={submitted?.SurfaceId ?? ""}, " + - $"Command={submitted?.Command ?? ""}, " + - $"Kind={submitted?.Kind.ToString() ?? ""}, " + - $"PlayerSettings={(submitted?.PlayerSettings == null ? "null" : "present")}, " + - $"InputSettings={(submitted?.InputSettings == null ? "null" : "present")}."); - } -} -finally -{ - if (Environment.GetEnvironmentVariable("AETHERIA_KEEP_SMOKE_STATE") == "1") - Console.Error.WriteLine($"Kept smoke state: {commitSmokeDirectory}"); - else - Directory.Delete(commitSmokeDirectory, true); -} - -Console.WriteLine($"Aetheria Unity runtime catalog smoke passed: {statePath}"); -Console.WriteLine($"Items/trade/equipment: {catalog.Items.Count}/{catalog.TradeItems.Count}/{catalog.EquipmentItems.Count}"); -Console.WriteLine($"Shape mask sample: {shaped.Name} {shaped.ShapeWidth}x{shaped.ShapeHeight}/{shaped.ShapeCells.Count}"); -Console.WriteLine($"Interior/hardpoint sample: {interior.Name} {interior.InteriorShapeCells.Count}; {hardpointHost.Name} {hardpoint.Type} {hardpoint.ShapeCells.Count}"); -Console.WriteLine($"Behavior payload sample: {behaviorHost.Name} {behaviorPayload.Kind}/{behaviorPayload.Fields.Count}"); -Console.WriteLine($"Behavior sample: {behaviorKind}"); -Console.WriteLine($"Eve surface: {surface.Surface.Id}"); -Console.WriteLine($"Catalog Eve surface document: {surface.Surface.Id}"); -Console.WriteLine("Runtime Eve command smoke: surface command document is a typed state record"); - -static int CountRequiredBehaviorItemRefsMissingItemKeys(AetheriaRuntimeBehaviorPayload payload) -{ - return payload.Fields.Count(field => - IsBehaviorItemRefField(payload.Kind, field.Key) && - IsNonEmptyLegacyRefMissingItemKey(field.Value)); -} - -static bool IsBehaviorItemRefField(string behaviorKind, int fieldKey) -{ - return (string.Equals(behaviorKind, "ItemUsage", StringComparison.OrdinalIgnoreCase) && fieldKey == 1) || - (IsWeaponBehavior(behaviorKind) && fieldKey == 12); -} - -static bool IsWeaponBehavior(string behaviorKind) -{ - return behaviorKind is "GuidedWeapon" or "InstantWeapon" or "ConstantWeapon" or "ChargedWeapon" or "AutoWeapon" or "LockWeapon"; -} - -static bool IsNonEmptyLegacyRefMissingItemKey(AetheriaRuntimeBehaviorValue value) -{ - return string.Equals(value.Kind, "legacy-id", StringComparison.OrdinalIgnoreCase) && - !IsEmptyLegacyId(value.LegacyIdValue) && - string.IsNullOrWhiteSpace(value.ItemKeyValue); -} - -static bool IsEmptyLegacyId(string legacyId) -{ - return string.IsNullOrWhiteSpace(legacyId) || - (Guid.TryParse(legacyId, out var parsed) && parsed == Guid.Empty); -} diff --git a/Aetheria.State.Unity/Aetheria.State.Unity.csproj b/Aetheria.State.Unity/Aetheria.State.Unity.csproj deleted file mode 100644 index d0c760e7b..000000000 --- a/Aetheria.State.Unity/Aetheria.State.Unity.csproj +++ /dev/null @@ -1,13 +0,0 @@ - - - netstandard2.1 - enable - latest - enable - Unity-facing runtime read facade over typed Aetheria CultCache state. - - - - - - diff --git a/Aetheria.State.Unity/AetheriaRuntimeCatalogClient.cs b/Aetheria.State.Unity/AetheriaRuntimeCatalogClient.cs deleted file mode 100644 index e003b0971..000000000 --- a/Aetheria.State.Unity/AetheriaRuntimeCatalogClient.cs +++ /dev/null @@ -1,43 +0,0 @@ -using GameCult.Aetheria.State.Verse; -using GameCult.Mesh; -using EveSurfaceDocument = GameCult.Eve.Surface.EveSurfaceDocument; - -namespace Aetheria.State.Unity; - -public sealed class AetheriaRuntimeCatalogClient : IAsyncDisposable, IDisposable -{ - private readonly AetheriaStateNode _node; - - private AetheriaRuntimeCatalogClient(AetheriaStateNode node) - { - _node = node; - } - - public static async Task OpenAsync(string statePath) - { - var node = await AetheriaStateNode - .OpenAsync(statePath, "aetheria-unity-runtime-catalog", enableDurableShardLogs: false) - .ConfigureAwait(false); - return new AetheriaRuntimeCatalogClient(node); - } - - public AetheriaRuntimeCatalogSnapshot ReadCatalog() - { - return _node.RuntimeCatalog().Latest(); - } - - public EveSurfaceDocument? ReadCatalogSurface() - { - return _node.CatalogSurface().Latest(); - } - - public ValueTask DisposeAsync() - { - return _node.DisposeAsync(); - } - - public void Dispose() - { - _node.Dispose(); - } -} diff --git a/Aetheria.State.Verify/Aetheria.State.Verify.csproj b/Aetheria.State.Verify/Aetheria.State.Verify.csproj deleted file mode 100644 index 6933551e3..000000000 --- a/Aetheria.State.Verify/Aetheria.State.Verify.csproj +++ /dev/null @@ -1,18 +0,0 @@ - - - - - Exe - net10.0 - enable - latest - enable - - - - - - - - - diff --git a/Aetheria.State.Verify/AetheriaCatalogEnumProbeTypes.cs b/Aetheria.State.Verify/AetheriaCatalogEnumProbeTypes.cs deleted file mode 100644 index 24334c355..000000000 --- a/Aetheria.State.Verify/AetheriaCatalogEnumProbeTypes.cs +++ /dev/null @@ -1,38 +0,0 @@ -internal enum AetheriaVerifyHardpointType -{ - Hull, - Tool, - Thermal, - Thruster, - WarpDrive, - Reactor, - Radiator, - Shield, - Sensors, - Energy, - Ballistic, - Launcher, - ControlModule, - AetherDrive -} - -internal enum AetheriaVerifySimpleCommodityCategory -{ - Minerals, - Metals, - Alloys, - Compounds, - Organics, - Ammo, - Consumer -} - -internal enum AetheriaVerifyCompoundCommodityCategory -{ - Wearables, - Consumables, - Luxuries, - Tools, - Manufacturing, - Assemblies -} diff --git a/Aetheria.State.Verify/Program.cs b/Aetheria.State.Verify/Program.cs deleted file mode 100644 index cc5ccf48e..000000000 --- a/Aetheria.State.Verify/Program.cs +++ /dev/null @@ -1,21377 +0,0 @@ -using Aetheria.State; -using Aetheria.State.Documents; -using GameCult.Aetheria.State.Verse; -using GameCult.Eve.Surface; -using GameCult.Mesh; -using System.Text.Json; -using System.Text.RegularExpressions; - -var root = args.Length > 0 ? Path.GetFullPath(args[0]) : Directory.GetCurrentDirectory(); -var statePath = args.Length > 1 - ? Path.GetFullPath(args[1]) - : AetheriaStatePaths.ResolveDefaultStatePath(root); -var verificationDirectory = Path.Combine( - Path.GetTempPath(), - "aetheria-state-verify", - Guid.NewGuid().ToString("N")); -Directory.CreateDirectory(verificationDirectory); -var verificationStatePath = Path.Combine(verificationDirectory, "aetheria-world.cc"); -File.Copy(statePath, verificationStatePath, overwrite: true); - -RequireGameplaySourcePurity(root); -RequireManagedContentOnlyAssetDelivery(root); -RequireClientTransportPlaneOwnership(root); -RequirePackageSerializerBoundary(root); -RequireSharedEvePackagesImportedFromEveRepo(root); -RequireSharedRuntimeSurfaceCommandsUseCultMeshTransport(root); -RequireSharedRuntimeSurfacesUseClientNeutralVocabulary(root); -RequireTypedPendingCommitKeys(root); -RequireTypedRuntimeFactionKeys(root); -RequireTypedGalaxyFactionRelationships(root); -RequireRuntimeCatalogKeyOnlyLookups(root); -RequireDaemonPlayableRunGenerationAuthority(root); -RequireTypedBehaviorBodyKeys(root); -RequireTypedOrbitTaskKeys(root); -RequireTypedAgentTaskKeys(root); -RequireTypedOrbitalEntityOrbitKeys(root); -RequireTypedOrbitConsumerKeys(root); -RequireKeyedOrbitRuntimeWrappers(root); -RequireNativeZoneKeyResolution(root); -RequireTypedZoneRuntimeCollections(root); -RequireDaemonRenderQueryAuthority(root); -RequireTypedZoneConstructionKeys(root); -RequireTypedZoneStateSnapshotKeys(root); -RequireTypedAsteroidZoneApi(root); -RequireTypedFactionShellLinks(root); -RequireFactionKeyIdentity(root); -RequireTypedRuntimeBehaviorCoverage(root); -RequireEveRuntimeBootstrap(root); -RequireNoRendererLocalConsole(root); -RequireNoRendererLocalDebugPanels(root); -RequireMainMenuSettingsCommands(root); -RequireMainMenuRootUsesEveSurface(root); -RequireMainMenuSettingsShellUsesEveSurface(root); -RequireConfirmationDialogOwnsMinimalPromptShell(root); -RequireMainMenuInputSettingsDelegateToRuntimeScreen(root); -RequireRuntimeInputScreenUsesEveSurface(root); -RequireActionGameManagerInputScreenUsesSharedFullscreenPrimitive(root); -RequireSectorMapZoneDetailsUseEveSurface(root); -RequireRuntimeMenuTabsUseEveSurface(root); -RequireInventoryShipSettingsUseEveSurface(root); -RequireInventoryCargoItemDetailsUseEveSurface(root); -RequireInventoryEquippedItemDetailsUseEveSurface(root); -RequireTradeCargoSelectorUseEveSurface(root); -RequireTradeFilterAndRowActionsUseEveSurface(root); -RequireTradeItemDetailsUseEveSurface(root); -RequireTradeItemValuesUseRuntimeQueries(root); -RequireItemTierValuesUseRuntimeQueries(root); -RequireInventoryDropdownUseEveSurface(root); -RequireStationRefitDockingBaysUseTypedDocuments(root); -RequireNoDeadPopupShells(root); -RequirePlayerSettingsEveSurface(root); -RequireVerseHostSettingsAuthority(root); -RequireClientTargetBootAuthority(root); -RequireClientRuntimeConformanceContract(root); -RequireVerseReplicaTool(root); -RequireVerseSettingsShellAndBridge(root); -RequireTypedStatRecipeOperations(root); -RequireTypedDaemonCommandPayloads(root); -RequireUnityPublicRequestVocabulary(root); -RequireCatalogSurfaceUsesManagedRuntimeCatalog(root); -RequireDaemonLoadoutRarityAuthority(root); -RequireDaemonVersePublication(root); -RequireUnityRuntimeCatalogClientUsesManagedDocument(root); -RequireAetheriaRuntimeVerseClientContract(root); -RequireTypedEveCommandBodies(root); -RequireMainMenuVerseHostDocumentAccess(root); -RequireMainMenuContinueRunState(root); -RequireUnityObserverDoesNotTickLocalSimulation(root); -RequireUnityDoesNotCallSharedSimulationTicks(root); -RequireDaemonEnergyAndThermalAuthority(root); -RequireThermalPresentationAssetAuthority(root); -RequireCatalogHullPresentationAuthority(root); -RequireUnityPhysicsIsNotGameplayAuthority(root); -RequireDeadPropertiesPanelShellDeleted(root); -RequireTypedBehaviorMetadataCoverage(root); -RequireNameToolsUsesUiToolkit(root); -RequireRuntimeStateReaderOwnsUnityStateAcquisition(root); -RequireNoScopedReactiveFacadeWrappers(root); -RequireNoDeadRuntimeProjectionCaches(root); -RequireNoDeadRuntimeProjectionClasses(root); -RequireNoDeadInspectorMetadata(root); -RequireDaemonHostDoesNotDrainRuntimeCommits(root); -RequireRuntimeSimulationTuningRequests(root); -RequireHullConductivityRequestAuthority(root); -RequireInventoryEntityRenameRequestAuthority(root); -RequireWeaponGroupRequestAuthority(root); -RequireActionBarBindingRequestAuthority(root); -RequireInventoryDoubleClickTransferRequestAuthority(root); -RequireLootPickupRequestAuthority(root); -RequireEntityDestroyedRequestAuthority(root); -RequireDroppedPickupCheckpointState(root); -RequireTradePurchaseRequestAuthority(root); -RequireTradeSaleAuthority(root); -RequireInventoryDocumentSlotIdentity(root); -RequireInventoryValidationUsesManagedTypedDocuments(root); -RequireMenuDockingUsesManagedTypedSnapshot(root); -RequireUnitySharedDocumentAccessorErgonomics(root); -RequireUnityViewportAndMapReadsUseManagedAccessors(root); -RequireAetheriaManagedStateAccessorsCoverDomainDocuments(root); -RequireInventoryLoadoutSaveRequestAuthority(root); -RequireInventoryLoadoutRestoreRequestAuthority(root); -RequireDockedCurrentShipRequestAuthority(root); -RequireAuthoritySmokeUsesManagedPointers(root); -RequireAetheriaStateNodeUsesManagedPointers(root); - -await using var node = await AetheriaStateNode.OpenAsync( - verificationStatePath, - "aetheria-state-verify", - enableDurableShardLogs: false); -await node.RefreshRuntimeCatalogAsync().ConfigureAwait(false); - -var ledger = await node.MutableDocument(AetheriaStateNode.MigrationLedgerKey).ReadAsync() - ?? throw new InvalidOperationException("Missing typed migration ledger."); -var quarantine = await node.MutableDocument(AetheriaStateNode.LegacyCatalogQuarantineKey).ReadAsync() - ?? throw new InvalidOperationException("Missing legacy catalog quarantine document."); -var publishedSurface = await node.CatalogSurface().LatestAsync().ConfigureAwait(false); -if (publishedSurface == null) - throw new InvalidOperationException("Missing managed Aetheria catalog Eve surface document."); - -var items = node.Cache.GetAll().ToArray(); -var corporations = node.Cache.GetAll().ToArray(); -var nameFiles = node.Cache.GetAll().ToArray(); -var catalog = await node.RuntimeCatalog().LatestAsync().ConfigureAwait(false); -var surface = AetheriaEveSurfaceDocuments.BuildCatalogSurface(catalog, DateTimeOffset.UtcNow.ToString("O")); -var tradeValuePolicy = await node.MutableDocument(AetheriaStateNode.TradeValuePolicyKey).ReadAsync() - ?? throw new InvalidOperationException("Missing authored trade value policy document."); -await RequireTradeValuePolicyEveCommandPersistsAsync(); -await RequireMainMenuEveCommandPersistsAsync(); -var runtimeCatalog = catalog; - -RequireCount(ledger, "aetheria.item_definition.v1", items.Length); -RequireCount(ledger, "aetheria.corporation.v2", corporations.Length); -RequireCount(ledger, "aetheria.name_file.v2", nameFiles.Length); - -if (items.Length == 0) -{ - throw new InvalidOperationException("Typed state has no item definitions."); -} - -if (corporations.Length == 0) -{ - throw new InvalidOperationException("Typed state has no corporations."); -} - -if (nameFiles.Length == 0) -{ - throw new InvalidOperationException("Typed state has no name files."); -} - -if (nameFiles.Any(nameFile => nameFile.Names.Length == 0 || nameFile.Names.Length != nameFile.NameCount)) -{ - throw new InvalidOperationException("Typed name files did not import their full name arrays."); -} -if (tradeValuePolicy.QualityPriceModifier == null || - tradeValuePolicy.QualityPriceModifier.Exponent <= 0 || - tradeValuePolicy.QualityPriceModifier.Maximum <= tradeValuePolicy.QualityPriceModifier.Minimum || - tradeValuePolicy.Tiers.Length == 0) -{ - throw new InvalidOperationException("Authored trade value policy is missing quality pricing or rarity tiers."); -} - -if (runtimeCatalog.TradeValueSettings.Tiers.Count != tradeValuePolicy.Tiers.Length || - Math.Abs(runtimeCatalog.TradeValueSettings.QualityPriceModifier.Exponent - tradeValuePolicy.QualityPriceModifier.Exponent) > 0.0001) -{ - throw new InvalidOperationException("Runtime catalog did not read the authored trade value policy."); -} - -var pricedItems = items.Count(item => item.Price > 0); -var manufacturedItems = items.Count(item => !string.IsNullOrWhiteSpace(item.ManufacturerLegacyId)); -var specificHeatItems = items.Count(item => item.SpecificHeat > 0); -var conductiveItems = items.Count(item => item.Conductivity > 0); -var shapedItems = items.Count(item => item.ShapeWidth > 0 && item.ShapeHeight > 0 && item.OccupiedCells > 0); -var shapedMaskItems = items.Count(item => item.ShapeCells.Length > 0); -var interiorShapeItems = items.Count(item => item.InteriorShapeCells.Length > 0); -var hardpointHostItems = items.Count(item => item.Hardpoints.Length > 0); -var hardpointCount = items.Sum(item => item.Hardpoints.Length); -var behaviorItems = items.Count(item => item.BehaviorCount > 0 && item.BehaviorKinds.Length > 0); -var behaviorPayloadItems = items.Count(item => item.BehaviorPayloads.Length > 0); -var behaviorPayloadCount = items.Sum(item => item.BehaviorPayloads.Length); -var behaviorFieldCount = items.SelectMany(item => item.BehaviorPayloads).Sum(behavior => behavior.Fields.Length); -var behaviorLegacyRefCount = items - .SelectMany(item => item.BehaviorPayloads) - .SelectMany(behavior => behavior.Fields) - .Count(field => ContainsBehaviorValueKind(field.Value, "legacy-id")); -var behaviorItemRefsMissingItemKeys = items - .SelectMany(item => item.BehaviorPayloads) - .Sum(CountRequiredBehaviorItemRefsMissingItemKeys); -var hardpointItems = items.Count(item => !string.IsNullOrWhiteSpace(item.HardpointType)); -var hullItems = items.Count(item => !string.IsNullOrWhiteSpace(item.HullType)); -var hullPrefabItems = items.Count(item => !string.IsNullOrWhiteSpace(item.HullPrefab)); -var hullArmorItems = items.Count(item => !string.IsNullOrWhiteSpace(item.HullType) && item.HullArmor > 0); -var hullPhysicalFacetItems = items.Count(item => - !string.IsNullOrWhiteSpace(item.HullType) && - item.HullArmor > 0 && - item.HullDrag >= 0 && - !double.IsNaN(item.HullGridOffset)); -var simpleCommodityCategoryItems = items.Count(item => !string.IsNullOrWhiteSpace(item.SimpleCommodityCategory)); -var compoundCommodityCategoryItems = items.Count(item => !string.IsNullOrWhiteSpace(item.CompoundCommodityCategory)); -var weaponItems = items.Count(item => - !string.IsNullOrWhiteSpace(item.WeaponType) && - !string.IsNullOrWhiteSpace(item.WeaponRange) && - !string.IsNullOrWhiteSpace(item.WeaponCaliber)); -var maxDurabilityItems = items - .Where(item => !string.IsNullOrWhiteSpace(item.HullType) || !string.IsNullOrWhiteSpace(item.WeaponType)) - .ToArray(); -var durableMaxDurabilityItems = maxDurabilityItems.Count(item => item.Durability > 0); -var importedThermalRangeItems = items.Count(item => item.MaximumTemperature > item.MinimumTemperature); -var importedThermalCurveItems = items.Count(item => item.ThermalPerformanceCurveKeys.Length > 0); -var thermalResilienceItems = items.Count(item => item.ThermalResilience > 0); -var audioStatItems = items.Count(item => item.AudioStats.Length > 0); -var audioStatCount = items.Sum(item => item.AudioStats.Length); -var consumableItems = items.Count(item => item.Category == AetheriaRuntimeItemCategories.Consumable); -var consumableDurationItems = items.Count(item => item.Category == AetheriaRuntimeItemCategories.Consumable && item.Duration > 0); -var consumableEffectivenessItems = items.Count(item => item.EffectivenessCurveKeys.Length > 0); -var actionBarIconItems = items.Count(item => !string.IsNullOrWhiteSpace(item.ActionBarIcon)); -var dockingBayItems = items.Count(item => item.Category == AetheriaRuntimeItemCategories.DockingBay); -var dockingBayMaxSizeItems = items.Count(item => - item.Category == AetheriaRuntimeItemCategories.DockingBay && - item.DockingMaxSizeX > 0 && - item.DockingMaxSizeY > 0); -var describedCorporations = corporations.Count(corporation => !string.IsNullOrWhiteSpace(corporation.Description)); -var corporationNameLinks = corporations.Count(corporation => !string.IsNullOrWhiteSpace(corporation.GeonameFileLegacyId)); -var corporationAllegianceEdges = corporations.Sum(corporation => corporation.Allegiances.Length); -var legacyDtoCategories = items - .Where(item => item.Category.EndsWith("Data", StringComparison.Ordinal)) - .Select(item => $"{item.Name}:{item.Category}") - .ToArray(); - -if (pricedItems == 0) -{ - throw new InvalidOperationException("Typed item definitions did not import any prices."); -} - -if (legacyDtoCategories.Length > 0) -{ - throw new InvalidOperationException( - "Typed item categories still contain legacy DTO class names: " + - string.Join(", ", legacyDtoCategories.Take(5))); -} - -if (manufacturedItems == 0) -{ - throw new InvalidOperationException("Typed item definitions did not import any manufacturer legacy IDs."); -} - -if (specificHeatItems != items.Length) -{ - throw new InvalidOperationException( - $"Typed item definitions did not import positive specific heat for every item: {specificHeatItems}/{items.Length}."); -} - -if (conductiveItems != items.Length) -{ - throw new InvalidOperationException( - $"Typed item definitions did not import positive conductivity for every item: {conductiveItems}/{items.Length}."); -} - -if (thermalResilienceItems != items.Length) -{ - throw new InvalidOperationException( - $"Typed item definitions did not import positive thermal resilience for every item: {thermalResilienceItems}/{items.Length}."); -} - -if (shapedItems == 0) -{ - throw new InvalidOperationException("Typed item definitions did not import any shape dimensions."); -} - -if (shapedMaskItems != shapedItems) -{ - throw new InvalidOperationException( - $"Typed item shape mask import mismatch: masks={shapedMaskItems}, shaped={shapedItems}."); -} - -foreach (var item in items.Where(item => item.ShapeCells.Length > 0)) -{ - if (item.ShapeCells.Length != item.OccupiedCells) - { - throw new InvalidOperationException( - $"Typed item shape mask cell count mismatch for {item.Name}: cells={item.ShapeCells.Length}, occupied={item.OccupiedCells}."); - } - - if (item.ShapeCells.Any(cell => cell.X < 0 || cell.Y < 0 || cell.X >= item.ShapeWidth || cell.Y >= item.ShapeHeight)) - { - throw new InvalidOperationException($"Typed item shape mask has out-of-bounds cells for {item.Name}."); - } -} - -foreach (var item in items.Where(item => item.InteriorShapeCells.Length > 0)) -{ - if (item.InteriorShapeCells.Length != item.InteriorOccupiedCells) - { - throw new InvalidOperationException( - $"Typed item interior shape mask count mismatch for {item.Name}: cells={item.InteriorShapeCells.Length}, occupied={item.InteriorOccupiedCells}."); - } - - if (item.InteriorShapeCells.Any(cell => - cell.X < 0 || cell.Y < 0 || cell.X >= item.InteriorShapeWidth || cell.Y >= item.InteriorShapeHeight)) - { - throw new InvalidOperationException($"Typed item interior shape mask has out-of-bounds cells for {item.Name}."); - } -} - -if (interiorShapeItems == 0) -{ - throw new InvalidOperationException("Typed item definitions did not import any interior shape masks."); -} - -if (hardpointHostItems == 0 || hardpointCount == 0) -{ - throw new InvalidOperationException("Typed item definitions did not import any hull hardpoints."); -} - -var missingMaxDurability = maxDurabilityItems - .Where(item => item.Durability <= 0) - .Select(item => item.Name) - .ToArray(); -if (missingMaxDurability.Length > 0) -{ - throw new InvalidOperationException( - $"Typed hull/weapon max durability missing for: {string.Join(", ", missingMaxDurability)}."); -} - -if (importedThermalRangeItems == 0) -{ - throw new InvalidOperationException("Typed item definitions did not import any thermal ranges."); -} - -if (importedThermalCurveItems == 0) -{ - throw new InvalidOperationException("Typed item definitions did not import any thermal performance curves."); -} - -foreach (var item in items.Where(item => item.ThermalPerformanceCurveKeys.Length > 0)) -{ - if (item.ThermalPerformanceCurveKeys.Any(key => key.Time < 0 || key.Time > 1)) - { - throw new InvalidOperationException($"Typed thermal performance curve has out-of-range key time for {item.Name}."); - } -} - -foreach (var item in items.Where(item => item.Hardpoints.Length > 0)) -{ - foreach (var hardpoint in item.Hardpoints) - { - if (string.IsNullOrWhiteSpace(hardpoint.Type)) - { - throw new InvalidOperationException($"Typed item hardpoint has no type for {item.Name}."); - } - - if (hardpoint.ShapeCells.Length != hardpoint.OccupiedCells) - { - throw new InvalidOperationException( - $"Typed item hardpoint shape count mismatch for {item.Name}: cells={hardpoint.ShapeCells.Length}, occupied={hardpoint.OccupiedCells}."); - } - - if (hardpoint.ShapeCells.Any(cell => - cell.X < 0 || cell.Y < 0 || cell.X >= hardpoint.ShapeWidth || cell.Y >= hardpoint.ShapeHeight)) - { - throw new InvalidOperationException($"Typed item hardpoint shape has out-of-bounds cells for {item.Name}."); - } - - // Legacy content includes at least one overhanging hardpoint. Preserve the - // payload faithfully here; content repair belongs to a separate pass. - } -} - -if (behaviorItems == 0) -{ - throw new InvalidOperationException("Typed item definitions did not import any behavior fingerprints."); -} - -if (behaviorPayloadItems != behaviorItems || behaviorPayloadCount == 0 || behaviorFieldCount == 0) -{ - throw new InvalidOperationException( - $"Typed behavior payload import mismatch: payloadItems={behaviorPayloadItems}, behaviorItems={behaviorItems}, payloads={behaviorPayloadCount}, fields={behaviorFieldCount}."); -} - -if (behaviorItemRefsMissingItemKeys > 0) -{ - throw new InvalidOperationException( - $"Typed behavior payload item refs missing item-key projections: {behaviorItemRefsMissingItemKeys}."); -} - -foreach (var item in items.Where(item => item.BehaviorPayloads.Length > 0)) -{ - if (item.BehaviorPayloads.Length != item.BehaviorCount) - { - throw new InvalidOperationException( - $"Typed item behavior payload count mismatch for {item.Name}: payloads={item.BehaviorPayloads.Length}, count={item.BehaviorCount}."); - } - - foreach (var behavior in item.BehaviorPayloads) - { - if (string.IsNullOrWhiteSpace(behavior.Kind) || behavior.UnionKey < 0 || behavior.Fields.Length == 0) - { - throw new InvalidOperationException($"Typed item behavior payload is incomplete for {item.Name}."); - } - } -} - -if (hardpointItems == 0) -{ - throw new InvalidOperationException("Typed item definitions did not import any hardpoint types."); -} - -if (hullItems == 0) -{ - throw new InvalidOperationException("Typed item definitions did not import any hull types."); -} - -if (hullPrefabItems != hullItems) -{ - throw new InvalidOperationException($"Typed hull prefab import mismatch: hulls={hullItems}, prefabs={hullPrefabItems}."); -} - -if (hullArmorItems != hullItems || hullPhysicalFacetItems != hullItems) -{ - throw new InvalidOperationException( - $"Typed hull physical facet import mismatch: hulls={hullItems}, armor={hullArmorItems}, physical={hullPhysicalFacetItems}."); -} - -if (simpleCommodityCategoryItems == 0) -{ - throw new InvalidOperationException("Typed simple commodity category import produced no categories."); -} - -if (compoundCommodityCategoryItems == 0) -{ - throw new InvalidOperationException("Typed compound commodity category import produced no categories."); -} - -if (weaponItems == 0) -{ - throw new InvalidOperationException("Typed item definitions did not import any weapon facets."); -} - -if (dockingBayItems == 0 || dockingBayMaxSizeItems != dockingBayItems) -{ - throw new InvalidOperationException( - $"Typed docking bay max-size import mismatch: dockingBays={dockingBayItems}, maxSizes={dockingBayMaxSizeItems}."); -} - -if (describedCorporations == 0) -{ - throw new InvalidOperationException("Typed corporations did not import descriptions from legacy key 3."); -} - -if (corporationNameLinks == 0) -{ - throw new InvalidOperationException("Typed corporations did not import geoname file legacy IDs."); -} - -if (corporationAllegianceEdges == 0 || corporations.Any(corporation => corporation.Allegiances.Length != corporation.AllegianceCount)) -{ - throw new InvalidOperationException("Typed corporations did not import full allegiance edges."); -} - -var tradeItems = catalog.TradeItems.ToArray(); -if (tradeItems.Length != pricedItems) -{ - throw new InvalidOperationException( - $"Typed catalog trade item query mismatch: query={tradeItems.Length}, priced={pricedItems}."); -} - -var equipmentItems = catalog.EquipmentItems.ToArray(); -if (equipmentItems.Length != hardpointItems) -{ - throw new InvalidOperationException( - $"Typed catalog equipment item query mismatch: query={equipmentItems.Length}, hardpoint={hardpointItems}."); -} - -var behaviorKind = catalog.Items.SelectMany(item => item.BehaviorKinds).FirstOrDefault() - ?? throw new InvalidOperationException("Cannot verify typed catalog behavior query: no behavior kinds."); -if (!catalog.FindItemsByBehavior(behaviorKind).Any()) -{ - throw new InvalidOperationException($"Typed catalog behavior query failed for {behaviorKind}."); -} - -var hardpointType = catalog.Items.FirstOrDefault(item => !string.IsNullOrWhiteSpace(item.HardpointType))?.HardpointType - ?? throw new InvalidOperationException("Cannot verify typed catalog hardpoint query: no hardpoint types."); -if (!catalog.FindItemsByHardpoint(hardpointType).Any()) -{ - throw new InvalidOperationException($"Typed catalog hardpoint query failed for {hardpointType}."); -} - -var typedHardpointItem = catalog.Items.FirstOrDefault(item => item.TryGetHardpointType(out _)) - ?? throw new InvalidOperationException("Typed catalog item hardpoint enum accessor failed."); -var typedSimpleCommodityItem = catalog.Items.FirstOrDefault(item => item.TryGetSimpleCommodityCategory(out _)) - ?? throw new InvalidOperationException("Typed catalog item simple commodity enum accessor failed."); -var typedCompoundCommodityItem = catalog.Items.FirstOrDefault(item => item.TryGetCompoundCommodityCategory(out _)) - ?? throw new InvalidOperationException("Typed catalog item compound commodity enum accessor failed."); -if (string.IsNullOrWhiteSpace(typedHardpointItem.HardpointType) || - string.IsNullOrWhiteSpace(typedSimpleCommodityItem.SimpleCommodityCategory) || - string.IsNullOrWhiteSpace(typedCompoundCommodityItem.CompoundCommodityCategory)) -{ - throw new InvalidOperationException("Typed catalog item enum accessors returned an item without its source field."); -} - -var manufacturedItem = catalog.Items.FirstOrDefault(item => !string.IsNullOrWhiteSpace(item.ManufacturerKey)) - ?? throw new InvalidOperationException("Cannot verify typed catalog manufacturer lookup: no manufactured item."); -if (string.IsNullOrWhiteSpace(manufacturedItem.ManufacturerKey) || - catalog.FindCorporation(manufacturedItem.ManufacturerKey) == null || - catalog.GetManufacturer(manufacturedItem) == null) -{ - throw new InvalidOperationException( - $"Typed catalog manufacturer-key lookup failed for item {manufacturedItem.Name}."); -} - -var corporationWithNames = catalog.Corporations.FirstOrDefault(corporation => - !string.IsNullOrWhiteSpace(corporation.GeonameFileKey)) - ?? throw new InvalidOperationException("Cannot verify typed catalog name-file lookup: no linked corporation."); -if (string.IsNullOrWhiteSpace(corporationWithNames.GeonameFileKey) || - catalog.FindNameFile(corporationWithNames.GeonameFileKey) == null || - catalog.GetNameFile(corporationWithNames) == null) -{ - throw new InvalidOperationException( - $"Typed catalog name-file-key lookup failed for corporation {corporationWithNames.Name}."); -} - -if (surface.Surface.Root.Kind != "surface" || - surface.Surface.Root.Children.Count == 0) -{ - throw new InvalidOperationException("Aetheria catalog Eve surface projection is not renderable."); -} - -if (publishedSurface.Surface.Id != AetheriaEveSurfaceDocuments.CatalogSurfaceId || - publishedSurface.Surface.Root.Kind != "surface") -{ - throw new InvalidOperationException("Published Aetheria catalog Eve surface is not the expected typed surface."); -} - -await RequireLegacyLookupAsync( - items[0].LegacyId, - () => node.MutableDocument(AetheriaCatalogKeys.ItemDefinitionFromLegacyId(items[0].LegacyId)).ReadAsync(), - "item definition"); -await RequireLegacyLookupAsync( - corporations[0].LegacyId, - () => node.MutableDocument(AetheriaCatalogKeys.CorporationFromLegacyId(corporations[0].LegacyId)).ReadAsync(), - "corporation"); -await RequireLegacyLookupAsync( - nameFiles[0].LegacyId, - () => node.MutableDocument(AetheriaCatalogKeys.NameFileFromLegacyId(nameFiles[0].LegacyId)).ReadAsync(), - "name file"); - -if (string.IsNullOrWhiteSpace(quarantine.CatalogFingerprint)) -{ - throw new InvalidOperationException("Legacy catalog quarantine has no catalog fingerprint."); -} - -Console.WriteLine($"Aetheria typed state verify passed: {statePath}"); -Console.WriteLine($"Catalog fingerprint: {quarantine.CatalogFingerprint}"); -Console.WriteLine($"Item definitions: {items.Length}"); -Console.WriteLine($"Priced/manufactured/specific-heat/conductive/shaped items: {pricedItems}/{manufacturedItems}/{specificHeatItems}/{conductiveItems}/{shapedItems}"); -Console.WriteLine($"Thermal resilience items: {thermalResilienceItems}"); -Console.WriteLine($"Audio stat items/stats: {audioStatItems}/{audioStatCount}"); -Console.WriteLine($"Consumable duration/effectiveness items: {consumableDurationItems}/{consumableEffectivenessItems} of {consumableItems}"); -Console.WriteLine($"Shape masks: {shapedMaskItems}"); -Console.WriteLine($"Interior masks/hardpoint hosts/hardpoints: {interiorShapeItems}/{hardpointHostItems}/{hardpointCount}"); -Console.WriteLine($"Behavior payload items/payloads/fields/legacy refs: {behaviorPayloadItems}/{behaviorPayloadCount}/{behaviorFieldCount}/{behaviorLegacyRefCount}"); -Console.WriteLine($"Behavior/hardpoint/hull/weapon items: {behaviorItems}/{hardpointItems}/{hullItems}/{weaponItems}"); -Console.WriteLine($"Hull prefab items: {hullPrefabItems}"); -Console.WriteLine($"Hull armor/physical facet items: {hullArmorItems}/{hullPhysicalFacetItems}"); -Console.WriteLine($"Simple/compound commodity category items: {simpleCommodityCategoryItems}/{compoundCommodityCategoryItems}"); -Console.WriteLine($"Durable hull/weapon items: {durableMaxDurabilityItems}/{maxDurabilityItems.Length}"); -Console.WriteLine($"Thermal range/curve items: {importedThermalRangeItems}/{importedThermalCurveItems}"); -Console.WriteLine($"Action-bar icon items: {actionBarIconItems}"); -Console.WriteLine($"Docking bay max-size items: {dockingBayMaxSizeItems}/{dockingBayItems}"); -Console.WriteLine($"Typed catalog trade items: {tradeItems.Length}"); -Console.WriteLine($"Eve catalog surface: {surface.Surface.Id} ({surface.Surface.Root.Children.Count} root children)"); -Console.WriteLine($"Corporations: {corporations.Length}"); -Console.WriteLine($"Described/geoname-linked corporations: {describedCorporations}/{corporationNameLinks}"); -Console.WriteLine($"Corporation allegiance edges: {corporationAllegianceEdges}"); -Console.WriteLine($"Name files: {nameFiles.Length}"); -Console.WriteLine("Live gameplay source purity: no serializer or legacy database symbols in Assets/Scripts"); -Console.WriteLine("Package serializer boundary: MessagePack execution remains in named CultCache transport files; document DTOs may carry schema annotations"); -Console.WriteLine("Shared Eve package ownership: generic Unity Eve packages import from the neighboring Eve repo instead of local staged copies"); -Console.WriteLine("Typed runtime behavior coverage: daemon behavior-state rows derive from catalog payloads; Unity behavior objects are presentation restore only"); -Console.WriteLine("Zone construction/runtime key authority: body/orbit wrappers and task shells retain typed keys instead of GUID sidecars"); -Console.WriteLine("Eve runtime bootstrap: daemon-published game surface mounts through UI Toolkit presenter"); -Console.WriteLine("Renderer-local console authority: deleted; UI commands flow through Eve command documents"); -Console.WriteLine("Renderer-local debug panels: obsolete uGUI field tester authority is deleted"); -Console.WriteLine("Main-menu settings authority: player name, gameplay, and graphics settings send typed Eve commands"); -Console.WriteLine("Main-menu root shell: root navigation lowers through an Eve UI Toolkit surface instead of the legacy PropertiesPanel/fade shell"); -Console.WriteLine("Main-menu settings shell: settings/input subpages lower through Eve UI Toolkit surfaces, and the fake audio page is deleted until a typed audio owner exists"); -Console.WriteLine("Confirmation dialog shell: runtime prompts no longer inherit the generic PropertiesPanel machinery"); -Console.WriteLine("Main-menu input shell: the Eve input page delegates to the live runtime remap screen when that owner exists"); -Console.WriteLine("Runtime input screen shell: input rebinding lowers through an Eve UI Toolkit surface instead of the old drag/drop uGUI screen"); -Console.WriteLine("Runtime input-screen authority: hotkey and menu handoff share the same fullscreen-menu primitive"); -Console.WriteLine("Sector-map zone details shell: shared zone detail surface lowers selected zone state through Eve UI Toolkit"); -Console.WriteLine("Runtime menu tab shell: MenuPanel owns its client-local tab surface and lowers it through Eve UI Toolkit"); -Console.WriteLine("Inventory ship-settings shell: shared ship settings surface lowers selected ship state through Eve UI Toolkit"); -Console.WriteLine("Inventory cargo-item shell: shared cargo detail surface lowers selected item state through Eve UI Toolkit"); -Console.WriteLine("Inventory equipped-item shell: shared equipped item surface lowers selected equipment state and controls through Eve UI Toolkit"); -Console.WriteLine("Trade cargo-selector shell: shared cargo selector surface lowers target cargo options through Eve UI Toolkit"); -Console.WriteLine("Trade filter and row-action shells: shared trade interaction surfaces lower filter and row actions through Eve UI Toolkit"); -Console.WriteLine("Trade item-details shell: shared trade item surface lowers selected market item state through Eve UI Toolkit"); -Console.WriteLine("Inventory dropdown shell: shared inventory dropdown surface lowers entity and loadout navigation through Eve UI Toolkit"); -Console.WriteLine("Main-menu Continue authority: Continue selects typed run state instead of a null button"); -Console.WriteLine("Generic popup inspector shell: PropertiesPanel, PropertiesList, and DropdownMenu are deleted from source and serialized assets"); -Console.WriteLine("Typed behavior metadata authority: live heat/mining/thermotoggle payload kinds stay owned by package metadata"); -Console.WriteLine("NameTools editor shell: the remaining name helper window lowers through UI Toolkit instead of IMGUI"); -Console.WriteLine("Verse client state authority: Unity gameplay/UI read typed state through the shared Verse client instead of direct store spelunking"); -Console.WriteLine("Verse host authority: daemon-owned typed verse host settings now drive provider advertisement, operations telemetry, and served Verse discovery"); -Console.WriteLine("Client target boot authority: Unity boot resolves the active Verse through a typed client target instead of local path folklore"); -Console.WriteLine("Client runtime conformance: Hermodr is the generic Eve RTS check; Godot is the generic Eve ARPG check"); -Console.WriteLine("Verse replica authority: remote client targets resolve to cache-only replica .cc files fed from the daemon"); -Console.WriteLine("Verse settings shell: client target edits and Verse-host visibility commands lower through typed Eve surfaces"); -Console.WriteLine("Main-menu Verse document access: the Unity Eve shell composes daemon-owned verse identity from typed documents instead of ad-libbing local menu copy"); -Console.WriteLine("Unity observer authority: gameplay scenes restore daemon frames and do not tick local simulation"); -Console.WriteLine("Shared simulation authority: Zone/Entity/Agent ticks stay out of Unity gameplay callers"); -Console.WriteLine("Ymir spatial kernel: authoritative gameplay physics uses Ymir computation and has no Unity collision callback fallback"); -Console.WriteLine("Runtime simulation tuning authority: UI sends daemon operations instead of local simulation mutation"); -Console.WriteLine("Hull conductivity authority: inventory UI sends daemon operations for toggles instead of checkpoint rewrites"); -Console.WriteLine("Inventory entity rename authority: UI sends daemon operations for renames instead of local graph edits"); -Console.WriteLine("Weapon group authority: UI sends daemon operations for assignments instead of checkpoint rewrites"); -Console.WriteLine("Action-bar binding authority: Unity owns local input bindings; activations send daemon operations"); -Console.WriteLine("Inventory transfer authority: UI transfer and drag/drop send daemon operations"); -Console.WriteLine("Loot pickup authority: daemon cargo collection occurs exactly once from ship/pickup XZ proximity"); -Console.WriteLine("Entity destruction authority: lethal daemon simulation commits exactly-once destruction and loot; clients cannot request erasure"); -Console.WriteLine("Dropped pickup state: daemon frames carry typed dropped-pickup snapshots and keyed live lowering"); -Console.WriteLine("Trade purchase authority: UI requests item and placement; daemon derives price, station, product kind, capacity, and ship creation"); -Console.WriteLine("Inventory loadout restore authority: UI restore requests send daemon operations instead of local mutation"); -Console.WriteLine("Docked current-ship authority: UI selection requests send daemon operations instead of checkpoint rewrites"); - -static void RequireCount(AetheriaMigrationLedger ledger, string documentType, int actual) -{ - var expected = ledger.Counts.FirstOrDefault(count => count.DocumentType == documentType)?.Count; - if (expected == null) - { - throw new InvalidOperationException($"Migration ledger is missing count for {documentType}."); - } - - if (expected.Value != actual) - { - throw new InvalidOperationException( - $"Migration ledger count mismatch for {documentType}: ledger={expected.Value}, actual={actual}."); - } -} - -static void RequireGameplaySourcePurity(string root) -{ - var gameplayRoot = Path.Combine(root, "Assets", "Scripts"); - if (!Directory.Exists(gameplayRoot)) - { - throw new InvalidOperationException($"Cannot verify live gameplay source purity; missing path: {gameplayRoot}"); - } - - var forbiddenSymbols = new[] - { - "MessagePack", - "MessagePackObject", - "MessagePackSerializer", - "MessagePackReader", - "IMessagePackFormatter", - "JsonConvert", - "Newtonsoft", - "JsonKnownTypes", - "RethinkDB", - "DatabaseEntry", - "DatabaseLink", - "[Union(", - "[Key(" - }; - - var hits = Directory.EnumerateFiles(gameplayRoot, "*.cs", SearchOption.AllDirectories) - .SelectMany(path => File.ReadLines(path) - .Select((line, index) => new { Path = path, LineNumber = index + 1, Line = line })) - .Where(line => forbiddenSymbols.Any(symbol => line.Line.Contains(symbol, StringComparison.Ordinal))) - .Select(line => $"{Path.GetRelativePath(root, line.Path)}:{line.LineNumber}: {line.Line.Trim()}") - .Take(10) - .ToArray(); - - if (hits.Length > 0) - { - throw new InvalidOperationException( - "Live gameplay source still contains serializer or legacy database symbols: " + - string.Join("; ", hits)); - } -} - -static void RequireSharedEvePackagesImportedFromEveRepo(string root) -{ - var manifestPath = Path.Combine(root, "Aetheria.Unity", "Packages", "manifest.json"); - var lockPath = Path.Combine(root, "Aetheria.Unity", "Packages", "packages-lock.json"); - var authoringManifestPath = Path.Combine(root, "Packages", "manifest.json"); - var authoringLockPath = Path.Combine(root, "Packages", "packages-lock.json"); - var unityAsmdefPath = Path.Combine( - root, - "Packages", - "org.gamecult.aetheria.state", - "Runtime", - "GameCult.Aetheria.State.Unity.asmdef"); - var localSurfacePath = Path.Combine(root, "Packages", "org.gamecult.eve.surface"); - var localUnityUiToolkitPath = Path.Combine(root, "Packages", "org.gamecult.eve.unity-uitoolkit"); - var evePackagesRoot = Path.GetFullPath(Path.Combine(root, "..", "EveUnity", "packages")); - var upstreamSurfacePath = Path.Combine(evePackagesRoot, "org.gamecult.eve.surface", "package.json"); - var upstreamSurfaceDocumentPath = Path.Combine( - evePackagesRoot, - "org.gamecult.eve.surface", - "Runtime", - "EveSurfaceDocument.cs"); - var upstreamSurfaceAsmdefPath = Path.Combine( - evePackagesRoot, - "org.gamecult.eve.surface", - "Runtime", - "GameCult.Eve.Surface.asmdef"); - var upstreamUnityUiToolkitPath = Path.Combine(evePackagesRoot, "org.gamecult.eve.unity-uitoolkit", "package.json"); - var upstreamUnityUiToolkitLowererPath = Path.Combine( - evePackagesRoot, - "org.gamecult.eve.unity-uitoolkit", - "Runtime", - "EveUiToolkitSurfaceLowerer.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", - "org.gamecult.aetheria.state", - "Runtime", - "AetheriaRuntimeCultCacheDocumentStore.cs"); - var playerSettingsSurfaceDocumentsPath = Path.Combine( - root, - "Aetheria.State", - "AetheriaEveSurfaceDocuments.cs"); - var catalogSurfaceDocumentsPath = Path.Combine( - root, - "Aetheria.State", - "AetheriaEveSurfaceDocuments.cs"); - var operationsSurfaceDocumentsPath = Path.Combine( - root, - "Aetheria.State", - "AetheriaEveSurfaceDocuments.cs"); - - var manifest = File.Exists(manifestPath) - ? File.ReadAllText(manifestPath) - : throw new InvalidOperationException("Cannot verify shared Eve package ownership; manifest.json is missing."); - var packageLock = File.Exists(lockPath) - ? File.ReadAllText(lockPath) - : throw new InvalidOperationException("Cannot verify shared Eve package ownership; packages-lock.json is missing."); - var authoringManifest = File.Exists(authoringManifestPath) - ? File.ReadAllText(authoringManifestPath) - : throw new InvalidOperationException("Cannot verify asset-authoring package ownership; manifest.json is missing."); - var authoringPackageLock = File.Exists(authoringLockPath) - ? File.ReadAllText(authoringLockPath) - : throw new InvalidOperationException("Cannot verify asset-authoring package ownership; packages-lock.json is missing."); - var unityAsmdef = File.Exists(unityAsmdefPath) - ? File.ReadAllText(unityAsmdefPath) - : throw new InvalidOperationException("Cannot verify shared Eve package ownership; GameCult.Aetheria.State.Unity.asmdef is missing."); - var upstreamSurfaceDocument = File.Exists(upstreamSurfaceDocumentPath) - ? File.ReadAllText(upstreamSurfaceDocumentPath) - : throw new InvalidOperationException("Cannot verify shared Eve package ownership; upstream EveSurfaceDocument.cs is missing."); - var upstreamSurfaceAsmdef = File.Exists(upstreamSurfaceAsmdefPath) - ? File.ReadAllText(upstreamSurfaceAsmdefPath) - : throw new InvalidOperationException("Cannot verify shared Eve package ownership; upstream GameCult.Eve.Surface.asmdef is missing."); - var upstreamUnityUiToolkitLowerer = File.Exists(upstreamUnityUiToolkitLowererPath) - ? File.ReadAllText(upstreamUnityUiToolkitLowererPath) - : throw new InvalidOperationException("Cannot verify shared Eve package ownership; upstream EveUiToolkitSurfaceLowerer.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."); - var playerSettingsSurfaceDocuments = File.Exists(playerSettingsSurfaceDocumentsPath) - ? File.ReadAllText(playerSettingsSurfaceDocumentsPath) - : throw new InvalidOperationException("Cannot verify shared Eve package ownership; AetheriaEveSurfaceDocuments.cs is missing."); - var catalogSurfaceDocuments = File.Exists(catalogSurfaceDocumentsPath) - ? File.ReadAllText(catalogSurfaceDocumentsPath) - : throw new InvalidOperationException("Cannot verify shared Eve package ownership; AetheriaEveSurfaceDocuments.cs is missing."); - var operationsSurfaceDocuments = File.Exists(operationsSurfaceDocumentsPath) - ? File.ReadAllText(operationsSurfaceDocumentsPath) - : throw new InvalidOperationException("Cannot verify shared Eve package ownership; AetheriaEveSurfaceDocuments.cs is missing."); - - using var manifestDocument = JsonDocument.Parse(manifest); - using var lockDocument = JsonDocument.Parse(packageLock); - using var authoringManifestDocument = JsonDocument.Parse(authoringManifest); - using var authoringLockDocument = JsonDocument.Parse(authoringPackageLock); - var manifestDependencies = manifestDocument.RootElement.GetProperty("dependencies"); - var lockedDependencies = lockDocument.RootElement.GetProperty("dependencies"); - var releasedPackageTags = new Dictionary(StringComparer.Ordinal) - { - ["org.gamecult.cultlib"] = - "https://github.com/GameCult/CultLib.git?path=/unity/org.gamecult.cultlib#cultlib-unity-v", - ["org.gamecult.eve.plugin-fields"] = - "https://github.com/GameCult/EvePlugins.git?path=/plugins/eve-plugin-fields/unity/org.gamecult.eve.plugin-fields#eve-plugin-fields-unity-v", - ["org.gamecult.eve.surface"] = - "https://github.com/GameCult/EveUnity.git?path=/packages/org.gamecult.eve.surface#eveunity-surface-v", - ["org.gamecult.eve.unity-scene"] = - "https://github.com/GameCult/EveUnity.git?path=/packages/org.gamecult.eve.unity-scene#eveunity-scene-v" - }; - - foreach (var (packageId, taggedUrlPrefix) in releasedPackageTags) - { - if (!manifestDependencies.TryGetProperty(packageId, out var manifestVersionElement)) - throw new InvalidOperationException($"Aetheria manifest does not import required released package '{packageId}'."); - - var manifestVersion = manifestVersionElement.GetString() ?? ""; - if (!manifestVersion.StartsWith(taggedUrlPrefix, StringComparison.Ordinal) || - manifestVersion.Length == taggedUrlPrefix.Length) - { - throw new InvalidOperationException( - $"Aetheria package '{packageId}' must use a released Git tag under '{taggedUrlPrefix}'."); - } - - if (!lockedDependencies.TryGetProperty(packageId, out var lockedPackage) || - !string.Equals(lockedPackage.GetProperty("source").GetString(), "git", StringComparison.Ordinal) || - !string.Equals(lockedPackage.GetProperty("version").GetString(), manifestVersion, StringComparison.Ordinal)) - { - throw new InvalidOperationException( - $"Unity lockfile does not resolve '{packageId}' from the exact tagged manifest version '{manifestVersion}'."); - } - - var resolvedHash = lockedPackage.TryGetProperty("hash", out var hashElement) - ? hashElement.GetString() ?? "" - : ""; - if (resolvedHash.Length != 40 || resolvedHash.Any(value => !Uri.IsHexDigit(value))) - throw new InvalidOperationException($"Unity lockfile has no immutable Git commit hash for '{packageId}'."); - } - - const string requiredAuthoringCultLib = - "https://github.com/GameCult/CultLib.git?path=/unity/org.gamecult.cultlib#cultlib-unity-v1.0.43"; - const string requiredAuthoringCultLibCommit = "f67f5122ed1bd11da016e7b820ed60145ccd0299"; - var authoringDependencies = authoringManifestDocument.RootElement.GetProperty("dependencies"); - var authoringLockedDependencies = authoringLockDocument.RootElement.GetProperty("dependencies"); - if (!authoringDependencies.TryGetProperty("org.gamecult.cultlib", out var authoringCultLib) || - !string.Equals(authoringCultLib.GetString(), requiredAuthoringCultLib, StringComparison.Ordinal) || - !authoringLockedDependencies.TryGetProperty("org.gamecult.cultlib", out var authoringCultLibLock) || - !string.Equals(authoringCultLibLock.GetProperty("version").GetString(), requiredAuthoringCultLib, StringComparison.Ordinal) || - !string.Equals(authoringCultLibLock.GetProperty("hash").GetString(), requiredAuthoringCultLibCommit, StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "The fossil asset-authoring Unity project is not pinned to the released CultLib body required by the generic scene package."); - } - - if (Directory.Exists(localSurfacePath) || Directory.Exists(localUnityUiToolkitPath)) - { - throw new InvalidOperationException( - "Aetheria should not keep local staged copies of the shared Eve Unity packages under Packages/."); - } - - if (!File.Exists(upstreamSurfacePath) || !File.Exists(upstreamUnityUiToolkitPath)) - { - throw new InvalidOperationException( - "The neighboring EveUnity repo is missing the shared Unity package roots Aetheria imports."); - } - - if (!unityAsmdef.Contains("\"GameCult.Eve.Surface\"", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Aetheria.State.Unity no longer references the shared Eve surface package assembly."); - } - - if (!upstreamSurfaceAsmdef.Contains("\"GameCult.Mesh\"", StringComparison.Ordinal) || - !upstreamSurfaceDocument.Contains("using GameCult.Mesh;", StringComparison.Ordinal) || - !upstreamSurfaceDocument.Contains("public IReadOnlyList StateBindings", StringComparison.Ordinal) || - !upstreamSurfaceDocument.Contains("public CultMeshOperationBindingDescriptor Operation", StringComparison.Ordinal) || - !upstreamSurfaceDocument.Contains("public CultMeshOperationInvocationDescriptor Operation", StringComparison.Ordinal) || - !upstreamSurfaceDocument.Contains("public CultMeshOperationPayload Payload", StringComparison.Ordinal) || - !upstreamSurfaceDocument.Contains("public string Command => Operation.OperationId", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "The shared Eve surface package no longer exposes first-class CultMesh state bindings and operation descriptors."); - } - - if (upstreamSurfaceDocument.Contains("EveCommandTemplate(string command", StringComparison.Ordinal) || - upstreamSurfaceDocument.Contains("string command,\r\n IReadOnlyDictionary payload", StringComparison.Ordinal) || - upstreamSurfaceDocument.Contains("CultMeshOperationInvocationDescriptor operation,\r\n IReadOnlyDictionary payload", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "The shared Eve surface package has reintroduced raw command or dictionary payload request constructors."); - } - - 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("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/payloads or nested document slots."); - } - - 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) || - !runtimeSurfaceDocuments.Contains("public static CultMeshStateBindingDescriptor ForDaemonStateRef(", StringComparison.Ordinal) || - !runtimeSurfaceDocuments.Contains("public CultMeshOperationBindingDescriptor Operation", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Aetheria runtime surfaces should use CultMesh state, operation, and embedded document descriptors directly instead of local live binding DTOs."); - } - - 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 and embedded document slots to the shared Eve component model."); - } - - if (!runtimeSurfaceDocuments.Contains("CultMesh.StateBindingRecord(binding)", StringComparison.Ordinal) || - !runtimeSurfaceDocuments.Contains("CultMesh.StateBindingRecord(", StringComparison.Ordinal) || - !runtimeSurfaceDocuments.Contains(".ToBinding()", StringComparison.Ordinal) || - runtimeSurfaceDocuments.Contains("CultMesh.RouteRecord(binding.RouteHint)", StringComparison.Ordinal) || - runtimeSurfaceDocuments.Contains("CultMesh.RouteRecord(binding.RouteKind, binding.RouteDescription)", StringComparison.Ordinal) || - runtimeSurfaceBuilder.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."); - } - - var deletedRuntimeSurfaceStateProjectorPath = Path.Combine( - root, - "Aetheria.State", - "AetheriaRuntimeEveSurfaceStateProjector.cs"); - if (File.Exists(deletedRuntimeSurfaceStateProjectorPath)) - { - throw new InvalidOperationException( - "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."); - } - - if (!runtimeCultCacheDocumentStore.Contains("CultMesh.OperationBindingRecord(command.Operation)", StringComparison.Ordinal) || - !runtimeCultCacheDocumentStore.Contains("CultMesh.OperationBindingRecord(", StringComparison.Ordinal) || - !runtimeCultCacheDocumentStore.Contains("routeDescription).ToBinding()", StringComparison.Ordinal) || - !runtimeSurfaceDocuments.Contains("public static EveUiSurfaceDocument ToPortableSurface(AetheriaRuntimeSurfaceDocument document)", StringComparison.Ordinal) || - !runtimeSurfaceDocuments.Contains("new EveUiCommandTemplate(command.Operation)", StringComparison.Ordinal) || - runtimeSurfaceDocuments.Contains("GameCult.Mesh.EveSurface", StringComparison.Ordinal) || - runtimeCultCacheDocumentStore.Contains("legacyTransportOrSchema", StringComparison.Ordinal) || - runtimeCultCacheDocumentStore.Contains("commandFields > 3 ? legacyTransportOrSchema", StringComparison.Ordinal) || - runtimeCultCacheDocumentStore.Contains("new CultMeshOperationBindingDescriptor(", StringComparison.Ordinal) || - runtimeSurfaceDocuments.Contains("new CultMeshOperationBindingDescriptor(", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Aetheria runtime surfaces reintroduced local operation binding flattening; portable surface command persistence belongs to CultMesh Eve documents."); - } -} - -static void RequireSharedRuntimeSurfaceCommandsUseCultMeshTransport(string root) -{ - var runtimePath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime"); - if (!Directory.Exists(runtimePath)) - { - throw new InvalidOperationException( - "Shared runtime surface command transport cannot be verified because the state runtime package is missing."); - } - - var runtimeSources = Directory.EnumerateFiles(runtimePath, "*.cs", SearchOption.TopDirectoryOnly) - .ToDictionary( - path => Path.GetRelativePath(root, path), - File.ReadAllText, - StringComparer.Ordinal); - var combinedRuntime = string.Join("\n", runtimeSources.Values); - - if (!combinedRuntime.Contains("public const string CultMeshTransport = \"cultmesh\"", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Shared runtime surface command templates do not expose a neutral CultMesh transport constant."); - } - - var unityToolkitTransportHits = runtimeSources - .Where(pair => - pair.Value.Contains("\"unity-uitoolkit\"", StringComparison.Ordinal) && - pair.Value.Contains("AetheriaRuntimeSurfaceCommandTemplate", StringComparison.Ordinal)) - .Select(pair => pair.Key) - .ToArray(); - if (unityToolkitTransportHits.Length > 0) - { - throw new InvalidOperationException( - "Shared runtime surface command templates still advertise Unity UI Toolkit as the transport owner: " + - string.Join(", ", unityToolkitTransportHits)); - } - - Console.WriteLine("Shared runtime surface commands: command templates advertise CultMesh transport instead of Unity UI Toolkit"); -} - -static void RequireSharedRuntimeSurfacesUseClientNeutralVocabulary(string root) -{ - var runtimePath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime"); - if (!Directory.Exists(runtimePath)) - { - throw new InvalidOperationException( - "Shared runtime surface vocabulary cannot be verified because the state runtime package is missing."); - } - - var forbiddenSurfaceVocabulary = new[] - { - "Unity supplies", - "Unity projects", - "Unity reads", - "Unity must observe", - "Unity runtime shells" - }; - var hits = Directory.EnumerateFiles(runtimePath, "*.cs", SearchOption.TopDirectoryOnly) - .SelectMany(path => File.ReadLines(path) - .Select((line, index) => new { Path = path, LineNumber = index + 1, Line = line })) - .Where(line => forbiddenSurfaceVocabulary.Any(symbol => line.Line.Contains(symbol, StringComparison.Ordinal))) - .Select(line => $"{Path.GetRelativePath(root, line.Path)}:{line.LineNumber}: {line.Line.Trim()}") - .ToArray(); - if (hits.Length > 0) - { - throw new InvalidOperationException( - "Shared runtime surfaces still describe their contract as Unity-owned instead of client-neutral CultMesh API: " + - string.Join("; ", hits)); - } - - Console.WriteLine("Shared runtime surfaces: surface copy uses client-neutral observer vocabulary"); -} - -static void RequirePackageSerializerBoundary(string root) -{ - var packageRuntimeRoot = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime"); - if (!Directory.Exists(packageRuntimeRoot)) - { - throw new InvalidOperationException($"Cannot verify package serializer boundary; missing path: {packageRuntimeRoot}"); - } - - var allowedSerializerExecutionFiles = new HashSet(StringComparer.Ordinal) - { - "AetheriaRuntimeCatalogStore.cs", - "AetheriaRuntimeClientTargetStore.cs", - "AetheriaRuntimeCultCacheDocumentStore.cs", - "AetheriaRuntimeVerseClient.cs", - "AetheriaRuntimeSnapshotDocuments.cs" - }; - - var serializerExecutionSymbols = new[] - { - "MessagePackObject", - "MessagePackSerializer", - "MessagePackReader", - "MessagePackWriter", - "IMessagePackFormatter" - }; - - var hits = Directory.EnumerateFiles(packageRuntimeRoot, "*.cs", SearchOption.AllDirectories) - .Where(path => !allowedSerializerExecutionFiles.Contains(Path.GetFileName(path))) - .SelectMany(path => File.ReadLines(path) - .Select((line, index) => new { Path = path, LineNumber = index + 1, Line = line })) - .Where(line => serializerExecutionSymbols - .Where(symbol => symbol != "MessagePackObject") - .Any(symbol => line.Line.Contains(symbol, StringComparison.Ordinal))) - .Select(line => $"{Path.GetRelativePath(root, line.Path)}:{line.LineNumber}: {line.Line.Trim()}") - .Take(10) - .ToArray(); - - if (hits.Length > 0) - { - throw new InvalidOperationException( - "Package serializer execution escaped the named CultCache transport boundary: " + - string.Join("; ", hits)); - } -} - -static void RequireTypedPendingCommitKeys(string root) -{ - var checkedFiles = new[] - { - Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeSnapshotDocuments.cs") - }; - - var forbiddenSymbols = new[] - { - "CorporationLegacyId", - "OrbitLegacyId", - "ParentLegacyId", - "BodyLegacyId", - "ResourceScannerTargetBodyId { get; set; }", - "MiningToolAsteroidBeltId { get; set; }", - "ReferenceKey(entity.FactionKey", - "ReferenceKey(orbit.OrbitKey", - "ReferenceKey(body.BodyKey", - "ReferenceKey(relationship.FactionKey" - }; - - var hits = checkedFiles - .Where(File.Exists) - .SelectMany(path => File.ReadLines(path) - .Select((line, index) => new { Path = path, LineNumber = index + 1, Line = line })) - .Where(line => forbiddenSymbols.Any(symbol => line.Line.Contains(symbol, StringComparison.Ordinal))) - .Select(line => $"{Path.GetRelativePath(root, line.Path)}:{line.LineNumber}: {line.Line.Trim()}") - .ToArray(); - - if (hits.Length > 0) - { - throw new InvalidOperationException( - "Pending runtime commit authority still accepts legacy ID fallback fields: " + - string.Join("; ", hits)); - } -} - -static void RequireTypedRuntimeFactionKeys(string root) -{ - var checkedFiles = new[] - { - Path.Combine(root, "Assets", "Scripts", "ServerShared", "EntityConstructionBlueprintMaterializer.cs"), - Path.Combine(root, "Assets", "Scripts", "Gameplay", "ActionGameManager.cs") - }; - - var forbiddenSymbols = new[] - { - "public Guid Faction", - "blueprint.Faction =", - "blueprint.Faction)", - "blueprint.Faction,", - "blueprint.Faction;", - "ResolveFaction(blueprint.Faction", - "LegacyId(blueprint.Faction", - "ReferenceKey(blueprint.FactionKey", - "CorporationKey(entity.Faction?.ID", - "CorporationKey(pair.Key.ID", - "private static string CorporationKey" - }; - - var hits = checkedFiles - .Where(File.Exists) - .SelectMany(path => File.ReadLines(path) - .Select((line, index) => new { Path = path, LineNumber = index + 1, Line = line })) - .Where(line => forbiddenSymbols.Any(symbol => line.Line.Contains(symbol, StringComparison.Ordinal))) - .Select(line => $"{Path.GetRelativePath(root, line.Path)}:{line.LineNumber}: {line.Line.Trim()}") - .ToArray(); - - if (hits.Length > 0) - { - throw new InvalidOperationException( - "Runtime faction commits must use typed FactionKey authority, not legacy GUID fallback: " + - string.Join("; ", hits)); - } -} - -static void RequireTypedGalaxyFactionRelationships(string root) -{ - var checkedFiles = new[] - { - Path.Combine(root, "Assets", "Scripts", "ServerShared", "Corporations.cs"), - Path.Combine(root, "Assets", "Scripts", "ServerShared", "Galaxy.cs"), - Path.Combine(root, "Assets", "Scripts", "ServerShared", "LoadoutGenerator.cs") - }; - - var forbiddenSymbols = new[] - { - "Dictionary Allegiance", - ".Allegiance[", - "ContainsFaction(Guid", - "ResolveFaction(Guid", - "CorporationLegacyId", - "factionsByLegacyId", - "_containedFactions" - }; - - var hits = checkedFiles - .Where(File.Exists) - .SelectMany(path => File.ReadLines(path) - .Select((line, index) => new { Path = path, LineNumber = index + 1, Line = line })) - .Where(line => forbiddenSymbols.Any(symbol => line.Line.Contains(symbol, StringComparison.Ordinal))) - .Select(line => $"{Path.GetRelativePath(root, line.Path)}:{line.LineNumber}: {line.Line.Trim()}") - .ToArray(); - - if (hits.Length > 0) - { - throw new InvalidOperationException( - "Galaxy faction relationships must use typed corporation keys, not legacy GUID allegiance state: " + - string.Join("; ", hits)); - } -} - -static void RequireRuntimeCatalogKeyOnlyLookups(string root) -{ - var checkedFiles = new[] - { - Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeCatalogSnapshot.cs"), - Path.Combine(root, "Aetheria.State.Unity.Smoke", "Program.cs"), - Path.Combine(root, "Assets", "Scripts", "ServerShared", "ItemManager.cs"), - Path.Combine(root, "Assets", "Scripts", "Gameplay", "ActionGameManager.cs") - }; - - var forbiddenSymbols = new[] - { - "IRuntimeItemCatalogReader", - "AetheriaRuntimeItemCatalog", - "_itemsByLegacyId", - "_corporationsByLegacyId", - "_nameFilesByLegacyId", - "FindItemByLegacyId", - "FindCorporationByLegacyId", - "FindNameFileByLegacyId", - "public string LegacyId { get; }", - "public string ManufacturerLegacyId", - "public string GeonameFileLegacyId", - "public string BossHullLegacyId", - "public string CorporationLegacyId", - ".ManufacturerLegacyId", - ".GeonameFileLegacyId", - ".BossHullLegacyId", - ".CorporationLegacyId" - }; - - var hits = checkedFiles - .Where(File.Exists) - .SelectMany(path => File.ReadLines(path) - .Select((line, index) => new { Path = path, LineNumber = index + 1, Line = line })) - .Where(line => forbiddenSymbols.Any(symbol => line.Line.Contains(symbol, StringComparison.Ordinal))) - .Select(line => $"{Path.GetRelativePath(root, line.Path)}:{line.LineNumber}: {line.Line.Trim()}") - .ToArray(); - - if (hits.Length > 0) - { - throw new InvalidOperationException( - "Unity runtime catalog lookup authority must stay on the typed snapshot; legacy-ID indexes and redundant catalog adapters belong to migration boundaries: " + - string.Join("; ", hits)); - } -} - -static void RequireClientTransportPlaneOwnership(string root) -{ - var daemonPath = Path.Combine(root, "Aetheria.State.Daemon", "Program.cs"); - var launcherPath = Path.Combine(root, "scripts", "run-aetheria-unity.ps1"); - var editorLauncherPath = Path.Combine(root, "scripts", "start-aetheria-daemon-editor.ps1"); - var editorControllerPath = Path.Combine( - root, "Aetheria.Unity", "Assets", "Editor", "AetheriaDaemonDevelopmentWindow.cs"); - var daemon = File.Exists(daemonPath) - ? File.ReadAllText(daemonPath) - : throw new InvalidOperationException("Cannot verify client transport ownership; daemon Program.cs is missing."); - var launcher = File.Exists(launcherPath) - ? File.ReadAllText(launcherPath) - : throw new InvalidOperationException("Cannot verify client transport ownership; Unity launcher is missing."); - var editorLauncher = File.Exists(editorLauncherPath) - ? File.ReadAllText(editorLauncherPath) - : throw new InvalidOperationException("Cannot verify client transport ownership; Unity editor daemon launcher is missing."); - var editorController = File.Exists(editorControllerPath) - ? File.ReadAllText(editorControllerPath) - : throw new InvalidOperationException("Cannot verify client transport ownership; Unity editor daemon controller is missing."); - var required = new[] - { - "new TcpFramedCultNetSchemaServer(", - "new CultMeshTcpContentServer(", - "CultMeshTcpContentTransportConnector.Scheme", - "CultMeshQuicRealtimeServer.ListenAsync(", - "CultMeshQuicRealtimeTransportConnector.Scheme", - "new CultNetDatabaseSubscriptionServer(cultMeshClientHost.Control, node.Database)", - "cultnet+tcp://127.0.0.1:$Port" - }; - var combined = daemon + "\n" + launcher; - var missing = required.Where(symbol => !combined.Contains(symbol, StringComparison.Ordinal)).ToArray(); - if (missing.Length > 0) - throw new InvalidOperationException( - "Aetheria client transport planes lost their TCP control/content ownership: " + string.Join(", ", missing)); - - var discoveryEndpoints = Regex.Match( - daemon, - @"discoveryEndpoints\s*:\s*new\[\]\s*\{(?[^}]*)\}", - RegexOptions.CultureInvariant | RegexOptions.Singleline); - if (!discoveryEndpoints.Success) - throw new InvalidOperationException("Aetheria provider Verse does not advertise an explicit transport endpoint set."); - var advertised = discoveryEndpoints.Groups["items"].Value - .Split(',') - .Select(endpoint => endpoint.Trim()) - .Where(endpoint => endpoint.Length > 0) - .ToHashSet(StringComparer.Ordinal); - var requiredAdvertisements = new[] - { - "advertisedEndpoint", - "advertisedContentEndpoint", - "advertisedRealtimeEndpoint" - }; - var missingAdvertisements = requiredAdvertisements.Where(endpoint => !advertised.Contains(endpoint)).ToArray(); - if (missingAdvertisements.Length > 0) - { - throw new InvalidOperationException( - "Aetheria provider Verse does not advertise every owned transport plane: " + - string.Join(", ", missingAdvertisements)); - } - - var forbidden = new[] - { - "RudpCultNetSchemaServer", - "new CultMeshContentServer(", - "new CultMeshBodyServer(", - "RunClientCultMeshPumpAsync", - "AETHERIA_TRACE_CLIENT_RUDP", - "rudp://127.0.0.1:$Port" - }; - var present = forbidden.Where(symbol => combined.Contains(symbol, StringComparison.Ordinal)).ToArray(); - if (present.Length > 0) - throw new InvalidOperationException( - "Aetheria reintroduced RUDP or schema-carried bulk state into the client boundary: " + - string.Join(", ", present)); - - var restoreIndex = daemon.IndexOf( - "using var physicsPersistence = await AetheriaYmirPersistenceCoordinator.OpenAsync(", - StringComparison.Ordinal); - var clientHostIndex = daemon.IndexOf( - "await using var cultMeshClientHost = await StartClientCultMeshHostAsync(", - StringComparison.Ordinal); - var readyGateIndex = daemon.IndexOf( - "if (!options.Once && !options.UseTerminusFixture)", - StringComparison.Ordinal); - var demandIndex = daemon.IndexOf( - "var playableWorldDemand = new AetheriaPlayableWorldDemandState()", - StringComparison.Ordinal); - var gameSessionIndex = daemon.IndexOf( - "await EnsureGameSessionAsync(node, options, startedAtUtc, latestFrame)", - StringComparison.Ordinal); - if (restoreIndex < 0 || clientHostIndex < 0 || readyGateIndex < 0 || demandIndex < 0 || gameSessionIndex < 0 || - clientHostIndex > readyGateIndex || demandIndex > readyGateIndex || - readyGateIndex > gameSessionIndex || gameSessionIndex > restoreIndex || - !daemon.Contains("Aetheria daemon ready; waiting for a client to load or generate a world.", StringComparison.Ordinal) || - !daemon.Contains("if (playableWorldDemand.IsActive", StringComparison.Ordinal) || - !daemon.Contains("while (!stopped.Task.IsCompleted && (options.UseTerminusFixture || playableWorldDemand.IsActive))", StringComparison.Ordinal) || - !daemon.Contains("Aetheria daemon saved frame", StringComparison.Ordinal) || - !daemon.Contains("if (await ApplyRequestedNewGameSessionAsync(node, options)", StringComparison.Ordinal)) - throw new InvalidOperationException( - "Aetheria daemon must expose transport in ready mode, demand-gate game/Ymir activation, and checkpoint before returning to ready after the last playable client drops."); - - var requiredEditorResetSymbols = new[] - { - "$ymirStatePath = \"$statePath.ymir.cc\"", - "$ymirRecordsPath = \"$ymirStatePath.records\"", - "$ymirStatePath, $ymirRecordsPath" - }; - var missingEditorResetSymbols = requiredEditorResetSymbols - .Where(symbol => !editorLauncher.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (missingEditorResetSymbols.Length > 0) - throw new InvalidOperationException( - "Aetheria editor reimport does not reset every daemon-private Ymir state path: " + - string.Join(", ", missingEditorResetSymbols)); - if (editorLauncher.Contains("Start-Process", StringComparison.Ordinal) || - !editorController.Contains("public static void Start()", StringComparison.Ordinal) || - !editorController.Contains("public static void Build(bool forceImport = false)", StringComparison.Ordinal) || - !editorController.Contains("Process.Start(startInfo)", StringComparison.Ordinal) || - !editorController.Contains("new ProcessStartInfo(DaemonExePath, arguments)", StringComparison.Ordinal) || - !editorController.Contains("DOTNET_ROOT", StringComparison.Ordinal) || - !editorController.Contains("DOTNET_ROOT_X64", StringComparison.Ordinal) || - !editorController.Contains("_status = \"Debug daemon prepared\"", StringComparison.Ordinal) || - !editorController.Contains("SessionState.SetInt(ProcessIdSessionKey, daemon.Id)", StringComparison.Ordinal) || - editorController.Contains("playModeStateChanged", StringComparison.Ordinal) || - editorController.Contains("_enterPlayWhenReady", StringComparison.Ordinal) || - editorController.Contains("Start & Play", StringComparison.Ordinal) || - editorController.Contains("Start before Play", StringComparison.Ordinal)) - throw new InvalidOperationException( - "The daemon window must independently build and launch through the system .NET host without coupling either operation to Unity Play lifecycle."); - - Console.WriteLine("Client transport ownership: ready transport precedes demand-gated game/Ymir activation; the daemon window owns an independent prepared Debug apphost and matching private state"); -} - -static void RequireDaemonPlayableRunGenerationAuthority(string root) -{ - var daemonProgramPath = Path.Combine(root, "Aetheria.State.Daemon", "Program.cs"); - var daemonZoneGeneratorPath = Path.Combine(root, "Aetheria.State.Daemon", "AetheriaDaemonZoneGenerator.cs"); - var daemonLoadoutGeneratorPath = Path.Combine(root, "Aetheria.State.Daemon", "AetheriaDaemonLoadoutGenerator.cs"); - var sharedLoadoutGeneratorPath = Path.Combine(root, "Assets", "Scripts", "ServerShared", "LoadoutGenerator.cs"); - - var daemonProgram = File.Exists(daemonProgramPath) - ? File.ReadAllText(daemonProgramPath) - : throw new InvalidOperationException("Cannot verify daemon playable-run generation authority; daemon Program.cs is missing."); - var daemonZoneGenerator = File.Exists(daemonZoneGeneratorPath) - ? File.ReadAllText(daemonZoneGeneratorPath) - : throw new InvalidOperationException("Cannot verify daemon playable-run generation authority; daemon zone generator is missing."); - var daemonLoadoutGenerator = File.Exists(daemonLoadoutGeneratorPath) - ? File.ReadAllText(daemonLoadoutGeneratorPath) - : throw new InvalidOperationException("Cannot verify daemon playable-run generation authority; daemon loadout generator is missing."); - var sharedLoadoutGenerator = File.Exists(sharedLoadoutGeneratorPath) - ? File.ReadAllText(sharedLoadoutGeneratorPath) - : throw new InvalidOperationException("Cannot verify daemon playable-run generation authority; legacy Unity LoadoutGenerator.cs is missing."); - - var requiredDaemonSymbols = new[] - { - "await AetheriaDaemonZoneGenerator.WritePlayableRunAsync(", - "internal static class AetheriaDaemonZoneGenerator", - "new AetheriaDaemonLoadoutGenerator(", - "GenerateEntities(loadouts, availabilityFactions, catalog, scenario)", - "AetheriaDaemonTerminusScenarios.Parse", - "ApplyReleasedClientProofScenario(entities, catalog, scenario)", - "TerminusScenario = terminusScenario", - "var rootRandom = new CultMath.Random(GenerationSeed)", - "(uint)rootRandom.NextInt(1, int.MaxValue)", - "Math.Pow(item.Price, _priceExponent)", - "ManufacturerDistance = _homeZones.TryGetValue(item.ManufacturerKey, out var home) ? Distance(_zoneIndex, home) : 1", - "FitsHardpoint(candidate, hardpoint)", - "PickHardpoint(availabilityFactionKey, hardpoint", - "var largestFootprint = candidates.Max(item => item.OccupiedCells)", - "HasBehavior(candidate, controllerKind)", - "for (var draw = 0; draw < 16 && pool.Count > 0; draw++)", - "FindFit(interiorCells, width, height, item, occupied)", - "slots.Add(Slot(hardpoint.PositionX, hardpoint.PositionY, item.ItemKey, rotation))", - "Rotation = RotationName(rotation)", - "AetheriaLoadoutGenerationReceipt", - "Selections = selectedKeys.Select" - }; - - var daemonText = daemonProgram + "\n" + daemonZoneGenerator + "\n" + daemonLoadoutGenerator; - var missingDaemonSymbols = requiredDaemonSymbols - .Where(symbol => !daemonText.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (missingDaemonSymbols.Length > 0) - { - throw new InvalidOperationException( - "Playable run and loadout generation must be daemon-owned, not recovered through Unity ServerShared generators: " + - string.Join(", ", missingDaemonSymbols)); - } - - var forbiddenDaemonSymbols = new[] - { - "new LoadoutGenerator(", - "ZoneGenerator.Generate(", - "Assets.Scripts.ServerShared", - "ServerShared.LoadoutGenerator", - "ManufacturerScore(", - "FallbackHull(", - "sensorArrayCount", - "item.OccupiedCells != hardpoint.OccupiedCells" - }; - var survivingDaemonSymbols = forbiddenDaemonSymbols - .Where(symbol => daemonText.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (survivingDaemonSymbols.Length > 0) - { - throw new InvalidOperationException( - "Daemon generation must not call the legacy Unity ServerShared loadout/zone generators: " + - string.Join(", ", survivingDaemonSymbols)); - } - - if (sharedLoadoutGenerator.Contains("// public LoadoutGenerator", StringComparison.Ordinal) || - sharedLoadoutGenerator.Contains("// ref Random random", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Legacy Unity LoadoutGenerator must not carry commented constructor scars; Git history owns removed APIs."); - } - - if (sharedLoadoutGenerator.Contains("item.OccupiedCells == hardpoint.OccupiedCells", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Hardpoint fit is same-type geometric containment; smaller equipment remains legal in larger mounts."); - } -} - -static void RequireTypedBehaviorBodyKeys(string root) -{ - var checkedFiles = new[] - { - Path.Combine(root, "Aetheria.State", "Documents", "AetheriaRuntimeStateDocuments.cs"), - Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeCatalogSnapshot.cs"), - Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeCatalogStore.cs"), - Path.Combine(root, "Assets", "Scripts", "Gameplay", "ActionGameManager.cs"), - Path.Combine(root, "Assets", "Scripts", "ServerShared", "Zone.cs"), - Path.Combine(root, "Aetheria.State.Smoke", "Program.cs"), - Path.Combine(root, "Aetheria.State.Unity.Smoke", "Program.cs") - }; - - var forbiddenSymbols = new[] - { - "ResourceScannerTargetBodyId", - "MiningToolAsteroidBeltId", - "ParseLegacyGuidFromReferenceKey", - "ParseLegacyIdFromReferenceKey", - "ParseBodyGuidFromKey(", - "private static string OrbitKey(Guid", - "private static string BodyKey(Guid", - "aetheria.orbit:legacy:", - "aetheria.body:legacy:", - "public Guid ScanTarget", - "public Guid AsteroidBelt", - "RestoreRuntimeState(Guid scanTarget", - "RestoreRuntimeState(Guid asteroidBelt" - }; - - var hits = checkedFiles - .Where(File.Exists) - .SelectMany(path => File.ReadLines(path) - .Select((line, index) => new { Path = path, LineNumber = index + 1, Line = line })) - .Where(line => forbiddenSymbols.Any(symbol => line.Line.Contains(symbol, StringComparison.Ordinal))) - .Select(line => $"{Path.GetRelativePath(root, line.Path)}:{line.LineNumber}: {line.Line.Trim()}") - .ToArray(); - - if (hits.Length > 0) - { - throw new InvalidOperationException( - "Runtime behavior body references must stay on typed BodyKey surfaces, with Zone owning key resolution and behaviors no longer storing raw GUID body ids: " + - string.Join("; ", hits)); - } - - var zoneSourcePath = Path.Combine(root, "Assets", "Scripts", "ServerShared", "Zone.cs"); - var zoneSource = File.Exists(zoneSourcePath) - ? File.ReadAllText(zoneSourcePath) - : throw new InvalidOperationException("Cannot verify body/orbit key ownership; Zone source is missing."); - var requiredZoneSymbols = new[] - { - "public const string OrbitKeyPrefix", - "public const string BodyKeyPrefix", - "public string BodyKey { get; }", - "public string OrbitKey { get; }", - "public string ParentOrbitKey { get; }", - "public bool TryGetPlanet(string bodyKey, out Planet planet)", - "public bool TryGetAsteroidBelt(string bodyKey, out AsteroidBelt belt)" - }; - var missingZoneSymbols = requiredZoneSymbols - .Where(symbol => !zoneSource.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (missingZoneSymbols.Length > 0) - { - throw new InvalidOperationException( - "Zone runtime body/orbit objects must own typed document key surfaces: " + - string.Join(", ", missingZoneSymbols)); - } - - var requiredBehaviorSymbols = new Dictionary - { - [Path.Combine(root, "Assets", "Scripts", "ServerShared", "Behaviors", "ResourceScanner.cs")] = new[] - { - "private string _scanTargetBodyKey = \"\";", - "public string ScanTargetBodyKey", - "Entity.Zone.TryGetAsteroidBelt(ScanTargetBodyKey, out var belt)", - "Entity.Zone.TryGetPlanet(ScanTargetBodyKey, out var planet)", - "RestoreRuntimeState(", - "string scanTargetBodyKey" - }, - [Path.Combine(root, "Assets", "Scripts", "ServerShared", "Behaviors", "MiningTool.cs")] = new[] - { - "public string AsteroidBeltBodyKey = \"\";", - "Entity.Zone.TryGetAsteroidBelt(AsteroidBeltBodyKey, out var belt)", - "Entity.Zone.AsteroidExists(AsteroidBeltBodyKey, Asteroid)", - "Entity.Zone.MineAsteroid(", - "AsteroidBeltBodyKey,", - "string asteroidBeltBodyKey" - } - }; - var missingBehaviorSymbols = requiredBehaviorSymbols - .Where(pair => !File.Exists(pair.Key) || pair.Value.Any(symbol => !File.ReadAllText(pair.Key).Contains(symbol, StringComparison.Ordinal))) - .SelectMany(pair => - { - var text = File.Exists(pair.Key) ? File.ReadAllText(pair.Key) : ""; - return pair.Value - .Where(symbol => !text.Contains(symbol, StringComparison.Ordinal)) - .Select(symbol => $"{Path.GetRelativePath(root, pair.Key)}: missing {symbol}"); - }) - .ToArray(); - if (missingBehaviorSymbols.Length > 0) - { - throw new InvalidOperationException( - "ResourceScanner and MiningTool must own typed body-key runtime references rather than raw GUID fields: " + - string.Join("; ", missingBehaviorSymbols)); - } -} -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(", - "ReactiveGameViewport(", - "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"); - if (Directory.Exists(runtimeDocumentsFactoryDirectory)) - { - var survivors = Directory.EnumerateFileSystemEntries(runtimeDocumentsFactoryDirectory) - .Select(path => Path.GetRelativePath(root, path)) - .ToArray(); - throw new InvalidOperationException( - "Dead runtime-projection helper directory should stay deleted; move live helpers under explicit runtime owners: " + - string.Join(", ", survivors)); - } - - var selectionExtensionsPath = Path.Combine(root, "Assets", "Scripts", "ServerShared", "EnumerableSelectionExtensions.cs"); - var sharedProjectPath = Path.Combine(root, "Aetheria.Shared.Unity.csproj"); - var tradeMenuPath = Path.Combine(root, "Assets", "Scripts", "UI", "Menu", "TradeMenu.cs"); - var selectionExtensions = File.Exists(selectionExtensionsPath) - ? File.ReadAllText(selectionExtensionsPath) - : ""; - var sharedProject = File.Exists(sharedProjectPath) - ? File.ReadAllText(sharedProjectPath) - : ""; - var tradeMenu = File.Exists(tradeMenuPath) - ? File.ReadAllText(tradeMenuPath) - : ""; - - if (!selectionExtensions.Contains("public static T MaxBy", StringComparison.Ordinal) || - !selectionExtensions.Contains("public static T MinBy", StringComparison.Ordinal) || - !sharedProject.Contains(@"Assets\Scripts\ServerShared\EnumerableSelectionExtensions.cs", StringComparison.Ordinal) || - sharedProject.Contains("RuntimeProjection", StringComparison.Ordinal) || - tradeMenu.Contains(".FormatTypeName()", StringComparison.Ordinal) || - !tradeMenu.Contains("private static string FormatTypeName(string typeName)", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Live RuntimeProjection remnants must move into explicit owners: enumerable selection helpers under ServerShared and trade labels inside TradeMenu."); - } -} - -static void RequireNoDeadRuntimeProjectionClasses(string root) -{ - var forbiddenSymbols = new[] - { - "AetheriaRuntimeRtsSimulation", - "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)); - } - - var activeDocsRoot = Path.Combine(root, "Aetheria.State", "docs"); - if (Directory.Exists(activeDocsRoot)) - { - var docHits = Directory.EnumerateFiles(activeDocsRoot, "*.md", SearchOption.AllDirectories) - .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 (docHits.Length > 0) - { - throw new InvalidOperationException( - "Active architecture docs must not point future work at deleted runtime-specific shared authorities: " + - string.Join("; ", docHits)); - } - - var daemonRuntimeSpecificHookPhrases = new[] - { - "daemon RTS viewport", - "Daemon RTS viewport", - "temporary RUDP RTS viewport", - "RUDP RTS viewport", - "daemon RTS command", - "Daemon RTS command" - }; - var hookPhraseHits = Directory.EnumerateFiles(activeDocsRoot, "*.md", SearchOption.AllDirectories) - .SelectMany(path => - { - var text = File.ReadAllText(path); - return daemonRuntimeSpecificHookPhrases - .Where(phrase => text.Contains(phrase, StringComparison.Ordinal)) - .Select(phrase => $"{Path.GetRelativePath(root, path)} contains {phrase}"); - }) - .ToArray(); - if (hookPhraseHits.Length > 0) - { - throw new InvalidOperationException( - "Active architecture docs must describe daemon RUDP hooks as client-neutral compatibility, not RTS-specific daemon authority: " + - string.Join("; ", hookPhraseHits)); - } - } -} - -static void RequireNoDeadInspectorMetadata(string root) -{ - var checkedFiles = new[] - { - Path.Combine(root, "Assets", "Scripts", "Gameplay", "FieldDriver.cs"), - Path.Combine(root, "Assets", "Scripts", "ServerShared", "Corporations.cs"), - Path.Combine(root, "Assets", "Scripts", "ServerShared", "Behaviors", "AetherDrive.cs"), - Path.Combine(root, "Assets", "Scripts", "ServerShared", "Behaviors", "StatModifier.cs"), - Path.Combine(root, "Assets", "Scripts", "ServerShared", "Behaviors", "VelocityLimit.cs"), - Path.Combine(root, "Assets", "Scripts", "ServerShared", "ExponentialCurve.cs"), - Path.Combine(root, "Assets", "Scripts", "ServerShared", "RuntimeGeometry.cs"), - Path.Combine(root, "Assets", "Scripts", "UI", "Properties Panel", "PropertiesPanel.cs") - }; - - var forbiddenSymbols = new[] - { - "InspectableAttribute", - "PreferredInspectorAttribute", - "InspectableTextAttribute", - "InspectablePrefabAttribute", - "InspectableTextureAttribute", - "InspectableTextAssetAttribute", - "InspectableTemperatureAttribute", - "InspectableAnimationCurveAttribute", - "InspectableColorAttribute", - "InspectableSoundBankAttribute", - "InspectableAudioParameterAttribute", - "InspectableSchematicShapeAttribute", - "InspectableEnumValuesAttribute", - "InspectableRangedFloatAttribute", - "InspectableRangedIntAttribute", - "OrderAttribute", - "EntityTypeRestrictionAttribute", - "InspectorHeaderAttribute", - "[Inspectable", - "[InspectableText", - "[InspectablePrefab", - "[InspectableTexture", - "[InspectableTextAsset", - "[InspectableTemperature", - "[InspectableAnimationCurve", - "[InspectableColor", - "[InspectableSoundBank", - "[InspectableAudioParameter", - "[InspectableSchematicShape", - "[InspectableEnumValues", - "[InspectableRangedFloat", - "[InspectableRangedInt", - "[Order(", - "EntityTypeRestriction(", - "[InspectorHeader(" - }; - - var hits = checkedFiles - .Where(File.Exists) - .SelectMany(path => File.ReadLines(path) - .Select((line, index) => new { Path = path, LineNumber = index + 1, Line = line })) - .Where(line => forbiddenSymbols.Any(symbol => line.Line.Contains(symbol, StringComparison.Ordinal))) - .Select(line => $"{Path.GetRelativePath(root, line.Path)}:{line.LineNumber}: {line.Line.Trim()}") - .ToArray(); - - if (hits.Length > 0) - { - throw new InvalidOperationException( - "Dead inspector-specialization metadata should stay deleted; no generic Inspectable attribute path should remain in live runtime code: " + - string.Join("; ", hits)); - } - - var deletedFiles = new[] - { - Path.Combine(root, "Assets", "Scripts", "ServerShared", "RuntimeProjection", "Attributes.cs"), - Path.Combine(root, "Assets", "Scripts", "ServerShared", "Attributes.cs") - }; - - var stillPresent = deletedFiles.Where(File.Exists).Select(path => Path.GetRelativePath(root, path)).ToArray(); - if (stillPresent.Length > 0) - { - throw new InvalidOperationException( - "Dead inspector attribute definition files should be deleted once the generic reflection inspector path is gone: " + - string.Join(", ", stillPresent)); - } -} - -static void RequireTypedOrbitTaskKeys(string root) -{ - var checkedFiles = new[] - { - Path.Combine(root, "Assets", "Scripts", "ServerShared", "Zone.cs"), - Path.Combine(root, "Assets", "Scripts", "ServerShared", "Agents", "States", "MoveTo.cs"), - Path.Combine(root, "Assets", "Scripts", "ServerShared", "Agents", "Tasks", "PatrolOrbits.cs"), - Path.Combine(root, "Assets", "Scripts", "ServerShared", "Agents", "Tasks", "StationTowing.cs") - }; - - var forbiddenSymbols = new[] - { - "public Guid Orbit { get; set; }", - "Guid[] Circuit", - "public Guid OrbitParent", - "Select(x => x.Key)" - }; - - var hits = checkedFiles - .Where(File.Exists) - .SelectMany(path => File.ReadLines(path) - .Select((line, index) => new { Path = path, LineNumber = index + 1, Line = line })) - .Where(line => forbiddenSymbols.Any(symbol => line.Line.Contains(symbol, StringComparison.Ordinal))) - .Select(line => $"{Path.GetRelativePath(root, line.Path)}:{line.LineNumber}: {line.Line.Trim()}") - .ToArray(); - - if (hits.Length > 0) - { - throw new InvalidOperationException( - "Orbit-targeting agent tasks and move states must stay on typed OrbitKey surfaces, with Zone owning orbit-key resolution: " + - string.Join("; ", hits)); - } - - var zoneSourcePath = Path.Combine(root, "Assets", "Scripts", "ServerShared", "Zone.cs"); - var zoneSource = File.Exists(zoneSourcePath) - ? File.ReadAllText(zoneSourcePath) - : throw new InvalidOperationException("Cannot verify orbit-key task ownership; Zone source is missing."); - var requiredZoneSymbols = new[] - { - "public bool TryGetOrbit(string orbitKey, out Orbit orbit)", - "public float2 GetOrbitPosition(string orbitKey)" - }; - var missingZoneSymbols = requiredZoneSymbols - .Where(symbol => !zoneSource.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (missingZoneSymbols.Length > 0) - { - throw new InvalidOperationException( - "Zone must own typed orbit-key parsing and resolution for agent/runtime orbit movement: " + - string.Join(", ", missingZoneSymbols)); - } - - var requiredTaskSymbols = new Dictionary - { - [Path.Combine(root, "Assets", "Scripts", "ServerShared", "Agents", "States", "MoveTo.cs")] = new[] - { - "public string OrbitKey { get; set; } = \"\";", - "_agent.Ship.Zone.GetOrbitPosition(OrbitKey)" - }, - [Path.Combine(root, "Assets", "Scripts", "ServerShared", "Agents", "Tasks", "PatrolOrbits.cs")] = new[] - { - "public string[] Circuit = Array.Empty();", - "public string CurrentTarget", - "patrolMoveState.OrbitKey = CurrentTarget" - }, - [Path.Combine(root, "Assets", "Scripts", "ServerShared", "Agents", "Tasks", "StationTowing.cs")] = new[] - { - "public string OrbitParentKey = \"\";" - } - }; - var missingTaskSymbols = requiredTaskSymbols - .Where(pair => !File.Exists(pair.Key) || pair.Value.Any(symbol => !File.ReadAllText(pair.Key).Contains(symbol, StringComparison.Ordinal))) - .SelectMany(pair => - { - var text = File.Exists(pair.Key) ? File.ReadAllText(pair.Key) : ""; - return pair.Value - .Where(symbol => !text.Contains(symbol, StringComparison.Ordinal)) - .Select(symbol => $"{Path.GetRelativePath(root, pair.Key)}: missing {symbol}"); - }) - .ToArray(); - if (missingTaskSymbols.Length > 0) - { - throw new InvalidOperationException( - "Orbit-targeting agent tasks must retain typed orbit keys rather than raw GUIDs: " + - string.Join("; ", missingTaskSymbols)); - } -} - -static void RequireTypedAgentTaskKeys(string root) -{ - var checkedFiles = new[] - { - Path.Combine(root, "Assets", "Scripts", "ServerShared", "Agents", "Tasks", "AgentTask.cs"), - Path.Combine(root, "Assets", "Scripts", "ServerShared", "Agents", "Tasks", "Survey.cs"), - Path.Combine(root, "Assets", "Scripts", "ServerShared", "Agents", "Tasks", "Mining.cs") - }; - - var forbiddenSymbols = new[] - { - "public Guid Zone", - "List Planets", - "public Guid Asteroids" - }; - - var hits = checkedFiles - .Where(File.Exists) - .SelectMany(path => File.ReadLines(path) - .Select((line, index) => new { Path = path, LineNumber = index + 1, Line = line })) - .Where(line => forbiddenSymbols.Any(symbol => line.Line.Contains(symbol, StringComparison.Ordinal))) - .Select(line => $"{Path.GetRelativePath(root, line.Path)}:{line.LineNumber}: {line.Line.Trim()}") - .ToArray(); - - if (hits.Length > 0) - { - throw new InvalidOperationException( - "Agent task shells must retain typed zone/body keys rather than raw GUID fields: " + - string.Join("; ", hits)); - } - - var requiredSymbols = new Dictionary - { - [Path.Combine(root, "Assets", "Scripts", "ServerShared", "Agents", "Tasks", "AgentTask.cs")] = new[] - { - "public string ZoneKey = \"\";" - }, - [Path.Combine(root, "Assets", "Scripts", "ServerShared", "Agents", "Tasks", "Survey.cs")] = new[] - { - "public List PlanetBodyKeys;" - }, - [Path.Combine(root, "Assets", "Scripts", "ServerShared", "Agents", "Tasks", "Mining.cs")] = new[] - { - "public string AsteroidBeltBodyKey = \"\";" - } - }; - - var missing = requiredSymbols - .Where(entry => !File.Exists(entry.Key) || - entry.Value.Any(symbol => !File.ReadAllText(entry.Key).Contains(symbol, StringComparison.Ordinal))) - .SelectMany(entry => entry.Value - .Where(symbol => !File.Exists(entry.Key) || !File.ReadAllText(entry.Key).Contains(symbol, StringComparison.Ordinal)) - .Select(symbol => $"{Path.GetRelativePath(root, entry.Key)}: missing {symbol}")) - .ToArray(); - - if (missing.Length > 0) - { - throw new InvalidOperationException( - "Agent task shells must keep typed key placeholders on zone/body references: " + - string.Join("; ", missing)); - } -} - -static void RequireTypedOrbitalEntityOrbitKeys(string root) -{ - var checkedFiles = new[] - { - Path.Combine(root, "Assets", "Scripts", "ServerShared", "OrbitalEntity.cs"), - Path.Combine(root, "Assets", "Scripts", "ServerShared", "EntityConstructionBlueprintMaterializer.cs"), - Path.Combine(root, "Assets", "Scripts", "ServerShared", "LoadoutGenerator.cs"), - Path.Combine(root, "Assets", "Scripts", "ServerShared", "ZoneGenerator.cs"), - Path.Combine(root, "Assets", "Scripts", "Gameplay", "ActionGameManager.cs"), - Path.Combine(root, "Assets", "Scripts", "ServerShared", "Zone.cs") - }; - - var forbiddenSymbols = new[] - { - "public Guid OrbitId;", - "public Guid Orbit;", - "Orbit = orbital.OrbitId", - "blueprint.Orbit,", - "Zone.Orbits[orbital.OrbitId]", - "GetOrbitPosition(OrbitId)", - "GetOrbitVelocity(OrbitId)", - "new OrbitalEntity(ItemManager, null, hull, Guid.Empty" - }; - - var hits = checkedFiles - .Where(File.Exists) - .SelectMany(path => File.ReadLines(path) - .Select((line, index) => new { Path = path, LineNumber = index + 1, Line = line })) - .Where(line => forbiddenSymbols.Any(symbol => line.Line.Contains(symbol, StringComparison.Ordinal))) - .Select(line => $"{Path.GetRelativePath(root, line.Path)}:{line.LineNumber}: {line.Line.Trim()}") - .ToArray(); - - if (hits.Length > 0) - { - throw new InvalidOperationException( - "Orbital runtime entities and construction blueprints must retain typed OrbitKey state, with Zone owning orbit-key resolution: " + - string.Join("; ", hits)); - } - - var requiredSymbols = new Dictionary - { - [Path.Combine(root, "Assets", "Scripts", "ServerShared", "OrbitalEntity.cs")] = new[] - { - "public string OrbitKey = \"\";", - "OrbitKey = orbitKey ?? \"\";", - "Zone.GetOrbitPosition(OrbitKey)", - "Zone.GetOrbitVelocity(OrbitKey)" - }, - [Path.Combine(root, "Assets", "Scripts", "ServerShared", "EntityConstructionBlueprintMaterializer.cs")] = new[] - { - "OrbitKey = orbital.OrbitKey,", - "blueprint.OrbitKey", - "public string OrbitKey = \"\";" - }, - [Path.Combine(root, "Assets", "Scripts", "ServerShared", "LoadoutGenerator.cs")] = new[] - { - "new OrbitalEntity(ItemManager, null, hull, \"\", ItemManager.GameplaySettings.DefaultEntitySettings)" - }, - [Path.Combine(root, "Assets", "Scripts", "ServerShared", "ZoneGenerator.cs")] = new[] - { - "turret.OrbitKey = turretOrbit.OrbitKey;", - "station.OrbitKey = lagrangeOrbit.OrbitKey;" - }, - [Path.Combine(root, "Assets", "Scripts", "Gameplay", "AetheriaUnityCurrentEntityPresentation.cs")] = new[] - { - "AetheriaRuntimeZoneRenderDocument zoneRender", - "zoneRender?.BodyPoses ?? Array.Empty()", - "string.Equals(body.OrbitKey ?? \"\", orbital.OrbitKey, StringComparison.Ordinal)", - "string.Equals(body.OrbitKey ?? \"\", parentOrbitKey, StringComparison.Ordinal)" - }, - [Path.Combine(root, "Assets", "Scripts", "ServerShared", "Zone.cs")] = new[] - { - "public float2 GetOrbitVelocity(string orbitKey)" - } - }; - - var missingSymbols = requiredSymbols - .Where(pair => !File.Exists(pair.Key) || pair.Value.Any(symbol => !File.ReadAllText(pair.Key).Contains(symbol, StringComparison.Ordinal))) - .SelectMany(pair => - { - var text = File.Exists(pair.Key) ? File.ReadAllText(pair.Key) : ""; - return pair.Value - .Where(symbol => !text.Contains(symbol, StringComparison.Ordinal)) - .Select(symbol => $"{Path.GetRelativePath(root, pair.Key)}: missing {symbol}"); - }) - .ToArray(); - - if (missingSymbols.Length > 0) - { - throw new InvalidOperationException( - "Orbital runtime seam must stay on typed orbit keys through the entity owner, blueprint projector, and docking path: " + - string.Join("; ", missingSymbols)); - } -} - -static void RequireTypedOrbitConsumerKeys(string root) -{ - var checkedFiles = new[] - { - Path.Combine(root, "Assets", "Scripts", "Gameplay", "ActionGameManager.cs"), - Path.Combine(root, "Assets", "Scripts", "Zone Display", "ZoneRenderer.cs"), - Path.Combine(root, "Assets", "Scripts", "ServerShared", "Behaviors", "ResourceScanner.cs") - }; - - var forbiddenSymbols = new[] - { - "planet => planet.OrbitId == followOrbit", - "planet => planet.OrbitId == rootOrbit", - "body => body.OrbitId == orbit.ID", - "body => body.Orbit == orbit.ID", - "GetOrbitPosition(planetInstance.OrbitId)", - "Zone.Orbits[planetInstance.OrbitId].Parent", - "zone.Orbits[belt.Orbit]", - "GetOrbitPosition(planet.OrbitId)" - }; - - var hits = checkedFiles - .Where(File.Exists) - .SelectMany(path => File.ReadLines(path) - .Select((line, index) => new { Path = path, LineNumber = index + 1, Line = line })) - .Where(line => forbiddenSymbols.Any(symbol => line.Line.Contains(symbol, StringComparison.Ordinal))) - .Select(line => $"{Path.GetRelativePath(root, line.Path)}:{line.LineNumber}: {line.Line.Trim()}") - .ToArray(); - - if (hits.Length > 0) - { - throw new InvalidOperationException( - "Orbit consumer edges must read typed OrbitKey surfaces rather than wrapper GUID fields in gameplay, renderer, and sensor code: " + - string.Join("; ", hits)); - } - - var requiredSymbols = new Dictionary - { - [Path.Combine(root, "Assets", "Scripts", "Zone Display", "ZoneRenderer.cs")] = new[] - { - "private readonly List _daemonBodyViews", - "private void SyncDaemonBodyViews()", - "private IReadOnlyList _zoneRenderBodies", - "_zoneRenderBodies = render?.Bodies ?? Array.Empty();", - "foreach (var bodyPose in _zoneRenderBodyPoses ?? Array.Empty())", - "ResolveDaemonRenderViewport()", - "private AetheriaRuntimeXzRect ResolveDaemonRenderViewport()", - "private void UnloadBodyView(string bodyKey)", - "foreach (var bodyView in _daemonBodyViews)", - "foreach (var pose in _daemonBodyPoses)", - "beltPosesByBodyKey.TryGetValue(bodyKey, out var beltPose)", - "_daemonBodyPosesByBodyKey.TryGetValue(planet.Key, out var pose)", - "var p = new float2((float)pose.CenterX, (float)pose.CenterZ);", - "var parent = new float2((float)pose.ParentCenterX, (float)pose.ParentCenterZ);" - }, - [Path.Combine(root, "Assets", "Scripts", "ServerShared", "Behaviors", "ResourceScanner.cs")] = new[] - { - "Entity.Zone.GetOrbitPosition(planet.OrbitKey)" - } - }; - - var missingSymbols = requiredSymbols - .Where(pair => !File.Exists(pair.Key) || pair.Value.Any(symbol => !File.ReadAllText(pair.Key).Contains(symbol, StringComparison.Ordinal))) - .SelectMany(pair => - { - var text = File.Exists(pair.Key) ? File.ReadAllText(pair.Key) : ""; - return pair.Value - .Where(symbol => !text.Contains(symbol, StringComparison.Ordinal)) - .Select(symbol => $"{Path.GetRelativePath(root, pair.Key)}: missing {symbol}"); - }) - .ToArray(); - - if (missingSymbols.Length > 0) - { - throw new InvalidOperationException( - "Orbit consumer edges must stay keyed at the public runtime wrapper surface: " + - string.Join("; ", missingSymbols)); - } -} - -static void RequireKeyedOrbitRuntimeWrappers(string root) -{ - var zoneSourcePath = Path.Combine(root, "Assets", "Scripts", "ServerShared", "Zone.cs"); - var zoneSource = File.Exists(zoneSourcePath) - ? File.ReadAllText(zoneSourcePath) - : throw new InvalidOperationException("Cannot verify keyed orbit runtime wrappers; Zone source is missing."); - - var forbiddenSymbols = new[] - { - "public Guid OrbitId { get; }", - "public Guid Orbit { get; }", - "public Guid ID { get; }", - "OrbitId = data.Orbit;", - "Orbit = data.Orbit;", - "AsteroidBelts[belt.ID] = new AsteroidBelt(belt);", - "var orbit = Orbits[belt.Orbit];" - }; - var hits = forbiddenSymbols - .Where(symbol => zoneSource.Contains(symbol, StringComparison.Ordinal)) - .Select(symbol => $"Assets/Scripts/ServerShared/Zone.cs: contains {symbol}") - .ToArray(); - if (hits.Length > 0) - { - throw new InvalidOperationException( - "Planet and asteroid-belt runtime wrappers must not publish wrapper orbit GUID fields once keyed/object orbit surfaces exist: " + - string.Join("; ", hits)); - } - - var requiredSymbols = new[] - { - "public Orbit Orbit;", - "public Orbit Orbit { get; }", - "var runtimeBelt = new AsteroidBelt(belt, Orbits[belt.OrbitKey]);", - "AsteroidBelts[runtimeBelt.BodyKey] = runtimeBelt;", - "belt.NewOrbitPosition = GetOrbitPosition(belt.Orbit.ParentOrbitKey);", - "public AsteroidBelt(AsteroidBeltConstructionData data, Orbit orbit)", - "Orbit = orbit;" - }; - var missingSymbols = requiredSymbols - .Where(symbol => !zoneSource.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (missingSymbols.Length > 0) - { - throw new InvalidOperationException( - "Planet and asteroid-belt runtime wrappers must keep orbit authority on Orbit objects plus OrbitKey surfaces: " + - string.Join(", ", missingSymbols)); - } -} - -static void RequireTypedFactionShellLinks(string root) -{ - var checkedFiles = new[] - { - Path.Combine(root, "Assets", "Scripts", "ServerShared", "Corporations.cs"), - Path.Combine(root, "Assets", "Scripts", "ServerShared", "Galaxy.cs") - }; - - var forbiddenSymbols = new[] - { - "Guid GeonameFile", - "Guid BossHull", - "GeonameFile =", - "BossHull =", - "ParseOptionalLegacyId", - "GeonameFileLegacyId", - "BossHullLegacyId", - ".BossHull != Guid.Empty", - "public Guid ID", - "ID = ParseLegacyId", - "ParseLegacyId(corporation.LegacyId" - }; - - var hits = checkedFiles - .Where(File.Exists) - .SelectMany(path => File.ReadLines(path) - .Select((line, index) => new { Path = path, LineNumber = index + 1, Line = line })) - .Where(line => forbiddenSymbols.Any(symbol => line.Line.Contains(symbol, StringComparison.Ordinal))) - .Select(line => $"{Path.GetRelativePath(root, line.Path)}:{line.LineNumber}: {line.Line.Trim()}") - .ToArray(); - - if (hits.Length > 0) - { - throw new InvalidOperationException( - "Temporary Faction shell links must use typed key fields, not geoname/boss-hull legacy GUID projections: " + - string.Join("; ", hits)); - } -} - -static void RequireNativeZoneKeyResolution(string root) -{ - var zoneSourcePath = Path.Combine(root, "Assets", "Scripts", "ServerShared", "Zone.cs"); - var zoneSource = File.Exists(zoneSourcePath) - ? File.ReadAllText(zoneSourcePath) - : throw new InvalidOperationException("Cannot verify native Zone key resolution; Zone source is missing."); - - var forbiddenSymbols = new[] - { - "public static Guid ParseOrbitGuid(string orbitKey)", - "public static Guid ParseBodyGuid(string bodyKey)", - "PlanetInstances.TryGetValue(ParseBodyGuid(bodyKey), out planet)", - "AsteroidBelts.TryGetValue(ParseBodyGuid(bodyKey), out belt)", - "Orbits.TryGetValue(ParseOrbitGuid(orbitKey), out orbit)", - "return GetOrbitPosition(ParseOrbitGuid(orbitKey));", - "return GetOrbitVelocity(ParseOrbitGuid(orbitKey));", - "public float2 GetOrbitPosition(Guid orbitID)", - "public float2 GetOrbitVelocity(Guid orbit)", - "public string GetBodyKey(Guid bodyId)", - "private readonly Dictionary _planetsById = new Dictionary();", - "private readonly Dictionary _asteroidBeltsById = new Dictionary();" - }; - var hits = forbiddenSymbols - .Where(symbol => zoneSource.Contains(symbol, StringComparison.Ordinal)) - .Select(symbol => $"Assets/Scripts/ServerShared/Zone.cs: contains {symbol}") - .ToArray(); - if (hits.Length > 0) - { - throw new InvalidOperationException( - "Zone key-facing APIs must resolve typed body/orbit keys natively instead of reparsing GUIDs from those keys: " + - string.Join("; ", hits)); - } - - var requiredSymbols = new[] - { - "public Dictionary PlanetInstances = new Dictionary(StringComparer.Ordinal);", - "public Dictionary Orbits = new Dictionary(StringComparer.Ordinal);", - "public Dictionary AsteroidBelts = new Dictionary(StringComparer.Ordinal);", - "PlanetInstances[runtimeSun.BodyKey] = runtimeSun;", - "PlanetInstances[runtimeGas.BodyKey] = runtimeGas;", - "PlanetInstances[runtimePlanet.BodyKey] = runtimePlanet;", - "AsteroidBelts[runtimeBelt.BodyKey] = runtimeBelt;", - "Orbits[runtimeOrbit.OrbitKey] = runtimeOrbit;", - "return PlanetInstances.TryGetValue(bodyKey ?? \"\", out planet);", - "return AsteroidBelts.TryGetValue(bodyKey ?? \"\", out belt);", - "return Orbits.TryGetValue(orbitKey ?? \"\", out orbit);", - "var parentPosition = string.IsNullOrWhiteSpace(orbit.ParentOrbitKey)", - ": GetOrbitPosition(orbit.ParentOrbitKey);", - "return TryGetOrbit(orbitKey, out var orbit) ? orbit.Velocity : float2.zero;", - "var runtimeBelt = new AsteroidBelt(belt, Orbits[belt.OrbitKey]);", - "var runtimeSun = new Sun(settings, sun, Orbits[planet.OrbitKey]);", - "var runtimeGas = new GasGiant(settings, gas, Orbits[planet.OrbitKey]);", - "var runtimePlanet = new Planet(settings, planet, Orbits[planet.OrbitKey]);", - "BodyKey = data.BodyKey ?? \"\";", - "OrbitKey = data.OrbitKey ?? \"\";", - "ParentOrbitKey = data.ParentOrbitKey ?? \"\";" - }; - var missingSymbols = requiredSymbols - .Where(symbol => !zoneSource.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (missingSymbols.Length > 0) - { - throw new InvalidOperationException( - "Zone must keep native key-indexed runtime lookup tables for body and orbit resolution: " + - string.Join(", ", missingSymbols)); - } -} - -static void RequireTypedZoneRuntimeCollections(string root) -{ - var zoneSourcePath = Path.Combine(root, "Assets", "Scripts", "ServerShared", "Zone.cs"); - var zoneRendererPath = Path.Combine(root, "Assets", "Scripts", "Zone Display", "ZoneRenderer.cs"); - var zoneSource = File.Exists(zoneSourcePath) - ? File.ReadAllText(zoneSourcePath) - : throw new InvalidOperationException("Cannot verify typed Zone runtime collections; Zone source is missing."); - var zoneRenderer = File.Exists(zoneRendererPath) - ? File.ReadAllText(zoneRendererPath) - : throw new InvalidOperationException("Cannot verify typed Zone runtime collections; ZoneRenderer source is missing."); - - var forbiddenZoneSymbols = new[] - { - "public Dictionary PlanetInstances", - "public Dictionary Orbits", - "public Dictionary AsteroidBelts", - "private HashSet _updatedOrbits", - "orbit.Value.Position = GetOrbitPosition(orbit.Key);", - "public int QueryGravityInfluenceBrushes(", - "AetheriaGravityInfluenceBrush" - }; - var zoneHits = forbiddenZoneSymbols - .Where(symbol => zoneSource.Contains(symbol, StringComparison.Ordinal)) - .Select(symbol => $"Assets/Scripts/ServerShared/Zone.cs: contains {symbol}"); - - var forbiddenRendererSymbols = new[] - { - "public Dictionary Planets", - "private Dictionary _beltObjects", - "private Dictionary _beltMeshes", - "private Dictionary _beltMatrices", - "_beltMeshes[runtimeBelt.ID] =", - "_beltObjects[runtimeBelt.ID] =", - "Planets[planetInstance.ID] = planet;", - "public Dictionary Planets", - "private readonly List _visibleGravityBrushes", - "public IReadOnlyList VisibleGravityBrushes", - "Zone.QueryGravityInfluenceBrushes(viewportMin, viewportMax, _visibleGravityBrushes);" - }; - var rendererHits = forbiddenRendererSymbols - .Where(symbol => zoneRenderer.Contains(symbol, StringComparison.Ordinal)) - .Select(symbol => $"Assets/Scripts/Zone Display/ZoneRenderer.cs: contains {symbol}"); - - var hits = zoneHits.Concat(rendererHits).ToArray(); - if (hits.Length > 0) - { - throw new InvalidOperationException( - "Zone runtime collections and renderer caches must keep typed OrbitKey/BodyKey ownership rather than Guid dictionary keys: " + - string.Join("; ", hits)); - } - - var requiredZoneSymbols = new[] - { - "public Dictionary PlanetInstances = new Dictionary(StringComparer.Ordinal);", - "public Dictionary Orbits = new Dictionary(StringComparer.Ordinal);", - "public Dictionary AsteroidBelts = new Dictionary(StringComparer.Ordinal);", - "private HashSet _updatedOrbits = new HashSet(StringComparer.Ordinal);", - "foreach (var orbit in Orbits.Values)", - "orbit.Position = GetOrbitPosition(orbit.OrbitKey);" - }; - var missingZoneSymbols = requiredZoneSymbols - .Where(symbol => !zoneSource.Contains(symbol, StringComparison.Ordinal)) - .Select(symbol => $"Assets/Scripts/ServerShared/Zone.cs: missing {symbol}"); - - var requiredRendererSymbols = new[] - { - "private readonly Dictionary _bodyViewsByBodyKey = new Dictionary(StringComparer.Ordinal);", - "public bool TryGetBodyView(string bodyKey, out PlanetObject bodyView)", - "private Dictionary _beltObjects = new Dictionary(StringComparer.Ordinal);", - "private Dictionary _beltMeshes = new Dictionary(StringComparer.Ordinal);", - "private Dictionary _beltMatrices = new Dictionary(StringComparer.Ordinal);", - "_beltMeshes[bodyKey] = meshes.ToArray();", - "_beltObjects[bodyKey] = belt;", - "_bodyViewsByBodyKey[bodyKey] = planet;" - }; - var missingRendererSymbols = requiredRendererSymbols - .Where(symbol => !zoneRenderer.Contains(symbol, StringComparison.Ordinal)) - .Select(symbol => $"Assets/Scripts/Zone Display/ZoneRenderer.cs: missing {symbol}"); - - var missing = missingZoneSymbols.Concat(missingRendererSymbols).ToArray(); - if (missing.Length > 0) - { - throw new InvalidOperationException( - "Zone runtime collections and renderer caches must expose typed keyed ownership end to end: " + - string.Join("; ", missing)); - } -} - -static void RequireDaemonRenderQueryAuthority(string root) -{ - var queryPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeDaemonRenderQueries.cs"); - var querySource = File.Exists(queryPath) - ? File.ReadAllText(queryPath) - : throw new InvalidOperationException("Daemon render queries must live in the shared Aetheria runtime package, not in Unity ZoneRenderer."); - if (querySource.Contains("Unity XZ viewport", StringComparison.Ordinal) || - querySource.Contains("through Unity axes", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Shared daemon render query types must use domain projection vocabulary, not runtime-specific Unity axis copy."); - } - - var packageSnapshotPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeSnapshotDocuments.cs"); - var packageSnapshot = File.Exists(packageSnapshotPath) - ? File.ReadAllText(packageSnapshotPath) - : throw new InvalidOperationException("Cannot verify daemon render query authority; package snapshot documents are missing."); - - var indirectRendererPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "AetheriaDaemonIndirectRenderer.cs"); - var indirectRenderer = File.Exists(indirectRendererPath) - ? File.ReadAllText(indirectRendererPath) - : throw new InvalidOperationException("Cannot verify daemon render query authority; AetheriaDaemonIndirectRenderer.cs is missing."); - - var zoneRendererPath = Path.Combine(root, "Assets", "Scripts", "Zone Display", "ZoneRenderer.cs"); - var zoneRenderer = File.Exists(zoneRendererPath) - ? File.ReadAllText(zoneRendererPath) - : throw new InvalidOperationException("Cannot verify daemon render query authority; ZoneRenderer.cs is missing."); - var actionGameManagerPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "ActionGameManager.cs"); - var actionGameManager = File.Exists(actionGameManagerPath) - ? File.ReadAllText(actionGameManagerPath) - : throw new InvalidOperationException("Cannot verify daemon render query authority; ActionGameManager.cs is missing."); - var targetPresentationPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "AetheriaUnityTargetPresentation.cs"); - var targetPresentation = File.Exists(targetPresentationPath) - ? File.ReadAllText(targetPresentationPath) - : throw new InvalidOperationException("Cannot verify daemon render query authority; AetheriaUnityTargetPresentation.cs is missing."); - var pilotFrameControllerPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "AetheriaUnityPilotFrameController.cs"); - var pilotFrameController = File.Exists(pilotFrameControllerPath) - ? File.ReadAllText(pilotFrameControllerPath) - : throw new InvalidOperationException("Cannot verify daemon render query authority; AetheriaUnityPilotFrameController.cs is missing."); - var gameplayInputShellPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "AetheriaUnityGameplayInputShell.cs"); - var gameplayInputShell = File.Exists(gameplayInputShellPath) - ? File.ReadAllText(gameplayInputShellPath) - : throw new InvalidOperationException("Cannot verify daemon render query authority; AetheriaUnityGameplayInputShell.cs is missing."); - var renderSettingsBridgePath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "AetheriaUnityRenderSettingsBridge.cs"); - var cockpitHudShellPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "AetheriaUnityCockpitHudShell.cs"); - var cockpitHudShell = File.Exists(cockpitHudShellPath) - ? File.ReadAllText(cockpitHudShellPath) - : throw new InvalidOperationException("Cannot verify daemon render query authority; AetheriaUnityCockpitHudShell.cs is missing."); - var gameplayBootShellPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "AetheriaUnityGameplayBootShell.cs"); - var gameplayBootShell = File.Exists(gameplayBootShellPath) - ? File.ReadAllText(gameplayBootShellPath) - : throw new InvalidOperationException("Cannot verify daemon render query authority; AetheriaUnityGameplayBootShell.cs is missing."); - var unityRenderPresentation = actionGameManager + "\n" + targetPresentation + "\n" + pilotFrameController + "\n" + gameplayInputShell + "\n" + cockpitHudShell + "\n" + gameplayBootShell; - - var canonicalSnapshotPath = Path.Combine(root, "Aetheria.State", "Documents", "AetheriaRuntimeStateDocuments.cs"); - var canonicalSnapshot = File.Exists(canonicalSnapshotPath) - ? File.ReadAllText(canonicalSnapshotPath) - : throw new InvalidOperationException("Cannot verify daemon render query authority; canonical runtime state documents are missing."); - - var requiredQuerySymbols = new[] - { - "public readonly struct AetheriaRuntimeDaemonRenderSettings", - "public readonly struct AetheriaRuntimeExponentialCurve", - "public double ConvergenceMinimumDistance { get; }", - "public double HypothermiaTemperature { get; }", - "public double HeatstrokeTemperature { get; }", - "public double SevereHeatstrokeRiskThreshold { get; }", - "public double TargetDetectionInfoThreshold { get; }", - "public double LockIndicatorNoiseAmplitude { get; }", - "public double HeatstrokePhasingFloor { get; }", - "public double HeatstrokePhasingFrequency { get; }", - "public double TargetSpottedBlinkFrequency { get; }", - "public double TargetSpottedBlinkOffset { get; }", - "public IReadOnlyList MinimapZoomLevels { get; }", - "public int DefaultMinimapZoom { get; }", - "public double WormholeDistanceRatio { get; }", - "public double DefaultViewDistance { get; }", - "public double MinimapIconScale { get; }", - "public double MinimapAsteroidSize { get; }", - "public AetheriaRuntimeExponentialCurve BodyIconSizeCurve { get; }", - "public double MinimapZoneGravityRange { get; }", - "public double AsteroidVerticalOffset { get; }", - "public double PlanetRotationSpeed { get; }", - "public double ZoneBoundaryPower { get; }", - "public double ZoneBoundaryDepth { get; }", - "public int AsteroidMeshCount { get; }", - "public AetheriaRuntimeExponentialCurve BodyRadiusCurve { get; }", - "public AetheriaRuntimeExponentialCurve LightRadiusCurve { get; }", - "public AetheriaRuntimeExponentialCurve GravityWaveFrequencyCurve { get; }", - "public int ResolveDefaultMinimapZoomIndex()", - "public int ResolveNextMinimapZoomIndex(int currentIndex)", - "public double ResolveMinimapDistance(int zoomIndex)", - "public double ResolveDefaultMinimapDistance()", - "public double ResolveMinimapIconSize(double minimapDistance)", - "public double ResolveBodyIconSize(double mass)", - "public double ResolveBodyRadius(double mass)", - "public double ResolveLightRadius(double mass)", - "public double ResolveGravityWaveFrequency(double mass)", - "public double NormalizeThermalRisk(double temperature)", - "public double NormalizeHeatstrokePost(double heatstroke)", - "public double NormalizeSevereHeatstrokePost(double heatstroke)", - "public double ResolveSevereHeatstrokePostWeight(double heatstroke, double timeSeconds)", - "public double NormalizeDetectionProgress(double infoGathered)", - "public bool ResolveTargetSpottedFillEnabled(double infoGathered, double timeSeconds)", - "public double NormalizeTargetVisibilityFill(double infoGathered)", - "public double NormalizeVisibilityToTargetFill(double infoGathered)", - "public double NormalizeTargetStatusFill(double normalizedValue)", - "public double ResolveLockIndicatorNoiseAmplitude(double lockProgress)", - "public double ResolveLockIndicatorNoiseFrequency(double lockProgress)", - "public double ResolveLockSpinSpeed(double lockProgress)", - "public AetheriaRuntimeExponentialCurve TemperatureEmissionCurve { get; }", - "public AetheriaRuntimeExponentialLerp LockIndicatorFrequency { get; }", - "public AetheriaRuntimeExponentialLerp LockSpinSpeed { get; }", - "public static AetheriaRuntimeGravityInfluenceBrush[] QueryGravityInfluences(", - "AetheriaRuntimeZoneSnapshotCommit? zone", - "public static int QueryGravityInfluences(", - "List brushes", - "public static AetheriaRuntimeDaemonRenderGroupDocument[] QueryRenderGroups(", - "AetheriaRuntimeDaemonSoaViewIndex? index", - "AetheriaRuntimeXzRect", - "IntersectsCircle(viewport, center.x, center.z, radius)", - "IntersectsBounds(group, minX, minY, minZ, maxX, maxY, maxZ)", - "TryResolveBodyCenter(body, orbitPositions, out var center)", - "public static double EvaluateGravityTerrainHeight(", - "public static double ResolveZoneRenderRadius(", - "public static AetheriaRuntimeGravityTerrainBand QueryGravityTerrainBand(", - "zone.GravityTerrainRadius", - "brush.WaveFrequency", - "public static AetheriaRuntimeDaemonBodyPose[] QueryBodyPoses(", - "public static int QueryBodyPoses(", - "new AetheriaRuntimeDaemonBodyPose(", - "AetheriaRuntimeDaemonBodyView", - "public static AetheriaRuntimeDaemonBodyView[] QueryBodyViews(", - "AetheriaRuntimeXzRect viewport)", - "public static int QueryBodyViews(", - "IntersectsCircle(", - "Math.Max(ResolveGravityRadius(body), ResolveWaveRadius(body))", - "AetheriaRuntimeDaemonAsteroidBeltPose", - "public static AetheriaRuntimeDaemonAsteroidBeltPose[] QueryAsteroidBeltPoses(", - "public static int QueryAsteroidBeltPoses(", - "AetheriaRuntimeDaemonAsteroidInstancePose", - "public static AetheriaRuntimeDaemonAsteroidInstancePose[] QueryAsteroidInstancePoses(", - "public static int QueryAsteroidInstancePoses(", - "TryResolveAsteroidBeltCenter(body, orbitPositions, orbits, out var center)", - "ResolveAsteroidBeltRadius(body)", - "AetheriaRuntimeDaemonCompassMarker", - "public static AetheriaRuntimeDaemonCompassMarker[] QueryCompassMarkers(", - "public static int QueryCompassMarkers(", - "public static int[] QueryVisibleEntityIndices(", - "public static int QueryVisibleEntityIndices(", - "public static int[] QueryPresentationEntityIndices(", - "public static int QueryPresentationEntityIndices(", - "TryParseEntityIndex(run?.CurrentEntityKey)", - "ContainsPoint(viewport, entity.PositionX, entity.PositionZ)", - "AetheriaRuntimeDaemonEntityContact", - "public static bool TryQueryEntityContact(", - "public static AetheriaRuntimeDaemonEntityContact[] QueryEntityContacts(", - "public static int QueryEntityContacts(", - "AetheriaRuntimeDaemonEntityTarget", - "public static bool TryQueryEntityTarget(", - "AetheriaRuntimeDaemonWormholeExit", - "public static AetheriaRuntimeDaemonWormholeExit[] QueryWormholeExits(", - "public static int QueryWormholeExits(", - "BuildZoneMap(run)", - "BuildEntityMap(zone)" - }; - - var missingQuerySymbols = requiredQuerySymbols - .Where(symbol => !querySource.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (missingQuerySymbols.Length > 0) - { - throw new InvalidOperationException( - "Daemon render query API must expose gravity-in-rect and render-group bounds queries over daemon snapshot/SoA data: " + - string.Join(", ", missingQuerySymbols)); - } - - var requiredSnapshotSymbols = new[] - { - "public double GravityInfluenceCenterX", - "public double GravityInfluenceCenterZ", - "public double GravityInfluenceRadius", - "public double GravityWellDepth", - "public double GravityWaveRadius", - "public double GravityWaveDepth", - "public double GravityWaveSpeed", - "public double GravityTerrainRadius", - "public double GravityTerrainDepth", - "public double GravityTerrainDepthExponent", - "public double GravityTerrainBoundaryFog", - "public double GravityTerrainWaveFrequency", - "public double SimulationTimeSeconds" - }; - - var missingPackageSnapshotSymbols = requiredSnapshotSymbols - .Where(symbol => !packageSnapshot.Contains(symbol, StringComparison.Ordinal)) - .Select(symbol => $"Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeSnapshotDocuments.cs: missing {symbol}"); - var missingCanonicalSnapshotSymbols = requiredSnapshotSymbols - .Where(symbol => !canonicalSnapshot.Contains(symbol, StringComparison.Ordinal)) - .Select(symbol => $"Aetheria.State/Documents/AetheriaRuntimeStateDocuments.cs: missing {symbol}"); - var missingSnapshotSymbols = missingPackageSnapshotSymbols.Concat(missingCanonicalSnapshotSymbols).ToArray(); - if (missingSnapshotSymbols.Length > 0) - { - throw new InvalidOperationException( - "Daemon body snapshots must publish resolved gravity influence fields so render clients do not reconstruct Unity gravity state: " + - string.Join("; ", missingSnapshotSymbols)); - } - - var requiredRendererSymbols = new[] - { - "private readonly List _visibleRenderGroups", - "AetheriaRuntimeDaemonRenderQueries.QueryRenderGroups(", - "TryGetCameraQueryBounds(out var min, out var max)", - "return index.RenderGroups;" - }; - var missingRendererSymbols = requiredRendererSymbols - .Where(symbol => !indirectRenderer.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (missingRendererSymbols.Length > 0) - { - throw new InvalidOperationException( - "Unity's daemon indirect renderer must query daemon SoA render groups by camera bounds before lowering draw calls: " + - string.Join(", ", missingRendererSymbols)); - } - - if (zoneRenderer.Contains("Zone.GetHeight(", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "ZoneRenderer must evaluate gravity terrain from daemon render queries instead of the mirrored Unity Zone height evaluator."); - } - - if (zoneRenderer.Contains("public Zone Zone", StringComparison.Ordinal) || - zoneRenderer.Contains("public void LoadZone(", StringComparison.Ordinal) || - zoneRenderer.Contains("public Dictionary Planets", StringComparison.Ordinal) || - zoneRenderer.Contains("public ItemManager ItemManager", StringComparison.Ordinal) || - actionGameManager.Contains("ZoneRenderer.ItemManager", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "ZoneRenderer must expose daemon-view rendering concepts instead of public mirrored Zone/ItemManager ownership."); - } - - if (zoneRenderer.Contains("ItemManager.Log(", StringComparison.Ordinal) || - zoneRenderer.Contains("ItemManager.Get", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "ZoneRenderer must not depend on ItemManager for renderer diagnostics or item projection."); - } - - if (zoneRenderer.Contains("Zone.PowerPulse(", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "ZoneRenderer must query daemon gravity terrain bands instead of calling mirrored Unity Zone gravity math."); - } - - if (zoneRenderer.Contains("Settings.PlanetSettings.GravityRadius.Evaluate(", StringComparison.Ordinal) || - zoneRenderer.Contains("Settings.PlanetSettings.GravityDepth.Evaluate(", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "ZoneRenderer must lower daemon-authored gravity influence radius/depth instead of reconstructing gravity brushes from Unity settings."); - } - - if (zoneRenderer.Contains("Settings.GameplaySettings.TargetDetectionInfoThreshold", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "ZoneRenderer must query daemon visibility through shared render settings instead of Unity GameplaySettings."); - } - - if (ContainsUnitySettingsMember(zoneRenderer, "MinimapZoomLevels") || - ContainsUnitySettingsMember(zoneRenderer, "DefaultMinimapZoom") || - ContainsUnitySettingsMember(zoneRenderer, "MinimapIconSize") || - ContainsUnitySettingsMember(zoneRenderer, "DefaultViewDistance") || - ContainsUnitySettingsMember(zoneRenderer, "MinimapAsteroidSize") || - ContainsUnitySettingsMember(zoneRenderer, "IconSize") || - ContainsUnitySettingsMember(zoneRenderer, "MinimapZoneGravityRange") || - ContainsUnitySettingsMember(zoneRenderer, "PlanetRotationSpeed") || - ContainsUnitySettingsMember(zoneRenderer, "AsteroidMeshCount")) - { - throw new InvalidOperationException( - "ZoneRenderer must initialize view/minimap presentation through shared daemon render settings instead of Unity GameSettings."); - } - - if (zoneRenderer.Contains(" Settings.WormholeDistanceRatio", StringComparison.Ordinal) || - zoneRenderer.Contains("(Settings.WormholeDistanceRatio", StringComparison.Ordinal) || - zoneRenderer.Contains(", Settings.WormholeDistanceRatio", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "ZoneRenderer must query daemon wormhole exits through shared render settings instead of Unity GameSettings."); - } - - if (zoneRenderer.Contains("Zone.GetOrbitPosition(", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "ZoneRenderer must position bodies from daemon render body poses instead of the mirrored Unity Zone orbit evaluator."); - } - - if (zoneRenderer.Contains("Zone.Time", StringComparison.Ordinal) || - zoneRenderer.Contains("Zone?.Time", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "ZoneRenderer must sample animation and terrain from daemon simulation time instead of mirrored Unity Zone time."); - } - - if (zoneRenderer.Contains("Zone.PlanetInstances[planet.Key]", StringComparison.Ordinal) || - zoneRenderer.Contains("zone.PlanetInstances.TryGetValue(pose.BodyKey, out var planet)", StringComparison.Ordinal) || - zoneRenderer.Contains("LoadPlanet(planet)", StringComparison.Ordinal) || - zoneRenderer.Contains("zone.AsteroidBelts.TryGetValue(body.BodyKey", StringComparison.Ordinal) || - zoneRenderer.Contains("LoadAsteroidBelt(belt)", StringComparison.Ordinal) || - zoneRenderer.Contains("new Wormhole", StringComparison.Ordinal) || - zoneRenderer.Contains("zone.Galaxy", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "ZoneRenderer frame updates must read body render state from daemon body poses instead of indexed mirrored Unity planets."); - } - - if (zoneRenderer.Contains("foreach (var orbit in zone.Orbits.Values)", StringComparison.Ordinal) || - zoneRenderer.Contains("FirstOrDefault(body => body.OrbitKey == orbit.OrbitKey)", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "ZoneRenderer must instantiate body views from daemon body poses keyed by body id instead of rebuilding a Unity orbit hierarchy."); - } - - if (zoneRenderer.Contains("belt.OrbitPosition", StringComparison.Ordinal) || - zoneRenderer.Contains("belt.Radius", StringComparison.Ordinal) || - zoneRenderer.Contains("belt.Transforms", StringComparison.Ordinal) || - zoneRenderer.Contains("foreach (var (key, belt) in Zone.AsteroidBelts)", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "ZoneRenderer frame updates must read asteroid belt and instance poses from daemon render queries instead of mirrored Unity asteroid belts."); - } - - if (zoneRenderer.Contains("_zoneSubscriptions", StringComparison.Ordinal) || - zoneRenderer.Contains("zone.Entities.ObserveAdd()", StringComparison.Ordinal) || - zoneRenderer.Contains("zone.Entities.ObserveRemove()", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "ZoneRenderer must reconcile rendered entities from daemon frame snapshots instead of subscribing to mirrored Unity entity collection mutations."); - } - - if (zoneRenderer.Contains("foreach (var entity in legacyEntityFacadeZone.Entities)", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "ZoneRenderer must use the daemon-indexed observed entity projection supplied by gameplay instead of enumerating Unity Zone.Entities."); - } - - if (zoneRenderer.Contains("_legacyEntityFacadeZone", StringComparison.Ordinal) || - zoneRenderer.Contains("_legacyEntityFacadeZone?.Radius", StringComparison.Ordinal) || - zoneRenderer.Contains("_legacyLootFacadeZone", StringComparison.Ordinal) || - zoneRenderer.Contains("Zone legacyLootFacadeZone", StringComparison.Ordinal) || - zoneRenderer.Contains("gridObject.Zone = ", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "ZoneRenderer must not keep Unity Zone facade handles; daemon snapshots and observed entity projections own renderer input."); - } - - if (zoneRenderer.Contains("PerspectiveEntity.EntityInfoGathered", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "ZoneRenderer must query daemon compass markers instead of reading mirrored Unity entity visibility state."); - } - - if (zoneRenderer.Contains("VisibleEntities", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "ZoneRenderer must query daemon visible entity indices instead of subscribing to mirrored Unity visibility collections."); - } - - if (zoneRenderer.Contains("AdjacentZones", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "ZoneRenderer must query daemon wormhole exits instead of enumerating mirrored GalaxyZone adjacency."); - } - - var requiredZoneRendererSymbols = new[] - { - "public AetheriaRuntimeDaemonRenderSettings RenderSettings { get; set; }", - "private string _daemonCurrentEntityKey = \"\";", - "private double _daemonSimulationTimeSeconds;", - "private readonly List _daemonBodyViews", - "private IReadOnlyList _zoneRenderBodies", - "private readonly List _daemonBodyPoses", - "private readonly Dictionary _daemonBodyPosesByBodyKey", - "private readonly Dictionary _bodyViewsByBodyKey", - "public bool TryGetBodyView(string bodyKey, out PlanetObject bodyView)", - "private readonly List _daemonAsteroidBeltPoses", - "private readonly List _visibleAsteroidInstancePoses", - "private readonly List _daemonCompassMarkers", - "private readonly Dictionary _daemonCompassMarkersByEntityIndex", - "private readonly Dictionary _daemonTargetRowsByEntityIndex", - "private readonly List _daemonContactRows", - "private IReadOnlyDictionary _observedEntitySnapshotsByDaemonIndex", - "private readonly List _daemonPresentationEntityIndices", - "private readonly HashSet _daemonPresentationEntityIndicesSet", - "private readonly List _daemonVisibleEntityIndices", - "private readonly HashSet _daemonVisibleEntityIndicesSet", - "private readonly HashSet _visibleDaemonEntityIndices", - "private AetheriaDaemonObserver _daemonObserver;", - "private readonly List _daemonWormholeExits", - "render?.WormholeExits ?? Array.Empty()", - "_daemonWormholeExits.Add(new AetheriaRuntimeDaemonWormholeExit(", - "public Dictionary WormholeInstances", - "public void LoadDaemonZoneView(", - "IReadOnlyDictionary observedEntitySnapshotsByDaemonIndex,", - "AetheriaRuntimeZoneRenderDocument render)", - "public BodySettingsCollection[] BodySettingsCollections;", - "public void ApplyZoneRender(AetheriaRuntimeZoneRenderDocument render)", - "_daemonCurrentEntityKey = render?.CurrentEntityKey ?? \"\";", - "_daemonSimulationTimeSeconds = render?.SimulationTimeSeconds ?? 0;", - "_zoneRenderBodies = render?.Bodies ?? Array.Empty();", - "_observedEntitySnapshotsByDaemonIndex = observedEntitySnapshotsByDaemonIndex;", - "render?.ZoneRenderRadius ?? 2000", - "private readonly HashSet _daemonVisibleBodyKeys", - "private void SyncDaemonBodyViews()", - "foreach (var bodyView in _daemonBodyViews)", - "if (bodyView.IsAsteroidBelt)", - "foreach (var bodyPose in _zoneRenderBodyPoses ?? Array.Empty())", - "ResolveDaemonRenderViewport()", - "private AetheriaRuntimeXzRect ResolveDaemonRenderViewport()", - "private static AetheriaRuntimeViewportBounds ToViewportBounds(AetheriaRuntimeXzRect viewport)", - "private void UnloadBodyView(string bodyKey)", - "foreach (var pose in _daemonBodyPoses)", - "LoadPlanet(body)", - "void LoadPlanet(AetheriaRuntimeBodySnapshotCommit body)", - "beltPosesByBodyKey.TryGetValue(bodyKey, out var beltPose)", - "LoadAsteroidBelt(beltPose)", - "void LoadAsteroidBelt(AetheriaRuntimeZoneRenderAsteroidBeltPose beltPose)", - "private void SyncDaemonEntityInstances()", - "TryCollectDaemonPresentationEntityIndicesFromSoa(viewport)", - "private bool TryCollectDaemonPresentationEntityIndicesFromSoa(AetheriaRuntimeXzRect viewport)", - "observer.LastRenderNativeView", - "!view.IsCreated || !view.HasEntityIndex", - "view.HasRenderVisibility && view.RenderVisibility[i] == 0", - "var position = view.Position[i];", - "var entityIndex = view.EntityIndex[i];", - "private AetheriaDaemonObserver ResolveDaemonObserver()", - "FindAnyObjectByType()", - "ResolveObjectsViewport(viewport)", - "private AetheriaRuntimeObjectsViewportDocument ResolveObjectsViewport(AetheriaRuntimeXzRect viewport)", - ".ZonePresentationDocuments(viewportBounds, \"unity-zone-renderer\")", - "_zonePresentationDocuments.CurrentObjectsViewport", - "foreach (var entity in objects?.Objects ?? Array.Empty())", - "_observedEntitySnapshotsByDaemonIndex.TryGetValue(entityIndex, out var entity)", - "Loading entity {entity.Name} from daemon presentation query", - "UnloadEntity(pair.Value.Entity)", - "private readonly Dictionary _entityInstancesByDaemonIndex", - "public IReadOnlyDictionary DaemonEntityInstances => _entityInstancesByDaemonIndex;", - "public bool TryGetEntityInstance(int daemonEntityIndex, out EntityInstance instance)", - "public bool TryGetEntityInstance(Entity entity, out EntityInstance instance)", - "public bool TryGetDaemonTargetDistance(int daemonEntityIndex, out float distance)", - "_daemonTargetRowsByEntityIndex.TryGetValue(daemonEntityIndex, out var target)", - "foreach (var entity in EntityInstances.Keys.ToArray())", - "_daemonBodyPoses.AddRange(_zoneRenderBodyPoses ?? Array.Empty());", - "_daemonAsteroidBeltPoses.AddRange(_zoneRenderAsteroidBeltPoses ?? Array.Empty());", - "foreach (var beltPose in _daemonAsteroidBeltPoses)", - "beltPose.InstancePoses ?? Array.Empty()", - "private void RefreshDaemonContactRows()", - "_zonePresentationDocuments.CurrentContacts", - "private void RefreshDaemonCompassMarkers()", - "private void RefreshDaemonVisibleEntityInstances()", - "PowerPulse(", - "RadialWaves(", - "RenderSettings.TargetDetectionInfoThreshold", - "RenderSettings.DefaultViewDistance", - "RenderSettings.MinimapAsteroidSize", - "RenderSettings.ResolveDefaultMinimapDistance()", - "RenderSettings.ResolveMinimapIconSize(value)", - "RenderSettings.ResolveBodyIconSize(mass)", - "RenderSettings.MinimapZoneGravityRange", - "RenderSettings.AsteroidVerticalOffset", - "RenderSettings.PlanetRotationSpeed", - "RenderSettings.ZoneBoundaryPower", - "RenderSettings.ZoneBoundaryDepth", - "RenderSettings.AsteroidMeshCount", - "RenderSettings.ResolveBodyRadius(mass)", - "RenderSettings.ResolveLightRadius(mass)", - "RenderSettings.ResolveGravityWaveFrequency(mass)", - "AddWormhole(exit)", - "public void AddWormhole(AetheriaRuntimeDaemonWormholeExit exit)", - "private double DaemonSimulationTimeSeconds => _daemonSimulationTimeSeconds;", - "_daemonCompassMarkersByEntityIndex.TryGetValue(entityInstance.DaemonEntityIndex", - "_daemonBodyPosesByBodyKey.TryGetValue(planet.Key, out var pose)", - "pose.GravityWaveSpeed" - }; - var missingZoneRendererSymbols = requiredZoneRendererSymbols - .Where(symbol => !zoneRenderer.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (missingZoneRendererSymbols.Length > 0) - { - throw new InvalidOperationException( - "ZoneRenderer must lower daemon-authored gravity terrain snapshots instead of recomputing render height through Zone: " + - string.Join(", ", missingZoneRendererSymbols)); - } - - if (zoneRenderer.Contains("public GameSettings Settings", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "ZoneRenderer still accepts the whole Unity GameSettings object instead of explicit renderer asset/tuning inputs."); - } - - if (zoneRenderer.Contains("foreach (var entitySnapshot in daemonZone?.Entities", StringComparison.Ordinal) || - zoneRenderer.Contains("Loading entity {entity.Name} from daemon entity snapshot", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "ZoneRenderer must query daemon presentation entities instead of loading every daemon entity snapshot into a mirrored Unity level."); - } - - var entityInstancePath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "EntityInstance.cs"); - var entityInstance = File.Exists(entityInstancePath) - ? File.ReadAllText(entityInstancePath) - : throw new InvalidOperationException("Cannot verify EntityInstance convergence authority; source file is missing."); - if (entityInstance.Contains("Entity.Target.Value != null ? Mathf.Max(Entity.TargetRange", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "EntityInstance convergence distance must read daemon target projection through ZoneRenderer instead of facade Entity.Target."); - } - - if (entityInstance.Contains("Entity.ItemManager.GameplaySettings", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "EntityInstance render code must use daemon render settings instead of drilling through the observed Entity ItemManager facade."); - } - - var requiredEntityInstanceSymbols = new[] - { - "ZoneRenderer.RenderSettings.TemperatureEmissionCurve.Evaluate(", - "ZoneRenderer.TryGetDaemonTargetDistance(DaemonEntityIndex, out var daemonTargetDistance)", - "Mathf.Max(daemonTargetDistance, (float)ZoneRenderer.RenderSettings.ConvergenceMinimumDistance)", - "LookAtPoint.position = transform.position + entityLookDirection * lookAtDistance;" - }; - - var missingEntityInstanceSymbols = requiredEntityInstanceSymbols - .Where(symbol => !entityInstance.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (missingEntityInstanceSymbols.Length > 0) - { - throw new InvalidOperationException( - "EntityInstance no longer resolves convergence from daemon target projection: " + - string.Join(", ", missingEntityInstanceSymbols)); - } - - var schematicDisplayPath = Path.Combine(root, "Assets", "Scripts", "UI", "HUD", "SchematicDisplay.cs"); - var schematicDisplay = File.Exists(schematicDisplayPath) - ? File.ReadAllText(schematicDisplayPath) - : throw new InvalidOperationException("Cannot verify HUD thermal presentation authority; SchematicDisplay.cs is missing."); - if (schematicDisplay.Contains("Settings.GameplaySettings.HypothermiaTemperature", StringComparison.Ordinal) || - schematicDisplay.Contains("Settings.GameplaySettings.HeatstrokeTemperature", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "SchematicDisplay must normalize cockpit thermal risk through shared daemon render settings instead of Unity GameSettings."); - } - - if (schematicDisplay.Contains("ActionGameManager.Instance.ZoneRenderer", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "SchematicDisplay must not reach through ActionGameManager.Instance.ZoneRenderer for HUD presentation settings."); - } - - var requiredSchematicHudSymbols = new[] - { - "CatalogSnapshot()", - "CurrentEntityHudStatus()", - "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", - "hud.Heatstroke", - "hud.Hypothermia", - "hud.Visibility", - "hud.HullDurabilityRatio" - }; - var missingSchematicHudSymbols = requiredSchematicHudSymbols - .Where(symbol => !schematicDisplay.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (missingSchematicHudSymbols.Length > 0) - { - throw new InvalidOperationException( - "SchematicDisplay no longer renders player HUD facts from the typed current-entity document: " + - string.Join(", ", missingSchematicHudSymbols)); - } - - var forbiddenSchematicHudSymbols = new[] - { - "CultMeshReactiveDocument", - "CultMeshReactiveDocument", - "CultMeshReactiveDocument", - ".ReactiveCatalogSnapshot(\"unity-schematic-display\")", - ".ReactivePlayerSettingsDocument(\"unity-schematic-display\")", - ".ReactiveCurrentEntity(\"unity-schematic-display\")", - "_catalog?.Dispose()", - "_playerSettings?.Dispose()", - "_currentEntity?.Dispose()", - "_entity.OverrideShutdown", - "_entity.HeatsinksEnabled", - "_entity.Heatstroke", - "_entity.Hypothermia" - }; - var presentForbiddenSchematicHudSymbols = forbiddenSchematicHudSymbols - .Where(symbol => schematicDisplay.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (presentForbiddenSchematicHudSymbols.Length > 0) - { - throw new InvalidOperationException( - "SchematicDisplay player HUD facts must not come from the Unity entity facade: " + - string.Join(", ", presentForbiddenSchematicHudSymbols)); - } - - if (!schematicDisplay.Contains("public void SetRenderSettings(AetheriaRuntimeDaemonRenderSettings renderSettings)", StringComparison.Ordinal) || - !schematicDisplay.Contains("_renderSettings?.NormalizeThermalRisk(", StringComparison.Ordinal) || - !gameplayBootShell.Contains("CockpitHudShell.SetRenderSettings(ZoneRenderer.RenderSettings)", StringComparison.Ordinal) || - !gameplayBootShell.Contains("runtimeState?.DaemonFrame?.Latest()", StringComparison.Ordinal) || - !gameplayBootShell.Contains("render/game-feel settings are daemon authority", StringComparison.Ordinal) || - !cockpitHudShell.Contains("public sealed class AetheriaUnityCockpitHudShell", StringComparison.Ordinal) || - !cockpitHudShell.Contains("public void SetRenderSettings(AetheriaRuntimeDaemonRenderSettings renderSettings)", StringComparison.Ordinal) || - !cockpitHudShell.Contains("SchematicDisplay?.SetRenderSettings(renderSettings)", StringComparison.Ordinal) || - !cockpitHudShell.Contains("TargetSchematicDisplay?.SetRenderSettings(renderSettings)", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "HUD thermal presentation must flow through daemon-frame render settings, not Unity-local render tuning."); - } - if (File.Exists(renderSettingsBridgePath) || - unityRenderPresentation.Contains("AetheriaUnityRenderSettingsBridge", StringComparison.Ordinal) || - unityRenderPresentation.Contains("local Unity Settings fallback", StringComparison.Ordinal) || - actionGameManager.Contains("TargetSpottedBlinkFrequency", StringComparison.Ordinal) || - actionGameManager.Contains("TargetSpottedBlinkOffset", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Unity render/game-feel boot must not preserve the local render-settings bridge or fallback; daemon frame settings are authoritative."); - } - - var requiredActionGameManagerRenderSymbols = new[] - { - "public sealed class AetheriaUnityCockpitHudShell", - "public sealed class AetheriaUnityGameplayBootShell", - "runtimeState?.DaemonFrame?.Latest()", - "CockpitHudShell.SetRenderSettings(ZoneRenderer.RenderSettings)", - "renderSettings.NormalizeDetectionProgress(", - "renderSettings.ResolveTargetSpottedFillEnabled(", - "renderSettings.NormalizeHeatstrokePost(", - "renderSettings.ResolveSevereHeatstrokePostWeight(", - "renderSettings.NormalizeTargetVisibilityFill(", - "renderSettings.NormalizeVisibilityToTargetFill(", - "renderSettings.NormalizeTargetStatusFill(", - "renderSettings.ResolveLockIndicatorNoiseAmplitude(", - "renderSettings.ResolveLockIndicatorNoiseFrequency(", - "renderSettings.ResolveLockSpinSpeed(", - "ZoneRenderer.RenderSettings.ResolveDefaultMinimapZoomIndex()", - "ZoneRenderer.RenderSettings.ResolveNextMinimapZoomIndex(_zoomLevelIndex)", - "ZoneRenderer.RenderSettings.ResolveMinimapDistance(_zoomLevelIndex)", - "ZoneRenderer.BodySettingsCollections = Settings.BodySettingsCollections;" - }; - - var missingActionGameManagerRenderSymbols = requiredActionGameManagerRenderSymbols - .Where(symbol => !unityRenderPresentation.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (missingActionGameManagerRenderSymbols.Length > 0) - { - throw new InvalidOperationException( - "Unity target/render presentation no longer consumes daemon-owned render settings: " + - string.Join(", ", missingActionGameManagerRenderSymbols)); - } - - var zoneRendererPresentationSettings = new Dictionary - { - ["ActionGameManager.Instance.Settings.PlanetSettings.AsteroidVerticalOffset"] = "ActionGameManager.Instance.Settings.PlanetSettings.AsteroidVerticalOffset", - ["PlanetRotationSpeed"] = "Settings.PlanetRotationSpeed", - ["Settings.PlanetSettings.ZoneDepthExponent"] = "Settings.PlanetSettings.ZoneDepthExponent", - ["Settings.PlanetSettings.ZoneDepth + Settings.PlanetSettings.ZoneBoundaryFog"] = "Settings.PlanetSettings.ZoneDepth + Settings.PlanetSettings.ZoneBoundaryFog", - ["AsteroidMeshCount"] = "Settings.AsteroidMeshCount", - ["Settings.PlanetSettings.BodyRadius"] = "Settings.PlanetSettings.BodyRadius", - ["Settings.PlanetSettings.LightRadius"] = "Settings.PlanetSettings.LightRadius", - ["Settings.PlanetSettings.WaveFrequency"] = "Settings.PlanetSettings.WaveFrequency" - }; - var survivingZoneRendererPresentationSettings = zoneRendererPresentationSettings - .Where(symbol => - symbol.Key == "PlanetRotationSpeed" || symbol.Key == "AsteroidMeshCount" - ? ContainsUnitySettingsMember(zoneRenderer, symbol.Key) - : zoneRenderer.Contains(symbol.Key, StringComparison.Ordinal)) - .Select(symbol => symbol.Value) - .ToArray(); - if (survivingZoneRendererPresentationSettings.Length > 0) - { - throw new InvalidOperationException( - "ZoneRenderer still reads zone presentation tuning from Unity GameSettings instead of shared daemon render settings: " + - string.Join(", ", survivingZoneRendererPresentationSettings)); - } - - var forbiddenRenderLoopSymbols = new[] - { - "Settings.GameplaySettings.TargetDetectionInfoThreshold", - "Settings.GameplaySettings.SevereHeatstrokeRiskThreshold", - "Settings.GameplaySettings.LockIndicatorNoiseAmplitude", - "Settings.GameplaySettings.LockIndicatorFrequency", - "Settings.GameplaySettings.LockSpinSpeed", - "Settings.HeatstrokePhasingFloor", - "Settings.HeatstrokePhasingFrequency", - "TargetSpottedBlinkFrequency", - "TargetSpottedBlinkOffset", - "Mathf.Lerp(.25f, .75f," - }; - var renderLoopHits = FindMethodScopedLineHits(actionGameManager, forbiddenRenderLoopSymbols) - .Where(hit => hit.MethodName is "Update" or "UpdateTargetIndicators") - .ToArray(); - if (renderLoopHits.Length > 0) - { - throw new InvalidOperationException( - "ActionGameManager render loops must interpret observed state through shared daemon render settings instead of Unity GameSettings: " + - string.Join(", ", renderLoopHits.Select(hit => $"{hit.MethodName}:{hit.LineNumber}:{hit.Line.Trim()}"))); - } - - var minimapBootHits = FindMethodScopedLineHits( - actionGameManager, - new[] { "Settings.MinimapZoomLevels", "Settings.DefaultMinimapZoom" }) - .Where(hit => hit.MethodName == "Start") - .ToArray(); - if (minimapBootHits.Length > 0) - { - throw new InvalidOperationException( - "ActionGameManager must bootstrap minimap zoom through shared daemon render settings instead of indexing Unity GameSettings directly: " + - string.Join(", ", minimapBootHits.Select(hit => $"{hit.MethodName}:{hit.LineNumber}:{hit.Line.Trim()}"))); - } -} - -static void RequireTypedZoneConstructionKeys(string root) -{ - var checkedFiles = new[] - { - Path.Combine(root, "Assets", "Scripts", "ServerShared", "ZoneConstructionData.cs"), - Path.Combine(root, "Assets", "Scripts", "ServerShared", "ZoneGenerator.cs"), - Path.Combine(root, "Assets", "Scripts", "ServerShared", "Zone.cs") - }; - - var forbiddenSymbols = new[] - { - "var runtimeBelt = new AsteroidBelt(belt, Orbits[OrbitKey(belt.Orbit)]);", - "var runtimeSun = new Sun(settings, sun, Orbits[OrbitKey(planet.Orbit)]);", - "var runtimeGas = new GasGiant(settings, gas, Orbits[OrbitKey(planet.Orbit)]);", - "var runtimePlanet = new Planet(settings, planet, Orbits[OrbitKey(planet.Orbit)]);", - "turret.OrbitKey = Zone.OrbitKey(turretOrbit.ID);", - "station.OrbitKey = Zone.OrbitKey(lagrangeOrbit.ID);", - "public Guid ID = Guid.NewGuid();", - "public Guid Orbit;", - "public Guid Parent;", - "data.OrbitKey = Zone.OrbitKey(data.ID);", - "planetData.BodyKey = Zone.BodyKey(planetData.ID);", - "lagrangeOrbit.OrbitKey = Zone.OrbitKey(lagrangeOrbit.ID);", - "turretOrbit.OrbitKey = Zone.OrbitKey(turretOrbit.ID);" - }; - - var hits = checkedFiles - .Where(File.Exists) - .SelectMany(path => File.ReadLines(path) - .Select((line, index) => new { Path = path, LineNumber = index + 1, Line = line })) - .Where(line => forbiddenSymbols.Any(symbol => line.Line.Contains(symbol, StringComparison.Ordinal))) - .Select(line => $"{Path.GetRelativePath(root, line.Path)}:{line.LineNumber}: {line.Line.Trim()}") - .ToArray(); - - if (hits.Length > 0) - { - throw new InvalidOperationException( - "Zone construction and generation must hand orbit/body identity through typed key fields instead of recomputing runtime authority from legacy GUID refs: " + - string.Join("; ", hits)); - } - - var requiredSymbols = new Dictionary - { - [Path.Combine(root, "Assets", "Scripts", "ServerShared", "ZoneConstructionData.cs")] = new[] - { - "public string BodyKey = \"\";", - "public string OrbitKey = \"\";", - "public string ParentOrbitKey = \"\";" - }, - [Path.Combine(root, "Assets", "Scripts", "ServerShared", "ZoneGenerator.cs")] = new[] - { - "OrbitKey = Zone.OrbitKey(Guid.NewGuid()),", - "data.ParentOrbitKey = orbitInverseMap[data].Parent != null", - "planetData.BodyKey = Zone.BodyKey(Guid.NewGuid());", - "planetData.OrbitKey = orbitMap[planet].OrbitKey;", - "planetData.Name = bodyName.Substring(0, Math.Min(8, bodyName.Length));", - "var bodyName = planetData.BodyKey.Substring(planetData.BodyKey.LastIndexOf(':') + 1);", - "OrbitKey = Zone.OrbitKey(Guid.NewGuid()),", - "ParentOrbitKey = baseOrbit.ParentOrbitKey,", - "ParentOrbitKey = orbit.ParentOrbitKey,", - "turret.OrbitKey = turretOrbit.OrbitKey;", - "station.OrbitKey = lagrangeOrbit.OrbitKey;" - }, - [Path.Combine(root, "Assets", "Scripts", "ServerShared", "Zone.cs")] = new[] - { - "var runtimeBelt = new AsteroidBelt(belt, Orbits[belt.OrbitKey]);", - "var runtimeSun = new Sun(settings, sun, Orbits[planet.OrbitKey]);", - "var runtimeGas = new GasGiant(settings, gas, Orbits[planet.OrbitKey]);", - "var runtimePlanet = new Planet(settings, planet, Orbits[planet.OrbitKey]);", - "BodyKey = data.BodyKey ?? \"\";", - "OrbitKey = data.OrbitKey ?? \"\";", - "ParentOrbitKey = data.ParentOrbitKey ?? \"\";" - } - }; - - var missingSymbols = requiredSymbols - .Where(pair => !File.Exists(pair.Key) || pair.Value.Any(symbol => !File.ReadAllText(pair.Key).Contains(symbol, StringComparison.Ordinal))) - .SelectMany(pair => - { - var text = File.Exists(pair.Key) ? File.ReadAllText(pair.Key) : ""; - return pair.Value - .Where(symbol => !text.Contains(symbol, StringComparison.Ordinal)) - .Select(symbol => $"{Path.GetRelativePath(root, pair.Key)}: missing {symbol}"); - }) - .ToArray(); - - if (missingSymbols.Length > 0) - { - throw new InvalidOperationException( - "Zone construction inputs and generator seams must retain typed orbit/body keys as the handoff authority: " + - string.Join("; ", missingSymbols)); - } -} - -static void RequireTypedZoneStateSnapshotKeys(string root) -{ - var checkedFiles = new[] - { - Path.Combine(root, "Aetheria.State", "Documents", "AetheriaRuntimeStateDocuments.cs"), - Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeSnapshotDocuments.cs") - }; - - var forbiddenSymbols = new[] - { - "public string OrbitId { get; set; } = \"\";", - "public string ParentId { get; set; } = \"\";", - "public string BodyId { get; set; } = \"\";", - "public AetheriaRuntimeOrbitSnapshot(string orbitId, string parentId", - "string bodyId,", - "string orbitId,", - "OrbitId = orbit.OrbitKey ?? \"\",", - "ParentId = orbit.ParentOrbitKey ?? \"\",", - "BodyId = body.BodyKey ?? \"\",", - "var orbitId = ReadFieldString(ref reader, orbitFields, 0);", - "var parentId = ReadFieldString(ref reader, orbitFields, 1);", - "var bodyId = ReadFieldString(ref reader, bodyFields, 0);", - "var orbitId = ReadFieldString(ref reader, bodyFields, 3);" - }; - - var hits = checkedFiles - .Where(File.Exists) - .SelectMany(path => File.ReadLines(path) - .Select((line, index) => new { Path = path, LineNumber = index + 1, Line = line })) - .Where(line => forbiddenSymbols.Any(symbol => line.Line.Contains(symbol, StringComparison.Ordinal))) - .Select(line => $"{Path.GetRelativePath(root, line.Path)}:{line.LineNumber}: {line.Line.Trim()}") - .ToArray(); - - if (hits.Length > 0) - { - throw new InvalidOperationException( - "Typed zone-state documents and runtime snapshots must name orbit/body identity as keys, not legacy ids: " + - string.Join("; ", hits)); - } - - var requiredSymbols = new Dictionary - { - [Path.Combine(root, "Aetheria.State", "Documents", "AetheriaRuntimeStateDocuments.cs")] = new[] - { - "public string OrbitKey { get; set; } = \"\";", - "public string ParentOrbitKey { get; set; } = \"\";", - "public string BodyKey { get; set; } = \"\";" - }, - [Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeSnapshotDocuments.cs")] = new[] - { - "public sealed class AetheriaRuntimeZoneSnapshotCommit", - "public sealed class AetheriaRuntimeOrbitSnapshotCommit", - "public sealed class AetheriaRuntimeBodySnapshotCommit", - "public string OrbitKey { get; set; } = \"\";", - "public string ParentOrbitKey { get; set; } = \"\";", - "public string BodyKey { get; set; } = \"\";", - "public double GravityInfluenceCenterX { get; set; }", - "public double GravityInfluenceCenterZ { get; set; }", - "public double GravityInfluenceRadius { get; set; }", - "public double GravityWellDepth { get; set; }", - "public double GravityWaveRadius { get; set; }", - "public double GravityWaveDepth { get; set; }", - "public double GravityWaveSpeed { get; set; }", - "public double GravityTerrainRadius { get; set; }", - "public double GravityTerrainDepth { get; set; }", - "public double GravityTerrainDepthExponent { get; set; }", - "public double GravityTerrainBoundaryFog { get; set; }", - "public double GravityTerrainWaveFrequency { get; set; }" - } - }; - - var missingSymbols = requiredSymbols - .Where(pair => !File.Exists(pair.Key) || pair.Value.Any(symbol => !File.ReadAllText(pair.Key).Contains(symbol, StringComparison.Ordinal))) - .SelectMany(pair => - { - var text = File.Exists(pair.Key) ? File.ReadAllText(pair.Key) : ""; - return pair.Value - .Where(symbol => !text.Contains(symbol, StringComparison.Ordinal)) - .Select(symbol => $"{Path.GetRelativePath(root, pair.Key)}: missing {symbol}"); - }) - .ToArray(); - - if (missingSymbols.Length > 0) - { - throw new InvalidOperationException( - "Typed zone-state documents and managed runtime snapshot commits must expose orbit/body key ownership explicitly: " + - string.Join("; ", missingSymbols)); - } -} - -static void RequireTypedAsteroidZoneApi(string root) -{ - var zoneSourcePath = Path.Combine(root, "Assets", "Scripts", "ServerShared", "Zone.cs"); - var zoneSource = File.Exists(zoneSourcePath) - ? File.ReadAllText(zoneSourcePath) - : throw new InvalidOperationException("Cannot verify typed asteroid Zone API; Zone source is missing."); - - var forbiddenSymbols = new[] - { - "public int NearestAsteroid(Guid planetDataID, float2 position)", - "public bool AsteroidExists(Guid planetDataID, int asteroid)", - "public void MineAsteroid(Entity miner, Guid asteroidBelt, int asteroid, float damage, float efficiency, float penetration)", - "BeltUpdates.Add(Task.Run(() => UpdateAsteroidTransforms(belt.Key)));", - "private void UpdateAsteroidTransforms(Guid planetDataID)", - "MineAsteroid(miner, belt.ID, asteroid, damage, efficiency, penetration);" - }; - var hits = forbiddenSymbols - .Where(symbol => zoneSource.Contains(symbol, StringComparison.Ordinal)) - .Select(symbol => $"Assets/Scripts/ServerShared/Zone.cs: contains {symbol}") - .ToArray(); - if (hits.Length > 0) - { - throw new InvalidOperationException( - "Zone asteroid helpers must keep typed body-key or runtime-object authority rather than Guid overloads: " + - string.Join("; ", hits)); - } - - var requiredSymbols = new[] - { - "foreach (var belt in AsteroidBelts.Values)", - "BeltUpdates.Add(Task.Run(() => UpdateAsteroidTransforms(belt)));", - "public int NearestAsteroid(string asteroidBeltKey, float2 position)", - "if (!TryGetAsteroidBelt(asteroidBeltKey, out var belt))", - "public bool AsteroidExists(string asteroidBeltKey, int asteroid)", - "private void UpdateAsteroidTransforms(AsteroidBelt belt)", - "public void MineAsteroid(Entity miner, string asteroidBeltKey, int asteroid, float damage, float efficiency, float penetration)" - }; - var missingSymbols = requiredSymbols - .Where(symbol => !zoneSource.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (missingSymbols.Length > 0) - { - throw new InvalidOperationException( - "Zone asteroid helpers must route through typed body keys or runtime belt objects: " + - string.Join(", ", missingSymbols)); - } -} - -static void RequireFactionKeyIdentity(string root) -{ - var checkedFiles = new[] - { - Path.Combine(root, "Assets", "Scripts", "ServerShared", "Corporations.cs"), - Path.Combine(root, "Assets", "Scripts", "ServerShared", "Entity.cs"), - Path.Combine(root, "Assets", "Scripts", "ServerShared", "Galaxy.cs"), - Path.Combine(root, "Assets", "Scripts", "ServerShared", "Zone.cs"), - Path.Combine(root, "Assets", "Scripts", "ServerShared", "Narrative", "ZoneConstraints.cs"), - Path.Combine(root, "Assets", "Scripts", "Gameplay", "ActionGameManager.cs"), - Path.Combine(root, "Assets", "Scripts", "UI", "Menu", "SectorRenderer.cs"), - Path.Combine(root, "Assets", "Scripts", "UI", "Menu", "SectorMap.cs") - }; - - var forbiddenSymbols = new[] - { - "ID.GetHashCode", - "faction.ID == ID", - "Faction.ID ==", - "faction.ID ==", - ".ID == TargetFaction", - ".ID == GalaxyZone.Owner", - ".ID != zone.Owner", - "pair.Key.ID", - "f.ID != zone.Owner", - "Owner.ID ==", - "zone.Owner == adjacentZone.Owner", - "zone.Owner != mega" - }; - - var hits = checkedFiles - .Where(File.Exists) - .SelectMany(path => File.ReadLines(path) - .Select((line, index) => new { Path = path, LineNumber = index + 1, Line = line })) - .Where(line => forbiddenSymbols.Any(symbol => line.Line.Contains(symbol, StringComparison.Ordinal))) - .Select(line => $"{Path.GetRelativePath(root, line.Path)}:{line.LineNumber}: {line.Line.Trim()}") - .ToArray(); - - if (hits.Length > 0) - { - throw new InvalidOperationException( - "Faction identity must compare and order by FactionKey; Faction.ID is temporary projection residue only: " + - string.Join("; ", hits)); - } -} - -static void RequireTypedRuntimeBehaviorCoverage(string root) -{ - var actionGameManagerPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "ActionGameManager.cs"); - var observedEntityRestorerPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "AetheriaUnityObservedEntityRestorer.cs"); - var behaviorStateProjectorPath = Path.Combine( - root, - "Packages", - "org.gamecult.aetheria.state", - "Runtime", - "AetheriaRuntimeBehaviorStateProjector.cs"); - var daemonProgramPath = Path.Combine(root, "Aetheria.State.Daemon", "Program.cs"); - var unityProjectPath = Path.Combine(root, "GameCult.Aetheria.State.Unity.csproj"); - - var actionGameManager = File.Exists(actionGameManagerPath) - ? File.ReadAllText(actionGameManagerPath) - : throw new InvalidOperationException("Cannot verify typed runtime behavior coverage; ActionGameManager.cs is missing."); - var observedEntityRestorer = File.Exists(observedEntityRestorerPath) - ? File.ReadAllText(observedEntityRestorerPath) - : throw new InvalidOperationException("Cannot verify typed runtime behavior coverage; AetheriaUnityObservedEntityRestorer.cs is missing."); - var behaviorStateProjector = File.Exists(behaviorStateProjectorPath) - ? File.ReadAllText(behaviorStateProjectorPath) - : throw new InvalidOperationException("Cannot verify typed runtime behavior coverage; behavior-state projector is missing."); - var daemonProgram = File.Exists(daemonProgramPath) - ? File.ReadAllText(daemonProgramPath) - : throw new InvalidOperationException("Cannot verify typed runtime behavior coverage; daemon program is missing."); - var unityProject = File.Exists(unityProjectPath) - ? File.ReadAllText(unityProjectPath) - : throw new InvalidOperationException("Cannot verify typed runtime behavior coverage; Unity project is missing."); - var runtimeRestoreSource = actionGameManager + "\n" + observedEntityRestorer; - - var requiredBehaviorStateProjection = new[] - { - "public static class AetheriaRuntimeBehaviorStateProjector", - "public static void EnsureEquipmentBehaviorStates(", - "public static AetheriaRuntimeBehaviorStateCommit[] CreateEquipmentBehaviorStates(", - "AetheriaRuntimeCatalogSnapshot? catalog", - "catalog.FindItem(item.ItemKey ?? \"\")", - "typedItem?.BehaviorPayloads", - "OwnerKind = EquipmentOwnerKind", - "BehaviorKind = payload.Kind" - }; - - var missingBehaviorStateProjection = requiredBehaviorStateProjection - .Where(symbol => !behaviorStateProjector.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - - if (missingBehaviorStateProjection.Length > 0) - { - throw new InvalidOperationException( - "Typed runtime behavior authority must derive daemon behavior-state rows from catalog payloads instead of Unity behavior objects: " + - string.Join(", ", missingBehaviorStateProjection)); - } - - if (!daemonProgram.Contains("node.RuntimeCatalog().Latest() ?? new AetheriaRuntimeCatalogSnapshot()", StringComparison.Ordinal) || - !daemonProgram.Contains("AetheriaRuntimeBehaviorStateProjector.CreateEquipmentBehaviorStates(equipment, catalog)", StringComparison.Ordinal) || - !daemonProgram.Contains("AetheriaRuntimeBehaviorStateProjector.EnsureEquipmentBehaviorStates(runtimeEntity, catalog)", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Daemon runtime checkpoint projection must publish typed behavior-state rows from managed catalog payloads."); - } - - if (!unityProject.Contains("AetheriaRuntimeBehaviorStateProjector.cs", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Unity-compatible shared runtime builds must include the typed behavior-state projector."); - } - - var requiredWeaponStateCoverage = new[] - { - "if (weapon is LockWeapon lockWeapon)", - "else if (weapon is ChargedWeapon chargedWeapon)", - "else if (weapon is ConstantWeapon constantWeapon)", - "else if (weapon is InstantWeapon instantWeapon)" - }; - - var missingWeaponStateCoverage = requiredWeaponStateCoverage - .Where(symbol => !runtimeRestoreSource.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - - if (missingWeaponStateCoverage.Length > 0) - { - throw new InvalidOperationException( - "Typed weapon snapshot coverage no longer restores the live articulated weapon families: " + - string.Join(", ", missingWeaponStateCoverage)); - } - - var requiredBehaviorStateCoverage = new[] - { - "case Sensor sensor:", - "case Radiator radiator:", - "case Reactor reactor:", - "case Capacitor capacitor:", - "case AetherDrive drive:", - "case ResourceScanner resourceScanner:", - "case MiningTool miningTool:", - "case Thruster thruster:", - "case Shield shield:", - "case VelocityLimit velocityLimit:", - "case Thermotoggle thermotoggle:", - "case Switch switchBehavior:", - "case Trigger trigger:", - "case StatModifier statModifier:", - "case TurretController turretController:" - }; - - var missingBehaviorStateCoverage = requiredBehaviorStateCoverage - .Where(symbol => !runtimeRestoreSource.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - - if (missingBehaviorStateCoverage.Length > 0) - { - throw new InvalidOperationException( - "Typed runtime behavior snapshot coverage no longer owns the mutable non-weapon behavior families: " + - string.Join(", ", missingBehaviorStateCoverage)); - } -} - -static void RequireEveRuntimeBootstrap(string root) -{ - var bootstrapPath = Path.Combine( - root, - "Packages", - "org.gamecult.aetheria.eve-runtime", - "Runtime", - "AetheriaEveRuntimeBootstrap.cs"); - var presenterPath = Path.Combine( - root, - "Packages", - "org.gamecult.aetheria.eve-runtime", - "Runtime", - "AetheriaEveSurfacePresenter.cs"); - var daemonSurfaceCommandsPath = Path.Combine( - root, - "Packages", - "org.gamecult.aetheria.state", - "Runtime", - "AetheriaRuntimeDaemonSurfaceCommands.cs"); - - if (!File.Exists(bootstrapPath)) - { - throw new InvalidOperationException("Aetheria Eve runtime bootstrap is missing."); - } - - if (!File.Exists(presenterPath)) - { - throw new InvalidOperationException("Aetheria Eve surface presenter is missing."); - } - - if (!File.Exists(daemonSurfaceCommandsPath)) - { - throw new InvalidOperationException("Aetheria daemon surface command router is missing."); - } - - var bootstrap = File.ReadAllText(bootstrapPath); - if (!bootstrap.Contains("RuntimeInitializeOnLoadMethod", StringComparison.Ordinal) || - !bootstrap.Contains("DefaultSurfaceId = AetheriaRuntimeDaemonGameSurfaceBuilder.SurfaceId", StringComparison.Ordinal) || - !bootstrap.Contains("AetheriaEveSurfacePresenter", StringComparison.Ordinal) || - !bootstrap.Contains("UIDocument", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Aetheria Eve runtime bootstrap no longer mounts the daemon-published game surface through the UI Toolkit presenter."); - } - - var presenter = File.ReadAllText(presenterPath); - var daemonSurfaceCommands = File.ReadAllText(daemonSurfaceCommandsPath); - var requiredPresenterSymbols = new[] - { - "private string surfaceId = AetheriaRuntimeDaemonGameSurfaceBuilder.SurfaceId", - "ReadDaemonSurface(stateBoot)", - ".EveSurfaceDocument(surfaceId)", - "?.Reactive()", - "AetheriaEveRuntimeUnityHooks.TryCreateStateRefResolver(", - "CultMeshReactiveDocument", - "ResolveReactiveDaemonSurfaceState(", - "DisposeReactiveSurfaceState()", - "AetheriaRuntimeSurfaceDocuments.ToEveSurfaceDocument(", - "private bool ShouldMountSurface(", - "_mountedSurfaceVersion != surface.Version", - "private static readonly AetheriaEveUnitySurfaceChrome RootOnlyChrome", - "UseShell = false", - "MountSurface(AetheriaRuntimeStateBootReport stateBoot, EveSurfaceDocument surface)", - "AetheriaEveUnitySurfaceHost.Render(", - "control.TrySubmitSurfaceCommand(request, out var daemonEnvelope)", - ".SurfaceCommandAsync(" - }; - var missingPresenterSymbols = requiredPresenterSymbols - .Where(symbol => !presenter.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (missingPresenterSymbols.Length > 0) - { - throw new InvalidOperationException( - "Aetheria Eve presenter no longer lowers refreshed daemon/CultMesh surfaces through typed Eve commands: " + - string.Join(", ", missingPresenterSymbols)); - } - - if (presenter.Contains("AetheriaRuntimeStateReader.ReadEveSurface", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Aetheria Eve presenter still mounts daemon surfaces through file-reader lookup instead of the shared AetheriaClient facade."); - } - - if (presenter.Contains(".DaemonGameSurfaceAsync()", StringComparison.Ordinal) || - presenter.Contains(".DaemonGameTuiSurfaceAsync()", StringComparison.Ordinal) || - presenter.Contains(".DaemonEditorSurfaceAsync()", StringComparison.Ordinal) || - presenter.Contains(".DaemonEditorTuiSurfaceAsync()", StringComparison.Ordinal) || - presenter.Contains("client.State.Daemon.ReactiveGameSurface()", StringComparison.Ordinal) || - presenter.Contains("client.State.Daemon.ReactiveGameTuiSurface()", StringComparison.Ordinal) || - presenter.Contains("client.State.Daemon.ReactiveEditorSurface()", StringComparison.Ordinal) || - presenter.Contains("client.State.Daemon.ReactiveEditorTuiSurface()", StringComparison.Ordinal) || - presenter.Contains("client.State.Daemon.GameSurface.LatestAsync()", StringComparison.Ordinal) || - presenter.Contains("client.State.Daemon.GameTuiSurface.LatestAsync()", StringComparison.Ordinal) || - presenter.Contains("client.State.Daemon.EditorSurface.LatestAsync()", StringComparison.Ordinal) || - presenter.Contains("client.State.Daemon.EditorTuiSurface.LatestAsync()", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Aetheria Eve presenter still reads daemon surfaces through one-shot compatibility helpers instead of managed reactive document handles."); - } - - if (presenter.Contains("new EveUiToolkitSurfaceLowerer", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Aetheria Eve presenter still lowers daemon-published surfaces directly instead of delegating to the shared Unity Eve surface host."); - } - - if (presenter.Contains("ResolveClient(", StringComparison.Ordinal) || - presenter.Contains("AetheriaRuntimeDaemonSurfaceCommands.TrySubmit(", StringComparison.Ordinal) || - presenter.Contains("AetheriaClientEveSurface.", StringComparison.Ordinal) || - presenter.Contains("client.State.", StringComparison.Ordinal) || - presenter.Contains("client.Ui.", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Aetheria Eve presenter must use provider-native typed state/control/UI access instead of raw client plumbing."); - } - - var daemonSubmitIndex = presenter.IndexOf( - "control.TrySubmitSurfaceCommand(request, out var daemonEnvelope)", - StringComparison.Ordinal); - var eveSubmitIndex = presenter.IndexOf( - ".SurfaceCommandAsync(", - StringComparison.Ordinal); - if (daemonSubmitIndex < 0 || eveSubmitIndex < 0 || daemonSubmitIndex > eveSubmitIndex) - { - throw new InvalidOperationException( - "Aetheria Eve presenter must route daemon-published surface commands to the typed daemon boundary before falling back to Eve requests."); - } - - var requiredDaemonSurfaceCommandSymbols = new[] - { - "public static class AetheriaRuntimeDaemonSurfaceCommands", - "EveSurfaceCommandRequest request", - "AetheriaClient", - "client.Control.TrySubmitSurfaceCommand(request, out envelope)" - }; - var missingDaemonSurfaceCommandSymbols = requiredDaemonSurfaceCommandSymbols - .Where(symbol => !daemonSurfaceCommands.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (missingDaemonSurfaceCommandSymbols.Length > 0) - { - throw new InvalidOperationException( - "Daemon Eve surface command routing no longer submits typed daemon operations: " + - string.Join(", ", missingDaemonSurfaceCommandSymbols)); - } - - if (daemonSurfaceCommands.Contains("AetheriaRuntimeStateReader.TryReadDaemonRenderView", StringComparison.Ordinal) || - daemonSurfaceCommands.Contains("TryReactiveDaemonSoaView(client)", StringComparison.Ordinal) || - daemonSurfaceCommands.Contains("client.CurrentObservedDaemon()", StringComparison.Ordinal) || - daemonSurfaceCommands.Contains("client.State.CurrentObservedDaemon()", StringComparison.Ordinal) || - daemonSurfaceCommands.Contains("client.State.ReactiveObservedDaemon()", StringComparison.Ordinal) || - daemonSurfaceCommands.Contains("observedState.TryCurrent(out var current)", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Daemon Eve surface command routing still reads observed daemon state through an aggregate reader instead of direct managed typed documents."); - } - - if (daemonSurfaceCommands.Contains(".ReadAsync(client.State)", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Daemon Eve surface command routing still samples observed daemon state through a one-shot compatibility helper instead of a managed reactive document."); - } - - if (daemonSurfaceCommands.Contains("ObserveAsync()", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Daemon Eve surface command routing still reads observed daemon state through the AetheriaClient compatibility helper."); - } - - if (daemonSurfaceCommands.Contains(".OpenAsync(", StringComparison.Ordinal) || - daemonSurfaceCommands.Contains("string stateFilePath", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Daemon Eve surface command routing still hides the managed client boundary behind a state-file-path shortcut."); - } - - var forbiddenDaemonSurfaceCommandSymbols = new[] - { - "request.Payload.TryGetValue", - "ApplyPayload(", - "ReadInt(", - "ReadDouble(", - "ReadString(", - "\"commandKind\"" - }; - var daemonSurfaceCommandHits = forbiddenDaemonSurfaceCommandSymbols - .Where(symbol => daemonSurfaceCommands.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (daemonSurfaceCommandHits.Length > 0) - { - throw new InvalidOperationException( - "Daemon Eve surface command routing must lower command ids to typed daemon documents, not decode string payload maps: " + - string.Join(", ", daemonSurfaceCommandHits)); - } - - if (daemonSurfaceCommands.Contains("AetheriaRuntimeCommandSubmitter.TrySubmitDaemonCommand(", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Daemon Eve surface command routing still calls the generic daemon submitter instead of the typed daemon operation client."); - } - - if (daemonSurfaceCommands.Contains("TrySendCommandKind(", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Daemon Eve surface command routing still uses a generic command-kind submission back door instead of explicit typed operations."); - } -} - -static void RequireNoRendererLocalConsole(string root) -{ - var consoleFiles = new[] - { - Path.Combine(root, "Assets", "Scripts", "UI", "ConsoleController.cs"), - Path.Combine(root, "Assets", "Scripts", "UI", "ConsoleView.cs") - }; - - var existingConsoleFiles = consoleFiles - .Where(File.Exists) - .Select(path => Path.GetRelativePath(root, path)) - .ToArray(); - - if (existingConsoleFiles.Length > 0) - { - throw new InvalidOperationException( - "Renderer-local console source files still exist: " + - string.Join(", ", existingConsoleFiles)); - } - - var forbiddenSymbols = new[] - { - "ConsoleController", - "ConsoleView", - "AddCommand(" - }; - - var hits = Directory.EnumerateFiles(Path.Combine(root, "Assets"), "*.*", SearchOption.AllDirectories) - .Where(path => - path.EndsWith(".cs", StringComparison.OrdinalIgnoreCase) || - path.EndsWith(".unity", StringComparison.OrdinalIgnoreCase) || - path.EndsWith(".prefab", StringComparison.OrdinalIgnoreCase)) - .SelectMany(path => File.ReadLines(path) - .Select((line, index) => new { Path = path, LineNumber = index + 1, Line = line })) - .Where(line => forbiddenSymbols.Any(symbol => line.Line.Contains(symbol, StringComparison.Ordinal))) - .Select(line => $"{Path.GetRelativePath(root, line.Path)}:{line.LineNumber}: {line.Line.Trim()}") - .Take(10) - .ToArray(); - - if (hits.Length > 0) - { - throw new InvalidOperationException( - "Renderer-local console authority is still wired in Assets: " + - string.Join("; ", hits)); - } -} - -static void RequireNoRendererLocalDebugPanels(string root) -{ - var forbiddenFiles = new[] - { - Path.Combine(root, "Assets", "Scripts", "UI", "FieldTester.cs") - }; - - var existingFiles = forbiddenFiles - .Where(File.Exists) - .Select(path => Path.GetRelativePath(root, path)) - .ToArray(); - - if (existingFiles.Length > 0) - { - throw new InvalidOperationException( - "Renderer-local debug panel source files still exist: " + - string.Join(", ", existingFiles)); - } - - var hits = Directory.EnumerateFiles(Path.Combine(root, "Assets"), "*.*", SearchOption.AllDirectories) - .Where(path => - path.EndsWith(".cs", StringComparison.OrdinalIgnoreCase) || - path.EndsWith(".unity", StringComparison.OrdinalIgnoreCase) || - path.EndsWith(".prefab", StringComparison.OrdinalIgnoreCase)) - .SelectMany(path => File.ReadLines(path) - .Select((line, index) => new { Path = path, LineNumber = index + 1, Line = line })) - .Where(line => line.Line.Contains("FieldTester", StringComparison.Ordinal)) - .Select(line => $"{Path.GetRelativePath(root, line.Path)}:{line.LineNumber}: {line.Line.Trim()}") - .Take(10) - .ToArray(); - - if (hits.Length > 0) - { - throw new InvalidOperationException( - "Renderer-local FieldTester authority is still wired in Assets: " + - string.Join("; ", hits)); - } -} - -static void RequireMainMenuSettingsCommands(string root) -{ - var mainMenuPath = Path.Combine(root, "Assets", "Scripts", "UI", "MainMenu.cs"); - var sharedCommandsPath = Path.Combine( - root, - "Packages", - "org.gamecult.aetheria.state", - "Runtime", - "AetheriaRuntimePlayerSettingsCommands.cs"); - var surfaceBuilderPath = Path.Combine( - root, - "Packages", - "org.gamecult.aetheria.state", - "Runtime", - "AetheriaRuntimePlayerSettingsSurfaceBuilder.cs"); - if (!File.Exists(mainMenuPath)) - { - throw new InvalidOperationException("Cannot verify main-menu settings command path; MainMenu.cs is missing."); - } - if (!File.Exists(sharedCommandsPath)) - { - throw new InvalidOperationException( - "Cannot verify main-menu settings command contract; AetheriaRuntimePlayerSettingsCommands.cs is missing."); - } - if (!File.Exists(surfaceBuilderPath)) - { - throw new InvalidOperationException( - "Cannot verify main-menu settings surface contract; AetheriaRuntimePlayerSettingsSurfaceBuilder.cs is missing."); - } - - var source = File.ReadAllText(mainMenuPath); - var sharedCommands = File.ReadAllText(sharedCommandsPath); - var surfaceBuilder = File.ReadAllText(surfaceBuilderPath); - var actionGameManagerPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "ActionGameManager.cs"); - var actionGameManager = File.Exists(actionGameManagerPath) - ? File.ReadAllText(actionGameManagerPath) - : throw new InvalidOperationException("Cannot verify main-menu settings authority; ActionGameManager.cs is missing."); - - var forbiddenSymbols = new[] - { - "ActionGameManager.RuntimePlayerSettings.Name =", - "ActionGameManager.RuntimePlayerSettings.GameplaySettings.TemperatureUnit =", - "ActionGameManager.RuntimePlayerSettings.GameplaySettings.SignificantDigits =", - "ActionGameManager.RuntimePlayerSettings.GraphicsSettings.NebulaQuality =", - "ActionGameManager.RuntimePlayerSettings.GraphicsSettings.ShowAsteroidsInMinimap =", - "ActionGameManager.RequestRuntimePlayerSettingsCommand", - "ActionGameManager.RequestRuntimePlayerName", - "ActionGameManager.RequestRuntimeTemperatureUnit", - "ActionGameManager.RequestRuntimeSignificantDigits", - "ActionGameManager.RequestRuntimeNebulaQuality", - "ActionGameManager.RequestRuntimeShowAsteroidsInMinimap" - }; - - var actionGameManagerForbiddenSymbols = new[] - { - "public static RuntimePlayerSettings RuntimePlayerSettings", - "RequestRuntimePlayerSettingsCommand", - "RequestRuntimePlayerName", - "RequestRuntimeTemperatureUnit", - "RequestRuntimeSignificantDigits", - "RequestRuntimeNebulaQuality", - "RequestRuntimeShowAsteroidsInMinimap" - }; - - var hits = File.ReadLines(mainMenuPath) - .Select((line, index) => new { Path = mainMenuPath, LineNumber = index + 1, Line = line }) - .Where(line => forbiddenSymbols.Any(symbol => line.Line.Contains(symbol, StringComparison.Ordinal))) - .Concat(File.ReadLines(actionGameManagerPath) - .Select((line, index) => new { Path = actionGameManagerPath, LineNumber = index + 1, Line = line }) - .Where(line => actionGameManagerForbiddenSymbols.Any(symbol => line.Line.Contains(symbol, StringComparison.Ordinal)))) - .Select(line => $"{Path.GetRelativePath(root, line.Path)}:{line.LineNumber}: {line.Line.Trim()}") - .ToArray(); - - if (hits.Length > 0) - { - throw new InvalidOperationException( - "Unity still owns direct player-settings mutation instead of sending Eve commands: " + - string.Join("; ", hits)); - } - - var requiredMainMenuAuthoritySymbols = new[] - { - "SendKnownAetheriaEveCommand(request, \"player-settings\")", - "AetheriaRuntimeMainMenuCommandKind.PlayerSettingsCommand", - "AetheriaUnityRuntimeClientProvider", - ".Ui(stateBoot, \"unity-main-menu\")", - ".SurfaceCommandAsync(request, \"unity-main-menu\")", - "stateBoot.StateFilePath", - "\"unity-main-menu\"" - }; - - var missingUiCalls = requiredMainMenuAuthoritySymbols - .Where(symbol => !source.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - - if (missingUiCalls.Length > 0) - { - throw new InvalidOperationException( - "MainMenu no longer routes settings changes through the Aetheria Eve command boundary: " + - string.Join(", ", missingUiCalls)); - } - - var forbiddenSubmitAcceptanceSymbols = new[] - { - "private static bool TrySendKnownAetheriaEveCommand(", - "if (!TrySendKnownAetheriaEveCommand(request, \"player-settings\"))" - }; - var submitAcceptanceHits = forbiddenSubmitAcceptanceSymbols - .Where(symbol => source.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (submitAcceptanceHits.Length > 0) - { - throw new InvalidOperationException( - "MainMenu player-settings command lowering still treats Eve submission as local acceptance state: " + - string.Join(", ", submitAcceptanceHits)); - } - - if (source.Contains("TrySendPlayerSettingsCommand(", StringComparison.Ordinal) || - source.Contains("CommandKindForSurface(request)", StringComparison.Ordinal) || - source.Contains("new AetheriaRuntimePlayerSettingsCommandBody", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "MainMenu still decodes player-settings Eve payloads locally instead of delegating to the typed command client."); - } - - var requiredSharedCommands = new[] - { - "SetPlayerName", - "CycleTemperatureUnit", - "DecrementSignificantDigits", - "IncrementSignificantDigits", - "CycleNebulaQuality", - "ToggleShowAsteroidsInMinimap" - }; - - var missingSharedCommands = requiredSharedCommands - .Where(symbol => !sharedCommands.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - - if (missingSharedCommands.Length > 0) - { - throw new InvalidOperationException( - "Shared player-settings Eve command contract is incomplete: " + - string.Join(", ", missingSharedCommands)); - } - - var requiredSurfaceBuilderSymbols = new[] - { - "AetheriaRuntimePlayerSettingsSurfaceBuilder", - "AetheriaRuntimePlayerSettingsCommands.SetPlayerName", - "\"control.text\"", - "AetheriaRuntimePlayerSettingsCommands.CycleTemperatureUnit", - "AetheriaRuntimePlayerSettingsCommands.DecrementSignificantDigits", - "AetheriaRuntimePlayerSettingsCommands.IncrementSignificantDigits", - "AetheriaRuntimePlayerSettingsCommands.CycleNebulaQuality", - "AetheriaRuntimePlayerSettingsCommands.ToggleShowAsteroidsInMinimap" - }; - - var missingSurfaceBuilderSymbols = requiredSurfaceBuilderSymbols - .Where(symbol => !surfaceBuilder.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - - if (missingSurfaceBuilderSymbols.Length > 0) - { - throw new InvalidOperationException( - "Shared player-settings Eve surface builder is incomplete: " + - string.Join(", ", missingSurfaceBuilderSymbols)); - } - - var requiredMainMenuSymbols = new[] - { - "ResolveMainMenuSurface(AetheriaRuntimeMainMenuCommands.PlayerSettingsSurfaceId)", - ".RuntimeState(stateBoot, \"unity-main-menu\")", - ".MainMenuSurface(surfaceId, CanOpenRuntimeInputScreen(), InGame)", - "AetheriaEveUnitySurfaceHost.RenderRuntime(", - "AetheriaRuntimeMainMenuCommandKind.PlayerSettingsCommand", - "SendKnownAetheriaEveCommand(request, \"player-settings\")" - }; - - var missingMainMenuSymbols = requiredMainMenuSymbols - .Where(symbol => !source.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - - if (missingMainMenuSymbols.Length > 0) - { - throw new InvalidOperationException( - "MainMenu no longer lowers the shared player-settings Eve surface contract: " + - string.Join(", ", missingMainMenuSymbols)); - } - - var forbiddenMainMenuSymbols = new[] - { - "new TextField(\"Name\")", - "ActionGameManager.RequestRuntimePlayerName(evt.newValue)", - "TryQueuePlayerSettingsCommand(", - "TryQueueAetheriaEveCommand(", - "AetheriaRuntimeEveCommandLog.QueueCommand(", - "BuildPlayerSettingsSurfaceDefinition()", - "ProjectPlayerSettingsSurfaceState(", - "new AetheriaRuntimePlayerSettingsSurfaceState(", - "ResolveMenuSurfaceDocument(", - "new EveUiToolkitSurfaceLowerer", - "host.AddComponent", - "new EveSurfaceCommandRequest(" - }; - - var mainMenuHits = forbiddenMainMenuSymbols - .Where(symbol => source.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - - if (mainMenuHits.Length > 0) - { - throw new InvalidOperationException( - "MainMenu should not keep renderer-local player-name input authority outside the shared Eve surface: " + - string.Join(", ", mainMenuHits)); - } - - var forbiddenUiFieldSymbols = new[] - { - "AddField(\"Temperature Unit\"", - "AddField(\"Significant Digits\"", - "AddField(\"Nebula Quality\"", - "AddField(\"Show Asteroids in Minimap\"", - "ShowGameplaySettings()", - "ShowGraphicsSettings()" - }; - - var uiFieldHits = File.ReadLines(mainMenuPath) - .Select((line, index) => new { LineNumber = index + 1, Line = line }) - .Where(line => forbiddenUiFieldSymbols.Any(symbol => line.Line.Contains(symbol, StringComparison.Ordinal))) - .Select(line => $"{Path.GetRelativePath(root, mainMenuPath)}:{line.LineNumber}: {line.Line.Trim()}") - .ToArray(); - - if (uiFieldHits.Length > 0) - { - throw new InvalidOperationException( - "MainMenu still uses legacy field widgets for Eve-owned gameplay/graphics settings: " + - string.Join("; ", uiFieldHits)); - } -} - -static void RequireMainMenuSettingsShellUsesEveSurface(string root) -{ - var mainMenuPath = Path.Combine(root, "Assets", "Scripts", "UI", "MainMenu.cs"); - var mainMenuSurfaceBuilderPath = Path.Combine( - root, - "Packages", - "org.gamecult.aetheria.state", - "Runtime", - "AetheriaRuntimeMainMenuSurfaceBuilder.cs"); - if (!File.Exists(mainMenuPath)) - { - throw new InvalidOperationException("Cannot verify main-menu settings shell; MainMenu.cs is missing."); - } - if (!File.Exists(mainMenuSurfaceBuilderPath)) - { - throw new InvalidOperationException("Cannot verify main-menu settings shell; AetheriaRuntimeMainMenuSurfaceBuilder.cs is missing."); - } - - var source = File.ReadAllText(mainMenuPath); - var mainMenuSurfaceBuilder = File.ReadAllText(mainMenuSurfaceBuilderPath); - var requiredSymbols = new[] - { - "RenderMenuSurface(", - "ResolveMainMenuSurface(AetheriaRuntimeMainMenuCommands.SettingsSurfaceId)", - "ResolveMainMenuSurface(AetheriaRuntimeMainMenuCommands.InputSettingsSurfaceId)", - "ResolveMainMenuSurface(AetheriaRuntimeMainMenuCommands.PlayerSettingsSurfaceId)", - "ResolveMainMenuSurface(AetheriaRuntimeMainMenuCommands.VerseSettingsSurfaceId)", - ".MainMenuSurface(surfaceId, CanOpenRuntimeInputScreen(), InGame)", - "HandleSettingsSurfaceCommand(", - "HandleVerseSettingsSurfaceCommand(", - "HandleInputSettingsSurfaceCommand(", - "AetheriaRuntimeMainMenuSurfaceCommands.TryRead(request, out var command)", - "AetheriaRuntimeMainMenuCommandKind.ShowPlayerSettings", - "AetheriaRuntimeMainMenuCommandKind.ShowVerseSettings", - "AetheriaRuntimeMainMenuCommandKind.ShowInputSettings", - "AetheriaRuntimeMainMenuCommandKind.BackToMain", - "AetheriaRuntimeMainMenuCommandKind.BackToSettings", - "AetheriaRuntimeMainMenuCommandKind.OpenRuntimeInputScreen", - "AetheriaEveUnitySurfaceHost.RenderRuntime(", - "AetheriaEveUnitySurfaceHost.Hide(_menuSurfaceDocument)" - }; - - var missingSymbols = requiredSymbols - .Where(symbol => !source.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - - if (missingSymbols.Length > 0) - { - throw new InvalidOperationException( - "MainMenu no longer lowers the settings shell through Eve surfaces: " + - string.Join(", ", missingSymbols)); - } - - var forbiddenSymbols = new[] - { - "BuildSettingsSurfaceDefinition(", - "BuildInputSettingsSurfaceDefinition(", - "BuildPlayerSettingsSurfaceDefinition(", - "BuildVerseSettingsSurfaceDefinition(", - "private static EveSurfaceComponent Button(", - "private static EveSurfaceComponent Card(", - "_nextMenu.panel.AddButton(\"Player Settings\"", - "_nextMenu.panel.AddButton(\"Verse\"", - "_nextMenu.panel.AddButton(\"Input\"", - "_nextMenu.panel.AddButton(\"Audio\"", - "BuildAudioSettingsSurfaceDefinition(", - "HandleAudioSettingsSurfaceCommand(", - "ShowAudioSettingsCommand", - "ResolveMenuSurfaceDocument(", - "new EveUiToolkitSurfaceLowerer", - "host.AddComponent", - "switch (request.Command)", - "string.Equals(request.Command, AetheriaRuntimeMainMenuCommands.BackToSettings", - "string.Equals(request.Command, AetheriaRuntimeMainMenuCommands.OpenRuntimeInputScreen", - "TrySendVerseHostCommand(request.Command)", - "CommandKindForSurface(request)", - "AetheriaRuntimeClientTargetCommands.IsKnown(request.Command", - "ProjectPlayerSettingsSurfaceState(", - "ProjectVerseSettingsSurfaceState(", - "ProjectMainMenuSurfaceState(", - "new AetheriaRuntimePlayerSettingsSurfaceState(", - "new AetheriaRuntimeClientTargetSurfaceState(", - "_nextMenu.panel.Title.text = \"settings\"", - "_nextMenu.panel.Title.text = TitleSubtitle(\"input\", \"settings\")", - "_nextMenu.panel.Title.text = TitleSubtitle(\"audio\", \"settings\")" - }; - - var hits = forbiddenSymbols - .Where(symbol => source.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - - if (hits.Length > 0) - { - throw new InvalidOperationException( - "MainMenu still keeps a fake audio/settings shell or old PropertiesPanel settings shell alive: " + - string.Join(", ", hits)); - } - - var requiredBuilderSymbols = new[] - { - "public static class AetheriaRuntimeMainMenuSurfaceBuilder", - "public static class AetheriaRuntimeMainMenuCommands", - "BuildSettings(", - "BuildInputSettings(", - "BuildPlayerSettings(", - "BuildVerseSettings(", - "private static AetheriaRuntimeSurfaceDocument BuildRoot(", - "private static AetheriaRuntimeSurfaceDocument BuildInputSettings(", - "\"STARBRIDGE\"", - "AetheriaRuntimePlayerSettingsSurfaceBuilder.Build(playerSettings, updatedAtUtc, version)", - "AetheriaRuntimeSurfaceDocument document", - "WithBackAction(", - "AetheriaRuntimeMainMenuCommands.BackToSettings", - "public enum AetheriaRuntimeMainMenuCommandKind", - "public readonly struct AetheriaRuntimeMainMenuCommand", - "public static class AetheriaRuntimeMainMenuSurfaceCommands", - "public static bool TryRead(", - "AetheriaRuntimeMainMenuCommands.ShowPlayerSettings", - "AetheriaRuntimeMainMenuCommands.ShowVerseSettings", - "AetheriaRuntimeMainMenuCommands.ShowInputSettings", - "AetheriaRuntimeMainMenuCommands.BackToSettings" - }; - var missingBuilderSymbols = requiredBuilderSymbols - .Where(symbol => !mainMenuSurfaceBuilder.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (missingBuilderSymbols.Length > 0) - { - throw new InvalidOperationException( - "Shared main-menu surface builder no longer owns the settings/input shell contract: " + - string.Join(", ", missingBuilderSymbols)); - } - - if (mainMenuSurfaceBuilder.Contains("AetheriaRuntimeMainMenuSurfaceState", StringComparison.Ordinal) || - mainMenuSurfaceBuilder.Contains("ComposeRootState(", StringComparison.Ordinal) || - mainMenuSurfaceBuilder.Contains("ComposePlayerSettingsState(", StringComparison.Ordinal) || - mainMenuSurfaceBuilder.Contains("new AetheriaRuntimePlayerSettingsSurfaceState(", StringComparison.Ordinal) || - mainMenuSurfaceBuilder.Contains("public static AetheriaRuntimeMainMenuSurfaceState Project", StringComparison.Ordinal) || - mainMenuSurfaceBuilder.Contains("public static AetheriaRuntimePlayerSettingsSurfaceState Project", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Shared main-menu surface builder must accept typed documents and return finished Eve documents; do not rebuild a shadow main-menu SurfaceState projection layer."); - } -} - -static void RequireMainMenuRootUsesEveSurface(string root) -{ - var mainMenuPath = Path.Combine(root, "Assets", "Scripts", "UI", "MainMenu.cs"); - var mainMenuSurfaceBuilderPath = Path.Combine( - root, - "Packages", - "org.gamecult.aetheria.state", - "Runtime", - "AetheriaRuntimeMainMenuSurfaceBuilder.cs"); - if (!File.Exists(mainMenuPath)) - { - throw new InvalidOperationException("Cannot verify main-menu root shell; MainMenu.cs is missing."); - } - if (!File.Exists(mainMenuSurfaceBuilderPath)) - { - throw new InvalidOperationException("Cannot verify main-menu root shell; AetheriaRuntimeMainMenuSurfaceBuilder.cs is missing."); - } - - var source = File.ReadAllText(mainMenuPath); - var mainMenuSurfaceBuilder = File.ReadAllText(mainMenuSurfaceBuilderPath); - var requiredSymbols = new[] - { - "ResolveMainMenuSurface(AetheriaRuntimeMainMenuCommands.RootSurfaceId)", - ".MainMenuSurface(surfaceId, CanOpenRuntimeInputScreen(), InGame)", - "AetheriaRuntimeMainMenuSurfaceCommands.TryRead(request, out var command)", - "AetheriaRuntimeMainMenuCommandKind.ContinueRun", - "AetheriaRuntimeMainMenuCommandKind.NewGame", - "AetheriaRuntimeMainMenuCommandKind.ShowSettings", - "AetheriaRuntimeMainMenuCommandKind.Quit", - "HandleMainSurfaceCommand(", - "ResolveSectorMap(stateBoot)", - "HideMenuSurface();" - }; - - var missingSymbols = requiredSymbols - .Where(symbol => !source.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - - if (missingSymbols.Length > 0) - { - throw new InvalidOperationException( - "MainMenu no longer lowers the root shell through Eve surfaces: " + - string.Join(", ", missingSymbols)); - } - - if (!mainMenuSurfaceBuilder.Contains("AetheriaRuntimeMainMenuSurfaceBuilder", StringComparison.Ordinal) || - !mainMenuSurfaceBuilder.Contains("public static AetheriaRuntimeSurfaceDocument BuildRoot(", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Shared runtime main-menu surface builder no longer owns the root shell composition."); - } - - var forbiddenSymbols = new[] - { - "PanelPrototype", - "FadeTime", - "FadeDistance", - "FadeAlphaExponent", - "FadePositionExponent", - "_currentMenu", - "_nextMenu", - "_fadeFromRight", - "_fadeLerp", - "_fading", - "_panelPosition", - "TitleSubtitle(", - "IsMenuSurfaceVisible(", - "_nextMenu.panel.AddButton(\"Continue\"", - "_nextMenu.panel.AddButton(\"New Game\"", - "_nextMenu.panel.AddButton(\"Settings\"", - "_nextMenu.panel.AddButton(\"Quit\"", - "BuildMainSurfaceDefinition(", - "private static EveSurfaceDocument BuildMenuSurfaceDocument(", - "switch (request.Command)", - "Fade(true)", - "Fade(false)" - }; - - var hits = forbiddenSymbols - .Where(symbol => source.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - - if (hits.Length > 0) - { - throw new InvalidOperationException( - "MainMenu still owns root navigation through the legacy PropertiesPanel/fade shell: " + - string.Join(", ", hits)); - } - - var prefabPath = Path.Combine(root, "Assets", "Prefabs", "UI", "Main Menu Canvas.prefab"); - if (!File.Exists(prefabPath)) - { - throw new InvalidOperationException("Cannot verify main-menu prefab shell; Main Menu Canvas.prefab is missing."); - } - - var prefab = File.ReadAllText(prefabPath); - var forbiddenPrefabSymbols = new[] - { - "PanelPrototype:", - "FadeTime:", - "FadeDistance:", - "FadeAlphaExponent:", - "FadePositionExponent:" - }; - - var prefabHits = forbiddenPrefabSymbols - .Where(symbol => prefab.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - - if (prefabHits.Length > 0) - { - throw new InvalidOperationException( - "Main-menu prefab still serializes the dead root shell: " + - string.Join(", ", prefabHits)); - } - - var requiredBuilderSymbols = new[] - { - "BuildRoot(", - "AetheriaRuntimeMainMenuCommands.RootSurfaceId", - "AetheriaRuntimeMainMenuCommands.ContinueRun", - "AetheriaRuntimeMainMenuCommands.NewGame", - "AetheriaRuntimeMainMenuCommands.ShowSettings", - "AetheriaRuntimeMainMenuCommands.Quit", - "private static AetheriaRuntimeSurfaceDocument BuildRoot(", - "\"STARBRIDGE\"", - "\"Aetheria Starbridge\"", - "GravitySurface($", - "public enum AetheriaRuntimeMainMenuCommandKind", - "public readonly struct AetheriaRuntimeMainMenuCommand", - "public static class AetheriaRuntimeMainMenuSurfaceCommands", - "public static bool TryRead(" - }; - var missingBuilderSymbols = requiredBuilderSymbols - .Where(symbol => !mainMenuSurfaceBuilder.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (missingBuilderSymbols.Length > 0) - { - throw new InvalidOperationException( - "Shared main-menu surface builder no longer owns the root shell contract: " + - string.Join(", ", missingBuilderSymbols)); - } -} - -static void RequireConfirmationDialogOwnsMinimalPromptShell(string root) -{ - var dialogPath = Path.Combine(root, "Assets", "Scripts", "UI", "ConfirmationDialog.cs"); - if (!File.Exists(dialogPath)) - { - throw new InvalidOperationException("Cannot verify confirmation dialog shell; ConfirmationDialog.cs is missing."); - } - - var source = File.ReadAllText(dialogPath); - var requiredSymbols = new[] - { - "public class ConfirmationDialog : MonoBehaviour", - "public TextMeshProUGUI Title;", - "public RectTransform Content;", - "public Property Property;", - "public InputField InputField;", - "public void Clear()", - "public Property AddProperty(Func read)", - "public void AddField(string name, Func read, Action write)", - "public void AddField(string name, Func read, Action write)", - "public void SetInputGate(Action enableGlobalInput, Action disableGlobalInput)" - }; - - var missingSymbols = requiredSymbols - .Where(symbol => !source.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - - if (missingSymbols.Length > 0) - { - throw new InvalidOperationException( - "ConfirmationDialog no longer owns the minimal prompt shell contract: " + - string.Join(", ", missingSymbols)); - } - - var forbiddenSymbols = new[] - { - ": PropertiesPanel", - "OnPropertyAdded", - "RefreshPropertyValues", - "PropertyLabel", - "Dropdown", - "RangedFloatField", - "BoolField", - "EnumField", - "StatSheet", - "ActionGameManager.Instance" - }; - - var hits = forbiddenSymbols - .Where(symbol => source.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - - if (hits.Length > 0) - { - throw new InvalidOperationException( - "ConfirmationDialog still drags generic PropertiesPanel machinery into the prompt shell: " + - string.Join(", ", hits)); - } - - var prefabBlock = ReadSerializedMonoBehaviourBlock( - Path.Combine(root, "Assets", "Prefabs", "UI", "Main Menu Canvas.prefab"), - "83488dc4b5e58cd40b4d1c4ee98207b5"); - var sceneBlock = ReadSerializedMonoBehaviourBlock( - Path.Combine(root, "Assets", "Scenes", "ARPG.unity"), - "83488dc4b5e58cd40b4d1c4ee98207b5"); - - var forbiddenSerializedFields = new[] - { - "Spacer:", - "Dropdown:", - "Section:", - "List:", - "PropertyLabel:", - "Attribute:", - "RangedFloatField:", - "ProgressField:", - "EnumField:", - "BoolField:", - "PropertyButton:", - "ButtonField:", - "IncrementField:", - "StatSheet:", - "CurveField:", - "SelectedChild:", - "GameManager:" - }; - - foreach (var (label, block) in new[] { ("Main Menu Canvas.prefab", prefabBlock), ("ARPG.unity", sceneBlock) }) - { - var blockHits = forbiddenSerializedFields - .Where(symbol => block.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - - if (blockHits.Length > 0) - { - throw new InvalidOperationException( - $"ConfirmationDialog still serializes dead PropertiesPanel fields in {label}: " + - string.Join(", ", blockHits)); - } - } -} - -static string ReadSerializedMonoBehaviourBlock(string path, string scriptGuid) -{ - if (!File.Exists(path)) - { - throw new InvalidOperationException($"Cannot inspect serialized MonoBehaviour block; file is missing: {path}"); - } - - var lines = File.ReadAllLines(path); - var scriptLine = $"guid: {scriptGuid}"; - for (var index = 0; index < lines.Length; index++) - { - if (!lines[index].Contains(scriptLine, StringComparison.Ordinal)) - { - continue; - } - - var start = index; - while (start >= 0 && !lines[start].StartsWith("--- !u!114 ", StringComparison.Ordinal)) - { - start--; - } - - if (start < 0) - { - break; - } - - var end = start + 1; - while (end < lines.Length && !lines[end].StartsWith("--- !u!", StringComparison.Ordinal)) - { - end++; - } - - return string.Join(Environment.NewLine, lines.Skip(start).Take(end - start)); - } - - throw new InvalidOperationException( - $"Cannot inspect serialized MonoBehaviour block for script guid {scriptGuid} in {path}."); -} - -static void RequireMainMenuInputSettingsDelegateToRuntimeScreen(string root) -{ - var mainMenuPath = Path.Combine(root, "Assets", "Scripts", "UI", "MainMenu.cs"); - var mainMenuSurfaceBuilderPath = Path.Combine( - root, - "Packages", - "org.gamecult.aetheria.state", - "Runtime", - "AetheriaRuntimeMainMenuSurfaceBuilder.cs"); - if (!File.Exists(mainMenuPath)) - { - throw new InvalidOperationException("Cannot verify main-menu input delegation; MainMenu.cs is missing."); - } - if (!File.Exists(mainMenuSurfaceBuilderPath)) - { - throw new InvalidOperationException("Cannot verify main-menu input delegation; AetheriaRuntimeMainMenuSurfaceBuilder.cs is missing."); - } - - var source = File.ReadAllText(mainMenuPath); - var mainMenuSurfaceBuilder = File.ReadAllText(mainMenuSurfaceBuilderPath); - var requiredSymbols = new[] - { - "AetheriaRuntimeMainMenuCommandKind.OpenRuntimeInputScreen", - "SetRuntimeInputScreenShell(Func canOpenRuntimeInputScreen, Action openRuntimeInputScreen)", - "CanOpenRuntimeInputScreen()", - "TryOpenRuntimeInputScreen()", - "ResolveMainMenuSurface(AetheriaRuntimeMainMenuCommands.InputSettingsSurfaceId)", - ".MainMenuSurface(surfaceId, CanOpenRuntimeInputScreen(), InGame)", - "_canOpenRuntimeInputScreen?.Invoke() == true", - "_openRuntimeInputScreen?.Invoke();" - }; - - var missingSymbols = requiredSymbols - .Where(symbol => !source.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - - if (missingSymbols.Length > 0) - { - throw new InvalidOperationException( - "MainMenu input page no longer delegates to the live runtime remap screen: " + - string.Join(", ", missingSymbols)); - } - - var forbiddenSymbols = new[] - { - "Typed input rebinding controls are not lowered through Eve yet.", - "The live remapping screen still owns drag/drop rebinding and low-level InputSystem edits.", - "ActionGameManager.Instance.ShowInputScreenFromMenu();", - "ActionGameManager.Instance.CanShowInputScreenFromMenu()" - }; - - var hits = forbiddenSymbols - .Where(symbol => source.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - - if (hits.Length > 0) - { - throw new InvalidOperationException( - "MainMenu input page still reports the live remap owner as future work instead of delegating to it: " + - string.Join(", ", hits)); - } - - if (!mainMenuSurfaceBuilder.Contains("AetheriaRuntimeMainMenuCommands.OpenRuntimeInputScreen", StringComparison.Ordinal) || - !mainMenuSurfaceBuilder.Contains("bool canOpenRuntimeInputScreen", StringComparison.Ordinal) || - !mainMenuSurfaceBuilder.Contains("if (canOpenRuntimeInputScreen)", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Shared main-menu input surface no longer exposes the runtime input-screen handoff."); - } -} - -static void RequireRuntimeInputScreenUsesEveSurface(string root) -{ - var inputScreenPath = Path.Combine(root, "Assets", "Scripts", "UI", "InputScreen", "InputDisplayLayout.cs"); - var commandsPath = Path.Combine( - root, - "Packages", - "org.gamecult.aetheria.state", - "Runtime", - "AetheriaRuntimeInputSettingsCommands.cs"); - var builderPath = Path.Combine( - root, - "Packages", - "org.gamecult.aetheria.state", - "Runtime", - "AetheriaRuntimeInputSettingsSurfaceBuilder.cs"); - - if (!File.Exists(inputScreenPath)) - { - throw new InvalidOperationException("Cannot verify runtime input-screen Eve lowering; InputDisplayLayout.cs is missing."); - } - - if (!File.Exists(commandsPath) || !File.Exists(builderPath)) - { - throw new InvalidOperationException( - "Cannot verify runtime input-screen Eve lowering; the shared input-settings Eve contract is missing."); - } - - var inputScreen = File.ReadAllText(inputScreenPath); - var commands = File.ReadAllText(commandsPath); - var builder = File.ReadAllText(builderPath); - - var requiredInputScreenSymbols = new[] - { - "AetheriaRuntimeInputSettingsSurfaceBuilder.Build(", - "AetheriaRuntimeInputSettingsSurfaceCommands.TryRead(request, out var command)", - "AetheriaRuntimeInputSettingsCommandKind.BeginCapture", - "AetheriaRuntimeInputSettingsCommandKind.ToggleActionBar", - "AetheriaEveUnitySurfaceHost.RenderRuntime(", - "AetheriaEveUnitySurfaceHost.Hide(_surfaceDocument)", - "UIDocument", - ".Ui(RequireLocalStateBoot(), \"unity-input-screen\")", - ".InputSettingsAsync(command, body, \"unity-input-screen\")", - "AetheriaRuntimeInputSettingsCommandBody", - "AetheriaRuntimeEveCommandKind.SetBindingOverride", - "AetheriaRuntimeEveCommandKind.SetActionBarEnabled", - "RequireLocalStateBoot()", - "AetheriaUnityRuntimeClientProvider", - "AetheriaUnityRuntimeClientProvider.RuntimeState(", - "_runtimeState.PlayerSettings.Latest()", - "action.ApplyBindingOverride", - "new InputAction(\"Aetheria Input Capture\")", - "AetheriaRuntimeInputSettingsSurfaceBuilder.IsSupportedCapturePath(", - "new AetheriaRuntimeObservedInputBinding(", - "HideLegacyChildren()" - }; - - var missingInputScreenSymbols = requiredInputScreenSymbols - .Where(symbol => !inputScreen.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - - if (missingInputScreenSymbols.Length > 0) - { - throw new InvalidOperationException( - "Runtime input screen no longer lowers through the Eve UI Toolkit surface contract: " + - string.Join(", ", missingInputScreenSymbols)); - } - - var forbiddenInputScreenSymbols = new[] - { - "CultMeshReactiveDocument", - ".ReactivePlayerSettingsDocument(", - "AetheriaRuntimePlayerSettingsSession", - ".ObservePlayer()", - "ClearClientCaches()", - "OnPointerClickAsObservable", - "BeginDragTrigger", - "EndDragTrigger", - "VerticalLayoutGroup", - "LayoutRebuilder", - "UILineRenderer", - "Observable.NextFrame()", - "DisplayLayout(_inputLayout)", - "EnsureSurfaceDocument(", - "new EveUiToolkitSurfaceLowerer()", - "host.AddComponent", - "AetheriaRuntimeStateCommitLog.QueuePlayerSettings", - "PlayerSettingsAsync()", - "VerseHostSettingsAsync()", - "ActionGameManager.CommitRuntimeInputBindingOverride", - "ActionGameManager.CommitRuntimeActionBarInput", - "BeginCapture(request.Payload)", - "ToggleActionBarInput(request.Payload)", - "private void BeginCapture(IReadOnlyDictionary payload)", - "private void ToggleActionBarInput(IReadOnlyDictionary payload)", - "request.Payload", - "private static readonly string[] DefaultActionBarCandidatePaths", - "private static bool IsSupportedCapturePath(string path)", - "ProjectSurfaceState(", - "AetheriaRuntimeInputSettingsSurfaceBuilder.Project(", - "new AetheriaRuntimeInputSettingsSurfaceState(", - "new AetheriaRuntimeInputBindingSurfaceState(", - "new AetheriaRuntimeActionBarInputSurfaceState(", - "public sealed class AetheriaRuntimeInputSettingsSurfaceState", - "public sealed class AetheriaRuntimeInputBindingSurfaceState", - "public sealed class AetheriaRuntimeActionBarInputSurfaceState", - "AetheriaRuntimeInputSettingsSurfaceBuilder.ProjectActionBarInputs(", - "AetheriaRuntimeInputSettingsSurfaceBuilder.ProjectActionBarCandidates(", - "new SortedDictionary(StringComparer.Ordinal)", - "private IReadOnlyList ProjectActionBarCandidates(" - }; - - var survivingLegacySymbols = forbiddenInputScreenSymbols - .Where(symbol => inputScreen.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - - if (survivingLegacySymbols.Length > 0) - { - throw new InvalidOperationException( - "Runtime input screen still carries the old drag/drop uGUI authority path: " + - string.Join(", ", survivingLegacySymbols)); - } - - if (!commands.Contains("SurfaceId = \"aetheria.input_settings\"", StringComparison.Ordinal) || - !commands.Contains("BeginCapture", StringComparison.Ordinal) || - !commands.Contains("ToggleActionBar", StringComparison.Ordinal) || - !commands.Contains("SetBindingOverride", StringComparison.Ordinal) || - !commands.Contains("SetActionBarEnabled", StringComparison.Ordinal) || - !commands.Contains("public static bool IsKnown", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Shared input-settings command contract is incomplete."); - } - - var requiredBuilderSymbols = new[] - { - "public sealed class AetheriaRuntimeInputPathSurfaceLabel", - "public sealed class AetheriaRuntimeObservedInputBinding", - "public static readonly IReadOnlyList DefaultActionBarCandidatePaths", - "public static readonly IReadOnlyList DefaultActionBarCandidateInputPaths", - "public static bool IsSupportedCapturePath(string path)", - "public static AetheriaRuntimeSurfaceDocument Build(", - "private static AetheriaRuntimeSurfaceDocument Build(", - "private static IReadOnlyList ComposeBindingInputs(", - "private static IReadOnlyList ComposeActionBarCandidates(", - "private static IReadOnlyList ComposeActionBarInputs(", - "private sealed class InputBindingRow", - "private sealed class ActionBarInputRow", - "new SortedDictionary(StringComparer.Ordinal)", - "public enum AetheriaRuntimeInputSettingsCommandKind", - "public readonly struct AetheriaRuntimeInputSettingsSurfaceCommand", - "public static class AetheriaRuntimeInputSettingsSurfaceCommands", - "public static bool TryRead(", - "AetheriaRuntimeInputSettingsCommands.BeginCapture", - "AetheriaRuntimeInputSettingsCommands.ToggleActionBar", - "ReadString(request, \"actionName\")", - "ReadInt(request, \"bindingIndex\", -1)", - "ReadString(request, \"inputPath\")", - "ReadBool(request, \"enabled\", false)", - "\"Low-level InputSystem edits flow through this Eve surface as typed input-setting requests.\"" - }; - - var missingBuilderSymbols = requiredBuilderSymbols - .Where(symbol => !builder.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - - if (missingBuilderSymbols.Length > 0) - { - throw new InvalidOperationException( - "Shared input-settings Eve surface builder is incomplete: " + - string.Join(", ", missingBuilderSymbols)); - } - - if (builder.Contains("AetheriaRuntimeInputSettingsSurfaceState", StringComparison.Ordinal) || - builder.Contains("AetheriaRuntimeInputBindingSurfaceState", StringComparison.Ordinal) || - builder.Contains("AetheriaRuntimeActionBarInputSurfaceState", StringComparison.Ordinal) || - builder.Contains("ComposeState(", StringComparison.Ordinal) || - builder.Contains("public static AetheriaRuntimeInputSettingsSurfaceState Project(", StringComparison.Ordinal) || - builder.Contains("public static IReadOnlyList ProjectBindingInputs(", StringComparison.Ordinal) || - builder.Contains("public static IReadOnlyList ProjectActionBarCandidates(", StringComparison.Ordinal) || - builder.Contains("public static IReadOnlyList ProjectActionBarInputs(", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Input-settings surface builder must emit the typed surface document directly; do not rebuild a shadow SurfaceState projection layer."); - } - - var actionGameManagerPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "ActionGameManager.cs"); - var actionGameManager = File.Exists(actionGameManagerPath) - ? File.ReadAllText(actionGameManagerPath) - : throw new InvalidOperationException("Cannot verify runtime input-screen authority; ActionGameManager.cs is missing."); - - var requiredInputSettingsSubmitSymbols = new[] - { - "SendInputSettingsCommand(", - "AetheriaUnityRuntimeClientProvider", - ".Ui(RequireLocalStateBoot(), \"unity-input-screen\")", - ".InputSettingsAsync(", - "AetheriaRuntimeEveCommandKind.SetBindingOverride", - "AetheriaRuntimeEveCommandKind.SetActionBarEnabled" - }; - - var missingInputSettingsSubmitSymbols = requiredInputSettingsSubmitSymbols - .Where(symbol => !inputScreen.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - - if (missingInputSettingsSubmitSymbols.Length > 0) - { - throw new InvalidOperationException( - "Runtime input writes no longer send explicit Eve input-setting commands: " + - string.Join(", ", missingInputSettingsSubmitSymbols)); - } - - var forbiddenActionGameManagerSymbols = new[] - { - "RequestRuntimeInputBindingOverride", - "RequestRuntimeActionBarInput", - "SendRuntimeInputSettingsCommand(", - "public static void CommitRuntimeInputBindingOverride", - "public static void CommitRuntimeActionBarInput", - "TrySendRuntimeInputSettingsCommand(", - "private static bool TrySendRuntimeInputSettingsCommand(", - "RuntimePlayerSettings.InputSettings.SetBindingOverride(actionName, bindingIndex, inputSystemPath)", - "RuntimePlayerSettings.InputSettings.SetActionBarInputEnabled(inputSystemPath, enabled)" - }; - - var survivingActionGameManagerSymbols = forbiddenActionGameManagerSymbols - .Where(symbol => actionGameManager.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - - if (survivingActionGameManagerSymbols.Length > 0) - { - throw new InvalidOperationException( - "Runtime input settings are still using Unity-local mutation authority instead of typed Eve requests: " + - string.Join(", ", survivingActionGameManagerSymbols)); - } -} - -static void RequireActionGameManagerInputScreenUsesSharedFullscreenPrimitive(string root) -{ - var managerPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "ActionGameManager.cs"); - var sceneWiringPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "AetheriaUnityGameplaySceneWiring.cs"); - var menuShellPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "AetheriaUnityMenuShell.cs"); - var gameplayInputShellPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "AetheriaUnityGameplayInputShell.cs"); - if (!File.Exists(managerPath)) - { - throw new InvalidOperationException("Cannot verify ActionGameManager input-screen delegation; ActionGameManager.cs is missing."); - } - if (!File.Exists(sceneWiringPath)) - { - throw new InvalidOperationException("Cannot verify Unity scene wiring input-screen delegation; AetheriaUnityGameplaySceneWiring.cs is missing."); - } - if (!File.Exists(menuShellPath)) - { - throw new InvalidOperationException("Cannot verify Unity menu shell input-screen delegation; AetheriaUnityMenuShell.cs is missing."); - } - if (!File.Exists(gameplayInputShellPath)) - { - throw new InvalidOperationException("Cannot verify Unity gameplay input shell delegation; AetheriaUnityGameplayInputShell.cs is missing."); - } - - var source = File.ReadAllText(managerPath); - var sceneWiring = File.ReadAllText(sceneWiringPath); - var menuShell = File.ReadAllText(menuShellPath); - var gameplayInputShell = File.ReadAllText(gameplayInputShellPath); - var requiredManagerSymbols = new[] - { - "SceneWiring.ConfigureRuntimeInputScreenShell(MenuShell)", - "GameplayInputShell.Bootstrap();" - }; - - var missingManagerSymbols = requiredManagerSymbols - .Where(symbol => !source.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - - if (missingManagerSymbols.Length > 0) - { - throw new InvalidOperationException( - "ActionGameManager no longer wires the Unity menu shell as the live input-screen owner: " + - string.Join(", ", missingManagerSymbols)); - } - - var requiredSceneWiringSymbols = new[] - { - "public sealed class AetheriaUnityGameplaySceneWiring", - "public void ConfigureRuntimeInputScreenShell(AetheriaUnityMenuShell menuShell)", - "MainMenu?.SetRuntimeInputScreenShell(menuShell.CanOpenRuntimeInputScreen, menuShell.ShowRuntimeInputScreen)" - }; - - var missingSceneWiringSymbols = requiredSceneWiringSymbols - .Where(symbol => !sceneWiring.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (missingSceneWiringSymbols.Length > 0) - { - throw new InvalidOperationException( - "AetheriaUnityGameplaySceneWiring no longer wires the Unity menu shell as the live input-screen owner: " + - string.Join(", ", missingSceneWiringSymbols)); - } - - var requiredShellSymbols = new[] - { - "public sealed class AetheriaUnityMenuShell", - "public bool CanOpenRuntimeInputScreen()", - "public void ShowRuntimeInputScreen()", - "public void ToggleFullscreenMenu(GameObject menu)", - "private void ShowFullscreenMenu(GameObject menu)", - "private void HideFullscreenMenu(GameObject menu)" - }; - - var missingShellSymbols = requiredShellSymbols - .Where(symbol => !menuShell.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (missingShellSymbols.Length > 0) - { - throw new InvalidOperationException( - "AetheriaUnityMenuShell no longer owns the shared fullscreen primitive for the live input-screen owner: " + - string.Join(", ", missingShellSymbols)); - } - - var requiredGameplayInputShellSymbols = new[] - { - "public sealed class AetheriaUnityGameplayInputShell", - "public AetheriaUnityMenuShell MenuShell { get; set; }", - "Input.Global.InputScreen.performed += context => MenuShell.ToggleFullscreenMenu(MenuShell.HelpScreen);", - "Input.Global.ZoneMap.performed += context =>", - "Input.Player.TargetNearest.performed += context => PilotOperationController.RequestTargetNearest();", - "private void RegisterActionBarInput()", - "DragSession.RegisterTarget(dragAction => ActionBarPresentation.RequestBinding(slot, dragAction));" - }; - - var missingGameplayInputShellSymbols = requiredGameplayInputShellSymbols - .Where(symbol => !gameplayInputShell.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (missingGameplayInputShellSymbols.Length > 0) - { - throw new InvalidOperationException( - "AetheriaUnityGameplayInputShell no longer owns Unity input callbacks and routes them through typed shell dependencies: " + - string.Join(", ", missingGameplayInputShellSymbols)); - } - - var forbiddenSymbols = new[] - { - "public bool CanShowInputScreenFromMenu()", - "public void ShowInputScreenFromMenu()", - "private bool CanOpenRuntimeInputScreen()", - "private void ShowRuntimeInputScreen()", - "private void ShowFullscreenMenu(GameObject menu)", - "private void HideFullscreenMenu(GameObject menu)", - "Input.Global.InputScreen.performed += context", - "Input.Global.ZoneMap.performed += context", - "Input.Player.TargetNearest.performed += context", - "new InputAction(binding: controlPath)", - "OnPointerEnterAsObservable()" - }; - var hits = forbiddenSymbols - .Where(symbol => source.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (hits.Length > 0) - { - throw new InvalidOperationException( - "ActionGameManager still exposes runtime input-screen menu handoff as public manager API: " + - string.Join(", ", hits)); - } -} - -static void RequireSectorMapZoneDetailsUseEveSurface(string root) -{ - var sectorRendererPath = Path.Combine(root, "Assets", "Scripts", "UI", "Menu", "SectorRenderer.cs"); - var zoneDetailsSurfaceBuilderPath = Path.Combine( - root, - "Packages", - "org.gamecult.aetheria.state", - "Runtime", - "AetheriaRuntimeZoneDetailsSurfaceBuilder.cs"); - if (!File.Exists(sectorRendererPath)) - { - throw new InvalidOperationException("Cannot verify sector-map zone details shell; SectorRenderer.cs is missing."); - } - if (!File.Exists(zoneDetailsSurfaceBuilderPath)) - { - 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(", - "HandleZoneDetailsSurfaceCommand(", - "HideZoneDetailsSurface(", - "AetheriaEveUnitySurfaceHost.RenderRuntime(", - "AetheriaEveUnitySurfaceHost.Hide(_zoneDetailsSurfaceDocument)", - "AetheriaUnityRuntimeClientProvider", - "AetheriaUnityRuntimeClientProvider.RuntimeState(\"unity-sector-renderer\")", - "RuntimeState.ZoneDetailsSurface(zoneIndex).Latest()", - "RuntimeState.CurrentZone.Latest()", - "AetheriaRuntimeZoneDetailsSurfaceCommands.TryRead(request, out var command)", - "AetheriaRuntimeZoneDetailsCommandKind.Close" - }; - - var missingSymbols = requiredSymbols - .Where(symbol => !source.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - - if (missingSymbols.Length > 0) - { - throw new InvalidOperationException( - "SectorRenderer no longer lowers zone details through an Eve surface shell: " + - string.Join(", ", missingSymbols)); - } - - var 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\")", - ".ReactiveZoneDetails(zoneIndex, \"unity-sector-renderer\")", - ".ReactiveCatalogSnapshot(\"unity-sector-renderer\")", - ".ReactivePlayerSettingsDocument(\"unity-sector-renderer\")", - "AetheriaRuntimeSectorMapSession", - "AetheriaRuntimeCurrentZoneSession", - "AetheriaRuntimeZoneDetailsSession", - "AetheriaRuntimeCatalogSession", - "AetheriaRuntimePlayerSettingsSession", - ".ObserveSectorMap()", - ".ObserveZone()", - ".ObserveZone(zoneIndex)", - "ResolveClient().State.ObserveCatalog()", - ".ObservePlayer()", - "ClearClientCaches()" - } - .Where(symbol => source.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (forbiddenAccessSymbols.Length > 0) - { - throw new InvalidOperationException( - "SectorRenderer should sample sector UI documents through named current typed state instead of owning reactive/session handles: " + - string.Join(", ", forbiddenAccessSymbols)); - } - - var forbiddenSymbols = new[] - { - "BuildZoneDetailsSurfaceDefinition(", - "ZoneDetailsSurfaceId", - "CloseZoneDetailsCommand", - "private const string ZoneDetailsSurfaceType", - "ResolveZoneDetailsSurfaceDocument(", - "new EveUiToolkitSurfaceLowerer()", - "Properties.Clear();", - "Properties.Title.text = zone.Name;", - "Properties.AddProperty(\"Owner\"", - "Properties.AddProperty(\"Mass\"", - "Properties.AddProperty(\"Radius\"", - "Properties.AddProperty(\"Planets\"", - "Properties.AddProperty(\"Asteroid Belts\"", - "Properties.AddProperty(\"Gas Giants\"", - "Properties.AddProperty(\"Stars\"", - "Properties.AddProperty(\"Stations\"", - "Properties.AddProperty(\"Turrets\"", - "Properties.AddProperty(\"Ships\"", - "GameManager.Settings.ZoneSettings.ZoneRadius.Evaluate(", - "GameManager.Settings.ZoneSettings.ZoneMass.Evaluate(", - "OpenRuntimeCatalog()", - "ProjectZoneBodies(", - "ProjectZoneEntities(", - "ComposeZoneDetailsSurfaceState(", - "ProjectZoneDetailsSurfaceState(", - "ProjectDaemonZone(", - "AetheriaRuntimeZoneDetailsSurfaceBuilder.Compose(", - "AetheriaRuntimeZoneDetailsSurfaceBuilder.Project(", - "AetheriaRuntimeZoneDetailsDaemonProjection", - "new AetheriaRuntimeZoneDetailsBodyProjection(", - "new AetheriaRuntimeZoneDetailsEntityProjection(", - "runtimeZone.PlanetInstances", - "runtimeZone.AsteroidBelts", - "runtimeZone.Entities", - "private static bool IsBodyKind(", - "private static bool IsPlanetBody(", - "private static bool HasHullType(", - "string.Equals(request.Command, AetheriaRuntimeZoneDetailsSurfaceBuilder.Close" - }; - - var hits = forbiddenSymbols - .Where(symbol => source.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - - if (hits.Length > 0) - { - throw new InvalidOperationException( - "SectorRenderer still owns zone details through the old PropertiesPanel path: " + - string.Join(", ", hits)); - } - - var requiredBuilderSymbols = new[] - { - "public static class AetheriaRuntimeZoneDetailsSurfaceBuilder", - "public sealed class AetheriaRuntimeZoneDetailsFacts", - "public sealed class AetheriaRuntimeZoneDetailsBodyFacts", - "public sealed class AetheriaRuntimeZoneDetailsEntityFacts", - "public static AetheriaRuntimeZoneDetailsFacts Facts(", - "Math.Max(0, zone.GravityTerrainRadius)", - ".Sum(body => body.Mass)", - "public static AetheriaRuntimeSurfaceDocument Build(", - "var bodyList = (bodies ?? Array.Empty())", - "var sortedFactions = (otherFactions ?? Array.Empty())", - "var planets = bodyList.Count(IsPlanetBody).ToString()", - "var stations = entityList.Count(entity => HasHullType(entity, \"Station\")).ToString()", - "private static bool IsBodyKind(", - "private static bool IsPlanetBody(", - "private static bool HasHullType(", - "public const string SurfaceId = \"aetheria.sector_map.zone_details\"", - "public const string Close = \"aetheria.sector_map.zone_details.close\"", - "public enum AetheriaRuntimeZoneDetailsCommandKind", - "public readonly struct AetheriaRuntimeZoneDetailsCommand", - "public static class AetheriaRuntimeZoneDetailsSurfaceCommands", - "public static bool TryRead(", - "public static AetheriaRuntimeSurfaceDocument Build(", - "providerKind: \"sector.map\"", - "Factions Present", - "Has not been visited.", - "Asteroid Belts" - }; - var missingBuilderSymbols = requiredBuilderSymbols - .Where(symbol => !zoneDetailsSurfaceBuilder.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (missingBuilderSymbols.Length > 0) - { - throw new InvalidOperationException( - "Shared runtime zone details surface builder no longer owns the sector-map zone shell contract: " + - string.Join(", ", missingBuilderSymbols)); - } - - if (zoneDetailsSurfaceBuilder.Contains("AetheriaRuntimeZoneDetailsSurfaceState", StringComparison.Ordinal) || - zoneDetailsSurfaceBuilder.Contains("ComposeState(", StringComparison.Ordinal) || - zoneDetailsSurfaceBuilder.Contains("public static AetheriaRuntimeZoneDetailsSurfaceState Compose(", StringComparison.Ordinal) || - zoneDetailsSurfaceBuilder.Contains("public static AetheriaRuntimeZoneDetailsSurfaceState Project(", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Runtime zone details must emit the typed surface document directly; do not rebuild a shadow SurfaceState projection layer."); - } -} - -static void RequireRuntimeMenuTabsUseEveSurface(string root) -{ - var menuPanelPath = Path.Combine(root, "Assets", "Scripts", "UI", "Menu", "MenuPanel.cs"); - var localMenuPath = Path.Combine(root, "Assets", "Scripts", "UI", "Menu", "LocalMenu.cs"); - var actionGameManagerPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "ActionGameManager.cs"); - var legacyMenuTabsSurfaceBuilderPath = Path.Combine( - root, - "Packages", - "org.gamecult.aetheria.state", - "Runtime", - "AetheriaRuntimeMenuTabsSurfaceBuilder.cs"); - var localStorySurfaceBuilderPath = Path.Combine( - root, - "Packages", - "org.gamecult.aetheria.state", - "Runtime", - "AetheriaRuntimeLocalStorySurfaceBuilder.cs"); - if (!File.Exists(menuPanelPath)) - { - throw new InvalidOperationException("Cannot verify runtime menu tab shell; MenuPanel.cs is missing."); - } - if (!File.Exists(localMenuPath)) - { - throw new InvalidOperationException("Cannot verify runtime local-menu shell; LocalMenu.cs is missing."); - } - if (!File.Exists(actionGameManagerPath)) - { - throw new InvalidOperationException("Cannot verify runtime menu tab shell; ActionGameManager.cs is missing."); - } - if (!File.Exists(localStorySurfaceBuilderPath)) - { - throw new InvalidOperationException("Cannot verify runtime local story shell; AetheriaRuntimeLocalStorySurfaceBuilder.cs is missing."); - } - - var legacyTabButtonPath = Path.Combine(root, "Assets", "Scripts", "UI", "Menu", "MenuTabButton.cs"); - if (File.Exists(legacyTabButtonPath)) - { - throw new InvalidOperationException("MenuPanel tab metadata still has a surviving MenuTabButton component shell."); - } - - var source = File.ReadAllText(menuPanelPath); - var localMenu = File.ReadAllText(localMenuPath); - var actionGameManager = File.ReadAllText(actionGameManagerPath); - var surfaceHostPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.eve-runtime", "Runtime", "AetheriaEveUnitySurfaceHost.cs"); - if (!File.Exists(surfaceHostPath)) - { - throw new InvalidOperationException("Cannot verify runtime menu tab shell; AetheriaEveUnitySurfaceHost.cs is missing."); - } - - var localStorySurfaceBuilder = File.ReadAllText(localStorySurfaceBuilderPath); - var surfaceHost = File.ReadAllText(surfaceHostPath); - var requiredSymbols = new[] - { - "MenuTabBinding", - "TabBindings = Array.Empty();", - "RenderTabSurface(", - "HandleTabSurfaceCommand(", - "AetheriaEveUnitySurfaceHost.RenderRuntime(", - "AetheriaEveUnitySurfaceHost.Hide(_tabSurfaceDocument)", - "BuildTabSurfaceDocument(", - "new AetheriaRuntimeSurfaceDocument(", - "new AetheriaRuntimeSurfaceTree(", - "new AetheriaRuntimeSurfaceCommandTemplate(", - "ResolveVisibleTabs(", - "TryResolveCurrentDocking(out var docking)", - "AetheriaUnityRuntimeClientProvider.RuntimeState(\"unity-runtime-menu-tabs\")", - "_runtimeState.CurrentDocking.Latest()", - "docking.IsDocked", - "GetTabLabel(", - "ToRuntimeTabKey(", - "NormalizeTabKey(tab.ToString())", - "TryReadTabSurfaceCommand(request, out var tabKey)", - "string.Equals(tabKey, ToRuntimeTabKey(tab), StringComparison.Ordinal)", - "TabButtons.gameObject.SetActive(false)" - }; - - var missingSymbols = requiredSymbols - .Where(symbol => !source.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - - if (missingSymbols.Length > 0) - { - throw new InvalidOperationException( - "MenuPanel no longer lowers the runtime tab shell through Eve surfaces: " + - string.Join(", ", missingSymbols)); - } - - var requiredHostSymbols = new[] - { - "public static class AetheriaEveUnitySurfaceHost", - "public static UIDocument Render(", - "new EveUiToolkitSurfaceLowerer(new EveUiToolkitSurfaceOptions(embeddedDocumentResolver))", - "lowerer.Lower(surface, commandHandler)", - "shell.Add(lowered)", - "root.Add(lowered)" - }; - - var missingHostSymbols = requiredHostSymbols - .Where(symbol => !surfaceHost.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - - if (missingHostSymbols.Length > 0) - { - throw new InvalidOperationException( - "AetheriaEveUnitySurfaceHost no longer owns the shared Unity Eve lowering path: " + - string.Join(", ", missingHostSymbols)); - } - - var forbiddenSymbols = new[] - { - "MenuTabButton", - "tabButton.Button.onClick.AddListener(", - "tabButton.gameObject.SetActive(!tabButton.RequireDock || GameManager.DockedEntity != null);", - "_tabs[MenuTab.Local].gameObject.SetActive(", - "BuildTabSurfaceDefinition(", - "private static EveSurfaceComponent Button(", - "private static EveSurfaceComponent Text(", - "private static EveSurfaceComponent Node(", - "ResolveTabSurfaceDocument(", - "new EveUiToolkitSurfaceLowerer()", - "string.Equals(request.Command, AetheriaRuntimeMenuTabsSurfaceBuilder.CommandFor(", - "AetheriaRuntimeMenuTabsSurfaceBuilder", - "AetheriaRuntimeMenuTabsSurfaceCommands", - "AetheriaRuntimeMenuTabModelOption", - "AetheriaRuntimeMenuTabCommandKind", - "ResolveClient().State.CurrentDocking()", - "CultMeshReactiveDocument _currentDocking", - ".ReactiveCurrentDocking(\"unity-runtime-menu-tabs\")", - "docking = _currentDocking.Current", - "AetheriaRuntimeObservedDockingState", - "SetPresentationEntityIndex(AetheriaUnityPresentationEntityIndex presentationEntityIndex)", - "private AetheriaUnityPresentationEntityIndex _presentationEntityIndex;", - "private AetheriaUnityObservedDockingIndex _observedDockingIndex;", - "TryResolveObservedDockingIndex(out AetheriaUnityObservedDockingIndex dockingIndex)", - "new AetheriaUnityObservedDockingIndex(_presentationEntityIndex)", - "ComposeTabSurface(", - "ProjectTabSurfaceState(", - "ProjectTabSurface(", - "AetheriaRuntimeMenuTabsSurfaceBuilder.Compose(", - "AetheriaRuntimeMenuTabsSurfaceBuilder.Project(", - "new AetheriaRuntimeMenuTabProjectionOption(", - "private static string TabKey(" - }; - - var hits = forbiddenSymbols - .Where(symbol => source.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - - if (hits.Length > 0) - { - throw new InvalidOperationException( - "MenuPanel still owns tab-shell behavior through the old MenuTabButton path: " + - string.Join(", ", hits)); - } - - 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("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."); - } - - if (File.Exists(legacyMenuTabsSurfaceBuilderPath)) - { - throw new InvalidOperationException( - "Runtime menu tabs are client-local UI; do not keep a shared MenuTabsSurfaceBuilder or projection shell for them."); - } - - var requiredDockedStoryObserverSymbols = new[] - { - "TryResolveDockedLocalInkStory(out _currentLocalInkLocation)", - "private bool TryResolveDockedLocalInkStory(out LocationStory story)", - "SetPresentationEntityIndex(AetheriaUnityPresentationEntityIndex presentationEntityIndex)", - "CurrentDockingSnapshot()", - "AetheriaUnityRuntimeClientProvider.RuntimeState(\"unity-local-menu\")", - "_runtimeState.CurrentDocking.Latest()", - "docking.IsDocked", - "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 }", - "AetheriaRuntimeLocalStorySurfaceBuilder.Build(", - "AetheriaRuntimeLocalStorySurfaceCommands.TryRead(request, out var command)", - "AetheriaRuntimeLocalStoryCommandKind.Continue", - "AetheriaRuntimeLocalStoryCommandKind.Choose", - "new AetheriaRuntimeLocalStoryChoiceState(" - }; - var dockedStoryObserverCorpus = localMenu; - var missingDockedStoryObserverSymbols = requiredDockedStoryObserverSymbols - .Where(symbol => !dockedStoryObserverCorpus.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (missingDockedStoryObserverSymbols.Length > 0) - { - throw new InvalidOperationException( - "Runtime menu/local story visibility no longer flows through typed docking state plus explicit Unity presentation lookup: " + - string.Join(", ", missingDockedStoryObserverSymbols)); - } - - if (source.Contains("GameManager.DockedEntity != null", StringComparison.Ordinal) || - 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(_presentationEntityIndex)", StringComparison.Ordinal) || - localMenu.Contains("dockingIndex.TryResolveCurrentDockingBay", StringComparison.Ordinal) || - localMenu.Contains("ActionGameManager.Instance.DockedEntity", StringComparison.Ordinal) || - localMenu.Contains("TryGetObservedDockedLocalStory", StringComparison.Ordinal) || - actionGameManager.Contains("TryGetObservedDockedLocalStory", StringComparison.Ordinal) || - actionGameManager.Contains("IsObservedDocked", StringComparison.Ordinal) || - localMenu.Contains("ProjectStorySurface(", StringComparison.Ordinal) || - localMenu.Contains("AetheriaRuntimeLocalStorySurfaceBuilder.Project(", StringComparison.Ordinal) || - localMenu.Contains("Instantiate(ChoicePrefab", StringComparison.Ordinal) || - localMenu.Contains("choiceInstance.Button.onClick.AddListener", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Runtime menu/local UI must not inspect DockedEntity, rebuild legacy prefab buttons, or carry a local story projection helper; use typed docking and build the Eve local-story surface directly."); - } - - var requiredLocalStoryBuilderSymbols = new[] - { - "public static class AetheriaRuntimeLocalStorySurfaceBuilder", - "public const string SurfaceId = \"aetheria.runtime_menu.local_story\"", - "public const string Continue = \"aetheria.runtime_menu.local_story.continue\"", - "ChoiceCommandFor(int choiceIndex)", - "public static AetheriaRuntimeSurfaceDocument Build(", - "var storyBody = string.IsNullOrWhiteSpace(body) ? \"...\" : body.Trim()", - "var orderedChoices = (choices ?? Array.Empty())", - "public enum AetheriaRuntimeLocalStoryCommandKind", - "public readonly struct AetheriaRuntimeLocalStoryCommand", - "public static class AetheriaRuntimeLocalStorySurfaceCommands", - "AetheriaRuntimeLocalStoryChoiceState" - }; - var missingLocalStoryBuilderSymbols = requiredLocalStoryBuilderSymbols - .Where(symbol => !localStorySurfaceBuilder.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (missingLocalStoryBuilderSymbols.Length > 0) - { - throw new InvalidOperationException( - "Shared runtime local story surface builder no longer owns the local story shell contract: " + - string.Join(", ", missingLocalStoryBuilderSymbols)); - } - - if (localStorySurfaceBuilder.Contains("AetheriaRuntimeLocalStorySurfaceState", StringComparison.Ordinal) || - localStorySurfaceBuilder.Contains("ComposeState(", StringComparison.Ordinal) || - localStorySurfaceBuilder.Contains("public static AetheriaRuntimeLocalStorySurfaceState Project(", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Runtime local story composition must emit the typed surface document directly; do not rebuild a shadow SurfaceState projection layer."); - } - -} - -static void RequireInventoryShipSettingsUseEveSurface(string root) -{ - var inventoryMenuPath = Path.Combine(root, "Assets", "Scripts", "UI", "Menu", "InventoryMenu.cs"); - var shipSettingsSurfaceBuilderPath = Path.Combine( - root, - "Packages", - "org.gamecult.aetheria.state", - "Runtime", - "AetheriaRuntimeShipSettingsSurfaceBuilder.cs"); - if (!File.Exists(inventoryMenuPath)) - { - throw new InvalidOperationException("Cannot verify inventory ship-settings shell; InventoryMenu.cs is missing."); - } - if (!File.Exists(shipSettingsSurfaceBuilderPath)) - { - throw new InvalidOperationException("Cannot verify inventory ship-settings shell; shared runtime ship settings surface builder is missing."); - } - - var source = File.ReadAllText(inventoryMenuPath); - var shipSettingsSurfaceBuilder = File.ReadAllText(shipSettingsSurfaceBuilderPath); - var requiredSymbols = new[] - { - "RenderCurrentShipSettingsSurface(", - "HandleCurrentShipSettingsSurfaceCommand(", - "AetheriaEveUnitySurfaceHost.RenderRuntime(", - "AetheriaEveUnitySurfaceHost.Hide(_shipSettingsSurfaceDocument)", - "AetheriaRuntimeShipSettingsSurfaceBuilder.Build(", - "currentEntity?.Entity?.DisplayName", - "(float)(currentEntity?.ShutdownPerformance ?? 0)", - "AetheriaRuntimeShipSettingsSurfaceCommands.TryRead(request, out var command)", - "AetheriaRuntimeShipSettingsCommandKind.DecrementShutdownThreshold", - "AetheriaRuntimeShipSettingsCommandKind.IncrementShutdownThreshold", - "AetheriaRuntimeShipSettingsCommandKind.ResetShutdownThreshold", - "AetheriaRuntimeShipSettingsCommandKind.Close", - "AetheriaRuntimeShipSettingsSurfaceCommands.ResolveShutdownPerformance(", - "ResolveDefaultShutdownPerformance()", - "PlayerSettingsSnapshot()?.DefaultShutdownPerformance", - "TryReadCurrentEntity(out var currentEntity)", - "SetPresentationEntityIndex(AetheriaUnityPresentationEntityIndex presentationEntityIndex)", - "private AetheriaRuntimeCurrentEntityDocument _shipSettingsCurrentEntity;", - "!TryReadCurrentEntity(out var latestCurrentEntity)", - "RequestEntityShutdownPerformance(", - "latestCurrentEntity.EntityKey", - "(float)latestCurrentEntity.ShutdownPerformance", - "RuntimeState.CurrentEntity.Latest()", - "CurrentEntitySnapshot()", - "currentEntity = CurrentEntitySnapshot();" - }; - - var missingSymbols = requiredSymbols - .Where(symbol => !source.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - - if (missingSymbols.Length > 0) - { - throw new InvalidOperationException( - "InventoryMenu no longer lowers current ship settings through an Eve surface shell: " + - string.Join(", ", missingSymbols)); - } - - var forbiddenSymbols = new[] - { - "BuildCurrentShipSettingsSurfaceDefinition(", - "ShipSettingsSurfaceId", - "DecrementShutdownThresholdCommand", - "IncrementShutdownThresholdCommand", - "ResetShutdownThresholdCommand", - "CloseShipSettingsCommand", - "private const string ShipSettingsSurfaceType", - "ResolveShipSettingsSurfaceDocument(", - "new EveUiToolkitSurfaceLowerer()", - "host.AddComponent", - "PropertiesPanel.AddField(\"Shutdown Threshold\"", - "() => GameManager.CurrentEntity.Settings.ShutdownPerformance", - "f => GameManager.RequestEntityShutdownPerformance(GameManager.CurrentEntity, f)", - "switch (request.Command)", - "ShutdownThresholdStep", - "Mathf.Clamp01(entity.Settings.ShutdownPerformance", - "Mathf.Clamp01(GameManager.Settings.GameplaySettings.DefaultShutdownPerformance", - "GameManager.Settings.GameplaySettings.DefaultShutdownPerformance", - "var entity = GameManager.CurrentEntity", - "GameManager.CurrentEntity == null", - "GameManager.TryGetObservedCurrentEntity(", - "RenderCurrentShipSettingsSurface(GameManager.CurrentEntity)", - "private Entity _shipSettingsEntity", - "var entity = _shipSettingsEntity;", - "entity == null || !IsCurrentEntity(entity)", - "entity.Settings.ShutdownPerformance", - "new AetheriaRuntimeShipSettingsSurfaceState(", - "ComposeCurrentShipSettingsSurface(", - "ProjectCurrentShipSettingsSurface(", - "AetheriaRuntimeShipSettingsSurfaceBuilder.Compose(", - "AetheriaRuntimeShipSettingsSurfaceBuilder.Project(" - }; - - var hits = forbiddenSymbols - .Where(symbol => source.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - - if (hits.Length > 0) - { - throw new InvalidOperationException( - "InventoryMenu still owns ship settings through the old PropertiesPanel field path: " + - string.Join(", ", hits)); - } - - var requiredBuilderSymbols = new[] - { - "public static class AetheriaRuntimeShipSettingsSurfaceBuilder", - "public const string SurfaceId = \"aetheria.inventory.current_ship_settings\"", - "public const string DecrementShutdownThreshold", - "public const string IncrementShutdownThreshold", - "public const string ResetShutdownThreshold", - "public const string Close", - "public enum AetheriaRuntimeShipSettingsCommandKind", - "public readonly struct AetheriaRuntimeShipSettingsCommand", - "public static class AetheriaRuntimeShipSettingsSurfaceCommands", - "public static bool TryRead(", - "public static AetheriaRuntimeSurfaceDocument Build(", - "var formattedShutdownPerformance = formatShutdownPerformance == null", - "updatedAtUtc.ToString(\"O\", CultureInfo.InvariantCulture)", - "public static float ResolveShutdownPerformance(", - "public static float ClampShutdownPerformance(" - }; - var missingBuilderSymbols = requiredBuilderSymbols - .Where(symbol => !shipSettingsSurfaceBuilder.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (missingBuilderSymbols.Length > 0) - { - throw new InvalidOperationException( - "Shared runtime ship settings surface builder no longer owns the ship-settings shell contract: " + - string.Join(", ", missingBuilderSymbols)); - } - - if (shipSettingsSurfaceBuilder.Contains("public static AetheriaRuntimeShipSettingsSurfaceState Compose(", StringComparison.Ordinal) || - shipSettingsSurfaceBuilder.Contains("public static AetheriaRuntimeShipSettingsSurfaceState Project(", StringComparison.Ordinal) || - shipSettingsSurfaceBuilder.Contains("AetheriaRuntimeShipSettingsSurfaceState", StringComparison.Ordinal) || - shipSettingsSurfaceBuilder.Contains("ComposeState(", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Runtime ship settings composition must emit the typed surface document directly; do not rebuild a shadow SurfaceState projection layer."); - } -} - -static void RequireInventoryCargoItemDetailsUseEveSurface(string root) -{ - var inventoryMenuPath = Path.Combine(root, "Assets", "Scripts", "UI", "Menu", "InventoryMenu.cs"); - var actionGameManagerPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "ActionGameManager.cs"); - var surfaceDocumentPath = Path.Combine( - root, - "Packages", - "org.gamecult.aetheria.state", - "Runtime", - "AetheriaRuntimeSurfaceDocuments.cs"); - var daemonItemStatQueriesPath = Path.Combine( - root, - "Packages", - "org.gamecult.aetheria.state", - "Runtime", - "AetheriaRuntimeDaemonItemStatQueries.cs"); - var eveUnitySurfaceHostPath = Path.Combine( - root, - "Packages", - "org.gamecult.aetheria.eve-runtime", - "Runtime", - "AetheriaEveUnitySurfaceHost.cs"); - var runtimeSurfaceDocumentsPath = Path.Combine( - root, - "Packages", - "org.gamecult.aetheria.state", - "Runtime", - "AetheriaRuntimeSurfaceDocuments.cs"); - var cargoItemSurfaceBuilderPath = Path.Combine( - root, - "Packages", - "org.gamecult.aetheria.state", - "Runtime", - "AetheriaRuntimeCargoItemDetailsSurfaceBuilder.cs"); - var equippedItemSurfaceBuilderPath = Path.Combine( - root, - "Packages", - "org.gamecult.aetheria.state", - "Runtime", - "AetheriaRuntimeEquippedItemDetailsSurfaceBuilder.cs"); - var unityProjectPath = Path.Combine(root, "GameCult.Aetheria.State.Unity.csproj"); - var requiredFiles = new[] - { - inventoryMenuPath, - actionGameManagerPath, - surfaceDocumentPath, - daemonItemStatQueriesPath, - eveUnitySurfaceHostPath, - runtimeSurfaceDocumentsPath, - cargoItemSurfaceBuilderPath, - equippedItemSurfaceBuilderPath, - unityProjectPath - }; - var missingFiles = requiredFiles - .Where(path => !File.Exists(path)) - .Select(path => Path.GetRelativePath(root, path)) - .ToArray(); - if (missingFiles.Length > 0) - { - throw new InvalidOperationException( - "Cannot verify inventory cargo-item shell; required files are missing: " + - string.Join(", ", missingFiles)); - } - - var source = File.ReadAllText(inventoryMenuPath); - var actionGameManager = File.ReadAllText(actionGameManagerPath); - var surfaceDocument = File.ReadAllText(surfaceDocumentPath); - var daemonItemStatQueries = File.ReadAllText(daemonItemStatQueriesPath); - var unityProject = File.ReadAllText(unityProjectPath); - var eveUnitySurfaceHost = File.ReadAllText(eveUnitySurfaceHostPath); - var runtimeSurfaceDocuments = File.ReadAllText(runtimeSurfaceDocumentsPath); - var cargoItemSurfaceBuilder = File.ReadAllText(cargoItemSurfaceBuilderPath); - var equippedItemSurfaceBuilder = File.ReadAllText(equippedItemSurfaceBuilderPath); - var requiredSymbols = new[] - { - "RenderCargoItemDetailsSurface(", - "HandleCargoItemDetailsSurfaceCommand(", - "AetheriaEveUnitySurfaceHost.RenderRuntime(", - "AetheriaEveUnitySurfaceHost.Hide(_cargoItemDetailsSurfaceDocument)", - "AetheriaRuntimeCargoItemDetailsSurfaceBuilder.Build(", - "ResolveManufacturerName(typedItem)", - "FormatItemTier(typedItem, equippableItem)", - "ComposeCargoItemObservation(", - "AetheriaRuntimeCargoItemDetailsSurfaceCommands.TryRead(request, out var command)", - "AetheriaRuntimeCargoItemDetailsCommandKind.Close" - }; - - var missingSymbols = requiredSymbols - .Where(symbol => !source.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - - if (missingSymbols.Length > 0) - { - throw new InvalidOperationException( - "InventoryMenu no longer lowers cargo-item inspection through an Eve surface shell: " + - string.Join(", ", missingSymbols)); - } - - if (source.Contains("InventoryCargoEventData", StringComparison.Ordinal) || - source.Contains("Occupancy[", StringComparison.Ordinal) || - source.Contains("RenderCargoItemDetailsSurface(item);", StringComparison.Ordinal)) - { - 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( - root, - "Packages", - "org.gamecult.aetheria.state", - "Runtime", - "AetheriaRuntimeStateRefResolver.cs"); - var runtimeStateRefResolver = File.Exists(runtimeStateRefResolverPath) - ? File.ReadAllText(runtimeStateRefResolverPath) - : throw new InvalidOperationException("Cannot verify item stat state-ref authority; AetheriaRuntimeStateRefResolver.cs is missing."); - - if (!runtimeStateRefResolver.Contains("TryResolveDaemonItemStatRef(", StringComparison.Ordinal) || - !runtimeStateRefResolver.Contains("AetheriaRuntimeDaemonItemStatQueries.TryReadItemStatRef(", StringComparison.Ordinal) || - !runtimeStateRefResolver.Contains("AetheriaRuntimeDaemonItemStatQueries.EvaluatePerformanceStat(", StringComparison.Ordinal) || - !runtimeStateRefResolver.Contains("item.Temperature", StringComparison.Ordinal) || - !runtimeStateRefResolver.Contains("FindDaemonItem(", StringComparison.Ordinal) || - !cargoItemSurfaceBuilder.Contains("AetheriaRuntimeDaemonItemStatQueries.ItemStatRef(", StringComparison.Ordinal) || - source.Contains("ResolveInventorySurfaceStateRef", StringComparison.Ordinal) || - source.Contains("TryResolveInventorySurfaceStateRef", StringComparison.Ordinal) || - source.Contains("private static bool TryReadItemStatRef(", StringComparison.Ordinal) || - source.Contains("DecodeRefToken", StringComparison.Ordinal) || - source.Contains("GameManager.ItemManager.GetTier", StringComparison.Ordinal) || - source.Contains("GameManager.ItemManager.Evaluate", StringComparison.Ordinal) || - actionGameManager.Contains("ObservedItemStat(", StringComparison.Ordinal) || - actionGameManager.Contains("ItemManager.Evaluate(stat", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "InventoryMenu must project current item stats through shared daemon item-stat queries instead of direct Unity ItemManager stat authority."); - } - - var stateRefRequiredSymbols = new[] - { - ("AetheriaRuntimeSurfaceDocuments.cs", surfaceDocument, "public static class AetheriaRuntimeSurfaceStateRefs"), - ("AetheriaRuntimeSurfaceDocuments.cs", surfaceDocument, "public const string Source = \"stateRef\""), - ("AetheriaRuntimeSurfaceDocuments.cs", surfaceDocument, "public const string Value = \"valueRef\""), - ("AetheriaRuntimeDaemonItemStatQueries.cs", daemonItemStatQueries, "public const string StateRefPrefix = \"aetheria.state/items\""), - ("AetheriaRuntimeDaemonItemStatQueries.cs", daemonItemStatQueries, "public static string ItemStatRef("), - ("AetheriaRuntimeDaemonItemStatQueries.cs", daemonItemStatQueries, "public static bool TryReadItemStatRef("), - ("AetheriaRuntimeDaemonItemStatQueries.cs", daemonItemStatQueries, "public string ValueRef =>"), - ("GameCult.Aetheria.State.Unity.csproj", unityProject, "AetheriaRuntimeDaemonItemStatQueries.cs"), - ("AetheriaEveUnitySurfaceHost.cs", eveUnitySurfaceHost, "CultMeshStateRefResolver stateRefResolver"), - ("AetheriaEveUnitySurfaceHost.cs", eveUnitySurfaceHost, "ContainsStateRefs(surface)"), - ("AetheriaEveUnitySurfaceHost.cs", eveUnitySurfaceHost, "prop.Key.EndsWith(\"Ref\", StringComparison.Ordinal)"), - ("AetheriaEveUnitySurfaceHost.cs", eveUnitySurfaceHost, "CreateDefaultStateRefResolver()"), - ("AetheriaEveUnitySurfaceHost.cs", eveUnitySurfaceHost, "AetheriaEveRuntimeUnityHooks"), - ("AetheriaEveUnitySurfaceHost.cs", eveUnitySurfaceHost, "TryCreateDefaultStateRefResolver()"), - ("AetheriaRuntimeSurfaceDocuments.cs", runtimeSurfaceDocuments, "public static EveUiSurfaceDocument ResolveStateRefs("), - ("AetheriaRuntimeSurfaceDocuments.cs", runtimeSurfaceDocuments, "ResolvePropRefs(props, resolveStateRef)"), - ("AetheriaRuntimeSurfaceDocuments.cs", runtimeSurfaceDocuments, "ResolvePropRef(props, AetheriaRuntimeSurfaceStateRefs.Source, \"value\", resolveStateRef)"), - ("AetheriaRuntimeSurfaceDocuments.cs", runtimeSurfaceDocuments, "IsStatePointerProp(prop.Key)"), - ("AetheriaRuntimeSurfaceDocuments.cs", runtimeSurfaceDocuments, "ResolvePointerValueKey(refProp.Key)"), - ("AetheriaRuntimeCargoItemDetailsSurfaceBuilder.cs", cargoItemSurfaceBuilder, "public string ValueRef { get; }"), - ("AetheriaRuntimeCargoItemDetailsSurfaceBuilder.cs", cargoItemSurfaceBuilder, "props.Add(AetheriaRuntimeSurfaceStateRefs.ValueRef(valueRef))"), - ("AetheriaRuntimeEquippedItemDetailsSurfaceBuilder.cs", equippedItemSurfaceBuilder, "public string ValueRef { get; }"), - ("AetheriaRuntimeEquippedItemDetailsSurfaceBuilder.cs", equippedItemSurfaceBuilder, "props.Add(AetheriaRuntimeSurfaceStateRefs.ValueRef(valueRef))") - }; - var missingStateRefSymbols = stateRefRequiredSymbols - .Where(symbol => !symbol.Item2.Contains(symbol.Item3, StringComparison.Ordinal)) - .Select(symbol => $"{symbol.Item1}: {symbol.Item3}") - .ToArray(); - if (missingStateRefSymbols.Length > 0) - { - throw new InvalidOperationException( - "Eve/CultUI item stat surfaces must expose typed daemon state refs that the UI runtime can resolve: " + - string.Join(", ", missingStateRefSymbols)); - } - - var forbiddenSymbols = new[] - { - "BuildCargoItemDetailsSurfaceDefinition(", - "BuildCargoItemBehaviorCards(", - "BuildCargoItemBehaviorMetric(", - "CargoItemDetailsSurfaceId", - "CloseCargoItemDetailsCommand", - "ResolveCargoItemDetailsSurfaceDocument(", - "new EveUiToolkitSurfaceLowerer()", - "host.AddComponent", - "string.Equals(request.Command, AetheriaRuntimeCargoItemDetailsSurfaceBuilder.Close", - "ComposeCargoItemDetailsSurface(", - "ProjectCargoItemDetailsSurfaceState(", - "ProjectCargoItemBehaviorSections(", - "ProjectCargoItemBehaviorMetric(", - "ProjectCargoItemDetailsSurface(", - "ProjectCargoItemObservation(", - "AetheriaRuntimeCargoItemDetailsSurfaceBuilder.Project(" - }; - var hits = forbiddenSymbols - .Where(symbol => source.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (hits.Length > 0) - { - throw new InvalidOperationException( - "InventoryMenu still owns cargo-item inspection surface construction instead of projecting shared runtime state: " + - string.Join(", ", hits)); - } - - var requiredBuilderSymbols = new[] - { - "public static class AetheriaRuntimeCargoItemDetailsSurfaceBuilder", - "public const string SurfaceId = \"aetheria.inventory.cargo_item_details\"", - "public const string Close = \"aetheria.inventory.cargo_item_details.close\"", - "public enum AetheriaRuntimeCargoItemDetailsCommandKind", - "public readonly struct AetheriaRuntimeCargoItemDetailsCommand", - "public static class AetheriaRuntimeCargoItemDetailsSurfaceCommands", - "public static bool TryRead(", - "AetheriaRuntimeCargoItemObservation", - "AetheriaRuntimeCargoItemSection", - "AetheriaRuntimeCargoItemMetric", - "var itemName = typedItem?.Name ?? \"\"", - "var behaviorSections = Array.Empty()", - "var hasEquipmentStatus = !string.IsNullOrWhiteSpace(tier)", - "ProjectBehaviorSections(", - "ProjectBehaviorMetric(", - "public static AetheriaRuntimeSurfaceDocument Build(" - }; - var missingBuilderSymbols = requiredBuilderSymbols - .Where(symbol => !cargoItemSurfaceBuilder.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (missingBuilderSymbols.Length > 0) - { - throw new InvalidOperationException( - "Shared cargo-item detail surface builder no longer owns the cargo inspection contract: " + - string.Join(", ", missingBuilderSymbols)); - } - - if (cargoItemSurfaceBuilder.Contains("AetheriaRuntimeCargoItemDetailsSurfaceState", StringComparison.Ordinal) || - cargoItemSurfaceBuilder.Contains("ComposeState(", StringComparison.Ordinal) || - cargoItemSurfaceBuilder.Contains("public static AetheriaRuntimeCargoItemDetailsSurfaceState Compose(", StringComparison.Ordinal) || - cargoItemSurfaceBuilder.Contains("public static AetheriaRuntimeCargoItemDetailsSurfaceState Project(", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Runtime cargo item details must emit the typed surface document directly; do not rebuild a shadow SurfaceState projection layer."); - } -} - -static void RequireInventoryEquippedItemDetailsUseEveSurface(string root) -{ - var inventoryMenuPath = Path.Combine(root, "Assets", "Scripts", "UI", "Menu", "InventoryMenu.cs"); - var actionGameManagerPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "ActionGameManager.cs"); - var equippedItemSurfaceBuilderPath = Path.Combine( - root, - "Packages", - "org.gamecult.aetheria.state", - "Runtime", - "AetheriaRuntimeEquippedItemDetailsSurfaceBuilder.cs"); - var stateProjectPath = Path.Combine(root, "Aetheria.State", "Aetheria.State.csproj"); - var unityProjectPath = Path.Combine(root, "GameCult.Aetheria.State.Unity.csproj"); - var requiredFiles = new[] - { - inventoryMenuPath, - actionGameManagerPath, - equippedItemSurfaceBuilderPath, - stateProjectPath, - unityProjectPath - }; - var missingFiles = requiredFiles - .Where(path => !File.Exists(path)) - .Select(path => Path.GetRelativePath(root, path)) - .ToArray(); - if (missingFiles.Length > 0) - { - throw new InvalidOperationException( - "Cannot verify inventory equipped-item shell; required files are missing: " + - string.Join(", ", missingFiles)); - } - - var source = File.ReadAllText(inventoryMenuPath); - var requiredSymbols = new[] - { - "RenderEquippedItemDetailsSurface(", - "HandleEquippedItemDetailsSurfaceCommand(", - "AetheriaEveUnitySurfaceHost.RenderRuntime(", - "AetheriaEveUnitySurfaceHost.Hide(_equippedItemDetailsSurfaceDocument)", - "AetheriaRuntimeEquippedItemDetailsSurfaceBuilder.Build(", - "BuildEquippedItemTitle(item, typedItem)", - "ResolveManufacturerName(typedItem)", - "ComposeEquippedItemObservation(", - "ComposeEquippedItemTemperatureControls(", - "ComposeEquippedItemWeaponGroupControls(", - "AetheriaRuntimeEquippedItemDetailsSurfaceCommands.TryRead(request, out var command)", - "switch (command.Kind)", - "AetheriaRuntimeEquippedItemDetailsCommandKind.Close", - "AetheriaRuntimeEquippedItemDetailsCommandKind.ToggleOverrideShutdown", - "AetheriaRuntimeEquippedItemDetailsCommandKind.SetTargetTemperature", - "AetheriaRuntimeEquippedItemDetailsCommandKind.ToggleWeaponGroup" - }; - - var missingSymbols = requiredSymbols - .Where(symbol => !source.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - - if (missingSymbols.Length > 0) - { - throw new InvalidOperationException( - "InventoryMenu no longer lowers equipped-item inspection through an Eve surface shell: " + - string.Join(", ", missingSymbols)); - } - - var forbiddenSymbols = new[] - { - "EquippedItemDetailsSurfaceId", - "BuildEquippedItemDetailsSurfaceDefinition(", - "BuildEquippedItemControlCard(", - "BuildEquippedItemWeaponGroupCard(", - "BuildEquippedItemActionBarCards(", - "ProjectEquippedItemActionBarSlots(", - "BuildItemBehaviorCards(", - "BuildItemBehaviorMetric(", - "CloseEquippedItemDetailsCommand", - "ToggleEquippedItemOverrideShutdownCommand", - "SetEquippedItemTargetTemperatureCommand", - "ToggleEquippedItemWeaponGroupCommand", - "BindEquippedItemWeaponGroupCommand", - "ClearEquippedItemActionBarBindingCommand", - "AetheriaRuntimeEquippedItemDetailsCommandKind.BindWeaponGroup", - "AetheriaRuntimeEquippedItemDetailsCommandKind.ClearActionBarBinding", - "RequestWeaponGroupActionBarBinding(", - "RequestClearActionBarBinding(", - "ResolveEquippedItemDetailsSurfaceDocument(", - "new EveUiToolkitSurfaceLowerer()", - "host.AddComponent", - "new AetheriaRuntimeEquippedItemDetailsSurfaceState(", - "ComposeEquippedItemDetailsSurface(", - "ProjectEquippedItemDetailsSurfaceState(", - "ProjectEquippedItemBehaviorSections(", - "ProjectEquippedItemBehaviorMetric(", - "new AetheriaRuntimeEquippedItemSection(", - "new AetheriaRuntimeEquippedItemMetric(", - "private static EveSurfaceComponent Card(", - "private static EveSurfaceComponent Metric(", - "private static EveSurfaceComponent Text(", - "private static EveSurfaceComponent Button(", - "private static EveSurfaceComponent CommandButton(", - "private static EveSurfaceComponent TextField(", - "private static EveSurfaceComponent ButtonRow(", - "private static EveSurfaceComponent Node(", - "TryReadPayloadInt(", - "TryReadPayloadFloat(", - "request.Payload", - "ProjectEquippedItemDetailsSurface(", - "ProjectEquippedItemObservation(", - "ProjectEquippedItemTemperatureControls(", - "ProjectEquippedItemWeaponGroupControls(", - "AetheriaRuntimeEquippedItemDetailsSurfaceBuilder.Compose(", - "AetheriaRuntimeEquippedItemDetailsSurfaceBuilder.Project(" - }; - var forbiddenHits = forbiddenSymbols - .Where(symbol => source.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (forbiddenHits.Length > 0) - { - throw new InvalidOperationException( - "InventoryMenu still owns the equipped-item surface contract locally: " + - string.Join(", ", forbiddenHits)); - } - - if (!source.Contains("RenderEquippedItemDetailsSurface(item);", StringComparison.Ordinal)) - { - throw new InvalidOperationException("InventoryMenu equipped-item click path no longer routes inspection through the Eve surface."); - } - - var legacyShellSymbols = new[] - { - "public PropertiesPanel PropertiesPanel;", - "PropertiesPanel.GameManager = GameManager;", - "PropertiesPanel.gameObject.SetActive(true);", - "PropertiesPanel.Inspect(item);" - }; - - var hits = legacyShellSymbols - .Where(symbol => source.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - - if (hits.Length > 0) - { - throw new InvalidOperationException( - "InventoryMenu still owns equipped-item inspection through the old PropertiesPanel shell: " + - string.Join(", ", hits)); - } - - var equippedItemSurfaceBuilder = File.ReadAllText(equippedItemSurfaceBuilderPath); - var stateProject = File.ReadAllText(stateProjectPath); - var unityProject = File.ReadAllText(unityProjectPath); - var requiredBuilderSymbols = new[] - { - "public sealed class AetheriaRuntimeEquippedItemObservation", - "public sealed class AetheriaRuntimeEquippedItemSection", - "public sealed class AetheriaRuntimeEquippedItemMetric", - "public sealed class AetheriaRuntimeEquippedItemControl", - "public sealed class AetheriaRuntimeEquippedItemTemperatureControl", - "public enum AetheriaRuntimeEquippedItemDetailsCommandKind", - "public readonly struct AetheriaRuntimeEquippedItemDetailsCommand", - "public static class AetheriaRuntimeEquippedItemDetailsSurfaceCommands", - "public static bool TryRead(", - "public const string SurfaceId = \"aetheria.inventory.equipped_item_details\"", - "public const string Close = \"aetheria.inventory.equipped_item_details.close\"", - "public const string ToggleOverrideShutdown = \"aetheria.inventory.equipped_item_details.override_shutdown.toggle\"", - "public const string SetTargetTemperature = \"aetheria.inventory.equipped_item_details.target_temperature.set\"", - "public const string ToggleWeaponGroup = \"aetheria.inventory.equipped_item_details.weapon_group.toggle\"", - "var behaviorSections = typedItem == null", - "BuildControlsCard(overrideShutdown, overrideShutdownLabel, temperatureControls)", - "weaponGroupControls.Count > 0", - "ProjectBehaviorSections(", - "ProjectBehaviorMetric(", - "AetheriaRuntimeDaemonItemStatQueries.ItemStatRef(", - "public static AetheriaRuntimeSurfaceDocument Build(", - "new AetheriaRuntimeSurfaceCommandTemplate(Close", - "new AetheriaRuntimeSurfaceCommandTemplate(ToggleOverrideShutdown", - "new AetheriaRuntimeSurfaceCommandTemplate(SetTargetTemperature", - "new AetheriaRuntimeSurfaceCommandTemplate(ToggleWeaponGroup", - "ReadInt(request, \"behaviorIndex\", -1)", - "ReadFloat(request, \"value\", 0f)", - "ReadInt(request, \"group\", -1)", - "\"control.text\"", - "\"inventory.menu\"" - }; - var missingBuilderSymbols = requiredBuilderSymbols - .Where(symbol => !equippedItemSurfaceBuilder.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (missingBuilderSymbols.Length > 0) - { - throw new InvalidOperationException( - "Shared equipped-item detail surface builder no longer owns the equipped-item inspection contract: " + - string.Join(", ", missingBuilderSymbols)); - } - - if (equippedItemSurfaceBuilder.Contains("AetheriaRuntimeEquippedItemDetailsSurfaceState", StringComparison.Ordinal) || - equippedItemSurfaceBuilder.Contains("ComposeState(", StringComparison.Ordinal) || - equippedItemSurfaceBuilder.Contains("public static AetheriaRuntimeEquippedItemDetailsSurfaceState Compose(", StringComparison.Ordinal) || - equippedItemSurfaceBuilder.Contains("public static AetheriaRuntimeEquippedItemDetailsSurfaceState Project(", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Runtime equipped item details must emit the typed surface document directly; do not rebuild a shadow SurfaceState projection layer."); - } - - var forbiddenBuilderActionBarSymbols = new[] - { - "AetheriaRuntimeEquippedItemActionBarSlot", - "ActionBarSlots", - "BindWeaponGroup", - "ClearActionBarBinding", - "ReadInt(request, \"slot\", -1)", - "action_bar" - }; - var builderActionBarHits = forbiddenBuilderActionBarSymbols - .Where(symbol => equippedItemSurfaceBuilder.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (builderActionBarHits.Length > 0) - { - throw new InvalidOperationException( - "Shared equipped-item detail surface still exposes Unity-local action-bar binding concepts: " + - string.Join(", ", builderActionBarHits)); - } - - var stateProjectIncludesRuntimePackage = stateProject.Contains(@"Runtime\*.cs", StringComparison.Ordinal); - if ((!stateProjectIncludesRuntimePackage && - !stateProject.Contains("AetheriaRuntimeEquippedItemDetailsSurfaceBuilder.cs", StringComparison.Ordinal)) || - !unityProject.Contains("AetheriaRuntimeEquippedItemDetailsSurfaceBuilder.cs", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Shared equipped-item surface builder is not included in both runtime project surfaces."); - } - - var actionGameManagerSource = File.ReadAllText(actionGameManagerPath); - var gameplaySceneWiringPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "AetheriaUnityGameplaySceneWiring.cs"); - var gameplaySceneWiring = File.Exists(gameplaySceneWiringPath) - ? File.ReadAllText(gameplaySceneWiringPath) - : throw new InvalidOperationException("Cannot verify action-bar scene wiring; AetheriaUnityGameplaySceneWiring.cs is missing."); - var actionBarPresentationPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "AetheriaUnityActionBarPresentation.cs"); - var actionBarPresentation = File.Exists(actionBarPresentationPath) - ? File.ReadAllText(actionBarPresentationPath) - : throw new InvalidOperationException("Cannot verify equipped-item action-bar presentation; AetheriaUnityActionBarPresentation.cs is missing."); - - var requiredActionBarPresentationSymbols = new[] - { - "public sealed class AetheriaUnityActionBarPresentation", - "public int SlotCount", - "public string SlotLabel(int slotIndex)", - "public string BindingLabel(int slotIndex)", - "public bool TryResolveControlPath(int slotIndex, out string controlPath)", - "public void RequestBinding(ActionBarSlot slot, DragObject dragAction)", - "public bool RequestWeaponGroupBinding(int slotIndex, int groupIndex)", - "public bool ClearBinding(int slotIndex)", - "public void ApplyLocalBindings()", - "public void ApplyBindings(", - "private AetheriaUnityActionBarBinding CreateBindingCommit(", - "private void SetLocalBinding(AetheriaUnityActionBarBinding binding)" - }; - - var missingActionBarPresentationSymbols = requiredActionBarPresentationSymbols - .Where(symbol => !actionBarPresentation.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - - if (missingActionBarPresentationSymbols.Length > 0) - { - throw new InvalidOperationException( - "Equipped-item action-bar presentation reads must live outside ActionGameManager: " + - string.Join(", ", missingActionBarPresentationSymbols)); - } - - var requiredActionBarInjectionSymbols = new[] - { - "private readonly AetheriaUnityActionBarPresentation _actionBarPresentation", - "SceneWiring.ConfigureActionBarPresentation(", - "_actionBarPresentation," - }; - var missingActionBarInjectionSymbols = requiredActionBarInjectionSymbols - .Where(symbol => !actionGameManagerSource.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - - if (missingActionBarInjectionSymbols.Length > 0) - { - throw new InvalidOperationException( - "ActionGameManager must inject action-bar presentation into the inventory shell instead of exposing manager read APIs: " + - string.Join(", ", missingActionBarInjectionSymbols)); - } - - var requiredActionBarSceneWiringSymbols = new[] - { - "public void ConfigureActionBarPresentation(", - "actionBarPresentation?.Bind(", - "resolveActionBarControl);", - "Inventory?.SetActionBarPresentation(actionBarPresentation)" - }; - var missingActionBarSceneWiringSymbols = requiredActionBarSceneWiringSymbols - .Where(symbol => !gameplaySceneWiring.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - - if (missingActionBarSceneWiringSymbols.Length > 0) - { - throw new InvalidOperationException( - "AetheriaUnityGameplaySceneWiring must bind action-bar presentation into the inventory shell instead of leaving ActionGameManager as the owner: " + - string.Join(", ", missingActionBarSceneWiringSymbols)); - } - - var forbiddenActionBarManagerReadSymbols = new[] - { - "public int GetActionBarSlotCount(", - "public string GetActionBarSlotLabel(", - "public string GetActionBarBindingLabel(", - "public bool TryResolveActionBarSlotControlPath(" - }; - var actionBarManagerReadHits = forbiddenActionBarManagerReadSymbols - .Where(symbol => actionGameManagerSource.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (actionBarManagerReadHits.Length > 0) - { - throw new InvalidOperationException( - "ActionGameManager still exposes action-bar presentation reads: " + - string.Join(", ", actionBarManagerReadHits)); - } - - var requiredActionBarInventorySymbols = new[] - { - "SetActionBarPresentation(AetheriaUnityActionBarPresentation actionBarPresentation)", - "AetheriaUnityRuntimeClientProvider.Control(\"unity-inventory-menu\")" - }; - - var missingActionBarInventorySymbols = requiredActionBarInventorySymbols - .Where(symbol => !source.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - - if (missingActionBarInventorySymbols.Length > 0) - { - throw new InvalidOperationException( - "InventoryMenu no longer submits equipped-item action-bar bindings through native Aetheria control access: " + - string.Join(", ", missingActionBarInventorySymbols)); - } - - var forbiddenInventoryActionBarDaemonSymbols = new[] - { - "operations => operations.SetActionBarBinding(", - "operations => operations.ClearActionBarBinding(controlPath)", - "SetActionBarBinding(", - "ClearActionBarBinding(controlPath)" - }; - var inventoryActionBarDaemonHits = forbiddenInventoryActionBarDaemonSymbols - .Where(symbol => source.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (inventoryActionBarDaemonHits.Length > 0) - { - throw new InvalidOperationException( - "InventoryMenu still treats local action-bar mapping as a daemon equipped-item command: " + - string.Join(", ", inventoryActionBarDaemonHits)); - } - - var forbiddenInventoryActionBarManagerReadSymbols = new[] - { - "GameManager.GetActionBarSlotCount(", - "GameManager.GetActionBarSlotLabel(", - "GameManager.GetActionBarBindingLabel(", - "GameManager.TryResolveActionBarSlotControlPath(" - }; - var inventoryActionBarManagerReadHits = forbiddenInventoryActionBarManagerReadSymbols - .Where(symbol => source.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (inventoryActionBarManagerReadHits.Length > 0) - { - throw new InvalidOperationException( - "InventoryMenu still reads action-bar presentation through ActionGameManager: " + - string.Join(", ", inventoryActionBarManagerReadHits)); - } - - var forbiddenActionBarManagerSymbols = new[] - { - "RequestWeaponGroupActionBarBinding(", - "RequestClearActionBarBinding(", - "TryRequestDaemonActionBarBinding(", - "TryRequestDaemonActionBarBindingClear(", - "RequestActionBarBinding(", - "CreateActionBarBinding(", - "CreateActionBarBindingCommit(", - "ResolveActionBarClient()?.Operations.SetActionBarBinding(" - }; - var survivingActionBarManagerSymbols = forbiddenActionBarManagerSymbols - .Where(symbol => actionGameManagerSource.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (survivingActionBarManagerSymbols.Length > 0) - { - throw new InvalidOperationException( - "ActionGameManager still owns equipped-item action-bar binding authority instead of InventoryMenu's typed facade path: " + - string.Join(", ", survivingActionBarManagerSymbols)); - } -} - -static void RequireTradeCargoSelectorUseEveSurface(string root) -{ - var tradeMenuPath = Path.Combine(root, "Assets", "Scripts", "UI", "Menu", "TradeMenu.cs"); - var tradeCargoSelectorSurfaceBuilderPath = Path.Combine( - root, - "Packages", - "org.gamecult.aetheria.state", - "Runtime", - "AetheriaRuntimeTradeCargoSelectorSurfaceBuilder.cs"); - if (!File.Exists(tradeMenuPath)) - { - throw new InvalidOperationException("Cannot verify trade cargo-selector shell; TradeMenu.cs is missing."); - } - if (!File.Exists(tradeCargoSelectorSurfaceBuilderPath)) - { - throw new InvalidOperationException("Cannot verify trade cargo-selector shell; shared runtime trade cargo selector surface builder is missing."); - } - - var source = File.ReadAllText(tradeMenuPath); - var tradeCargoSelectorSurfaceBuilder = File.ReadAllText(tradeCargoSelectorSurfaceBuilderPath); - var requiredSymbols = new[] - { - "RenderCargoSelectorSurface(", - "_cargoSelectorSurfaceModel = BuildTradeCargoSelectorSurfaceModel();", - "HandleCargoSelectorSurfaceCommand(", - "AetheriaEveUnitySurfaceHost.RenderRuntime(", - "AetheriaEveUnitySurfaceHost.Hide(_cargoSelectorSurfaceDocument)", - "_cargoSelectorSurfaceModel.Document", - "BuildTradeCargoSelectorSurfaceModel(", - "AetheriaRuntimeTradeCargoSelectorSurfaceBuilder.Build(", - "_cargoSelectorSurfaceModel?.TryResolve(command.Command, out var selection) == true", - "ApplyCargoSelection(", - "new AetheriaRuntimeTradeCargoModelOption(", - "AetheriaRuntimeTradeCargoTargetKind.DockingBay", - "AetheriaRuntimeTradeCargoTargetKind.ShipBay", - "AetheriaRuntimeTradeCargoSelectorSurfaceCommands.TryRead(request, out var command)", - "AetheriaRuntimeTradeCargoSelectorCommandKind.Close", - "AetheriaRuntimeTradeCargoSelectorCommandKind.Select", - "RuntimeState.StationRefit.Latest()", - "RuntimeState.CurrentDocking.Latest()", - "SetTargetCargo(", - "selection.EntityKey", - "OwnedQuantity", - "CargoTargets", - "AetheriaRuntimeStationCargoTargetRow" - }; - - var missingSymbols = requiredSymbols - .Where(symbol => !source.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - - if (missingSymbols.Length > 0) - { - throw new InvalidOperationException( - "TradeMenu no longer lowers the cargo selector through an Eve surface shell: " + - string.Join(", ", missingSymbols)); - } - - var forbiddenSymbols = new[] - { - "private const string CargoSelectorSurfaceType", - "private const string CargoSelectorSurfaceId", - "CloseCargoSelectorCommand", - "BuildCargoSelectorSurfaceDefinition(", - "ResolveCargoSelectorSurfaceDocument(", - "new EveUiToolkitSurfaceLowerer()", - "host.AddComponent", - "ContextMenu.AddOption(\"Docking Bay\"", - "ContextMenu.AddOption($\"{ship.Name} Bay {bay.index+1}\"", - "private static EveSurfaceComponent", - "new EveSurfaceComponent(", - "string.Equals(request.Command, AetheriaRuntimeTradeCargoSelectorSurfaceBuilder.Close", - "_cargoSelectionCommands.TryGetValue(request.Command", - "private readonly Dictionary _cargoSelectionCommands", - "BuildCargoSelectionCommands(", - "ProjectTradeCargoSelectorSurfaceState(", - "ProjectTradeCargoSelectorSurface(", - "ComposeTradeCargoSelectorSurface(", - "_cargoSelectorSurfaceModel.State", - "AetheriaRuntimeTradeCargoSelectorSurfaceBuilder.Compose(", - "_cargoSelectorSurfaceProjection", - "AetheriaRuntimeObservedDockingState", - "SetPresentationEntityIndex(AetheriaUnityPresentationEntityIndex presentationEntityIndex)", - "private AetheriaUnityPresentationEntityIndex _presentationEntityIndex;", - "private AetheriaUnityObservedDockingIndex _observedDockingIndex;", - "TryResolveObservedDockingIndex(out AetheriaUnityObservedDockingIndex dockingIndex)", - "new AetheriaUnityObservedDockingIndex(_presentationEntityIndex)", - "dockingIndex.StationRefitSnapshot()", - "dockingIndex.TryResolveCurrentDocking(out docking)", - "ClearClientCaches()", - "ResolveClient().State.CurrentDocking()", - "CultMeshReactiveDocument _currentDocking", - ".ReactiveCurrentDocking(\"unity-trade\")", - "docking = _currentDocking.Current", - "CultMeshReactiveDocument _stationRefit", - ".ReactiveStationRefit(\"unity-trade\")", - "return _stationRefit?.Current", - "docking.Refit", - "GameManager.DockedEntity.Children", - "GameManager.CurrentEntity.Parent.Children", - "GameManager.DockingBay", - "GameManager.AvailableEntities()", - "AetheriaClientReactiveDockingState _reactiveDockingState", - "AetheriaClientDockingSnapshot _dockingState", - "private AetheriaClientDockingSnapshot ResolveDockingState()", - "_cargoSelectorStationRefitEntities", - "stationRefit.AvailableEntities ?? Array.Empty()", - "stationRefit.StationStock ?? Array.Empty()" - }; - - var hits = forbiddenSymbols - .Where(symbol => source.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - - if (hits.Length > 0) - { - throw new InvalidOperationException( - "TradeMenu still owns cargo selection through the old context-menu option path: " + - string.Join(", ", hits)); - } - - var forbiddenTradeCountingSymbols = new[] - { - "CountAvailablePlayerShips(", - "CountTargetCargoItems(", - "ItemsOfType" - }; - var tradeCountingHits = forbiddenTradeCountingSymbols - .Where(symbol => source.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (tradeCountingHits.Length > 0) - { - throw new InvalidOperationException( - "TradeMenu still computes trade ownership locally instead of reading typed station stock facts: " + - string.Join(", ", tradeCountingHits)); - } - - var requiredBuilderSymbols = new[] - { - "public static class AetheriaRuntimeTradeCargoSelectorSurfaceBuilder", - "public const string SurfaceId = \"aetheria.trade.target_cargo_selector\"", - "public const string Close = \"aetheria.trade.target_cargo_selector.close\"", - "public const string DockingBay = \"aetheria.trade.target_cargo_selector.docking_bay\"", - "public enum AetheriaRuntimeTradeCargoSelectorCommandKind", - "public readonly struct AetheriaRuntimeTradeCargoSelectorCommand", - "public static class AetheriaRuntimeTradeCargoSelectorSurfaceCommands", - "public static bool TryRead(", - "AetheriaRuntimeTradeCargoTargetOption", - "AetheriaRuntimeTradeCargoModelOption", - "AetheriaRuntimeTradeCargoTargetKind", - "AetheriaRuntimeTradeCargoSelection", - "AetheriaRuntimeTradeCargoSelectorSurfaceModel", - "public AetheriaRuntimeSurfaceDocument Document { get; }", - "public static string ShipBayCommand(", - "public static AetheriaRuntimeTradeCargoSelectorSurfaceModel Build(", - "public bool TryResolve(string command, out AetheriaRuntimeTradeCargoSelection selection)", - "private static AetheriaRuntimeSurfaceDocument Build(", - "providerKind: \"trade.menu\"", - "The observing client lists available cargo targets; the shared runtime surface owns the cargo selector contract." - }; - var missingBuilderSymbols = requiredBuilderSymbols - .Where(symbol => !tradeCargoSelectorSurfaceBuilder.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (missingBuilderSymbols.Length > 0) - { - throw new InvalidOperationException( - "Shared runtime trade cargo selector surface builder no longer owns the trade cargo-selector shell contract: " + - string.Join(", ", missingBuilderSymbols)); - } - - if (tradeCargoSelectorSurfaceBuilder.Contains("AetheriaRuntimeTradeCargoSelectorSurfaceState", StringComparison.Ordinal) || - tradeCargoSelectorSurfaceBuilder.Contains("public AetheriaRuntimeTradeCargoSelectorSurfaceState State { get; }", StringComparison.Ordinal) || - tradeCargoSelectorSurfaceBuilder.Contains("public static AetheriaRuntimeSurfaceDocument Build(", StringComparison.Ordinal) || - tradeCargoSelectorSurfaceBuilder.Contains("public static AetheriaRuntimeTradeCargoSelectorSurfaceModel Compose(", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Shared runtime trade cargo selector builder must return a model with a finished surface document; do not rebuild a shadow SurfaceState projection layer."); - } -} - -static void RequireTradeFilterAndRowActionsUseEveSurface(string root) -{ - var tradeMenuPath = Path.Combine(root, "Assets", "Scripts", "UI", "Menu", "TradeMenu.cs"); - var tradeInteractionSurfaceBuilderPath = Path.Combine( - root, - "Packages", - "org.gamecult.aetheria.state", - "Runtime", - "AetheriaRuntimeTradeInteractionSurfaceBuilder.cs"); - if (!File.Exists(tradeMenuPath)) - { - throw new InvalidOperationException("Cannot verify trade filter and row-action shells; TradeMenu.cs is missing."); - } - if (!File.Exists(tradeInteractionSurfaceBuilderPath)) - { - throw new InvalidOperationException("Cannot verify trade filter and row-action shells; shared runtime trade interaction surface builder is missing."); - } - - var source = File.ReadAllText(tradeMenuPath); - var tradeInteractionSurfaceBuilder = File.ReadAllText(tradeInteractionSurfaceBuilderPath); - var requiredSymbols = new[] - { - "RenderFilterSurface(", - "_filterSurfaceModel = BuildTradeFilterSurfaceModel();", - "HandleFilterSurfaceCommand(", - "AetheriaEveUnitySurfaceHost.RenderRuntime(", - "AetheriaEveUnitySurfaceHost.Hide(_filterSurfaceDocument)", - "_filterSurfaceModel.Document", - "BuildTradeFilterSurfaceModel(", - "AetheriaRuntimeTradeInteractionSurfaceBuilder.BuildFilters(", - "_filterSurfaceModel?.TryResolve(command.Command, out var selection) == true", - "ExecuteTradeFilterSelection(", - "new AetheriaRuntimeTradeFilterOption(", - "RenderRowActionSurface(", - "HandleRowActionSurfaceCommand(", - "AetheriaEveUnitySurfaceHost.Hide(_rowActionSurfaceDocument)", - "AetheriaRuntimeTradeInteractionSurfaceBuilder.BuildRowActions(", - "_rowActionSurfaceModel.Document", - "_rowActionSurfaceModel?.TryResolve(command.Command, out var selection) == true", - "new AetheriaRuntimeTradeRowActionOption(index, action.Label)", - "ShowBuyQuantityDialog(", - "AetheriaRuntimeTradeInteractionSurfaceCommands.TryReadFilter(request, out var command)", - "AetheriaRuntimeTradeInteractionSurfaceCommands.TryReadRowAction(request, out var command)", - "AetheriaRuntimeTradeInteractionCommandKind.Close", - "AetheriaRuntimeTradeInteractionCommandKind.Select" - }; - - var missingSymbols = requiredSymbols - .Where(symbol => !source.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - - if (missingSymbols.Length > 0) - { - throw new InvalidOperationException( - "TradeMenu no longer lowers filter and row-action shells through Eve surfaces: " + - string.Join(", ", missingSymbols)); - } - - var forbiddenSymbols = new[] - { - "private const string FilterSurfaceId", - "private const string RowActionSurfaceId", - "CloseFilterSurfaceCommand", - "CloseRowActionSurfaceCommand", - "BuildFilterSurfaceDefinition(", - "BuildRowActionSurfaceDefinition(", - "ResolveFilterSurfaceDocument(", - "ResolveRowActionSurfaceDocument(", - "new EveUiToolkitSurfaceLowerer()", - "host.AddComponent", - "public ContextMenu ContextMenu;", - "ContextMenu.Clear();", - "ContextMenu.AddDropdown(", - "ContextMenu.AddOption(", - "ContextMenu.Show();", - "private static EveSurfaceComponent", - "new EveSurfaceComponent(", - "string.Equals(request.Command, AetheriaRuntimeTradeInteractionSurfaceBuilder.CloseFilter", - "string.Equals(request.Command, AetheriaRuntimeTradeInteractionSurfaceBuilder.CloseRowAction", - "_filterSurfaceCommands.TryGetValue(request.Command", - "_filterSurfaceProjection", - "private readonly Dictionary _filterSurfaceCommands", - "BuildFilterSurfaceCommands(", - "ProjectTradeFilterSurfaceState(", - "ProjectTradeFilterSurface(", - "ComposeTradeFilterSurface(", - "AetheriaRuntimeTradeInteractionSurfaceBuilder.ComposeFilters(", - "_filterSurfaceModel.State", - "AddTradeFilterGroup(", - "_rowActionSurfaceCommands.TryGetValue(request.Command", - "_rowActionSurfaceProjection", - "private readonly Dictionary _rowActionSurfaceCommands", - "BuildRowActionSurfaceCommands(", - "ProjectTradeRowActionSurfaceState(", - "ProjectRowActions(", - "AetheriaRuntimeTradeInteractionSurfaceBuilder.ComposeRowActions(", - "_rowActionSurfaceModel.State" - }; - - var hits = forbiddenSymbols - .Where(symbol => source.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - - if (hits.Length > 0) - { - throw new InvalidOperationException( - "TradeMenu still owns filter or row-action behavior through the old context-menu path: " + - string.Join(", ", hits)); - } - - var requiredBuilderSymbols = new[] - { - "public static class AetheriaRuntimeTradeInteractionSurfaceBuilder", - "public const string FilterSurfaceId = \"aetheria.trade.filter_selector\"", - "public const string CloseFilter = \"aetheria.trade.filter_selector.close\"", - "public const string RowActionSurfaceId = \"aetheria.trade.row_actions\"", - "public const string CloseRowAction = \"aetheria.trade.row_actions.close\"", - "public enum AetheriaRuntimeTradeInteractionCommandKind", - "public readonly struct AetheriaRuntimeTradeInteractionCommand", - "public static class AetheriaRuntimeTradeInteractionSurfaceCommands", - "public static bool TryReadFilter(", - "public static bool TryReadRowAction(", - "AetheriaRuntimeTradeFilterOption", - "AetheriaRuntimeTradeFilterSelectionKind", - "AetheriaRuntimeTradeFilterSelection", - "AetheriaRuntimeTradeFilterSurfaceModel", - "AetheriaRuntimeTradeRowActionOption", - "AetheriaRuntimeTradeRowActionSelection", - "AetheriaRuntimeTradeRowActionSurfaceModel", - "AetheriaRuntimeTradeSurfaceGroup", - "AetheriaRuntimeTradeSurfaceOption", - "public AetheriaRuntimeSurfaceDocument Document { get; }", - "public static string HardpointFilterCommand(", - "public static string RowActionCommand(", - "public static AetheriaRuntimeTradeFilterSurfaceModel BuildFilters(", - "public bool TryResolve(string command, out AetheriaRuntimeTradeFilterSelection selection)", - "public static AetheriaRuntimeTradeRowActionSurfaceModel BuildRowActions(", - "public bool TryResolve(string command, out AetheriaRuntimeTradeRowActionSelection selection)", - "private static AetheriaRuntimeSurfaceDocument BuildFilter(", - "private static AetheriaRuntimeSurfaceDocument BuildRowActions(", - "providerKind: \"trade.menu\"", - "The observing client lists available trade filters; the shared runtime surface owns the filter selector contract.", - "The observing client lists available row actions; the shared runtime surface owns the row action contract." - }; - var missingBuilderSymbols = requiredBuilderSymbols - .Where(symbol => !tradeInteractionSurfaceBuilder.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (missingBuilderSymbols.Length > 0) - { - throw new InvalidOperationException( - "Shared runtime trade interaction surface builder no longer owns the trade filter/row-action shell contracts: " + - string.Join(", ", missingBuilderSymbols)); - } - - if (tradeInteractionSurfaceBuilder.Contains("AetheriaRuntimeTradeFilterSurfaceState", StringComparison.Ordinal) || - tradeInteractionSurfaceBuilder.Contains("AetheriaRuntimeTradeRowActionSurfaceState", StringComparison.Ordinal) || - tradeInteractionSurfaceBuilder.Contains("public AetheriaRuntimeTradeFilterSurfaceState State { get; }", StringComparison.Ordinal) || - tradeInteractionSurfaceBuilder.Contains("public AetheriaRuntimeTradeRowActionSurfaceState State { get; }", StringComparison.Ordinal) || - tradeInteractionSurfaceBuilder.Contains("public static AetheriaRuntimeSurfaceDocument BuildFilter(", StringComparison.Ordinal) || - tradeInteractionSurfaceBuilder.Contains("public static AetheriaRuntimeSurfaceDocument BuildRowActions(", StringComparison.Ordinal) || - tradeInteractionSurfaceBuilder.Contains("public static AetheriaRuntimeTradeFilterSurfaceModel ComposeFilters(", StringComparison.Ordinal) || - tradeInteractionSurfaceBuilder.Contains("public static AetheriaRuntimeTradeRowActionSurfaceModel ComposeRowActions(", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Shared runtime trade interaction builder must return models with finished surface documents; do not rebuild shadow SurfaceState projection layers."); - } -} - -static void RequireTradeItemDetailsUseEveSurface(string root) -{ - var tradeMenuPath = Path.Combine(root, "Assets", "Scripts", "UI", "Menu", "TradeMenu.cs"); - var tradeItemDetailsSurfaceBuilderPath = Path.Combine( - root, - "Packages", - "org.gamecult.aetheria.state", - "Runtime", - "AetheriaRuntimeTradeItemDetailsSurfaceBuilder.cs"); - if (!File.Exists(tradeMenuPath)) - { - throw new InvalidOperationException("Cannot verify trade item-details shell; TradeMenu.cs is missing."); - } - if (!File.Exists(tradeItemDetailsSurfaceBuilderPath)) - { - throw new InvalidOperationException("Cannot verify trade item-details shell; shared runtime trade item details surface builder is missing."); - } - - var source = File.ReadAllText(tradeMenuPath); - var tradeItemDetailsSurfaceBuilder = File.ReadAllText(tradeItemDetailsSurfaceBuilderPath); - var requiredSymbols = new[] - { - "RenderTradeItemDetailsSurface(", - "HandleTradeItemDetailsSurfaceCommand(", - "AetheriaEveUnitySurfaceHost.RenderRuntime(", - "AetheriaEveUnitySurfaceHost.Hide(_tradeItemSurfaceDocument)", - "AetheriaRuntimeTradeItemDetailsSurfaceBuilder.Build(", - "ResolveManufacturerName(item)", - "FormatTemperature", - "AetheriaRuntimeTradeItemDetailsSurfaceCommands.TryRead(request, out var command)", - "AetheriaRuntimeTradeItemDetailsCommandKind.Close" - }; - - var missingSymbols = requiredSymbols - .Where(symbol => !source.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - - if (missingSymbols.Length > 0) - { - throw new InvalidOperationException( - "TradeMenu no longer lowers typed item inspection through an Eve surface: " + - string.Join(", ", missingSymbols)); - } - - var forbiddenSymbols = new[] - { - "TradeItemSurfaceId", - "CloseTradeItemDetailsCommand", - "BuildTradeItemDetailsSurfaceDefinition(", - "BuildTradeItemBehaviorCards(", - "BuildTradeItemBehaviorMetric(", - "ResolveTradeItemDetailsSurfaceDocument(", - "new EveUiToolkitSurfaceLowerer()", - "host.AddComponent", - "public PropertiesPanel Properties;", - "OnClick = () => Properties.Inspect(i.TypedItem)", - "private static EveSurfaceComponent", - "new EveSurfaceComponent(", - "string.Equals(request.Command, AetheriaRuntimeTradeItemDetailsSurfaceBuilder.Close", - "ProjectTradeItemDetailsSurface(", - "ProjectTradeItemDetailsSurfaceState(", - "AetheriaRuntimeTradeItemDetailsSurfaceBuilder.Project(", - "ProjectTradeItemBehaviorSections(", - "ProjectTradeItemBehaviorMetric(" - }; - - var hits = forbiddenSymbols - .Where(symbol => source.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - - if (hits.Length > 0) - { - throw new InvalidOperationException( - "TradeMenu still owns typed item inspection through the old PropertiesPanel path: " + - string.Join(", ", hits)); - } - - var requiredBuilderSymbols = new[] - { - "public static class AetheriaRuntimeTradeItemDetailsSurfaceBuilder", - "public const string SurfaceId = \"aetheria.trade.item_details\"", - "public const string Close = \"aetheria.trade.item_details.close\"", - "public enum AetheriaRuntimeTradeItemDetailsCommandKind", - "public readonly struct AetheriaRuntimeTradeItemDetailsCommand", - "public static class AetheriaRuntimeTradeItemDetailsSurfaceCommands", - "public static bool TryRead(", - "AetheriaRuntimeTradeItemSection", - "AetheriaRuntimeTradeItemMetric", - "var itemName = item?.Name ?? \"\"", - "var behaviorSections = Array.Empty()", - "var hasEquipmentStatus = !string.IsNullOrWhiteSpace(durability)", - "ProjectBehaviorSections(", - "ProjectBehaviorMetric(", - "public static AetheriaRuntimeSurfaceDocument Build(", - "providerKind: \"trade.menu\"", - "The observing client supplies the selected market row; the shared runtime surface owns trade item inspection layout." - }; - var missingBuilderSymbols = requiredBuilderSymbols - .Where(symbol => !tradeItemDetailsSurfaceBuilder.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (missingBuilderSymbols.Length > 0) - { - throw new InvalidOperationException( - "Shared runtime trade item details surface builder no longer owns the trade item-details shell contract: " + - string.Join(", ", missingBuilderSymbols)); - } - - if (tradeItemDetailsSurfaceBuilder.Contains("AetheriaRuntimeTradeItemDetailsSurfaceState", StringComparison.Ordinal) || - tradeItemDetailsSurfaceBuilder.Contains("ComposeState(", StringComparison.Ordinal) || - tradeItemDetailsSurfaceBuilder.Contains("public static AetheriaRuntimeTradeItemDetailsSurfaceState Project(", StringComparison.Ordinal) || - tradeItemDetailsSurfaceBuilder.Contains("public static AetheriaRuntimeTradeItemDetailsSurfaceState Compose(", StringComparison.Ordinal) || - tradeItemDetailsSurfaceBuilder.Contains("private static AetheriaRuntimeTradeItemDetailsSurfaceState ProjectState(", StringComparison.Ordinal) || - tradeItemDetailsSurfaceBuilder.Contains("Build(ProjectState(", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Runtime trade item details must emit the typed surface document directly; do not rebuild a shadow SurfaceState projection layer."); - } -} - -static void RequireTradeItemValuesUseRuntimeQueries(string root) -{ - var tradeMenuPath = Path.Combine(root, "Assets", "Scripts", "UI", "Menu", "TradeMenu.cs"); - var inventoryPanelPath = Path.Combine(root, "Assets", "Scripts", "UI", "Menu", "InventoryPanel.cs"); - var inventoryMenuPath = Path.Combine(root, "Assets", "Scripts", "UI", "Menu", "InventoryMenu.cs"); - var catalogSnapshotPath = Path.Combine( - root, - "Packages", - "org.gamecult.aetheria.state", - "Runtime", - "AetheriaRuntimeCatalogSnapshot.cs"); - var tradeQueriesPath = Path.Combine( - root, - "Packages", - "org.gamecult.aetheria.state", - "Runtime", - "AetheriaRuntimeDaemonTradeItemQueries.cs"); - if (!File.Exists(tradeMenuPath)) - { - throw new InvalidOperationException("Cannot verify trade item value; TradeMenu.cs is missing."); - } - if (!File.Exists(inventoryPanelPath)) - { - throw new InvalidOperationException("Cannot verify typed catalog item enum accessors; InventoryPanel.cs is missing."); - } - if (!File.Exists(inventoryMenuPath)) - { - throw new InvalidOperationException("Cannot verify typed inventory document vocabulary; InventoryMenu.cs is missing."); - } - if (!File.Exists(catalogSnapshotPath)) - { - throw new InvalidOperationException("Cannot verify typed catalog item enum accessors; AetheriaRuntimeCatalogSnapshot.cs is missing."); - } - if (!File.Exists(tradeQueriesPath)) - { - throw new InvalidOperationException("Cannot verify trade item value; shared runtime trade item queries are missing."); - } - - var tradeMenu = File.ReadAllText(tradeMenuPath); - var inventoryPanel = File.ReadAllText(inventoryPanelPath); - var inventoryMenu = File.ReadAllText(inventoryMenuPath); - var catalogSnapshot = File.ReadAllText(catalogSnapshotPath); - var tradeQueries = File.ReadAllText(tradeQueriesPath); - var requiredTradeMenuSymbols = new[] - { - "TradeItemValue(stock, typedItem)", - "TradeItemCommit(", - "CatalogSnapshot()?.TradeValueSettings", - "AetheriaRuntimeDaemonTradeItemQueries.TradeItemValue(", - "AetheriaRuntimeTradeItemValue TradeValue", - "public int Price => TradeValue.Price", - "public string TierColorHex => TradeValue.TierColorHex", - "x.TypedItem.TryGetSimpleCommodityCategory(", - "x.TypedItem.TryGetCompoundCommodityCategory(", - "x.TypedItem.TryGetHardpointType(" - }; - var missingTradeMenuSymbols = requiredTradeMenuSymbols - .Where(symbol => !tradeMenu.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (missingTradeMenuSymbols.Length > 0) - { - throw new InvalidOperationException( - "TradeMenu no longer reads trade item price/tier values through shared daemon runtime queries: " + - string.Join(", ", missingTradeMenuSymbols)); - } - - var forbiddenTradeMenuSymbols = new[] - { - "GameManager.ItemManager.GetTier", - "GameManager.ItemManager.GameplaySettings.QualityPriceModifier", - "_itemManager.GameplaySettings.QualityPriceModifier", - "GameManager.ObservedTradeValueSettings()", - "AetheriaUnityProjectionSettings.TradeValueSettings", - "private readonly ItemManager _itemManager", - "new TradeRow(item, FindTypedTradeItem(item), GameManager.ItemManager)", - "TryGetTypedSimpleCommodityCategory(", - "TryGetTypedCompoundCommodityCategory(", - "TryGetTypedHardpoint(" - }; - var tradeMenuHits = forbiddenTradeMenuSymbols - .Where(symbol => tradeMenu.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (tradeMenuHits.Length > 0) - { - throw new InvalidOperationException( - "TradeMenu still asks Unity ItemManager for trade item value: " + - string.Join(", ", tradeMenuHits)); - } - - var requiredCatalogItemEnumSymbols = new[] - { - "public bool TryGetHardpointType(out TEnum hardpointType) where TEnum : struct", - "public bool TryGetSimpleCommodityCategory(out TEnum category) where TEnum : struct", - "public bool TryGetCompoundCommodityCategory(out TEnum category) where TEnum : struct", - "public AetheriaRuntimeCatalogItem? FindItem(T? item, Func itemKey) where T : class", - "Enum.TryParse(HardpointType, true, out hardpointType)", - "Enum.TryParse(SimpleCommodityCategory, true, out category)", - "Enum.TryParse(CompoundCommodityCategory, true, out category)" - }; - var missingCatalogItemEnumSymbols = requiredCatalogItemEnumSymbols - .Where(symbol => !catalogSnapshot.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (missingCatalogItemEnumSymbols.Length > 0) - { - throw new InvalidOperationException( - "AetheriaRuntimeCatalogItem must own typed enum accessors for catalog category/hardpoint fields: " + - string.Join(", ", missingCatalogItemEnumSymbols)); - } - - if (inventoryPanel.Contains("TryGetTypedHardpointType(", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "InventoryPanel must not parse hardpoint classification locally; daemon-owned inventory surfaces consume typed catalog state."); - } - - if (inventoryPanel.Contains("inventory projection", StringComparison.OrdinalIgnoreCase) || - inventoryMenu.Contains("inventory projection", StringComparison.OrdinalIgnoreCase)) - { - throw new InvalidOperationException( - "Inventory UI must describe direct reactive inventory document access as typed documents, not inventory projection chaff."); - } - - var requiredQuerySymbols = new[] - { - "public static class AetheriaRuntimeDaemonTradeItemQueries", - "public readonly struct AetheriaRuntimeTradeItemValue", - "public sealed class AetheriaRuntimeTradeValueSettings", - "public readonly struct AetheriaRuntimeItemRarityTier", - "public readonly struct AetheriaRuntimeExponentialLerp", - "public static AetheriaRuntimeTradeItemValue TradeItemValue(", - "settings.QualityPriceModifier.Evaluate(quality.Value) * typedItem.Price", - "SelectTier(settings.Tiers, quality.Value)", - "AetheriaRuntimeDaemonItemStatQueries.ItemCommit(" - }; - var missingQuerySymbols = requiredQuerySymbols - .Where(symbol => !tradeQueries.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (missingQuerySymbols.Length > 0) - { - throw new InvalidOperationException( - "Shared runtime trade item queries no longer own price/tier value: " + - string.Join(", ", missingQuerySymbols)); - } -} - -static void RequireItemTierValuesUseRuntimeQueries(string root) -{ - var actionGameManagerPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "ActionGameManager.cs"); - var inventoryMenuPath = Path.Combine(root, "Assets", "Scripts", "UI", "Menu", "InventoryMenu.cs"); - var zoneRendererPath = Path.Combine(root, "Assets", "Scripts", "Zone Display", "ZoneRenderer.cs"); - var tradeQueriesPath = Path.Combine( - root, - "Packages", - "org.gamecult.aetheria.state", - "Runtime", - "AetheriaRuntimeDaemonTradeItemQueries.cs"); - - var actionGameManager = File.Exists(actionGameManagerPath) - ? File.ReadAllText(actionGameManagerPath) - : throw new InvalidOperationException("Cannot verify item tier value; ActionGameManager.cs is missing."); - var inventoryMenu = File.Exists(inventoryMenuPath) - ? File.ReadAllText(inventoryMenuPath) - : throw new InvalidOperationException("Cannot verify item tier value; InventoryMenu.cs is missing."); - var zoneRenderer = File.Exists(zoneRendererPath) - ? File.ReadAllText(zoneRendererPath) - : throw new InvalidOperationException("Cannot verify item tier value; ZoneRenderer.cs is missing."); - var tradeQueries = File.Exists(tradeQueriesPath) - ? File.ReadAllText(tradeQueriesPath) - : throw new InvalidOperationException("Cannot verify item tier value; runtime trade item queries are missing."); - - if (actionGameManager.Contains("ObservedItemTier(", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "ActionGameManager still exposes item tier value as a Unity ItemManager bridge."); - } - - var forbiddenUnityTierSymbols = new[] - { - "GameManager.ObservedItemTier", - "ItemManager.GetTier(", - "GameManager.ItemManager.GetTier" - }; - var hits = new[] { inventoryMenuPath, zoneRendererPath } - .SelectMany(path => File.ReadLines(path) - .Select((line, index) => new { Path = path, LineNumber = index + 1, Line = line })) - .Where(line => forbiddenUnityTierSymbols.Any(symbol => line.Line.Contains(symbol, StringComparison.Ordinal))) - .Select(line => $"{Path.GetRelativePath(root, line.Path)}:{line.LineNumber}: {line.Line.Trim()}") - .ToArray(); - - if (hits.Length > 0) - { - throw new InvalidOperationException( - "Unity UI/rendering still projects item tiers through ItemManager instead of shared runtime queries: " + - string.Join("; ", hits)); - } - - if (!inventoryMenu.Contains("FormatItemTier(", StringComparison.Ordinal) || - !inventoryMenu.Contains("AetheriaRuntimeDaemonTradeItemQueries.TradeItemValue(", StringComparison.Ordinal) || - !inventoryMenu.Contains("tradeValue.TierName", StringComparison.Ordinal) || - !inventoryMenu.Contains("tradeValue.Upgrades", StringComparison.Ordinal) || - !zoneRenderer.Contains("AetheriaRuntimeDaemonTradeItemQueries.TradeItemValue(", StringComparison.Ordinal) || - !zoneRenderer.Contains("tradeValue.TierColorHex", StringComparison.Ordinal) || - !tradeQueries.Contains("public string TierName { get; }", StringComparison.Ordinal) || - !tradeQueries.Contains("public string TierColorHex { get; }", StringComparison.Ordinal) || - !tradeQueries.Contains("public int Upgrades { get; }", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Item tier labels and pickup colors must use shared runtime trade item query results."); - } -} - -static void RequireInventoryDropdownUseEveSurface(string root) -{ - var inventoryPanelPath = Path.Combine(root, "Assets", "Scripts", "UI", "Menu", "InventoryPanel.cs"); - var inventoryMenuPath = Path.Combine(root, "Assets", "Scripts", "UI", "Menu", "InventoryMenu.cs"); - var inventoryDropdownSurfaceBuilderPath = Path.Combine( - root, - "Packages", - "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."); - } - if (!File.Exists(inventoryMenuPath)) - { - throw new InvalidOperationException("Cannot verify inventory dropdown shell; InventoryMenu.cs is missing."); - } - if (!File.Exists(inventoryDropdownSurfaceBuilderPath)) - { - 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) + "\n" + - File.ReadAllText(inventoryDropdownSurfaceDocumentsPath); - var requiredSymbols = new[] - { - "RenderDropdownSurface(", - "ResolveDropdownSurface(", - "_dropdownSurface = ResolveDropdownSurface();", - "BuildDropdownSurfaceRequest(", - "RuntimeState.InventoryDropdownSurface(BuildDropdownSurfaceRequest()).Latest()", - "HandleDropdownSurfaceCommand(", - "ExecuteDropdownSelection(", - "AetheriaEveUnitySurfaceHost.RenderRuntime(", - "AetheriaEveUnitySurfaceHost.Hide(_dropdownSurfaceDocument)", - "_dropdownSurface,", - "AetheriaRuntimeInventoryDropdownSurfaceCommands.TryRead(request, out var command)", - "AetheriaRuntimeInventoryDropdownCommandKind.Close", - "AetheriaRuntimeInventoryDropdownCommandKind.Select", - "TryResolveDropdownSelection(request, command.Command, out var selection)", - "PayloadValue(request, \"selectionKind\")", - "AetheriaRuntimeInventoryDropdownSelectionKind.EntityBay", - "AetheriaRuntimeInventoryDropdownSelectionKind.Loadout", - "selection.EntityKey", - "_displayedEntityKey", - "_displayedCargoEntityKey", - "TryResolveStationRefitEntity(string entityKey", - "SetPresentationEntityIndex(AetheriaUnityPresentationEntityIndex presentationEntityIndex)", - "_presentationEntityIndex.TryGetPresentationEntityByRecordKey", - "_presentationEntityIndex.TryGetPresentationDockingBayByRecordKey", - "RenderInventoryPanelSurface()", - "AetheriaEveUnitySurfaceHost.RenderRuntime(", - "HandleInventoryPanelSurfaceCommand", - "StationRefitSnapshot", - "LoadoutRestoreOptions" - }; - - var missingSymbols = requiredSymbols - .Where(symbol => !source.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - - if (missingSymbols.Length > 0) - { - throw new InvalidOperationException( - "InventoryPanel no longer lowers the dropdown shell through an Eve surface: " + - string.Join(", ", missingSymbols)); - } - - var forbiddenSymbols = new[] - { - "DropdownSurfaceId", - "CloseDropdownSurfaceCommand", - "SaveLoadoutCommand", - "BuildDropdownSurfaceDefinition(", - "private const string DropdownSurfaceType", - "ResolveDropdownSurfaceDocument(", - "new EveUiToolkitSurfaceLowerer()", - "host.AddComponent", - "ContextMenu.AddDropdown(entity.Name", - "ContextMenu.AddOption(GameManager.DockingBay.Name", - "ContextMenu.AddOption(\"Save Loadout\"", - "ContextMenu.AddDropdown(\"Restore Loadout\"", - "ContextMenu.Show();", - "GameManager.ObservedAvailableEntities()", - "GameManager.TryGetObservedDockingBay(", - "string.Equals(request.Command, AetheriaRuntimeInventoryDropdownSurfaceBuilder.Close", - "_dropdownCommands.TryGetValue(request.Command", - "private readonly Dictionary _dropdownCommands", - "BuildDropdownCommands(", - "ProjectDropdownSurfaceState(", - "ProjectDropdownSurface(", - "ComposeDropdownSurface(", - "_dropdownSurfaceModel.State", - "_dropdownSurfaceModel", - "AetheriaRuntimeInventoryDropdownSurfaceModel", - "AetheriaRuntimeInventoryDropdownSurfaceBuilder.Compose(", - "_dropdownSurfaceProjection", - "GameManager.DockingBay", - "GameManager.AvailableEntities()", - "GameManager.LoadoutTemplates" - }; - - var hits = forbiddenSymbols - .Where(symbol => source.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - - if (hits.Length > 0) - { - throw new InvalidOperationException( - "InventoryPanel still owns dropdown behavior through the old ContextMenu path: " + - string.Join(", ", hits)); - } - - 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("TryResolveCurrentDockingBay(out var dockingBay)", StringComparison.Ordinal) || - !source.Contains("_presentationEntityIndex.TryGetPresentationDockingBayByRecordKey", StringComparison.Ordinal) || - !source.Contains("StationRefitSnapshot", 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) || - source.Contains("TryResolveCurrentDockingBayFacade", StringComparison.Ordinal) || - source.Contains("ResolveDockingState()?.CurrentDocking", StringComparison.Ordinal) || - source.Contains("AetheriaClientReactiveDockingState _reactiveDockingState", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "InventoryPanel dropdown must carry typed entity identity through the shared Eve surface and must project docking display state from StationRefitAsync docking-bay rows."); - } - - if (!inventoryMenu.Contains("TryResolveCurrentEntity(out var currentEntity)", StringComparison.Ordinal) || - !inventoryMenu.Contains("TryResolveCurrentDockingBay(out var dockingBay)", 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) || - inventoryMenu.Contains("TryResolveObservedDockingIndex(out var dockingIndex)", StringComparison.Ordinal) || - inventoryMenu.Contains("dockingIndex.TryResolveCurrentDockingBay", StringComparison.Ordinal) || - inventoryMenu.Contains("TryGetTypedCurrentDockingBayFacade", StringComparison.Ordinal) || - inventoryMenu.Contains("TryResolveCurrentEntityFacade", StringComparison.Ordinal) || - inventoryMenu.Contains("ResolveDockingState()?.CurrentDocking", StringComparison.Ordinal) || - inventoryMenu.Contains("AetheriaClientReactiveDockingState _reactiveDockingState", StringComparison.Ordinal) || - inventoryMenu.Contains("GameManager.TryGetObservedDockingBay(", StringComparison.Ordinal) || - inventoryMenu.Contains("GameManager.DockingBay", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "InventoryMenu must resolve typed current entity/refit state through managed current documents before adapting docking state to Unity objects."); - } - - var requiredBuilderSymbols = new[] - { - "public static class AetheriaRuntimeInventoryDropdownSurfaceBuilder", - "public const string SurfaceId = \"aetheria.inventory.panel.dropdown\"", - "public const string Close = \"aetheria.inventory.panel.dropdown.close\"", - "public const string SaveLoadout = \"aetheria.inventory.panel.dropdown.save_loadout\"", - "public const string DockingBay = \"aetheria.inventory.panel.dropdown.docking_bay\"", - "public enum AetheriaRuntimeInventoryDropdownCommandKind", - "public readonly struct AetheriaRuntimeInventoryDropdownCommand", - "public static class AetheriaRuntimeInventoryDropdownSurfaceCommands", - "public static bool TryRead(", - "AetheriaRuntimeInventoryDropdownGroup", - "AetheriaRuntimeInventoryDropdownOption", - "AetheriaRuntimeInventoryDropdownEntityOption", - "AetheriaRuntimeInventoryDropdownBayOption", - "AetheriaRuntimeInventoryDropdownLoadoutOption", - "AetheriaRuntimeInventoryDropdownSelectionKind", - "public string EntityKey { get; }", - "entityKey: entity.EntityKey", - "public static AetheriaRuntimeSurfaceDocument Build(", - "selectionKind", - "templateIndex", - "public static string EntityEquipmentCommand(", - "public static string EntityBayCommand(", - "public static string EntityCommand(", - "public static string LoadoutCommand(", - "private static AetheriaRuntimeSurfaceDocument Build(", - "providerKind: \"inventory.panel\"", - "The observing client lists available inventory navigation; the shared runtime surface owns the dropdown contract." - }; - var missingBuilderSymbols = requiredBuilderSymbols - .Where(symbol => !inventoryDropdownSurfaceBuilder.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (missingBuilderSymbols.Length > 0) - { - throw new InvalidOperationException( - "Shared runtime inventory dropdown surface builder no longer owns the inventory dropdown shell contract: " + - string.Join(", ", missingBuilderSymbols)); - } - - if (inventoryDropdownSurfaceBuilder.Contains("AetheriaRuntimeInventoryDropdownSurfaceState", StringComparison.Ordinal) || - inventoryDropdownSurfaceBuilder.Contains("public AetheriaRuntimeInventoryDropdownSurfaceState State { get; }", 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 the finished surface document and carry typed selections in command payload; do not rebuild a shadow SurfaceState/model projection layer."); - } -} - -static void RequireStationRefitDockingBaysUseTypedDocuments(string root) -{ - var runtimeDocumentsPath = Path.Combine( - root, - "Packages", - "org.gamecult.aetheria.state", - "Runtime", - "AetheriaRuntimeGameViewportDocuments.cs"); - var runtimeDocumentsFactoryPath = Path.Combine( - root, - "Packages", - "org.gamecult.aetheria.state", - "Runtime", - "AetheriaRuntimeGameDocuments.cs"); - var inventoryPanelPath = Path.Combine(root, "Assets", "Scripts", "UI", "Menu", "InventoryPanel.cs"); - var inventoryMenuPath = Path.Combine(root, "Assets", "Scripts", "UI", "Menu", "InventoryMenu.cs"); - - var runtimeDocuments = File.Exists(runtimeDocumentsPath) - ? File.ReadAllText(runtimeDocumentsPath) - : throw new InvalidOperationException("Cannot verify station-refit docking-bay document; runtime viewport documents are missing."); - var runtimeDocumentsFactory = File.Exists(runtimeDocumentsFactoryPath) - ? File.ReadAllText(runtimeDocumentsFactoryPath) - : throw new InvalidOperationException("Cannot verify station-refit docking-bay document; runtime document factory is missing."); - var inventoryPanel = File.Exists(inventoryPanelPath) - ? File.ReadAllText(inventoryPanelPath) - : throw new InvalidOperationException("Cannot verify station-refit docking-bay document; InventoryPanel.cs is missing."); - var inventoryMenu = File.Exists(inventoryMenuPath) - ? File.ReadAllText(inventoryMenuPath) - : throw new InvalidOperationException("Cannot verify station-refit docking-bay document; InventoryMenu.cs is missing."); - - var requiredDocumentSymbols = new[] - { - "public IReadOnlyList DockingBays", - "public sealed class AetheriaRuntimeStationDockingBayRow", - "public int DockingBayIndex", - "public string OccupiedEntityKey", - "public int OccupiedEntityIndex", - "public string OccupiedEntityName", - "public string OccupiedHullItemKey", - "public bool OccupiedByCurrentEntity", - "public IReadOnlyList CargoItems", - "public IReadOnlyList CargoTargets", - "public sealed class AetheriaRuntimeStationCargoTargetRow", - "public AetheriaRuntimeTradeCargoTargetKind Kind", - "public int TargetIndex", - "public string EntityKey", - "public int BayIndex", - "public int Price", - "public bool CanAfford", - "public int OwnedQuantity" - }; - var missingDocumentSymbols = requiredDocumentSymbols - .Where(symbol => !runtimeDocuments.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - - if (missingDocumentSymbols.Length > 0) - { - throw new InvalidOperationException( - "StationRefitAsync no longer exposes typed docking-bay rows: " + - string.Join(", ", missingDocumentSymbols)); - } - - var requiredDocumentFactorySymbols = new[] - { - "var dockingBays = parent == null", - "DockingBays = dockingBays", - "ProjectStationDockingBays(context, parent, currentEntityIndex)", - "private static IReadOnlyList ProjectStationDockingBays(", - "dockParent.DockingBays", - "dockParent.DockingBayAssignments", - "dockParent.DockingBayContents", - "OccupiedEntityIndex = assignedEntity?.EntityIndex ?? assignedEntityIndex", - "OccupiedEntityKey = assignedEntity == null", - "AetheriaRuntimeRunCheckpointCommit.EntityRecordKey(context.RunId, context.Zone.ZoneIndex, assignedEntity.EntityIndex)", - "OccupiedEntityName = assignedEntity?.Name", - "OccupiedHullItemKey = assignedEntity?.HullItemKey", - "OccupiedByCurrentEntity = assignedEntityIndex >= 0 && assignedEntityIndex == currentEntityIndex", - "CargoItems = dockingBayIndex < contents.Count", - "StationStock(contents[dockingBayIndex], dockingBayIndex)", - "var cargoTargets = parent == null", - "CargoTargets = cargoTargets", - "ProjectStationCargoTargets(parentKey, dockingBayIndex, dockingBays, availableEntities)", - "private static IReadOnlyList ProjectStationCargoTargets(", - "AetheriaRuntimeTradeCargoTargetKind.DockingBay", - "AetheriaRuntimeTradeCargoTargetKind.ShipBay", - "currentDockingBay?.CargoItems", - "entity.CargoItems ?? Array.Empty()", - "StationStockTradeFacts(", - "private static IReadOnlyList StationStockTradeFacts(", - "private static int StationStockPrice(", - "private static int CountStationOwnedQuantity(", - "AetheriaRuntimeDaemonTradeItemQueries.TradeItemValue(", - "CanAfford = price >= 0 && credits >= price", - "OwnedQuantity = CountStationOwnedQuantity" - }; - var missingDocumentFactorySymbols = requiredDocumentFactorySymbols - .Where(symbol => !runtimeDocumentsFactory.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - - if (missingDocumentFactorySymbols.Length > 0) - { - throw new InvalidOperationException( - "StationRefitAsync docking-bay rows are no longer derived from daemon station state: " + - string.Join(", ", missingDocumentFactorySymbols)); - } - - if (runtimeDocumentsFactory.Contains("private static string BuildEntityKey(", StringComparison.Ordinal) || - runtimeDocumentsFactory.Contains("private static bool TryParseEntityKey(", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Runtime document factory reintroduced local entity key folklore; use AetheriaRuntimeRunCheckpointCommit entity key helpers."); - } - - var requiredPanelSymbols = new[] - { - "StationRefitSnapshot", - "RuntimeState.StationRefit.Latest()", - "_presentationEntityIndex.TryGetPresentationDockingBayByRecordKey" - }; - var missingPanelSymbols = requiredPanelSymbols - .Where(symbol => !inventoryPanel.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - - if (missingPanelSymbols.Length > 0) - { - throw new InvalidOperationException( - "InventoryPanel must resolve remaining docking presentation from managed station-refit identity before touching Unity docking objects: " + - string.Join(", ", missingPanelSymbols)); - } - - var requiredMenuSymbols = new[] - { - "TryResolveCurrentDockingBayRow(out AetheriaRuntimeStationDockingBayRow dockingBay)", - "AetheriaRuntimeStationDockingBayRow dockingBay", - "StationRefitSnapshot", - "refit.DockingBays ?? Array.Empty()" - }; - var missingMenuSymbols = requiredMenuSymbols - .Where(symbol => !inventoryMenu.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - - if (missingMenuSymbols.Length > 0) - { - throw new InvalidOperationException( - "InventoryMenu must validate current docking-bay display through StationRefitAsync typed docking-bay rows: " + - string.Join(", ", missingMenuSymbols)); - } - - var forbiddenMenuSymbols = new[] - { - "GameManager.TryGetObservedDockingBay(", - "GameManager.DockingBay" - }; - var forbiddenHits = forbiddenMenuSymbols - .Where(symbol => inventoryPanel.Contains(symbol, StringComparison.Ordinal) || inventoryMenu.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - - if (forbiddenHits.Length > 0) - { - throw new InvalidOperationException( - "Inventory docking-bay display still asks ActionGameManager for broad docking state instead of typed station-refit rows: " + - string.Join(", ", forbiddenHits)); - } -} - -static void RequireNoDeadPopupShells(string root) -{ - var deletedShellPaths = new[] - { - Path.Combine(root, "Assets", "Scripts", "UI", "ContextMenu.cs"), - Path.Combine(root, "Assets", "Scripts", "UI", "ContextMenu.cs.meta"), - Path.Combine(root, "Assets", "Scripts", "UI", "ContextMenuOption.cs"), - Path.Combine(root, "Assets", "Scripts", "UI", "ContextMenuOption.cs.meta"), - Path.Combine(root, "Assets", "Prefabs", "UI", "Context Menu.prefab"), - Path.Combine(root, "Assets", "Prefabs", "UI", "Context Menu.prefab.meta"), - Path.Combine(root, "Assets", "Prefabs", "UI", "Context Menu Option.prefab"), - Path.Combine(root, "Assets", "Prefabs", "UI", "Context Menu Option.prefab.meta") - }; - - var survivingShells = deletedShellPaths - .Where(File.Exists) - .Select(path => Path.GetRelativePath(root, path)) - .ToArray(); - - if (survivingShells.Length > 0) - { - throw new InvalidOperationException( - "The dead ContextMenu popup shell still survives in source or prefab assets: " + - string.Join(", ", survivingShells)); - } - - var sourceChecks = new Dictionary - { - [Path.Combine(root, "Assets", "Scripts", "UI", "Menu", "InventoryPanel.cs")] = new[] - { - "public ContextMenu ContextMenu;" - }, - [Path.Combine(root, "Assets", "Scripts", "Gameplay", "ActionGameManager.cs")] = new[] - { - "public ContextMenu Context;", - "public DropdownMenu Dropdown;" - }, - [Path.Combine(root, "Assets", "Scripts", "UI", "Menu", "SectorRenderer.cs")] = new[] - { - "public PropertiesPanel Properties;", - "Properties.gameObject.SetActive(false);" - } - }; - - var survivingSymbols = sourceChecks - .SelectMany(entry => - { - var text = File.Exists(entry.Key) ? File.ReadAllText(entry.Key) : ""; - return entry.Value - .Where(symbol => text.Contains(symbol, StringComparison.Ordinal)) - .Select(symbol => $"{Path.GetRelativePath(root, entry.Key)}: {symbol}"); - }) - .ToArray(); - - if (survivingSymbols.Length > 0) - { - throw new InvalidOperationException( - "Renderer-local popup shell authority still survives in live source: " + - string.Join("; ", survivingSymbols)); - } - - var serializedChecks = new Dictionary - { - [Path.Combine(root, "Assets", "Scenes", "ARPG.unity")] = new[] - { - "ContextMenu: {", - "propertyPath: ContextMenu", - "Context: {fileID:", - "Properties: {fileID:" - }, - [Path.Combine(root, "Assets", "Prefabs", "UI", "Inventory.prefab")] = new[] - { - "ContextMenu: {" - } - }; - - var survivingSerializedShells = serializedChecks - .SelectMany(entry => - { - var text = File.Exists(entry.Key) ? File.ReadAllText(entry.Key) : ""; - return entry.Value - .Where(symbol => text.Contains(symbol, StringComparison.Ordinal)) - .Select(symbol => $"{Path.GetRelativePath(root, entry.Key)}: {symbol}"); - }) - .ToArray(); - - if (survivingSerializedShells.Length > 0) - { - throw new InvalidOperationException( - "Scene or prefab YAML still serializes the deleted popup shell or orphan PropertiesPanel links: " + - string.Join("; ", survivingSerializedShells)); - } -} - -static void RequirePlayerSettingsEveSurface(string root) -{ - var surfaceDocumentsPath = Path.Combine(root, "Aetheria.State", "AetheriaEveSurfaceDocuments.cs"); - var bridgePath = Path.Combine(root, "Aetheria.State", "AetheriaEveCommandBridge.cs"); - var providerPath = Path.Combine(root, "Aetheria.State", "AetheriaEveSurfaceDocuments.cs"); - var daemonHostPath = Path.Combine(root, "Aetheria.State.Daemon", "Program.cs"); - var sharedCommandsPath = Path.Combine( - root, - "Packages", - "org.gamecult.aetheria.state", - "Runtime", - "AetheriaRuntimePlayerSettingsCommands.cs"); - var surfaceBuilderPath = Path.Combine( - root, - "Packages", - "org.gamecult.aetheria.state", - "Runtime", - "AetheriaRuntimePlayerSettingsSurfaceBuilder.cs"); - - if (!File.Exists(surfaceDocumentsPath)) - { - throw new InvalidOperationException("Player settings Eve surface document is missing."); - } - - var surfaceDocuments = File.ReadAllText(surfaceDocumentsPath); - var requiredSurfaceDocumentSymbols = new[] - { - "AetheriaRuntimePlayerSettingsCommands.SurfaceId", - "AetheriaRuntimePlayerSettingsSurfaceBuilder.Build", - "settings.ActiveRunKey" - }; - - var missingSurfaceDocumentSymbols = requiredSurfaceDocumentSymbols - .Where(symbol => !surfaceDocuments.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - - if (missingSurfaceDocumentSymbols.Length > 0) - { - throw new InvalidOperationException( - "Player settings Eve surface document is missing required typed controls: " + - string.Join(", ", missingSurfaceDocumentSymbols)); - } - - if (surfaceDocuments.Contains("AetheriaRuntimePlayerSettingsSurfaceState", StringComparison.Ordinal) || - surfaceDocuments.Contains("new AetheriaRuntimePlayerSettingsSurfaceState(", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Player settings Eve surface document should pass typed settings directly into the shared builder, not repack them into a shadow surface state."); - } - - var runtimeVerseClientPath = Path.Combine( - root, - "Packages", - "org.gamecult.aetheria.state", - "Runtime", - "AetheriaRuntimeVerseClient.cs"); - var runtimeEveCommandClientPath = Path.Combine( - root, - "Packages", - "org.gamecult.aetheria.state", - "Runtime", - "AetheriaRuntimeEveCommandClient.cs"); - var runtimeUiPath = Path.Combine( - root, - "Packages", - "org.gamecult.aetheria.state", - "Runtime", - "AetheriaUi.cs"); - var runtimeVerseClient = File.Exists(runtimeVerseClientPath) - ? File.ReadAllText(runtimeVerseClientPath) - : throw new InvalidOperationException("Managed runtime Verse client is missing."); - var runtimeEveCommandClient = File.Exists(runtimeEveCommandClientPath) - ? File.ReadAllText(runtimeEveCommandClientPath) - : throw new InvalidOperationException("Managed runtime Eve command client is missing."); - var runtimeUi = File.Exists(runtimeUiPath) - ? File.ReadAllText(runtimeUiPath) - : throw new InvalidOperationException("Managed runtime UI command facade is missing."); - var sharedCommands = File.Exists(sharedCommandsPath) - ? File.ReadAllText(sharedCommandsPath) - : throw new InvalidOperationException("Shared player-settings Eve command contract is missing."); - var surfaceBuilder = File.Exists(surfaceBuilderPath) - ? File.ReadAllText(surfaceBuilderPath) - : throw new InvalidOperationException("Shared player-settings Eve surface builder is missing."); - - if (!runtimeUi.Contains("public async Task SurfaceCommandAsync(", StringComparison.Ordinal) || - !runtimeUi.Contains(".SubmitKnownSurfaceCommandAsync(request, clientId, flush)", StringComparison.Ordinal) || - !runtimeVerseClient.Contains("SubmitKnownSurfaceCommandAsync(", StringComparison.Ordinal) || - !runtimeVerseClient.Contains("AetheriaRuntimeEveCommandClient.TryCreateKnownSurfaceCommand(request, out var envelope)", StringComparison.Ordinal) || - !runtimeVerseClient.Contains("Database.PutAsync(AetheriaRuntimeVerseRecordKeys.EveCommand(command.CommandId), command)", StringComparison.Ordinal) || - !runtimeEveCommandClient.Contains("AetheriaRuntimePlayerSettingsCommands.SurfaceId", StringComparison.Ordinal) || - !runtimeEveCommandClient.Contains("AetheriaRuntimePlayerSettingsCommands.IsKnown(command)", StringComparison.Ordinal) || - !runtimeEveCommandClient.Contains("CreatePlayerSettingsCommand(CommandKindForSurface(request), request, clientId)", StringComparison.Ordinal) || - !runtimeEveCommandClient.Contains("playerSettings: ReadPlayerSettingsBody(request)", StringComparison.Ordinal) || - !runtimeEveCommandClient.Contains("invocation: request.Operation", StringComparison.Ordinal) || - !runtimeEveCommandClient.Contains("payload: request.Payload", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Managed Eve command path no longer submits typed player-settings surface commands through CultMesh."); - } - - if ((File.Exists(bridgePath) && File.ReadAllText(bridgePath).Contains("MutableDocument", StringComparison.Ordinal)) || - (File.Exists(daemonHostPath) && File.ReadAllText(daemonHostPath).Contains("MutableDocument", StringComparison.Ordinal)) || - runtimeVerseClient.Contains("EveSurfaceState", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Player-settings Eve command path still depends on local EveSurfaceState publication instead of managed CultMesh command documents."); - } - - if (!sharedCommands.Contains("SurfaceId = \"aetheria.player_settings\"", StringComparison.Ordinal) || - !sharedCommands.Contains("public static bool IsKnown", StringComparison.Ordinal) || - !sharedCommands.Contains("SetPlayerName", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Shared player-settings Eve command contract is missing the surface id or command registry helper."); - } - - if (!surfaceBuilder.Contains("public static class AetheriaRuntimePlayerSettingsSurfaceBuilder", StringComparison.Ordinal) || - !surfaceBuilder.Contains("\"control.text\"", StringComparison.Ordinal) || - !surfaceBuilder.Contains("SetPlayerName", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Shared player-settings Eve surface builder no longer owns the portable settings surface contract."); - } - - if (surfaceBuilder.Contains("AetheriaRuntimePlayerSettingsSurfaceState", StringComparison.Ordinal) || - surfaceBuilder.Contains("new AetheriaRuntimePlayerSettingsSurfaceState(", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Shared player-settings Eve surface builder should accept typed documents or semantic values directly, not a duplicate SurfaceState DTO."); - } -} - -static void RequireDaemonHostDoesNotDrainRuntimeCommits(string root) -{ - var economyServerPath = Path.Combine(root, "Economy" + ".Server", "Program.cs"); - var economyServerProjectPath = Path.Combine(root, "Economy" + ".Server", "Economy" + ".Server.csproj"); - var daemonHostPath = Path.Combine(root, "Aetheria.State.Daemon", "Program.cs"); - var drainCommandsPath = Path.Combine(root, "Aetheria.State.DrainCommands", "Program.cs"); - var packageCommitLogPath = Path.Combine( - root, - "Packages", - "org.gamecult.aetheria.state", - "Runtime", - "AetheriaRuntimeStateCommitLog.cs"); - var generatedUnityProjectPath = Path.Combine(root, "GameCult.Aetheria.State.Unity.csproj"); - if (File.Exists(economyServerPath) || File.Exists(economyServerProjectPath)) - { - throw new InvalidOperationException("Economy" + ".Server still exists beside the first-class Aetheria.State.Daemon host."); - } - - var daemonHost = File.Exists(daemonHostPath) - ? File.ReadAllText(daemonHostPath) - : throw new InvalidOperationException("Cannot verify daemon host command authority; Aetheria.State.Daemon program is missing."); - var generatedUnityProject = File.Exists(generatedUnityProjectPath) - ? File.ReadAllText(generatedUnityProjectPath) - : throw new InvalidOperationException("Cannot verify daemon host command authority; GameCult.Aetheria.State.Unity.csproj is missing."); - - if (File.Exists(drainCommandsPath) || - File.Exists(Path.Combine(root, "Aetheria.State.DrainCommands", "Aetheria.State.DrainCommands.csproj"))) - { - throw new InvalidOperationException( - "Aetheria.State.DrainCommands still exists as a sidecar command applicator; command acceptance belongs inside the Verse daemon."); - } - - if (File.Exists(packageCommitLogPath) || - generatedUnityProject.Contains("AetheriaRuntimeStateCommitLog.cs", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Unity package still exposes the legacy runtime state commit log instead of typed Eve/daemon requests."); - } - - var forbiddenSymbols = new[] - { - "AetheriaLegacyRuntimeSnapshotImporter.ImportPendingAsync", - "DrainCommandsRuntimeCommitsAsync", - "CountPendingRuntimeCommits", - "Applied pending Aetheria runtime commits", - "AetheriaRuntimeCommitDrainStatus", - "PutRuntimeCommitDrainStatusAsync", - "GetRuntimeCommitDrainStatusAsync", - "PublishRuntimeCommitDrainDisabledAsync", - "disabled-daemon-authority" - }; - - var hits = forbiddenSymbols - .SelectMany(symbol => new[] - { - (Path: daemonHostPath, Symbol: symbol, Hit: daemonHost.Contains(symbol, StringComparison.Ordinal)) - }) - .Where(entry => entry.Hit) - .Select(entry => $"{Path.GetRelativePath(root, entry.Path)}: {entry.Symbol}") - .ToArray(); - - if (hits.Length > 0) - { - throw new InvalidOperationException( - "First-party daemon drains still apply legacy runtime commits instead of daemon/Eve command authority: " + - string.Join(", ", hits)); - } - - var requiredSymbols = new[] - { - "AetheriaEveCommandBridge.AcceptObservedAsync", - "AppliedInputSettingsCommands = report.AcceptedInputSettingsCommands", - "AppliedLoadoutTemplateCommands = report.AcceptedLoadoutTemplateCommands" - }; - - var missingSymbols = requiredSymbols - .Where(symbol => !daemonHost.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - - if (missingSymbols.Length > 0) - { - throw new InvalidOperationException( - "Aetheria.State.Daemon no longer advertises daemon authority while accepting observed Eve command documents: " + - string.Join(", ", missingSymbols)); - } -} - -static void RequireInventoryDocumentSlotIdentity(string root) -{ - var documentsFactoryPath = Path.Combine( - root, - "Packages", - "org.gamecult.aetheria.state", - "Runtime", - "AetheriaRuntimeGameDocuments.cs"); - var viewportDocumentsPath = Path.Combine( - root, - "Packages", - "org.gamecult.aetheria.state", - "Runtime", - "AetheriaRuntimeGameViewportDocuments.cs"); - - if (!File.Exists(documentsFactoryPath)) - { - throw new InvalidOperationException("Cannot verify inventory document slot identity; AetheriaRuntimeGameDocuments.cs is missing."); - } - if (!File.Exists(viewportDocumentsPath)) - { - throw new InvalidOperationException("Cannot verify inventory document slot identity; AetheriaRuntimeGameViewportDocuments.cs is missing."); - } - - var documentsFactory = File.ReadAllText(documentsFactoryPath); - var viewportDocuments = File.ReadAllText(viewportDocumentsPath); - - var requiredDocumentFactorySymbols = new[] - { - "AddSlot(items, \"equipment\", equipmentIndex, equipment[equipmentIndex])", - "AddSlot(items, \"cargo\", cargoBayIndex, slot)", - "SourceIndex = sourceIndex", - "X = slot.X", - "Y = slot.Y" - }; - var missingDocumentFactorySymbols = requiredDocumentFactorySymbols - .Where(symbol => !documentsFactory.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - - if (missingDocumentFactorySymbols.Length > 0) - { - throw new InvalidOperationException( - "Inventory document no longer carries enough typed slot identity for cargo/equipment operations: " + - string.Join(", ", missingDocumentFactorySymbols)); - } - - var requiredDocumentSymbols = new[] - { - "public sealed class AetheriaRuntimeInventoryItem", - "[Key(6)]", - "public int SourceIndex { get; set; } = -1;", - "[Key(7)]", - "public int X { get; set; } = -1;", - "[Key(8)]", - "public int Y { get; set; } = -1;" - }; - var missingDocumentSymbols = requiredDocumentSymbols - .Where(symbol => !viewportDocuments.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - - if (missingDocumentSymbols.Length > 0) - { - throw new InvalidOperationException( - "Inventory item document no longer publishes cargo/equipment slot identity to CultMesh clients: " + - string.Join(", ", missingDocumentSymbols)); - } -} - -static void RequireInventoryValidationUsesManagedTypedDocuments(string root) -{ - var clientStatePath = Path.Combine( - root, - "Packages", - "org.gamecult.aetheria.state", - "Runtime", - "AetheriaClientState.cs"); - var inventoryMenuPath = Path.Combine(root, "Assets", "Scripts", "UI", "Menu", "InventoryMenu.cs"); - var inventoryPanelPath = Path.Combine(root, "Assets", "Scripts", "UI", "Menu", "InventoryPanel.cs"); - - if (!File.Exists(clientStatePath) || - !File.Exists(inventoryMenuPath) || - !File.Exists(inventoryPanelPath)) - { - throw new InvalidOperationException("Cannot verify managed inventory document access; expected client state and inventory UI sources are missing."); - } - - var clientState = File.ReadAllText(clientStatePath); - if (!clientState.Contains("public CultMeshDocumentHandle Inventory(", StringComparison.Ordinal) || - !clientState.Contains("public CultMeshDocumentHandle ZoneDetails(", 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 for current entity, station refit, inventory, and zone documents; callers use Handle.Reactive() when they need a stream."); - } - - var sources = new Dictionary - { - ["InventoryMenu.cs"] = File.ReadAllText(inventoryMenuPath), - ["InventoryPanel.cs"] = File.ReadAllText(inventoryPanelPath) - }; - foreach (var (name, source) in sources) - { - var compact = CompactSource(source); - var runtimeId = name == "InventoryMenu.cs" - ? "unity-inventory-menu" - : "unity-inventory"; - var requiredSymbols = new[] - { - $"AetheriaUnityRuntimeClientProvider.RuntimeState(\"{runtimeId}\")", - "CurrentEntitySnapshot()", - "StationRefitSnapshot", - "RuntimeState.CurrentEntity.Latest()", - "RuntimeState.StationRefit.Latest()" - }; - var missingSymbols = requiredSymbols - .Where(symbol => !source.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - var forbiddenShortcutSymbols = new[] - { - $"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 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[] - { - "AetheriaRuntimeCurrentEntitySession", - "AetheriaRuntimeStationRefitSession", - "AetheriaRuntimeInventorySession", - ".ObserveEntity()", - ".ObserveStationRefit()", - ".ObserveInventory(entityIndex)", - ".Latest()", - ".Latest()", - ".Details.Latest(entityIndex)", - ".State.Current.Entity.LatestAsync()", - ".State.StationRefit.LatestAsync()", - ".Details.Inventory(entityIndex).LatestAsync()" - } - .Select(CompactSource) - .ToArray(); - var hits = forbiddenCompactedSymbols - .Where(symbol => compact.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (hits.Length > 0) - { - throw new InvalidOperationException( - $"{name} still routes inventory validation through session wrappers instead of direct managed reactive typed documents."); - } - - var forbiddenCurrentDocumentSymbols = new[] - { - "CultMeshReactiveDocument<", - $".ReactiveCurrentEntity(\"{runtimeId}\")", - $".ReactiveStationRefit(\"{runtimeId}\")", - ".ReactiveInventory(entityIndex)", - "_currentEntity?.Dispose()", - "_stationRefit?.Dispose()", - "_inventory?.Dispose()" - } - .Where(symbol => source.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (forbiddenCurrentDocumentSymbols.Length > 0) - { - throw new InvalidOperationException( - $"{name} should sample inventory validation state through current typed documents instead of owning reactive handles: " + - string.Join(", ", forbiddenCurrentDocumentSymbols)); - } - } - - Console.WriteLine("Inventory validation: Unity samples shell context only; mutation authority belongs to daemon-presented typed inventory surfaces"); -} - -static void RequireMenuDockingUsesManagedTypedSnapshot(string root) -{ - var clientStatePath = Path.Combine( - root, - "Packages", - "org.gamecult.aetheria.state", - "Runtime", - "AetheriaClientState.cs"); - if (!File.Exists(clientStatePath)) - throw new InvalidOperationException("Cannot verify managed docking snapshot access; AetheriaClientState.cs is missing."); - - var clientState = File.ReadAllText(clientStatePath); - var requiredClientSymbols = new[] - { - "public CultMeshDocumentHandle StationRefit { get; }", - "public CultMeshDocumentHandle CurrentDocking { get; }" - }; - var missingClientSymbols = requiredClientSymbols - .Where(symbol => !clientState.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (missingClientSymbols.Length > 0) - { - throw new InvalidOperationException( - "AetheriaClientState must expose managed typed docking documents directly for Unity menu surfaces: " + - string.Join(", ", missingClientSymbols)); - } - - var forbiddenClientSymbols = new[] - { - "AetheriaClientDockingState", - "AetheriaClientDockingSnapshot", - "AetheriaClientReactiveDockingState", - "AetheriaClientCurrentState", - "LatestDockingState", - "ReactiveDockingState" - }; - var forbiddenClientHits = forbiddenClientSymbols - .Where(symbol => clientState.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (forbiddenClientHits.Length > 0) - { - throw new InvalidOperationException( - "AetheriaClientState still exposes aggregate docking snapshot wrappers instead of direct managed typed documents: " + - string.Join(", ", forbiddenClientHits)); - } - - if (clientState.Contains("public AetheriaRuntimeObservedDockingState? CurrentDocking(", StringComparison.Ordinal) || - clientState.Contains("AetheriaRuntimeObservedDockingState.TryCreateCurrent(entity, docking, refit", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "AetheriaClientState must not expose one-shot observed docking aggregation; callers should hold the managed typed docking documents they need."); - } - - var menuPaths = new[] - { - Path.Combine(root, "Assets", "Scripts", "UI", "Menu", "InventoryMenu.cs"), - Path.Combine(root, "Assets", "Scripts", "UI", "Menu", "InventoryPanel.cs"), - Path.Combine(root, "Assets", "Scripts", "UI", "Menu", "LocalMenu.cs"), - Path.Combine(root, "Assets", "Scripts", "UI", "Menu", "MenuPanel.cs"), - Path.Combine(root, "Assets", "Scripts", "UI", "Menu", "TradeMenu.cs") - }; - - var missingPaths = menuPaths.Where(path => !File.Exists(path)).ToArray(); - if (missingPaths.Length > 0) - { - throw new InvalidOperationException( - "Cannot verify managed docking snapshot menu access; missing sources: " + - string.Join(", ", missingPaths.Select(path => Path.GetRelativePath(root, path)))); - } - - var offenders = menuPaths - .Select(path => new - { - Path = path, - Source = File.ReadAllText(path) - }) - .Select(entry => new - { - entry.Path, - entry.Source, - Compact = CompactSource(entry.Source) - }) - .Where(entry => - !HasManagedDockingSnapshotAccess(entry.Source) || - entry.Source.Contains(".DockingState.Reactive()", StringComparison.Ordinal) || - entry.Compact.Contains(CompactSource(".DockingState.Latest()"), StringComparison.Ordinal) || - entry.Compact.Contains(CompactSource(".DockingState.TryLatest("), StringComparison.Ordinal)) - .Select(entry => Path.GetRelativePath(root, entry.Path)) - .ToArray(); - - if (offenders.Length > 0) - { - throw new InvalidOperationException( - "Unity menu docking state must read managed typed docking documents instead of nested latest/reactive wrappers: " + - string.Join(", ", offenders)); - } - - var menuPanel = File.ReadAllText(Path.Combine(root, "Assets", "Scripts", "UI", "Menu", "MenuPanel.cs")); - var tradeMenu = File.ReadAllText(Path.Combine(root, "Assets", "Scripts", "UI", "Menu", "TradeMenu.cs")); - var directDockingOffenders = new List(); - if (menuPanel.Contains("CultMeshReactiveDocument _currentDocking", StringComparison.Ordinal) || - menuPanel.Contains(".ReactiveCurrentDocking(\"unity-runtime-menu-tabs\")", StringComparison.Ordinal) || - menuPanel.Contains("docking = _currentDocking.Current", StringComparison.Ordinal) || - menuPanel.Contains("ResolveClient().State.Latest()", StringComparison.Ordinal)) - { - directDockingOffenders.Add("Assets/Scripts/UI/Menu/MenuPanel.cs"); - } - - if (tradeMenu.Contains("CultMeshReactiveDocument _currentDocking", StringComparison.Ordinal) || - tradeMenu.Contains(".ReactiveCurrentDocking(\"unity-trade\")", StringComparison.Ordinal) || - tradeMenu.Contains("docking = _currentDocking.Current", StringComparison.Ordinal) || - tradeMenu.Contains("CultMeshReactiveDocument _stationRefit", StringComparison.Ordinal) || - tradeMenu.Contains(".ReactiveStationRefit(\"unity-trade\")", StringComparison.Ordinal) || - tradeMenu.Contains("return _stationRefit?.Current", StringComparison.Ordinal) || - tradeMenu.Contains("ResolveClient().State.Latest()", StringComparison.Ordinal)) - { - directDockingOffenders.Add("Assets/Scripts/UI/Menu/TradeMenu.cs"); - } - - if (directDockingOffenders.Count > 0) - { - throw new InvalidOperationException( - "Unity menu docking reads must use current typed state directly and adapt Unity objects at the caller edge; do not own duplicate reactive docking/refit documents: " + - string.Join(", ", directDockingOffenders)); - } - - 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) || - source.Contains(".CurrentStationRefit()", StringComparison.Ordinal)) && - !source.Contains("AetheriaClientReactiveDockingState _reactiveDockingState", StringComparison.Ordinal) && - !source.Contains("CultMeshReactiveDocument", StringComparison.Ordinal) && - !source.Contains("CultMeshReactiveDocument", StringComparison.Ordinal)) - { - return true; - } - - return false; -} - -static void RequireUnitySharedDocumentAccessorErgonomics(string root) -{ - var clientStatePath = Path.Combine( - root, - "Packages", - "org.gamecult.aetheria.state", - "Runtime", - "AetheriaClientState.cs"); - if (!File.Exists(clientStatePath)) - throw new InvalidOperationException("Cannot verify shared document accessor ergonomics; AetheriaClientState.cs is missing."); - - var clientState = File.ReadAllText(clientStatePath); - var requiredClientSymbols = new[] - { - "public CultMeshDocumentHandle Catalog", - "public CultMeshDocumentHandle PlayerSettings", - "public CultMeshDocumentHandle Inventory(" - }; - var missingClientSymbols = requiredClientSymbols - .Where(symbol => !clientState.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (missingClientSymbols.Length > 0) - { - throw new InvalidOperationException( - "AetheriaClientState must expose named typed document handles for shared and indexed Unity documents, with reactive sync owned by CultMesh handles: " + - string.Join(", ", missingClientSymbols)); - } - - if (clientState.Contains("public Task LatestAsync", StringComparison.Ordinal) || - clientState.Contains("public TDocument Latest", StringComparison.Ordinal) || - clientState.Contains("public Task> ReactiveAsync", StringComparison.Ordinal) || - clientState.Contains("public Observable Watch", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "AetheriaClientState must not expose latest/watch/session wrappers; callers should hold native typed documents and CultMesh reactive handles."); - } - - var unityPaths = new[] - { - Path.Combine(root, "Assets", "Scripts", "Gameplay", "ActionBarSlot.cs"), - Path.Combine(root, "Assets", "Scripts", "Gameplay", "AetheriaUnityGameplayBootShell.cs"), - Path.Combine(root, "Assets", "Scripts", "Gameplay", "AetheriaUnityRuntimeClientProvider.cs"), - Path.Combine(root, "Assets", "Scripts", "Gameplay", "AetheriaUnityTargetPresentation.cs"), - Path.Combine(root, "Assets", "Scripts", "UI", "HUD", "SchematicDisplay.cs"), - Path.Combine(root, "Assets", "Scripts", "UI", "InputScreen", "InputDisplayLayout.cs"), - Path.Combine(root, "Assets", "Scripts", "UI", "MainMenu.cs"), - Path.Combine(root, "Assets", "Scripts", "UI", "Menu", "MapRenderer.cs"), - Path.Combine(root, "Assets", "Scripts", "UI", "Menu", "InventoryMenu.cs"), - Path.Combine(root, "Assets", "Scripts", "UI", "Menu", "InventoryPanel.cs"), - Path.Combine(root, "Assets", "Scripts", "UI", "Menu", "TradeMenu.cs"), - Path.Combine(root, "Assets", "Scripts", "UI", "Menu", "SectorRenderer.cs"), - Path.Combine(root, "Assets", "Scripts", "Zone Display", "ZoneRenderer.cs"), - Path.Combine(root, "Assets", "Scripts", "Zone Display", "VolumeCloudRenderer.cs") - }; - - var missingPaths = unityPaths.Where(path => !File.Exists(path)).ToArray(); - if (missingPaths.Length > 0) - { - throw new InvalidOperationException( - "Cannot verify shared Unity document accessor ergonomics; missing sources: " + - string.Join(", ", missingPaths.Select(path => Path.GetRelativePath(root, path)))); - } - - var forbiddenCompactedSymbols = new[] - { - ".State.Catalog.Latest()", - ".State.Settings.Player.Latest()", - ".State.Settings.VerseHost.Latest()" - } - .Select(CompactSource) - .ToArray(); - var offenders = unityPaths - .Select(path => new - { - Path = path, - Compact = CompactSource(File.ReadAllText(path)) - }) - .Where(entry => forbiddenCompactedSymbols.Any(symbol => entry.Compact.Contains(symbol, StringComparison.Ordinal))) - .Select(entry => Path.GetRelativePath(root, entry.Path)) - .ToArray(); - - if (offenders.Length > 0) - { - throw new InvalidOperationException( - "Unity presentation code still walks shared CultMesh document handles instead of managed typed document access: " + - string.Join(", ", offenders)); - } - - var actionBarSlot = File.ReadAllText(Path.Combine( - root, - "Assets", - "Scripts", - "Gameplay", - "ActionBarSlot.cs")); - var requiredActionBarSymbols = new[] - { - "public abstract class ActionBarBinding", - "AetheriaUnityRuntimeClientProvider", - "AetheriaUnityRuntimeClientProvider.RuntimeState(\"unity-action-bar\")", - "s_runtimeState.Catalog.Latest()?.FindItem(item, x => x.ItemKey)", - ".FindItem(item, x => x.ItemKey)", - "private void OnDestroy()" - }; - var missingActionBarSymbols = requiredActionBarSymbols - .Where(symbol => !actionBarSlot.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (missingActionBarSymbols.Length > 0) - { - throw new InvalidOperationException( - "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) || - actionBarSlot.Contains("public virtual void Dispose()", StringComparison.Ordinal) || - actionBarSlot.Contains("Client.State.ObserveCatalog()", StringComparison.Ordinal) || - actionBarSlot.Contains("Client.State.ReactiveCatalogSnapshot()", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "ActionBarSlot still routes through client/session/reactive catalog plumbing instead of reading the named current catalog from typed client state."); - } - - var schematicDisplay = File.ReadAllText(Path.Combine( - root, - "Assets", - "Scripts", - "UI", - "HUD", - "SchematicDisplay.cs")); - var playerHudStatePath = Path.Combine( - root, - "Packages", - "org.gamecult.aetheria.state", - "Runtime", - "AetheriaRuntimePlayerHudState.cs"); - var requiredSchematicDisplaySymbols = new[] - { - "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()", - "private AetheriaRuntimeCurrentEntityHudStatus CurrentEntityHudStatus()", - "?.Hud ?? new AetheriaRuntimeCurrentEntityHudStatus();" - }; - var missingSchematicDisplaySymbols = requiredSchematicDisplaySymbols - .Where(symbol => !schematicDisplay.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (missingSchematicDisplaySymbols.Length > 0) - { - throw new InvalidOperationException( - "SchematicDisplay should sample shared catalog/settings/current-entity HUD through named current typed documents: " + - string.Join(", ", missingSchematicDisplaySymbols)); - } - if (schematicDisplay.Contains("ResolveCatalog()?.Current?.FindItem", StringComparison.Ordinal) || - schematicDisplay.Contains("private CultMeshReactiveDocument ResolveCatalog()", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "SchematicDisplay should expose catalog values through CatalogSnapshot() instead of returning reactive document plumbing to callers."); - } - - var forbiddenSchematicDisplaySymbols = new[] - { - "CultMeshReactiveDocument", - "CultMeshReactiveDocument", - "CultMeshReactiveDocument", - ".ReactiveCatalogSnapshot(\"unity-schematic-display\")", - ".ReactivePlayerSettingsDocument(\"unity-schematic-display\")", - ".ReactiveCurrentEntity(\"unity-schematic-display\")", - "_catalog?.Dispose()", - "_playerSettings?.Dispose()", - "_currentEntity?.Dispose()", - "AetheriaRuntimePlayerHudSession _playerHud", - "ResolvePlayerHud()", - "ResolveClient().State.ObservePlayerHud()", - "ResolvePlayerHud()?.Catalog", - "ResolvePlayerHud()?.PlayerSettings", - "ResolvePlayerHud()?.Hud" - }; - var schematicDisplayHits = forbiddenSchematicDisplaySymbols - .Where(symbol => schematicDisplay.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (schematicDisplayHits.Length > 0) - { - throw new InvalidOperationException( - "SchematicDisplay still routes through legacy HUD/reactive wrappers instead of named current typed documents: " + - string.Join(", ", schematicDisplayHits)); - } - - if (File.Exists(playerHudStatePath) || - clientState.Contains("ObservePlayerHud(", StringComparison.Ordinal) || - clientState.Contains("AetheriaRuntimePlayerHudSession", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Player HUD state must be read as direct reactive catalog/settings/current-entity documents; remove the aggregate AetheriaRuntimePlayerHudSession helper."); - } - - var volumeCloudRenderer = File.ReadAllText(Path.Combine( - root, - "Assets", - "Scripts", - "Zone Display", - "VolumeCloudRenderer.cs")); - var requiredVolumeCloudSymbols = new[] - { - "AetheriaUnityRuntimeClientProvider", - "AetheriaUnityRuntimeClientProvider.RuntimeState(\"unity-volume-cloud-renderer\")", - "_runtimeState.PlayerSettings.Latest()" - }; - var missingVolumeCloudSymbols = requiredVolumeCloudSymbols - .Where(symbol => !volumeCloudRenderer.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (missingVolumeCloudSymbols.Length > 0) - { - throw new InvalidOperationException( - "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) || - volumeCloudRenderer.Contains("AetheriaRuntimePlayerSettingsSession _playerSettings", StringComparison.Ordinal) || - volumeCloudRenderer.Contains(".ObservePlayer()", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "VolumeCloudRenderer still routes player settings through legacy session/reactive wrapper state instead of named current typed state."); - } - - var targetPresentation = File.ReadAllText(Path.Combine( - root, - "Assets", - "Scripts", - "Gameplay", - "AetheriaUnityTargetPresentation.cs")); - var deletedObservedTargetQueryPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "AetheriaUnityObservedTargetQuery.cs"); - if (File.Exists(deletedObservedTargetQueryPath)) - { - throw new InvalidOperationException( - "AetheriaUnityObservedTargetQuery is dead contact-query chaff; target presentation should read typed zone contacts directly."); - } - - var requiredTargetContactSymbols = new[] - { - "public Func ResolveZoneContacts { get; set; }", - "GetObservedTarget(Entity observer)", - "GetObservedVisibleContacts(", - "ResolveZoneContacts?.Invoke()" - }; - var missingTargetContactSymbols = requiredTargetContactSymbols - .Where(symbol => !targetPresentation.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (missingTargetContactSymbols.Length > 0) - { - throw new InvalidOperationException( - "AetheriaUnityTargetPresentation should query typed zone contacts directly for target HUD state: " + - string.Join(", ", missingTargetContactSymbols)); - } - - if (targetPresentation.Contains("CultMeshReactiveDocument", StringComparison.Ordinal) || - targetPresentation.Contains(".ReactiveZoneContacts(\"unity-observed-target-query\")", StringComparison.Ordinal) || - targetPresentation.Contains("_zoneContacts?.Dispose()", StringComparison.Ordinal) || - targetPresentation.Contains("_zoneContacts?.Current", StringComparison.Ordinal) || - targetPresentation.Contains("AetheriaRuntimeZoneContactsSession _zoneContacts", StringComparison.Ordinal) || - targetPresentation.Contains(".ObserveZoneContacts()", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "AetheriaUnityTargetPresentation still routes zone contacts through legacy session/reactive wrapper state instead of typed current state."); - } - - var mapRenderer = File.ReadAllText(Path.Combine( - root, - "Assets", - "Scripts", - "UI", - "Menu", - "MapRenderer.cs")); - var requiredMapRendererSharedDocumentSymbols = new[] - { - "AetheriaUnityGameViewportDocuments _viewportDocuments", - "ClearViewportCaches()", - "_viewportDocuments?.Dispose()", - "_viewportDocuments?.CurrentObjectsViewport", - "_viewportDocuments?.CurrentRenderSplatsViewport", - "private void OnDestroy()" - }; - var missingMapRendererSharedDocumentSymbols = requiredMapRendererSharedDocumentSymbols - .Where(symbol => !mapRenderer.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - var compactMapRenderer = CompactSource(mapRenderer); - missingMapRendererSharedDocumentSymbols = missingMapRendererSharedDocumentSymbols - .Concat(new[] - { - ".MapViewportDocuments(viewport,\"unity-map-renderer\")", - "AetheriaUnityRuntimeClientProvider.RuntimeState(\"unity-map-renderer\")", - "_runtimeState.PlayerSettings.Latest()" - } - .Select(CompactSource) - .Where(symbol => !compactMapRenderer.Contains(symbol, StringComparison.Ordinal))) - .ToArray(); - if (missingMapRendererSharedDocumentSymbols.Length > 0) - { - throw new InvalidOperationException( - "MapRenderer should bind viewport render feeds through managed reactive documents and sample settings through current typed state: " + - string.Join(", ", missingMapRendererSharedDocumentSymbols)); - } - - var forbiddenMapRendererSharedDocumentSymbols = new[] - { - "CultMeshReactiveDocument _objectsViewport", - "CultMeshReactiveDocument _renderSplatsViewport", - ".ReactiveObjectsViewport(viewport, \"unity-map-renderer\")", - ".ReactiveRenderSplatsViewport(viewport, \"unity-map-renderer\")", - "AetheriaRuntimeObjectsViewportSession _objectsViewport", - "AetheriaRuntimeRenderSplatsViewportSession _renderSplatsViewport", - "CultMeshReactiveDocument", - "AetheriaUnityRuntimeClientProvider.CurrentPlayerSettings(\"unity-map-renderer\")", - ".ReactivePlayerSettingsDocument(\"unity-map-renderer\")", - "AetheriaRuntimePlayerSettingsSession", - ".ObserveObjects(viewport)", - ".ObserveRenderSplats(viewport)", - ".ObservePlayer()", - "_playerSettings?.Dispose()", - "RtsIcon", - "RTS Map Icon", - "RTS Command Icons", - "local Verse state" - }; - var mapRendererRawDocumentHits = forbiddenMapRendererSharedDocumentSymbols - .Where(symbol => mapRenderer.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (mapRendererRawDocumentHits.Length > 0) - { - throw new InvalidOperationException( - "MapRenderer still routes viewport documents through session wrappers instead of managed reactive typed documents: " + - string.Join(", ", mapRendererRawDocumentHits)); - } - - var mainMenu = File.ReadAllText(Path.Combine( - root, - "Assets", - "Scripts", - "UI", - "MainMenu.cs")); - var requiredMainMenuSharedDocumentSymbols = new[] - { - "AetheriaUnityRuntimeClientProvider", - ".RuntimeState(stateBoot, \"unity-main-menu\")", - "ResolveMainMenuSurface(string surfaceId)", - ".MainMenuSurface(surfaceId, CanOpenRuntimeInputScreen(), InGame)", - ".Latest()", - "private void OnDestroy()" - }; - var missingMainMenuSharedDocumentSymbols = requiredMainMenuSharedDocumentSymbols - .Where(symbol => !mainMenu.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (missingMainMenuSharedDocumentSymbols.Length > 0) - { - throw new InvalidOperationException( - "MainMenu should sample daemon-published menu surfaces through a managed typed Aetheria runtime state handle: " + - string.Join(", ", missingMainMenuSharedDocumentSymbols)); - } - - var forbiddenMainMenuSharedDocumentSymbols = new[] - { - "CultMeshReactiveDocument", - "CultMeshReactiveDocument", - "CultMeshReactiveDocument", - ".ReactiveSectorMap(", - ".ReactivePlayerSettingsDocument(", - ".ReactiveVerseHostSettings(", - "AetheriaRuntimeSectorMapSession", - "AetheriaRuntimePlayerSettingsSession", - "AetheriaRuntimeVerseHostSettingsSession", - ".ObserveSectorMap()", - ".ObservePlayer()", - ".ObserveVerseHost()" - }; - var forbiddenMainMenuSharedDocumentHits = forbiddenMainMenuSharedDocumentSymbols - .Where(symbol => mainMenu.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (forbiddenMainMenuSharedDocumentHits.Length > 0) - { - throw new InvalidOperationException( - "MainMenu should sample managed current typed documents instead of owning reactive wrappers: " + - string.Join(", ", forbiddenMainMenuSharedDocumentHits)); - } - - var managedVerseCopySources = new Dictionary - { - ["Packages/org.gamecult.aetheria.state/Runtime/AetheriaClient.cs"] = File.ReadAllText(Path.Combine( - root, - "Packages", - "org.gamecult.aetheria.state", - "Runtime", - "AetheriaClient.cs")), - ["Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeClientTargetSurfaceBuilder.cs"] = File.ReadAllText(Path.Combine( - root, - "Packages", - "org.gamecult.aetheria.state", - "Runtime", - "AetheriaRuntimeClientTargetSurfaceBuilder.cs")), - ["Assets/Scripts/Gameplay/AetheriaDaemonObserver.cs"] = File.ReadAllText(Path.Combine( - root, - "Assets", - "Scripts", - "Gameplay", - "AetheriaDaemonObserver.cs")), - ["Assets/Scripts/UI/InputScreen/InputDisplayLayout.cs"] = File.ReadAllText(Path.Combine( - root, - "Assets", - "Scripts", - "UI", - "InputScreen", - "InputDisplayLayout.cs")), - ["Assets/Scripts/UI/MainMenu.cs"] = mainMenu, - ["Assets/Scripts/UI/Menu/InventoryPanel.cs"] = File.ReadAllText(Path.Combine( - root, - "Assets", - "Scripts", - "UI", - "Menu", - "InventoryPanel.cs")), - ["Assets/Scripts/UI/Menu/SectorRenderer.cs"] = File.ReadAllText(Path.Combine( - root, - "Assets", - "Scripts", - "UI", - "Menu", - "SectorRenderer.cs")) - }; - var staleLocalVerseCopyHits = managedVerseCopySources - .Where(pair => - pair.Value.Contains("local Verse state", StringComparison.Ordinal) || - pair.Value.Contains("local Aetheria Verse state", StringComparison.Ordinal) || - pair.Value.Contains("selected local Verse state file", StringComparison.Ordinal) || - pair.Value.Contains("readable local Verse state file", StringComparison.Ordinal)) - .Select(pair => pair.Key) - .ToArray(); - if (staleLocalVerseCopyHits.Length > 0) - { - throw new InvalidOperationException( - "Managed client/UI surfaces must describe typed Verse targets/documents instead of teaching local-file Verse authority: " + - string.Join(", ", staleLocalVerseCopyHits)); - } - - var tradeMenu = File.ReadAllText(Path.Combine( - root, - "Assets", - "Scripts", - "UI", - "Menu", - "TradeMenu.cs")); - var compactTradeMenu = CompactSource(tradeMenu); - var requiredTradeMenuSharedDocumentSymbols = new[] - { - "AetheriaUnityRuntimeClientProvider.RuntimeState(\"unity-trade\")", - "RuntimeState.Catalog.Latest()", - "RuntimeState.PlayerSettings.Latest()", - "RuntimeState.StationRefit.Latest()", - "RuntimeState.CurrentDocking.Latest()", - "private void OnDestroy()" - }; - var missingTradeMenuSharedDocumentSymbols = requiredTradeMenuSharedDocumentSymbols - .Where(symbol => !compactTradeMenu.Contains(symbol, StringComparison.Ordinal) && - !tradeMenu.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (missingTradeMenuSharedDocumentSymbols.Length > 0) - { - throw new InvalidOperationException( - "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", - "AetheriaRuntimePlayerSettingsSession", - "ResolveClient().State.ObserveCatalog()", - ".ObservePlayer()", - "_catalog?.Dispose()", - "_playerSettings?.Dispose()" - } - .Where(symbol => tradeMenu.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (forbiddenTradeMenuSharedDocumentSymbols.Length > 0) - { - throw new InvalidOperationException( - "TradeMenu still routes catalog/settings through wrapper/session/reactive handles instead of current typed documents: " + - string.Join(", ", forbiddenTradeMenuSharedDocumentSymbols)); - } - - var inventoryMenu = File.ReadAllText(Path.Combine( - root, - "Assets", - "Scripts", - "UI", - "Menu", - "InventoryMenu.cs")); - var compactInventoryMenu = CompactSource(inventoryMenu); - var requiredInventoryMenuSharedDocumentSymbols = new[] - { - "AetheriaUnityRuntimeClientProvider.RuntimeState(\"unity-inventory-menu\")", - "RuntimeState.Catalog.Latest()", - "RuntimeState.PlayerSettings.Latest()", - "RuntimeState.CurrentEntity.Latest()", - "RuntimeState.StationRefit.Latest()", - "private void OnDestroy()" - }; - var missingInventoryMenuSharedDocumentSymbols = requiredInventoryMenuSharedDocumentSymbols - .Where(symbol => !compactInventoryMenu.Contains(symbol, StringComparison.Ordinal) && - !inventoryMenu.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (missingInventoryMenuSharedDocumentSymbols.Length > 0) - { - throw new InvalidOperationException( - "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", - "AetheriaRuntimePlayerSettingsSession", - "ResolveClient().State.ObserveCatalog()", - ".ObservePlayer()", - "_catalog?.Dispose()", - "_playerSettings?.Dispose()" - } - .Where(symbol => inventoryMenu.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (forbiddenInventoryMenuSharedDocumentSymbols.Length > 0) - { - throw new InvalidOperationException( - "InventoryMenu still routes catalog/settings through wrapper/session/reactive handles instead of current typed documents: " + - string.Join(", ", forbiddenInventoryMenuSharedDocumentSymbols)); - } - - var inventoryPanel = File.ReadAllText(Path.Combine( - root, - "Assets", - "Scripts", - "UI", - "Menu", - "InventoryPanel.cs")); - var compactInventoryPanel = CompactSource(inventoryPanel); - var requiredInventoryPanelSharedDocumentSymbols = new[] - { - "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 - .Where(symbol => !compactInventoryPanel.Contains(symbol, StringComparison.Ordinal) && - !inventoryPanel.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (missingInventoryPanelSharedDocumentSymbols.Length > 0) - { - throw new InvalidOperationException( - "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", - "AetheriaRuntimePlayerSettingsSession", - "ResolveClient().State.ObserveCatalog()", - ".ObservePlayer()", - "_catalog?.Dispose()", - "_playerSettings?.Dispose()" - } - .Where(symbol => inventoryPanel.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (forbiddenInventoryPanelSharedDocumentSymbols.Length > 0) - { - throw new InvalidOperationException( - "InventoryPanel still routes catalog/settings through wrapper/session/reactive handles instead of current typed documents: " + - 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", - "Scripts", - "UI", - "Menu", - "SectorRenderer.cs")); - var compactSectorRenderer = CompactSource(sectorRenderer); - var requiredSectorRendererSharedDocumentSymbols = new[] - { - "AetheriaUnityRuntimeClientProvider.RuntimeState(\"unity-sector-renderer\")", - "RuntimeState.CurrentZone.Latest()", - "RuntimeState.ZoneDetailsSurface(zoneIndex).Latest()" - } - .Select(CompactSource) - .Where(symbol => !compactSectorRenderer.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (requiredSectorRendererSharedDocumentSymbols.Length > 0) - { - throw new InvalidOperationException( - "SectorRenderer should sample shared sector UI state through named current typed Aetheria documents: " + - string.Join(", ", requiredSectorRendererSharedDocumentSymbols)); - } - - var forbiddenSectorRendererSharedDocumentSymbols = new[] - { - "CultMeshReactiveDocument<", - ".ReactiveSectorMap(\"unity-sector-renderer\")", - ".ReactiveCurrentZone(\"unity-sector-renderer\")", - ".ReactiveZoneDetails(zoneIndex, \"unity-sector-renderer\")", - ".ReactiveCatalogSnapshot(\"unity-sector-renderer\")", - ".ReactivePlayerSettingsDocument(\"unity-sector-renderer\")", - "AetheriaRuntimeSectorMapSession _sectorMap", - "AetheriaRuntimeCurrentZoneSession _currentZone", - "AetheriaRuntimeZoneDetailsSession _zoneDetails", - "AetheriaRuntimeCatalogSession _catalog", - "AetheriaRuntimePlayerSettingsSession _playerSettings", - ".ObserveSectorMap()", - ".ObserveZone()", - ".ObserveZone(zoneIndex)", - "ResolveClient().State.ObserveCatalog()", - ".ObservePlayer()", - "_sectorMap?.Dispose()", - "_currentZone?.Dispose()", - "_zoneDetails?.Dispose()", - "_catalog?.Dispose()", - "_playerSettings?.Dispose()" - }; - var sectorRendererRawDocumentHits = forbiddenSectorRendererSharedDocumentSymbols - .Where(symbol => sectorRenderer.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (sectorRendererRawDocumentHits.Length > 0) - { - throw new InvalidOperationException( - "SectorRenderer still routes map/current-zone/details through wrapper/session/reactive handles instead of current typed documents: " + - string.Join(", ", sectorRendererRawDocumentHits)); - } - - var zoneRenderer = File.ReadAllText(Path.Combine( - root, - "Assets", - "Scripts", - "Zone Display", - "ZoneRenderer.cs")); - var requiredZoneRendererSharedDocumentSymbols = new[] - { - "AetheriaUnityRuntimeClientProvider.RuntimeState(\"unity-zone-renderer\")", - "_runtimeState.Catalog.Latest()", - "AetheriaUnityGameViewportDocuments _zonePresentationDocuments", - ".ZonePresentationDocuments(", - "_zonePresentationDocuments?.Dispose()", - "_zonePresentationDocuments?.CurrentContacts", - "private void OnDestroy()" - }; - var missingZoneRendererSharedDocumentSymbols = requiredZoneRendererSharedDocumentSymbols - .Where(symbol => !zoneRenderer.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (missingZoneRendererSharedDocumentSymbols.Length > 0) - { - throw new InvalidOperationException( - "ZoneRenderer should sample catalog through current typed state while keeping contact state on managed reactive Aetheria documents: " + - string.Join(", ", missingZoneRendererSharedDocumentSymbols)); - } - - var forbiddenZoneRendererSharedDocumentSymbols = new[] - { - "CultMeshReactiveDocument _zoneContacts", - ".ReactiveZoneContacts(\"unity-zone-renderer\")", - "_zoneContacts?.Current", - "_zoneContacts?.Dispose()", - "AetheriaRuntimeZoneContactsSession _zoneContacts", - "ResolveClient().State.ObserveZoneContacts()", - "CultMeshReactiveDocument", - "AetheriaUnityRuntimeClientProvider.CurrentCatalog(\"unity-zone-renderer\")", - ".ReactiveCatalogSnapshot(\"unity-zone-renderer\")", - "AetheriaRuntimeCatalogSession _catalog", - "ResolveClient().State.ObserveCatalog()", - "_catalog?.Dispose()" - }; - var zoneRendererRawDocumentHits = forbiddenZoneRendererSharedDocumentSymbols - .Where(symbol => zoneRenderer.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (zoneRendererRawDocumentHits.Length > 0) - { - throw new InvalidOperationException( - "ZoneRenderer still routes contact state through session wrappers instead of managed reactive typed documents: " + - string.Join(", ", zoneRendererRawDocumentHits)); - } - - Console.WriteLine("Shared document accessors: Unity shared runtime reads use named managed typed documents"); -} - -static void RequireUnityViewportAndMapReadsUseManagedAccessors(string root) -{ - var clientStatePath = Path.Combine( - root, - "Packages", - "org.gamecult.aetheria.state", - "Runtime", - "AetheriaClientState.cs"); - if (!File.Exists(clientStatePath)) - throw new InvalidOperationException("Cannot verify Unity viewport/map managed accessors; AetheriaClientState.cs is missing."); - - var clientState = File.ReadAllText(clientStatePath); - var requiredClientSymbols = new[] - { - "public CultMeshDocumentHandle ObjectsViewport(", - "public CultMeshDocumentHandle RenderSplatsViewport(", - "public CultMeshDocumentHandle ZoneDetails(" - }; - var missingClientSymbols = requiredClientSymbols - .Where(symbol => !clientState.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (missingClientSymbols.Length > 0) - { - throw new InvalidOperationException( - "AetheriaClientState must expose named parameterized document handles for map, viewport, indexed, contacts, and current-entity documents: " + - 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"), - Path.Combine(root, "Assets", "Scripts", "Gameplay", "AetheriaUnityRenderSplatViewportSource.cs"), - Path.Combine(root, "Assets", "Scripts", "UI", "HUD", "SchematicDisplay.cs"), - Path.Combine(root, "Assets", "Scripts", "UI", "MainMenu.cs"), - Path.Combine(root, "Assets", "Scripts", "UI", "Menu", "MapRenderer.cs"), - Path.Combine(root, "Assets", "Scripts", "UI", "Menu", "SectorMap.cs"), - Path.Combine(root, "Assets", "Scripts", "UI", "Menu", "SectorRenderer.cs"), - Path.Combine(root, "Assets", "Scripts", "Zone Display", "ZoneRenderer.cs") - }; - - var missingPaths = unityPaths.Where(path => !File.Exists(path)).ToArray(); - if (missingPaths.Length > 0) - { - throw new InvalidOperationException( - "Cannot verify Unity viewport/map managed accessors; missing sources: " + - string.Join(", ", missingPaths.Select(path => Path.GetRelativePath(root, path)))); - } - - var forbiddenCompactedSymbols = new[] - { - ".State.SectorMap.LatestAsync()", - ".State.ZoneContacts.LatestAsync()", - ".State.Current.Entity.LatestAsync()", - ".State.Viewports.Objects(", - ".State.Viewports.RenderSplats(", - ".Details.Zone(zoneIndex).LatestAsync()" - } - .Select(CompactSource) - .ToArray(); - var offenders = unityPaths - .Select(path => new - { - Path = path, - Compact = CompactSource(File.ReadAllText(path)) - }) - .Where(entry => forbiddenCompactedSymbols.Any(symbol => entry.Compact.Contains(symbol, StringComparison.Ordinal))) - .Select(entry => Path.GetRelativePath(root, entry.Path)) - .ToArray(); - - if (offenders.Length > 0) - { - throw new InvalidOperationException( - "Unity map, viewport, contact, and current-entity reads still walk CultMesh document handles instead of managed typed document access: " + - string.Join(", ", offenders)); - } - - var renderSplatViewportSource = File.ReadAllText(Path.Combine( - root, - "Assets", - "Scripts", - "Gameplay", - "AetheriaUnityRenderSplatViewportSource.cs")); - var requiredRenderSplatViewportSourceSymbols = new[] - { - "AetheriaUnityGameViewportDocuments _viewportDocuments", - "_viewportDocuments?.CurrentRenderSplatsViewport", - "ClearViewportDocument()" - }; - var missingRenderSplatViewportSourceSymbols = requiredRenderSplatViewportSourceSymbols - .Where(symbol => !renderSplatViewportSource.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (!CompactSource(renderSplatViewportSource).Contains( - ".RenderSplatViewportDocuments(viewport,\"unity-render-splat-viewport\")", - StringComparison.Ordinal)) - { - missingRenderSplatViewportSourceSymbols = missingRenderSplatViewportSourceSymbols - .Concat(new[] { ".RenderSplatViewportDocuments(viewport,\"unity-render-splat-viewport\")" }) - .ToArray(); - } - if (missingRenderSplatViewportSourceSymbols.Length > 0 || - renderSplatViewportSource.Contains(".LatestRenderSplats(", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "AetheriaUnityRenderSplatViewportSource must keep a managed reactive render-splats viewport document instead of polling latest snapshots: " + - string.Join(", ", missingRenderSplatViewportSourceSymbols)); - } - - if (renderSplatViewportSource.Contains("CultMeshReactiveDocument _renderSplatsViewport", StringComparison.Ordinal) || - renderSplatViewportSource.Contains(".ReactiveRenderSplatsViewport(", StringComparison.Ordinal) || - renderSplatViewportSource.Contains("AetheriaRuntimeRenderSplatsViewportSession _renderSplatsViewport", StringComparison.Ordinal) || - renderSplatViewportSource.Contains(".ObserveRenderSplats(viewport)", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "AetheriaUnityRenderSplatViewportSource still routes render-splats viewports through raw/session wrapper state instead of a managed viewport document."); - } - - if (renderSplatViewportSource.Contains("_fallbackRenderSplatsViewport", StringComparison.Ordinal) || - renderSplatViewportSource.Contains("EmptyRenderSplatsViewport", StringComparison.Ordinal) || - renderSplatViewportSource.Contains("new EveFieldsSplatsDocument", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "AetheriaUnityRenderSplatViewportSource must not fabricate renderer-local render splat documents; daemon/CultMesh viewport documents own render fields."); - } - - var zoneRenderer = File.ReadAllText(Path.Combine( - root, - "Assets", - "Scripts", - "Zone Display", - "ZoneRenderer.cs")); - var requiredZoneRendererViewportSymbols = new[] - { - "AetheriaUnityGameViewportDocuments _zonePresentationDocuments", - "_zonePresentationDocuments.CurrentObjectsViewport", - "_zonePresentationDocuments?.Dispose()" - }; - var missingZoneRendererViewportSymbols = requiredZoneRendererViewportSymbols - .Where(symbol => !zoneRenderer.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (!CompactSource(zoneRenderer).Contains( - ".ZonePresentationDocuments(viewportBounds,\"unity-zone-renderer\")", - StringComparison.Ordinal)) - { - missingZoneRendererViewportSymbols = missingZoneRendererViewportSymbols - .Concat(new[] { ".ZonePresentationDocuments(viewportBounds,\"unity-zone-renderer\")" }) - .ToArray(); - } - if (missingZoneRendererViewportSymbols.Length > 0 || - zoneRenderer.Contains(".LatestObjects(", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "ZoneRenderer fallback presentation queries must keep a managed reactive objects viewport document instead of polling latest snapshots: " + - string.Join(", ", missingZoneRendererViewportSymbols)); - } - - if (zoneRenderer.Contains("CultMeshReactiveDocument _objectsViewport", StringComparison.Ordinal) || - zoneRenderer.Contains(".ReactiveObjectsViewport(", StringComparison.Ordinal) || - zoneRenderer.Contains("AetheriaRuntimeObjectsViewportSession _objectsViewport", StringComparison.Ordinal) || - zoneRenderer.Contains(".ObserveObjects(viewportBounds)", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "ZoneRenderer still routes fallback objects viewports through raw/session wrapper state instead of a managed viewport document."); - } - - Console.WriteLine("Viewport and map document accessors: Unity reads fixed map/contact state and parameterized viewport state through named managed typed documents"); -} - -static void RequireAetheriaManagedStateAccessorsCoverDomainDocuments(string root) -{ - var clientStatePath = Path.Combine( - root, - "Packages", - "org.gamecult.aetheria.state", - "Runtime", - "AetheriaClientState.cs"); - var observedDaemonStatePath = Path.Combine( - root, - "Packages", - "org.gamecult.aetheria.state", - "Runtime", - "AetheriaRuntimeDaemonRenderView.cs"); - var daemonRuntimeDocumentTestsPath = Path.Combine( - root, - "Assets", - "Scripts", - "Tests", - "DaemonRuntimeDocumentTests.cs"); - var starbridgePlayerSeatTestsPath = Path.Combine( - root, - "Assets", - "Scripts", - "Tests", - "StarbridgePlayerSeatDocumentTests.cs"); - - var requiredPaths = new[] - { - clientStatePath, - observedDaemonStatePath, - daemonRuntimeDocumentTestsPath, - starbridgePlayerSeatTestsPath - }; - var missingPaths = requiredPaths.Where(path => !File.Exists(path)).ToArray(); - if (missingPaths.Length > 0) - { - throw new InvalidOperationException( - "Cannot verify Aetheria managed state accessor coverage; missing sources: " + - string.Join(", ", missingPaths.Select(path => Path.GetRelativePath(root, path)))); - } - - var clientState = File.ReadAllText(clientStatePath); - var daemonRuntimeDocumentTests = File.ReadAllText(daemonRuntimeDocumentTestsPath); - var topLevelClientState = clientState.Split( - "public sealed class AetheriaClientDaemonState", - StringSplitOptions.None)[0]; - var requiredClientSymbols = new[] - { - "public enum AetheriaClientEveSurface", - "public CultMeshDocumentHandle ProviderAdvertisement { get; }", - "public CultMeshDocumentHandle Health { get; }", - "public CultMeshDocumentHandle CommandBoundary { get; }", - "public CultMeshDocumentHandle AuthorityPolicy { get; }", - "public CultMeshDocumentHandle DaemonFrame { get; }", - "public CultMeshDocumentHandle DaemonSoaView { get; }", - "public CultMeshDocumentHandle ZoneRender { get; }", - "public CultMeshDocumentHandle LoadoutTemplates { get; }", - "public CultMeshDocumentHandle SectorMap { get; }", - "public CultMeshDocumentHandle CurrentZone { get; }", - "public CultMeshDocumentHandle ZoneContacts { get; }", - "public CultMeshDocumentHandle VerseHostSettings { get; }", - "public CultMeshDocumentHandle CurrentDocking { get; }", - "public CultMeshDocumentHandle CurrentEntity { get; }", - "public CultMeshDocumentHandle StationRefit { get; }", - "public CultMeshDocumentHandle Catalog { get; }", - "public CultMeshDocumentHandle PlayerSettings { get; }", - "public static bool TryResolveEveSurface(", - "public CultMeshDocumentHandle? EveSurfaceDocument(", - "public CultMeshDocumentHandle GameViewport(", - "public CultMeshDocumentHandle ObjectsViewport(", - "public CultMeshDocumentHandle GravityViewport(", - "public CultMeshDocumentHandle RenderSplatsViewport(", - "public CultMeshDocumentHandle ZoneDetails(", - "public CultMeshDocumentHandle ZoneDetailsSurface(", - "public CultMeshDocumentHandle InventoryPanelSurface(", - "public CultMeshDocumentHandle InventoryDropdownSurface(", - "public CultMeshDocumentHandle SelectedObject(int entityIndex)", - "public CultMeshDocumentHandle Inventory(", - "public CultMeshDocumentHandle StarbridgeScenario { get; }", - "public CultMeshDocumentHandle StarbridgeSession { get; }", - "public CultMeshDocumentHandle StarbridgeSummary { get; }", - "public CultMeshDocumentHandle StarbridgePlayerSeat(" - }; - var missingClientSymbols = requiredClientSymbols - .Where(symbol => !clientState.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (missingClientSymbols.Length > 0) - { - throw new InvalidOperationException( - "AetheriaClientState must expose managed document handles for remaining domain documents: " + - string.Join(", ", missingClientSymbols)); - } - if (clientState.Contains("SelectedObject(int zoneIndex)", StringComparison.Ordinal) || - clientState.Contains("_selectedObject(zoneIndex)", StringComparison.Ordinal) || - clientState.Contains("Aetheria zone index must be non-negative.\";\r\n\r\n return _selectedObject", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Selected object documents are entity-indexed; AetheriaClientState must not expose a zone-indexed selected-object handle."); - } - - 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 ReactiveGameViewport(", - "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) || - clientState.Contains("public CultMeshDocumentHandle Document", StringComparison.Ordinal) || - clientState.Contains("public CultMeshReactiveDocument Reactive", StringComparison.Ordinal) || - clientState.Contains("TryGetDocument", StringComparison.Ordinal) || - clientState.Contains("CultMeshDocumentCatalog", StringComparison.Ordinal) || - clientState.Contains("public CultMeshDocumentHandle ProviderAdvertisementDocument(", StringComparison.Ordinal) || - clientState.Contains("public CultMeshDocumentHandle HealthDocument(", StringComparison.Ordinal) || - clientState.Contains("public CultMeshDocumentHandle CommandBoundaryDocument(", StringComparison.Ordinal) || - clientState.Contains("public CultMeshDocumentHandle AuthorityPolicyDocument(", StringComparison.Ordinal) || - clientState.Contains("public CultMeshDocumentHandle DaemonFrameDocument(", StringComparison.Ordinal) || - clientState.Contains("public CultMeshDocumentHandle DaemonSoaViewDocument(", StringComparison.Ordinal) || - clientState.Contains("public CultMeshDocumentHandle CatalogSnapshot(", StringComparison.Ordinal) || - clientState.Contains("public CultMeshDocumentHandle PlayerSettingsDocument(", StringComparison.Ordinal) || - clientState.Contains("public CultMeshDocumentHandle CurrentEntityDocument(", StringComparison.Ordinal) || - clientState.Contains("public CultMeshDocumentHandle CurrentDocking(", StringComparison.Ordinal) || - clientState.Contains("public CultMeshDocumentHandle StationRefitDocument(", StringComparison.Ordinal) || - clientState.Contains("public CultMeshDocumentHandle StarbridgeSummaryDocument(", StringComparison.Ordinal) || - clientState.Contains("public Task> ReactiveAsync", StringComparison.Ordinal) || - clientState.Contains("public Observable Watch", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "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 AetheriaRuntimeGameViewportDocument CurrentGameViewport(", - "public AetheriaRuntimeObjectsViewportDocument CurrentObjectsViewport(", - "public AetheriaRuntimeGravityViewportDocument CurrentGravityViewport(", - "public EveFieldsSplatsDocument 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(", - "LatestCatalogAsync(", - "ReactiveCatalog(", - "ReactiveCatalogAsync(", - "LatestLoadoutTemplates(", - "LatestLoadoutTemplatesAsync(", - "ReactiveLoadoutTemplatesAsync(", - "LatestSectorMap(", - "LatestSectorMapAsync(", - "ReactiveSectorMapAsync(", - "LatestZoneContacts(", - "LatestZoneContactsAsync(", - "ReactiveZoneContactsAsync(", - "LatestStationRefit(", - "LatestStationRefitAsync(", - "ReactiveStationRefitAsync(", - "LatestDaemonFrame(", - "LatestDaemonFrameAsync(", - "ReactiveDaemonFrameAsync(", - "LatestDaemonSoaView(", - "LatestDaemonSoaViewAsync(", - "ReactiveDaemonSoaViewAsync(", - "LatestZoneRender(", - "LatestZoneRenderAsync(", - "ReactiveZoneRenderAsync(", - "LatestPlayer(", - "LatestPlayerAsync(", - "ReactivePlayer(", - "ReactivePlayerAsync(", - "LatestVerseHost(", - "LatestVerseHostAsync(", - "ReactiveVerseHost(", - "ReactiveVerseHostAsync(", - "LatestProviderAdvertisement(", - "LatestProviderAdvertisementAsync(", - "LatestHealth(", - "LatestHealthAsync(", - "LatestCommandBoundary(", - "LatestCommandBoundaryAsync(", - "LatestAuthorityPolicy(", - "LatestAuthorityPolicyAsync(", - "LatestFrameDocument(", - "LatestFrameDocumentAsync(", - "LatestSoaViewDocument(", - "LatestSoaViewDocumentAsync(", - "LatestScenario(", - "LatestScenarioAsync(", - "ReactiveScenario(", - "ReactiveScenarioAsync(", - "LatestSession(", - "LatestSessionAsync(", - "ReactiveSession(", - "ReactiveSessionAsync(", - "LatestSummary(", - "LatestSummaryAsync(", - "ReactiveSummary(", - "ReactiveSummaryAsync(", - "LatestGameSurface(", - "LatestGameSurfaceAsync(", - "ReactiveGameSurface(", - "ReactiveGameSurfaceAsync(", - "LatestGameTuiSurface(", - "LatestGameTuiSurfaceAsync(", - "ReactiveGameTuiSurface(", - "ReactiveGameTuiSurfaceAsync(", - "LatestEditorSurface(", - "LatestEditorSurfaceAsync(", - "ReactiveEditorSurface(", - "ReactiveEditorSurfaceAsync(", - "LatestEditorTuiSurface(", - "LatestEditorTuiSurfaceAsync(", - "ReactiveEditorTuiSurface(", - "ReactiveEditorTuiSurfaceAsync(" - }; - var forbiddenObservedDaemonConvenienceWrappers = new[] - { - "LatestObservedDaemonAsync(", - "LatestObservedDaemon()", - "TryReadDaemonSoaViewAsync(" - }; - var survivingObservedDaemonConvenienceWrappers = forbiddenObservedDaemonConvenienceWrappers - .Where(symbol => clientState.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (survivingObservedDaemonConvenienceWrappers.Length > 0) - { - throw new InvalidOperationException( - "AetheriaClientState still exposes one-shot observed-daemon aggregate wrappers; use caller-owned managed typed documents: " + - string.Join(", ", survivingObservedDaemonConvenienceWrappers)); - } - var survivingFixedReactiveWrappers = forbiddenFixedReactiveWrappers - .Where(symbol => clientState.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (survivingFixedReactiveWrappers.Length > 0) - { - throw new InvalidOperationException( - "AetheriaClientState must not reintroduce latest/async fixed-document wrappers; use named reactive typed documents for ordinary gameplay state: " + - string.Join(", ", survivingFixedReactiveWrappers)); - } - - var forbiddenClientStateWrappers = new[] - { - "public AetheriaClientDaemonState Daemon { get; }", - "public AetheriaClientSettingsState Settings { get; }", - "public AetheriaClientCurrentState Current { get; }", - "public AetheriaClientViewportState Viewports { get; }", - "public AetheriaClientDetailState Details { get; }", - "public AetheriaClientStarbridgeState Starbridge { get; }", - "public sealed class AetheriaClientDaemonState", - "public sealed class AetheriaClientSettingsState", - "public sealed class AetheriaClientCurrentState", - "public sealed class AetheriaClientViewportState", - "public sealed class AetheriaClientDetailState", - "public sealed class AetheriaClientStarbridgeState" - }; - var survivingClientStateWrappers = forbiddenClientStateWrappers - .Where(symbol => clientState.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (survivingClientStateWrappers.Length > 0) - { - throw new InvalidOperationException( - "AetheriaClientState still exposes nested wrapper state; use flat named typed handles for ordinary gameplay state: " + - string.Join(", ", survivingClientStateWrappers)); - } - - var forbiddenViewportConvenienceWrappers = new[] - { - "LatestMap(", - "LatestMapAsync(", - "ReactiveMap(", - "ReactiveMapAsync(", - "LatestObjects(", - "LatestObjectsAsync(", - "ReactiveObjectsAsync(", - "LatestGravity(", - "LatestGravityAsync(", - "ReactiveGravityAsync(", - "LatestRenderSplats(", - "LatestRenderSplatsAsync(", - "ReactiveRenderSplatsAsync(" - }; - var survivingViewportConvenienceWrappers = forbiddenViewportConvenienceWrappers - .Where(symbol => clientState.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (survivingViewportConvenienceWrappers.Length > 0) - { - throw new InvalidOperationException( - "AetheriaClientState must avoid latest/async viewport wrappers; callers should use named reactive typed viewport documents: " + - string.Join(", ", survivingViewportConvenienceWrappers)); - } - - var forbiddenDetailConvenienceWrappers = new[] - { - "LatestZone(", - "LatestZoneAsync(", - "ReactiveZone(", - "ReactiveZoneAsync(", - "LatestSelectedObject(", - "LatestSelectedObjectAsync(", - "ReactiveSelectedObjectAsync(", - "LatestInventory(", - "LatestInventoryAsync(", - "ReactiveInventoryAsync(" - }; - var survivingDetailConvenienceWrappers = forbiddenDetailConvenienceWrappers - .Where(symbol => clientState.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (survivingDetailConvenienceWrappers.Length > 0) - { - throw new InvalidOperationException( - "AetheriaClientState must avoid latest/async scoped wrappers; callers should use named reactive typed scoped documents: " + - string.Join(", ", survivingDetailConvenienceWrappers)); - } - - var forbiddenCurrentConvenienceWrappers = new[] - { - "LatestZone(", - "LatestZoneAsync(", - "ReactiveZone(", - "ReactiveZoneAsync(", - "LatestEntity(", - "LatestEntityAsync(", - "ReactiveEntity(", - "ReactiveEntityAsync(", - "LatestDocking(", - "LatestDockingAsync(", - "ReactiveDocking(", - "ReactiveDockingAsync(" - }; - var survivingCurrentConvenienceWrappers = forbiddenCurrentConvenienceWrappers - .Where(symbol => clientState.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (survivingCurrentConvenienceWrappers.Length > 0) - { - throw new InvalidOperationException( - "AetheriaClientState must use top-level generic document access instead of named current-state wrappers: " + - string.Join(", ", survivingCurrentConvenienceWrappers)); - } - - var compactClientState = CompactSource(clientState); - var requiredStarbridgeParameterizedSymbols = new[] - { - "publicCultMeshDocumentHandleStarbridgePlayerSeat(stringseatId)" - }; - var missingStarbridgeParameterizedSymbols = requiredStarbridgeParameterizedSymbols - .Where(symbol => !compactClientState.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (missingStarbridgeParameterizedSymbols.Length > 0) - { - throw new InvalidOperationException( - "AetheriaClientState must expose named parameterized document handles for player-seat state: " + - string.Join(", ", missingStarbridgeParameterizedSymbols)); - } - - var forbiddenStarbridgeConvenienceWrappers = new[] - { - "LatestPlayerSeat(", - "LatestPlayerSeatAsync(", - "ReactivePlayerSeatAsync(" - }; - var survivingStarbridgeConvenienceWrappers = forbiddenStarbridgeConvenienceWrappers - .Where(symbol => clientState.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (survivingStarbridgeConvenienceWrappers.Length > 0) - { - throw new InvalidOperationException( - "AetheriaClientState must avoid latest/async player-seat wrappers; callers should use named reactive typed player-seat documents: " + - string.Join(", ", survivingStarbridgeConvenienceWrappers)); - } - - var observedDaemonState = File.ReadAllText(observedDaemonStatePath); - if (!observedDaemonState.Contains("public static bool TryCreateCurrent(", StringComparison.Ordinal) || - !observedDaemonState.Contains("new AetheriaRuntimeDaemonRenderView(currentFrame, currentSoaView, currentZoneRender)", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Observed daemon state must compose current values from managed typed frame, SoA, and zone-render documents."); - } - - if (File.Exists(Path.Combine( - root, - "Packages", - "org.gamecult.aetheria.state", - "Runtime", - "AetheriaRuntimeObservedDockingState.cs"))) - { - throw new InvalidOperationException( - "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.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( - "Daemon runtime document tests must teach direct docking samples from caller-owned managed reactive typed documents."); - } - - if (observedDaemonState.Contains("state.LatestFrame.ReactiveAsync", StringComparison.Ordinal) || - observedDaemonState.Contains("state.LatestSoaView.ReactiveAsync", StringComparison.Ordinal) || - observedDaemonState.Contains("AetheriaRuntimeReactiveObservedDaemonState", StringComparison.Ordinal) || - observedDaemonState.Contains("ReadAsync(", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Observed daemon state must not hide typed document access behind aggregate reactive compatibility wrappers."); - } - - if (observedDaemonState.Contains("state.LatestAsync()", StringComparison.Ordinal) || - observedDaemonState.Contains("state.LatestDaemonSoaViewAsync()", StringComparison.Ordinal) || - observedDaemonState.Contains("state.ReactiveObservedDaemonAsync()", StringComparison.Ordinal) || - observedDaemonState.Contains("TryReadLatestSoaViewAsync", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Observed daemon state ReadAsync still performs one-shot daemon reads instead of sampling the managed reactive observed document."); - } - - var checkedSources = new Dictionary - { - ["DaemonRuntimeDocumentTests.cs"] = File.ReadAllText(daemonRuntimeDocumentTestsPath), - ["StarbridgePlayerSeatDocumentTests.cs"] = File.ReadAllText(starbridgePlayerSeatTestsPath) - }; - var forbiddenCompactedSymbols = new[] - { - ".State.Catalog.LatestAsync()", - ".State.Settings.Player.LatestAsync()", - ".State.Settings.VerseHost.LatestAsync()", - ".State.Current.Entity.LatestAsync()", - ".State.Viewports.Objects(", - ".State.Details.Zone(", - ".State.Details.Inventory(", - ".State.Starbridge.PlayerSeat(", - ".State.DockingState.Latest(", - ".State.DockingState.Reactive(", - "AetheriaRuntimeDaemonRenderView.ReadAsync(", - ".State.Daemon.LatestFrame.LatestAsync()", - ".State.Daemon.LatestSoaView.LatestAsync()", - ".State.Daemon.AuthorityPolicy.LatestAsync()", - ".State.Daemon.GameSurface.LatestAsync()", - ".State.Daemon.GameTuiSurface.LatestAsync()", - ".State.Daemon.EditorSurface.LatestAsync()", - ".State.Daemon.EditorTuiSurface.LatestAsync()", - "client.State.Daemon.LatestGameSurface()", - "client.State.Daemon.LatestGameTuiSurface()", - "client.State.Daemon.LatestEditorSurface()", - "client.State.Daemon.LatestEditorTuiSurface()", - "client.State.LatestGameSurface()", - "client.State.LatestGameTuiSurface()", - "client.State.LatestEditorSurface()", - "client.State.LatestEditorTuiSurface()", - "client.State.ReactiveGameSurface()", - "client.State.ReactiveGameTuiSurface()", - "client.State.ReactiveEditorSurface()", - "client.State.ReactiveEditorTuiSurface()", - "client.State.ObserveCatalog()", - "client.State.ObserveDaemonFrame()", - "client.State.ObserveLoadoutTemplates()", - "client.State.ObserveSectorMap()", - "client.State.Settings.ObservePlayer()", - "client.State.ObserveZoneContacts()", - "client.State.Details.ObserveInventory(0)", - "client.State.Viewports.ObserveObjects(viewport)", - "client.State.Viewports.ObserveRenderSplats(viewport)" - } - .Select(CompactSource) - .ToArray(); - var offenders = checkedSources - .Select(entry => new - { - entry.Key, - Compact = CompactSource(entry.Value) - }) - .Where(entry => forbiddenCompactedSymbols.Any(symbol => entry.Compact.Contains(symbol, StringComparison.Ordinal))) - .Select(entry => entry.Key) - .ToArray(); - - if (offenders.Length > 0) - { - throw new InvalidOperationException( - "Managed state examples still teach raw CultMesh handle walks for ordinary latest reads: " + - string.Join(", ", offenders)); - } - - if (!clientState.Contains("StarbridgeSummary", StringComparison.Ordinal) || - !checkedSources["DaemonRuntimeDocumentTests.cs"].Contains( - "client.State.StarbridgeSummary", - StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Starbridge summary must be registered in managed Aetheria typed document access."); - } - - if (!checkedSources["StarbridgePlayerSeatDocumentTests.cs"].Contains( - ".StarbridgePlayerSeat(seat.SeatId)", - StringComparison.Ordinal) || - !checkedSources["StarbridgePlayerSeatDocumentTests.cs"].Contains( - ".StarbridgePlayerSeat(seat.SeatId).Reactive()", - StringComparison.Ordinal) || - checkedSources["StarbridgePlayerSeatDocumentTests.cs"].Contains( - ".ReactiveStarbridgePlayerSeat(seat.SeatId)", - StringComparison.Ordinal) || - checkedSources["StarbridgePlayerSeatDocumentTests.cs"].Contains( - ".Latest(seat.SeatId)", - StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Starbridge player-seat examples must use named parameterized managed document access instead of raw handle walks."); - } - - Console.WriteLine("Domain document accessors: managed Aetheria state exposes named fixed-document reads plus parameterized viewport/detail/Starbridge/Eve-surface access"); -} - -static void RequireVerseHostSettingsAuthority(string root) -{ - var settingsPath = Path.Combine(root, "Aetheria.State", "Documents", "AetheriaVerseHostSettings.cs"); - var normalizerPath = Path.Combine(root, "Aetheria.State", "AetheriaVerseHostSettingsNormalizer.cs"); - var verseCatalogDocumentsPath = Path.Combine(root, "Aetheria.State", "AetheriaVerseCatalogDocuments.cs"); - var deletedVerseCatalogProjectorPath = Path.Combine(root, "Aetheria.State", "AetheriaVerseCatalogProjector.cs"); - var verseDiscoveryHostPath = Path.Combine(root, "Aetheria.State", "AetheriaVerseDiscoveryHost.cs"); - var registryPath = Path.Combine(root, "Aetheria.State", "AetheriaDocumentRegistry.cs"); - var nodePath = Path.Combine(root, "Aetheria.State", "AetheriaStateNode.cs"); - var providerPath = Path.Combine(root, "Aetheria.State", "AetheriaEveSurfaceDocuments.cs"); - var operationsPath = Path.Combine(root, "Aetheria.State", "AetheriaEveSurfaceDocuments.cs"); - var daemonHostPath = Path.Combine(root, "Aetheria.State.Daemon", "Program.cs"); - - var requiredFiles = new[] - { - settingsPath, - normalizerPath, - verseCatalogDocumentsPath, - verseDiscoveryHostPath, - registryPath, - nodePath, - providerPath, - operationsPath, - daemonHostPath - }; - - var missingFiles = requiredFiles - .Where(path => !File.Exists(path)) - .Select(path => Path.GetRelativePath(root, path)) - .ToArray(); - if (missingFiles.Length > 0) - { - throw new InvalidOperationException( - "Verse host authority cut is incomplete; missing files: " + string.Join(", ", missingFiles)); - } - - var settings = File.ReadAllText(settingsPath); - var normalizer = File.ReadAllText(normalizerPath); - if (File.Exists(deletedVerseCatalogProjectorPath)) - { - throw new InvalidOperationException( - "AetheriaVerseCatalogProjector should stay deleted; Verse discovery publishes typed catalog documents, not projector chaff."); - } - - var verseCatalogDocuments = File.ReadAllText(verseCatalogDocumentsPath); - var verseDiscoveryHost = File.ReadAllText(verseDiscoveryHostPath); - var registry = File.ReadAllText(registryPath); - var node = File.ReadAllText(nodePath); - var provider = File.ReadAllText(providerPath); - var operations = File.ReadAllText(operationsPath); - var daemonHost = File.ReadAllText(daemonHostPath); - - var requiredSettingsSymbols = new[] - { - "[CultDocument(\"aetheria.verse_host_settings\", \"aetheria.verse_host_settings.v1\")]", - "public sealed class AetheriaVerseHostSettings", - "public string VerseId", - "public string CultMeshAddress", - "public string Visibility" - }; - var missingSettingsSymbols = requiredSettingsSymbols - .Where(symbol => !settings.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (missingSettingsSymbols.Length > 0) - { - throw new InvalidOperationException( - "Typed verse-host settings document is missing required authority fields: " + - string.Join(", ", missingSettingsSymbols)); - } - - if (!normalizer.Contains("public static AetheriaVerseHostSettings Normalize", StringComparison.Ordinal) || - !normalizer.Contains("public static bool Equivalent", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Verse-host settings normalizer is missing normalize/equivalence ownership."); - } - - var requiredVerseCatalogDocumentSymbols = new[] - { - "public static class AetheriaVerseCatalogDocuments", - "CultMeshVerseDescriptor", - "BuildDiscoveryEndpoint", - "CultMeshVerseDescriptor.ComputeRulesHash", - "cultnet://" - }; - var missingVerseCatalogDocumentSymbols = requiredVerseCatalogDocumentSymbols - .Where(symbol => !verseCatalogDocuments.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (missingVerseCatalogDocumentSymbols.Length > 0) - { - throw new InvalidOperationException( - "Verse catalog documents no longer derive public CultMesh discovery from typed host settings: " + - string.Join(", ", missingVerseCatalogDocumentSymbols)); - } - - var requiredVerseDiscoveryHostSymbols = new[] - { - "public sealed class AetheriaVerseDiscoveryHost", - "AetheriaVerseCatalogDocuments.Build(normalized)", - "CultMesh.CreateVerseCatalog()", - "CultMesh.ServeVerseCatalog(_node.MeshNode, _catalog)", - "normalized.Visibility" - }; - var missingVerseDiscoveryHostSymbols = requiredVerseDiscoveryHostSymbols - .Where(symbol => !verseDiscoveryHost.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (missingVerseDiscoveryHostSymbols.Length > 0) - { - throw new InvalidOperationException( - "Verse discovery host no longer gates served Verse catalogs through the typed host settings: " + - string.Join(", ", missingVerseDiscoveryHostSymbols)); - } - - if (!registry.Contains("CultMesh.CreateCultNetDocumentRegistry(DocumentTypes, registry)", StringComparison.Ordinal) || - !registry.Contains("typeof(AetheriaVerseHostSettings)", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Aetheria document registry does not register typed verse-host settings."); - } - - if (!node.Contains("public CultMeshMutableStatePointer MutableDocument(CultRecordKey key)", StringComparison.Ordinal) || - !node.Contains("public static CultRecordKey VerseHostSettingsKey", StringComparison.Ordinal) || - !node.Contains("public static CultRecordKey PlayerSettingsKey", StringComparison.Ordinal) || - !node.Contains("public static CultRecordKey EveCommandAcceptanceStatusKey", StringComparison.Ordinal) || - !node.Contains("public CultMeshNode MeshNode => _node;", StringComparison.Ordinal) || - !node.Contains("global:aetheria.verse_host_settings.v1", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Aetheria state node does not expose typed verse-host/settings managed state pointers."); - } - - if (node.Contains("public CultMeshMutableStatePointer VerseHostSettings()", StringComparison.Ordinal) || - node.Contains("public CultMeshMutableStatePointer PlayerSettings()", StringComparison.Ordinal) || - node.Contains("public CultMeshMutableStatePointer EveCommandAcceptanceStatus()", StringComparison.Ordinal) || - node.Contains("public CultMeshMutableStatePointer CatalogSurface()", StringComparison.Ordinal) || - node.Contains("public CultMeshMutableStatePointer OperationsSurface()", StringComparison.Ordinal) || - node.Contains("public CultMeshMutableStatePointer PlayerSettingsSurface()", StringComparison.Ordinal) || - node.Contains("public CultMeshMutableStatePointer ProviderAdvertisementSurface()", StringComparison.Ordinal) || - node.Contains("public CultMeshMutableStatePointer RuntimeSession(", StringComparison.Ordinal) || - node.Contains("public CultMeshMutableStatePointer TradeValuePolicy()", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Aetheria state node still exposes named document helpers instead of generic mutable typed document access."); - } - - var forbiddenHardcodedProviderSymbols = new[] - { - "VerseId = \"aetheria.local\"", - "RootVerse = \"asgard\"", - "CanonicalService = \"asgard.aetheria\"", - "LocatedService = \"asgard.local.aetheria\"", - "CultMeshAddress = \"asgard.local.aetheria/eve\"" - }; - var survivingHardcodedProviderSymbols = forbiddenHardcodedProviderSymbols - .Where(symbol => provider.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (survivingHardcodedProviderSymbols.Length > 0) - { - throw new InvalidOperationException( - "Provider advertisement still hardcodes daemon verse identity: " + - string.Join(", ", survivingHardcodedProviderSymbols)); - } - - if (!provider.Contains("AetheriaVerseHostSettings settings", StringComparison.Ordinal) || - !provider.Contains("AetheriaVerseHostSettingsNormalizer.Normalize(settings)", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Provider advertisement no longer derives Verse identity from typed daemon settings."); - } - - if (!operations.Contains("AetheriaVerseHostSettings? verseHostSettings = null", StringComparison.Ordinal) || - !operations.Contains("AetheriaVerseHostSettingsNormalizer.Normalize(verseHostSettings)", StringComparison.Ordinal) || - !operations.Contains("\"aetheria.operations.verseHost\"", StringComparison.Ordinal) || - !operations.Contains("Metric(\"verseHost.visibility\"", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Operations surface does not expose the typed daemon Verse-host telemetry card."); - } - - var requiredDaemonHostSymbols = new[] - { - "EnsureVerseHostSettingsAsync", - "AetheriaVerseDiscoveryHost", - "discoveryHost.Update", - "ReadOption(args, \"--verse-id\")", - "ReadOption(args, \"--cultmesh-address\")", - "node.MutableDocument(AetheriaStateNode.VerseHostSettingsKey).ReadAsync()", - "node.MutableDocument(AetheriaStateNode.VerseHostSettingsKey)", - ".ReplaceAsync(normalized)", - "AetheriaEveSurfaceDocuments.BuildProviderAdvertisement(verseHost, node.StatePath, updatedAtUtc)" - }; - var missingDaemonHostSymbols = requiredDaemonHostSymbols - .Where(symbol => !daemonHost.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (missingDaemonHostSymbols.Length > 0) - { - throw new InvalidOperationException( - "Aetheria.State.Daemon is missing typed verse-host startup authority: " + - string.Join(", ", missingDaemonHostSymbols)); - } -} - -static void RequireClientTargetBootAuthority(string root) -{ - var unityPackageProjectPath = Path.Combine(root, "GameCult.Aetheria.State.Unity.csproj"); - var boundaryPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeStateBoundary.cs"); - var bootPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeStateBoot.cs"); - var clientTargetStorePath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeClientTargetStore.cs"); - var clientTargetCommandsPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeClientTargetCommands.cs"); - var aetheriaStatePath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaState.cs"); - var verseDiscoveryPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeVerseDiscovery.cs"); - var replicaBridgePath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeVerseReplicaBridge.cs"); - var mainMenuSurfaceBuilderPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeMainMenuSurfaceBuilder.cs"); - var bootstrapPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.eve-runtime", "Runtime", "AetheriaEveRuntimeBootstrap.cs"); - var presenterPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.eve-runtime", "Runtime", "AetheriaEveSurfacePresenter.cs"); - var eveRuntimeClientCachePath = Path.Combine(root, "Packages", "org.gamecult.aetheria.eve-runtime", "Runtime", "AetheriaEveRuntimeUnityClientCache.cs"); - var eveRuntimeHookInstallerPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.eve-runtime", "Runtime", "AetheriaEveRuntimeUnityHookInstaller.cs"); - var actionGameManagerPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "ActionGameManager.cs"); - var gameplayBootShellPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "AetheriaUnityGameplayBootShell.cs"); - var mainMenuPath = Path.Combine(root, "Assets", "Scripts", "UI", "MainMenu.cs"); - - var requiredFiles = new[] - { - unityPackageProjectPath, - boundaryPath, - bootPath, - clientTargetStorePath, - clientTargetCommandsPath, - aetheriaStatePath, - verseDiscoveryPath, - replicaBridgePath, - mainMenuSurfaceBuilderPath, - bootstrapPath, - presenterPath, - eveRuntimeClientCachePath, - eveRuntimeHookInstallerPath, - actionGameManagerPath, - gameplayBootShellPath, - mainMenuPath - }; - - var missingFiles = requiredFiles - .Where(path => !File.Exists(path)) - .Select(path => Path.GetRelativePath(root, path)) - .ToArray(); - if (missingFiles.Length > 0) - { - throw new InvalidOperationException( - "Client target boot authority cannot be verified because required files are missing: " + - string.Join(", ", missingFiles)); - } - - var unityPackageProject = File.ReadAllText(unityPackageProjectPath); - var boundary = File.ReadAllText(boundaryPath); - var boot = File.ReadAllText(bootPath); - var clientTargetStore = File.ReadAllText(clientTargetStorePath); - var clientTargetCommands = File.ReadAllText(clientTargetCommandsPath); - var aetheriaState = File.ReadAllText(aetheriaStatePath); - var verseDiscovery = File.ReadAllText(verseDiscoveryPath); - var replicaBridge = File.ReadAllText(replicaBridgePath); - var mainMenuSurfaceBuilder = File.ReadAllText(mainMenuSurfaceBuilderPath); - var bootstrap = File.ReadAllText(bootstrapPath); - var presenter = File.ReadAllText(presenterPath); - var eveRuntimeClientCache = File.ReadAllText(eveRuntimeClientCachePath); - var eveRuntimeHookInstaller = File.ReadAllText(eveRuntimeHookInstallerPath); - var actionGameManager = File.ReadAllText(actionGameManagerPath); - var gameplayBootShell = File.ReadAllText(gameplayBootShellPath); - var mainMenu = File.ReadAllText(mainMenuPath); - - var requiredUnityPackageProjectSymbols = new[] - { - "AetheriaRuntimeClientTargetStore.cs", - "AetheriaState.cs", - "AetheriaRuntimeVerseDiscovery.cs", - "AetheriaRuntimeStateBoundary.cs", - "AetheriaRuntimeStateBoot.cs", - "AetheriaRuntimeVerseReplicaBridge.cs" - }; - var missingUnityPackageProjectSymbols = requiredUnityPackageProjectSymbols - .Where(symbol => !unityPackageProject.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (missingUnityPackageProjectSymbols.Length > 0) - { - throw new InvalidOperationException( - "GameCult.Aetheria.State.Unity.csproj no longer compiles the shared client-target boot files: " + - string.Join(", ", missingUnityPackageProjectSymbols)); - } - - var requiredBoundarySymbols = new[] - { - "RuntimeClientTargetFileName = \"aetheria-client.cc\"", - "RuntimeReplicaDirectoryName = \"Verses\"", - "RuntimeStatePathOverrideEnvironmentVariable = \"AETHERIA_STATE_PATH\"", - "GetClientTargetPath", - "GetReplicaStateFilePath", - "ResolveStatePathOverride" - }; - var missingBoundarySymbols = requiredBoundarySymbols - .Where(symbol => !boundary.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (missingBoundarySymbols.Length > 0) - { - throw new InvalidOperationException( - "Runtime state boundary no longer owns the client target file and override resolution: " + - string.Join(", ", missingBoundarySymbols)); - } - - var requiredClientTargetSymbols = new[] - { - "public static class AetheriaRuntimeClientTargetKinds", - "public sealed class AetheriaRuntimeClientTargetDocument", - "SchemaId = \"gamecult.aetheria.runtime_client_target.v1\"", - "TargetKind", - "StateFilePath", - "CultMeshAddress", - "DiscoveryEndpoints", - "DiscoveredVerses", - "LastDiscoveryAtUtc", - "LastDiscoveryError", - "ReplicaStateFilePath", - "LastReplicaSyncAtUtc", - "LastReplicaSyncError", - "ReadOrInitialize", - "CreateDefault", - "Write(string clientTargetPath, AetheriaRuntimeClientTargetDocument document)", - "Read(string clientTargetPath)" - }; - var missingClientTargetSymbols = requiredClientTargetSymbols - .Where(symbol => !clientTargetStore.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (missingClientTargetSymbols.Length > 0) - { - throw new InvalidOperationException( - "Typed client target store is incomplete: " + - string.Join(", ", missingClientTargetSymbols)); - } - - var requiredBootSymbols = new[] - { - "ClientTargetPath", - "TargetKind", - "TargetSource", - "SupportsLocalStateFileRead", - "FailureMessage", - "DiscoveryEndpoints", - "DiscoveredVerses", - "LastDiscoveryAtUtc", - "LastDiscoveryError", - "ReplicaStateFilePath", - "LastReplicaSyncAtUtc", - "LastReplicaSyncError", - "TargetLabel", - "AetheriaRuntimeClientTargetStore.ReadOrInitialize", - "AetheriaRuntimeStateBoundary.ResolveStatePathOverride()", - "AetheriaRuntimeClientTargetKinds.CultMeshVerse", - "GetReplicaStateFilePath", - "Sync the local replica", - "state-path-override", - "client-target" - }; - var missingBootSymbols = requiredBootSymbols - .Where(symbol => !boot.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (missingBootSymbols.Length > 0) - { - throw new InvalidOperationException( - "Runtime boot no longer resolves the active Verse through the client target owner: " + - string.Join(", ", missingBootSymbols)); - } - - var requiredVerseDiscoverySymbols = new[] - { - "CultMesh.CreateVerseCatalog()", - "CultMesh.CreateVerseDiscoveryClient()", - "DiscoverAsync(catalog, endpoints)", - "DiscoveredVerses", - "LastDiscoveryAtUtc", - "LastDiscoveryError" - }; - var missingVerseDiscoverySymbols = requiredVerseDiscoverySymbols - .Where(symbol => !verseDiscovery.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (missingVerseDiscoverySymbols.Length > 0) - { - throw new InvalidOperationException( - "Runtime Verse discovery helper no longer refreshes the typed client-target catalog through CultMesh discovery: " + - string.Join(", ", missingVerseDiscoverySymbols)); - } - - var requiredAetheriaStateSymbols = new[] - { - "public readonly struct AetheriaState", - "public static AetheriaState At(DirectoryInfo gameDataDirectory)", - "public AetheriaClientTarget ClientTarget", - "public readonly struct AetheriaClientTarget", - "public AetheriaRuntimeClientTargetDocument Refresh()", - "public AetheriaRuntimeClientTargetDocument DiscoverVerses()", - "public AetheriaRuntimeClientTargetDocument SyncReplica()", - "public AetheriaRuntimeClientTargetDocument CycleTransport()", - "public AetheriaRuntimeClientTargetDocument RequestTitle(string title)", - "public AetheriaRuntimeClientTargetDocument RequestVerseId(string verseId)", - "public AetheriaRuntimeClientTargetDocument RequestCultMeshAddress(string cultMeshAddress)", - "public AetheriaRuntimeClientTargetDocument RequestStateFilePath(string stateFilePath)", - "public AetheriaRuntimeClientTargetDocument RequestDiscoveryEndpoints(IEnumerable? discoveryEndpoints)", - "public AetheriaRuntimeClientTargetDocument SelectDiscoveredVerse(", - "AetheriaRuntimeStateBoundary.GetClientTargetPath(_gameDataDirectory)", - "AetheriaRuntimeStateBoundary.GetStateFilePath(_gameDataDirectory)", - "AetheriaRuntimeVerseDiscovery.RefreshClientTarget(", - "AetheriaRuntimeVerseReplicaBridge.Sync(", - "AetheriaRuntimeClientTargetStore.Update(", - "NormalizeDiscoveryEndpoints(", - "GetReplicaStateFilePath(gameDataDirectory, document.VerseId)" - }; - var missingAetheriaStateSymbols = requiredAetheriaStateSymbols - .Where(symbol => !aetheriaState.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (missingAetheriaStateSymbols.Length > 0) - { - throw new InvalidOperationException( - "Aetheria state sugar no longer owns typed client-target mutation: " + - string.Join(", ", missingAetheriaStateSymbols)); - } - - if (aetheriaState.Contains("public bool TryApply(AetheriaClientTargetOperation", StringComparison.Ordinal) || - aetheriaState.Contains("public AetheriaRuntimeClientTargetDocument Apply(AetheriaClientTargetOperation", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Aetheria state sugar still exposes Apply vocabulary for client-target edits; Unity should request typed target changes."); - } - - if (aetheriaState.Contains("public bool TryRequest(AetheriaClientTargetOperation", StringComparison.Ordinal) || - aetheriaState.Contains("public AetheriaRuntimeClientTargetDocument Request(AetheriaClientTargetOperation", StringComparison.Ordinal) || - clientTargetCommands.Contains("public readonly struct AetheriaClientTargetOperation", StringComparison.Ordinal) || - 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 document API."); - } - - var forbiddenClientTargetSetters = new[] - { - "public AetheriaRuntimeClientTargetDocument SetTitle(", - "public AetheriaRuntimeClientTargetDocument SetVerseId(", - "public AetheriaRuntimeClientTargetDocument SetCultMeshAddress(", - "public AetheriaRuntimeClientTargetDocument SetStateFilePath(", - "public AetheriaRuntimeClientTargetDocument SetDiscoveryEndpoints(" - }; - var clientTargetSetterHits = forbiddenClientTargetSetters - .Where(symbol => aetheriaState.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (clientTargetSetterHits.Length > 0) - { - throw new InvalidOperationException( - "Aetheria state sugar still exposes Set vocabulary for client-target edits; Unity should request typed target changes: " + - string.Join(", ", clientTargetSetterHits)); - } - - if (aetheriaState.Contains("DaemonOperations", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Aetheria state sugar regressed to command-applier naming instead of native state handles."); - } - - if (aetheriaState.Contains("AetheriaRuntimeStateReader", StringComparison.Ordinal) || - aetheriaState.Contains("TryReadDaemonFrame", StringComparison.Ordinal) || - aetheriaState.Contains("ReadVerseHostSettings", StringComparison.Ordinal) || - aetheriaState.Contains("AetheriaRuntimeDaemonFrameStore", StringComparison.Ordinal) || - aetheriaState.Contains("AetheriaRuntimeCatalogStore", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Aetheria state sugar still exposes file-backed daemon state reads; clients should use AetheriaRuntimeVerseClient."); - } - - if (aetheriaState.Contains("IReadOnlyDictionary", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Aetheria state sugar exposes generic dictionary payload plumbing instead of typed client-target operations."); - } - - if (aetheriaState.Contains("EveSurfaceCommandRequest", StringComparison.Ordinal) || - aetheriaState.Contains("ReadPayloadValue(", StringComparison.Ordinal) || - aetheriaState.Contains("request.Payload", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Aetheria state sugar still exposes Eve request payload plumbing instead of typed client-target operations."); - } - - var requiredReplicaBridgeSymbols = new[] - { - "public static class AetheriaRuntimeVerseReplicaBridge", - "AetheriaRuntimeVerseReplicaSyncResult", - "Aetheria.State.Replica", - "Sync(", - "ProcessStartInfo", - "AetheriaRuntimeStateBoundary.GetReplicaStateFilePath" - }; - var missingReplicaBridgeSymbols = requiredReplicaBridgeSymbols - .Where(symbol => !replicaBridge.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (missingReplicaBridgeSymbols.Length > 0) - { - throw new InvalidOperationException( - "Runtime Verse replica bridge is incomplete: " + - string.Join(", ", missingReplicaBridgeSymbols)); - } - - var requiredActionGameManagerSymbols = new[] - { - "private AetheriaUnityGameplayBootShell GameplayBootShell =>", - "var boot = GameplayBootShell.Boot();", - "SceneWiring.ConfigureCurrentEntityPresentation(_currentEntityPresentation, boot.RuntimeCatalog);", - "SceneWiring.ConfigureTargetPresentation(", - "boot.RuntimeCatalog,", - "SceneWiring.ConfigureActionBarPresentation(", - "ItemManager = boot.ItemManager;", - "_loadoutItemFactory = boot.LoadoutItemFactory;" - }; - var missingActionGameManagerSymbols = requiredActionGameManagerSymbols - .Where(symbol => !actionGameManager.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (missingActionGameManagerSymbols.Length > 0) - { - throw new InvalidOperationException( - "ActionGameManager no longer delegates gameplay boot through AetheriaUnityGameplayBootShell: " + - string.Join(", ", missingActionGameManagerSymbols)); - } - - var forbiddenActionGameManagerBootSymbols = new[] - { - "AetheriaRuntimeStateBoot.Inspect(AetheriaUnityRuntimePaths.GameDataDirectory)", - "Aetheria runtime target: {stateBoot.TargetLabel} via {stateBoot.TargetKind} ({stateBoot.TargetSource})", - "!stateBoot.SupportsLocalStateFileRead", - "stateBoot.StateFileExists", - "private static AetheriaRuntimeCatalogSnapshot _runtimeCatalog", - "FindTypedRuntimeItem(", - "_runtimeCatalog?.FindItem(", - ".OpenRuntimeCatalog()" - }; - var actionGameManagerBootHits = forbiddenActionGameManagerBootSymbols - .Where(symbol => actionGameManager.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (actionGameManagerBootHits.Length > 0) - { - throw new InvalidOperationException( - "ActionGameManager still owns runtime-target boot details instead of the Unity gameplay boot shell: " + - string.Join(", ", actionGameManagerBootHits)); - } - - var requiredGameplayBootShellSymbols = new[] - { - "public sealed class AetheriaUnityGameplayBootShell", - "public AetheriaUnityGameplayBootResult Boot()", - "AetheriaRuntimeStateBoot.Inspect(AetheriaUnityRuntimePaths.GameDataDirectory)", - "Aetheria runtime target: {stateBoot.TargetLabel} via {stateBoot.TargetKind} ({stateBoot.TargetSource})", - "Aetheria runtime id: {stateBoot.RuntimeId}", - "Aetheria runtime state file: {stateBoot.StateFilePath}", - "!stateBoot.SupportsLocalStateFileRead", - "stateBoot.StateFileExists", - "AetheriaUnityRuntimeClientProvider.RuntimeState(stateBoot, stateBoot.RuntimeId)", - "runtimeState.Catalog.Latest()", - "runtimeState.SectorMap.Latest()", - "new ItemManager(", - "new AetheriaUnityLoadoutItemFactory(itemManager, runtimeCatalog)", - "ZoneRenderer.SetDroppedPickupItemFactory(loadoutItemFactory.CreateLoadoutItem)", - "ZoneRenderer.BodySettingsCollections = Settings.BodySettingsCollections", - "runtimeState?.DaemonFrame?.Latest()", - "render/game-feel settings are daemon authority", - "CockpitHudShell.SetRenderSettings(ZoneRenderer.RenderSettings)", - "AetheriaUnityRuntimeClientProvider.PlayerSettings.GraphicsSettings.ShowAsteroidsInMinimap", - "public readonly struct AetheriaUnityGameplayBootResult" - }; - var missingGameplayBootShellSymbols = requiredGameplayBootShellSymbols - .Where(symbol => !gameplayBootShell.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (missingGameplayBootShellSymbols.Length > 0) - { - throw new InvalidOperationException( - "AetheriaUnityGameplayBootShell no longer boots gameplay through the shared client-target report: " + - string.Join(", ", missingGameplayBootShellSymbols)); - } - - if (gameplayBootShell.Contains(".ObserveCatalog()", StringComparison.Ordinal) || - gameplayBootShell.Contains(".ObserveSectorMap()", StringComparison.Ordinal) || - gameplayBootShell.Contains(".ReactiveCatalogSnapshot()", StringComparison.Ordinal) || - gameplayBootShell.Contains("runtimeCatalogDocument.Current", StringComparison.Ordinal) || - gameplayBootShell.Contains(".ReactiveSectorMap()", StringComparison.Ordinal) || - gameplayBootShell.Contains("sectorMapDocument.Current", 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;")) - { - throw new InvalidOperationException( - "AetheriaUnityGameplayBootShell must acquire runtime state through provider-native typed state access, not legacy wrappers or full-client peels."); - } - - var requiredPresenterSymbols = new[] - { - "ResolveStateBoot()", - "AetheriaEveRuntimeUnityHooks.RequireStateBoot(", - "!stateBoot.SupportsLocalStateFileRead", - "stateBoot.StateFileExists" - }; - var missingPresenterSymbols = requiredPresenterSymbols - .Where(symbol => !presenter.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (missingPresenterSymbols.Length > 0) - { - throw new InvalidOperationException( - "Aetheria Eve presenter no longer mounts through the shared client-target boot report: " + - string.Join(", ", missingPresenterSymbols)); - } - - var requiredEveRuntimeClientCacheSymbols = new[] - { - "public static class AetheriaEveRuntimeUnityClientCache", - "new DirectoryInfo(Application.dataPath).Parent.CreateSubdirectory(\"GameData\")", - "AetheriaRuntimeStateBoot.Inspect(GameDataDirectory).StateFilePath", - "AetheriaClient", - ".OpenAsync(", - "startServer: false", - "pullOnOpen: true", - "CreateEveSurfaceCultMeshStateRefResolver()", - "public static void Dispose()" - }; - var missingEveRuntimeClientCacheSymbols = requiredEveRuntimeClientCacheSymbols - .Where(symbol => !eveRuntimeClientCache.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (missingEveRuntimeClientCacheSymbols.Length > 0) - { - throw new InvalidOperationException( - "Aetheria Eve runtime package no longer owns its generic Unity client cache: " + - string.Join(", ", missingEveRuntimeClientCacheSymbols)); - } - - var requiredEveRuntimeHookInstallerSymbols = new[] - { - "public static class AetheriaEveRuntimeUnityHookInstaller", - "RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)", - "public static void Install()", - "AetheriaEveRuntimeUnityHooks.ResolveStateBoot", - "AetheriaRuntimeStateBoot.Inspect(", - "AetheriaEveRuntimeUnityClientCache.GameDataDirectory", - "AetheriaEveRuntimeUnityHooks.RuntimeState", - "AetheriaEveRuntimeUnityHooks.Control", - "AetheriaEveRuntimeUnityHooks.Ui", - "AetheriaEveRuntimeUnityHooks.StateRefResolver" - }; - var missingEveRuntimeHookInstallerSymbols = requiredEveRuntimeHookInstallerSymbols - .Where(symbol => !eveRuntimeHookInstaller.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (missingEveRuntimeHookInstallerSymbols.Length > 0) - { - throw new InvalidOperationException( - "Aetheria Eve runtime package no longer owns Unity client-target hook installation: " + - string.Join(", ", missingEveRuntimeHookInstallerSymbols)); - } - - if (File.Exists(Path.Combine(root, "Assets", "Scripts", "Gameplay", "AetheriaUnityEveRuntimeHooks.cs"))) - { - throw new InvalidOperationException( - "Aetheria Eve runtime hooks are still installed by the legacy Unity gameplay assembly; the provider package must own this boundary."); - } - - if (bootstrap.Contains("StatePathEnvironmentVariable", StringComparison.Ordinal) || - bootstrap.Contains("presenter.StateFilePathOverride =", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Aetheria Eve runtime bootstrap still tries to own state-path override resolution."); - } - - var requiredMainMenuSymbols = new[] - { - "CurrentStateBoot()", - "ResolveSectorMap(AetheriaRuntimeStateBootReport stateBoot)", - "AetheriaUnityRuntimeClientProvider", - ".RuntimeState(stateBoot, \"unity-main-menu\")", - ".SectorMap", - ".Latest()", - "ResolveMainMenuSurface(", - ".MainMenuSurface(surfaceId, CanOpenRuntimeInputScreen(), InGame)", - "AetheriaState.At(AetheriaUnityRuntimePaths.GameDataDirectory)", - ".ClientTarget", - "RequestClientTargetCommand(request)", - "AetheriaRuntimeClientTargetSurfaceCommands.TryRequest(" - }; - var requiredMainMenuBuilderSymbols = new[] - { - "private static AetheriaRuntimeSurfaceDocument BuildRoot(", - "\"STARBRIDGE\"", - "GravitySurface($", - "AetheriaRuntimeDaemonSchemas.RenderSplatsViewport", - "AetheriaRuntimeDaemonSchemas.GravityViewport", - "AetheriaRuntimeDaemonSchemas.ObjectsViewport", - "new AetheriaRuntimeEmbeddedDocumentSlot(" - }; - var missingMainMenuSymbols = requiredMainMenuSymbols - .Where(symbol => !mainMenu.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (missingMainMenuSymbols.Length > 0) - { - throw new InvalidOperationException( - "MainMenu no longer lowers the shared client-target boot state through Eve: " + - string.Join(", ", missingMainMenuSymbols)); - } - var missingMainMenuBuilderSymbols = requiredMainMenuBuilderSymbols - .Where(symbol => !mainMenuSurfaceBuilder.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (missingMainMenuBuilderSymbols.Length > 0) - { - throw new InvalidOperationException( - "Shared main-menu surface builder no longer lowers the client-target boot state through Eve: " + - string.Join(", ", missingMainMenuBuilderSymbols)); - } - - if (mainMenu.Contains("PrepareDocumentAccess(stateBoot)", StringComparison.Ordinal) || - mainMenu.Contains(".ReactiveSectorMap(", StringComparison.Ordinal) || - mainMenu.Contains(".ReactivePlayerSettingsDocument(", StringComparison.Ordinal) || - mainMenu.Contains(".ReactiveVerseHostSettings(", StringComparison.Ordinal) || - mainMenu.Contains("CultMeshReactiveDocument", StringComparison.Ordinal) || - mainMenu.Contains("CultMeshReactiveDocument", StringComparison.Ordinal) || - mainMenu.Contains("CultMeshReactiveDocument", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "MainMenu should sample named current typed documents instead of owning reactive document handles."); - } - - var forbiddenDirectPathSymbols = new Dictionary - { - [actionGameManagerPath] = new[] - { - "AetheriaRuntimeStateBoundary.GetStateFilePath(GameDataDirectory)" - }, - [presenterPath] = new[] - { - "AetheriaRuntimeStateBoundary.GetStateFilePath(gameDataDirectory)" - }, - [bootstrapPath] = new[] - { - "StatePathOverride()" - }, - [mainMenuPath] = new[] - { - "AetheriaRuntimeStateBoundary.", - "AetheriaRuntimeClientTargetStore.", - "AetheriaRuntimeVerseDiscovery.RefreshClientTarget(", - "AetheriaRuntimeVerseReplicaBridge.Sync(", - "AetheriaRuntimeClientTarget.", - "DaemonOperations", - } - }; - - var hits = forbiddenDirectPathSymbols - .SelectMany(entry => - { - var source = File.ReadAllText(entry.Key); - return entry.Value - .Where(symbol => source.Contains(symbol, StringComparison.Ordinal)) - .Select(symbol => $"{Path.GetRelativePath(root, entry.Key)} -> {symbol}"); - }) - .ToArray(); - - if (hits.Length > 0) - { - throw new InvalidOperationException( - "Unity boot still contains direct local state-path owners instead of the shared client target: " + - string.Join("; ", hits)); - } -} - -static void RequireClientRuntimeConformanceContract(string root) -{ - var conformancePath = Path.Combine(root, "Aetheria.State", "docs", "client-runtime-conformance.md"); - var authorityPlanPath = Path.Combine(root, "Aetheria.State", "docs", "verse-authority-implementation-plan.md"); - var ergonomicsPlanPath = Path.Combine(root, "Aetheria.State", "docs", "cultmesh-ergonomics-staged-migration-plan.md"); - - var conformance = File.Exists(conformancePath) - ? File.ReadAllText(conformancePath) - : throw new InvalidOperationException("Client runtime conformance contract is missing."); - var authorityPlan = File.Exists(authorityPlanPath) - ? File.ReadAllText(authorityPlanPath) - : throw new InvalidOperationException("Verse authority implementation plan is missing."); - var ergonomicsPlan = File.Exists(ergonomicsPlanPath) - ? File.ReadAllText(ergonomicsPlanPath) - : throw new InvalidOperationException("CultMesh ergonomics staged migration plan is missing."); - - var requiredContractSymbols = new[] - { - "Hermodr | Unspecialized Eve/browser lowering sanity check for the RTS surface.", - "Electron | Player-facing Starbridge RTS client.", - "Unity | Current ARPG reference client and demolition target.", - "Godot | Future unspecialized Eve/runtime parity target for the ARPG surface.", - "No daemon-side `Godot` mode, `Electron` mode, `Hermodr` mode, or `Unity`", - "No Aetheria-specific Hermodr plugin for RTS map semantics.", - "Godot lowers the ARPG contract without inheriting Unity authority." - }; - var missingContractSymbols = requiredContractSymbols - .Where(symbol => !conformance.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (missingContractSymbols.Length > 0) - { - throw new InvalidOperationException( - "Client runtime conformance contract no longer names the runtime authority map: " + - string.Join(", ", missingContractSymbols)); - } - - var requiredPlanSymbols = new[] - { - "Hermodr is the unspecialized Eve sanity check for the RTS surface", - "player-facing RTS client", - "future unspecialized Eve parity target for the ARPG surface", - "Hermodr must be able to reconstruct the RTS surface from Eve plus daemon API", - "Any daemon-side `Godot` behavior branch would be the wrong cut" - }; - var missingPlanSymbols = requiredPlanSymbols - .Where(symbol => !authorityPlan.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (missingPlanSymbols.Length > 0) - { - throw new InvalidOperationException( - "Verse authority plan no longer preserves Hermodr/Electron/Godot/Unity conformance roles: " + - string.Join(", ", missingPlanSymbols)); - } - - var requiredErgonomicsSymbols = new[] - { - "Hermodr can reconstruct the RTS gameplay surface as an unspecialized Eve", - "Electron may be the shipped RTS shell but cannot own private", - "Godot can become the ARPG equivalent conformance target" - }; - var missingErgonomicsSymbols = requiredErgonomicsSymbols - .Where(symbol => !ergonomicsPlan.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (missingErgonomicsSymbols.Length > 0) - { - throw new InvalidOperationException( - "CultMesh ergonomics plan no longer carries the client runtime conformance gate: " + - string.Join(", ", missingErgonomicsSymbols)); - } -} - -static void RequireVerseReplicaTool(string root) -{ - var replicaToolProjectPath = Path.Combine(root, "Aetheria.State.Replica", "Aetheria.State.Replica.csproj"); - var replicaToolProgramPath = Path.Combine(root, "Aetheria.State.Replica", "Program.cs"); - var replicaHostPath = Path.Combine(root, "Aetheria.State", "AetheriaVerseReplica.cs"); - var stateReadmePath = Path.Combine(root, "Aetheria.State", "README.md"); - - var requiredFiles = new[] - { - replicaToolProjectPath, - replicaToolProgramPath, - replicaHostPath, - stateReadmePath - }; - - var missingFiles = requiredFiles - .Where(path => !File.Exists(path)) - .Select(path => Path.GetRelativePath(root, path)) - .ToArray(); - if (missingFiles.Length > 0) - { - throw new InvalidOperationException( - "Verse replica tool cannot be verified because required files are missing: " + - string.Join(", ", missingFiles)); - } - - var replicaToolProject = File.ReadAllText(replicaToolProjectPath); - var replicaToolProgram = File.ReadAllText(replicaToolProgramPath); - var replicaHost = File.ReadAllText(replicaHostPath); - var stateReadme = File.ReadAllText(stateReadmePath); - - var requiredProjectSymbols = new[] - { - "" - }; - var missingProjectSymbols = requiredProjectSymbols - .Where(symbol => !replicaToolProject.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (missingProjectSymbols.Length > 0) - { - throw new InvalidOperationException( - "Verse replica tool project is incomplete: " + - string.Join(", ", missingProjectSymbols)); - } - - if (replicaToolProject.Contains("Aetheria.State.Unity", StringComparison.Ordinal) || - replicaToolProject.Contains("GameCult.Aetheria.State.Unity", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Verse replica tool should not depend on Unity state assemblies; replica sync must stay daemon-state native."); - } - - var requiredProgramSymbols = new[] - { - "sync", - "follow", - "AetheriaVerseReplica", - "ResolveReplicaPath", - "--endpoint", - "--replica", - "--game-data-root", - "--verse-id", - "--runtime-id" - }; - var missingProgramSymbols = requiredProgramSymbols - .Where(symbol => !replicaToolProgram.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (missingProgramSymbols.Length > 0) - { - throw new InvalidOperationException( - "Verse replica tool program is incomplete: " + - string.Join(", ", missingProgramSymbols)); - } - - var requiredReplicaHostSymbols = new[] - { - "public static class AetheriaVerseReplica", - "SyncSnapshotAsync", - "RunReplicaAsync", - "CultNetSchemaShardSnapshotFetcher", - "CultNetSchemaShardLogFetcher", - "CultNetShardReplicator", - "ApplyRawSnapshotResponseAsync" - }; - var missingReplicaHostSymbols = requiredReplicaHostSymbols - .Where(symbol => !replicaHost.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (missingReplicaHostSymbols.Length > 0) - { - throw new InvalidOperationException( - "Verse replica host is incomplete: " + - string.Join(", ", missingReplicaHostSymbols)); - } - - if (!stateReadme.Contains("Aetheria.State.Replica", StringComparison.Ordinal) || - !stateReadme.Contains("GameData\\Verses\\.cc", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Aetheria.State README does not describe the cache-only Verse replica workflow."); - } -} - -static void RequireVerseSettingsShellAndBridge(string root) -{ - var unityPackageProjectPath = Path.Combine(root, "GameCult.Aetheria.State.Unity.csproj"); - var stateProjectPath = Path.Combine(root, "Aetheria.State", "Aetheria.State.csproj"); - var clientTargetCommandsPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeClientTargetCommands.cs"); - var aetheriaStatePath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaState.cs"); - var clientTargetSurfaceBuilderPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeClientTargetSurfaceBuilder.cs"); - var verseHostCommandsPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeVerseHostCommands.cs"); - var clientTargetStorePath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeClientTargetStore.cs"); - var verseDiscoveryPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeVerseDiscovery.cs"); - var replicaBridgePath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeVerseReplicaBridge.cs"); - var mainMenuPath = Path.Combine(root, "Assets", "Scripts", "UI", "MainMenu.cs"); - var commandBridgePath = Path.Combine(root, "Aetheria.State", "AetheriaEveCommandBridge.cs"); - var acceptanceStatusPath = Path.Combine(root, "Aetheria.State", "Documents", "AetheriaEveCommandAcceptanceStatus.cs"); - var operationsSurfaceDocumentsPath = Path.Combine(root, "Aetheria.State", "AetheriaEveSurfaceDocuments.cs"); - var daemonHostPath = Path.Combine(root, "Aetheria.State.Daemon", "Program.cs"); - - var requiredFiles = new[] - { - unityPackageProjectPath, - stateProjectPath, - clientTargetCommandsPath, - aetheriaStatePath, - clientTargetSurfaceBuilderPath, - verseHostCommandsPath, - clientTargetStorePath, - verseDiscoveryPath, - replicaBridgePath, - mainMenuPath, - commandBridgePath, - acceptanceStatusPath, - operationsSurfaceDocumentsPath, - daemonHostPath - }; - - var missingFiles = requiredFiles - .Where(path => !File.Exists(path)) - .Select(path => Path.GetRelativePath(root, path)) - .ToArray(); - if (missingFiles.Length > 0) - { - throw new InvalidOperationException( - "Verse settings shell/bridge cannot be verified because required files are missing: " + - string.Join(", ", missingFiles)); - } - - var unityPackageProject = File.ReadAllText(unityPackageProjectPath); - var stateProject = File.ReadAllText(stateProjectPath); - var clientTargetCommands = File.ReadAllText(clientTargetCommandsPath); - var aetheriaState = File.ReadAllText(aetheriaStatePath); - var clientTargetSurfaceBuilder = File.ReadAllText(clientTargetSurfaceBuilderPath); - var verseHostCommands = File.ReadAllText(verseHostCommandsPath); - var clientTargetStore = File.ReadAllText(clientTargetStorePath); - var verseDiscovery = File.ReadAllText(verseDiscoveryPath); - var replicaBridge = File.ReadAllText(replicaBridgePath); - var mainMenu = File.ReadAllText(mainMenuPath); - var commandBridge = File.ReadAllText(commandBridgePath); - var acceptanceStatus = File.ReadAllText(acceptanceStatusPath); - var operationsSurfaceDocuments = File.ReadAllText(operationsSurfaceDocumentsPath); - var daemonHost = File.ReadAllText(daemonHostPath); - - var requiredUnityPackageProjectSymbols = new[] - { - "AetheriaRuntimeClientTargetCommands.cs", - "AetheriaState.cs", - "AetheriaRuntimeClientTargetSurfaceBuilder.cs", - "AetheriaRuntimeVerseDiscovery.cs", - "AetheriaRuntimeVerseReplicaBridge.cs" - }; - var missingUnityPackageProjectSymbols = requiredUnityPackageProjectSymbols - .Where(symbol => !unityPackageProject.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (missingUnityPackageProjectSymbols.Length > 0) - { - throw new InvalidOperationException( - "GameCult.Aetheria.State.Unity.csproj does not compile the Verse settings shell files: " + - string.Join(", ", missingUnityPackageProjectSymbols)); - } - - var stateProjectIncludesRuntimePackage = stateProject.Contains(@"Runtime\*.cs", StringComparison.Ordinal); - if (!stateProjectIncludesRuntimePackage && - !stateProject.Contains("AetheriaRuntimeVerseHostCommands.cs", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Aetheria.State.csproj does not compile the Verse-host Eve command contract."); - } - - var requiredClientTargetCommandSymbols = new[] - { - "SurfaceId = \"aetheria.client_target\"", - "CycleTargetKind", - "SetTitle", - "SetVerseId", - "SetCultMeshAddress", - "SetStateFilePath", - "SetDiscoveryEndpoints", - "DiscoverVerses", - "SelectDiscoveredVerse", - "SyncReplica", - "IsKnown" - }; - var missingClientTargetCommandSymbols = requiredClientTargetCommandSymbols - .Where(symbol => !clientTargetCommands.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (missingClientTargetCommandSymbols.Length > 0) - { - throw new InvalidOperationException( - "Client-target command contract is incomplete: " + - string.Join(", ", missingClientTargetCommandSymbols)); - } - - var requiredVerseHostCommandSymbols = new[] - { - "SurfaceId = \"aetheria.verse_host_settings\"", - "CycleVisibility", - "Refresh", - "IsKnown" - }; - var missingVerseHostCommandSymbols = requiredVerseHostCommandSymbols - .Where(symbol => !verseHostCommands.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (missingVerseHostCommandSymbols.Length > 0) - { - throw new InvalidOperationException( - "Verse-host command contract is incomplete: " + - string.Join(", ", missingVerseHostCommandSymbols)); - } - - var requiredSurfaceBuilderSymbols = new[] - { - "AetheriaRuntimeStateBootReport stateBoot", - "AetheriaRuntimeVerseHostSettingsDocument verseHost", - "var targetKind = stateBoot.TargetKind ?? AetheriaRuntimeClientTargetKinds.StateFile", - "var discoveredVerses = stateBoot.DiscoveredVerses ?? Array.Empty()", - "Metric(\"aetheria.clientTarget.summary.target\", \"Target\", stateBoot.TargetLabel)", - "Metric(\"aetheria.clientTarget.host.verse\", \"Verse\", BuildVerseLabel(hostTitle, hostVerseId))", - "\"Client Target\"", - "\"Target Fields\"", - "\"Verse Discovery\"", - "\"Daemon Verse Host\"", - "\"Replica State File\"", - "\"Replica Sync\"", - "AetheriaRuntimeClientTargetCommands.SetStateFilePath", - "AetheriaRuntimeClientTargetCommands.SetDiscoveryEndpoints", - "AetheriaRuntimeClientTargetCommands.DiscoverVerses", - "AetheriaRuntimeClientTargetCommands.SelectDiscoveredVerse", - "AetheriaRuntimeClientTargetCommands.SyncReplica", - "AetheriaRuntimeVerseHostCommands.CycleVisibility", - "AetheriaRuntimeVerseHostCommands.Refresh" - }; - var missingSurfaceBuilderSymbols = requiredSurfaceBuilderSymbols - .Where(symbol => !clientTargetSurfaceBuilder.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (missingSurfaceBuilderSymbols.Length > 0) - { - throw new InvalidOperationException( - "Client-target Eve surface builder is incomplete: " + - string.Join(", ", missingSurfaceBuilderSymbols)); - } - - if (clientTargetSurfaceBuilder.Contains("AetheriaRuntimeClientTargetSurfaceState", StringComparison.Ordinal) || - clientTargetSurfaceBuilder.Contains("new AetheriaRuntimeClientTargetSurfaceState(", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Client-target Eve surface builder must compose from typed boot and Verse-host documents directly; do not rebuild a shadow SurfaceState layer."); - } - - if (!clientTargetStore.Contains("Update(", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Client-target store no longer exposes typed update ownership for local target edits."); - } - - var requiredAetheriaStateSymbols = new[] - { - "public readonly struct AetheriaState", - "public AetheriaClientTarget ClientTarget", - "public readonly struct AetheriaClientTarget", - "public AetheriaRuntimeClientTargetDocument Refresh()", - "public AetheriaRuntimeClientTargetDocument DiscoverVerses()", - "public AetheriaRuntimeClientTargetDocument SyncReplica()", - "public AetheriaRuntimeClientTargetDocument CycleTransport()", - "public AetheriaRuntimeClientTargetDocument RequestTitle(string title)", - "public AetheriaRuntimeClientTargetDocument RequestVerseId(string verseId)", - "public AetheriaRuntimeClientTargetDocument RequestCultMeshAddress(string cultMeshAddress)", - "public AetheriaRuntimeClientTargetDocument RequestStateFilePath(string stateFilePath)", - "public AetheriaRuntimeClientTargetDocument RequestDiscoveryEndpoints(IEnumerable? discoveryEndpoints)", - "public AetheriaRuntimeClientTargetDocument SelectDiscoveredVerse(", - "AetheriaRuntimeClientTargetStore.Update(", - "AetheriaRuntimeVerseDiscovery.RefreshClientTarget(", - "AetheriaRuntimeVerseReplicaBridge.Sync(", - "NormalizeDiscoveryEndpoints(" - }; - var missingAetheriaStateSymbols = requiredAetheriaStateSymbols - .Where(symbol => !aetheriaState.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (missingAetheriaStateSymbols.Length > 0) - { - throw new InvalidOperationException( - "Aetheria state sugar no longer owns the Verse settings mutation bridge: " + - string.Join(", ", missingAetheriaStateSymbols)); - } - - if (aetheriaState.Contains("public bool TryApply(AetheriaClientTargetOperation", StringComparison.Ordinal) || - aetheriaState.Contains("public AetheriaRuntimeClientTargetDocument Apply(AetheriaClientTargetOperation", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Aetheria state sugar still exposes Apply vocabulary for Verse settings edits; Unity should request typed target changes."); - } - - if (aetheriaState.Contains("public bool TryRequest(AetheriaClientTargetOperation", StringComparison.Ordinal) || - aetheriaState.Contains("public AetheriaRuntimeClientTargetDocument Request(AetheriaClientTargetOperation", StringComparison.Ordinal) || - clientTargetCommands.Contains("public readonly struct AetheriaClientTargetOperation", StringComparison.Ordinal) || - 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 document API."); - } - - var forbiddenClientTargetSetters = new[] - { - "public AetheriaRuntimeClientTargetDocument SetTitle(", - "public AetheriaRuntimeClientTargetDocument SetVerseId(", - "public AetheriaRuntimeClientTargetDocument SetCultMeshAddress(", - "public AetheriaRuntimeClientTargetDocument SetStateFilePath(", - "public AetheriaRuntimeClientTargetDocument SetDiscoveryEndpoints(" - }; - var clientTargetSetterHits = forbiddenClientTargetSetters - .Where(symbol => aetheriaState.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (clientTargetSetterHits.Length > 0) - { - throw new InvalidOperationException( - "Aetheria state sugar still exposes Set vocabulary for Verse settings edits; Unity should request typed target changes: " + - string.Join(", ", clientTargetSetterHits)); - } - - if (aetheriaState.Contains("DaemonOperations", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Aetheria state sugar regressed to command-applier naming instead of native state handles."); - } - - if (aetheriaState.Contains("AetheriaRuntimeStateReader", StringComparison.Ordinal) || - aetheriaState.Contains("TryReadDaemonFrame", StringComparison.Ordinal) || - aetheriaState.Contains("ReadVerseHostSettings", StringComparison.Ordinal) || - aetheriaState.Contains("AetheriaRuntimeDaemonFrameStore", StringComparison.Ordinal) || - aetheriaState.Contains("AetheriaRuntimeCatalogStore", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Aetheria state sugar still exposes file-backed daemon state reads; clients should use AetheriaRuntimeVerseClient."); - } - - if (aetheriaState.Contains("IReadOnlyDictionary", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Aetheria state sugar exposes generic dictionary payload plumbing instead of typed client-target operations."); - } - - if (aetheriaState.Contains("EveSurfaceCommandRequest", StringComparison.Ordinal) || - aetheriaState.Contains("ReadPayloadValue(", StringComparison.Ordinal) || - aetheriaState.Contains("request.Payload", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Aetheria state sugar still exposes Eve request payload plumbing instead of typed client-target operations."); - } - - var requiredVerseDiscoverySymbols = new[] - { - "CultMesh.CreateVerseCatalog()", - "CultMesh.CreateVerseDiscoveryClient()", - "DiscoverAsync(catalog, endpoints)" - }; - var missingVerseDiscoverySymbols = requiredVerseDiscoverySymbols - .Where(symbol => !verseDiscovery.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (missingVerseDiscoverySymbols.Length > 0) - { - throw new InvalidOperationException( - "Verse discovery helper no longer routes scans through the CultMesh catalog client: " + - string.Join(", ", missingVerseDiscoverySymbols)); - } - - var requiredReplicaBridgeSymbols = new[] - { - "public static class AetheriaRuntimeVerseReplicaBridge", - "ProcessStartInfo", - "Aetheria.State.Replica", - "Sync(" - }; - var missingReplicaBridgeSymbols = requiredReplicaBridgeSymbols - .Where(symbol => !replicaBridge.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (missingReplicaBridgeSymbols.Length > 0) - { - throw new InvalidOperationException( - "Verse replica bridge no longer exposes the sync transport used by the Unity shell: " + - string.Join(", ", missingReplicaBridgeSymbols)); - } - - var requiredMainMenuSymbols = new[] - { - "AetheriaRuntimeMainMenuCommandKind.ShowVerseSettings", - "ShowVerseSettingsSurface(", - "HandleVerseSettingsSurfaceCommand(EveSurfaceCommandRequest request)", - "ResolveMainMenuSurface(AetheriaRuntimeMainMenuCommands.VerseSettingsSurfaceId)", - ".MainMenuSurface(surfaceId, CanOpenRuntimeInputScreen(), InGame)", - "AetheriaRuntimeMainMenuSurfaceCommands.TryRead(request, out var command)", - "AetheriaRuntimeMainMenuCommandKind.ClientTargetCommand", - "AetheriaRuntimeMainMenuCommandKind.VerseHostCommand", - "RequestClientTargetCommand(request)", - "SendKnownAetheriaEveCommand(request, \"Verse-host\")", - ".Ui(stateBoot, \"unity-main-menu\")", - ".SurfaceCommandAsync(request, \"unity-main-menu\")", - "AetheriaState.At(AetheriaUnityRuntimePaths.GameDataDirectory)", - ".ClientTarget", - "AetheriaRuntimeClientTargetSurfaceCommands.TryRequest(" - }; - var missingMainMenuSymbols = requiredMainMenuSymbols - .Where(symbol => !mainMenu.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (missingMainMenuSymbols.Length > 0) - { - throw new InvalidOperationException( - "MainMenu no longer owns the typed Verse settings shell handoff: " + - string.Join(", ", missingMainMenuSymbols)); - } - - var forbiddenSubmitAcceptanceSymbols = new[] - { - "private static bool TryRequestClientTargetCommand(", - "private static bool TrySendKnownAetheriaEveCommand(", - "if (TryRequestClientTargetCommand(request))", - "if (TrySendKnownAetheriaEveCommand(request, \"Verse-host\"))" - }; - var submitAcceptanceHits = forbiddenSubmitAcceptanceSymbols - .Where(symbol => mainMenu.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (submitAcceptanceHits.Length > 0) - { - throw new InvalidOperationException( - "MainMenu Verse settings command lowering still treats submission as local acceptance state: " + - string.Join(", ", submitAcceptanceHits)); - } - - var forbiddenMainMenuMutationSymbols = new[] - { - "AetheriaRuntimeClientTargetStore.", - "AetheriaRuntimeVerseDiscovery.RefreshClientTarget(", - "AetheriaRuntimeVerseReplicaBridge.Sync(", - "AetheriaRuntimeStateBoundary.", - "AetheriaRuntimeClientTarget.", - "DaemonOperations", - "ReadPayloadValue(", - "ParseDiscoveryEndpoints(", - "request.Payload", - "IReadOnlyDictionary", - "clientTarget.SetTitle(", - "clientTarget.SetVerseId(", - "clientTarget.SetCultMeshAddress(", - "clientTarget.SetStateFilePath(", - "clientTarget.SetDiscoveryEndpoints(", - "clientTarget.DiscoverVerses()", - "clientTarget.SelectDiscoveredVerse(", - "clientTarget.SyncReplica()", - "TryQueueVerseHostCommand(", - "TrySendVerseHostCommand(request.Command)", - "CommandKindForSurface(request)", - "AetheriaRuntimeClientTargetCommands.IsKnown(request.Command", - "AetheriaRuntimeVerseHostCommands.IsKnown(request.Command", - "new EveSurfaceCommandRequest(", - "AetheriaRuntimeEveCommandLog.Queue", - }; - var mainMenuMutationHits = forbiddenMainMenuMutationSymbols - .Where(symbol => mainMenu.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (mainMenuMutationHits.Length > 0) - { - throw new InvalidOperationException( - "MainMenu still mutates the client target directly instead of dispatching through the runtime daemon operations: " + - string.Join(", ", mainMenuMutationHits)); - } - - var requiredBridgeSymbols = new[] - { - "AcceptedVerseHostCommands", - "AetheriaRuntimeEveCommandKind.VerseHostRefresh", - "AetheriaRuntimeEveCommandKind.CycleVerseHostVisibility", - "ExecuteVerseHostCommandAsync", - "node.MutableDocument(AetheriaStateNode.VerseHostSettingsKey)", - ".ReplaceAsync(normalized)", - "node.MutableDocument(AetheriaStateNode.ProviderAdvertisementSurfaceKey)", - "AetheriaEveSurfaceDocuments.BuildOperationsSurface(", - "switch (command.Kind)" - }; - var missingBridgeSymbols = requiredBridgeSymbols - .Where(symbol => !commandBridge.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (missingBridgeSymbols.Length > 0) - { - throw new InvalidOperationException( - "Aetheria Eve command bridge no longer routes Verse-host commands through the provider owner: " + - string.Join(", ", missingBridgeSymbols)); - } - - var requiredAcceptanceSymbols = new[] - { - "AetheriaEveCommandAcceptanceStatus", - "aetheria.eve_command_acceptance_status.v1", - "ObservedBeforeAccept", - "AppliedVerseHostCommands" - }; - var missingAcceptanceSymbols = requiredAcceptanceSymbols - .Where(symbol => !acceptanceStatus.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (missingAcceptanceSymbols.Length > 0) - { - throw new InvalidOperationException( - "Aetheria Eve command acceptance status no longer records Verse-host command counts: " + - string.Join(", ", missingAcceptanceSymbols)); - } - - if (File.Exists(Path.Combine(root, "Aetheria.State", "Documents", "AetheriaEveCommandDrainStatus.cs")) || - acceptanceStatus.Contains("AetheriaEveCommandDrainStatus", StringComparison.Ordinal) || - acceptanceStatus.Contains("aetheria.eve_command_drain_status", StringComparison.Ordinal) || - daemonHost.Contains("DrainEveCommandsAsync", StringComparison.Ordinal) || - daemonHost.Contains("PutEveCommandDrainStatusAsync", StringComparison.Ordinal) || - daemonHost.Contains("GetEveCommandDrainStatusAsync", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Eve command acceptance regressed to drain/queue vocabulary; the daemon should accept observed typed command records."); - } - - if (acceptanceStatus.Contains("public int PendingBeforeApply", StringComparison.Ordinal) && - !acceptanceStatus.Contains("[IgnoreMember]", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Eve command acceptance exposes the legacy PendingBeforeApply schema shim as serialized command state."); - } - - var pendingBeforeApplyUseSources = new Dictionary - { - ["Aetheria daemon host"] = daemonHost, - ["Aetheria Eve command bridge"] = commandBridge, - ["operations surface document"] = operationsSurfaceDocuments - }; - var pendingBeforeApplyUses = pendingBeforeApplyUseSources - .Where(source => source.Value.Contains("PendingBeforeApply", StringComparison.Ordinal)) - .Select(source => source.Key) - .ToArray(); - if (pendingBeforeApplyUses.Length > 0) - { - throw new InvalidOperationException( - "Live Eve command acceptance code still uses the legacy PendingBeforeApply shim instead of ObservedBeforeAccept: " + - string.Join(", ", pendingBeforeApplyUses)); - } - - if (!operationsSurfaceDocuments.Contains("Verse Host Commands", StringComparison.Ordinal) || - !operationsSurfaceDocuments.Contains("AppliedVerseHostCommands", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Operations surface no longer projects Verse-host Eve command counts."); - } - - if (!commandBridge.Contains("report.AcceptedVerseHostCommands++", StringComparison.Ordinal) || - !daemonHost.Contains("AppliedVerseHostCommands = report.AcceptedVerseHostCommands", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Daemon command acceptance no longer publishes Verse-host Eve command totals."); - } -} - -static void RequireTypedStatRecipeOperations(string root) -{ - var statRecipesPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeStatRecipes.cs"); - var legacyApplierPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeStatRecipeCommandApplier.cs"); - var catalogStorePath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeCatalogStore.cs"); - var testsPath = Path.Combine(root, "Assets", "Scripts", "Tests", "StatRecipeSurfaceCommandTests.cs"); - - if (!File.Exists(statRecipesPath)) - throw new InvalidOperationException("Typed stat recipe operations are missing."); - if (File.Exists(legacyApplierPath)) - throw new InvalidOperationException("Legacy stat recipe command applier still exists."); - if (!File.Exists(catalogStorePath) || !File.Exists(testsPath)) - throw new InvalidOperationException("Cannot verify typed stat recipe operations; required files are missing."); - - var statRecipes = File.ReadAllText(statRecipesPath); - var catalogStore = File.ReadAllText(catalogStorePath); - var tests = File.ReadAllText(testsPath); - - var requiredTypedSymbols = new[] - { - "public static class AetheriaRuntimeStatRecipes", - "public static AetheriaRuntimeStatRecipeSurfaceState Refresh(", - "public static AetheriaRuntimeStatRecipeSurfaceState SelectStat(", - "public static AetheriaRuntimeStatRecipeSurfaceState AddStat(", - "public static AetheriaRuntimeStatRecipeSurfaceState RemoveStat(", - "public static AetheriaRuntimeStatRecipeSurfaceState SetStatName(", - "public static AetheriaRuntimeStatRecipeSurfaceState SetBaseValue(", - "public static AetheriaRuntimeStatRecipeSurfaceState SetConditionEnabled(", - "public static AetheriaRuntimeStatRecipeSurfaceState ToggleCondition(", - "public static AetheriaRuntimeStatRecipeSurfaceState CycleInfluenceOperation(", - "public static AetheriaRuntimeStatRecipeSurfaceState SetInfluenceAmount(", - "public static AetheriaRuntimeStatRecipeSurfaceState SetInfluenceCurve(", - "public static AetheriaRuntimeStatRecipeSurfaceState SetPreviewCondition(" - }; - var missingTypedSymbols = requiredTypedSymbols - .Where(symbol => !statRecipes.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (missingTypedSymbols.Length > 0) - { - throw new InvalidOperationException( - "Typed stat recipe operations are incomplete: " + - string.Join(", ", missingTypedSymbols)); - } - - var forbiddenPublicStringlySymbols = new[] - { - "public static AetheriaRuntimeStatRecipeSurfaceState Apply", - "public static AetheriaRuntimeStatRecipeSurfaceState ApplyCommand", - "public static class AetheriaRuntimeStatRecipeDaemonOperations", - "public static AetheriaRuntimeStatRecipeSurfaceState", - "IReadOnlyDictionary? payload" - }; - var publicStringlyHits = forbiddenPublicStringlySymbols - .Where(symbol => - symbol == "public static AetheriaRuntimeStatRecipeSurfaceState" - ? statRecipes.Contains("public static AetheriaRuntimeStatRecipeSurfaceState Apply(", StringComparison.Ordinal) - : statRecipes.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (publicStringlyHits.Length > 0) - { - throw new InvalidOperationException( - "Stat recipe client API still exposes stringly command/payload operations: " + - string.Join(", ", publicStringlyHits)); - } - - var forbiddenCatalogDecoderSymbols = new[] - { - "DrainCommandsStatRecipeCommand(", - "AetheriaRuntimeStatRecipes.ApplyCommand(", - "ReadPayloadDouble(", - "ReadPayloadBool(" - }; - var catalogDecoderHits = forbiddenCatalogDecoderSymbols - .Where(symbol => catalogStore.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (catalogDecoderHits.Length > 0) - { - throw new InvalidOperationException( - "Catalog store still replays stringly stat recipe command payloads: " + - string.Join(", ", catalogDecoderHits)); - } - - if (tests.Contains("AetheriaRuntimeStatRecipeDaemonOperations", StringComparison.Ordinal) || - tests.Contains("AetheriaRuntimeStatRecipeCommands.", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Stat recipe tests still exercise string command constants instead of typed operations."); - } -} - -static void RequireTypedDaemonCommandPayloads(string root) -{ - var daemonDocumentsPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeDaemonDocuments.cs"); - var daemonClientPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeDaemonOperationClient.cs"); - var daemonRuntimeOperationsPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeDaemonOperations.cs"); - var legacyDaemonApplierPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeDaemonCommandApplier.cs"); - var stateNodePath = Path.Combine(root, "Aetheria.State", "AetheriaStateNode.cs"); - var observerPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "AetheriaDaemonObserver.cs"); - var actionGameManagerPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "ActionGameManager.cs"); - var testsPath = Path.Combine(root, "Assets", "Scripts", "Tests", "DaemonRuntimeDocumentTests.cs"); - var daemonSurfaceCommandsPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeDaemonSurfaceCommands.cs"); - var starbridgeDocumentsPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeStarbridgeDocuments.cs"); - - if (File.Exists(legacyDaemonApplierPath)) - throw new InvalidOperationException("Legacy daemon command applier still exists; daemon operations must own execution."); - - var requiredFiles = new[] { daemonDocumentsPath, daemonClientPath, daemonRuntimeOperationsPath, stateNodePath, observerPath, actionGameManagerPath, testsPath, daemonSurfaceCommandsPath, starbridgeDocumentsPath }; - var missingFiles = requiredFiles - .Where(path => !File.Exists(path)) - .Select(path => Path.GetRelativePath(root, path)) - .ToArray(); - if (missingFiles.Length > 0) - { - throw new InvalidOperationException( - "Typed daemon command payloads cannot be verified because required files are missing: " + - string.Join(", ", missingFiles)); - } - - var daemonDocuments = File.ReadAllText(daemonDocumentsPath); - var daemonClient = File.ReadAllText(daemonClientPath); - var daemonOperationsSource = File.ReadAllText(daemonRuntimeOperationsPath); - var stateNode = File.ReadAllText(stateNodePath); - var observer = File.ReadAllText(observerPath); - var actionGameManager = File.ReadAllText(actionGameManagerPath); - var tests = File.ReadAllText(testsPath); - var daemonSurfaceCommands = File.ReadAllText(daemonSurfaceCommandsPath); - var starbridgeDocuments = File.ReadAllText(starbridgeDocumentsPath); - - if (daemonOperationsSource.Contains("private static string BuildEntityKey(", StringComparison.Ordinal) || - daemonOperationsSource.Contains("private static bool TryParseEntityKey(", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Daemon operations reintroduced local entity key parsing/building; use AetheriaRuntimeRunCheckpointCommit entity key helpers."); - } - - if (starbridgeDocuments.Contains("private static string BuildEntityKey(", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Starbridge documents reintroduced local entity key building; use AetheriaRuntimeRunCheckpointCommit entity key helpers."); - } - - var requiredPayloadTypes = new[] - { - "public enum AetheriaRuntimeDaemonCommandKinds", - "public sealed class AetheriaRuntimeCargoTransferCommand", - "public sealed class AetheriaRuntimeTradePurchaseCommand", - "public sealed class AetheriaRuntimeLoadoutRestoreCommand", - "public sealed class AetheriaRuntimeEquipmentTransferCommand", - "public sealed class AetheriaRuntimeStoreItemCommand" - }; - var missingPayloadTypes = requiredPayloadTypes - .Where(symbol => !daemonDocuments.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (missingPayloadTypes.Length > 0) - { - throw new InvalidOperationException( - "Daemon command document is missing typed command payloads: " + - string.Join(", ", missingPayloadTypes)); - } - - if (!daemonDocuments.Contains("public sealed class AetheriaRuntimeLootPickupCommand", StringComparison.Ordinal) || - !daemonDocuments.Contains("public AetheriaRuntimeLootPickupCommand LootPickup { get; set; }", StringComparison.Ordinal) || - !daemonDocuments.Contains("Serialization tombstone", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "The retired loot-pickup payload must remain only as an explicit serialization tombstone for persisted command compatibility."); - } - - var forbiddenRetiredCommandOpinionUses = new[] - { - ".LootPickup.", - "purchase.PurchaseKind", - "purchase.UnitPrice", - "purchase.TotalPrice", - "purchase.StationEntityKey", - "purchase.TargetEntityKey", - "purchase.CreatesDockedShip", - "command.TradePurchase.PurchaseKind", - "command.TradePurchase.UnitPrice", - "command.TradePurchase.TotalPrice", - "command.TradePurchase.StationEntityKey", - "command.TradePurchase.TargetEntityKey", - "command.TradePurchase.CreatesDockedShip" - }; - var liveCommandImplementation = daemonClient + "\n" + daemonOperationsSource; - var survivingRetiredCommandOpinionUses = forbiddenRetiredCommandOpinionUses - .Where(symbol => liveCommandImplementation.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (survivingRetiredCommandOpinionUses.Length > 0) - { - throw new InvalidOperationException( - "Retired compatibility fields still influence live loot or trade command behavior: " + - string.Join(", ", survivingRetiredCommandOpinionUses)); - } - - if (daemonDocuments.Contains("public sealed class AetheriaRuntimeActionBarBindingCommand", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Action-bar input mappings are Unity-local and must not be daemon command payloads."); - } - - var liveSources = new Dictionary - { - ["daemon documents"] = daemonDocuments, - ["daemon operation client"] = daemonClient, - ["daemon operations"] = daemonOperationsSource, - ["state node"] = stateNode, - ["Unity daemon observer"] = observer, - ["ActionGameManager"] = actionGameManager, - ["daemon surface commands"] = daemonSurfaceCommands - }; - foreach (var source in liveSources) - { - var forbidden = new[] - { - "Dictionary Payload", - ".Payload[", - "ReadPayloadInt(", - "ReadPayloadDouble(", - "ReadPayloadBool(", - "Payload(command," - } - .Where(symbol => source.Value.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (forbidden.Length > 0) - { - throw new InvalidOperationException( - $"{source.Key} still uses stringly daemon command payloads: " + - string.Join(", ", forbidden)); - } - } - - var forbiddenDaemonKindSymbols = new[] - { - "public static class AetheriaRuntimeDaemonCommandKinds", - "public const string SetTarget", - "Queue(\r\n string kind", - "Create(\r\n string kind" - }; - var daemonKindSources = new Dictionary - { - ["daemon documents"] = daemonDocuments, - ["daemon operation client"] = daemonClient, - ["Unity daemon observer"] = observer - }; - foreach (var source in daemonKindSources) - { - var forbidden = forbiddenDaemonKindSymbols - .Where(symbol => source.Value.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (forbidden.Length > 0) - { - throw new InvalidOperationException( - $"{source.Key} still exposes daemon command kind as strings: " + - string.Join(", ", forbidden)); - } - } - - if (!daemonDocuments.Contains("public AetheriaRuntimeDaemonCommandKinds Kind { get; set; }", StringComparison.Ordinal) || - !daemonDocuments.Contains("public AetheriaRuntimeDaemonCommandKinds Kind { get; }", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Daemon command document/envelope must expose typed enum command kinds."); - } - - var requiredClientSymbols = new[] - { - "public const string DefaultClientId = \"aetheria-daemon-client\"", - "ClientId = string.IsNullOrWhiteSpace(clientId) ? DefaultClientId : clientId", - "private AetheriaRuntimeDaemonCommandEnvelope Send(AetheriaRuntimeDaemonCommandDocument command)", - "Func submit", - "return Submit((client, frame) => client.SetTarget(frame, targetEntityKey));", - "return Send((client, frame) => client.TransferCargoItem(", - "command.CargoTransfer.OriginEntityKey", - "ReadPayloadString(request, \"itemKey\", \"\")", - "command.LoadoutRestore.TemplateName", - "command.EquipmentTransfer.SourceKind", - "command.StoreItem.SourceEquipmentIndex" - }; - var daemonOperationsClientPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeDaemonOperationsClient.cs"); - var daemonOperationsClient = File.Exists(daemonOperationsClientPath) - ? File.ReadAllText(daemonOperationsClientPath) - : throw new InvalidOperationException("Shared daemon operations client is missing from the runtime package."); - var requiredDaemonOperationsFacadeSymbols = new[] - { - "using GameCult.Mesh;", - "public AetheriaRuntimeDaemonCommandEnvelope SetTarget(string targetEntityKey)", - "public AetheriaRuntimeDaemonCommandEnvelope SetMoveVector(", - "public CultMeshOperationReceipt TransferCargoItem(", - "private AetheriaRuntimeDaemonCommandEnvelope Submit(", - "private CultMeshOperationReceipt Send(" - }; - var missingDaemonOperationsFacadeSymbols = requiredDaemonOperationsFacadeSymbols - .Where(symbol => !daemonOperationsClient.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (missingDaemonOperationsFacadeSymbols.Length > 0) - { - throw new InvalidOperationException( - "Aetheria runtime operations facade no longer exposes shared CultMesh receipts: " + - string.Join(", ", missingDaemonOperationsFacadeSymbols)); - } - - var forbiddenPublicEnvelopeSymbols = new[] - { - "public AetheriaRuntimeDaemonCommandEnvelope ClearTarget(", - "public AetheriaRuntimeDaemonCommandEnvelope TransferCargoItem(", - "public AetheriaRuntimeDaemonCommandEnvelope SetLookDirection(", - "public AetheriaRuntimeDaemonCommandEnvelope SetTractorPower(" - }; - var publicEnvelopeHits = forbiddenPublicEnvelopeSymbols - .Where(symbol => daemonOperationsClient.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (publicEnvelopeHits.Length > 0) - { - throw new InvalidOperationException( - "Aetheria runtime operations facade exposes command envelopes beyond the typed identity-sensitive command path: " + - string.Join(", ", publicEnvelopeHits)); - } - - var legacyUnityOperationsPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "AetheriaDaemonOperations.cs"); - if (File.Exists(legacyUnityOperationsPath)) - { - throw new InvalidOperationException( - "Aetheria daemon operation facade still lives under Unity gameplay; move shared client operations into the runtime package."); - } - - var clientAndObserver = daemonClient + "\n" + observer + "\n" + stateNode + "\n" + daemonOperationsClient; - var missingClientSymbols = requiredClientSymbols - .Where(symbol => !clientAndObserver.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (missingClientSymbols.Length > 0) - { - throw new InvalidOperationException( - "Daemon command producers are not filling typed payloads: " + - string.Join(", ", missingClientSymbols)); - } - - if (clientAndObserver.Contains("ActionBarBinding", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Daemon command producers still expose Unity-local action-bar input bindings."); - } - - var forbiddenQueueSymbols = new[] - { - "public static class AetheriaRuntimeDaemonCommandLog", - "internal static class AetheriaRuntimeDaemonCommandLog", - "public AetheriaRuntimeDaemonCommandEnvelope Queue(", - "public static AetheriaRuntimeDaemonCommandEnvelope QueueCommand(", - "AetheriaRuntimeDaemonCommandLog.QueueCommand(", - "AetheriaRuntimeDaemonCommandLog.", - "AetheriaRuntimeDaemonCommandInbox", - "ReadPending(", - ".daemon.pending", - "stateFilePath + \".daemon.commands\"", - "_operationClient.Queue(" - }; - var daemonQueueSources = new Dictionary - { - ["daemon operation client"] = daemonClient, - ["state node"] = stateNode, - ["Unity daemon observer"] = observer, - ["ActionGameManager"] = actionGameManager, - ["daemon command tests"] = tests - }; - var survivingQueueSymbols = daemonQueueSources - .SelectMany(source => forbiddenQueueSymbols - .Where(symbol => source.Value.Contains(symbol, StringComparison.Ordinal)) - .Select(symbol => $"{source.Key}: {symbol}")) - .ToArray(); - - if (survivingQueueSymbols.Length > 0 || - observer.Contains("_operationClient.Queue(", StringComparison.Ordinal) || - daemonClient.Contains("AetheriaRuntimeDaemonCommandLog.", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Unity-facing daemon operation clients still expose queue semantics instead of sending typed daemon operations: " + - string.Join(", ", survivingQueueSymbols)); - } - - var forbiddenUnityDocumentSubmitSymbols = new[] - { - "SendOperation(AetheriaRuntimeDaemonCommandKinds", - "Action", - "_operationClient.Create(", - "_operationClient.TrySend(command", - "command.TargetEntityKey =", - "command.ActionBarBinding.", - "command.CargoTransfer.", - "command.TradePurchase.", - "command.LootPickup.", - "command.LoadoutRestore.", - "command.EquipmentTransfer.", - "command.StoreItem." - }; - var unityDocumentSubmitHits = new Dictionary - { - ["Unity daemon observer"] = observer - } - .SelectMany(source => forbiddenUnityDocumentSubmitSymbols - .Where(symbol => source.Value.Contains(symbol, StringComparison.Ordinal)) - .Select(symbol => $"{source.Key}: {symbol}")) - .ToArray(); - if (unityDocumentSubmitHits.Length > 0) - { - throw new InvalidOperationException( - "Unity daemon operation controllers still fill command documents instead of delegating to typed runtime operations: " + - string.Join(", ", unityDocumentSubmitHits)); - } - - var forbiddenPublicDocumentSubmitSymbols = new[] - { - "public AetheriaRuntimeDaemonCommandEnvelope Send(", - "public bool TrySend(\r\n AetheriaRuntimeDaemonCommandKinds", - "public bool TrySend(\r\n AetheriaRuntimeDaemonCommandDocument", - "public AetheriaRuntimeDaemonCommandDocument Create(" - }; - var publicDocumentSubmitHits = forbiddenPublicDocumentSubmitSymbols - .Where(symbol => daemonClient.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (publicDocumentSubmitHits.Length > 0) - { - throw new InvalidOperationException( - "Daemon operation client still exposes public document-shaped submission APIs instead of typed operations: " + - string.Join(", ", publicDocumentSubmitHits)); - } - - var forbiddenClientDocumentBuilderSymbols = new[] - { - "TrySendCommandKind(", - "Action", - "configure?.Invoke(command)", - "Send(AetheriaRuntimeDaemonCommandKinds" - }; - var clientDocumentBuilderHits = forbiddenClientDocumentBuilderSymbols - .Where(symbol => daemonClient.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (clientDocumentBuilderHits.Length > 0) - { - throw new InvalidOperationException( - "Daemon operation client still routes typed operations through mutable document builder callbacks: " + - string.Join(", ", clientDocumentBuilderHits)); - } - - var forbiddenSharedDaemonClientDefaults = new[] - { - "string clientId = \"unity-observer\"", - "? \"unity-observer\" :", - "? \"unity-uitoolkit\" : request.ClientId" - }; - var sharedDaemonDefaultSources = new Dictionary - { - ["daemon operation client"] = daemonClient, - ["daemon surface commands"] = daemonSurfaceCommands - }; - var sharedDaemonDefaultHits = sharedDaemonDefaultSources - .SelectMany(source => forbiddenSharedDaemonClientDefaults - .Where(symbol => source.Value.Contains(symbol, StringComparison.Ordinal)) - .Select(symbol => $"{source.Key}: {symbol}")) - .ToArray(); - if (sharedDaemonDefaultHits.Length > 0) - { - throw new InvalidOperationException( - "Shared daemon command clients still use Unity-specific fallback client ids: " + - string.Join(", ", sharedDaemonDefaultHits)); - } - - if (tests.Contains("AetheriaRuntimeDaemonCommandLog.", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Daemon command tests still reach through the old command log instead of the typed command client/inbox."); - } - - if (actionGameManager.Contains("local mutation suppressed", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "ActionGameManager still treats failed daemon submission as a local-mutation fallback."); - } - - var failedSubmitSuccessHits = System.Text.RegularExpressions.Regex - .Matches( - actionGameManager, - "Failed to send Aetheria daemon[\\s\\S]*?return true;", - System.Text.RegularExpressions.RegexOptions.Multiline) - .Cast() - .Where(match => !match.Value.Contains("return false;", StringComparison.Ordinal)) - .Select(match => match.Value.Split('\n').FirstOrDefault()?.Trim() ?? "daemon submit catch") - .Take(5) - .ToArray(); - if (failedSubmitSuccessHits.Length > 0) - { - throw new InvalidOperationException( - "ActionGameManager reports failed daemon submissions as successful commands: " + - string.Join("; ", failedSubmitSuccessHits)); - } -} - -static void RequireUnityPublicRequestVocabulary(string root) -{ - var checkedRoots = new[] - { - Path.Combine(root, "Assets", "Scripts", "Gameplay"), - Path.Combine(root, "Assets", "Scripts", "UI") - }; - - var commitPattern = new System.Text.RegularExpressions.Regex( - "\\bCommit[A-Z][A-Za-z0-9_]*\\b", - System.Text.RegularExpressions.RegexOptions.Compiled); - var hits = checkedRoots - .Where(Directory.Exists) - .SelectMany(path => Directory.EnumerateFiles(path, "*.cs", SearchOption.AllDirectories)) - .Where(path => !path.Contains(Path.Combine("ServerShared", "NIH"), StringComparison.Ordinal)) - .SelectMany(path => File.ReadLines(path) - .Select((line, index) => new { Path = path, LineNumber = index + 1, Line = line })) - .SelectMany(line => commitPattern.Matches(line.Line) - .Cast() - .Select(match => $"{Path.GetRelativePath(root, line.Path)}:{line.LineNumber}: {match.Value}")) - .Take(20) - .ToArray(); - - if (hits.Length > 0) - { - throw new InvalidOperationException( - "Unity-facing Aetheria APIs must say Request/Submit, not Commit; Unity is an input provider, not state authority: " + - string.Join("; ", hits)); - } -} - -static void RequireAuthoritySmokeUsesManagedPointers(string root) -{ - var authoritySmokePath = Path.Combine(root, "Aetheria.State.AuthoritySmoke", "Program.cs"); - if (!File.Exists(authoritySmokePath)) - { - throw new InvalidOperationException("Authority smoke source is missing."); - } - - var authoritySmoke = File.ReadAllText(authoritySmokePath); - var requiredSymbols = new[] - { - "writer.MutableDocument(AetheriaRuntimeVerseRecordKeys.VerseAuthorityPolicy)", - "writer.MutableDocument(AetheriaRuntimeVerseRecordKeys.StarbridgeScenarioLatest)", - "writer.MutableDocument(AetheriaRuntimeVerseRecordKeys.StarbridgeSessionLatest)", - "ravenNode.MutableDocument(AetheriaRuntimeVerseRecordKeys.VerseAuthorityPolicy)", - "commanderNode.MutableDocument(AetheriaRuntimeVerseRecordKeys.VerseAuthorityPolicy)", - "node.MutableDocument(AetheriaRuntimeVerseRecordKeys.VerseAuthorityPolicy)", - "node.MutableDocument(AetheriaRuntimeVerseRecordKeys.DaemonFrameLatest)", - ".ReplaceAsync(policy)", - ".ReplaceAsync(scenario)", - ".ReplaceAsync(session)", - ".ReadAsync().ConfigureAwait(false)" - }; - var missingSymbols = requiredSymbols - .Where(symbol => !authoritySmoke.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (missingSymbols.Length > 0) - { - throw new InvalidOperationException( - "Authority smoke no longer exercises managed Verse state pointers: " + - string.Join(", ", missingSymbols)); - } - - var forbiddenSymbols = new[] - { - "PutStarbridgeScenarioAsync(", - "PutStarbridgeSessionAsync(", - "PutVerseAuthorityPolicyAsync(", - "GetVerseAuthorityPolicyAsync(", - "GetDaemonFrameAsync(", - "writer.StarbridgeScenario()", - "writer.StarbridgeSession()", - "ravenNode.VerseAuthorityPolicy()", - "commanderNode.VerseAuthorityPolicy()", - "node.VerseAuthorityPolicy()", - "node.LatestFrame()" - }; - var survivingSymbols = forbiddenSymbols - .Where(symbol => authoritySmoke.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (survivingSymbols.Length > 0) - { - throw new InvalidOperationException( - "Authority smoke still teaches compatibility helper access instead of managed Verse pointers: " + - string.Join(", ", survivingSymbols)); - } -} - -static void RequireAetheriaStateNodeUsesManagedPointers(string root) -{ - var stateNodePath = Path.Combine(root, "Aetheria.State", "AetheriaStateNode.cs"); - if (!File.Exists(stateNodePath)) - { - throw new InvalidOperationException("AetheriaStateNode source is missing."); - } - - var stateNode = File.ReadAllText(stateNodePath); - var requiredSymbols = new[] - { - "public static CultRecordKey WorldKey { get; }", - "public static CultRecordKey MigrationLedgerKey { get; }", - "public static CultRecordKey LegacyCatalogQuarantineKey { get; }", - "public CultMeshMutableStatePointer MutableDocument(CultRecordKey key)", - "public IReadOnlyList Documents()", - "private CultMeshMutableStatePointer MutableDocumentPointer(CultRecordKey key)", - "CultMesh.MutableStatePointer(" - }; - var missingSymbols = requiredSymbols - .Where(symbol => !stateNode.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (missingSymbols.Length > 0) - { - throw new InvalidOperationException( - "AetheriaStateNode no longer exposes managed typed state pointers: " + - string.Join(", ", missingSymbols)); - } - - var forbiddenSymbols = new[] - { - "public Task> PutWorldAsync(", - "public Task GetWorldAsync(", - "public Task> PutMigrationLedgerAsync(", - "public Task GetMigrationLedgerAsync(", - "public Task> PutItemDefinitionAsync(", - "public Task GetItemDefinitionAsync(", - "public Task GetItemDefinitionByLegacyIdAsync(", - "public Task> PutCorporationAsync(", - "public Task GetCorporationAsync(", - "public Task> PutNameFileAsync(", - "public Task GetNameFileAsync(", - "public Task> PutTradeValuePolicyAsync(", - "public Task GetTradeValuePolicyAsync(", - "public Task> PutCatalogSurfaceAsync(", - "public Task GetCatalogSurfaceAsync(", - "public Task> PutProviderAdvertisementAsync(", - "public Task GetProviderAdvertisementAsync(", - "public Task> PutDaemonProviderAdvertisementAsync(", - "public Task GetDaemonProviderAdvertisementAsync(", - "public Task> PutVerseAuthorityPolicyAsync(", - "public Task GetVerseAuthorityPolicyAsync(", - "public Task> PutDaemonFrameAsync(", - "public Task GetDaemonFrameAsync(", - "public Task> PutDaemonSoaViewAsync(", - "public Task GetDaemonSoaViewAsync(", - "public Task> PutStarbridgeScenarioAsync(", - "public Task GetStarbridgeScenarioAsync(", - "public Task> PutDaemonGameSurfaceAsync(", - "public Task GetDaemonGameSurfaceAsync(", - "public Task> PutRuntimeSessionAsync(", - "public Task GetRuntimeSessionAsync(", - "public Task> PutPlayerSettingsAsync(", - "public Task GetPlayerSettingsAsync(", - "public Task> PutRunStateAsync(", - "public Task GetRunStateAsync(", - "public Task> PutEntitySnapshotAsync(", - "public Task GetEntitySnapshotAsync(", - "public Task> PutVerseHostSettingsAsync(", - "public Task GetVerseHostSettingsAsync(", - "public CultMeshMutableStatePointer World()", - "public CultMeshMutableStatePointer MigrationLedger()", - "public CultMeshMutableStatePointer LegacyCatalogQuarantine()", - "public CultMeshMutableStatePointer ItemDefinition(", - "public CultMeshMutableStatePointer ItemDefinitionByLegacyId(", - "public CultMeshMutableStatePointer Corporation(", - "public CultMeshMutableStatePointer CorporationByLegacyId(", - "public CultMeshMutableStatePointer NameFile(", - "public CultMeshMutableStatePointer NameFileByLegacyId(", - "public CultMeshMutableStatePointer LoadoutTemplate(", - "public CultMeshMutableStatePointer RunState(", - "public CultMeshMutableStatePointer ZoneState(", - "public CultMeshMutableStatePointer EntitySnapshot(", - "public CultMeshMutableStatePointer ProviderAdvertisement()", - "public CultMeshMutableStatePointer Health()", - "public CultMeshMutableStatePointer CommandBoundary()", - "public CultMeshMutableStatePointer VerseAuthorityPolicy()", - "public CultMeshMutableStatePointer LatestFrame()", - "public CultMeshMutableStatePointer LatestSoaView()", - "public CultMeshMutableStatePointer StarbridgeScenario()", - "public CultMeshMutableStatePointer StarbridgeSession()", - "public CultMeshMutableStatePointer StarbridgeSessionSummary()", - "public CultMeshMutableStatePointer DaemonGameSurface()", - "public CultMeshMutableStatePointer DaemonGameTuiSurface()", - "public CultMeshMutableStatePointer DaemonEditorSurface()", - "public CultMeshMutableStatePointer DaemonEditorTuiSurface()", - "ReadObservedDaemonCommands(", - "ReadCommittedCommandFacts(", - "ReadAuthorityLeases(", - "ReadObservedEveCommands(" - }; - var survivingSymbols = forbiddenSymbols - .Where(symbol => stateNode.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (survivingSymbols.Length > 0) - { - throw new InvalidOperationException( - "AetheriaStateNode still exposes compatibility document Get/Put helpers instead of managed typed pointers: " + - string.Join(", ", survivingSymbols)); - } -} - -static void RequireDaemonVersePublication(string root) -{ - var daemonDocumentsPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeDaemonDocuments.cs"); - var daemonTickRunnerPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeDaemonTickRunner.cs"); - var daemonSoaDocumentsPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeDaemonSoaDocuments.cs"); - var daemonSoaFramePublisherPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeDaemonSoaFramePublisher.cs"); - var eveSoaProjectionPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeEveEntitySoaProjection.cs"); - var assetDocumentsPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeAssetDocuments.cs"); - var runtimeAssetsPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeAssets.cs"); - var runtimeGameDocumentsPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeGameDocuments.cs"); - var daemonRenderAssetCatalogPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "AetheriaDaemonRenderAssetCatalog.cs"); - var daemonIndirectRendererPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "AetheriaDaemonIndirectRenderer.cs"); - var daemonStateRefsPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeDaemonStateRefs.cs"); - var daemonGameSurfaceBuilderPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeDaemonGameSurfaceBuilder.cs"); - var daemonEditorSurfaceBuilderPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeDaemonEditorSurfaceBuilder.cs"); - var statRecipeSurfaceBuilderPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeStatRecipeSurfaceBuilder.cs"); - var tradeValuePolicySurfaceBuilderPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeTradeValuePolicySurfaceBuilder.cs"); - var runtimeCatalogStorePath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeCatalogStore.cs"); - var daemonSurfaceCommandCatalogPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeDaemonSurfaceCommandCatalog.cs"); - var committedFactImporterPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeCommittedFactImporter.cs"); - var daemonHostProjectPath = Path.Combine(root, "Aetheria.State.Daemon", "Aetheria.State.Daemon.csproj"); - var daemonHostProgramPath = Path.Combine(root, "Aetheria.State.Daemon", "Program.cs"); - var daemonZoneGeneratorPath = Path.Combine(root, "Aetheria.State.Daemon", "AetheriaDaemonZoneGenerator.cs"); - var daemonLoadoutGeneratorPath = Path.Combine(root, "Aetheria.State.Daemon", "AetheriaDaemonLoadoutGenerator.cs"); - var documentRegistryPath = Path.Combine(root, "Aetheria.State", "AetheriaDocumentRegistry.cs"); - var stateNodePath = Path.Combine(root, "Aetheria.State", "AetheriaStateNode.cs"); - var runtimeSurfaceDocumentsPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeSurfaceDocuments.cs"); - var providerAdvertisementPath = Path.Combine(root, "Aetheria.State", "AetheriaEveSurfaceDocuments.cs"); - var legacyProviderAdvertisementPath = Path.Combine(root, "Aetheria.State", "Documents", "EveProviderAdvertisementState.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"); - var testsPath = Path.Combine(root, "Assets", "Scripts", "Tests", "DaemonRuntimeDocumentTests.cs"); - var smokePath = Path.Combine(root, "Aetheria.State.Smoke", "Program.cs"); - var notePath = Path.Combine(root, "Aetheria.State", "docs", "verse-daemon-shape.md"); - - var requiredFiles = new[] - { - daemonDocumentsPath, - daemonTickRunnerPath, - daemonSoaDocumentsPath, - daemonSoaFramePublisherPath, - eveSoaProjectionPath, - assetDocumentsPath, - runtimeAssetsPath, - runtimeGameDocumentsPath, - daemonRenderAssetCatalogPath, - daemonIndirectRendererPath, - daemonStateRefsPath, - daemonGameSurfaceBuilderPath, - daemonEditorSurfaceBuilderPath, - statRecipeSurfaceBuilderPath, - tradeValuePolicySurfaceBuilderPath, - runtimeCatalogStorePath, - daemonSurfaceCommandCatalogPath, - committedFactImporterPath, - daemonHostProjectPath, - daemonHostProgramPath, - daemonZoneGeneratorPath, - daemonLoadoutGeneratorPath, - documentRegistryPath, - stateNodePath, - runtimeSurfaceDocumentsPath, - providerAdvertisementPath, - documentStorePath, - boundaryPath, - testsPath, - smokePath, - notePath - }; - var missingFiles = requiredFiles - .Where(path => !File.Exists(path)) - .Select(path => Path.GetRelativePath(root, path)) - .ToArray(); - if (missingFiles.Length > 0) - { - throw new InvalidOperationException( - "Aetheria daemon Verse publication cannot be verified because required files are missing: " + - string.Join(", ", missingFiles)); - } - - var daemonDocuments = File.ReadAllText(daemonDocumentsPath); - var daemonTickRunner = File.ReadAllText(daemonTickRunnerPath); - var daemonSoaDocuments = File.ReadAllText(daemonSoaDocumentsPath); - var daemonSoaFramePublisher = File.ReadAllText(daemonSoaFramePublisherPath); - var eveSoaProjection = File.ReadAllText(eveSoaProjectionPath); - var assetDocuments = File.ReadAllText(assetDocumentsPath); - var runtimeAssets = File.ReadAllText(runtimeAssetsPath); - var runtimeGameDocuments = File.ReadAllText(runtimeGameDocumentsPath); - var daemonRenderAssetCatalog = File.ReadAllText(daemonRenderAssetCatalogPath); - var daemonIndirectRenderer = File.ReadAllText(daemonIndirectRendererPath); - var daemonStateRefs = File.ReadAllText(daemonStateRefsPath); - var daemonGameSurfaceBuilder = File.ReadAllText(daemonGameSurfaceBuilderPath); - var daemonEditorSurfaceBuilder = File.ReadAllText(daemonEditorSurfaceBuilderPath); - var statRecipeSurfaceBuilder = File.ReadAllText(statRecipeSurfaceBuilderPath); - var tradeValuePolicySurfaceBuilder = File.ReadAllText(tradeValuePolicySurfaceBuilderPath); - var runtimeCatalogStore = File.ReadAllText(runtimeCatalogStorePath); - var daemonSurfaceCommandCatalog = File.ReadAllText(daemonSurfaceCommandCatalogPath); - var committedFactImporter = File.ReadAllText(committedFactImporterPath); - var daemonHostProject = File.ReadAllText(daemonHostProjectPath); - var daemonHostProgram = File.ReadAllText(daemonHostProgramPath); - var daemonZoneGenerator = File.ReadAllText(daemonZoneGeneratorPath); - var daemonLoadoutGenerator = File.ReadAllText(daemonLoadoutGeneratorPath); - var documentRegistry = File.ReadAllText(documentRegistryPath); - var stateNode = File.ReadAllText(stateNodePath); - var runtimeSurfaceDocuments = File.ReadAllText(runtimeSurfaceDocumentsPath); - var providerAdvertisement = File.ReadAllText(providerAdvertisementPath); - var documentStore = File.ReadAllText(documentStorePath); - var boundary = File.ReadAllText(boundaryPath); - var tests = File.ReadAllText(testsPath); - var smoke = File.ReadAllText(smokePath); - var note = File.ReadAllText(notePath); - - var requiredDocumentSymbols = new[] - { - "public const string ProviderAdvertisement = \"gamecult.aetheria.daemon_provider_advertisement.v1\"", - "public const string Health = \"gamecult.aetheria.daemon_health.v1\"", - "public const string CommandBoundary = \"gamecult.aetheria.daemon_command_boundary.v1\"", - "public const string GameSurface = \"gamecult.aetheria.daemon_game_surface.v1\"", - "public const string EditorSurface = \"gamecult.aetheria.daemon_editor_surface.v1\"", - "public sealed class AetheriaRuntimeDaemonProviderAdvertisementDocument", - "public sealed class AetheriaRuntimeDaemonHealthDocument", - "public sealed class AetheriaRuntimeDaemonCommandBoundaryDocument", - "public sealed class AetheriaRuntimeDaemonCommandBoundaryEntry", - "AetheriaRuntimeDaemonCommandKinds Kind", - "AetheriaRuntimeCargoTransferCommand", - "[CultDocument(\"gamecult.aetheria.daemon_provider_advertisement\", \"gamecult.aetheria.daemon_provider_advertisement.v1\")]", - "[CultDocument(\"gamecult.aetheria.daemon_health\", \"gamecult.aetheria.daemon_health.v1\")]", - "[CultDocument(\"gamecult.aetheria.daemon_command_boundary\", \"gamecult.aetheria.daemon_command_boundary.v1\")]", - "[CultDocument(\"gamecult.aetheria.daemon_frame\", \"gamecult.aetheria.daemon_frame.v1\")]", - "[CultDocument(\"gamecult.aetheria.daemon_command\", \"gamecult.aetheria.daemon_command.v1\")]", - "FrameRecordRef", - "SoaViewRecordRef", - "EveGuiSurfaceRecordRef", - "EveTuiSurfaceRecordRef", - "EditorGuiSurfaceRecordRef", - "EditorTuiSurfaceRecordRef" - }; - var missingDocumentSymbols = requiredDocumentSymbols - .Where(symbol => !daemonDocuments.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (missingDocumentSymbols.Length > 0) - { - throw new InvalidOperationException( - "Daemon Verse publication documents are incomplete: " + - string.Join(", ", missingDocumentSymbols)); - } - - var forbiddenDaemonProviderDocumentSymbols = new[] - { - "StateWitnessPath", - "FrameWitnessPath", - "SoaWitnessPath", - "HealthWitnessPath", - "CommandBoundaryWitnessPath", - "EveGuiSurfaceWitnessPath", - "EveTuiSurfaceWitnessPath", - "EditorGuiSurfaceWitnessPath", - "EditorTuiSurfaceWitnessPath", - "AssetManifestWitnessPath" - }; - var daemonProviderDocumentHits = forbiddenDaemonProviderDocumentSymbols - .Where(symbol => daemonDocuments.Contains(symbol, StringComparison.Ordinal) || - daemonEditorSurfaceBuilder.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (daemonProviderDocumentHits.Length > 0) - { - throw new InvalidOperationException( - "Daemon provider advertisement still exposes witness-path vocabulary instead of managed record refs: " + - string.Join(", ", daemonProviderDocumentHits)); - } - - if (!daemonSoaDocuments.Contains("[CultDocument(\"gamecult.aetheria.daemon_soa_view\", \"gamecult.aetheria.daemon_soa_view.v1\")]", StringComparison.Ordinal)) - { - throw new InvalidOperationException("Daemon SoA view is no longer a registered CultCache/CultNet document."); - } - - var requiredSoaFramePublisherSymbols = new[] - { - "public sealed class AetheriaRuntimeDaemonSoaFramePublisher : IDisposable", - "BuildCurrentZoneEntities(", - "new CultMeshFrameBodyPublisher(", - "new CultMeshBodyPublicationHandle(BodyId, publication.ProducerEpoch, publication.Sequence)", - ".Validate(publication)", - "view.Buffers[0].BufferId, publication.BodyId", - "view.Columns.Any(column => !string.Equals(column.BufferId, publication.BodyId", - "ObserverWritable = false", - "AetheriaRuntimeDaemonSoaColumnKinds.EntityIndex", - "AetheriaRuntimeDaemonSoaColumnKinds.Position", - "AetheriaRuntimeDaemonSoaColumnKinds.Velocity", - "AetheriaRuntimeDaemonSoaColumnKinds.PhysicsBodyRadius", - "AetheriaRuntimeDaemonSoaColumnKinds.RenderVisibility", - "AetheriaRuntimeDaemonSoaColumnKinds.RenderGroupId" - }; - var missingSoaFramePublisherSymbols = requiredSoaFramePublisherSymbols - .Where(symbol => !daemonSoaFramePublisher.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (missingSoaFramePublisherSymbols.Length > 0) - { - throw new InvalidOperationException( - "Daemon SoA frame publisher no longer turns authoritative frames into observer-readable direct-memory slabs: " + - string.Join(", ", missingSoaFramePublisherSymbols)); - } - var forbiddenSoaPublisherSymbols = new[] - { - "MemoryMappedFile", - "RetainedMappedBuffer", - "CreateLocation(", - "CultMeshNetworkBodyStore", - "PublishNetwork" - }; - var forbiddenSoaPublisherHits = forbiddenSoaPublisherSymbols - .Where(symbol => daemonSoaFramePublisher.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (forbiddenSoaPublisherHits.Length > 0) - { - throw new InvalidOperationException( - "Aetheria still owns SoA mapping location or retention instead of CultMesh: " + - string.Join(", ", forbiddenSoaPublisherHits)); - } - var forbiddenProjectionSymbols = new[] - { - "CultMeshBodyDescriptor", - "TransportKind", - "CapabilityToken", - "Backend =", - "Location =", - "StableEpoch(" - }; - var forbiddenProjectionHits = forbiddenProjectionSymbols - .Where(symbol => eveSoaProjection.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (forbiddenProjectionHits.Length > 0) - { - throw new InvalidOperationException( - "Eve SoA layout projection still owns transport material or producer epoch: " + - string.Join(", ", forbiddenProjectionHits)); - } - const string bodyPublicationWrite = - "CultMeshBodyPublicationDocument.CreateLatestRecordKey(publication.Body.BodyId)"; - if (!daemonHostProgram.Contains(bodyPublicationWrite, StringComparison.Ordinal) || - daemonHostProgram.IndexOf(bodyPublicationWrite, StringComparison.Ordinal) > - daemonHostProgram.IndexOf("MutableDocument(AetheriaRuntimeVerseRecordKeys.EveEntitySoaViewLatest)", StringComparison.Ordinal)) - { - throw new InvalidOperationException("CultMesh body publication must precede the Eve entity SoA layout record."); - } - if (!smoke.Contains("new CultMeshBodyPublicationHandle(", StringComparison.Ordinal) || - !smoke.Contains("published.View.ProducerEpoch", StringComparison.Ordinal) || - !smoke.Contains("published.View.Sequence", StringComparison.Ordinal) || - !smoke.Contains("CreateLatestRecordKey", StringComparison.Ordinal) || - !smoke.Contains("bodyHandle.Validate(body)", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "The Aetheria pipeline smoke must prove view N resolves its generation record while latest points at N+1."); - } - if (File.Exists(legacyProviderAdvertisementPath)) - { - throw new InvalidOperationException( - "The deleted Aetheria-specific EveProviderAdvertisementState writer has returned: " + - Path.GetRelativePath(root, legacyProviderAdvertisementPath)); - } - - if (assetDocuments.Contains("UnityAddressable", StringComparison.Ordinal) || - assetDocuments.Contains("unity_addressable", StringComparison.Ordinal) || - assetDocuments.Contains("public const string Resources", StringComparison.Ordinal) || - assetDocuments.Contains("\"resources\"", StringComparison.Ordinal) || - daemonSoaFramePublisher.Contains("AetheriaRuntimeAssetTransports.Resources", StringComparison.Ordinal) || - daemonSoaFramePublisher.Contains("resources://Aetheria/Daemon/EntityProxy", StringComparison.Ordinal) || - !daemonSoaFramePublisher.Contains("AetheriaRuntimeAssetTransports.CultMesh", StringComparison.Ordinal) || - !daemonSoaFramePublisher.Contains("cultmesh://aetheria/assets/daemon/entity_proxy/mesh", StringComparison.Ordinal) || - !daemonSoaFramePublisher.Contains("cultmesh://aetheria/assets/daemon/entity_proxy/material", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Daemon SoA render groups must publish CultMesh CDN asset refs, not renderer-local Resources or Unity Addressables transport."); - } - - if (runtimeAssets.Contains("sourceTransport", StringComparison.Ordinal) || - runtimeAssets.Contains("resources://", StringComparison.Ordinal) || - runtimeGameDocuments.Contains("resources://", StringComparison.Ordinal) || - daemonHostProgram.Contains("text.StartsWith(\"resources://\"", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Aetheria asset refs and daemon CDN requests must stay CultMesh-native; Resources paths are source resolution details, not public transport."); - } - - if (daemonRenderAssetCatalog.Contains("Resources.Load", StringComparison.Ordinal) || - daemonRenderAssetCatalog.Contains("resources://", StringComparison.Ordinal) || - daemonRenderAssetCatalog.Contains("AetheriaRuntimeAssetTransports.Resources", StringComparison.Ordinal) || - daemonIndirectRenderer.Contains("TryResolveMesh(renderGroup?.MeshKey", StringComparison.Ordinal) || - daemonIndirectRenderer.Contains("TryResolveMaterial(renderGroup?.MaterialKey", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Unity daemon rendering must resolve explicit daemon asset refs/catalog entries, not Resources fallback paths or legacy string-key mirrors."); - } - - var requiredBoundarySymbols = new[] - { - "RuntimeDaemonProviderFileSuffix", - "RuntimeDaemonHealthFileSuffix", - "RuntimeDaemonCommandBoundaryFileSuffix", - "RuntimeDaemonStarbridgeSessionSummaryFileSuffix", - "RuntimeDaemonGameSurfaceFileSuffix", - "RuntimeDaemonGameTuiSurfaceFileSuffix", - "RuntimeDaemonEditorSurfaceFileSuffix", - "RuntimeDaemonEditorTuiSurfaceFileSuffix", - "GetDaemonProviderPath(", - "GetDaemonHealthPath(", - "GetDaemonCommandBoundaryPath(", - "GetDaemonStarbridgeSessionSummaryPath(", - "GetDaemonGameSurfacePath(", - "GetDaemonGameTuiSurfacePath(", - "GetDaemonEditorSurfacePath(", - "GetDaemonEditorTuiSurfacePath(" - }; - var missingBoundarySymbols = requiredBoundarySymbols - .Where(symbol => !boundary.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (missingBoundarySymbols.Length > 0) - { - throw new InvalidOperationException( - "Daemon Verse publication paths are incomplete: " + - string.Join(", ", missingBoundarySymbols)); - } - - var forbiddenPublicationStoreSymbols = new[] - { - "WriteDaemonFrame(", - "ReadDaemonFrame(", - "WriteDaemonSoaView(", - "ReadDaemonSoaView(", - "WriteDaemonProviderAdvertisement(", - "ReadDaemonProviderAdvertisement(", - "WriteDaemonHealth(", - "ReadDaemonHealth(", - "WriteVerseAuthorityPolicy(", - "ReadVerseAuthorityPolicy(", - "WriteDaemonCommandBoundary(", - "ReadDaemonCommandBoundary(", - "WriteStarbridgeSessionSummary(", - "ReadStarbridgeSessionSummary(", - "WriteDaemonGameSurface(", - "ReadDaemonGameSurface(", - "WriteDaemonEditorSurface(", - "ReadDaemonEditorSurface(", - "TryReadProviderAdvertisement(", - "TryReadHealth(", - "TryReadVerseAuthorityPolicy(", - "TryReadCommandBoundary(", - "TryReadAssetManifest(", - "TryReadStarbridgeSessionSummary(", - "TryReadGameSurface(", - "TryReadGameTuiSurface(", - "TryReadEditorSurface(", - "TryReadEditorTuiSurface(" - }; - var publicationStoreReaderHits = forbiddenPublicationStoreSymbols - .Where(symbol => documentStore.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (publicationStoreReaderHits.Length > 0) - { - throw new InvalidOperationException( - "Daemon latest publication still exposes file-backed sidecar helpers instead of managed typed documents: " + - string.Join(", ", publicationStoreReaderHits)); - } - - var daemonFrameStorePath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeDaemonFrameStore.cs"); - if (File.Exists(daemonFrameStorePath)) - { - throw new InvalidOperationException( - "Daemon latest frames must stay on managed CultMesh pointers; delete AetheriaRuntimeDaemonFrameStore.cs instead of reviving sidecar path writes."); - } - - var daemonPublicationStorePath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeDaemonPublicationStore.cs"); - if (File.Exists(daemonPublicationStorePath)) - { - throw new InvalidOperationException( - "Daemon latest publications must stay on managed CultMesh pointers; delete AetheriaRuntimeDaemonPublicationStore.cs instead of reviving sidecar path writes."); - } - - var daemonSoaViewStorePath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeDaemonSoaViewStore.cs"); - if (File.Exists(daemonSoaViewStorePath)) - { - throw new InvalidOperationException( - "Daemon SoA latest-view publication must stay on managed CultMesh pointers; delete AetheriaRuntimeDaemonSoaViewStore.cs instead of reviving sidecar path writes."); - } - - var requiredDaemonRegistrySymbols = new[] - { - "CultMesh.CreateCultCacheDocumentRegistry(DocumentTypes)", - "CultMesh.CreateCultNetDocumentRegistry(DocumentTypes, registry)", - "typeof(AetheriaRuntimeDaemonProviderAdvertisementDocument)", - "typeof(AetheriaRuntimeDaemonHealthDocument)", - "typeof(AetheriaRuntimeDaemonCommandBoundaryDocument)", - "typeof(AetheriaRuntimeDaemonFrameDocument)", - "typeof(AetheriaRuntimeDaemonSoaViewDocument)", - "typeof(AetheriaRuntimeDaemonCommandDocument)" - }; - var missingDaemonRegistrySymbols = requiredDaemonRegistrySymbols - .Where(symbol => !documentRegistry.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (missingDaemonRegistrySymbols.Length > 0) - { - throw new InvalidOperationException( - "Daemon API documents are not registered as CultCache/CultNet records: " + - string.Join(", ", missingDaemonRegistrySymbols)); - } - - var requiredDaemonNodeSymbols = new[] - { - "public string RuntimeId { get; }", - "public CultMeshDocumentHandle Document(CultRecordKey key)", - "CultMesh.Document(", - "Database.WatchRecord(key)", - "public CultMeshDocumentHandle RuntimeCatalog()", - "AetheriaRuntimeCatalogStore.OpenReadOnly(StatePath)", - "public CultMeshMutableStatePointer MutableDocument(CultRecordKey key)", - "public static CultRecordKey WorldKey { get; }", - "public static CultRecordKey MigrationLedgerKey { get; }", - "public static CultRecordKey LegacyCatalogQuarantineKey { get; }", - "private CultMeshMutableStatePointer MutableDocumentPointer(CultRecordKey key)", - "CultMesh.MutableStatePointer(", - "AetheriaRuntimeVerseRecordKeys.DaemonFrameLatest" - }; - var missingDaemonNodeSymbols = requiredDaemonNodeSymbols - .Where(symbol => !stateNode.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (missingDaemonNodeSymbols.Length > 0) - { - throw new InvalidOperationException( - "AetheriaStateNode no longer exposes daemon Verse API records ergonomically: " + - string.Join(", ", missingDaemonNodeSymbols)); - } - - var forbiddenDaemonNodeSymbols = new[] - { - "public CultMeshMutableStatePointer World()", - "public CultMeshMutableStatePointer MigrationLedger()", - "public CultMeshMutableStatePointer LegacyCatalogQuarantine()", - "public CultMeshMutableStatePointer ItemDefinition(", - "public CultMeshMutableStatePointer ItemDefinitionByLegacyId(", - "public CultMeshMutableStatePointer Corporation(", - "public CultMeshMutableStatePointer CorporationByLegacyId(", - "public CultMeshMutableStatePointer NameFile(", - "public CultMeshMutableStatePointer NameFileByLegacyId(", - "public CultMeshMutableStatePointer LoadoutTemplate(", - "public CultMeshMutableStatePointer RunState(", - "public CultMeshMutableStatePointer ZoneState(", - "public CultMeshMutableStatePointer EntitySnapshot(", - "public CultMeshMutableStatePointer ProviderAdvertisement()", - "public CultMeshMutableStatePointer Health()", - "public CultMeshMutableStatePointer CommandBoundary()", - "public CultMeshMutableStatePointer VerseAuthorityPolicy()", - "public CultMeshMutableStatePointer LatestFrame()", - "public CultMeshMutableStatePointer LatestSoaView()", - "public CultMeshMutableStatePointer StarbridgeScenario()", - "public CultMeshMutableStatePointer StarbridgeSession()", - "public CultMeshMutableStatePointer StarbridgeSessionSummary()", - "public CultMeshMutableStatePointer DaemonGameSurface()", - "public CultMeshMutableStatePointer DaemonGameTuiSurface()", - "public CultMeshMutableStatePointer DaemonEditorSurface()", - "public CultMeshMutableStatePointer DaemonEditorTuiSurface()" - }; - var survivingDaemonNodeSymbols = forbiddenDaemonNodeSymbols - .Where(symbol => stateNode.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (survivingDaemonNodeSymbols.Length > 0) - { - throw new InvalidOperationException( - "AetheriaStateNode still exposes named daemon Verse record helpers instead of the generic typed document handle: " + - string.Join(", ", survivingDaemonNodeSymbols)); - } - - var deletedDaemonSurfaceProjectorPath = Path.Combine(root, "Aetheria.State", "AetheriaRuntimeEveSurfaceStateProjector.cs"); - if (File.Exists(deletedDaemonSurfaceProjectorPath)) - { - throw new InvalidOperationException( - "Daemon Eve surface state lowering still lives in Aetheria.State projector chaff instead of the runtime surface documents."); - } - - if (!runtimeSurfaceDocuments.Contains("public static EveUiSurfaceDocument ToPortableSurface(AetheriaRuntimeSurfaceDocument document)", StringComparison.Ordinal) || - !runtimeSurfaceDocuments.Contains("new EveUiCommandTemplate(command.Operation)", StringComparison.Ordinal) || - !runtimeSurfaceDocuments.Contains("new EveUiSurfaceComponent(", StringComparison.Ordinal) || - runtimeSurfaceDocuments.Contains("GameCult.Mesh.EveSurface", StringComparison.Ordinal)) - { - throw new InvalidOperationException("Runtime surface documents no longer lower daemon surfaces into canonical Eve surface documents published through CultMesh."); - } - - if (!daemonDocuments.Contains("AetheriaRuntimeDaemonCommandKinds", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Daemon command boundary no longer exposes typed daemon command metadata."); - } - - var forbiddenDaemonQueueSymbols = new[] - { - "public static AetheriaRuntimeDaemonCommandEnvelope QueueCommand(", - "AetheriaRuntimeDaemonCommandLog.", - "AetheriaRuntimeDaemonCommandLog.QueueCommand(", - "var envelope = AetheriaRuntimeDaemonCommandLog.QueueCommand", - ".daemon.pending", - "ReadPending(" - }; - var daemonQueueSource = daemonDocuments + "\n" + daemonTickRunner + "\n" + tests; - var survivingDaemonQueueSymbols = forbiddenDaemonQueueSymbols - .Where(symbol => daemonQueueSource.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (survivingDaemonQueueSymbols.Length > 0) - { - throw new InvalidOperationException( - "Daemon command boundary still exposes mailbox queue semantics: " + - string.Join(", ", survivingDaemonQueueSymbols)); - } - - if (daemonDocuments.Contains("PendingBeforeApply", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Daemon health still exposes the legacy PendingBeforeApply queue vocabulary instead of observed Verse command documents."); - } - - var requiredProviderAdvertisementSymbols = new[] - { - "private const string DaemonCommandBoundaryId = \"aetheria.daemon.commands\"", - "private const string DaemonRecordTransport = \"cultmesh-record\"", - "AetheriaRuntimeDaemonSchemas.ProviderAdvertisement", - "AetheriaRuntimeDaemonSchemas.Frame", - "AetheriaRuntimeDaemonSchemas.SoaView", - "AetheriaRuntimeDaemonSchemas.Health", - "AetheriaRuntimeDaemonSchemas.CommandBoundary", - "AetheriaRuntimeDaemonSchemas.GameSurface", - "AetheriaRuntimeDaemonSchemas.EditorSurface", - "AetheriaRuntimeDaemonSchemas.Command", - "AetheriaRuntimeVerseRecordKeys.DaemonProviderAdvertisement.ToString()", - "AetheriaRuntimeVerseRecordKeys.DaemonFrameLatest.ToString()", - "AetheriaRuntimeVerseRecordKeys.DaemonSoaViewLatest.ToString()", - "AetheriaRuntimeVerseRecordKeys.DaemonHealth.ToString()", - "AetheriaRuntimeVerseRecordKeys.DaemonCommandBoundary.ToString()", - "AetheriaRuntimeVerseRecordKeys.DaemonGameSurface.ToString()", - "AetheriaRuntimeVerseRecordKeys.DaemonGameTuiSurface.ToString()", - "AetheriaRuntimeVerseRecordKeys.DaemonEditorSurface.ToString()", - "AetheriaRuntimeVerseRecordKeys.DaemonEditorTuiSurface.ToString()", - "public const string DaemonGameSurfaceKey = \"eve:surface:aetheria.daemon.game\"", - "public const string DaemonGameTuiSurfaceKey = \"eve:surface:aetheria.daemon.game.tui\"", - "public const string DaemonEditorSurfaceKey = \"eve:surface:aetheria.daemon.editor\"", - "public const string DaemonEditorTuiSurfaceKey = \"eve:surface:aetheria.daemon.editor.tui\"" - }; - var requiredDaemonProviderDocumentSymbols = new[] - { - "AetheriaRuntimeDaemonGameSurfaceBuilder.SurfaceId", - "AetheriaRuntimeDaemonGameSurfaceBuilder.TuiSurfaceId", - "AetheriaRuntimeDaemonEditorSurfaceBuilder.SurfaceId", - "AetheriaRuntimeDaemonEditorSurfaceBuilder.TuiSurfaceId" - }; - var missingProviderAdvertisementSymbols = requiredProviderAdvertisementSymbols - .Where(symbol => !providerAdvertisement.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (missingProviderAdvertisementSymbols.Length > 0) - { - throw new InvalidOperationException( - "Odin-visible provider advertisement no longer points at managed daemon CultMesh records: " + - string.Join(", ", missingProviderAdvertisementSymbols)); - } - var missingDaemonProviderDocumentSymbols = requiredDaemonProviderDocumentSymbols - .Where(symbol => !daemonDocuments.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (missingDaemonProviderDocumentSymbols.Length > 0) - { - throw new InvalidOperationException( - "Daemon provider advertisement document no longer owns daemon surface ids: " + - string.Join(", ", missingDaemonProviderDocumentSymbols)); - } - - var forbiddenProviderAdvertisementSymbols = new[] - { - "DaemonWitnessTransport", - "AetheriaRuntimeStateBoundary.GetDaemonProviderPath(statePath)", - "AetheriaRuntimeStateBoundary.GetDaemonFramePath(statePath)", - "AetheriaRuntimeStateBoundary.GetDaemonSoaViewPath(statePath)", - "AetheriaRuntimeStateBoundary.GetDaemonHealthPath(statePath)", - "AetheriaRuntimeStateBoundary.GetDaemonCommandBoundaryPath(statePath)", - "AetheriaRuntimeStateBoundary.GetDaemonGameSurfacePath(statePath)", - "AetheriaRuntimeStateBoundary.GetDaemonGameTuiSurfacePath(statePath)", - "AetheriaRuntimeStateBoundary.GetDaemonEditorSurfacePath(statePath)", - "AetheriaRuntimeStateBoundary.GetDaemonEditorTuiSurfacePath(statePath)" - }; - var forbiddenProviderAdvertisementHits = forbiddenProviderAdvertisementSymbols - .Where(symbol => providerAdvertisement.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (forbiddenProviderAdvertisementHits.Length > 0) - { - throw new InvalidOperationException( - "Odin-visible provider advertisement still advertises daemon sidecar paths instead of managed CultMesh records: " + - string.Join(", ", forbiddenProviderAdvertisementHits)); - } - - var requiredTickSymbols = new[] - { - "VerseId", - "CultMeshAddress", - "BuildPublications", - "AetheriaRuntimeDaemonCommandBoundaryDocument.Create", - "AetheriaRuntimeDaemonProviderAdvertisementDocument.Create", - "AetheriaRuntimeStarbridgeDocuments.SessionSummary(", - "StarbridgeScenario", - "StarbridgeSession", - "var catalog = options.Catalog ?? new AetheriaRuntimeCatalogSnapshot(", - "AetheriaRuntimeDaemonGameSurfaceBuilder.Build", - "AetheriaRuntimeStatRecipeSurfaceBuilder.BuildFromCatalog(catalog)", - "AetheriaRuntimeTradeValuePolicySurfaceBuilder.BuildFromCatalog(catalog)", - "AetheriaRuntimeDaemonEditorSurfaceBuilder.Build", - "ProviderAdvertisement = providerAdvertisement", - "Health = health", - "CommandBoundary = commandBoundary", - "StarbridgeSessionSummary = starbridgeSessionSummary", - "GameSurface = gameSurface", - "ObservedCommands", - "AccountedCommandIds", - "frame.AccountedCommandIds = accountedBeforeTick", - "ObservedCommandCount = observedCommandCount", - "PublicationSource = \"daemon-published\"", - "Transport = \"cultmesh-managed\"", - "CommandBoundaryPath = AetheriaRuntimeVerseRecordKeys.DaemonCommandBoundary.ToString()" - }; - var missingTickSymbols = requiredTickSymbols - .Where(symbol => !daemonTickRunner.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (missingTickSymbols.Length > 0) - { - throw new InvalidOperationException( - "Daemon tick does not publish Verse-facing provider/health/command-boundary records: " + - string.Join(", ", missingTickSymbols)); - } - - var forbiddenTickPublicationSymbols = new[] - { - "AetheriaRuntimeDaemonFrameStore.PublishFrame(", - "AetheriaRuntimeDaemonPublicationStore.PublishCommandBoundary(", - "AetheriaRuntimeDaemonPublicationStore.PublishProviderAdvertisement(", - "AetheriaRuntimeDaemonPublicationStore.PublishHealth(", - "AetheriaRuntimeDaemonPublicationStore.PublishAssetManifest(", - "AetheriaRuntimeDaemonPublicationStore.PublishStarbridgeSessionSummary(", - "AetheriaRuntimeDaemonPublicationStore.PublishGameSurface(", - "AetheriaRuntimeDaemonPublicationStore.PublishGameTuiSurface(", - "AetheriaRuntimeDaemonPublicationStore.PublishEditorSurface(", - "AetheriaRuntimeDaemonPublicationStore.PublishEditorTuiSurface(", - "AetheriaRuntimeCatalogStore.ProjectStatRecipeSurfaceDocument(catalog)", - "AetheriaRuntimeCatalogStore.ProjectTradeValuePolicySurfaceDocument(catalog)", - "AetheriaRuntimeCatalogStore.ProjectStatRecipeSurfaceDocument(stateFilePath)", - "AetheriaRuntimeCatalogStore.ProjectTradeValuePolicySurfaceDocument(stateFilePath)" - }; - var forbiddenTickPublicationHits = forbiddenTickPublicationSymbols - .Where(symbol => daemonTickRunner.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (forbiddenTickPublicationHits.Length > 0) - { - throw new InvalidOperationException( - "Daemon tick runner must build typed publication documents, not write legacy witness stores: " + - string.Join(", ", forbiddenTickPublicationHits)); - } - - if (daemonZoneGenerator.Contains("fallbackCargo", StringComparison.Ordinal) || - daemonZoneGenerator.Contains("scenarioCargo", StringComparison.Ordinal) || - daemonLoadoutGenerator.Contains("scenarioCargo", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Daemon zone generation must not restore invented scenario cargo as a second item-identity authority."); - } - - if (daemonZoneGenerator.Contains("settings.TutorialPassed = true", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Generating a Terminus scenario must not award tutorial completion; progression requires its own daemon-owned completion fact."); - } - - var requiredDaemonHostSymbols = new[] - { - "Exe", - "ProjectReference Include=\"..\\Aetheria.State\\Aetheria.State.csproj\"", - "AetheriaStateNode.OpenAsync(", - "startServer: true", - "node.MutableDocument(AetheriaRuntimeVerseRecordKeys.DaemonFrameLatest)", - "Catalog = node.RuntimeCatalog().Latest()", - "AetheriaDaemonZoneGenerator.WritePlayableRunAsync(", - "node.MutableDocument(AetheriaRuntimeVerseRecordKeys.VerseAuthorityPolicy)", - "node.MutableDocument(AetheriaRuntimeVerseRecordKeys.StarbridgeScenarioLatest)", - "MutableDocument(AetheriaRuntimeVerseRecordKeys.StarbridgeSessionLatest)", - "StarbridgeScenario = starbridgeScenario", - "StarbridgeSession = starbridgeSession", - "BuildPublications = buildPublications", - "PublishDurableDaemonDocumentsAsync(node, result", - "PublishClientGameplayDocumentsAsync(", - "PublishSecondaryTopologyDocumentsAsync(node, options, result)", - "PublishHotEntityStateAsync(", - "node.MutableDocument(AetheriaRuntimeVerseRecordKeys.EveEntitySoaViewLatest)", - "node.MutableDocument(AetheriaRuntimeVerseRecordKeys.DaemonProviderAdvertisement)", - "node.MutableDocument(AetheriaRuntimeVerseRecordKeys.DaemonHealth)", - "node.MutableDocument(AetheriaRuntimeVerseRecordKeys.DaemonCommandBoundary)", - "node.MutableDocument(AetheriaRuntimeVerseRecordKeys.DaemonAssetManifest)", - "node.MutableDocument(AetheriaRuntimeVerseRecordKeys.DaemonGameSurface)", - "node.MutableDocument(AetheriaRuntimeVerseRecordKeys.DaemonGameTuiSurface)", - "node.MutableDocument(AetheriaRuntimeVerseRecordKeys.DaemonEditorSurface)", - "node.MutableDocument(AetheriaRuntimeVerseRecordKeys.DaemonEditorTuiSurface)", - "node.MutableDocument(AetheriaRuntimeVerseRecordKeys.StarbridgeSessionSummary)", - ".ReplaceAsync(result.Frame)", - ".ReplaceAsync(result.Health)", - ".ReplaceAsync(result.AssetManifest)", - ".ReplaceAsync(result.StarbridgeSessionSummary)", - "InjectEveSurfaceSnapshotAsync(", - "ReadEveSurfacePublicationAsync(", - "AetheriaRuntimeVerseRecordKeys.DaemonGameSurface", - "AetheriaRuntimeVerseRecordKeys.DaemonGameTuiSurface", - "AetheriaRuntimeVerseRecordKeys.DaemonEditorSurface", - "AetheriaRuntimeVerseRecordKeys.DaemonEditorTuiSurface", - "AetheriaEveCommandBridge.AcceptObservedAsync(", - "node.Documents()", - "currentFrame?.AccountedCommandIds ?? Array.Empty()", - "AetheriaRuntimeDaemonTickRunner.Tick(", - "node.MutableDocument(AetheriaStateNode.EveCommandAcceptanceStatusKey).ReadAsync()", - "node.MutableDocument(AetheriaStateNode.EveCommandAcceptanceStatusKey)", - "node.MutableDocument(AetheriaStateNode.RuntimeSessionKey(options.DaemonId))", - "node.MutableDocument(AetheriaStateNode.PlayerSettingsKey).ReadAsync()", - "node.MutableDocument(AetheriaStateNode.OperationsSurfaceKey)", - "node.MutableDocument(AetheriaStateNode.PlayerSettingsSurfaceKey)", - "node.MutableDocument(AetheriaStateNode.ProviderAdvertisementSurfaceKey)", - "AetheriaEveSurfaceDocuments.BuildProviderAdvertisement(verseHost, node.StatePath, updatedAtUtc)", - "AetheriaEveSurfaceDocuments.BuildOperationsSurface(eveStatus, verseHost, runtimeSession)", - "AetheriaEveSurfaceDocuments.BuildPlayerSettingsSurface(playerSettings, playerSettingsUpdatedAt)", - "Role = \"verse-daemon\"", - "Console.CancelKeyPress", - "Aetheria Verse daemon is running" - }; - var daemonHostSource = daemonHostProject + "\n" + daemonHostProgram; - var missingDaemonHostSymbols = requiredDaemonHostSymbols - .Where(symbol => !daemonHostSource.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (missingDaemonHostSymbols.Length > 0) - { - throw new InvalidOperationException( - "Aetheria.State.Daemon no longer exposes its direct CultMesh daemon host authority: " + - string.Join(", ", missingDaemonHostSymbols)); - } - - var forbiddenNamedDaemonHostSymbols = new[] - { - "node.LatestFrame()", - "node.LatestSoaView()", - "node.ProviderAdvertisement()", - "node.Health()", - "node.CommandBoundary()", - "node.VerseAuthorityPolicy()", - "node.StarbridgeScenario()", - "node.StarbridgeSession()", - "node.StarbridgeSessionSummary()", - "node.DaemonGameSurface()", - "node.DaemonGameTuiSurface()", - "node.DaemonEditorSurface()", - "node.DaemonEditorTuiSurface()" - }; - var survivingNamedDaemonHostSymbols = forbiddenNamedDaemonHostSymbols - .Where(symbol => daemonHostSource.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (survivingNamedDaemonHostSymbols.Length > 0) - { - throw new InvalidOperationException( - "Aetheria.State.Daemon still uses named daemon Verse record helpers instead of generic typed document handles: " + - string.Join(", ", survivingNamedDaemonHostSymbols)); - } - - if (!committedFactImporter.Contains("AetheriaRuntimeCatalogSnapshot? catalog = null", StringComparison.Ordinal) || - !committedFactImporter.Contains("Catalog = catalog ?? EmptyCatalog()", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Remote committed fact import must pass the managed runtime catalog document into the tick importer instead of reopening catalog storage."); - } - - var forbiddenEveProviderAdvertisementWriters = new[] - { - "EveProviderAdvertisementState", - "static Dictionary BuildOdinProviderAdvertisement(", - "[\"rootVerse\"]", - "[\"canonicalService\"]", - "[\"locatedService\"]", - "[\"routes\"]" - }; - var survivingEveProviderAdvertisementWriters = forbiddenEveProviderAdvertisementWriters - .Where(symbol => daemonHostProgram.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (survivingEveProviderAdvertisementWriters.Length > 0) - { - throw new InvalidOperationException( - "Daemon Odin publication still writes the deleted Aetheria-specific Eve provider advertisement shape: " + - string.Join(", ", survivingEveProviderAdvertisementWriters)); - } - - if (committedFactImporter.Contains("AetheriaRuntimeCatalogStore.OpenReadOnly", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Committed fact import must use managed catalog input or an explicit empty typed catalog; it must not reopen catalog storage from the state path."); - } - - var apiPublicationStart = daemonHostSource.IndexOf( - "static async Task PublishDurableDaemonDocumentsAsync", - StringComparison.Ordinal); - var apiPublicationEnd = apiPublicationStart >= 0 - ? daemonHostSource.IndexOf("static async Task AcceptEveCommandsAsync", apiPublicationStart, StringComparison.Ordinal) - : -1; - var apiPublicationBlock = apiPublicationStart >= 0 && apiPublicationEnd > apiPublicationStart - ? daemonHostSource.Substring(apiPublicationStart, apiPublicationEnd - apiPublicationStart) - : ""; - if (apiPublicationBlock.Contains("AetheriaRuntimeDaemonPublicationStore.TryRead", StringComparison.Ordinal) || - apiPublicationBlock.Contains("SoaViewStore.TryRead", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Daemon API document publication must use typed tick result documents instead of reopening witness files."); - } - - if (daemonTickRunner.Contains("AetheriaRuntimeDaemonPublicationStore.TryReadHealth(stateFilePath", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Daemon tick must pass the typed health document through surface builders instead of reopening its witness file."); - } - - var daemonTickStart = daemonHostSource.IndexOf( - "static async Task TickAsync(", - StringComparison.Ordinal); - var daemonTickEnd = daemonTickStart >= 0 - ? daemonHostSource.IndexOf("static async Task PublishCommittedCommandFactsAsync", daemonTickStart, StringComparison.Ordinal) - : -1; - var daemonTickBlock = daemonTickStart >= 0 && daemonTickEnd > daemonTickStart - ? daemonHostSource.Substring(daemonTickStart, daemonTickEnd - daemonTickStart) - : ""; - if (daemonTickBlock.Contains("AetheriaRuntimeDaemonFrameStore.PublishFrame(", StringComparison.Ordinal) || - daemonTickBlock.Contains("AetheriaRuntimeDaemonPublicationStore.Publish", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Daemon host tick publication must publish typed documents through AetheriaStateNode, not legacy witness stores."); - } - - if (!daemonTickBlock.Contains("if (buildPublications)", StringComparison.Ordinal) || - !daemonTickBlock.Contains("PublishDurableDaemonDocumentsAsync(node, result", StringComparison.Ordinal) || - !daemonTickBlock.Contains("PublishClientGameplayDocumentsAsync(", StringComparison.Ordinal) || - !daemonTickBlock.Contains("PublishSecondaryTopologyDocumentsAsync(node, options, result)", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Daemon ticks must publish API cadence documents through managed AetheriaStateNode pointers."); - } - - var snapshotHandlerStart = daemonHostSource.IndexOf( - "server.OnCultNet", - StringComparison.Ordinal); - var snapshotHandlerEnd = snapshotHandlerStart >= 0 - ? daemonHostSource.IndexOf("var factPuts = node.Documents()", snapshotHandlerStart, StringComparison.Ordinal) - : -1; - var snapshotHandler = snapshotHandlerStart >= 0 && snapshotHandlerEnd > snapshotHandlerStart - ? daemonHostSource.Substring(snapshotHandlerStart, snapshotHandlerEnd - snapshotHandlerStart) - : ""; - var forbiddenSnapshotStoreReads = new[] - { - "AetheriaRuntimeDaemonPublicationStore.TryReadHealth", - "AetheriaRuntimeDaemonPublicationStore.TryReadStarbridgeSessionSummary" - }; - var snapshotStoreHits = forbiddenSnapshotStoreReads - .Where(symbol => snapshotHandler.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (snapshotStoreHits.Length > 0) - { - throw new InvalidOperationException( - "RUDP snapshot responses must inject managed daemon node documents instead of reopening publication-store files: " + - string.Join(", ", snapshotStoreHits)); - } - - var surfaceSnapshotStart = daemonHostSource.IndexOf( - "static async Task InjectEveSurfaceSnapshotAsync", - StringComparison.Ordinal); - var surfaceSnapshotEnd = surfaceSnapshotStart >= 0 - ? daemonHostSource.IndexOf("static Task ReadEveSurfacePublicationAsync", surfaceSnapshotStart, StringComparison.Ordinal) - : -1; - var surfaceSnapshotBlock = surfaceSnapshotStart >= 0 && surfaceSnapshotEnd > surfaceSnapshotStart - ? daemonHostSource.Substring(surfaceSnapshotStart, surfaceSnapshotEnd - surfaceSnapshotStart) - : ""; - if (surfaceSnapshotBlock.Contains("AetheriaRuntimeDaemonPublicationStore.TryRead", StringComparison.Ordinal) || - surfaceSnapshotBlock.Contains("AetheriaRuntimeEveSurfaceStateProjector.ToState(surfaceDocument)", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "RUDP Eve surface snapshots still reopen publication-store files instead of using managed Eve surface records."); - } - - var forbiddenDaemonHostSymbols = new[] - { - ".Document(", - "node.Document(", - "node.Document(", - "node.Document(", - "node.Document(", - "LatestOrDefaultAsync(", - "AetheriaRuntimeDaemonFrameStore.TryReadFrame(node.StatePath", - "Catalog = node.OpenRuntimeCatalog()", - "AetheriaRuntimeDaemonCommandLog.", - "DeleteProcessedCommands", - "new HttpListener", - "UseUrls(", - "MapGet(", - "JsonSerializer.Serialize" - }; - if (daemonHostSource.Contains("KeepAccountedCommandRecords", StringComparison.Ordinal) || - daemonHostSource.Contains("--keep-accounted-command-records", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Aetheria.State.Daemon still exposes command-retention toggles after adopting receipt-ledger command accounting."); - } - - var requiredTradeValuePolicySurfaceSymbols = new[] - { - "public static class AetheriaRuntimeTradeValuePolicySurfaceBuilder", - "public static AetheriaRuntimeSurfaceDocument BuildFromCatalog(", - "SurfaceId = \"aetheria.tradeValuePolicy\"", - "var settings = catalog?.TradeValueSettings ?? AetheriaRuntimeTradeValueSettings.Default", - "updatedAtUtc: DateTime.UtcNow.ToString(\"O\", CultureInfo.InvariantCulture)", - "Quality Price Modifier", - "Rarity Tiers", - "AetheriaRuntimeTradeValuePolicyCommands.SetQualityMinimum", - "AetheriaRuntimeTradeValuePolicyCommands.SetTierQuality", - "BuildCommandTemplates()", - "control.text", - "curve.Evaluate(0.25)", - "ToHex(tier.Red, tier.Green, tier.Blue)" - }; - var tradeValuePolicySurfaceSource = tradeValuePolicySurfaceBuilder + "\n" + documentStore; - var missingTradeValuePolicySurfaceSymbols = requiredTradeValuePolicySurfaceSymbols - .Where(symbol => !tradeValuePolicySurfaceSource.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (missingTradeValuePolicySurfaceSymbols.Length > 0) - { - throw new InvalidOperationException( - "Trade value policy is no longer exposed as a designer-owned Eve surface: " + - string.Join(", ", missingTradeValuePolicySurfaceSymbols)); - } - - var requiredStatRecipeSurfaceProjectionSymbols = new[] - { - "public static AetheriaRuntimeSurfaceDocument BuildFromCatalog(", - "SelectMany(ProjectRows)", - "AetheriaRuntimeBehaviorMetadataCatalog.Get(behavior.Kind)", - "AetheriaRuntimeBehaviorFieldValueKind.PerformanceStat", - "new AetheriaRuntimeStatRecipeState(", - "new AetheriaRuntimeStatInfluenceState(" - }; - var missingStatRecipeSurfaceProjectionSymbols = requiredStatRecipeSurfaceProjectionSymbols - .Where(symbol => !statRecipeSurfaceBuilder.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (missingStatRecipeSurfaceProjectionSymbols.Length > 0) - { - throw new InvalidOperationException( - "Stat recipe designer surface projection must live on AetheriaRuntimeStatRecipeSurfaceBuilder: " + - string.Join(", ", missingStatRecipeSurfaceProjectionSymbols)); - } - - if (tradeValuePolicySurfaceBuilder.Contains("public static AetheriaRuntimeTradeValuePolicySurfaceState ProjectState(", StringComparison.Ordinal) || - tradeValuePolicySurfaceBuilder.Contains("AetheriaRuntimeTradeValuePolicySurfaceState", StringComparison.Ordinal) || - tradeValuePolicySurfaceBuilder.Contains("ComposeState(", StringComparison.Ordinal) || - statRecipeSurfaceBuilder.Contains("ComposeState(", StringComparison.Ordinal) || - statRecipeSurfaceBuilder.Contains("public static AetheriaRuntimeStatRecipeSurfaceState ProjectState(", StringComparison.Ordinal) || - tradeValuePolicySurfaceBuilder.Contains("Build(ProjectState(", StringComparison.Ordinal) || - statRecipeSurfaceBuilder.Contains("Build(ProjectState(", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Designer surface builders must build finished Eve documents from typed catalog input; do not re-expose ProjectState or shadow SurfaceState helpers."); - } - - var forbiddenCatalogStoreSurfaceProjectionSymbols = new[] - { - "ProjectStatRecipeSurfaceState(", - "ProjectTradeValuePolicySurfaceState(", - "ProjectStatRecipeRows(", - "ProjectStatRecipeRow(", - "ProjectStatInfluence(" - }; - var catalogStoreSurfaceProjectionHits = forbiddenCatalogStoreSurfaceProjectionSymbols - .Where(symbol => runtimeCatalogStore.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (catalogStoreSurfaceProjectionHits.Length > 0) - { - throw new InvalidOperationException( - "Runtime catalog store still owns designer surface projection helpers that belong on typed surface builders: " + - string.Join(", ", catalogStoreSurfaceProjectionHits)); - } - - if (stateNode.Contains("DeleteDaemonCommandAsync(", StringComparison.Ordinal) || - daemonHostSource.Contains("DeleteAccountedDaemonCommandsAsync", StringComparison.Ordinal) || - daemonHostSource.Contains("DeleteDaemonCommandAsync(", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Daemon command documents are still consumed by deletion instead of accounted by frame receipts."); - } - - var survivingDaemonHostSymbols = forbiddenDaemonHostSymbols - .Where(symbol => daemonHostSource.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (survivingDaemonHostSymbols.Length > 0) - { - throw new InvalidOperationException( - "Aetheria.State.Daemon has drifted into private command-log/dashboard ownership instead of CultMesh Verse publication: " + - string.Join(", ", survivingDaemonHostSymbols)); - } - - var requiredGameSurfaceSymbols = new[] - { - "public const string PilotSurfaceId = \"aetheria.pilot\"", - "public const string CommanderSurfaceId = \"aetheria.starbridge.commander\"", - "public const string SurfaceId = PilotSurfaceId", - "public const string TuiSurfaceId = \"aetheria.pilot.tui\"", - "AetheriaRuntimeDaemonFrameDocument frame", - "AetheriaRuntimeDaemonHealthDocument health", - "AetheriaRuntimeDaemonCommandBoundaryDocument commandBoundary", - "AetheriaRuntimeStarbridgeSessionSummaryDocument? starbridge = null", - "AetheriaRuntimeStarbridgeDocuments.SessionSummary(frame)", - "AetheriaRuntimeSurfaceStateRefs.SourceRef(stateRef)", - "AetheriaRuntimeDaemonStateRefs.CurrentEntityName", - "AetheriaRuntimeDaemonStateRefs.CurrentTargetName", - "\"game.daemon\"", - "\"Station Stock\"", - "\"Wave Forecast\"", - "\"Runtime Roles\"", - "BuildStarbridgeStationStockCard(starbridge)", - "BuildStarbridgeWaveForecastCard(starbridge)", - "BuildStarbridgeRuntimeRolesCard(starbridge)", - "\"Typed Command Boundary\"", - "\"commandBody\"", - "nameof(AetheriaRuntimeDaemonCommandDocument)", - "AetheriaRuntimeDaemonCommandKinds.SetMoveVector" - }; - var missingGameSurfaceSymbols = requiredGameSurfaceSymbols - .Where(symbol => !daemonGameSurfaceBuilder.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (missingGameSurfaceSymbols.Length > 0) - { - throw new InvalidOperationException( - "Daemon game Eve surface builder is incomplete: " + - string.Join(", ", missingGameSurfaceSymbols)); - } - - var requiredDaemonStateRefSymbols = new[] - { - "public static class AetheriaRuntimeDaemonStateRefs", - "public const string Prefix = \"aetheria.daemon\"", - "public const string CurrentEntityName = Prefix + \"/current/entityName\"", - "public const string CurrentTargetName = Prefix + \"/current/targetName\"", - "public const string FrameId = Prefix + \"/frame/frameId\"", - "public const string CommandCount = Prefix + \"/commands/count\"" - }; - var missingDaemonStateRefSymbols = requiredDaemonStateRefSymbols - .Where(symbol => !daemonStateRefs.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (missingDaemonStateRefSymbols.Length > 0) - { - throw new InvalidOperationException( - "Daemon Eve surfaces no longer expose a shared typed state-ref vocabulary: " + - string.Join(", ", missingDaemonStateRefSymbols)); - } - - var requiredSurfaceCommandCatalogSymbols = new[] - { - "public static class AetheriaRuntimeDaemonSurfaceCommandCatalog", - "public const string CommandPrefix = \"aetheria.daemon.commands.\"", - "public static IReadOnlyList ArgumentlessCommands", - "public static bool IsArgumentlessCommand(", - "internal static bool TrySubmitArgumentless(", - "AetheriaRuntimeDaemonCommandKinds.SensorPing => client.SensorPing(frame)" - }; - var missingSurfaceCommandCatalogSymbols = requiredSurfaceCommandCatalogSymbols - .Where(symbol => !daemonSurfaceCommandCatalog.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (missingSurfaceCommandCatalogSymbols.Length > 0) - { - throw new InvalidOperationException( - "Daemon surface command catalog no longer owns the argumentless CultMesh command set: " + - string.Join(", ", missingSurfaceCommandCatalogSymbols)); - } - - var surfaceCommandTemplateSources = new Dictionary - { - ["daemon game surface"] = daemonGameSurfaceBuilder, - ["daemon editor surface"] = daemonEditorSurfaceBuilder - }; - var overBroadSurfaceCommandTemplates = surfaceCommandTemplateSources - .Where(source => - !source.Value.Contains("AetheriaRuntimeDaemonSurfaceCommandCatalog.IsArgumentlessCommand(entry.Kind)", StringComparison.Ordinal) || - !source.Value.Contains("AetheriaRuntimeDaemonSurfaceCommandCatalog.CommandName(entry.Kind)", StringComparison.Ordinal)) - .Select(source => source.Key) - .ToArray(); - if (overBroadSurfaceCommandTemplates.Length > 0) - { - throw new InvalidOperationException( - "Daemon Eve surfaces still advertise command-boundary entries that are not directly submittable surface commands: " + - string.Join(", ", overBroadSurfaceCommandTemplates)); - } - - var requiredEditorSurfaceSymbols = new[] - { - "public const string SurfaceId = \"aetheria.daemon.editor\"", - "public const string TuiSurfaceId = \"aetheria.daemon.editor.tui\"", - "AetheriaRuntimeDaemonProviderAdvertisementDocument provider", - "AetheriaRuntimeDaemonHealthDocument health", - "AetheriaRuntimeDaemonCommandBoundaryDocument commandBoundary", - "\"editor.daemon\"", - "\"Verse Provider\"", - "\"Managed Records\"", - "\"aetheria.daemon.editor.records\"", - "provider.FrameRecordRef", - "provider.SoaViewRecordRef", - "provider.HealthRecordRef", - "provider.CommandBoundaryRecordRef", - "provider.EveGuiSurfaceRecordRef", - "provider.EditorGuiSurfaceRecordRef", - "\"Designer Surfaces\"", - "AetheriaRuntimeSurfaceDocument", - "\"Typed Commands\"", - "AetheriaRuntimeDaemonCommandBoundaryEntry" - }; - var missingEditorSurfaceSymbols = requiredEditorSurfaceSymbols - .Where(symbol => !daemonEditorSurfaceBuilder.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (missingEditorSurfaceSymbols.Length > 0) - { - throw new InvalidOperationException( - "Daemon editor Eve surface builder is incomplete: " + - string.Join(", ", missingEditorSurfaceSymbols)); - } - - var requiredTestSymbols = new[] - { - "var providerAdvertisement = result.ProviderAdvertisement", - "var health = result.Health", - "var commandBoundary = result.CommandBoundary", - "var gameSurface = result.GameSurface", - "var editorSurface = result.EditorSurface", - "var gameTuiSurface = result.GameTuiSurface", - "var editorTuiSurface = result.EditorTuiSurface", - "AetheriaRuntimeDaemonSchemas.ProviderAdvertisement", - "AetheriaRuntimeDaemonSchemas.CommandBoundary", - "AetheriaRuntimeDaemonSchemas.Health", - "AetheriaRuntimeDaemonSchemas.GameSurface", - "AetheriaRuntimeDaemonSchemas.EditorSurface", - "AetheriaRuntimeDaemonGameSurfaceBuilder.SurfaceId", - "AetheriaRuntimeDaemonEditorSurfaceBuilder.SurfaceId", - "AetheriaRuntimeStatRecipeCommands.SurfaceId", - "AetheriaRuntimeDaemonCommandKinds.TransferCargoItem", - "nameof(AetheriaRuntimeCargoTransferCommand)" - }; - var missingTestSymbols = requiredTestSymbols - .Where(symbol => !tests.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (missingTestSymbols.Length > 0) - { - throw new InvalidOperationException( - "Daemon Verse publication tests are incomplete: " + - string.Join(", ", missingTestSymbols)); - } - - var requiredSmokeSymbols = new[] - { - "node.MutableDocument(AetheriaRuntimeVerseRecordKeys.DaemonProviderAdvertisement)", - "node.MutableDocument(AetheriaRuntimeVerseRecordKeys.DaemonHealth)", - "node.MutableDocument(AetheriaRuntimeVerseRecordKeys.DaemonCommandBoundary)", - "AetheriaClient.OpenAsync(", - "daemonCommandClient.Control.SensorPing();", - "eveCommandClient.Ui.InputSettingsAsync(", - "AetheriaClient control submission did not appear as a typed daemon state record.", - "AetheriaClient UI submission did not appear as a typed Eve state record.", - "node.MutableDocument(AetheriaRuntimeVerseRecordKeys.DaemonFrameLatest)", - "node.MutableDocument(AetheriaRuntimeVerseRecordKeys.DaemonGameSurface)", - ".ReplaceAsync(AetheriaRuntimeSurfaceDocuments.ToPortableSurface(daemonGameSurface))", - "reopened.MutableDocument(AetheriaRuntimeVerseRecordKeys.DaemonProviderAdvertisement)", - "reopened.MutableDocument(AetheriaRuntimeVerseRecordKeys.DaemonHealth)", - "reopened.MutableDocument(AetheriaRuntimeVerseRecordKeys.DaemonCommandBoundary)", - "reopened.MutableDocument(AetheriaRuntimeVerseRecordKeys.DaemonFrameLatest)", - "reopened.MutableDocument(AetheriaRuntimeVerseRecordKeys.DaemonGameSurface)", - "DaemonGameSurfaceKey", - "DaemonGameTuiSurfaceKey", - "DaemonEditorSurfaceKey", - "DaemonEditorTuiSurfaceKey" - }; - var missingSmokeSymbols = requiredSmokeSymbols - .Where(symbol => !smoke.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (missingSmokeSymbols.Length > 0) - { - throw new InvalidOperationException( - "State smoke no longer proves daemon Verse API records round-trip through CultCache: " + - string.Join(", ", missingSmokeSymbols)); - } - - var forbiddenSmokeSymbols = new[] - { - "PutWorldAsync(", - "GetWorldAsync(", - "PutLegacyItemDefinitionAsync(", - "GetItemDefinitionByLegacyIdAsync(", - "PutLegacyCorporationAsync(", - "GetCorporationByLegacyIdAsync(", - "PutLegacyNameFileAsync(", - "GetNameFileByLegacyIdAsync(", - "PutCatalogSurfaceAsync(", - "GetCatalogSurfaceAsync(", - "PutOperationsSurfaceAsync(", - "GetOperationsSurfaceAsync(", - "PutProviderAdvertisementAsync(", - "GetProviderAdvertisementAsync(", - "PutDaemonProviderAdvertisementAsync(", - "GetDaemonProviderAdvertisementAsync(", - "PutDaemonHealthAsync(", - "GetDaemonHealthAsync(", - "PutDaemonCommandBoundaryAsync(", - "GetDaemonCommandBoundaryAsync(", - "PutDaemonFrameAsync(", - "GetDaemonFrameAsync(", - "PutDaemonGameSurfaceAsync(", - "GetDaemonGameSurfaceAsync(", - "node.ProviderAdvertisement()", - "node.Health()", - "node.CommandBoundary()", - "node.LatestFrame()", - "node.DaemonGameSurface()", - "reopened.ProviderAdvertisement()", - "reopened.Health()", - "reopened.CommandBoundary()", - "reopened.LatestFrame()", - "reopened.DaemonGameSurface()", - "PutRuntimeSessionAsync(", - "GetRuntimeSessionAsync(", - "PutLoadoutTemplateAsync(", - "GetLoadoutTemplateAsync(", - "PutEntitySnapshotAsync(", - "GetEntitySnapshotAsync(", - "PutZoneStateAsync(", - "GetZoneStateAsync(", - "PutRunStateAsync(", - "GetRunStateAsync(", - "PutPlayerSettingsAsync(", - "GetPlayerSettingsAsync(", - "PutPlayerSettingsSurfaceAsync(", - "GetPlayerSettingsSurfaceAsync(", - "PutEveCommandAcceptanceStatusAsync(", - "GetEveCommandAcceptanceStatusAsync(" - }; - var survivingSmokeSymbols = forbiddenSmokeSymbols - .Where(symbol => smoke.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (survivingSmokeSymbols.Length > 0) - { - throw new InvalidOperationException( - "State smoke still teaches compatibility helper access instead of managed typed state pointers: " + - string.Join(", ", survivingSmokeSymbols)); - } - - var requiredNoteSymbols = new[] - { - "Odin is the all-seer, not the owner.", - "Idunn keeps the daemon alive from daemon-published health and command-boundary records.", - "Bifrost hosts the MCP crossing for Codex and other xeno agents.", - "The Aetheria daemon remains the side-effect owner.", - "Witness-authoritative networking sharpens this rule.", - "Unity should not be a dumb terminal;", - "local projection caches, prediction state, render-native SoA views, and eventually immutable witness observations", - "Observation, prediction, consensus candidates, and committed facts are separate stages.", - "it must not mutate canonical Aetheria state or promote a local projection cache into authority.", - "gamecult.aetheria.daemon_provider_advertisement.v1", - "gamecult.aetheria.daemon_health.v1", - "gamecult.aetheria.daemon_command_boundary.v1", - "gamecult.aetheria.daemon_frame.v1", - "gamecult.aetheria.daemon_soa_view.v1", - "gamecult.aetheria.daemon_game_surface.v1", - "gamecult.aetheria.daemon_editor_surface.v1", - "gamecult.eve.provider_advertisement.v1` is the Odin-visible Eve provider card", - "gamecult.aetheria.daemon_provider_advertisement.v1` is the daemon-owned Aetheria runtime contract", - "The bridge layer must point at daemon-owned witnesses instead of becoming a second source of truth.", - "Long term, Odin should discover the daemon-owned provider advertisement and interface bindings directly", - "Queues are an implementation detail.", - "Eve commands and daemon commands are typed `gamecult.eve.command.v1` and `gamecult.aetheria.daemon_command.v1` records in the Aetheria state graph", - "`AetheriaRuntimeVerseClient` is the only shared submission boundary for typed command records.", - "Do not add command ports, cached submitters, mailboxes, or queue-like buses between clients and the typed Verse graph.", - "`CommandLog`, `Inbox`, `mailbox`, `.eve.commands`, `.daemon.commands`, `.cc.pending`, or `" + "Pending" + "CultCacheStore` in Unity-facing daemon/Eve command code", - "Do not add a private HTTP dashboard, Unity-only inspector, JSON status blob, or agent-specific daemon wrapper as canonical truth." - }; - var missingNoteSymbols = requiredNoteSymbols - .Where(symbol => !note.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (missingNoteSymbols.Length > 0) - { - throw new InvalidOperationException( - "Daemon Verse shape note no longer records the Odin/VoidBot daemon authority contract: " + - string.Join(", ", missingNoteSymbols)); - } -} - -static void RequireUnityRuntimeCatalogClientUsesManagedDocument(string root) -{ - var clientPath = Path.Combine( - root, - "Packages", - "org.gamecult.aetheria.state", - "Runtime", - "AetheriaClientState.cs"); - var verseClientPath = Path.Combine( - root, - "Packages", - "org.gamecult.aetheria.state", - "Runtime", - "AetheriaRuntimeVerseClient.cs"); - var mapperPath = Path.Combine(root, "Aetheria.State.Unity", "AetheriaRuntimeCatalogSnapshotMapper.cs"); - var client = File.Exists(clientPath) - ? File.ReadAllText(clientPath) - : throw new InvalidOperationException("Cannot verify managed Unity runtime catalog client; AetheriaClientState.cs is missing."); - var verseClient = File.Exists(verseClientPath) - ? File.ReadAllText(verseClientPath) - : throw new InvalidOperationException("Cannot verify managed Unity runtime catalog client; AetheriaRuntimeVerseClient.cs is missing."); - - var requiredSymbols = new[] - { - "using EveSurfaceDocument = GameCult.Eve.Surface.EveSurfaceDocument;", - "CultMeshDocumentHandle catalog", - "public CultMeshDocumentHandle Catalog", - "public CultMeshDocumentHandle? EveSurfaceDocument(", - "AetheriaClientEveSurface.Game => GameSurface", - "AetheriaClientEveSurface.Editor => EditorSurface" - }; - var missingSymbols = requiredSymbols - .Where(symbol => !client.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (missingSymbols.Length > 0) - { - throw new InvalidOperationException( - "Unity runtime catalog client must read the managed runtime catalog document: " + - string.Join(", ", missingSymbols)); - } - - var requiredVerseClientSymbols = new[] - { - "BootstrapCatalogDocument(", - "Document(", - "AetheriaRuntimeVerseRecordKeys.DaemonGameSurface", - "AetheriaRuntimeVerseRecordKeys.DaemonEditorSurface" - }; - var missingVerseClientSymbols = requiredVerseClientSymbols - .Where(symbol => !verseClient.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (missingVerseClientSymbols.Length > 0) - { - throw new InvalidOperationException( - "Runtime Verse client no longer exposes managed catalog/Eve surface document handles: " + - string.Join(", ", missingVerseClientSymbols)); - } - - var forbiddenSymbols = new[] - { - "EveSurfaceState", - "_node.ReadCatalog", - "_node.GetCatalogSurfaceAsync()", - "ReadCatalogSurfaceAsync(", - "AetheriaRuntimeCatalogStore.ReadEveSurfaces", - "AetheriaRuntimeCatalogStore", - "ToState(global::GameCult.Eve.Surface.EveSurfaceDocument", - "AetheriaRuntimeCatalogSnapshotMapper.FromCatalog(", - "AetheriaCatalogSnapshot" - }; - var forbiddenHits = forbiddenSymbols - .Where(symbol => client.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (forbiddenHits.Length > 0) - { - throw new InvalidOperationException( - "Unity runtime catalog client still maps authored catalog state instead of using the managed runtime document: " + - string.Join(", ", forbiddenHits)); - } - - if (File.Exists(mapperPath)) - { - throw new InvalidOperationException( - "Unity runtime catalog snapshot mapper is dead projection chaff; AetheriaStateNode.RuntimeCatalog owns this document now."); - } - - Console.WriteLine("Unity runtime catalog client: catalog reads use the managed typed runtime document"); -} - -static void RequireCatalogSurfaceUsesManagedRuntimeCatalog(string root) -{ - var stateNodePath = Path.Combine(root, "Aetheria.State", "AetheriaStateNode.cs"); - var surfaceDocumentsPath = Path.Combine(root, "Aetheria.State", "AetheriaEveSurfaceDocuments.cs"); - var bridgePath = Path.Combine(root, "Aetheria.State", "AetheriaEveCommandBridge.cs"); - var importPath = Path.Combine(root, "Aetheria.State.Import", "Program.cs"); - var legacySnapshotPath = Path.Combine(root, "Aetheria.State", "AetheriaCatalogSnapshot.cs"); - var surfaceDocuments = File.Exists(surfaceDocumentsPath) - ? File.ReadAllText(surfaceDocumentsPath) - : throw new InvalidOperationException("Cannot verify catalog surface document; AetheriaEveSurfaceDocuments.cs is missing."); - var bridge = File.Exists(bridgePath) - ? File.ReadAllText(bridgePath) - : throw new InvalidOperationException("Cannot verify Eve command bridge catalog refresh; AetheriaEveCommandBridge.cs is missing."); - var import = File.Exists(importPath) - ? File.ReadAllText(importPath) - : throw new InvalidOperationException("Cannot verify legacy catalog import; Aetheria.State.Import/Program.cs is missing."); - var stateNode = File.Exists(stateNodePath) - ? File.ReadAllText(stateNodePath) - : throw new InvalidOperationException("Cannot verify runtime catalog ownership; AetheriaStateNode.cs is missing."); - - var requiredStateNodeSymbols = new[] - { - "Database.GetAsync(RuntimeCatalogKey)", - "Database.WatchRecord(RuntimeCatalogKey)", - "public async Task RefreshRuntimeCatalogAsync()", - "await Database.PutAsync(RuntimeCatalogKey, snapshot).ConfigureAwait(false);", - "_ => catalog.Watch()" - }; - var missingStateNodeSymbols = requiredStateNodeSymbols - .Where(symbol => !stateNode.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (missingStateNodeSymbols.Length > 0) - { - throw new InvalidOperationException( - "The state node must own one explicitly refreshed runtime catalog document: " + - string.Join(", ", missingStateNodeSymbols)); - } - - var runtimeCatalogMethod = stateNode[ - stateNode.IndexOf("public CultMeshDocumentHandle RuntimeCatalog()", StringComparison.Ordinal).. - stateNode.IndexOf("public CultMeshDocumentHandle CatalogSurface()", StringComparison.Ordinal)]; - if (runtimeCatalogMethod.Contains("DaemonFrameLatest", StringComparison.Ordinal) || - runtimeCatalogMethod.Contains("OpenReadOnly(StatePath)", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Daemon frames must not invalidate or recompile the runtime catalog."); - } - - var requiredSurfaceDocumentSymbols = new[] - { - "BuildCatalogSurface(", - "AetheriaRuntimeCatalogSnapshot catalog", - "item.ItemKey", - "corporation.CorporationKey", - "catalog.GetManufacturer(item)", - "catalog.GetNameFile(corporation)" - }; - var missingSurfaceDocumentSymbols = requiredSurfaceDocumentSymbols - .Where(symbol => !surfaceDocuments.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (missingSurfaceDocumentSymbols.Length > 0) - { - throw new InvalidOperationException( - "Catalog Eve surface must build from the managed runtime catalog document: " + - string.Join(", ", missingSurfaceDocumentSymbols)); - } - - if (surfaceDocuments.Contains("Build(AetheriaCatalogSnapshot catalog", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Catalog Eve surface still depends on the legacy authored catalog snapshot instead of the managed runtime catalog document."); - } - - if (File.Exists(legacySnapshotPath)) - { - throw new InvalidOperationException( - "AetheriaCatalogSnapshot is dead projection chaff; AetheriaRuntimeCatalogSnapshot is the managed typed catalog document."); - } - - var requiredBridgeSymbols = new[] - { - "var catalog = await node.RefreshRuntimeCatalogAsync().ConfigureAwait(false);", - "AetheriaEveSurfaceDocuments.BuildCatalogSurface(catalog, command.IssuedAtUtc)" - }; - var missingBridgeSymbols = requiredBridgeSymbols - .Where(symbol => !bridge.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (missingBridgeSymbols.Length > 0) - { - throw new InvalidOperationException( - "Eve catalog refresh must read the managed runtime catalog document: " + - string.Join(", ", missingBridgeSymbols)); - } - - if (bridge.Contains("ReadCatalog", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Eve catalog refresh still rebuilds the legacy catalog snapshot instead of using AetheriaStateNode.RuntimeCatalog()."); - } - - var requiredImportSymbols = new[] - { - "node.MutableDocument(AetheriaStateNode.LegacyCatalogQuarantineKey)", - "node.MutableDocument(AetheriaStateNode.MigrationLedgerKey)", - "node.MutableDocument(AetheriaCatalogKeys.ItemDefinitionFromLegacyId(item.LegacyId))", - "node.MutableDocument(AetheriaCatalogKeys.CorporationFromLegacyId(corporation.LegacyId))", - "node.MutableDocument(AetheriaCatalogKeys.NameFileFromLegacyId(nameFile.LegacyId))", - "node.MutableDocument(AetheriaStateNode.TradeValuePolicyKey)", - "AetheriaRuntimeStateMapper.ToTradeValuePolicy(", - "TryReadRectangularShape(columns, out var rectangularShape)", - "ProjectCatalogRarity(ProjectStationSensorMount(ProjectDeployableWeapon(entry.ItemDefinition!)))", - "AetherDB.2021-04-14.msgpack", - "LegacyCatalogLayout.April2021", - "c4b20032-8cd3-4206-807f-97b296797425", - "bb5d9f73-612f-4060-8fb4-f72d6b93b616", - "rarity:rare", - "--merge-station-sensor", - "RefreshRuntimeCatalogAsync()", - "await node.CatalogSurface().LatestAsync().ConfigureAwait(false);" - }; - var missingImportSymbols = requiredImportSymbols - .Where(symbol => !import.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (missingImportSymbols.Length > 0) - { - throw new InvalidOperationException( - "Legacy catalog import must write through managed typed document handles and materialize the managed catalog surface: " + - string.Join(", ", missingImportSymbols)); - } - - var forbiddenImportSymbols = new[] - { - "PutLegacyCatalogQuarantineAsync(", - "PutMigrationLedgerAsync(", - "PutLegacyItemDefinitionAsync(", - "PutLegacyCorporationAsync(", - "PutLegacyNameFileAsync(", - "PutTradeValuePolicyAsync(", - "PutCatalogSurfaceAsync(" - }; - var forbiddenImportHits = forbiddenImportSymbols - .Where(symbol => import.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (forbiddenImportHits.Length > 0) - { - throw new InvalidOperationException( - "Legacy catalog import still uses AetheriaStateNode Put helpers instead of managed typed documents: " + - string.Join(", ", forbiddenImportHits)); - } - - Console.WriteLine("Catalog Eve surface: refresh path uses the managed runtime catalog document"); -} - -static void RequireDaemonLoadoutRarityAuthority(string root) -{ - var generatorPath = Path.Combine(root, "Aetheria.State.Daemon", "AetheriaDaemonLoadoutGenerator.cs"); - var generator = File.ReadAllText(generatorPath); - var required = new[] - { - "private Random _random;", - "RareGenerationWeight", - "rarity:rare", - "RarityWeight(item)" - }; - var missing = required.Where(symbol => !generator.Contains(symbol, StringComparison.Ordinal)).ToArray(); - if (missing.Length > 0) - throw new InvalidOperationException( - "Daemon loadout generation must own one advancing CultMath stream and apply typed rarity policy: " + - string.Join(", ", missing)); - if (generator.Contains("private readonly Random _random;", StringComparison.Ordinal)) - throw new InvalidOperationException( - "A readonly CultMath.Random field mutates a defensive copy and restarts weighted selection on every draw."); -} - -static void RequireAetheriaRuntimeVerseClientContract(string root) -{ - var clientPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeVerseClient.cs"); - var clientStatePath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaClientState.cs"); - var surfaceStatePath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeEveSurfaceState.cs"); - var oldSurfaceStatePath = Path.Combine(root, "Aetheria.State", "Documents", "EveSurfaceState.cs"); - var stateRefResolverPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeStateRefResolver.cs"); - var docPath = Path.Combine(root, "docs", "aetheria-verse-client-contract.md"); - - var requiredFiles = new[] { clientPath, clientStatePath, stateRefResolverPath, docPath }; - var missingFiles = requiredFiles - .Where(path => !File.Exists(path)) - .Select(path => Path.GetRelativePath(root, path)) - .ToArray(); - if (missingFiles.Length > 0) - { - throw new InvalidOperationException( - "Aetheria runtime Verse client contract cannot be verified because required files are missing: " + - string.Join(", ", missingFiles)); - } - - var client = File.ReadAllText(clientPath); - var clientState = File.ReadAllText(clientStatePath); - var stateRefResolver = File.ReadAllText(stateRefResolverPath); - var doc = File.ReadAllText(docPath); - - if (File.Exists(oldSurfaceStatePath)) - { - throw new InvalidOperationException( - "Aetheria-local EveSurfaceState must not own UI surface state; clients watch the shared CultMesh EveSurfaceDocument."); - } - - if (File.Exists(surfaceStatePath)) - { - throw new InvalidOperationException( - "AetheriaRuntimeEveSurfaceState.cs is legacy surface-state cruft; daemon UI surfaces must publish GameCult.Eve.Surface.EveSurfaceDocument."); - } - - var requiredClientSymbols = new[] - { - "public sealed class AetheriaRuntimeVerseClient", - "public static class AetheriaRuntimeVerseRecordKeys", - "public static class AetheriaRuntimeVerseContractRegistry", - "AetheriaRuntimeVerseContractRegistry.CreateCultCacheRegistry()", - "AetheriaRuntimeVerseContractRegistry.CreateCultNetRegistry(registry)", - "public Observable> WatchRecord(", - "WatchRecord", - "public CultMeshMutableStatePointer MutableDocument(", - "CultMesh.MutableStatePointer(", - "CultMesh.CreateCultCacheDocumentRegistry(RuntimeDocumentTypes)", - "CultMesh.CreateCultNetDocumentRegistry(RuntimeDocumentTypes, registry)", - "typeof(AetheriaRuntimeVerseAuthorityPolicyDocument)", - "typeof(AetheriaRuntimeCatalogSnapshot)", - "typeof(AetheriaRuntimeLoadoutTemplatesDocument)", - "AetheriaRuntimeVerseRecordKeys.VerseAuthorityPolicy", - "public static CultRecordKey StarbridgeSessionSummary", - "new CultRecordKey(\"daemon:aetheria.starbridge.session.latest.v1\")", - "Document(", - "typeof(EveSurfaceDocument)", - "private AetheriaClientState? _aetheriaState", - "private CultMeshReactiveDocument? _managedDaemonFrame", - "private CultMeshReactiveDocument? _managedCatalog", - "private CultMeshReactiveDocument? _managedLoadoutTemplates", - "private CultMeshReactiveDocument? _managedStarbridgeScenario", - "private CultMeshReactiveDocument? _managedStarbridgeSession", - "_managedDaemonFrame?.Dispose()", - "_managedCatalog?.Dispose()", - "_managedLoadoutTemplates?.Dispose()", - "_managedStarbridgeScenario?.Dispose()", - "_managedStarbridgeSession?.Dispose()", - "return _aetheriaState ??= CreateAetheriaState();", - "AetheriaRuntimeLoadoutTemplatesDocument", - "StarbridgeSummaryAsync", - "CultMeshReactiveDocument? managedDaemonFrame = null;", - "managedDaemonFrame ??= latestFrameDocument.Reactive();", - "managedCatalog ??= catalogDocument.Reactive();", - "managedLoadoutTemplates ??= loadoutTemplatesDocument.Reactive();", - "managedPlayerSettings = state.PlayerSettings.Reactive();", - "managedStarbridgeScenario ??= starbridgeScenarioDocument.Reactive();", - "managedStarbridgeSession ??= starbridgeSessionDocument.Reactive();", - "AetheriaRuntimeDaemonFrameDocument RequireManagedFrame()", - "RequireManagedFrame()", - "RequireManagedCatalog()", - "RequireManagedLoadoutTemplates().Templates", - "return managedStarbridgeScenario.Current;", - "return managedStarbridgeSession.Current;", - "BootstrapCatalogDocument(", - "CatalogBootstrapSource(", - "BootstrapRuntimeCatalog", - "BootstrapLoadoutTemplates", - "BootstrapPlayerSettings", - "BootstrapVerseHostSettings", - "SubmitDaemonCommandAsync(", - "SubmitEveCommandAsync(", - "AetheriaRuntimeVerseRecordKeys.DaemonCommand(command.CommandId)", - "AetheriaRuntimeVerseRecordKeys.EveCommand(command.CommandId)", - "DaemonGameTuiSurface { get; }", - "DaemonEditorTuiSurface { get; }" - }; - var missingClientSymbols = requiredClientSymbols - .Where(symbol => !client.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (missingClientSymbols.Length > 0) - { - throw new InvalidOperationException( - "Aetheria runtime Verse client is missing typed CultMesh client contract symbols: " + - string.Join(", ", missingClientSymbols)); - } - - var forbiddenNamedWatchHelpers = new[] - { - "WatchProviderAdvertisements(", - "WatchHealth(", - "WatchCommandBoundary(", - "WatchVerseAuthorityPolicies(", - "WatchLatestFrames(", - "WatchLatestSoaViews(", - "WatchStarbridgeScenarios(", - "WatchStarbridgeSessions(", - "WatchStarbridgePlayerSeat(", - "WatchDaemonGameSurfaces(", - "WatchDaemonGameTuiSurfaces(", - "WatchDaemonEditorSurfaces(", - "WatchDaemonEditorTuiSurfaces(" - }; - var survivingNamedWatchHelpers = forbiddenNamedWatchHelpers - .Where(symbol => client.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (survivingNamedWatchHelpers.Length > 0) - { - throw new InvalidOperationException( - "AetheriaRuntimeVerseClient must expose generic WatchRecord(CultRecordKey) instead of named record-watch wrappers: " + - string.Join(", ", survivingNamedWatchHelpers)); - } - - var forbiddenNamedMutablePointers = new[] - { - "ProviderAdvertisement(", - "Health(", - "CommandBoundary(", - "VerseAuthorityPolicy(", - "LatestFrame(", - "LatestSoaView(", - "StarbridgeScenario(", - "StarbridgeSession(", - "StarbridgePlayerSeat(", - "DaemonGameSurface(", - "DaemonGameTuiSurface(", - "DaemonEditorSurface(", - "DaemonEditorTuiSurface(" - }; - var runtimeClientInstanceApi = client.Split( - "public AetheriaClientState Aetheria()", - StringSplitOptions.None).Last().Split( - "public CultMeshDocumentHandle Document(", - StringSplitOptions.None)[0]; - var survivingNamedMutablePointers = forbiddenNamedMutablePointers - .Where(symbol => runtimeClientInstanceApi.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (survivingNamedMutablePointers.Length > 0) - { - throw new InvalidOperationException( - "AetheriaRuntimeVerseClient must expose generic MutableDocument(CultRecordKey) instead of named mutable pointer wrappers: " + - string.Join(", ", survivingNamedMutablePointers)); - } - - if (client.Contains("ReactiveDocumentAsync", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "AetheriaRuntimeVerseClient must not expose a second reactive-document alias; callers should use Document(key).ReactiveAsync()."); - } - - if (File.Exists(Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeManagedClientInputs.cs")) || - client.Contains("AetheriaRuntimeManagedClientInputs", StringComparison.Ordinal) || - client.Contains("AetheriaRuntimeDaemonFrameSession _daemonFrame", StringComparison.Ordinal) || - client.Contains("AetheriaRuntimeCatalogSession _catalog", StringComparison.Ordinal) || - client.Contains("AetheriaRuntimeLoadoutTemplatesSession _loadoutTemplates", StringComparison.Ordinal) || - client.Contains("AetheriaRuntimeStarbridgeScenarioSession _starbridgeScenario", StringComparison.Ordinal) || - client.Contains("AetheriaRuntimeStarbridgeRunSession _starbridgeSession", StringComparison.Ordinal) || - client.Contains("state.ObserveDaemonFrame()", StringComparison.Ordinal) || - client.Contains("state.ObserveCatalog()", StringComparison.Ordinal) || - client.Contains("state.ObserveLoadoutTemplates()", StringComparison.Ordinal) || - client.Contains("state.Starbridge.ObserveScenario()", StringComparison.Ordinal) || - client.Contains("state.Starbridge.ObserveSession()", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Aetheria Verse client derived documents must sample directly owned named reactive typed documents instead of an input/session wrapper."); - } - - if (client.Contains("AetheriaRuntimeReactiveProjectionInputs", StringComparison.Ordinal) || - client.Contains("private sealed class AetheriaRuntimeManagedClientInputs", StringComparison.Ordinal) || - client.Contains("_projectionInputs", StringComparison.Ordinal) || - client.Contains("ProjectionInputs", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "AetheriaRuntimeVerseClient reintroduced a projection-input helper instead of owning generic reactive typed documents directly."); - } - - if (client.Contains("StationRefitAsync", StringComparison.Ordinal) && - client.Contains("loadoutTemplatesDocument.LatestAsync()", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Aetheria runtime Verse client station-refit projection still polls loadout templates instead of sampling managed reactive client inputs."); - } - - if (client.Contains("StarbridgeSummaryAsync", StringComparison.Ordinal) && - (client.Contains("starbridgeScenarioDocument.LatestAsync()", StringComparison.Ordinal) || - client.Contains("starbridgeSessionDocument.LatestAsync()", StringComparison.Ordinal) || - client.Contains("catalogDocument.LatestAsync()", StringComparison.Ordinal))) - { - throw new InvalidOperationException( - "Aetheria runtime Verse client Starbridge summary document still polls sibling documents instead of sampling managed reactive client inputs."); - } - - if (client.Contains("RequireFrameAsync", StringComparison.Ordinal) || - client.Contains("Aetheria().Daemon.LatestFrame.LatestAsync()", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Aetheria runtime Verse client managed documents still bootstrap through one-shot latest-frame reads instead of the managed reactive client inputs."); - } - - if (client.Contains("_fallbackCatalog", StringComparison.Ordinal) || - client.Contains("_fallbackLoadoutTemplates", StringComparison.Ordinal) || - client.Contains("fallbackCatalog", StringComparison.Ordinal) || - client.Contains("fallbackLoadoutTemplates", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Aetheria runtime Verse client projections must use managed reactive catalog/loadout documents directly instead of carrying manual fallback snapshots."); - } - - var requiredClientStateSymbols = new[] - { - "public CultMeshStateRefResolver CreateEveSurfaceCultMeshStateRefResolver()", - "public sealed class AetheriaClientState : IDisposable", - "public CultMeshDocumentHandle Inventory(", - "public CultMeshDocumentHandle StarbridgeSession", - "CultMeshReactiveDocument? _eveStateRefFrame", - "CultMeshReactiveDocument? _eveStateRefHealth", - "CultMeshReactiveDocument? _eveStateRefCommandBoundary", - "CultMeshReactiveDocument? _eveStateRefCatalog", - "() => ReadOptionalReactive(ref _eveStateRefFrame, DaemonFrame)", - "() => ReadOptionalReactive(ref _eveStateRefHealth, Health)", - "() => ReadOptionalReactive(ref _eveStateRefCommandBoundary, CommandBoundary)", - "() => ReadOptionalReactive(ref _eveStateRefCatalog, Catalog)", - "private static TDocument? ReadOptionalReactive(", - "reactive ??= handle.Reactive()", - "return reactive.Current", - "public void Dispose()", - "_eveStateRefFrame?.Dispose()", - "_eveStateRefHealth?.Dispose()", - "_eveStateRefCommandBoundary?.Dispose()", - "_eveStateRefCatalog?.Dispose()", - "AetheriaRuntimeStateRefResolver.CreateEveSurfaceCultMeshStateRefResolver(" - }; - var missingClientStateSymbols = requiredClientStateSymbols - .Where(symbol => !clientState.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (missingClientStateSymbols.Length > 0) - { - throw new InvalidOperationException( - "AetheriaClientState must own Eve state-ref resolver creation through managed typed documents: " + - string.Join(", ", missingClientStateSymbols)); - } - - if (clientState.Contains("var frameTask = Daemon.LatestFrameDocumentAsync()", StringComparison.Ordinal) || - clientState.Contains("public readonly struct AetheriaClientZone", StringComparison.Ordinal) || - clientState.Contains("public readonly struct AetheriaClientEntity", StringComparison.Ordinal) || - clientState.Contains("public static AetheriaClientZone Zone(", StringComparison.Ordinal) || - clientState.Contains("public static AetheriaClientEntity Entity(", StringComparison.Ordinal) || - clientState.Contains("Document(AetheriaClientZone", StringComparison.Ordinal) || - clientState.Contains("Document(AetheriaClientEntity", StringComparison.Ordinal) || - clientState.Contains("var healthTask = Daemon.LatestHealthAsync()", StringComparison.Ordinal) || - clientState.Contains("var commandBoundaryTask = Daemon.LatestCommandBoundaryAsync()", StringComparison.Ordinal) || - clientState.Contains("LatestAsync()", StringComparison.Ordinal) || - clientState.Contains("LatestAsync()", StringComparison.Ordinal) || - clientState.Contains("LatestAsync()", StringComparison.Ordinal) || - clientState.Contains("Latest()", StringComparison.Ordinal) || - clientState.Contains("LatestCatalog)", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "AetheriaClientState must expose native managed typed documents instead of bootstrapping one-shot latest snapshots or scoped wrapper handles."); - } - - if (!client.Contains("_aetheriaState?.Dispose()", StringComparison.Ordinal) || - !client.Contains("_aetheriaState = null", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "AetheriaRuntimeVerseClient must dispose AetheriaClientState so cached reactive state-ref resolver documents do not leak."); - } - - var requiredStateRefResolverSymbols = new[] - { - "public static class AetheriaRuntimeStateRefResolver", - "TryResolveDaemonStateRef(", - "TryResolveDaemonItemStatRef(" - }; - var missingStateRefResolverSymbols = requiredStateRefResolverSymbols - .Where(symbol => !stateRefResolver.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (missingStateRefResolverSymbols.Length > 0) - { - throw new InvalidOperationException( - "Aetheria runtime state-ref resolver is missing typed Eve state-ref symbols: " + - string.Join(", ", missingStateRefResolverSymbols)); - } - - if (stateRefResolver.Contains("public static class AetheriaRuntimeStateReader", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "AetheriaRuntimeStateReader compatibility wrapper still exists; use AetheriaRuntimeStateRefResolver and managed AetheriaClient documents."); - } - - if (client.Contains("AetheriaRuntimeStateReader.CreateEveSurfaceCultMeshStateRefResolver", StringComparison.Ordinal) || - client.Contains("AetheriaRuntimeStateReader.CreateEveSurfaceStateRefResolver", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "AetheriaRuntimeVerseClient still routes Eve state-ref resolution through the file-backed compatibility reader."); - } - - if (client.Contains("public Func CreateEveSurfaceStateRefResolver()", StringComparison.Ordinal) || - client.Contains("public CultMeshStateRefResolver CreateEveSurfaceCultMeshStateRefResolver()", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "AetheriaRuntimeVerseClient still exposes Eve state-ref resolver compatibility factories; use AetheriaClientState managed documents."); - } - - if (client.Contains("GetObservedDaemonStateAsync()", StringComparison.Ordinal) || - client.Contains("GetLatestAuthoritativeRunFrameAsync()", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "AetheriaRuntimeVerseClient still exposes daemon observation compatibility helpers; use AetheriaClient managed documents instead."); - } - - if (stateRefResolver.Contains("TryReadDaemonRenderView", StringComparison.Ordinal) || - stateRefResolver.Contains("TryReadDaemonSoaView(", StringComparison.Ordinal) || - stateRefResolver.Contains("ResolveEveSurfaceStateRef(", StringComparison.Ordinal) || - stateRefResolver.Contains("TryResolveEveSurfaceStateRef(", StringComparison.Ordinal) || - stateRefResolver.Contains("CreateEveSurfaceCultMeshStateRefResolver(\r\n string stateFilePath", StringComparison.Ordinal) || - stateRefResolver.Contains("CreateEveSurfaceCultMeshStateRefResolver(\n string stateFilePath", StringComparison.Ordinal) || - stateRefResolver.Contains("ReadEveSurface(", StringComparison.Ordinal) || - stateRefResolver.Contains("TryReadDaemonGameSurface(", StringComparison.Ordinal) || - stateRefResolver.Contains("TryReadDaemonGameTuiSurface(", StringComparison.Ordinal) || - stateRefResolver.Contains("TryReadDaemonEditorSurface(", StringComparison.Ordinal) || - stateRefResolver.Contains("TryReadDaemonEditorTuiSurface(", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Aetheria runtime state-ref resolver still exposes daemon acquisition that belongs on managed AetheriaClient documents."); - } - - var starbridgeSummaryStart = client.IndexOf( - "Task StarbridgeSummaryAsync", - StringComparison.Ordinal); - if (starbridgeSummaryStart < 0) - { - throw new InvalidOperationException( - "AetheriaRuntimeVerseClient no longer exposes the managed Starbridge summary document."); - } - - var indexedDocumentStart = client.IndexOf("static string IndexedDocumentId", starbridgeSummaryStart, StringComparison.Ordinal); - var starbridgeSummaryBlock = indexedDocumentStart > starbridgeSummaryStart - ? client.Substring(starbridgeSummaryStart, indexedDocumentStart - starbridgeSummaryStart) - : client.Substring(starbridgeSummaryStart); - if (!starbridgeSummaryBlock.Contains("RequireManagedCatalog()", StringComparison.Ordinal) || - starbridgeSummaryBlock.Contains("catalogDocument.LatestAsync()", StringComparison.Ordinal) || - starbridgeSummaryBlock.Contains("BootstrapRuntimeCatalog", StringComparison.Ordinal) || - starbridgeSummaryBlock.Contains("AetheriaRuntimeCatalogStore.OpenReadOnly", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Starbridge summary document must use the managed runtime catalog document instead of reopening the catalog store."); - } - - var requiredClientBootstrapSymbols = new[] - { - "BootstrapCatalogDocument(", - "CatalogBootstrapSource(", - "managed Aetheria document bootstrap seed", - "BootstrapRuntimeCatalog", - "BootstrapLoadoutTemplates", - "BootstrapPlayerSettings", - "BootstrapVerseHostSettings", - "return AetheriaRuntimeCatalogStore.OpenReadOnly(StatePath);", - "AetheriaRuntimeCatalogStore.ReadLoadoutTemplates(StatePath)", - "AetheriaRuntimeCatalogStore.ReadPlayerSettings(StatePath)", - "AetheriaRuntimeCatalogStore.ReadVerseHostSettings(StatePath)" - }; - var missingClientBootstrapSymbols = requiredClientBootstrapSymbols - .Where(symbol => !client.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (missingClientBootstrapSymbols.Length > 0) - { - throw new InvalidOperationException( - "AetheriaRuntimeVerseClient must name catalog/settings file reads as bootstrap seeds for managed documents: " + - string.Join(", ", missingClientBootstrapSymbols)); - } - - var forbiddenClientCompatibilityStoreBypasses = new[] - { - "return Task.FromResult(AetheriaRuntimeCatalogStore.ReadPlayerSettings(StatePath));", - "return Task.FromResult(AetheriaRuntimeCatalogStore.ReadVerseHostSettings(StatePath));", - "public static class AetheriaRuntimeBootstrapDocuments", - "public AetheriaRuntimeCatalogSnapshot ReadRuntimeCatalog(", - "public AetheriaRuntimeLoadoutTemplatesDocument ReadLoadoutTemplates(", - "public AetheriaRuntimePlayerSettingsDocument ReadPlayerSettings(", - "public AetheriaRuntimeVerseHostSettingsDocument ReadVerseHostSettings(", - "public Task ReadRuntimeCatalogAsync(", - "public Task ReadLoadoutTemplatesAsync(", - "public Task ReadPlayerSettingsAsync(", - "public Task ReadVerseHostSettingsAsync(", - "var catalogDocument = CatalogDocument(", - "var loadoutTemplatesDocument = CatalogDocument(", - "CultMeshDocumentHandle CatalogDocument(", - "projected Aetheria catalog document", - "Aetheria typed catalog state", - "public async Task GetProviderAdvertisementAsync()", - "public async Task GetHealthAsync()", - "public async Task GetCommandBoundaryAsync()", - "public async Task GetVerseAuthorityPolicyAsync()", - "public async Task GetLatestFrameAsync()", - "public async Task GetLatestSoaViewAsync()", - "public async Task GetStarbridgeScenarioAsync()", - "public async Task GetStarbridgeSessionAsync()", - "public async Task GetStarbridgePlayerSeatAsync(string seatId)", - "public async Task PutStarbridgeScenarioAsync(", - "public async Task PutStarbridgeSessionAsync(", - "public async Task PutStarbridgePlayerSeatAsync(", - "public async Task GetDaemonGameSurfaceAsync()", - "public async Task GetDaemonGameTuiSurfaceAsync()", - "public async Task GetDaemonEditorSurfaceAsync()", - "public async Task GetDaemonEditorTuiSurfaceAsync()", - "public AetheriaRuntimeCatalogSnapshot OpenRuntimeCatalog()", - "public async Task GetPlayerSettingsAsync()", - "public async Task GetVerseHostSettingsAsync()", - "() => Task.FromResult(OpenRuntimeCatalog())", - "OpenRuntimeCatalog());", - "var frame = await GetLatestFrameAsync().ConfigureAwait(false);", - "var soaView = await GetLatestSoaViewAsync().ConfigureAwait(false);", - "var frameTask = GetLatestFrameAsync();", - "var healthTask = GetHealthAsync();", - "var commandBoundaryTask = GetCommandBoundaryAsync();", - "return Database.GetAsync", - "return Database.GetAsync", - "return Database.GetAsync", - "return Database.GetAsync", - "return Database.GetAsync", - "return Database.GetAsync", - "return Database.GetAsync", - "return Database.GetAsync", - "return Database.GetAsync", - "return Database.GetAsync(AetheriaRuntimeVerseRecordKeys.DaemonGameSurface);", - "return Database.GetAsync(AetheriaRuntimeVerseRecordKeys.DaemonGameTuiSurface);", - "return Database.GetAsync(AetheriaRuntimeVerseRecordKeys.DaemonEditorSurface);", - "return Database.GetAsync(AetheriaRuntimeVerseRecordKeys.DaemonEditorTuiSurface);" - }; - var clientCompatibilityStoreBypassHits = forbiddenClientCompatibilityStoreBypasses - .Where(symbol => client.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (clientCompatibilityStoreBypassHits.Length > 0) - { - throw new InvalidOperationException( - "AetheriaRuntimeVerseClient compatibility reads still bypass managed typed documents: " + - string.Join(", ", clientCompatibilityStoreBypassHits)); - } - - if (client.Contains("public async Task SubmitDaemonCommandAsync(", StringComparison.Ordinal) || - client.Contains("public async Task SubmitEveCommandAsync(", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "AetheriaRuntimeVerseClient exposes raw command-envelope submission publicly; clients should use AetheriaClient.Control and AetheriaClient.Ui."); - } - - if (client.Contains("AetheriaRuntimeVerseDocument", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "AetheriaRuntimeVerseClient reintroduced a private document handle; use shared CultMeshMutableStatePointer instead."); - } - - var deletedCommandPortPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeCommandPort.cs"); - if (File.Exists(deletedCommandPortPath)) - { - throw new InvalidOperationException( - "AetheriaRuntimeCommandPort.cs has returned. Submit typed commands through AetheriaRuntimeVerseClient."); - } - - var requiredDocSymbols = new[] - { - "AetheriaRuntimeVerseClient", - "WatchRecord(CultRecordKey)", - "MutableDocument(CultRecordKey)", - "Unity", - "Verse records", - "EveSurfaceDocument", - "same typed records" - }; - var missingDocSymbols = requiredDocSymbols - .Where(symbol => !doc.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (missingDocSymbols.Length > 0) - { - throw new InvalidOperationException( - "Aetheria Verse client contract note is missing required architecture terms: " + - string.Join(", ", missingDocSymbols)); - } -} - -static void RequireTypedEveCommandBodies(string root) -{ - var eveCommandDocumentPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeEveCommandDocument.cs"); - var eveCommandClientPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeEveCommandClient.cs"); - var verseClientPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeVerseClient.cs"); - var aetheriaClientPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaClient.cs"); - var aetheriaUiPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaUi.cs"); - var runtimeCommandPortPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeCommandPort.cs"); - var eveCommandBridgePath = Path.Combine(root, "Aetheria.State", "AetheriaEveCommandBridge.cs"); - var stateNodePath = Path.Combine(root, "Aetheria.State", "AetheriaStateNode.cs"); - var documentRegistryPath = Path.Combine(root, "Aetheria.State", "AetheriaDocumentRegistry.cs"); - var actionGameManagerPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "ActionGameManager.cs"); - var mainMenuPath = Path.Combine(root, "Assets", "Scripts", "UI", "MainMenu.cs"); - var evePresenterPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.eve-runtime", "Runtime", "AetheriaEveSurfacePresenter.cs"); - - var requiredFiles = new[] { eveCommandDocumentPath, eveCommandClientPath, verseClientPath, aetheriaClientPath, aetheriaUiPath, eveCommandBridgePath, stateNodePath, documentRegistryPath, actionGameManagerPath, mainMenuPath, evePresenterPath }; - var missingFiles = requiredFiles - .Where(path => !File.Exists(path)) - .Select(path => Path.GetRelativePath(root, path)) - .ToArray(); - if (missingFiles.Length > 0) - { - throw new InvalidOperationException( - "Typed Eve command bodies cannot be verified because required files are missing: " + - string.Join(", ", missingFiles)); - } - - var eveCommandDocument = File.ReadAllText(eveCommandDocumentPath); - var eveCommandClient = File.ReadAllText(eveCommandClientPath); - var verseClient = File.ReadAllText(verseClientPath); - var aetheriaClient = File.ReadAllText(aetheriaClientPath); - var aetheriaUi = File.ReadAllText(aetheriaUiPath); - var normalizedEveCommandClient = eveCommandClient.Replace("\r\n", "\n", StringComparison.Ordinal); - var eveCommandBridge = File.ReadAllText(eveCommandBridgePath); - var stateNode = File.ReadAllText(stateNodePath); - var documentRegistry = File.ReadAllText(documentRegistryPath); - var actionGameManager = File.ReadAllText(actionGameManagerPath); - var evePresenter = File.ReadAllText(evePresenterPath); - - var requiredDocumentSymbols = new[] - { - "public enum AetheriaRuntimeEveCommandKind", - "public sealed class AetheriaRuntimePlayerSettingsCommandBody", - "public sealed class AetheriaRuntimeInputSettingsCommandBody", - "public sealed class AetheriaRuntimeTradeValuePolicyCommandBody", - "public AetheriaRuntimeEveCommandKind Kind", - "public AetheriaRuntimePlayerSettingsCommandBody PlayerSettings", - "public AetheriaRuntimeInputSettingsCommandBody InputSettings", - "public AetheriaRuntimeLoadoutTemplateCommit? LoadoutTemplate", - "public AetheriaRuntimeTradeValuePolicyCommandBody TradeValuePolicy", - "public CultMeshOperationInvocationDescriptor Invocation", - "public CultMeshOperationPayload Payload", - "public CultMeshOperationInvocationRecord Operation", - "public Dictionary Payload", - "SetTradeValueQualityMinimum", - "SetTradeValueTierQuality", - "[CultDocument(\"gamecult.eve.command\", \"gamecult.eve.command.v1\")]" - }; - var missingDocumentSymbols = requiredDocumentSymbols - .Where(symbol => !eveCommandDocument.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (missingDocumentSymbols.Length > 0) - { - throw new InvalidOperationException( - "Eve submitted command document is missing typed command bodies: " + - string.Join(", ", missingDocumentSymbols)); - } - - var forbiddenDocumentSymbols = new[] - { - "IReadOnlyDictionary Payload", - "SubmitSurfaceRequest(", - "CreateSurfaceRequestCommand(", - "public string OperationSchemaId", - "public string OperationRouteKind", - "public string OperationRouteDescription", - "public string OperationIdempotencyKey", - "[Key(11)]", - "[Key(12)]", - "[Key(13)]", - "[Key(14)]", - "[Key(15)]" - }; - var forbiddenDocumentHits = forbiddenDocumentSymbols - .Where(symbol => eveCommandDocument.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (forbiddenDocumentHits.Length > 0) - { - throw new InvalidOperationException( - "Eve submitted command document still persists string payload maps: " + - string.Join(", ", forbiddenDocumentHits)); - } - - var requiredTypedCommandSymbols = new[] - { - "internal static class AetheriaRuntimeEveCommands", - "public static class AetheriaRuntimeEveCommandClient", - "namespace GameCult.Aetheria.State.Verse", - "SurfaceCommandAsync(", - "internal async Task SubmitDaemonCommandAsync(", - "internal async Task SubmitEveCommandAsync(", - "CreatePlayerSettingsCommand(", - "CreateInputSettingsCommand(", - "CreateCatalogCommand(", - "CreateOperationsCommand(", - "CreateVerseHostCommand(", - "CreateLoadoutTemplateCommand(", - "CreateTradeValuePolicyCommand(", - "ReadTradeValuePolicyBody(", - "var command = OperationIdFor(request);", - "return CommandKindForSurface(request.SurfaceId ?? \"\", OperationIdFor(request));", - "private static string OperationIdFor(EveSurfaceCommandRequest request)", - "return request?.Operation?.OperationId ?? \"\";", - "CultMeshOperationInvocationDescriptor", - "CultMeshOperationPayload", - "invocation: request.Operation", - "payload: request.Payload", - "CreateInvocation(", - "CultMesh.OperationInvocationRecord(", - "Operation = invocation", - "Operation = invocationRecord", - "NormalizeInvocationRecord(", - "Payload = envelope.Payload.ToDictionary()", - "Payload = (payload ?? CultMeshOperationPayload.Empty).ToDictionary()", - "return request.Payload.GetString(key);", - "return request.Payload.GetInt32(key, defaultValue);", - "return request.Payload.GetDouble(key, defaultValue);", - "TrySendPlayerSettingsCommand(", - "TrySendInputSettingsCommand(", - "TrySendVerseHostCommand(", - "TrySendLoadoutTemplateCommand(", - "TrySendKnownSurfaceCommand(", - "TryCreateKnownSurfaceCommand(", - "CreateVerseHostCommand(CommandKindForSurface(request), clientId)", - "CommandKindForSurface(", - "CommandText(", - "ToDocument(", - "AcceptObservedAsync(", - "switch (command.Kind)", - "node.Documents()", - "AetheriaRuntimeEveCommandClient.NormalizeDocument", - "AccountedCommandIds", - "SubmitEveCommandAsync(", - "EveCommandKey(", - "typeof(AetheriaRuntimeEveCommandDocument)", - "SubmitPlayerSettingsCommand(", - "command.PlayerSettings.PlayerName", - "command.InputSettings.ActionName", - "command.InputSettings.InputSystemPath", - "command.TradeValuePolicy.Value", - "command.TradeValuePolicy.TierIndex", - "ExecuteTradeValuePolicyCommandAsync(", - "MutableDocument(AetheriaStateNode.TradeValuePolicyKey)", - "SubmitInputSettingsCommandAsync(", - "SubmitLoadoutTemplateCommandAsync(", - "SubmitKnownSurfaceCommandAsync(", - "public AetheriaUi Ui => _ui", - "public sealed class AetheriaUi", - "Task InputSettingsAsync(", - "Task SaveLoadoutTemplateAsync(", - "Task SurfaceCommandAsync(" - }; - var mainMenu = File.ReadAllText(mainMenuPath); - var typedCommandSources = eveCommandClient + "\n" + verseClient + "\n" + aetheriaClient + "\n" + aetheriaUi + "\n" + eveCommandBridge + "\n" + stateNode + "\n" + documentRegistry + "\n" + actionGameManager + "\n" + mainMenu + "\n" + evePresenter; - if (aetheriaClient.Contains("public Task SubmitInputSettingsCommandAsync(", StringComparison.Ordinal) || - aetheriaClient.Contains("public Task SubmitLoadoutTemplateCommandAsync(", StringComparison.Ordinal) || - aetheriaClient.Contains("public Task SubmitKnownSurfaceCommandAsync(", StringComparison.Ordinal) || - verseClient.Contains("public Task SubmitInputSettingsCommandAsync(", StringComparison.Ordinal) || - verseClient.Contains("public Task SubmitLoadoutTemplateCommandAsync(", StringComparison.Ordinal) || - verseClient.Contains("public Task SubmitKnownSurfaceCommandAsync(", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "AetheriaClient exposes Eve command envelopes publicly; UI callers should use AetheriaClient.Ui and receive CultMeshOperationReceipt."); - } - - if (eveCommandClient.Contains("ParseRouteKind(", StringComparison.Ordinal) || - eveCommandClient.Contains("OperationId = envelope.Invocation.OperationId", StringComparison.Ordinal) || - eveCommandClient.Contains("OperationId = invocation.OperationId", StringComparison.Ordinal) || - eveCommandClient.Contains("OperationId = invocationRecord.OperationId", StringComparison.Ordinal) || - eveCommandClient.Contains("ApplyInvocationCompatibilityFields(", StringComparison.Ordinal) || - eveCommandClient.Contains("document.OperationId", StringComparison.Ordinal) || - eveCommandClient.Contains("document.OperationSchemaId", StringComparison.Ordinal) || - eveCommandClient.Contains("document.OperationRouteKind", StringComparison.Ordinal) || - eveCommandClient.Contains("document.OperationRouteDescription", StringComparison.Ordinal) || - eveCommandClient.Contains("document.OperationIdempotencyKey", StringComparison.Ordinal) || - eveCommandClient.Contains("Payload = new System.Collections.Generic.Dictionary(\r\n envelope.Payload", StringComparison.Ordinal) || - eveCommandClient.Contains("Payload = new System.Collections.Generic.Dictionary(\n envelope.Payload", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Aetheria Eve command persistence reintroduced local CultMesh invocation/payload flattening; use CultMesh.OperationInvocationRecord and CultMeshOperationPayload.ToDictionary."); - } - - if (eveCommandClient.Contains("public static class AetheriaRuntimeEveCommands", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "AetheriaRuntimeEveCommands is public again; public Eve UI operations belong on AetheriaClient.Ui."); - } - - if (typedCommandSources.Contains("DeleteEveCommandAsync(", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Eve command documents are still consumed by deletion instead of accounted by acceptance receipts."); - } - - if (eveCommandBridge.Contains("AcceptedPaths", StringComparison.Ordinal) || - eveCommandBridge.Contains("RejectedPaths", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Eve command acceptance report still exposes path vocabulary instead of command-id receipts."); - } - - if (typedCommandSources.Contains("TradeValuePolicy()", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Eve command handling still uses the named trade-value policy helper instead of generic mutable typed document access."); - } - - var missingTypedCommandSymbols = requiredTypedCommandSymbols - .Where(symbol => !typedCommandSources.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (missingTypedCommandSymbols.Length > 0) - { - throw new InvalidOperationException( - "Eve command append/apply path is missing typed command body usage: " + - string.Join(", ", missingTypedCommandSymbols)); - } - - var forbiddenUnityEveCommandSubmitSymbols = new[] - { - "AetheriaRuntimeEveCommands.TrySendInputSettingsCommand", - "AetheriaRuntimeEveCommands.TrySendLoadoutTemplateCommand", - "AetheriaRuntimeEveCommands.TrySendKnownSurfaceCommand" - }; - var unityCommandSources = new Dictionary - { - [actionGameManagerPath] = actionGameManager, - [mainMenuPath] = mainMenu, - [evePresenterPath] = evePresenter - }; - var legacyUnityCommandHits = unityCommandSources - .SelectMany(entry => forbiddenUnityEveCommandSubmitSymbols - .Where(symbol => entry.Value.Contains(symbol, StringComparison.Ordinal)) - .Select(symbol => $"{Path.GetRelativePath(root, entry.Key)} -> {symbol}")) - .ToArray(); - if (legacyUnityCommandHits.Length > 0) - { - throw new InvalidOperationException( - "Unity-facing shells still submit Eve commands through legacy helper ports instead of AetheriaRuntimeVerseClient: " + - string.Join("; ", legacyUnityCommandHits)); - } - - var unityNamedRuntimeSources = new Dictionary - { - ["Aetheria.State.Daemon"] = File.Exists(Path.Combine(root, "Aetheria.State.Daemon", "Program.cs")) - ? File.ReadAllText(Path.Combine(root, "Aetheria.State.Daemon", "Program.cs")) - : "", - ["Aetheria.State"] = string.Join( - "\n", - Directory.EnumerateFiles(Path.Combine(root, "Aetheria.State"), "*.cs", SearchOption.AllDirectories) - .Select(File.ReadAllText)), - ["org.gamecult.aetheria.state/Runtime"] = string.Join( - "\n", - Directory.EnumerateFiles( - Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime"), - "*.cs", - SearchOption.TopDirectoryOnly) - .Select(File.ReadAllText)) - }; - var unityNamedRuntimeHits = unityNamedRuntimeSources - .Where(source => source.Value.Contains("GameCult.Aetheria.State.Unity", StringComparison.Ordinal)) - .Select(source => source.Key) - .ToArray(); - if (unityNamedRuntimeHits.Length > 0) - { - throw new InvalidOperationException( - "Daemon/shared Aetheria runtime contracts still live under a Unity-named API namespace: " + - string.Join(", ", unityNamedRuntimeHits)); - } - - if (File.Exists(runtimeCommandPortPath)) - { - throw new InvalidOperationException( - "AetheriaRuntimeCommandPort.cs has returned. Submit typed command documents through AetheriaRuntimeVerseClient."); - } - - var forbiddenSharedCommandRuntimeIds = new[] - { - "string runtimeId = \"unity-input-provider\"", - "? \"unity-input-provider\" : runtimeId" - }; - var sharedCommandRuntimeIdHits = forbiddenSharedCommandRuntimeIds - .Where(symbol => typedCommandSources.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (sharedCommandRuntimeIdHits.Length > 0) - { - throw new InvalidOperationException( - "Shared command runtime still uses Unity-specific fallback runtime ids: " + - string.Join(", ", sharedCommandRuntimeIdHits)); - } - - if (typedCommandSources.Contains("AetheriaRuntimeCommandSubmitter", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Shared command runtime still preserves the cached command submitter instead of routing typed clients through AetheriaRuntimeVerseClient."); - } - - var forbiddenUnitySubmitterSymbols = new[] - { - "AetheriaRuntimeCommandSubmitter.TrySubmitEveCommand(", - "AetheriaRuntimeCommandSubmitter.TrySubmitDaemonCommand(", - "AetheriaRuntimeEveCommandClient.ToDocument(" - }; - var unitySubmitterSources = new Dictionary - { - [Path.GetRelativePath(root, actionGameManagerPath)] = actionGameManager, - [Path.GetRelativePath(root, mainMenuPath)] = mainMenu, - [Path.GetRelativePath(root, evePresenterPath)] = evePresenter - }; - var unitySubmitterHits = unitySubmitterSources - .SelectMany(pair => forbiddenUnitySubmitterSymbols - .Where(symbol => pair.Value.Contains(symbol, StringComparison.Ordinal)) - .Select(symbol => $"{pair.Key}: {symbol}")) - .ToArray(); - if (unitySubmitterHits.Length > 0) - { - throw new InvalidOperationException( - "Unity-facing Eve command code still assembles transport documents instead of using typed send helpers: " + - string.Join(", ", unitySubmitterHits)); - } - - var forbiddenGenericSurfaceCommandSymbols = new[] - { - "SubmitSurfaceRequest(", - "CreateSurfaceRequestCommand(" - }; - var genericSurfaceCommandHits = forbiddenGenericSurfaceCommandSymbols - .Where(symbol => typedCommandSources.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (genericSurfaceCommandHits.Length > 0) - { - throw new InvalidOperationException( - "Eve command edge still exposes a generic surface-command submission back door: " + - string.Join(", ", genericSurfaceCommandHits)); - } - - var forbiddenStringCommandOverloads = new[] - { - "SubmitPlayerSettingsCommand(\n string stateFilePath,\n string command", - "SubmitInputSettingsCommand(\n string stateFilePath,\n string command", - "SubmitCatalogCommand(\n string stateFilePath,\n string command", - "SubmitOperationsCommand(\n string stateFilePath,\n string command", - "SubmitVerseHostCommand(\n string stateFilePath,\n string command", - "TrySendInputSettingsCommand(\n string stateFilePath,\n string command", - "TrySendVerseHostCommand(\n string stateFilePath,\n string command", - "CreateCatalogCommand(\n string command", - "CreateOperationsCommand(\n string command", - "CreateVerseHostCommand(\n string command" - }; - var survivingStringCommandOverloads = forbiddenStringCommandOverloads - .Where(symbol => normalizedEveCommandClient.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (survivingStringCommandOverloads.Length > 0) - { - throw new InvalidOperationException( - "Aetheria Eve command client still exposes public string command overloads instead of typed command kinds: " + - string.Join(", ", survivingStringCommandOverloads)); - } - - var forbiddenQueueSymbols = new[] - { - "public static class AetheriaRuntimeEveCommandLog", - "internal static class AetheriaRuntimeEveCommandLog", - "public static AetheriaRuntimeEveCommandEnvelope QueueCommand(", - "public static AetheriaRuntimeEveCommandEnvelope QueuePlayerSettingsCommand(", - "public static AetheriaRuntimeEveCommandEnvelope QueueInputSettingsCommand(", - "public static AetheriaRuntimeEveCommandEnvelope QueueCatalogCommand(", - "public static AetheriaRuntimeEveCommandEnvelope QueueOperationsCommand(", - "public static AetheriaRuntimeEveCommandEnvelope QueueVerseHostCommand(", - "public static AetheriaRuntimeEveCommandEnvelope QueueLoadoutTemplateCommand(", - "private static AetheriaRuntimeEveCommandEnvelope QueueTypedCommand(", - "AetheriaRuntimeEveCommandLog.", - "AetheriaRuntimeEveCommandLog.Queue", - "AetheriaRuntimeEveCommandInbox", - "GetInboxDirectory(", - "Read" + "Submitted(", - ".eve.commands", - "ReadPending(", - ".eve.pending" - }; - var forbiddenQueueSources = new Dictionary - { - ["Eve command client"] = eveCommandClient, - ["Eve command bridge"] = eveCommandBridge, - ["Aetheria state node"] = stateNode, - ["ActionGameManager"] = actionGameManager, - ["MainMenu"] = mainMenu, - ["Eve presenter"] = evePresenter - }; - var survivingQueueSymbols = forbiddenQueueSources - .SelectMany(source => forbiddenQueueSymbols - .Where(symbol => source.Value.Contains(symbol, StringComparison.Ordinal)) - .Select(symbol => $"{source.Key}: {symbol}")) - .ToArray(); - if (survivingQueueSymbols.Length > 0) - { - throw new InvalidOperationException( - "Eve command edge still exposes queue-shaped public API: " + - string.Join(", ", survivingQueueSymbols)); - } - - var rendererLogSources = new Dictionary - { - ["ActionGameManager"] = actionGameManager, - ["MainMenu"] = mainMenu, - ["Eve presenter"] = evePresenter - }; - var rendererLogHits = rendererLogSources - .Where(source => source.Value.Contains("AetheriaRuntimeEveCommandLog.", StringComparison.Ordinal)) - .Select(source => $"{source.Key}: AetheriaRuntimeEveCommandLog.") - .ToArray(); - if (rendererLogHits.Length > 0) - { - throw new InvalidOperationException( - "Renderer code still speaks to the Eve mailbox log instead of the typed Verse client path: " + - string.Join(", ", rendererLogHits)); - } - - if (actionGameManager.Contains("TryQueueRuntimeEveCommand(", StringComparison.Ordinal) || - actionGameManager.Contains("new EveSurfaceCommandRequest(", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "ActionGameManager still manufactures generic Eve surface command payloads instead of typed Eve command bodies."); - } -} - -static void RequireMainMenuVerseHostDocumentAccess(string root) -{ - var packageSnapshotPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeCatalogSnapshot.cs"); - var packageStorePath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeCatalogStore.cs"); - var runtimeStateRefResolverPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeStateRefResolver.cs"); - var mainMenuPath = Path.Combine(root, "Assets", "Scripts", "UI", "MainMenu.cs"); - var mainMenuSurfaceBuilderPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeMainMenuSurfaceBuilder.cs"); - var clientTargetSurfaceBuilderPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeClientTargetSurfaceBuilder.cs"); - - var requiredFiles = new[] - { - packageSnapshotPath, - packageStorePath, - runtimeStateRefResolverPath, - mainMenuPath, - mainMenuSurfaceBuilderPath, - clientTargetSurfaceBuilderPath - }; - - var missingFiles = requiredFiles - .Where(path => !File.Exists(path)) - .Select(path => Path.GetRelativePath(root, path)) - .ToArray(); - if (missingFiles.Length > 0) - { - throw new InvalidOperationException( - "Main-menu Verse document access cannot be verified because required files are missing: " + - string.Join(", ", missingFiles)); - } - - var packageSnapshot = File.ReadAllText(packageSnapshotPath); - var packageStore = File.ReadAllText(packageStorePath); - var runtimeStateRefResolver = File.ReadAllText(runtimeStateRefResolverPath); - var mainMenu = File.ReadAllText(mainMenuPath); - var mainMenuSurfaceBuilder = File.ReadAllText(mainMenuSurfaceBuilderPath); - var clientTargetSurfaceBuilder = File.ReadAllText(clientTargetSurfaceBuilderPath); - - var requiredSnapshotSymbols = new[] - { - "public sealed class AetheriaRuntimeVerseHostSettingsSnapshot", - "public string VerseId", - "public string CultMeshAddress", - "public string Visibility" - }; - var missingSnapshotSymbols = requiredSnapshotSymbols - .Where(symbol => !packageSnapshot.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (missingSnapshotSymbols.Length > 0) - { - throw new InvalidOperationException( - "Unity runtime snapshot does not expose typed verse-host settings: " + - string.Join(", ", missingSnapshotSymbols)); - } - - var requiredStoreSymbols = new[] - { - "VerseHostSettingsSchema = \"aetheria.verse_host_settings\"", - "VerseHostSettingsKey = \"global:aetheria.verse_host_settings.v1\"", - "ReadVerseHostSettings(string stateFilePath)", - "ReadVerseHostSettingsPayload", - "AetheriaRuntimeVerseHostSettingsSnapshot" - }; - var missingStoreSymbols = requiredStoreSymbols - .Where(symbol => !packageStore.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (missingStoreSymbols.Length > 0) - { - throw new InvalidOperationException( - "Unity runtime store does not preserve typed verse-host read authority: " + - string.Join(", ", missingStoreSymbols)); - } - - var requiredMainMenuSymbols = new[] - { - "AetheriaUnityRuntimeClientProvider", - ".RuntimeState(stateBoot, \"unity-main-menu\")", - ".MainMenuSurface(surfaceId, CanOpenRuntimeInputScreen(), InGame)", - ".Latest()", - "ResolveMainMenuSurface(" - }; - var requiredBuilderSymbols = new[] - { - "public static AetheriaRuntimeSurfaceDocument BuildRoot(", - "AetheriaRuntimeStateBootReport stateBoot", - "AetheriaRuntimeVerseHostSettingsDocument verseHost", - "GravitySurface($", - "AetheriaRuntimeDaemonSchemas.RenderSplatsViewport", - "AetheriaRuntimeDaemonSchemas.GravityViewport", - "AetheriaRuntimeDaemonSchemas.ObjectsViewport", - "new AetheriaRuntimeEmbeddedDocumentSlot(", - "\"AETHERIA\"", - "\"STARBRIDGE\"", - "\"Aetheria Starbridge\"" - }; - var requiredClientTargetBuilderSymbols = new[] - { - "Build(", - "AetheriaRuntimeStateBootReport stateBoot", - "\"Transport\"", - "\"Target Source\"", - "\"Verse\"", - "\"Visibility\"", - "\"CultMesh\"", - "Client target edits persist through the managed target document. Visibility changes append provider-owned Eve requests against the selected Verse target." - }; - var missingMainMenuSymbols = requiredMainMenuSymbols - .Where(symbol => !mainMenu.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (missingMainMenuSymbols.Length > 0) - { - throw new InvalidOperationException( - "MainMenu no longer lowers daemon-owned Verse identity through its Eve shell: " + - string.Join(", ", missingMainMenuSymbols)); - } - if (mainMenu.Contains(".ReactiveVerseHostSettings(", StringComparison.Ordinal) || - mainMenu.Contains("CultMeshReactiveDocument", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "MainMenu should sample daemon-owned Verse identity through the named current typed document."); - } - var missingBuilderSymbols = requiredBuilderSymbols - .Where(symbol => !mainMenuSurfaceBuilder.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - var missingClientTargetBuilderSymbols = requiredClientTargetBuilderSymbols - .Where(symbol => !clientTargetSurfaceBuilder.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (missingBuilderSymbols.Length > 0) - { - throw new InvalidOperationException( - "Shared main-menu surface builder no longer lowers daemon-owned Verse identity through its Eve shell: " + - string.Join(", ", missingBuilderSymbols)); - } - if (missingClientTargetBuilderSymbols.Length > 0) - { - throw new InvalidOperationException( - "Client-target surface builder no longer owns the typed Verse-host document merge: " + - string.Join(", ", missingClientTargetBuilderSymbols)); - } - - if (mainMenu.Contains("AetheriaRuntimeMainMenuSurfaceBuilder.BuildRoot(", StringComparison.Ordinal) || - mainMenu.Contains("AetheriaRuntimeClientTargetSurfaceBuilder.Build(", StringComparison.Ordinal) || - mainMenu.Contains("AetheriaRuntimeMainMenuSurfaceBuilder.ProjectVerseSettings(", StringComparison.Ordinal) || - mainMenuSurfaceBuilder.Contains("ProjectVerseSettings(", StringComparison.Ordinal) || - mainMenu.Contains("new AetheriaRuntimeClientTargetSurfaceState(", StringComparison.Ordinal) || - mainMenuSurfaceBuilder.Contains("new AetheriaRuntimeClientTargetSurfaceState(", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Unity MainMenu must consume daemon-published main-menu surfaces instead of reintroducing menu-local client-target state shaping."); - } -} - -static void RequireMainMenuContinueRunState(string root) -{ - var mainMenuPath = Path.Combine(root, "Assets", "Scripts", "UI", "MainMenu.cs"); - var mainMenu = File.Exists(mainMenuPath) - ? File.ReadAllText(mainMenuPath) - : throw new InvalidOperationException("Cannot verify main-menu Continue path; MainMenu.cs is missing."); - - var actionGameManagerPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "ActionGameManager.cs"); - var actionGameManager = File.Exists(actionGameManagerPath) - ? File.ReadAllText(actionGameManagerPath) - : throw new InvalidOperationException("Cannot verify Continue run authority; ActionGameManager.cs is missing."); - var packageSnapshotPath = Path.Combine( - root, - "Packages", - "org.gamecult.aetheria.state", - "Runtime", - "AetheriaRuntimeCatalogSnapshot.cs"); - var packageSnapshot = File.Exists(packageSnapshotPath) - ? File.ReadAllText(packageSnapshotPath) - : throw new InvalidOperationException("Cannot verify Continue entity identity; package runtime snapshots are missing."); - var runtimeStateRefResolverPath = Path.Combine( - root, - "Packages", - "org.gamecult.aetheria.state", - "Runtime", - "AetheriaRuntimeStateRefResolver.cs"); - var runtimeStateRefResolver = File.Exists(runtimeStateRefResolverPath) - ? File.ReadAllText(runtimeStateRefResolverPath) - : throw new InvalidOperationException("Cannot verify Continue entity readback; shared runtime state-ref resolver is missing."); - var packageStorePath = Path.Combine( - root, - "Packages", - "org.gamecult.aetheria.state", - "Runtime", - "AetheriaRuntimeCatalogStore.cs"); - var packageStore = File.Exists(packageStorePath) - ? File.ReadAllText(packageStorePath) - : throw new InvalidOperationException("Cannot verify Continue entity payload readback; package runtime store is missing."); - var deletedObservedDockingIndexPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "AetheriaUnityObservedDockingIndex.cs"); - if (File.Exists(deletedObservedDockingIndexPath)) - { - throw new InvalidOperationException( - "AetheriaUnityObservedDockingIndex is dead state-access chaff; read current typed docking/refit documents directly and adapt Unity objects at the caller edge."); - } - var currentEntityPresentationPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "AetheriaUnityCurrentEntityPresentation.cs"); - var currentEntityPresentation = File.Exists(currentEntityPresentationPath) - ? File.ReadAllText(currentEntityPresentationPath) - : throw new InvalidOperationException("Cannot verify Continue entity presentation; AetheriaUnityCurrentEntityPresentation.cs is missing."); - var currentEntityBinderPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "AetheriaUnityCurrentEntityBinder.cs"); - var currentEntityBinder = File.Exists(currentEntityBinderPath) - ? File.ReadAllText(currentEntityBinderPath) - : throw new InvalidOperationException("Cannot verify Continue current entity binder; AetheriaUnityCurrentEntityBinder.cs is missing."); - var targetPresentationPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "AetheriaUnityTargetPresentation.cs"); - var targetPresentation = File.Exists(targetPresentationPath) - ? File.ReadAllText(targetPresentationPath) - : throw new InvalidOperationException("Cannot verify Continue target presentation; AetheriaUnityTargetPresentation.cs is missing."); - var deletedObservedTargetQueryPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "AetheriaUnityObservedTargetQuery.cs"); - if (File.Exists(deletedObservedTargetQueryPath)) - { - throw new InvalidOperationException( - "AetheriaUnityObservedTargetQuery is dead contact-query chaff; target presentation should read typed zone contacts directly."); - } - var observedFrameApplierPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "AetheriaUnityObservedFrameApplier.cs"); - var observedFrameApplier = File.Exists(observedFrameApplierPath) - ? File.ReadAllText(observedFrameApplierPath) - : throw new InvalidOperationException("Cannot verify Continue frame application; AetheriaUnityObservedFrameApplier.cs is missing."); - var pilotFrameControllerPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "AetheriaUnityPilotFrameController.cs"); - var pilotFrameController = File.Exists(pilotFrameControllerPath) - ? File.ReadAllText(pilotFrameControllerPath) - : throw new InvalidOperationException("Cannot verify Continue pilot frame controller; AetheriaUnityPilotFrameController.cs is missing."); - var legacyUnityDaemonEntitySnapshotProjectorPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "AetheriaUnityDaemonEntitySnapshotProjector.cs"); - if (File.Exists(legacyUnityDaemonEntitySnapshotProjectorPath)) - { - throw new InvalidOperationException( - "Unity still owns daemon entity snapshot projection; package runtime should lower typed daemon entity snapshots."); - } - - var zoneRenderDocumentsPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeGameViewportDocuments.cs"); - var zoneRenderDocuments = File.Exists(zoneRenderDocumentsPath) - ? File.ReadAllText(zoneRenderDocumentsPath) - : throw new InvalidOperationException("Cannot verify Continue entity snapshot access; AetheriaRuntimeGameViewportDocuments.cs is missing."); - var daemonEntitySnapshotProjectorPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeEntitySnapshotProjector.cs"); - if (File.Exists(daemonEntitySnapshotProjectorPath)) - { - throw new InvalidOperationException( - "AetheriaRuntimeEntitySnapshotProjector is dead access chaff; AetheriaRuntimeZoneRenderDocument owns managed entity snapshot access."); - } - var observedEntityRestorerPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "AetheriaUnityObservedEntityRestorer.cs"); - var observedEntityRestorer = File.Exists(observedEntityRestorerPath) - ? File.ReadAllText(observedEntityRestorerPath) - : throw new InvalidOperationException("Cannot verify Continue entity projection; AetheriaUnityObservedEntityRestorer.cs is missing."); - var entityBlueprintMaterializerPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "AetheriaUnityEntityBlueprintMaterializer.cs"); - var entityBlueprintMaterializer = File.Exists(entityBlueprintMaterializerPath) - ? File.ReadAllText(entityBlueprintMaterializerPath) - : throw new InvalidOperationException("Cannot verify Continue entity construction materialization; AetheriaUnityEntityBlueprintMaterializer.cs is missing."); - var observedZoneContextFactoryPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "AetheriaUnityObservedZoneContextFactory.cs"); - var observedZoneContextFactory = File.Exists(observedZoneContextFactoryPath) - ? File.ReadAllText(observedZoneContextFactoryPath) - : throw new InvalidOperationException("Cannot verify Continue zone context projection; AetheriaUnityObservedZoneContextFactory.cs is missing."); - var gameplaySceneWiringPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "AetheriaUnityGameplaySceneWiring.cs"); - var gameplaySceneWiring = File.Exists(gameplaySceneWiringPath) - ? File.ReadAllText(gameplaySceneWiringPath) - : throw new InvalidOperationException("Cannot verify Continue scene wiring; AetheriaUnityGameplaySceneWiring.cs is missing."); - - var requiredMenuSymbols = new[] - { - "ResolveSectorMap", - "AetheriaUnityRuntimeClientProvider", - ".RuntimeState(stateBoot, \"unity-main-menu\")", - ".SectorMap", - ".Latest()", - "ContinueGame()", - "SceneManager.LoadScene(\"ARPG\")" - }; - - var missingMenuSymbols = requiredMenuSymbols - .Where(symbol => !mainMenu.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - - if (missingMenuSymbols.Length > 0) - { - throw new InvalidOperationException( - "MainMenu Continue no longer selects an authoritative daemon frame: " + - string.Join(", ", missingMenuSymbols)); - } - - if (mainMenu.Contains("PrepareDocumentAccess(stateBoot)", StringComparison.Ordinal) || - mainMenu.Contains(".ReactiveSectorMap(", StringComparison.Ordinal) || - mainMenu.Contains("CultMeshReactiveDocument", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "MainMenu Continue should sample the typed sector map through named current state access."); - } - - if (mainMenu.Contains("AddButton(\"Continue\", null)", StringComparison.Ordinal)) - { - throw new InvalidOperationException("MainMenu Continue regressed to a null button."); - } - - var requiredGameplaySymbols = new[] - { - "ObservedFrameApplier.ApplyLatestZoneRender()", - "private AetheriaUnityGameplayLoopShell GameplayLoopShell =>", - "GameplayLoopShell.Tick(Time.deltaTime, Time.time)", - "GameplayLoopShell.LateTick()", - "ApplyLatestZoneRender = () => ObservedFrameApplier.ApplyLatestZoneRender()", - "private AetheriaUnityObservedFrameApplier ObservedFrameApplier =>", - "ObservedZoneContextFactory = new AetheriaUnityObservedZoneContextFactory(", - "boot.ObservedGalaxy", - "ObservedZoneContextFactory.ResolveGalaxyZone", - "private AetheriaUnityObservedZoneContextFactory ObservedZoneContextFactory { get; set; }", - "entity => CurrentEntityBinder.RestoreBinding(entity)", - "ResolveObservedTarget = entity => _targetPresentation.GetObservedTarget(entity)", - "TargetPresentation = _targetPresentation", - "private readonly AetheriaUnityPresentationEntityIndex _presentationEntityIndex", - "private AetheriaUnityEntityBlueprintMaterializer EntityBlueprintMaterializer =>", - "EntityBlueprintMaterializer.MaterializeObservedEntity", - "_loadoutItemFactory.CreateLoadoutItem", - "ResolveDockParent = ResolveDockParentFromCurrentDocking", - "CurrentDockingSnapshot()", - "private AetheriaClientState _gameplayState;", - "AetheriaUnityRuntimeClientProvider.RuntimeState(\"unity-action-game-manager\")", - "GameplayState.CurrentDocking.Latest()", - "IsCurrentEntityObservedUndocked(", - "private AetheriaUnityCurrentEntityBinder CurrentEntityBinder =>", - "private readonly AetheriaUnityCurrentEntityPresentation _currentEntityPresentation", - "CurrentEntityPresentation = _currentEntityPresentation", - "private AetheriaUnityPilotFrameController PilotFrameController =>", - "PilotFrameController = PilotFrameController", - "SceneWiring.ConfigureCurrentEntityPresentation(_currentEntityPresentation, boot.RuntimeCatalog)", - "SceneWiring.ConfigureTargetPresentation(" - }; - - if (actionGameManager.Contains("_authoritativeDaemonEntities", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Unity entity wrappers are observed daemon facades, not authoritative state; keep the daemon as authority."); - } - - var forbiddenGameplayCatalogPolicySymbols = new[] - { - "private static AetheriaRuntimeCatalogSnapshot _runtimeCatalog", - "FindTypedRuntimeItem(", - "_runtimeCatalog?.FindItem(", - "ArticulatedWeaponBehaviorKinds", - "_targetPresentation.ResolveTypedRuntimeItem" - }; - var gameplayCatalogPolicyHits = forbiddenGameplayCatalogPolicySymbols - .Where(symbol => actionGameManager.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (gameplayCatalogPolicyHits.Length > 0) - { - throw new InvalidOperationException( - "ActionGameManager still owns runtime catalog presentation policy instead of injecting the boot catalog into Unity presentation helpers: " + - string.Join(", ", gameplayCatalogPolicyHits)); - } - - var requiredZoneRenderSnapshotSymbols = new[] - { - "public AetheriaRuntimeEntitySnapshot[] CreateEntitySnapshots()", - "public static AetheriaRuntimeEntitySnapshot[] CreateEntitySnapshots(", - "public static string EntityRecordKey(string runId, int zoneIndex, int entityIndex)", - "new AetheriaRuntimeEntitySnapshot(", - "entity.EntityIndex,", - "CreateEntityItemSlots(entity.Equipment)", - "CreateWeaponStates(runId, zoneIndex, entity.WeaponStates)", - "CreateBehaviorStates(entity.BehaviorStates)", - "CreateCargoBays(entity.CargoContents)", - "new AetheriaRuntimeEntityContactSnapshot(" - }; - var missingZoneRenderSnapshotSymbols = requiredZoneRenderSnapshotSymbols - .Where(symbol => !zoneRenderDocuments.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (missingZoneRenderSnapshotSymbols.Length > 0) - { - throw new InvalidOperationException( - "Managed daemon entity snapshot access must live on AetheriaRuntimeZoneRenderDocument instead of a projector-shaped helper: " + - string.Join(", ", missingZoneRenderSnapshotSymbols)); - } - - var forbiddenManagerSnapshotProjectionSymbols = new[] - { - "private static AetheriaRuntimeEntitySnapshot[] CreateDaemonEntitySnapshots", - "private static AetheriaRuntimeEntitySnapshot CreateDaemonEntitySnapshot", - "private static AetheriaRuntimeEntityItemSlotSnapshot[] CreateDaemonItemSlots", - "private static AetheriaRuntimeCargoBayLoadoutSnapshot[] CreateDaemonCargoBays", - "private static AetheriaRuntimeWeaponStateSnapshot[] CreateDaemonWeaponStates", - "private static AetheriaRuntimeBehaviorStateSnapshot[] CreateDaemonBehaviorStates", - "private static string DaemonEntityRecordKey(", - "private static int EntityIndexFromRecordKey(" - }; - var managerSnapshotProjectionHits = forbiddenManagerSnapshotProjectionSymbols - .Where(symbol => actionGameManager.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (managerSnapshotProjectionHits.Length > 0) - { - throw new InvalidOperationException( - "ActionGameManager still owns daemon entity snapshot projection internals: " + - string.Join(", ", managerSnapshotProjectionHits)); - } - - var requiredObservedEntityRestorerSymbols = new[] - { - "public sealed class AetheriaUnityObservedEntityRestorer", - "public bool TryApplyInPlace(", - "public void Replace(", - "EntityConstructionBlueprintMaterializer.MaterializeObservedFromBlueprint(_itemManager, zone, blueprint)", - "entity.RestoreStatGrids(entitySnapshot.StatGrids)", - "entity.RestoreThermalExposure((float)entitySnapshot.Heatstroke, (float)entitySnapshot.Hypothermia)", - "RestoreActiveConsumables(entity, entitySnapshot)", - "activeEffectIds.Add(activeConsumable.EffectId)", - "entity.RetainActiveConsumables(activeEffectIds)", - "RestoreRuntimeBehaviorState(entity, entitySnapshot, restoredEntities)", - "_entityIndex.PresentationEntitiesByRecordKey", - "ResolveRuntimeBehavior(entity, weaponState.OwnerKind, weaponState.OwnerIndex, weaponState.BehaviorIndex)", - "lockWeapon.RestoreRuntimeState(", - "drive.RestoreRuntimeState(", - "resourceScanner.RestoreRuntimeState(", - "_entityIndex.RefreshDaemonIndex();" - }; - var missingObservedEntityRestorerSymbols = requiredObservedEntityRestorerSymbols - .Where(symbol => !observedEntityRestorer.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (missingObservedEntityRestorerSymbols.Length > 0) - { - throw new InvalidOperationException( - "Observed Unity entity mutation/restoration must live in AetheriaUnityObservedEntityRestorer instead of ActionGameManager: " + - string.Join(", ", missingObservedEntityRestorerSymbols)); - } - - var forbiddenManagerFacadeProjectionSymbols = new[] - { - "private bool CanApplyDaemonEntitySnapshotsInPlace(", - "private void ApplyDaemonEntitySnapshotsInPlace(", - "private void ReplaceObservedEntitySnapshotsFromTypedSnapshots(", - "private void RestoreActiveConsumablesFromTypedEntitySnapshot(", - "private void RestoreRuntimeBehaviorStateFromTypedSnapshot(", - "private static Behavior ResolveRuntimeBehavior(", - "private static IReadOnlyList ResolveRuntimeBehaviorList(", - "EntityConstructionBlueprintMaterializer.MaterializeObservedFromBlueprint(ItemManager, Zone, blueprint)", - "entity.RestoreStatGrids(entitySnapshot.StatGrids)", - "RestoreActiveConsumablesFromTypedEntitySnapshot(entity, entitySnapshot)", - "RestoreRuntimeBehaviorStateFromTypedSnapshot(entity, entitySnapshot, restoredEntities)" - }; - var managerFacadeProjectionHits = forbiddenManagerFacadeProjectionSymbols - .Where(symbol => actionGameManager.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (managerFacadeProjectionHits.Length > 0) - { - throw new InvalidOperationException( - "ActionGameManager still owns observed entity mutation/restoration internals: " + - string.Join(", ", managerFacadeProjectionHits)); - } - - var requiredEntityBlueprintMaterializerSymbols = new[] - { - "public sealed class AetheriaUnityEntityBlueprintMaterializer", - "public EntityConstructionBlueprint MaterializeTemplate(AetheriaRuntimeLoadoutTemplateSnapshot template)", - "public EntityConstructionBlueprint MaterializeLoadoutEntity(AetheriaRuntimeEntityLoadoutSnapshot entity)", - "public EntityConstructionBlueprint MaterializeObservedEntity(", - "private static EntityConstructionBlueprint CreateBlueprint(string kind)", - "new OrbitalEntityConstructionBlueprint()", - "new ShipConstructionBlueprint()", - "shipBlueprint.Position = new float3((float)entity.PositionX", - "shipBlueprint.IsPlayerShip = isCurrentEntity", - "blueprint.Equipment = CreateEquippableSlots(entity.Equipment)", - "blueprint.CargoContents = CreateCargoBayContents(entity.CargoContents)", - "blueprint.Children = entity.Children", - "_loadoutItemFactory.CreateLoadoutItem(slot.Item)", - "_loadoutItemFactory.CreateLoadoutItem(item) as EquippableItem" - }; - var missingEntityBlueprintMaterializerSymbols = requiredEntityBlueprintMaterializerSymbols - .Where(symbol => !entityBlueprintMaterializer.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (missingEntityBlueprintMaterializerSymbols.Length > 0) - { - throw new InvalidOperationException( - "Unity entity construction blueprint materialization must live in AetheriaUnityEntityBlueprintMaterializer instead of ActionGameManager: " + - string.Join(", ", missingEntityBlueprintMaterializerSymbols)); - } - - var forbiddenManagerEntityConstructionSymbols = new[] - { - "private EntityConstructionBlueprint CreateEntityConstructionBlueprint(", - "private (int2 position, EquippableItem item)[] CreateEquippableSlots(", - "private (int2 position, ItemInstance item)[][] CreateCargoBayContents(", - "private EquippableItem CreateEquippableLoadoutItem(", - "private ItemInstance CreateLoadoutItem(AetheriaRuntimeLoadoutItemSnapshot", - "new OrbitalEntityConstructionBlueprint()", - "new ShipConstructionBlueprint", - "blueprint.Children = entity.Children", - "ItemManager.CreateSimpleCommodityInstance(typedItem", - "ItemManager.CreateCraftedInstance(typedItem" - }; - var managerEntityConstructionHits = forbiddenManagerEntityConstructionSymbols - .Where(symbol => actionGameManager.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (managerEntityConstructionHits.Length > 0) - { - throw new InvalidOperationException( - "ActionGameManager still owns Unity entity construction blueprint lowering internals: " + - string.Join(", ", managerEntityConstructionHits)); - } - - var requiredObservedZoneContextFactorySymbols = new[] - { - "public sealed class AetheriaUnityObservedZoneContextFactory", - "public Zone ResolveContext(", - "private readonly Dictionary _observedZoneContextsByDaemonIndex", - "private static ZoneConstructionBlueprint CreateZoneConstructionBlueprint(", - "private static BodyConstructionData CreateBodyConstructionData(", - "private static GasGiantConstructionData CreateGasGiantConstructionData(", - "private static SunConstructionData CreateSunConstructionData(", - "private static void PopulateBodyConstructionData(", - "new Zone(_itemManager, _planetSettings, constructionBlueprint, galaxyZone, _observedGalaxy)", - "_playMusic(MusicType.Overworld);" - }; - var missingObservedZoneContextFactorySymbols = requiredObservedZoneContextFactorySymbols - .Where(symbol => !observedZoneContextFactory.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (missingObservedZoneContextFactorySymbols.Length > 0) - { - throw new InvalidOperationException( - "Observed Unity zone context lowering must live in AetheriaUnityObservedZoneContextFactory instead of ActionGameManager: " + - string.Join(", ", missingObservedZoneContextFactorySymbols)); - } - - var forbiddenManagerZoneContextProjectionSymbols = new[] - { - "private void PrepareObservedDaemonZoneContext(", - "private static ZoneConstructionBlueprint CreateDaemonZoneConstructionBlueprint(", - "private static BodyConstructionData CreateDaemonBodyConstructionData(", - "private static GasGiantConstructionData CreateDaemonGasGiantConstructionData(", - "private static SunConstructionData CreateDaemonSunConstructionData(", - "private static void PopulateDaemonBodyConstructionData(", - "new Zone(ItemManager, Settings.PlanetSettings", - "private readonly Dictionary _observedZoneContextsByDaemonIndex" - }; - var managerZoneContextProjectionHits = forbiddenManagerZoneContextProjectionSymbols - .Where(symbol => actionGameManager.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (managerZoneContextProjectionHits.Length > 0) - { - throw new InvalidOperationException( - "ActionGameManager still owns observed zone/body context lowering internals: " + - string.Join(", ", managerZoneContextProjectionHits)); - } - - var requiredObservedFrameApplierSymbols = new[] - { - "public sealed class AetheriaUnityObservedFrameApplier", - "public bool ApplyLatestZoneRender()", - "observer.LastRenderView?.ZoneRender", - "private bool TryRestoreEntityGraphFromZoneRender(", - "if (string.IsNullOrWhiteSpace(render.RunId))", - "Aetheria zone-render feed does not identify a run id.", - "render.CreateEntitySnapshots()", - ".OrderBy(entity => entity.EntityIndex)", - "_entityRestorer.TryApplyInPlace(", - "zoneRenderer?.ApplyZoneRender(render)", - "_zoneContextFactory.ResolveContext(targetZone, render)", - "_entityRestorer.Replace(entitySnapshots, currentEntityKey, _getZone())", - "_restoreCurrentEntityBinding(currentEntity)", - "_entityIndex.TryGetPresentationEntityByRecordKey(currentEntityKey, out var currentEntity)", - "_entityIndex.PresentationEntitiesByDaemonIndex", - "zoneRenderer?.LoadDaemonZoneView(_entityIndex.PresentationEntitiesByDaemonIndex, render)", - "zoneRenderer?.RestoreDroppedPickupsFromZoneRender(render)" - }; - var missingObservedFrameApplierSymbols = requiredObservedFrameApplierSymbols - .Where(symbol => !observedFrameApplier.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (missingObservedFrameApplierSymbols.Length > 0) - { - throw new InvalidOperationException( - "Observed Unity frame application must live in AetheriaUnityObservedFrameApplier instead of ActionGameManager: " + - string.Join(", ", missingObservedFrameApplierSymbols)); - } - - var forbiddenManagerFrameApplicationSymbols = new[] - { - "_lastAppliedAuthoritativeDaemonFrameId", - "_lastAppliedAuthoritativeDaemonFramePath", - "_lastAppliedAuthoritativeDaemonRunId", - "_lastAppliedAuthoritativeDaemonZoneIndex", - "private bool TryRestoreEntityGraphFromZoneRender(", - "AetheriaRuntimeGameDocuments.ZoneRender(observed.Frame)", - "AetheriaRuntimeEntitySnapshotProjector.CreateSnapshots(runId, daemonZone)", - "ObservedEntityRestorer.TryApplyInPlace(", - "ObservedEntityRestorer.Replace(entitySnapshots, currentEntityKey, Zone)", - "ZoneRenderer?.LoadDaemonZoneView(_presentationEntityIndex.PresentationEntitiesByDaemonIndex, render)", - "ZoneRenderer?.RestoreDroppedPickupsFromDaemonZoneState(daemonZone)", - "render.ActionBarBindings", - "private static AetheriaRuntimeActionBarBindingSnapshot ToActionBarBindingSnapshot(" - }; - var managerFrameApplicationHits = forbiddenManagerFrameApplicationSymbols - .Where(symbol => actionGameManager.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (managerFrameApplicationHits.Length > 0) - { - throw new InvalidOperationException( - "ActionGameManager still owns authoritative daemon frame application internals: " + - string.Join(", ", managerFrameApplicationHits)); - } - - var requiredTargetContactQuerySymbols = new[] - { - "public Func ResolveZoneContacts { get; set; }", - "public Entity GetObservedTarget(Entity observer)", - "private float GetObservedInfoGathered(Entity observer, Entity target)", - "private bool IsObservedHostileContact(Entity observer, Entity target)", - "private AetheriaRuntimeZoneContactRow[] GetObservedVisibleContacts(", - "private bool TryQueryEntityContact(", - "private bool TryQueryEntityTarget(", - "AetheriaRuntimeZoneContactRow", - "AetheriaRuntimeZoneTargetRow", - "ResolveZoneContacts?.Invoke()", - "ResolveEntity?.Invoke(targetEntityIndex)" - }; - var missingTargetContactQuerySymbols = requiredTargetContactQuerySymbols - .Where(symbol => !targetPresentation.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (missingTargetContactQuerySymbols.Length > 0) - { - throw new InvalidOperationException( - "Observed Unity target/contact query lowering must live in AetheriaUnityTargetPresentation instead of ActionGameManager: " + - string.Join(", ", missingTargetContactQuerySymbols)); - } - - - if (observedEntityRestorer.Contains("case \"active_consumable\":", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Unity still restores daemon active-consumable behavior state by transient list index."); - } - - if (targetPresentation.Contains("CultMeshReactiveDocument", StringComparison.Ordinal) || - targetPresentation.Contains(".ReactiveZoneContacts(\"unity-observed-target-query\")", StringComparison.Ordinal) || - targetPresentation.Contains("_zoneContacts?.Dispose()", StringComparison.Ordinal) || - targetPresentation.Contains("_zoneContacts?.Current", StringComparison.Ordinal) || - targetPresentation.Contains("AetheriaRuntimeZoneContactsSession _zoneContacts", StringComparison.Ordinal) || - targetPresentation.Contains(".ObserveZoneContacts()", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Observed Unity target/contact query still routes zone contacts through legacy session/reactive wrapper state instead of named current typed state."); - } - - var forbiddenManagerTargetQuerySymbols = new[] - { - "private bool TryQueryDaemonEntityContact(", - "private bool TryQueryDaemonEntityTarget(", - "private Entity GetObservedTarget(", - "private float GetObservedInfoGathered(", - "private bool IsObservedHostileContact(", - "AetheriaRuntimeDaemonRenderQueries.TryQueryEntityContact(", - "AetheriaRuntimeDaemonRenderQueries.TryQueryEntityTarget(" - }; - var managerTargetQueryHits = forbiddenManagerTargetQuerySymbols - .Where(symbol => actionGameManager.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (managerTargetQueryHits.Length > 0) - { - throw new InvalidOperationException( - "ActionGameManager still owns observed daemon target/contact query internals: " + - string.Join(", ", managerTargetQueryHits)); - } - - var requiredCurrentEntityBinderSymbols = new[] - { - "public sealed class AetheriaUnityCurrentEntityBinder", - "public void RestoreBinding(", - "public void ClearBinding()", - "private void BindUndocked(", - "public Func ResolveDockParent { get; set; }", - "var dockParent = ResolveDockParent?.Invoke(currentEntity)", - "CurrentEntityPresentation?.BindDocked(", - "CurrentEntityPresentation?.BindUndocked(", - "TargetPresentation?.ClearIndicators();", - "CurrentEntityPresentation?.ClearBinding();", - "DisablePlayerInput?.Invoke();", - "ApplyActionBarBindings?.Invoke(Array.Empty())", - "TargetPresentation?.ReconcileVisibleTargetIndicators(GetCurrentEntity?.Invoke())", - "SetViewDirection?.Invoke((float3)AetheriaMath.ToUnityXZ(entity.CultDirection))" - }; - var missingCurrentEntityBinderSymbols = requiredCurrentEntityBinderSymbols - .Where(symbol => !currentEntityBinder.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (missingCurrentEntityBinderSymbols.Length > 0) - { - throw new InvalidOperationException( - "Current-entity binding orchestration must live in AetheriaUnityCurrentEntityBinder instead of ActionGameManager: " + - string.Join(", ", missingCurrentEntityBinderSymbols)); - } - - var forbiddenManagerCurrentEntityBinderSymbols = new[] - { - "if (ObservedDocking.TryResolveDockingBay(currentEntity, out var dockParent, out _))", - "private AetheriaUnityObservedDockingIndex ObservedDocking =>", - "ObservedDocking.IsEntityUndocked(CurrentEntity)", - "_currentEntityPresentation.BindDocked(", - "_currentEntityPresentation.BindUndocked(", - "_targetPresentation.ClearIndicators();", - "_currentEntityPresentation.ClearBinding();", - "private void BindToEntity(", - "DeathPost.weight = 0;", - "ZoneRenderer.TryGetEntityInstance(entity, out var entityInstance)", - "UpdateTowingStation" - }; - var managerCurrentEntityBinderHits = forbiddenManagerCurrentEntityBinderSymbols - .Where(symbol => actionGameManager.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (managerCurrentEntityBinderHits.Length > 0) - { - throw new InvalidOperationException( - "ActionGameManager still owns current-entity bind orchestration instead of delegating to AetheriaUnityCurrentEntityBinder: " + - string.Join(", ", managerCurrentEntityBinderHits)); - } - - var requiredTargetPresentationSymbols = new[] - { - "public sealed class AetheriaUnityTargetPresentation", - "public Func ResolveZoneContacts { get; set; }", - "ResolveZoneContacts?.Invoke()", - "public Entity GetObservedTarget(Entity observer)", - "public Entity Tick(Entity currentEntity, float time)", - "public void ReconcileVisibleTargetIndicators(Entity currentEntity)", - "public void UpdateTargetIndicators(", - "renderSettings.NormalizeDetectionProgress(", - "renderSettings.ResolveTargetSpottedFillEnabled(", - "renderSettings.NormalizeTargetVisibilityFill(", - "renderSettings.NormalizeVisibilityToTargetFill(", - "renderSettings.NormalizeTargetStatusFill(", - "renderSettings.ResolveLockIndicatorNoiseAmplitude(", - "renderSettings.ResolveLockIndicatorNoiseFrequency(", - "renderSettings.ResolveLockSpinSpeed(", - "GetObservedInfoGathered(currentEntity, target)", - "IsObservedHostileContact(currentEntity, observedTarget)", - "public AetheriaRuntimeCatalogSnapshot RuntimeCatalog { get; set; }", - "RuntimeCatalog?.FindItem(target.Hull, x => x.ItemKey)" - }; - var missingTargetPresentationSymbols = requiredTargetPresentationSymbols - .Where(symbol => !targetPresentation.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (missingTargetPresentationSymbols.Length > 0) - { - throw new InvalidOperationException( - "Daemon-observed target HUD presentation must live in AetheriaUnityTargetPresentation instead of ActionGameManager: " + - string.Join(", ", missingTargetPresentationSymbols)); - } - - var forbiddenTargetPresentationSymbols = new[] - { - "Func", - "ResolveClient", - "ResolveInfoGathered", - "ResolveHostileContact", - "ResolveVisibleContacts" - }; - var targetPresentationAccessHits = forbiddenTargetPresentationSymbols - .Where(symbol => targetPresentation.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (targetPresentationAccessHits.Length > 0) - { - throw new InvalidOperationException( - "AetheriaUnityTargetPresentation must consume observed typed target data, not hold a runtime client resolver: " + - string.Join(", ", targetPresentationAccessHits)); - } - - var requiredPilotFrameControllerSymbols = new[] - { - "public sealed class AetheriaUnityPilotFrameController", - "public void Tick(Entity currentEntity, float deltaTime, float timeSeconds)", - "TargetPresentation?.Tick(currentEntity, timeSeconds)", - "Input.Player.Look.ReadValue()", - "PilotCommands.RequestLookDirection(viewDirection)", - "renderSettings.NormalizeHeatstrokePost(currentEntity.Heatstroke)", - "renderSettings.ResolveSevereHeatstrokePostWeight(currentEntity.Heatstroke, timeSeconds)", - "Input.Player.Move.ReadValue()", - "PilotCommands.RequestMoveVector(movement)", - "Input.Player.TractorBeam.ReadValue()", - "PilotCommands.RequestTractorPower(Saturate(" - }; - var missingPilotFrameControllerSymbols = requiredPilotFrameControllerSymbols - .Where(symbol => !pilotFrameController.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (missingPilotFrameControllerSymbols.Length > 0) - { - throw new InvalidOperationException( - "Undocked pilot-frame input/presentation policy must live in AetheriaUnityPilotFrameController instead of ActionGameManager: " + - string.Join(", ", missingPilotFrameControllerSymbols)); - } - - var forbiddenManagerPilotFrameSymbols = new[] - { - "Input.Player.Look.ReadValue()", - "Input.Player.Move.ReadValue()", - "Input.Player.TractorBeam.ReadValue()", - "PilotCommands.RequestLookDirection(_viewDirection)", - "PilotCommands.RequestMoveVector(movement)", - "PilotCommands.RequestTractorPower(Saturate(", - "HeatstrokePost.weight = (float)renderSettings.NormalizeHeatstrokePost(", - "SevereHeatstrokePost.weight =" - }; - var managerPilotFrameHits = forbiddenManagerPilotFrameSymbols - .Where(symbol => actionGameManager.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (managerPilotFrameHits.Length > 0) - { - throw new InvalidOperationException( - "ActionGameManager still owns undocked pilot-frame input/presentation internals instead of delegating to AetheriaUnityPilotFrameController: " + - string.Join(", ", managerPilotFrameHits)); - } - - var forbiddenManagerDockingScanSymbols = new[] - { - "TryGetDaemonParentSnapshot(", - "TryResolveDaemonDockingBay(", - "parentSnapshot.DockingBayAssignments" - }; - var managerDockingScanHits = forbiddenManagerDockingScanSymbols - .Where(symbol => actionGameManager.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (managerDockingScanHits.Length > 0) - { - throw new InvalidOperationException( - "ActionGameManager still scans daemon docking relationships instead of reading current typed docking state at the caller edge: " + - string.Join(", ", managerDockingScanHits)); - } - - var requiredCurrentEntityPresentationSymbols = new[] - { - "public sealed class AetheriaUnityCurrentEntityPresentation", - "public void BindUndocked(", - "public void BindDocked(", - "public void ClearBinding()", - "public void Tick(float deltaTime)", - "private readonly List _shipSubscriptions", - "private readonly List _targetSubscriptions", - "public (HardpointData[] hardpoints, Transform[] barrels, PlaceUIElementWorldspace crosshair)[] ArticulationGroups", - "public (LockWeapon targetLock, PlaceUIElementWorldspace indicator, Rotate spin)[] LockingIndicators", - "entity.TargetedByCount.Subscribe", - "entity.Target.Subscribe", - "target.IncomingHit.Where", - "_hitMarkerTime = HitMarkerDuration", - "LockIndicator.Instantiate()", - "TradeMenu.Inventory = entity.CargoBays.First()", - "DockCamera.enabled = true", - "FollowCamera.enabled = false", - "DockCamera.Follow = orbitalInstance.transform", - "ZoneRenderer.TryGetBodyView(parentOrbitPlanetBodyKey, out var parentBodyView)", - "Menu.ShowTab(MenuTab.Inventory)" - }; - var missingCurrentEntityPresentationSymbols = requiredCurrentEntityPresentationSymbols - .Where(symbol => !currentEntityPresentation.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (missingCurrentEntityPresentationSymbols.Length > 0) - { - throw new InvalidOperationException( - "Current-entity Unity presentation binding must live in AetheriaUnityCurrentEntityPresentation instead of ActionGameManager: " + - string.Join(", ", missingCurrentEntityPresentationSymbols)); - } - - var forbiddenManagerCurrentPresentationSymbols = new[] - { - "private List _shipSubscriptions", - "private List _targetSubscriptions", - "private (HardpointData[] hardpoints, Transform[] barrels, PlaceUIElementWorldspace crosshair)[] _articulationGroups", - "private (LockWeapon targetLock, PlaceUIElementWorldspace indicator, Rotate spin)[] _lockingIndicators", - "private float _hitMarkerTime", - "private void DoDock(", - "TradeMenu.Inventory =", - "DockCamera.Follow =", - "DockCamera.LookAt =", - "DockCamera.enabled = true", - "FollowCamera.enabled = false", - "ZoneRenderer.TryGetBodyView(parentOrbitPlanetBodyKey, out var parentBodyView)", - "Menu.ShowTab(MenuTab.Inventory)", - "CurrentEntity.TargetedByCount.Subscribe", - "CurrentEntity.Target.Subscribe", - "target.IncomingHit.Where", - "LockIndicator.Instantiate()" - }; - var managerCurrentPresentationHits = forbiddenManagerCurrentPresentationSymbols - .Where(symbol => actionGameManager.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (managerCurrentPresentationHits.Length > 0) - { - throw new InvalidOperationException( - "ActionGameManager still owns bind-time current-entity presentation state instead of delegating to AetheriaUnityCurrentEntityPresentation: " + - string.Join(", ", managerCurrentPresentationHits)); - } - - if (actionGameManager.Contains("ItemManager.GetRuntimeItem", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "ActionGameManager still resolves item metadata through Unity ItemManager instead of the runtime catalog."); - } - - if (actionGameManager.Contains("public bool TryGetObservedCurrentEntity(out Entity entity)", StringComparison.Ordinal) || - 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 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 the managed current-docking document."); - } - - if (actionGameManager.Contains("public Entity TowingStation", StringComparison.Ordinal) || - actionGameManager.Contains("AvailableCargoBays()", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "ActionGameManager must not expose renderer-local towing context or available cargo bay enumeration as public gameplay truth; clients must use typed documents and daemon operations."); - } - - if (actionGameManager.Contains("public int Credits", StringComparison.Ordinal) || - actionGameManager.Contains("Credits = render.Credits", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "ActionGameManager must not mirror run credits as manager-global gameplay state; clients must read credits through typed documents such as StationRefitAsync."); - } - - if (actionGameManager.Contains("public static AetheriaRuntimeCatalogSnapshot RuntimeCatalog", StringComparison.Ordinal) || - actionGameManager.Contains("public AetheriaRuntimeCatalogSnapshot RuntimeCatalog", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "ActionGameManager must not expose the typed runtime catalog as manager-global gameplay state; clients must open typed catalog views through AetheriaClient."); - } - - if (actionGameManager.Contains("public AetheriaRuntimeDaemonRenderSettings ObservedDaemonRenderSettings()", StringComparison.Ordinal) || - actionGameManager.Contains("ObservedDaemonRenderSettings(", StringComparison.Ordinal) || - actionGameManager.Contains("new AetheriaRuntimeDaemonRenderSettings(", StringComparison.Ordinal) || - actionGameManager.Contains("ConfigureSchematicDisplayRenderSettings()", StringComparison.Ordinal) || - actionGameManager.Contains("SchematicDisplay?.SetRenderSettings(", StringComparison.Ordinal) || - actionGameManager.Contains("TargetSchematicDisplay?.SetRenderSettings(", StringComparison.Ordinal) || - actionGameManager.Contains("private void UpdatePlayerPanel()", StringComparison.Ordinal) || - actionGameManager.Contains("private void UpdateTargetPanel(Entity target)", StringComparison.Ordinal) || - actionGameManager.Contains("public EntityConstructionBlueprint CreateEntityConstructionBlueprint(AetheriaRuntimeLoadoutTemplateSnapshot template)", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "ActionGameManager must delegate daemon render-settings and loadout-template lowering to focused renderer/bootstrap adapters, not expose them as client gameplay APIs."); - } - - if (actionGameManager.Contains("ObservedLoadoutTemplates", StringComparison.Ordinal) || - actionGameManager.Contains("_observedLoadoutTemplates", StringComparison.Ordinal) || - actionGameManager.Contains("LoadRuntimeLoadoutTemplates", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "ActionGameManager must not cache or expose loadout templates as manager-global observed state; clients must read typed loadout templates directly."); - } - - var missingGameplaySymbols = requiredGameplaySymbols - .Where(symbol => !actionGameManager.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - - if (missingGameplaySymbols.Length > 0) - { - throw new InvalidOperationException( - "ActionGameManager no longer has the daemon-frame Continue boot path: " + - string.Join(", ", missingGameplaySymbols)); - } - - var requiredGameplaySceneWiringSymbols = new[] - { - "public void ConfigureCurrentEntityPresentation(", - "presentation.RuntimeCatalog = runtimeCatalog", - "public void ConfigureTargetPresentation(", - "presentation.ResolveZoneContacts = ReadZoneContacts", - "private AetheriaClientState _targetPresentationState;", - "AetheriaUnityRuntimeClientProvider.RuntimeState(\"unity-target-presentation\")", - "TargetPresentationState.ZoneContacts.Latest()", - "presentation.RuntimeCatalog = runtimeCatalog" - }; - - var missingGameplaySceneWiringSymbols = requiredGameplaySceneWiringSymbols - .Where(symbol => !gameplaySceneWiring.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - - if (missingGameplaySceneWiringSymbols.Length > 0) - { - throw new InvalidOperationException( - "AetheriaUnityGameplaySceneWiring no longer has the daemon-frame Continue presentation boot path: " + - string.Join(", ", missingGameplaySceneWiringSymbols)); - } - - var forbiddenGameplaySceneWiringSymbols = new[] - { - "Func resolveClient", - "presentation.ResolveClient = resolveClient" - }; - var gameplaySceneWiringAccessHits = forbiddenGameplaySceneWiringSymbols - .Where(symbol => gameplaySceneWiring.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (gameplaySceneWiringAccessHits.Length > 0) - { - throw new InvalidOperationException( - "AetheriaUnityGameplaySceneWiring must wire typed observed target query delegates instead of passing runtime client resolvers: " + - string.Join(", ", gameplaySceneWiringAccessHits)); - } - - var forbiddenGameplaySymbols = new[] - { - "if (run == null ||\r\n run.CurrentZoneIndex < 0 ||\r\n run.CurrentZoneEntityIndex < 0)", - "var currentEntityKey = $\"{zoneEntityKeyPrefix}{run.CurrentZoneEntityIndex}.v1\"", - "TargetVisibilityFill.fillAmount = Mathf.Lerp(.25f, .75f, (CurrentEntity.EntityInfoGathered[target] - threshold) / (1 - threshold));", - "VisibilityToTargetFill.fillAmount = Mathf.Lerp(.25f, .75f, target.EntityInfoGathered[CurrentEntity] / threshold);", - "indicator.Key.EntityInfoGathered[CurrentEntity]", - "CurrentEntity.Target.Value.IsHostileTo(CurrentEntity)", - "CurrentEntity.VisibleEnemies.ObserveAdd()", - "CurrentEntity.VisibleEnemies.ObserveRemove()", - "CurrentEntity.VisibleFriendlies.ObserveAdd()", - "CurrentEntity.VisibleFriendlies.ObserveRemove()", - "_currentEntityPresentation.Tick(Time.deltaTime);", - "PilotFrameController.Tick(CurrentEntity, Time.deltaTime, Time.time)", - "_targetPresentation.UpdateTargetIndicators(", - "foreach (var hostile in CurrentEntity.VisibleEnemies)", - "foreach (var friendly in CurrentEntity.VisibleFriendlies)", - "dockedEntitySnapshot.ChildEntityIndices", - "UpdateTargetPanel(CurrentEntity.Target.Value)", - "var target = CurrentEntity.Target.Value;", - "TargetIndicator.Target = CurrentEntity.Target.Value.Position", - "indicator.Target = CurrentEntity.Target.Value.Position", - "foreach (var bay in CurrentEntity.Parent.DockingBays)", - "foreach (var entity in DockedEntity.Children)", - "CurrentEntity != null && CurrentEntity.Parent == null", - "CurrentEntity !=null && CurrentEntity.Parent==null", - "CurrentEntity == null || CurrentEntity.Parent != null", - "return observer?.Target.Value;", - "observer.EntityInfoGathered.TryGetValue(target, out var infoGathered)", - "return target != null && target.IsHostileTo(observer);", - "RestoreEntityContactsFromTypedSnapshot", - "entity.Target.Value = target", - "entity.EntityInfoGathered[target]", - "entity.EntityHostility[target]", - "entity.VisibleEntities.Add(target)", - "entity.VisibleEnemies.Add(target)", - "entity.VisibleFriendlies.Add(target)", - "currentEntity.Parent.DockingBays", - "currentEntity.Parent is OrbitalEntity", - "DoDock(currentEntity.Parent, dockingBay)", - "RestoreChildAndDockingRelationships", - "child.SetParent(parent)", - "ship.SetParent(parent)", - "dockingBay.DockedShip = ship", - "Zone.Entities.Contains(ship)" - }; - - var forbiddenGameplayHits = forbiddenGameplaySymbols - .Where(symbol => actionGameManager.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - - if (forbiddenGameplayHits.Length > 0) - { - throw new InvalidOperationException( - "ActionGameManager still reconstructs Continue current-entity ownership from integer slot state: " + - string.Join(", ", forbiddenGameplayHits)); - } - - var canonicalRunStatePath = Path.Combine(root, "Aetheria.State", "Documents", "AetheriaRuntimeStateDocuments.cs"); - var canonicalRunState = File.Exists(canonicalRunStatePath) - ? File.ReadAllText(canonicalRunStatePath) - : throw new InvalidOperationException("Cannot verify canonical run-state authority; AetheriaRuntimeStateDocuments.cs is missing."); - - if (canonicalRunState.Contains("CurrentZoneEntityIndex", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Canonical typed run state still exposes integer current-entity slot authority."); - } - - if (packageSnapshot.Contains("CurrentZoneEntityIndex", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Unity package runtime run snapshot still exposes integer current-entity slot authority."); - } - - var snapshotDocumentPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeSnapshotDocuments.cs"); - var snapshotDocument = File.Exists(snapshotDocumentPath) - ? File.ReadAllText(snapshotDocumentPath) - : throw new InvalidOperationException("Cannot verify daemon snapshot authority; AetheriaRuntimeSnapshotDocuments.cs is missing."); - if (snapshotDocument.Contains("CurrentZoneEntityIndex", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Daemon runtime snapshot transport still exposes integer current-entity slot authority."); - } - - var forbiddenLegacyCommitSymbols = new[] - { - "public enum AetheriaRuntimeCommitKind", - "public sealed class AetheriaRuntimeCommitEnvelope", - "public sealed class AetheriaRuntimeStateCommitDocument", - "internal enum AetheriaRuntimeCommitKind", - "internal sealed class AetheriaRuntimeCommitEnvelope", - "internal sealed class AetheriaRuntimeStateCommitDocument" - }; - var legacyCommitHits = forbiddenLegacyCommitSymbols - .Where(symbol => snapshotDocument.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (legacyCommitHits.Length > 0) - { - throw new InvalidOperationException( - "Legacy runtime commit envelope leaked back into the daemon snapshot contract: " + - string.Join(", ", legacyCommitHits)); - } - - var forbiddenLegacyFiles = new[] - { - Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeStateCommitDocument.cs"), - Path.Combine(root, "Aetheria.State", "AetheriaLegacyRuntimeSnapshotImporter.cs") - }; - var existingLegacyFiles = forbiddenLegacyFiles - .Where(File.Exists) - .Select(path => Path.GetRelativePath(root, path)) - .ToArray(); - if (existingLegacyFiles.Length > 0) - { - throw new InvalidOperationException( - "Legacy runtime commit compatibility files must stay deleted: " + - string.Join(", ", existingLegacyFiles)); - } - - var requiredPackageSymbols = new[] - { - "public string RecordKey" - }; - - var missingPackageSymbols = requiredPackageSymbols - .Where(symbol => - !packageSnapshot.Contains(symbol, StringComparison.Ordinal) && - !packageStore.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - - if (missingPackageSymbols.Length > 0) - { - throw new InvalidOperationException( - "Unity package entity readback no longer preserves record identity for Continue: " + - string.Join(", ", missingPackageSymbols)); - } - - var entitySourcePath = Path.Combine(root, "Assets", "Scripts", "ServerShared", "Entity.cs"); - var entitySource = File.Exists(entitySourcePath) - ? File.ReadAllText(entitySourcePath) - : throw new InvalidOperationException("Cannot verify Continue entity restore ownership; Entity.cs is missing."); - var requiredEntityRestoreSymbols = new[] - { - "RestoreThermalExposure", - "RestoreActiveConsumable", - "FindActiveConsumableByEffectId", - "RetainActiveConsumables", - "RestoreStatGrids", - "RestoreHullConductivityGrid", - "new ConsumableItemEffect(item, this, effectId, remainingDuration, duration)" - }; - var missingEntityRestoreSymbols = requiredEntityRestoreSymbols - .Where(symbol => !entitySource.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - - if (missingEntityRestoreSymbols.Length > 0) - { - throw new InvalidOperationException( - "Entity no longer exposes narrow runtime-owned restore primitives for typed Continue state: " + - string.Join(", ", missingEntityRestoreSymbols)); - } -} - -static void RequireUnityObserverDoesNotTickLocalSimulation(string root) -{ - var actionGameManagerPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "ActionGameManager.cs"); - var mainMenuPath = Path.Combine(root, "Assets", "Scripts", "UI", "MainMenu.cs"); - var galaxyPath = Path.Combine(root, "Assets", "Scripts", "ServerShared", "Galaxy.cs"); - var daemonDocumentsPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeDaemonDocuments.cs"); - var daemonOperationClientPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeDaemonOperationClient.cs"); - var daemonRuntimeOperationsPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeDaemonOperations.cs"); - var daemonRuntimeSimulationPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeDaemonSimulation.cs"); - var daemonIntentPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeDaemonIntentState.cs"); - var zoneRenderDocumentsPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeGameViewportDocuments.cs"); - var daemonObserverPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "AetheriaDaemonObserver.cs"); - var pilotCommandSenderPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "AetheriaUnityPilotCommandSender.cs"); - var pilotFrameControllerPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "AetheriaUnityPilotFrameController.cs"); - var pilotOperationControllerPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "AetheriaUnityPilotOperationController.cs"); - var legacyPilotFrameAdapterPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "AetheriaUnityPilotFrameAdapter.cs"); - var legacyPilotOperationAdapterPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "AetheriaUnityPilotOperationAdapter.cs"); - var targetPresentationPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "AetheriaUnityTargetPresentation.cs"); - var deletedObservedTargetQueryPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "AetheriaUnityObservedTargetQuery.cs"); - var observedFrameApplierPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "AetheriaUnityObservedFrameApplier.cs"); - var legacyUnityDaemonEntitySnapshotProjectorPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "AetheriaUnityDaemonEntitySnapshotProjector.cs"); - var daemonEntitySnapshotProjectorPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeEntitySnapshotProjector.cs"); - var observedEntityRestorerPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "AetheriaUnityObservedEntityRestorer.cs"); - var entityBlueprintMaterializerPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "AetheriaUnityEntityBlueprintMaterializer.cs"); - var observedZoneContextFactoryPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "AetheriaUnityObservedZoneContextFactory.cs"); - var currentEntityBinderPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "AetheriaUnityCurrentEntityBinder.cs"); - var gameplaySceneWiringPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "AetheriaUnityGameplaySceneWiring.cs"); - var daemonGameplayOperationsPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeDaemonOperationsClient.cs"); - var legacyDaemonGameplayOperationsPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "AetheriaDaemonOperations.cs"); - var actionGameManager = File.Exists(actionGameManagerPath) - ? File.ReadAllText(actionGameManagerPath) - : throw new InvalidOperationException("Cannot verify Unity observer authority; ActionGameManager.cs is missing."); - var mainMenu = File.Exists(mainMenuPath) - ? File.ReadAllText(mainMenuPath) - : throw new InvalidOperationException("Cannot verify Unity observer authority; MainMenu.cs is missing."); - var galaxy = File.Exists(galaxyPath) - ? File.ReadAllText(galaxyPath) - : throw new InvalidOperationException("Cannot verify Unity observer authority; Galaxy.cs is missing."); - var daemonDocuments = File.Exists(daemonDocumentsPath) - ? File.ReadAllText(daemonDocumentsPath) - : throw new InvalidOperationException("Cannot verify Unity observer authority; daemon command documents are missing."); - var daemonOperationClient = File.Exists(daemonOperationClientPath) - ? File.ReadAllText(daemonOperationClientPath) - : throw new InvalidOperationException("Cannot verify Unity observer authority; daemon operation client is missing."); - var daemonOperationsSource = File.Exists(daemonRuntimeOperationsPath) - ? File.ReadAllText(daemonRuntimeOperationsPath) - : throw new InvalidOperationException("Cannot verify Unity observer authority; daemon operations is missing."); - var daemonSimulationSource = File.Exists(daemonRuntimeSimulationPath) - ? File.ReadAllText(daemonRuntimeSimulationPath) - : throw new InvalidOperationException("Cannot verify Unity observer authority; daemon simulation is missing."); - var daemonIntent = File.Exists(daemonIntentPath) - ? File.ReadAllText(daemonIntentPath) - : throw new InvalidOperationException("Cannot verify Unity observer authority; daemon intent state is missing."); - var daemonObserver = File.Exists(daemonObserverPath) - ? File.ReadAllText(daemonObserverPath) - : throw new InvalidOperationException("Cannot verify Unity observer authority; AetheriaDaemonObserver.cs is missing."); - var pilotCommandSender = File.Exists(pilotCommandSenderPath) - ? File.ReadAllText(pilotCommandSenderPath) - : throw new InvalidOperationException("Cannot verify Unity observer authority; AetheriaUnityPilotCommandSender.cs is missing."); - var pilotFrameController = File.Exists(pilotFrameControllerPath) - ? File.ReadAllText(pilotFrameControllerPath) - : throw new InvalidOperationException("Cannot verify Unity observer authority; AetheriaUnityPilotFrameController.cs is missing."); - var pilotOperationController = File.Exists(pilotOperationControllerPath) - ? File.ReadAllText(pilotOperationControllerPath) - : throw new InvalidOperationException("Cannot verify Unity observer authority; AetheriaUnityPilotOperationController.cs is missing."); - if (File.Exists(legacyPilotFrameAdapterPath) || - File.Exists(legacyPilotOperationAdapterPath) || - actionGameManager.Contains("AetheriaUnityPilotFrameAdapter", StringComparison.Ordinal) || - actionGameManager.Contains("AetheriaUnityPilotOperationAdapter", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Unity pilot control must be owned by named controllers; adapter-shaped pilot access is obsolete."); - } - if (File.Exists(deletedObservedTargetQueryPath)) - { - throw new InvalidOperationException( - "AetheriaUnityObservedTargetQuery is dead contact-query chaff; target presentation should read typed zone contacts directly."); - } - var targetPresentation = File.Exists(targetPresentationPath) - ? File.ReadAllText(targetPresentationPath) - : throw new InvalidOperationException("Cannot verify Unity observer authority; AetheriaUnityTargetPresentation.cs is missing."); - var observedFrameApplier = File.Exists(observedFrameApplierPath) - ? File.ReadAllText(observedFrameApplierPath) - : throw new InvalidOperationException("Cannot verify Unity observer authority; AetheriaUnityObservedFrameApplier.cs is missing."); - if (File.Exists(legacyUnityDaemonEntitySnapshotProjectorPath)) - { - throw new InvalidOperationException( - "Unity still owns daemon entity snapshot projection; package runtime should lower typed daemon entity snapshots."); - } - - var zoneRenderDocuments = File.Exists(zoneRenderDocumentsPath) - ? File.ReadAllText(zoneRenderDocumentsPath) - : throw new InvalidOperationException("Cannot verify Unity observer authority; AetheriaRuntimeGameViewportDocuments.cs is missing."); - if (File.Exists(daemonEntitySnapshotProjectorPath)) - { - throw new InvalidOperationException( - "AetheriaRuntimeEntitySnapshotProjector is dead access chaff; AetheriaRuntimeZoneRenderDocument owns managed entity snapshot access."); - } - var observedEntityRestorer = File.Exists(observedEntityRestorerPath) - ? File.ReadAllText(observedEntityRestorerPath) - : throw new InvalidOperationException("Cannot verify Unity observer authority; AetheriaUnityObservedEntityRestorer.cs is missing."); - var entityBlueprintMaterializer = File.Exists(entityBlueprintMaterializerPath) - ? File.ReadAllText(entityBlueprintMaterializerPath) - : throw new InvalidOperationException("Cannot verify Unity observer authority; AetheriaUnityEntityBlueprintMaterializer.cs is missing."); - var observedZoneContextFactory = File.Exists(observedZoneContextFactoryPath) - ? File.ReadAllText(observedZoneContextFactoryPath) - : throw new InvalidOperationException("Cannot verify Unity observer authority; AetheriaUnityObservedZoneContextFactory.cs is missing."); - var currentEntityBinder = File.Exists(currentEntityBinderPath) - ? File.ReadAllText(currentEntityBinderPath) - : throw new InvalidOperationException("Cannot verify Unity observer authority; AetheriaUnityCurrentEntityBinder.cs is missing."); - var gameplaySceneWiring = File.Exists(gameplaySceneWiringPath) - ? File.ReadAllText(gameplaySceneWiringPath) - : throw new InvalidOperationException("Cannot verify Unity observer authority; AetheriaUnityGameplaySceneWiring.cs is missing."); - var daemonOperations = File.Exists(daemonGameplayOperationsPath) - ? File.ReadAllText(daemonGameplayOperationsPath) - : throw new InvalidOperationException("Cannot verify Unity observer authority; shared daemon operations client is missing."); - - if (File.Exists(legacyDaemonGameplayOperationsPath)) - { - throw new InvalidOperationException( - "Unity gameplay still owns AetheriaDaemonOperations; shared daemon operation access belongs in the runtime package."); - } - - var forbiddenGameplaySymbols = new[] - { - "CurrentGalaxy", - "Zone.Update(Time.deltaTime)", - "Zone.Update(", - "CurrentEntity.Update(", - ".Update(Time.deltaTime)", - "Death.Subscribe(Die)", - "private void Die(CauseOfDeath", - "ZoneGenerator.GenerateZone", - "TryDock(", - "TryUndock(", - "IntroCutscene(", - "IntroDuration", - "ship.CultPositionXZ =", - "ship.CultDirection =", - "ship.CultVelocity =", - "CurrentEntity.Zone.Entities.Remove(CurrentEntity)", - "CurrentEntity.Zone = Zone", - "Zone.Entities.Add(CurrentEntity)", - "CurrentEntity.CultPositionXZ = TowingStation.CultPositionXZ", - "PopulateLevel(TowingStation.Zone.GalaxyZone)", - "TowingStation.Zone?.GalaxyZone", - "Zone.PlanetInstances.Values.FirstOrDefault", - "Zone.TryGetOrbit(orbital.OrbitKey", - "ZoneRenderer.Planets", - "RestoreDaemonAsteroidRuntimeState", - "belt.Damage.Clear()", - "belt.RespawnTimers.Clear()", - "AetheriaRuntimeStateCommitLog.QueueRunCheckpoint", - "AetheriaRuntimeStateCommitLog.QueuePlayerSettings", - "AetheriaRuntimeStateCommitLog.QueueLoadoutTemplate" - }; - - var forbiddenGameplayHits = forbiddenGameplaySymbols - .Where(symbol => actionGameManager.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - - if (forbiddenGameplayHits.Length > 0) - { - throw new InvalidOperationException( - "Unity gameplay still owns local simulation or legacy write fallback instead of daemon observation: " + - string.Join(", ", forbiddenGameplayHits)); - } - - if (daemonObserver.Contains("AetheriaRuntimeCommandSubmitter.TrySubmitDaemonCommand(", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Unity daemon observer still calls the generic command submitter instead of the typed daemon operation client."); - } - - var requiredGameplaySymbols = new[] - { - "ObservedFrameApplier.ApplyLatestZoneRender()", - "private AetheriaUnityGameplayLoopShell GameplayLoopShell =>", - "GameplayLoopShell.Tick(Time.deltaTime, Time.time)", - "GameplayLoopShell.LateTick()", - "ApplyLatestZoneRender = () => ObservedFrameApplier.ApplyLatestZoneRender()", - "ResolveDaemonObserver()", - "private AetheriaUnityObservedFrameApplier ObservedFrameApplier =>", - "ObservedZoneContextFactory = new AetheriaUnityObservedZoneContextFactory(", - "boot.ObservedGalaxy", - "ObservedZoneContextFactory.ResolveGalaxyZone", - "private AetheriaUnityObservedZoneContextFactory ObservedZoneContextFactory { get; set; }", - "entity => CurrentEntityBinder.RestoreBinding(entity)", - "private AetheriaUnityEntityBlueprintMaterializer EntityBlueprintMaterializer =>", - "EntityBlueprintMaterializer.MaterializeObservedEntity", - "_loadoutItemFactory.CreateLoadoutItem", - "private AetheriaUnityPilotCommandSender PilotCommands =>", - "private AetheriaUnityPilotFrameController PilotFrameController =>", - "PilotFrameController = PilotFrameController", - "private AetheriaUnityPilotOperationController PilotOperationController =>", - "SceneWiring.ConfigureTargetPresentation(" - }; - - var missingGameplaySymbols = requiredGameplaySymbols - .Where(symbol => !actionGameManager.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - - if (missingGameplaySymbols.Length > 0) - { - throw new InvalidOperationException( - "Unity gameplay no longer behaves as a daemon-frame observer and command lowerer: " + - string.Join(", ", missingGameplaySymbols)); - } - - var requiredSceneWiringObserverSymbols = new[] - { - "presentation.ResolveZoneContacts = ReadZoneContacts", - "AetheriaUnityRuntimeClientProvider.RuntimeState(\"unity-target-presentation\")", - "TargetPresentationState.ZoneContacts.Latest()" - }; - - var missingSceneWiringObserverSymbols = requiredSceneWiringObserverSymbols - .Where(symbol => !gameplaySceneWiring.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - - if (missingSceneWiringObserverSymbols.Length > 0) - { - throw new InvalidOperationException( - "Unity scene wiring no longer connects target presentation to observed daemon target queries: " + - string.Join(", ", missingSceneWiringObserverSymbols)); - } - - if (!targetPresentation.Contains("public Entity GetObservedTarget(Entity observer)", StringComparison.Ordinal) || - !targetPresentation.Contains("ResolveZoneContacts?.Invoke()", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Unity target presentation must query target/contact HUD state from typed zone contacts directly."); - } - - if (!zoneRenderDocuments.Contains("new AetheriaRuntimeEntitySnapshot(", StringComparison.Ordinal) || - !zoneRenderDocuments.Contains("CreateWeaponStates(runId, zoneIndex, entity.WeaponStates)", StringComparison.Ordinal) || - !zoneRenderDocuments.Contains("entity.EntityIndex,", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Unity observer entity snapshot access no longer carries typed daemon entity indices through AetheriaRuntimeZoneRenderDocument."); - } - - if (zoneRenderDocuments.Contains("EntityIndexFromRecordKey", StringComparison.Ordinal) || - observedFrameApplier.Contains("EntityIndexFromRecordKey", StringComparison.Ordinal) || - observedEntityRestorer.Contains("EntityIndexFromRecordKey", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Unity observer entity projection must use typed daemon entity indices instead of parsing synthetic record keys."); - } - - if (!observedEntityRestorer.Contains("public bool TryApplyInPlace(", StringComparison.Ordinal) || - !observedEntityRestorer.Contains("public void Replace(", StringComparison.Ordinal) || - !observedEntityRestorer.Contains("EntityConstructionBlueprintMaterializer.MaterializeObservedFromBlueprint(_itemManager, zone, blueprint)", StringComparison.Ordinal) || - !observedEntityRestorer.Contains("RestoreRuntimeBehaviorState(entity, entitySnapshot, restoredEntities)", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Unity observer facade restoration no longer flows through the dedicated observed entity restorer."); - } - - var requiredEntityBlueprintMaterializerSymbols = new[] - { - "public sealed class AetheriaUnityEntityBlueprintMaterializer", - "public EntityConstructionBlueprint MaterializeTemplate(AetheriaRuntimeLoadoutTemplateSnapshot template)", - "public EntityConstructionBlueprint MaterializeLoadoutEntity(AetheriaRuntimeEntityLoadoutSnapshot entity)", - "public EntityConstructionBlueprint MaterializeObservedEntity(", - "private static EntityConstructionBlueprint CreateBlueprint(string kind)", - "new OrbitalEntityConstructionBlueprint()", - "new ShipConstructionBlueprint()", - "shipBlueprint.Position = new float3((float)entity.PositionX", - "shipBlueprint.Direction = new float2((float)entity.DirectionX", - "shipBlueprint.IsPlayerShip = isCurrentEntity", - "blueprint.Equipment = CreateEquippableSlots(entity.Equipment)", - "blueprint.CargoContents = CreateCargoBayContents(entity.CargoContents)", - "blueprint.Children = entity.Children", - "_loadoutItemFactory.CreateLoadoutItem(slot.Item)", - "_loadoutItemFactory.CreateLoadoutItem(item) as EquippableItem" - }; - var missingEntityBlueprintMaterializerSymbols = requiredEntityBlueprintMaterializerSymbols - .Where(symbol => !entityBlueprintMaterializer.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (missingEntityBlueprintMaterializerSymbols.Length > 0) - { - throw new InvalidOperationException( - "Unity observer entity construction materialization must live in AetheriaUnityEntityBlueprintMaterializer instead of ActionGameManager: " + - string.Join(", ", missingEntityBlueprintMaterializerSymbols)); - } - - var forbiddenManagerEntityConstructionSymbols = new[] - { - "private EntityConstructionBlueprint CreateEntityConstructionBlueprint(", - "private (int2 position, EquippableItem item)[] CreateEquippableSlots(", - "private (int2 position, ItemInstance item)[][] CreateCargoBayContents(", - "private EquippableItem CreateEquippableLoadoutItem(", - "private ItemInstance CreateLoadoutItem(AetheriaRuntimeLoadoutItemSnapshot", - "new OrbitalEntityConstructionBlueprint()", - "new ShipConstructionBlueprint", - "blueprint.Children = entity.Children", - "ItemManager.CreateSimpleCommodityInstance(typedItem", - "ItemManager.CreateCraftedInstance(typedItem" - }; - var managerEntityConstructionHits = forbiddenManagerEntityConstructionSymbols - .Where(symbol => actionGameManager.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (managerEntityConstructionHits.Length > 0) - { - throw new InvalidOperationException( - "ActionGameManager still owns Unity entity construction blueprint lowering internals: " + - string.Join(", ", managerEntityConstructionHits)); - } - - if (!observedZoneContextFactory.Contains("public Zone ResolveContext(", StringComparison.Ordinal) || - !observedZoneContextFactory.Contains("private static ZoneConstructionBlueprint CreateZoneConstructionBlueprint(", StringComparison.Ordinal) || - !observedZoneContextFactory.Contains("private static BodyConstructionData CreateBodyConstructionData(", StringComparison.Ordinal) || - !observedZoneContextFactory.Contains("new Zone(_itemManager, _planetSettings, constructionBlueprint, galaxyZone, _observedGalaxy)", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Unity observer zone context lowering no longer flows through the dedicated observed zone context factory."); - } - - var requiredZoneRenderSnapshotSymbols = new[] - { - "public AetheriaRuntimeEntitySnapshot[] CreateEntitySnapshots()", - "public static AetheriaRuntimeEntitySnapshot[] CreateEntitySnapshots(", - "public static string EntityRecordKey(string runId, int zoneIndex, int entityIndex)", - "new AetheriaRuntimeEntitySnapshot(", - "CreateEntityItemSlots(entity.Equipment)", - "CreateWeaponStates(runId, zoneIndex, entity.WeaponStates)", - "CreateBehaviorStates(entity.BehaviorStates)", - "CreateCargoBays(entity.CargoContents)", - "new AetheriaRuntimeEntityContactSnapshot(" - }; - var missingZoneRenderSnapshotSymbols = requiredZoneRenderSnapshotSymbols - .Where(symbol => !zoneRenderDocuments.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (missingZoneRenderSnapshotSymbols.Length > 0) - { - throw new InvalidOperationException( - "Managed daemon entity snapshot access must live on AetheriaRuntimeZoneRenderDocument instead of a projector-shaped helper: " + - string.Join(", ", missingZoneRenderSnapshotSymbols)); - } - - var requiredObservedFrameApplierSymbols = new[] - { - "public sealed class AetheriaUnityObservedFrameApplier", - "public bool ApplyLatestZoneRender()", - "observer.LastRenderView?.ZoneRender", - "private bool TryRestoreEntityGraphFromZoneRender(", - "render.CreateEntitySnapshots()", - ".OrderBy(entity => entity.EntityIndex)", - "_entityRestorer.Replace(entitySnapshots, currentEntityKey, _getZone())", - "_entityRestorer.TryApplyInPlace(", - "_entityIndex.TryGetPresentationEntityByRecordKey(currentEntityKey, out var currentEntity)", - "_entityIndex.PresentationEntitiesByDaemonIndex", - "_zoneContextFactory.ResolveContext(targetZone, render)", - "zoneRenderer?.LoadDaemonZoneView(_entityIndex.PresentationEntitiesByDaemonIndex, render)", - "zoneRenderer?.ApplyZoneRender(render)", - "zoneRenderer?.RestoreDroppedPickupsFromZoneRender(render)", - "_restoreCurrentEntityBinding(currentEntity)" - }; - var missingObservedFrameApplierSymbols = requiredObservedFrameApplierSymbols - .Where(symbol => !observedFrameApplier.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (missingObservedFrameApplierSymbols.Length > 0) - { - throw new InvalidOperationException( - "Unity observer frame restoration must live in AetheriaUnityObservedFrameApplier instead of ActionGameManager: " + - string.Join(", ", missingObservedFrameApplierSymbols)); - } - - var forbiddenManagerFrameApplicationSymbols = new[] - { - "_lastAppliedAuthoritativeDaemonFrameId", - "_lastAppliedAuthoritativeDaemonFramePath", - "_lastAppliedAuthoritativeDaemonRunId", - "_lastAppliedAuthoritativeDaemonZoneIndex", - "private bool TryRestoreEntityGraphFromZoneRender(", - "AetheriaRuntimeGameDocuments.ZoneRender(observed.Frame)", - "AetheriaRuntimeEntitySnapshotProjector.CreateSnapshots(runId, daemonZone)", - "ObservedEntityRestorer.Replace(entitySnapshots, currentEntityKey, Zone)", - "ObservedEntityRestorer.TryApplyInPlace(", - "ZoneRenderer?.LoadDaemonZoneView(_presentationEntityIndex.PresentationEntitiesByDaemonIndex, render)", - "ZoneRenderer?.ApplyZoneRender(render)", - "ZoneRenderer?.RestoreDroppedPickupsFromDaemonZoneState(daemonZone)", - "render.ActionBarBindings", - "private static AetheriaRuntimeActionBarBindingSnapshot ToActionBarBindingSnapshot(" - }; - var managerFrameApplicationHits = forbiddenManagerFrameApplicationSymbols - .Where(symbol => actionGameManager.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (managerFrameApplicationHits.Length > 0) - { - throw new InvalidOperationException( - "ActionGameManager still owns authoritative daemon frame restoration instead of delegating to AetheriaUnityObservedFrameApplier: " + - string.Join(", ", managerFrameApplicationHits)); - } - - var requiredPilotFrameControllerSymbols = new[] - { - "public sealed class AetheriaUnityPilotFrameController", - "public void Tick(Entity currentEntity, float deltaTime, float timeSeconds)", - "TargetPresentation?.Tick(currentEntity, timeSeconds)", - "Input.Player.Look.ReadValue()", - "PilotCommands.RequestLookDirection(viewDirection)", - "renderSettings.NormalizeHeatstrokePost(currentEntity.Heatstroke)", - "renderSettings.ResolveSevereHeatstrokePostWeight(currentEntity.Heatstroke, timeSeconds)", - "Input.Player.Move.ReadValue()", - "PilotCommands.RequestMoveVector(movement)", - "Input.Player.TractorBeam.ReadValue()", - "PilotCommands.RequestTractorPower(Saturate(" - }; - var missingPilotFrameControllerSymbols = requiredPilotFrameControllerSymbols - .Where(symbol => !pilotFrameController.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (missingPilotFrameControllerSymbols.Length > 0) - { - throw new InvalidOperationException( - "Unity observer pilot-frame input and presentation policy must live in AetheriaUnityPilotFrameController instead of ActionGameManager: " + - string.Join(", ", missingPilotFrameControllerSymbols)); - } - - var forbiddenManagerPilotFrameSymbols = new[] - { - "Input.Player.Look.ReadValue()", - "Input.Player.Move.ReadValue()", - "Input.Player.TractorBeam.ReadValue()", - "PilotCommands.RequestLookDirection(_viewDirection)", - "PilotCommands.RequestMoveVector(movement)", - "PilotCommands.RequestTractorPower(Saturate(", - "HeatstrokePost.weight = (float)renderSettings.NormalizeHeatstrokePost(", - "SevereHeatstrokePost.weight =" - }; - var managerPilotFrameHits = forbiddenManagerPilotFrameSymbols - .Where(symbol => actionGameManager.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (managerPilotFrameHits.Length > 0) - { - throw new InvalidOperationException( - "ActionGameManager still owns undocked pilot-frame input/presentation internals: " + - string.Join(", ", managerPilotFrameHits)); - } - - var requiredPilotOperationControllerSymbols = new[] - { - "public sealed class AetheriaUnityPilotOperationController", - "public void RequestInteract()", - "public void RequestTargetSelection(Entity target)", - "public void RequestTargetReticle()", - "public void RequestDock()", - "public void RequestUndock()", - "_presentationEntityIndex.TryGetRecordKeyForPresentationEntity(target, out var targetEntityKey)", - "Submit(operations => operations.Interact(), \"interact\")", - "Submit(operations => operations.ClearTarget(), \"target clear\")", - "operations => operations.SetTarget(targetEntityKey)", - "operations => operations.TargetNearest()", - "operations => operations.TargetNext()", - "operations => operations.TargetPrevious()", - "operations => operations.TargetReticle(", - "operations => operations.SetOverrideShutdown(enabled)", - "operations => operations.SensorPing()", - "operations => operations.SetHeatsinksEnabled(enabled)", - "operations => operations.ToggleShieldEnabled()", - "operations => operations.DockNearest()", - "operations => operations.Undock()", - "_resolvePilotCommands()?.TrySubmit(submit, label)" - }; - var missingPilotOperationControllerSymbols = requiredPilotOperationControllerSymbols - .Where(symbol => !pilotOperationController.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (missingPilotOperationControllerSymbols.Length > 0) - { - throw new InvalidOperationException( - "Unity pilot operation lowering must live in AetheriaUnityPilotOperationController instead of ActionGameManager: " + - string.Join(", ", missingPilotOperationControllerSymbols)); - } - - var forbiddenManagerOperationLoweringSymbols = new[] - { - "PilotCommands.TrySubmit(", - "operations => operations.Interact()", - "operations => operations.ClearTarget()", - "operations => operations.SetTarget(", - "operations => operations.TargetNearest()", - "operations => operations.TargetNext()", - "operations => operations.TargetPrevious()", - "operations => operations.TargetReticle(", - "operations => operations.SetOverrideShutdown(", - "operations => operations.SensorPing()", - "operations => operations.SetHeatsinksEnabled(", - "operations => operations.ToggleShieldEnabled()", - "operations => operations.DockNearest()", - "operations => operations.Undock()" - }; - var managerOperationLoweringHits = forbiddenManagerOperationLoweringSymbols - .Where(symbol => actionGameManager.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (managerOperationLoweringHits.Length > 0) - { - throw new InvalidOperationException( - "ActionGameManager still owns typed daemon operation lowering instead of delegating to AetheriaUnityPilotOperationController: " + - string.Join(", ", managerOperationLoweringHits)); - } - - var requiredTargetContactQuerySymbols = new[] - { - "public Func ResolveZoneContacts { get; set; }", - "public Entity GetObservedTarget(Entity observer)", - "private float GetObservedInfoGathered(Entity observer, Entity target)", - "private bool IsObservedHostileContact(Entity observer, Entity target)", - "private AetheriaRuntimeZoneContactRow[] GetObservedVisibleContacts(", - "private bool TryQueryEntityContact(", - "private bool TryQueryEntityTarget(", - "AetheriaRuntimeZoneContactRow", - "AetheriaRuntimeZoneTargetRow", - "ResolveZoneContacts?.Invoke()", - "ResolveEntity?.Invoke(targetEntityIndex)" - }; - var missingTargetContactQuerySymbols = requiredTargetContactQuerySymbols - .Where(symbol => !targetPresentation.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (missingTargetContactQuerySymbols.Length > 0) - { - throw new InvalidOperationException( - "Unity observer target/contact projection must live in AetheriaUnityTargetPresentation instead of ActionGameManager: " + - string.Join(", ", missingTargetContactQuerySymbols)); - } - - if (targetPresentation.Contains("CultMeshReactiveDocument", StringComparison.Ordinal) || - targetPresentation.Contains(".ReactiveZoneContacts(\"unity-observed-target-query\")", StringComparison.Ordinal) || - targetPresentation.Contains("_zoneContacts?.Dispose()", StringComparison.Ordinal) || - targetPresentation.Contains("_zoneContacts?.Current", StringComparison.Ordinal) || - targetPresentation.Contains("AetheriaRuntimeZoneContactsSession _zoneContacts", StringComparison.Ordinal) || - targetPresentation.Contains(".ObserveZoneContacts()", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Unity observer target/contact projection still routes zone contacts through legacy session/reactive wrapper state instead of named current typed state."); - } - - var forbiddenManagerTargetQuerySymbols = new[] - { - "private bool TryQueryDaemonEntityContact(", - "private bool TryQueryDaemonEntityTarget(", - "private Entity GetObservedTarget(", - "private float GetObservedInfoGathered(", - "private bool IsObservedHostileContact(", - "AetheriaRuntimeDaemonRenderQueries.TryQueryEntityContact(", - "AetheriaRuntimeDaemonRenderQueries.TryQueryEntityTarget(" - }; - var managerTargetQueryHits = forbiddenManagerTargetQuerySymbols - .Where(symbol => actionGameManager.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (managerTargetQueryHits.Length > 0) - { - throw new InvalidOperationException( - "ActionGameManager still owns observed daemon target/contact query internals: " + - string.Join(", ", managerTargetQueryHits)); - } - - if (!currentEntityBinder.Contains("public void RestoreBinding(", StringComparison.Ordinal) || - !currentEntityBinder.Contains("var dockParent = ResolveDockParent?.Invoke(currentEntity)", StringComparison.Ordinal) || - !currentEntityBinder.Contains("CurrentEntityPresentation?.BindDocked(", StringComparison.Ordinal) || - !currentEntityBinder.Contains("CurrentEntityPresentation?.BindUndocked(", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Unity observer current-entity binding no longer flows through the dedicated current entity binder."); - } - - var forbiddenObservedReadShims = new[] - { - "TryGetObservedGalaxy(out Galaxy galaxy)", - "TryGetObservedZoneSnapshot(", - "TryGetObservedRunZone(" - }; - - var observedReadShimHits = forbiddenObservedReadShims - .Where(symbol => actionGameManager.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - - if (observedReadShimHits.Length > 0) - { - throw new InvalidOperationException( - "ActionGameManager must not expose broad observed galaxy/zone read shims; clients read typed documents through AetheriaClient: " + - string.Join(", ", observedReadShimHits)); - } - - if (actionGameManager.Contains("private Galaxy ObservedGalaxy", StringComparison.Ordinal) || - actionGameManager.Contains("ObservedGalaxy = boot.ObservedGalaxy", StringComparison.Ordinal) || - actionGameManager.Contains("() => ObservedGalaxy", StringComparison.Ordinal) || - observedZoneContextFactory.Contains("Func resolveObservedGalaxy", StringComparison.Ordinal) || - observedZoneContextFactory.Contains("_resolveObservedGalaxy", StringComparison.Ordinal) || - observedZoneContextFactory.Contains("_observedGalaxy ??=", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "ActionGameManager must not own the raw observed galaxy projection; gameplay boot may pass it once into the legacy Zone construction adapter only."); - } - - if (actionGameManager.Contains("public static void ProjectObservedDaemonRun(", StringComparison.Ordinal) || - mainMenu.Contains("ActionGameManager.ProjectObservedDaemonRun(", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Daemon-run projection must not be routed through ActionGameManager; use the explicit observed-run projection holder."); - } - - if (actionGameManager.Contains("public static bool IsTutorial", StringComparison.Ordinal) || - 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 managed daemon run documents."); - } - - if (actionGameManager.Contains("public static DirectoryInfo GameDataDirectory", StringComparison.Ordinal) || - actionGameManager.Contains("public static string RuntimeStateFilePath", StringComparison.Ordinal) || - mainMenu.Contains("ActionGameManager.GameDataDirectory", StringComparison.Ordinal) || - mainMenu.Contains("ActionGameManager.RuntimeStateFilePath", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Unity runtime path access must live in AetheriaUnityRuntimePaths, not ActionGameManager gameplay state."); - } - - var volumeSamplingPath = Path.Combine(root, "Assets", "Scripts", "Zone Display", "VolumeSampling.cs"); - var volumeSampling = File.Exists(volumeSamplingPath) - ? File.ReadAllText(volumeSamplingPath) - : throw new InvalidOperationException("Cannot verify presentation environment ownership; VolumeSampling.cs is missing."); - if (actionGameManager.Contains("CurrentEnvironment", StringComparison.Ordinal) || - volumeSampling.Contains("ActionGameManager.Instance?.CurrentEnvironment", StringComparison.Ordinal) || - volumeSampling.Contains("ActionGameManager.Instance.CurrentEnvironment", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "ActionGameManager must not expose presentation environment as manager-global gameplay state; visual sampling should read presentation settings directly."); - } - - var sceneSplatSourcePath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "AetheriaSceneRenderSplatSource.cs"); - if (File.Exists(sceneSplatSourcePath) || - volumeSampling.Contains("AetheriaSceneRenderSplatSource", StringComparison.Ordinal) || - volumeSampling.Contains("ResolveSceneSplatDocument", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Unity scene mocks must not publish a competing fog-splat document; VolumeSampling may lower daemon-owned runtime splats only."); - } - - var mapRendererPath = Path.Combine(root, "Assets", "Scripts", "UI", "Menu", "MapRenderer.cs"); - var mapRenderer = File.Exists(mapRendererPath) - ? File.ReadAllText(mapRendererPath) - : throw new InvalidOperationException("Cannot verify daemon current-zone UI projection; MapRenderer.cs is missing."); - var sectorRendererPath = Path.Combine(root, "Assets", "Scripts", "UI", "Menu", "SectorRenderer.cs"); - var sectorRenderer = File.Exists(sectorRendererPath) - ? File.ReadAllText(sectorRendererPath) - : throw new InvalidOperationException("Cannot verify daemon current-zone UI projection; SectorRenderer.cs is missing."); - var sectorMapPath = Path.Combine(root, "Assets", "Scripts", "UI", "Menu", "SectorMap.cs"); - var sectorMap = File.Exists(sectorMapPath) - ? File.ReadAllText(sectorMapPath) - : throw new InvalidOperationException("Cannot verify daemon current-zone UI projection; SectorMap.cs is missing."); - - if (mapRenderer.Contains("GameManager.Zone.GalaxyZone", StringComparison.Ordinal) || - sectorRenderer.Contains("GameManager.Zone.GalaxyZone", StringComparison.Ordinal) || - sectorRenderer.Contains("GameManager.CurrentEntity.Zone.GalaxyZone", StringComparison.Ordinal) || - mapRenderer.Contains("GameManager.CurrentDaemonGalaxyZone", StringComparison.Ordinal) || - sectorRenderer.Contains("GameManager.CurrentDaemonGalaxyZone", StringComparison.Ordinal) || - mapRenderer.Contains("GameManager.ZoneRenderer", StringComparison.Ordinal) || - actionGameManager.Contains("public GalaxyZone CurrentDaemonGalaxyZone", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Map and sector UI must read current-zone identity from the daemon-observed run, not Unity's mirrored Zone hierarchy."); - } - - var compactMapRenderer = CompactSource(mapRenderer); - var compactSectorRenderer = CompactSource(sectorRenderer); - var compactSectorMap = CompactSource(sectorMap); - if (!mapRenderer.Contains("AetheriaUnityRuntimeClientProvider", StringComparison.Ordinal) || - !compactMapRenderer.Contains(".MapViewportDocuments(viewport,\"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.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("AetheriaUnityRuntimeClientProvider.RuntimeState(\"unity-sector-map\")", StringComparison.Ordinal) || - !sectorMap.Contains("_sectorMap = _runtimeState.SectorMap.Latest()", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Map and sector UI must request managed typed map, zone, and surface documents through the Unity runtime client provider."); - } - - var forbiddenMapRendererSettingsSymbols = new[] - { - "CultMeshReactiveDocument", - ".ReactivePlayerSettingsDocument(\"unity-map-renderer\")", - "AetheriaRuntimePlayerSettingsSession", - ".ObservePlayer()", - "_playerSettings?.Dispose()" - } - .Where(symbol => mapRenderer.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (forbiddenMapRendererSettingsSymbols.Length > 0) - { - throw new InvalidOperationException( - "MapRenderer should sample player settings through current typed state; only viewport rendering documents should stay reactive: " + - string.Join(", ", forbiddenMapRendererSettingsSymbols)); - } - - if (sectorMap.Contains("ActionGameManager.ObservedGalaxy", StringComparison.Ordinal) || - sectorRenderer.Contains("ActionGameManager.ObservedGalaxy", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Map and sector UI must not read the raw ObservedGalaxy projection directly."); - } - - var forbiddenSectorMapSymbols = new[] - { - "CultMeshReactiveDocument", - ".ReactiveSectorMap(\"unity-sector-map\")", - "AetheriaRuntimeSectorMapSession", - ".ObserveSectorMap()", - "_sectorMapDocument?.Dispose()" - } - .Where(symbol => sectorMap.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (forbiddenSectorMapSymbols.Length > 0) - { - throw new InvalidOperationException( - "SectorMap should sample the sector map through named current typed state instead of owning a reactive/session handle: " + - string.Join(", ", forbiddenSectorMapSymbols)); - } - - var requiredDaemonControlValidationSymbols = new[] - { - "case AetheriaRuntimeDaemonCommandKinds.SetTractorPower:", - "!IsNormalizedScalar(command.ScalarValue)", - "private static bool IsNormalizedScalar(double value)", - "!IsFinite(command.DirectionX)", - "!IsFinite(command.DirectionY)" - }; - var missingDaemonControlValidationSymbols = requiredDaemonControlValidationSymbols - .Where(symbol => !daemonOperationsSource.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (missingDaemonControlValidationSymbols.Length > 0) - { - throw new InvalidOperationException( - "Daemon continuous controls no longer validate normalized movement and tractor inputs: " + - string.Join(", ", missingDaemonControlValidationSymbols)); - } - - if (actionGameManager.Contains("EntityConstructionBlueprintMaterializer.InstantiateAuthoritativeFromBlueprint", StringComparison.Ordinal) || - actionGameManager.Contains("EntityConstructionBlueprintProjector.InstantiateAuthoritativeFromBlueprint", StringComparison.Ordinal) || - actionGameManager.Contains("EntityConstructionBlueprintProjector.InstantiateFromBlueprint", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Unity frame application must project observed daemon state, not instantiate authoritative gameplay entities."); - } - - if (actionGameManager.Contains("galaxyZone.Contents", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Unity gameplay must not cache observed runtime zone context on the projected GalaxyZone model."); - } - - if (actionGameManager.Contains("ObservedGalaxy.Zones[run.CurrentZoneIndex]", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Unity gameplay must resolve observed zones by daemon zone identity, not projected array slot."); - } - - if (actionGameManager.Contains("FindDaemonZoneSnapshot(", StringComparison.Ordinal) || - sectorRenderer.Contains("FindDaemonZoneSnapshot(", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Unity UI must query daemon zone snapshots by daemon zone identity instead of passing projected zone objects."); - } - - if (galaxy.Contains("public Zone Contents", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Projected GalaxyZone must not own Unity runtime Zone contents; observed zone context belongs to the client lowerer."); - } - - var requiredObservedZoneIdentitySymbols = new[] - { - "public int ZoneIndex = -1;", - "ZoneIndex = zone.ZoneIndex,", - "new GalaxyZone { ZoneIndex = index, Position = v }" - }; - var missingObservedZoneIdentitySymbols = requiredObservedZoneIdentitySymbols - .Where(symbol => !galaxy.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (missingObservedZoneIdentitySymbols.Length > 0) - { - throw new InvalidOperationException( - "Projected GalaxyZone no longer preserves daemon zone identity: " + - string.Join(", ", missingObservedZoneIdentitySymbols)); - } - - var contextPrepRendererLoads = FindMethodScopedLineHits( - actionGameManager, - new[] { "LoadDaemonZoneView(", "BindToEntity(" }) - .Where(hit => hit.MethodName == "ResolveContext") - .Select(hit => $"{hit.MethodName}:{hit.LineNumber}: {hit.Line.Trim()}") - .ToArray(); - if (contextPrepRendererLoads.Length > 0) - { - throw new InvalidOperationException( - "Unity zone context prep must not load renderer instances before observed daemon facades have been rebuilt: " + - string.Join("; ", contextPrepRendererLoads)); - } - - var unauthorizedEntityProjectionHits = FindMethodScopedLineHits( - actionGameManager, - new[] { "Zone.Entities.Add(", "Zone.Entities.Remove(", "Zone.Agents.Clear()" }) - .Select(hit => $"{hit.MethodName}:{hit.LineNumber}: {hit.Line.Trim()}") - .ToArray(); - if (unauthorizedEntityProjectionHits.Length > 0) - { - throw new InvalidOperationException( - "Unity gameplay must not rebuild renderer-local Zone entity membership while lowering daemon snapshots: " + - string.Join("; ", unauthorizedEntityProjectionHits)); - } - - var zonePath = Path.Combine(root, "Assets", "Scripts", "ServerShared", "Zone.cs"); - var zoneSource = File.Exists(zonePath) - ? File.ReadAllText(zonePath) - : throw new InvalidOperationException("Cannot verify Unity observer authority; Zone.cs is missing."); - var materializerPath = Path.Combine(root, "Assets", "Scripts", "ServerShared", "EntityConstructionBlueprintMaterializer.cs"); - var materializerSource = File.Exists(materializerPath) - ? File.ReadAllText(materializerPath) - : throw new InvalidOperationException("Cannot verify Unity observer authority; EntityConstructionBlueprintMaterializer.cs is missing."); - - var requiredProjectionBoundarySymbols = new[] - { - "public static Galaxy ProjectObservedDaemonRun", - "private Galaxy(", - "AetheriaRuntimeRunCheckpointCommit run,", - "public static class EntityConstructionBlueprintCapture", - "public static EntityConstructionBlueprint Capture(Entity entity)", - "public static class EntityConstructionBlueprintMaterializer", - "public static Entity InstantiateAuthoritativeFromBlueprint", - "public static Entity MaterializeObservedFromBlueprint", - "private static Entity BuildFromBlueprint", - "EntityConstructionBlueprintMaterializer.InstantiateAuthoritativeFromBlueprint(_itemManager, this, entityBlueprint)" - }; - var projectionBoundaryCorpus = materializerSource + "\n" + zoneSource + "\n" + galaxy; - var missingProjectionBoundarySymbols = requiredProjectionBoundarySymbols - .Where(symbol => !projectionBoundaryCorpus.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (missingProjectionBoundarySymbols.Length > 0) - { - throw new InvalidOperationException( - "Entity projection no longer separates authoritative construction from observed daemon lowering: " + - string.Join(", ", missingProjectionBoundarySymbols)); - } - - if (galaxy.Contains("public Galaxy(\r\n AetheriaRuntimeRunCheckpointCommit run,", StringComparison.Ordinal) || - galaxy.Contains("public Galaxy(\n AetheriaRuntimeRunCheckpointCommit run,", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Daemon-observed galaxy projection must be reached through ProjectObservedDaemonRun, not a public constructor."); - } - - var forbiddenDaemonQueueLanguage = new[] - { - "observer.Queue", - "_lastQueuedDaemon", - "_hasQueuedDaemon", - "Failed to queue Aetheria daemon", - "Queued Aetheria daemon" - }; - var forbiddenDaemonQueueHits = forbiddenDaemonQueueLanguage - .Where(symbol => actionGameManager.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (forbiddenDaemonQueueHits.Length > 0) - { - throw new InvalidOperationException( - "Unity gameplay still uses queue-shaped daemon language instead of typed sent operations: " + - string.Join(", ", forbiddenDaemonQueueHits)); - } - - if (daemonObserver.Contains("public AetheriaRuntimeDaemonCommandEnvelope Queue", StringComparison.Ordinal) || - daemonObserver.Contains("Queued Aetheria daemon command", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "AetheriaDaemonObserver still exposes queue-shaped public daemon APIs instead of typed Operations."); - } - - var forbiddenManagerFacadeSymbols = new[] - { - "public static ActionGameManager Instance", - "public Entity CurrentEntity", - "public ItemManager ItemManager", - "public Zone Zone", - "public EntitySettings NewEntitySettings", - "using Ink;", - "using Ink.Runtime;", - "private List", - "private readonly (float2 direction, string name)[] _directions", - "private static float Unlerp(", - "DeathPostTransitionTime", - "HypothermiaPost", - "SevereHypothermiaPost", - "WormholeCamera", - "public SectorMap SectorMap", - "public EventLog EventLog", - "public void EnablePlayerInput()", - "public void DisablePlayerInput()" - }; - var managerFacadeHits = forbiddenManagerFacadeSymbols - .Where(symbol => actionGameManager.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (managerFacadeHits.Length > 0) - { - throw new InvalidOperationException( - "ActionGameManager still exposes Unity gameplay facade state as public API: " + - string.Join(", ", managerFacadeHits)); - } - - var requiredDaemonNavigationAuthoritySymbols = new[] - { - "ApplyEnterWormholeIntent(run, command, context.Intents,", - "ApplyTowToStation(run, command, context.DockingDistance)", - "intents.Wormholes.Add", - "entity.WormholeTransition != null", - "StepWormholeTransitions(", - "AetheriaRuntimeDaemonOperations.MoveEntityToZone(", - "entity.WormholeTransition = null", - "run.CurrentZoneIndex = targetZoneIndex", - "run.CurrentEntityKey = movedEntityKey", - "run.DiscoveredZoneIndices = discovered.ToArray()" - }; - var daemonNavigationCorpus = daemonOperationsSource + "\n" + daemonSimulationSource; - var missingDaemonNavigationAuthoritySymbols = requiredDaemonNavigationAuthoritySymbols - .Where(symbol => !daemonNavigationCorpus.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (missingDaemonNavigationAuthoritySymbols.Length > 0) - { - throw new InvalidOperationException( - "Daemon navigation no longer validates intent then commits canonical transition state inside daemon-owned operations/simulation: " + - string.Join(", ", missingDaemonNavigationAuthoritySymbols)); - } - - var requiredSentOperationSymbols = new[] - { - "public sealed class AetheriaUnityPilotCommandSender", - "_lastSentDaemonMoveVector", - "_lastSentDaemonLookDirection", - "_lastSentDaemonTractorPower", - "_hasSentDaemonMoveVector", - "_hasSentDaemonLookDirection", - "_hasSentDaemonTractorPower", - "Func _resolveControl", - "Action submit", - "submit(control)", - "Failed to send Aetheria daemon pilot {label} operation", - "operations => operations.SetMoveVector", - "operations => operations.SetLookDirection", - "operations => operations.SetTractorPower" - }; - var missingSentOperationSymbols = requiredSentOperationSymbols - .Where(symbol => !pilotCommandSender.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (missingSentOperationSymbols.Length > 0) - { - throw new InvalidOperationException( - "Unity pilot command sender no longer describes daemon input as sent typed operations: " + - string.Join(", ", missingSentOperationSymbols)); - } - - var forbiddenSentOperationSymbols = new[] - { - "Func _resolveClient", - "submit(client.Control)", - "Func _resolveObserver", - "Action submit", - "submit(observer.Operations)" - }; - var sentOperationHits = forbiddenSentOperationSymbols - .Where(symbol => pilotCommandSender.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (sentOperationHits.Length > 0) - { - throw new InvalidOperationException( - "Unity pilot command sender still routes typed input through the daemon observer operation escape hatch: " + - string.Join(", ", sentOperationHits)); - } - - var forbiddenManagerPilotCommandSymbols = new[] - { - "private bool TrySubmitPilotOperation(", - "_lastSentDaemonMoveVector", - "_lastSentDaemonLookDirection", - "_lastSentDaemonTractorPower", - "_hasSentDaemonMoveVector", - "_hasSentDaemonLookDirection", - "_hasSentDaemonTractorPower", - "DaemonMoveCommandIntervalSeconds", - "DaemonLookCommandIntervalSeconds", - "DaemonTractorCommandIntervalSeconds" - }; - var managerPilotCommandHits = forbiddenManagerPilotCommandSymbols - .Where(symbol => actionGameManager.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (managerPilotCommandHits.Length > 0) - { - throw new InvalidOperationException( - "ActionGameManager still owns pilot command transport/throttling state instead of delegating to AetheriaUnityPilotCommandSender: " + - string.Join(", ", managerPilotCommandHits)); - } - - var requiredDockingLoweringSymbols = new[] - { - "private AetheriaUnityPilotOperationController PilotOperationController =>", - "PilotOperationController = PilotOperationController", - "public sealed class AetheriaUnityPilotOperationController", - "operations => operations.DockNearest()", - "operations => operations.Undock()", - "operations => operations.Interact()" - }; - - var dockingLoweringSource = actionGameManager + "\n" + pilotOperationController; - var missingDockingLoweringSymbols = requiredDockingLoweringSymbols - .Where(symbol => !dockingLoweringSource.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - - if (missingDockingLoweringSymbols.Length > 0) - { - throw new InvalidOperationException( - "Unity docking/towing UI no longer lowers intent through daemon commands: " + - string.Join(", ", missingDockingLoweringSymbols)); - } - - var forbiddenUnityDockingAuthoritySymbols = new[] - { - "public void Dock()", - "public void Undock()", - "public void TowShip()", - "public ActionGameManager GameManager", - "ActionGameManager GameManager", - "private void RequestDock()", - "private void RequestUndock()", - "private void RequestInteract()", - "private void RequestTowToStation()", - "PilotOperationController.RequestTowToStation(_towingStation)", - "public void RequestTowToStation(Entity towingStation)", - "_towingStation", - "UpdateTowingStation", - "ResolveObservedEntityZoneIndex(_towingStation)", - "_towingStation.CultPositionXZ.x", - "_towingStation.CultPositionXZ.y", - "Missing cockpit component", - "Missing thruster component", - "Missing reactor component", - "Must empty docking bay", - "ZoneRenderer.WormholeInstances.Keys", - "RequestEnterWormhole(wormhole);" - }; - var unityDockingAuthorityHits = forbiddenUnityDockingAuthoritySymbols - .Where(symbol => actionGameManager.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (unityDockingAuthorityHits.Length > 0) - { - throw new InvalidOperationException( - "Unity docking/towing still exposes imperative local authority instead of daemon-bound requests: " + - string.Join(", ", unityDockingAuthorityHits)); - } - - var undockLocalAuthorityHits = FindMethodScopedLineHits( - actionGameManager, - new[] { "CurrentEntity?.Parent == null" }) - .Where(hit => hit.MethodName == "TryRequestDaemonUndock") - .Select(hit => $"ActionGameManager.cs:{hit.LineNumber}: {hit.Line.Trim()}") - .ToArray(); - if (undockLocalAuthorityHits.Length > 0) - { - throw new InvalidOperationException( - "Unity undock request still rejects through renderer-local parent state instead of daemon acceptance: " + - string.Join("; ", undockLocalAuthorityHits)); - } - - var dockLocalAuthorityHits = FindMethodScopedLineHits( - actionGameManager, - new[] { "CurrentEntity?.Parent != null" }) - .Where(hit => hit.MethodName == "TryRequestDaemonDock") - .Select(hit => $"ActionGameManager.cs:{hit.LineNumber}: {hit.Line.Trim()}") - .ToArray(); - if (dockLocalAuthorityHits.Length > 0) - { - throw new InvalidOperationException( - "Unity dock request still rejects through renderer-local parent state instead of daemon acceptance: " + - string.Join("; ", dockLocalAuthorityHits)); - } - - var dockTargetSelectionHits = FindMethodScopedLineHits( - actionGameManager, - new[] { "FindDaemonDockTarget", "Zone.Entities", "CultPositionXZ - CurrentEntity.CultPositionXZ" }) - .Where(hit => hit.MethodName == "TryRequestDaemonDock" || hit.MethodName == "FindDaemonDockTarget") - .Select(hit => $"ActionGameManager.cs:{hit.LineNumber}: {hit.Line.Trim()}") - .ToArray(); - if (dockTargetSelectionHits.Length > 0) - { - throw new InvalidOperationException( - "Unity dock request still selects docking targets from renderer-local zone state instead of daemon acceptance: " + - string.Join("; ", dockTargetSelectionHits)); - } - - var requiredDaemonDockNearestSymbols = new[] - { - "AetheriaRuntimeDaemonCommandKinds.DockNearest", - "AetheriaRuntimeDaemonCommandKinds.Interact", - "internal AetheriaRuntimeDaemonCommandEnvelope DockNearest(", - "internal AetheriaRuntimeDaemonCommandEnvelope Interact(", - "ApplyDockNearestIntent(run, command, context, context.DockingDistance)", - "context.WormholeExitRadius", - "AetheriaRuntimeDaemonOperationContext.DefaultDockingDistance", - "AetheriaRuntimeDaemonOperationContext.DefaultWormholeExitRadius", - "var maximumDistance = defaultDockingDistance;", - "TryFindFirstDockTarget(", - "TryFindNearestWormholeTarget(" - }; - var daemonDockNearestSources = daemonDocuments + "\n" + daemonOperationClient + "\n" + daemonOperationsSource; - var missingDaemonDockNearestSymbols = requiredDaemonDockNearestSymbols - .Where(symbol => !daemonDockNearestSources.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (missingDaemonDockNearestSymbols.Length > 0) - { - throw new InvalidOperationException( - "Daemon no longer owns first-eligible docking target selection: " + - string.Join(", ", missingDaemonDockNearestSymbols)); - } - - var unityOwnedInteractionTuningHits = FindMethodScopedLineHits( - actionGameManager, - new[] { "Settings.GameplaySettings.DockingDistance", "Settings.GameplaySettings.WormholeExitRadius" }) - .Where(hit => hit.MethodName == "TryRequestDaemonDock" || hit.MethodName == "TryRequestDaemonInteract") - .Select(hit => $"ActionGameManager.cs:{hit.LineNumber}: {hit.Line.Trim()}") - .ToArray(); - if (unityOwnedInteractionTuningHits.Length > 0) - { - throw new InvalidOperationException( - "Unity interaction requests still carry GameSettings tuning instead of semantic daemon operations: " + - string.Join("; ", unityOwnedInteractionTuningHits)); - } - - var movementLocalAuthorityHits = FindMethodScopedLineHits( - actionGameManager, - new[] { "CurrentEntity?.Parent != null" }) - .Where(hit => hit.MethodName == "TryRequestDaemonEnterWormhole" || hit.MethodName == "TryRequestDaemonTowToStation") - .Select(hit => $"ActionGameManager.cs:{hit.LineNumber}: {hit.Line.Trim()}") - .ToArray(); - if (movementLocalAuthorityHits.Length > 0) - { - throw new InvalidOperationException( - "Unity movement requests still reject through renderer-local parent state instead of daemon acceptance: " + - string.Join("; ", movementLocalAuthorityHits)); - } - - var requiredShieldToggleSymbols = new[] - { - "PilotOperationController.RequestShieldToggle()", - "operations => operations.ToggleShieldEnabled()", - "AetheriaRuntimeDaemonCommandKinds.ToggleShieldEnabled", - "ApplyToggleEquipmentBehaviorItem(run, command, \"Shield\"" - }; - var gameplayInputShellPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "AetheriaUnityGameplayInputShell.cs"); - var gameplayInputShell = File.Exists(gameplayInputShellPath) - ? File.ReadAllText(gameplayInputShellPath) - : throw new InvalidOperationException("Cannot verify Unity shield input lowering; AetheriaUnityGameplayInputShell.cs is missing."); - var unityPilotOperationSources = actionGameManager + "\n" + pilotOperationController + "\n" + gameplayInputShell; - var missingShieldToggleSymbols = requiredShieldToggleSymbols - .Where(symbol => - !unityPilotOperationSources.Contains(symbol, StringComparison.Ordinal) && - !daemonOperationsSource.Contains(symbol, StringComparison.Ordinal) && - !daemonDocuments.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (missingShieldToggleSymbols.Length > 0) - { - throw new InvalidOperationException( - "Shield input no longer lowers through a semantic daemon shield command: " + - string.Join(", ", missingShieldToggleSymbols)); - } - - var forbiddenUnityShieldAuthoritySymbols = new[] - { - "RequestShieldEnabled(", - "TryRequestDaemonShieldEnabled(", - "CurrentEntity.Shield.Item.Enabled", - "var shieldItem = CurrentEntity?.Shield?.Item", - "CurrentEntity.Equipment.IndexOf(shieldItem)" - }; - var shieldAuthorityHits = forbiddenUnityShieldAuthoritySymbols - .Where(symbol => actionGameManager.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (shieldAuthorityHits.Length > 0) - { - throw new InvalidOperationException( - "Unity shield input still derives equipment authority from renderer-local shield components: " + - string.Join(", ", shieldAuthorityHits)); - } - - var forbiddenActionBarIntentAcceptanceSymbols = new[] - { - "observer == null || !observer.HasAuthoritativeState || equipmentIndex < 0 || behaviorIndex < 0", - "observer == null || !observer.HasAuthoritativeState || weaponGroup < 0" - }; - var actionBarIntentAcceptanceHits = forbiddenActionBarIntentAcceptanceSymbols - .Where(symbol => actionGameManager.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (actionBarIntentAcceptanceHits.Length > 0) - { - throw new InvalidOperationException( - "Unity action-bar intent requests still reject through renderer-local index checks instead of daemon authority: " + - string.Join(", ", actionBarIntentAcceptanceHits)); - } - - var requiredTargetAuthoritySymbols = new[] - { - "ApplySetTarget(run, command, context)", - "ApplyTargetCycle(run, command, TargetCycleMode.Nearest)", - "ApplyTargetCycle(run, command, TargetCycleMode.Next)", - "ApplyTargetCycle(run, command, TargetCycleMode.Previous)", - "ApplyTargetReticle(run, command)", - "VisibleHostileTargets(zone, actor, actorIndex)", - "actor.Contacts ?? Array.Empty()", - "contact.TargetEntityIndex == targetIndex && contact.Visible" - }; - var missingTargetAuthoritySymbols = requiredTargetAuthoritySymbols - .Where(symbol => !daemonOperationsSource.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (missingTargetAuthoritySymbols.Length > 0) - { - throw new InvalidOperationException( - "Daemon target selection no longer validates visibility through authoritative contact state: " + - string.Join(", ", missingTargetAuthoritySymbols)); - } - - var requiredUnityTargetRequestSymbols = new[] - { - "RequestTargetNearest()", - "RequestTargetNext()", - "RequestTargetPrevious()", - "RequestTargetReticle()", - "operations => operations.TargetNearest()", - "operations => operations.TargetNext()", - "operations => operations.TargetPrevious()", - "operations => operations.TargetReticle(" - }; - var unityTargetRequestSources = actionGameManager + "\n" + pilotOperationController + "\n" + daemonOperations; - var missingUnityTargetRequestSymbols = requiredUnityTargetRequestSymbols - .Where(symbol => !unityTargetRequestSources.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (missingUnityTargetRequestSymbols.Length > 0) - { - throw new InvalidOperationException( - "Unity target cycling no longer lowers through typed daemon requests: " + - string.Join(", ", missingUnityTargetRequestSymbols)); - } - - var forbiddenUnityTargetCycleSymbols = new[] - { - ".MaxBy(x => CultMath.math.length(x.CultPosition - CurrentEntity.CultPosition))", - ".OrderBy(x => CultMath.math.length(x.CultPosition - CurrentEntity.CultPosition))", - ".MaxBy(x => CultMath.math.dot(", - "Array.IndexOf(targets" - }; - var unityTargetCycleHits = forbiddenUnityTargetCycleSymbols - .Where(symbol => actionGameManager.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (unityTargetCycleHits.Length > 0) - { - throw new InvalidOperationException( - "Unity target cycling still orders or selects targets from renderer-local entity state instead of daemon contacts: " + - string.Join(", ", unityTargetCycleHits)); - } - - var requiredIntentAuthoritySymbols = new[] - { - "!HasWeaponGroup(entity, command.WeaponGroup)", - "!HasEquipmentBehavior(entity, command.EquipmentIndex, command.BehaviorIndex)", - "private static bool HasWeaponGroup(AetheriaRuntimeEntitySnapshotCommit entity, int weaponGroup)", - "private static bool HasEquipmentBehavior(" - }; - var missingIntentAuthoritySymbols = requiredIntentAuthoritySymbols - .Where(symbol => !daemonOperationsSource.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (missingIntentAuthoritySymbols.Length > 0) - { - throw new InvalidOperationException( - "Daemon simulation intents no longer validate weapon-group and behavior indices against authoritative entity state: " + - string.Join(", ", missingIntentAuthoritySymbols)); - } - - var forbiddenUnityInputApplySymbols = new[] - { - "ApplyEnterWormhole(", - "ApplyLookDirection(", - "ApplyTractorPower(", - "ApplyTargetSelection(", - "ApplyOverrideShutdown(", - "ApplySensorPing(", - "ApplyHeatsinksEnabled(", - "ApplyShieldEnabled(", - "ApplyDock(", - "ApplyUndock(" - }; - var unityInputApplyHits = forbiddenUnityInputApplySymbols - .Where(symbol => actionGameManager.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (unityInputApplyHits.Length > 0) - { - throw new InvalidOperationException( - "Unity input still exposes Apply vocabulary for daemon-bound requests: " + - string.Join(", ", unityInputApplyHits)); - } - - var requiredTowDaemonSymbols = new[] - { - "TowToStation", - "AetheriaRuntimeDaemonCommandKinds.TowToStation", - "ApplyTowToStation(", - "double attachmentDistance", - "towing.ChildEntityIndices", - "command.TextValue, \"attach\"", - "attachmentDistance * attachmentDistance", - "command.TextValue, \"detach\"", - "zone.Orbits =", - "station.OrbitKey = orbitKey", - "public CultMeshOperationReceipt TowToStation(string stationEntityKey)" - }; - - var missingTowDaemonSymbols = requiredTowDaemonSymbols - .Where(symbol => - !daemonDocuments.Contains(symbol, StringComparison.Ordinal) && - !daemonOperationsSource.Contains(symbol, StringComparison.Ordinal) && - !daemonIntent.Contains(symbol, StringComparison.Ordinal) && - !daemonObserver.Contains(symbol, StringComparison.Ordinal) && - !daemonOperations.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - - if (missingTowDaemonSymbols.Length > 0) - { - throw new InvalidOperationException( - "Towing is not fully owned by daemon command state: " + - string.Join(", ", missingTowDaemonSymbols)); - } - - var forbiddenMenuSymbols = new[] - { - "new Galaxy(Settings", - "new Galaxy(settings", - "new Galaxy(", - "SectorGenerationSettings", - "RunGenerator", - "ZoneGenerator.GenerateZone" - }; - - var forbiddenMenuHits = forbiddenMenuSymbols - .Where(symbol => mainMenu.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - - if (forbiddenMenuHits.Length > 0) - { - throw new InvalidOperationException( - "MainMenu still has local run generation fallback on the gameplay boot path: " + - string.Join(", ", forbiddenMenuHits)); - } - - var requiredMenuSymbols = new[] - { - "TryStartDaemonObservedGame", - "ResolveSectorMap(stateBoot)", - "AetheriaUnityRuntimeClientProvider", - ".RuntimeState(stateBoot, \"unity-main-menu\")", - ".SectorMap", - ".Latest()", - "sectorMap.FrameId", - "SceneManager.LoadScene(\"ARPG\")" - }; - - var missingMenuSymbols = requiredMenuSymbols - .Where(symbol => !mainMenu.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - - if (missingMenuSymbols.Length > 0) - { - throw new InvalidOperationException( - "MainMenu no longer boots gameplay strictly from an authoritative daemon frame: " + - string.Join(", ", missingMenuSymbols)); - } -} - -static void RequireDaemonEnergyAndThermalAuthority(string root) -{ - var runtime = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime"); - var energyPath = Path.Combine(runtime, "AetheriaRuntimeEnergySimulation.cs"); - var thermalPath = Path.Combine(runtime, "AetheriaRuntimeThermalSimulation.cs"); - var medicalPath = Path.Combine(runtime, "AetheriaRuntimeThermalMedicalSimulation.cs"); - var settingsPath = Path.Combine(runtime, "AetheriaRuntimeDaemonSimulationSettings.cs"); - var simulationPath = Path.Combine(runtime, "AetheriaRuntimeDaemonSimulation.cs"); - var snapshotPath = Path.Combine(runtime, "AetheriaRuntimeSnapshotDocuments.cs"); - var surfacePath = Path.Combine(runtime, "AetheriaRuntimeDaemonGameSurfaceBuilder.cs"); - var operationsPath = Path.Combine(runtime, "AetheriaRuntimeDaemonOperations.cs"); - - var sources = new Dictionary - { - [energyPath] = File.Exists(energyPath) ? File.ReadAllText(energyPath) : "", - [thermalPath] = File.Exists(thermalPath) ? File.ReadAllText(thermalPath) : "", - [medicalPath] = File.Exists(medicalPath) ? File.ReadAllText(medicalPath) : "", - [settingsPath] = File.Exists(settingsPath) ? File.ReadAllText(settingsPath) : "", - [simulationPath] = File.Exists(simulationPath) ? File.ReadAllText(simulationPath) : "", - [snapshotPath] = File.Exists(snapshotPath) ? File.ReadAllText(snapshotPath) : "", - [surfacePath] = File.Exists(surfacePath) ? File.ReadAllText(surfacePath) : "" - }; - var required = new Dictionary - { - [energyPath] = new[] - { - "public static class AetheriaRuntimeEnergySimulation", - "public static bool TryConsume(", - "public static void StepRadiators(", - "public static void SettleReactors(", - "AddCapacitorCharge(" - }, - [thermalPath] = new[] - { - "public static void EnsureTopology(", - "public static void UpdateEquipmentStates(", - "public static void AddHeatToEquipment(", - "public static void ApplyWear(" - }, - [medicalPath] = new[] - { - "public static class AetheriaRuntimeThermalMedicalSimulation", - "temperature > settings.HeatstrokeTemperature", - "settings.HeatstrokeRecoveryPerSecond * deltaSeconds", - "temperature < settings.HypothermiaTemperature", - "settings.HypothermiaRecoveryPerSecond * deltaSeconds", - "entity.Heatstroke > 0.99", - "entity.Hypothermia > 0.99" - }, - [settingsPath] = new[] - { - "public double SevereThermalRiskThreshold", - "public double HeatstrokeTemperature", - "public double HeatstrokeMultiplier", - "public double HypothermiaTemperature", - "public double HypothermiaMultiplier" - }, - [simulationPath] = new[] - { - "AetheriaRuntimeThermalSimulation.EnsureTopology(entity, catalog)", - "AetheriaRuntimeThermalSimulation.UpdateEquipmentStates(entity, catalog, deltaSeconds)", - "AetheriaRuntimeEnergySimulation.BeginTick(entity, catalog)", - "AetheriaRuntimeEnergySimulation.StepRadiators(entity, catalog, deltaSeconds)", - "AetheriaRuntimeEnergySimulation.SettleReactors(entity, catalog, deltaSeconds)", - "AetheriaRuntimeThermalSimulation.Step(entity, deltaSeconds, catalog)", - "AetheriaRuntimeThermalMedicalSimulation.Step(", - "PublishThermalMedicalEvents(", - "CommitDestruction(run, zone, entity, -1,", - "Reason = target.CauseOfDeath", - "AetheriaRuntimeEnergySimulation.TryConsume(entity, catalog, demand)" - }, - [snapshotPath] = new[] - { - "public IReadOnlyList EquipmentStates", - "public sealed class AetheriaRuntimeEquipmentStateCommit", - "public double ThermalPerformance", - "public double Wear", - "public bool Online", - "public string CauseOfDeath" - }, - [surfacePath] = new[] - { - "[\"capacitorCharge\"]", - "[\"reactorDraw\"]", - "[\"radiatorTemperature\"]", - "[\"minimumEquipmentThermalPerformance\"]", - "[\"offlineEquipmentCount\"]", - "[\"cockpitTemperature\"]", - "[\"heatstrokeRisk\"]", - "[\"hypothermiaRisk\"]", - "[\"heatstrokePhasingFrequency\"]", - "[\"deathTransitionSeconds\"]", - "[\"causeOfDeath\"]" - } - }; - var missing = required.SelectMany(pair => pair.Value - .Where(symbol => !sources[pair.Key].Contains(symbol, StringComparison.Ordinal)) - .Select(symbol => $"{Path.GetRelativePath(root, pair.Key)}: missing {symbol}")) - .ToArray(); - if (missing.Length > 0) - throw new InvalidOperationException( - "Daemon energy and thermal authority must execute one catalog-derived network and publish its facts through Eve: " + - string.Join("; ", missing)); - - var operations = File.Exists(operationsPath) ? File.ReadAllText(operationsPath) : ""; - var forbiddenOperations = new[] - { - "ApplyCommitEnergy(", - "ApplySetHeat(", - "ApplySetEquipmentOnline(", - "ApplyEquipmentWear(", - "ApplySetHeatstroke(", - "ApplySetHypothermia(", - "ApplySetCauseOfDeath(" - }; - var surviving = forbiddenOperations.Where(symbol => operations.Contains(symbol, StringComparison.Ordinal)).ToArray(); - if (surviving.Length > 0) - throw new InvalidOperationException( - "Client commands may request semantic behavior but may not directly write daemon energy, heat, wear, or equipment online state: " + - string.Join(", ", surviving)); - - var simulation = sources[simulationPath]; - var ordered = new[] - { - "AetheriaRuntimeEnergySimulation.StepRadiators(entity, catalog, deltaSeconds)", - "AetheriaRuntimeEnergySimulation.SettleReactors(entity, catalog, deltaSeconds)", - "AetheriaRuntimeThermalSimulation.Step(entity, deltaSeconds, catalog)" - }; - if (!(simulation.IndexOf(ordered[0], StringComparison.Ordinal) < simulation.IndexOf(ordered[1], StringComparison.Ordinal) && - simulation.IndexOf(ordered[1], StringComparison.Ordinal) < simulation.IndexOf(ordered[2], StringComparison.Ordinal))) - throw new InvalidOperationException( - "Daemon tick order must pump radiators before reactor settlement, then conduct and radiate the resulting cell heat."); -} - -static void RequireThermalPresentationAssetAuthority(string root) -{ - var assets = File.ReadAllText(Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeAssets.cs")); - var daemon = File.ReadAllText(Path.Combine(root, "Aetheria.State.Daemon", "Program.cs")); - var builder = File.ReadAllText(Path.Combine(root, "Assets", "Editor", "EveAssetBundleBuilder.cs")); - var migrator = File.ReadAllText(Path.Combine(root, "Assets", "Editor", "EveThermalProfileMigrator.cs")); - var sink = File.ReadAllText(Path.Combine(root, "Aetheria.Unity", "Assets", "AetheriaUnityThermalPresentationSink.cs")); - var manifest = File.ReadAllText(Path.Combine(root, "Aetheria.Unity", "Packages", "manifest.json")); - var required = new[] - { - "post.thermal.heatstroke", "post.thermal.severe-heatstroke", - "post.thermal.hypothermia", "post.thermal.severe-hypothermia", "post.death", - "Assets/Generated/Eve/Thermal/Heatstroke.asset", - "EveThermalProfileMigrator.EnsureGenerated()", - "[\"presentationRole\"] = entry.Ref.Metadata.TryGetValue", - "IEveUnityThermalPresentationAssetSink", - "assets.Heatstroke as VolumeProfile", - "_death.weight = frame.DeathWeight" - }; - var body = assets + daemon + builder + migrator + sink + manifest; - var missing = required.Where(symbol => !body.Contains(symbol, StringComparison.Ordinal)).ToArray(); - if (missing.Length > 0) - throw new InvalidOperationException( - "Thermal presentation assets must remain provider-owned, role-addressed CDN artifacts lowered by the generic EveUnity sink contract: " + - string.Join(", ", missing)); - if (sink.Contains("HeatstrokeTemperature", StringComparison.Ordinal) || - sink.Contains("HypothermiaTemperature", StringComparison.Ordinal) || - sink.Contains("Mathf.Sin", StringComparison.Ordinal)) - throw new InvalidOperationException( - "The Aetheria Unity thermal sink may bind profiles and weights but may not reconstruct medical or phasing authority."); -} - -static void RequireCatalogHullPresentationAuthority(string root) -{ - var assets = File.ReadAllText(Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeAssets.cs")); - var surface = File.ReadAllText(Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeDaemonGameSurfaceBuilder.cs")); - var soa = File.ReadAllText(Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeDaemonSoaFramePublisher.cs")); - var tick = File.ReadAllText(Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeDaemonTickRunner.cs")); - var daemon = File.ReadAllText(Path.Combine(root, "Aetheria.State.Daemon", "Program.cs")); - var builder = File.ReadAllText(Path.Combine(root, "Assets", "Editor", "EveAssetBundleBuilder.cs")); - var required = new[] - { - "catalog?.FindItem(entity.HullItemKey", - "HullPrefabAssetKey(ResolvePresentationHull(hull, catalog!).ItemKey)", - "HullPrefabAssetKey(item.ItemKey)", - "!string.IsNullOrWhiteSpace(item.HullPrefab)", - "ResolveEntityPrefabAssetRef(entity, catalog)", - "using var hotFrame = publisher.BuildCurrentZoneEntities(", - "realtimeDemand: hasRealtimeConsumers);", - "AetheriaRuntimeCatalogStore.OpenReadOnly(", - "AetheriaRuntimeAssets.ProjectManifest(catalog).Assets" - }; - var body = assets + surface + soa + tick + daemon + builder; - var missing = required.Where(symbol => !body.Contains(symbol, StringComparison.Ordinal)).ToArray(); - if (missing.Length > 0) - throw new InvalidOperationException( - "Typed hull identity must choose one provider-owned catalog prefab across the manifest, Eve scene, and SoA paths: " + - string.Join(", ", missing)); - if (!Regex.IsMatch( - daemon, - @"publisher\.BuildCurrentZoneEntities\(\s*frame,\s*catalog,\s*realtimeDemand:\s*hasRealtimeConsumers\)", - RegexOptions.CultureInvariant)) - throw new InvalidOperationException( - "The daemon hot SoA path must pass the same managed runtime catalog used by the asset manifest and Eve scene projection."); - - var catalogLookup = assets.IndexOf("catalog?.FindItem(entity.HullItemKey", StringComparison.Ordinal); - var fallbackLookup = assets.IndexOf("var kind = (entity.Kind", StringComparison.Ordinal); - if (catalogLookup < 0 || fallbackLookup < 0 || catalogLookup > fallbackLookup) - throw new InvalidOperationException( - "Faction/category prefab fallbacks must not override an authored typed hull prefab."); -} - -static void RequireUnityDoesNotCallSharedSimulationTicks(string root) -{ - var checkedRoots = new[] - { - Path.Combine(root, "Assets", "Scripts", "Gameplay"), - Path.Combine(root, "Assets", "Scripts", "UI") - }; - - var forbiddenCallerSymbols = new[] - { - "Zone.Update(", - ".Zone.Update(", - "CurrentEntity.Update(", - ".CurrentEntity.Update(", - "entity.Update(delta", - "entity.Update(Time.deltaTime", - "ship.Update(delta", - "ship.Update(Time.deltaTime", - "agent.Update(delta", - "agent.Update(Time.deltaTime", - "equippedItem.Update(delta", - "equippedItem.Update(Time.deltaTime", - "alwaysUpdatedBehavior.Update(delta", - "alwaysUpdatedBehavior.Update(Time.deltaTime" - }; - - var hits = checkedRoots - .Where(Directory.Exists) - .SelectMany(path => Directory.EnumerateFiles(path, "*.cs", SearchOption.AllDirectories)) - .Where(path => !Path.GetFileName(path).EndsWith("Tests.cs", StringComparison.Ordinal)) - .SelectMany(path => File.ReadLines(path) - .Select((line, index) => new { Path = path, LineNumber = index + 1, Line = line })) - .Where(line => forbiddenCallerSymbols.Any(symbol => line.Line.Contains(symbol, StringComparison.Ordinal))) - .Select(line => $"{Path.GetRelativePath(root, line.Path)}:{line.LineNumber}: {line.Line.Trim()}") - .ToArray(); - - if (hits.Length > 0) - { - throw new InvalidOperationException( - "Unity gameplay/UI still calls shared simulation tick methods instead of observing daemon frames: " + - string.Join("; ", hits)); - } - - var daemonTickRunnerPath = Path.Combine( - root, - "Packages", - "org.gamecult.aetheria.state", - "Runtime", - "AetheriaRuntimeDaemonTickRunner.cs"); - var daemonTickRunner = File.Exists(daemonTickRunnerPath) - ? File.ReadAllText(daemonTickRunnerPath) - : throw new InvalidOperationException("Cannot verify shared simulation authority; daemon tick runner is missing."); - var daemonProgramPath = Path.Combine(root, "Aetheria.State.Daemon", "Program.cs"); - var daemonProgram = File.Exists(daemonProgramPath) - ? File.ReadAllText(daemonProgramPath) - : throw new InvalidOperationException("Cannot verify shared simulation authority; daemon host is missing."); - - var requiredDaemonTickSymbols = new[] - { - "AetheriaRuntimeDaemonTickRunner", - "Tick(", - "AetheriaRuntimeDaemonFrameDocument.Create", - "BuildPublications", - "AetheriaRuntimeDaemonOperations.Execute(" - }; - - var missingDaemonTickSymbols = requiredDaemonTickSymbols - .Where(symbol => !daemonTickRunner.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - - if (missingDaemonTickSymbols.Length > 0) - { - throw new InvalidOperationException( - "Shared simulation ticks no longer produce daemon-owned managed frame documents: " + - string.Join(", ", missingDaemonTickSymbols)); - } - - var snapshotDocumentsPath = Path.Combine( - root, - "Packages", - "org.gamecult.aetheria.state", - "Runtime", - "AetheriaRuntimeSnapshotDocuments.cs"); - var oldRuntimeSpecificAuthorityFiles = new[] - { - Path.Combine( - root, - "Packages", - "org.gamecult.aetheria.state", - "Runtime", - "AetheriaRuntimeRtsSimulation.cs"), - Path.Combine( - root, - "Packages", - "org.gamecult.aetheria.state", - "Runtime", - "AetheriaRuntimeRtsSimulation.cs.meta"), - Path.Combine( - root, - "Packages", - "org.gamecult.aetheria.state", - "Runtime", - "AetheriaRuntimeRtsDocuments.cs"), - Path.Combine( - root, - "Packages", - "org.gamecult.aetheria.state", - "Runtime", - "AetheriaRuntimeRtsViewportDocuments.cs") - }; - var survivingRuntimeSpecificAuthorityFiles = oldRuntimeSpecificAuthorityFiles - .Where(File.Exists) - .Select(path => Path.GetRelativePath(root, path)) - .ToArray(); - if (survivingRuntimeSpecificAuthorityFiles.Length > 0) - { - throw new InvalidOperationException( - "Aetheria daemon/shared simulation and viewport authority must not be runtime-named: " + - string.Join(", ", survivingRuntimeSpecificAuthorityFiles)); - } - - var daemonSimulationPath = Path.Combine( - root, - "Packages", - "org.gamecult.aetheria.state", - "Runtime", - "AetheriaRuntimeDaemonSimulation.cs"); - var daemonSimulationSettingsPath = Path.Combine( - root, - "Packages", - "org.gamecult.aetheria.state", - "Runtime", - "AetheriaRuntimeDaemonSimulationSettings.cs"); - var flightSimulationPath = Path.Combine( - root, - "Packages", - "org.gamecult.aetheria.state", - "Runtime", - "AetheriaRuntimeFlightSimulation.cs"); - var worldPhysicsContractPath = Path.Combine( - root, - "Packages", - "org.gamecult.aetheria.state", - "Runtime", - "AetheriaRuntimeWorldPhysics.cs"); - var ymirWorldPhysicsPath = Path.Combine(root, "Aetheria.State.Daemon", "AetheriaYmirWorldPhysics.cs"); - var obsoletePhysicalPayloadAuthorityPaths = new[] - { - Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimePhysicalPayloadPhysics.cs"), - Path.Combine(root, "Aetheria.State.Daemon", "AetheriaYmirPhysicalPayloadPhysics.cs") - }; - var daemonProjectPath = Path.Combine(root, "Aetheria.State.Daemon", "Aetheria.State.Daemon.csproj"); - var gameDocumentsPath = Path.Combine( - root, - "Packages", - "org.gamecult.aetheria.state", - "Runtime", - "AetheriaRuntimeGameDocuments.cs"); - var gameViewportDocumentsPath = Path.Combine( - root, - "Packages", - "org.gamecult.aetheria.state", - "Runtime", - "AetheriaRuntimeGameViewportDocuments.cs"); - var verseAuthorityPolicyPath = Path.Combine( - root, - "Packages", - "org.gamecult.aetheria.state", - "Runtime", - "AetheriaRuntimeVerseAuthorityPolicy.cs"); - var stateBoundaryPath = Path.Combine( - root, - "Packages", - "org.gamecult.aetheria.state", - "Runtime", - "AetheriaRuntimeStateBoundary.cs"); - var clientTargetStorePath = Path.Combine( - root, - "Packages", - "org.gamecult.aetheria.state", - "Runtime", - "AetheriaRuntimeClientTargetStore.cs"); - var runtimeClientProviderPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "AetheriaUnityRuntimeClientProvider.cs"); - var rtsLocalDocumentsPath = Path.Combine(root, "Aetheria.Rts.Web", "Electron", "aetheria-rts-local-documents.ts"); - var rtsGeneratedBindingsPath = Path.Combine(root, "Aetheria.Rts.Web", "Electron", "aetheria-rts-generated-bindings.ts"); - var rtsElectronMainPath = Path.Combine(root, "Aetheria.Rts.Web", "Electron", "main.ts"); - var rtsElectronCultMeshPath = Path.Combine(root, "Aetheria.Rts.Web", "Electron", "aetheria-cultmesh.ts"); - var rtsClientAppPath = Path.Combine(root, "Aetheria.Rts.Web", "Client", "app.ts"); - var verseAuthorityPlanPath = Path.Combine(root, "Aetheria.State", "docs", "verse-authority-implementation-plan.md"); - var stage7ThinClientPlanPath = Path.Combine(root, "Aetheria.State", "docs", "stage-7-thin-client-staged-implementation-plan.md"); - var stage7ClientSurfaceInventoryPath = Path.Combine(root, "Aetheria.State", "docs", "stage-7-client-surface-inventory.md"); - var daemonCodeMapPath = Path.Combine(root, "Aetheria.State", "docs", "daemon-code-map-for-rust-port.md"); - var daemonRuntimeDocumentTestsPath = Path.Combine(root, "Assets", "Scripts", "Tests", "DaemonRuntimeDocumentTests.cs"); - var authoritySmokePath = Path.Combine(root, "Aetheria.State.AuthoritySmoke", "Program.cs"); - var freezePath = Path.Combine(root, "Aetheria.State.Freeze", "Program.cs"); - var snapshotDocuments = File.Exists(snapshotDocumentsPath) - ? File.ReadAllText(snapshotDocumentsPath) - : throw new InvalidOperationException("Cannot verify daemon projectile authority; runtime snapshot documents are missing."); - var daemonSimulation = File.Exists(daemonSimulationPath) - ? File.ReadAllText(daemonSimulationPath) - : throw new InvalidOperationException("Cannot verify daemon projectile authority; daemon simulation is missing."); - var daemonSimulationSettings = File.Exists(daemonSimulationSettingsPath) - ? File.ReadAllText(daemonSimulationSettingsPath) - : throw new InvalidOperationException("Cannot verify daemon simulation authority; daemon simulation settings are missing."); - var flightSimulation = File.Exists(flightSimulationPath) - ? File.ReadAllText(flightSimulationPath) - : throw new InvalidOperationException("Cannot verify daemon flight authority; actuator simulation is missing."); - var worldPhysicsContract = File.Exists(worldPhysicsContractPath) - ? File.ReadAllText(worldPhysicsContractPath) - : throw new InvalidOperationException("Cannot verify daemon projectile authority; shared world physics port is missing."); - var ymirWorldPhysics = File.Exists(ymirWorldPhysicsPath) - ? File.ReadAllText(ymirWorldPhysicsPath) - : throw new InvalidOperationException("Cannot verify daemon projectile authority; retained daemon Ymir owner is missing."); - var daemonProject = File.ReadAllText(daemonProjectPath); - var gameDocuments = File.Exists(gameDocumentsPath) - ? File.ReadAllText(gameDocumentsPath) - : throw new InvalidOperationException("Cannot verify daemon projectile authority; game viewport documents are missing."); - var gameViewportDocuments = File.Exists(gameViewportDocumentsPath) - ? File.ReadAllText(gameViewportDocumentsPath) - : throw new InvalidOperationException("Cannot verify daemon projectile authority; zone render documents are missing."); - var verseAuthorityPolicy = File.Exists(verseAuthorityPolicyPath) - ? File.ReadAllText(verseAuthorityPolicyPath) - : throw new InvalidOperationException("Cannot verify runtime-neutral Verse authority policy; policy document is missing."); - var stateBoundary = File.Exists(stateBoundaryPath) - ? File.ReadAllText(stateBoundaryPath) - : throw new InvalidOperationException("Cannot verify runtime-neutral client target authority; state boundary is missing."); - var clientTargetStore = File.Exists(clientTargetStorePath) - ? File.ReadAllText(clientTargetStorePath) - : throw new InvalidOperationException("Cannot verify runtime-neutral client target authority; client target store is missing."); - var runtimeClientProvider = File.Exists(runtimeClientProviderPath) - ? File.ReadAllText(runtimeClientProviderPath) - : throw new InvalidOperationException("Cannot verify runtime-neutral client target authority; Unity runtime client provider is missing."); - var rtsLocalDocuments = File.Exists(rtsLocalDocumentsPath) - ? File.ReadAllText(rtsLocalDocumentsPath) - : throw new InvalidOperationException("Cannot verify daemon projectile authority; Electron local projection is missing."); - var rtsGeneratedBindings = File.Exists(rtsGeneratedBindingsPath) - ? File.ReadAllText(rtsGeneratedBindingsPath) - : throw new InvalidOperationException("Cannot verify Electron generated game bindings; generated bindings are missing."); - var rtsElectronMain = File.Exists(rtsElectronMainPath) - ? File.ReadAllText(rtsElectronMainPath) - : throw new InvalidOperationException("Cannot verify Electron CultMesh CDN asset authority; Electron main process is missing."); - var rtsElectronCultMesh = File.Exists(rtsElectronCultMeshPath) - ? File.ReadAllText(rtsElectronCultMeshPath) - : throw new InvalidOperationException("Cannot verify Electron CultMesh CDN asset authority; Electron CultMesh client is missing."); - var rtsClientApp = File.Exists(rtsClientAppPath) - ? File.ReadAllText(rtsClientAppPath) - : throw new InvalidOperationException("Cannot verify Electron CultMesh CDN asset authority; renderer app is missing."); - var activeStage7ArchitectureDocs = new[] - { - verseAuthorityPlanPath, - stage7ThinClientPlanPath, - stage7ClientSurfaceInventoryPath, - daemonCodeMapPath - } - .Select(path => File.Exists(path) - ? File.ReadAllText(path) - : throw new InvalidOperationException( - "Cannot verify RTS client authority documentation; architecture doc is missing: " + - Path.GetRelativePath(root, path))) - .ToArray(); - var daemonRuntimeDocumentTests = File.Exists(daemonRuntimeDocumentTestsPath) - ? File.ReadAllText(daemonRuntimeDocumentTestsPath) - : throw new InvalidOperationException("Cannot verify daemon projectile authority; daemon runtime document tests are missing."); - var authoritySmoke = File.Exists(authoritySmokePath) - ? File.ReadAllText(authoritySmokePath) - : throw new InvalidOperationException("Cannot verify daemon transport vocabulary; authority smoke harness is missing."); - var freeze = File.Exists(freezePath) - ? File.ReadAllText(freezePath) - : throw new InvalidOperationException("Cannot verify daemon transport vocabulary; freeze harness is missing."); - - var requiredPhysicalPayloadAuthoritySymbols = new Dictionary - { - [snapshotDocumentsPath] = new[] - { - "public IReadOnlyList PhysicalPayloads", - "public sealed class AetheriaRuntimePhysicalPayloadCommit" - }, - [daemonSimulationPath] = new[] - { - "public static class AetheriaRuntimeDaemonSimulation", - "AetheriaRuntimeDaemonSimulationSettings settings", - "StepCombat(run, zone, entities, intents, deltaSeconds, settings, worldPhysics, catalog,", - ".FindAllOperational(attacker, catalog);", - "ResolveWeapons(attacker, operationalBehaviors, catalog, settings)", - "EnsureWeaponState(", - "CommitWeaponRound(attacker, weapon, catalog)", - "weapon.State.BurstRemaining = weapon.BurstCount", - "weapon.State.BurstTimer += deltaSeconds", - "weapon.SingleAmmoBurst", - "weapon.reload.started", - "weapon.reload.completed", - "AetheriaRuntimeCargoTransactions.TryRemoveQuantity(", - "CommitEnergy(entity, catalog, weapon.Energy)", - "CommitShotResolution(run, zone, attacker, target, weapon, shotId, frameId, catalog, settings)", - "PreparePhysicalPayloads(zone, byIndex, deltaSeconds)", - "worldPhysics.StepPhysicalPayloads(", - "Kind = \"physical-payload.contact\"" - }, - [daemonSimulationSettingsPath] = new[] - { - "public readonly struct AetheriaRuntimeDaemonSimulationSettings", - "public static AetheriaRuntimeDaemonSimulationSettings AetheriaDefault", - "public double ProjectileSpeed { get; }", - "public double AttackRange { get; }" - }, - [worldPhysicsContractPath] = new[] - { - "public interface IAetheriaRuntimeWorldPhysics", - "AetheriaRuntimePhysicalPayloadStep StepPhysicalPayloads(", - "public sealed class AetheriaRuntimePhysicalPayloadStep", - "public sealed class AetheriaRuntimePhysicalPayloadHit" - }, - [ymirWorldPhysicsPath] = new[] - { - "public sealed class AetheriaYmirWorldPhysics : IAetheriaRuntimeWorldPhysics, IDisposable", - "private sealed class SessionState(YmirSession worldSession)", - "public YmirSession WorldSession { get; set; } = worldSession", - "public YmirSession? PayloadSession { get; set; }", - "var stepped = payloadSession.Step(new YmirStepSessionCommand(", - "state.WorldSession.CastCircle(new YmirSessionCircleCastQuery(", - "state.WorldSession.OverlapCircle(new YmirSessionCircleOverlapQuery(", - "PhysicalPayloadPrefix + payload.PayloadId", - "Payload phase requires the retained world phase", - "present.IsBullet != body.IsBullet", - "present.ParticipatesInFields != body.ParticipatesInFields", - "present.CollisionCategoryBits != body.CollisionCategoryBits", - "\"respawn-profile\"" - }, - [daemonProjectPath] = new[] - { - "Ymir.Core.csproj" - }, - [daemonProgramPath] = new[] - { - "using var worldPhysics = new AetheriaYmirWorldPhysics();", - "WorldPhysics = worldPhysics" - }, - [gameDocumentsPath] = new[] - { - "var projectiles = zone.PhysicalPayloads", - ".Concat(projectiles", - "private static AetheriaRuntimeViewportObject ToViewportObject(", - "AetheriaRuntimePhysicalPayloadCommit projectile)" - }, - [gameViewportDocumentsPath] = new[] - { - "public IReadOnlyList PhysicalPayloads" - } - }; - var missingPhysicalPayloadAuthoritySymbols = requiredPhysicalPayloadAuthoritySymbols - .SelectMany(pair => - { - var text = pair.Key == snapshotDocumentsPath ? snapshotDocuments : - pair.Key == daemonSimulationPath ? daemonSimulation : - pair.Key == daemonSimulationSettingsPath ? daemonSimulationSettings : - pair.Key == worldPhysicsContractPath ? worldPhysicsContract : - pair.Key == ymirWorldPhysicsPath ? ymirWorldPhysics : - pair.Key == daemonProjectPath ? daemonProject : - pair.Key == daemonProgramPath ? daemonProgram : - pair.Key == gameDocumentsPath ? gameDocuments : - gameViewportDocuments; - return pair.Value - .Where(symbol => !text.Contains(symbol, StringComparison.Ordinal)) - .Select(symbol => $"{Path.GetRelativePath(root, pair.Key)}: missing {symbol}"); - }) - .ToArray(); - if (missingPhysicalPayloadAuthoritySymbols.Length > 0) - { - throw new InvalidOperationException( - "Daemon combat must resolve ordinary shots as receipts while the retained Ymir zone owner supplies typed payload motion and query facts: " + - string.Join("; ", missingPhysicalPayloadAuthoritySymbols)); - } - - var forbiddenObsoletePhysicalPayloadAuthorities = new[] - { - "IAetheriaRuntimePhysicalPayloadPhysics", - "AetheriaRuntimePhysicalPayloadPhysicsUnavailable", - "AetheriaYmirPhysicalPayloadPhysics", - "PhysicalPayloadPhysics =", - "private readonly YmirSimulator _simulator" - }; - var survivingObsoletePhysicalPayloadAuthorities = forbiddenObsoletePhysicalPayloadAuthorities - .Where(symbol => daemonSimulation.Contains(symbol, StringComparison.Ordinal) || - worldPhysicsContract.Contains(symbol, StringComparison.Ordinal) || - ymirWorldPhysics.Contains(symbol, StringComparison.Ordinal) || - daemonProgram.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (survivingObsoletePhysicalPayloadAuthorities.Length > 0) - { - throw new InvalidOperationException( - "Physical payloads must not retain a snapshot-world port or adapter beside the retained zone physics owner: " + - string.Join(", ", survivingObsoletePhysicalPayloadAuthorities)); - } - var survivingObsoletePhysicalPayloadAuthorityFiles = obsoletePhysicalPayloadAuthorityPaths - .Where(File.Exists) - .Select(path => Path.GetRelativePath(root, path)) - .ToArray(); - if (survivingObsoletePhysicalPayloadAuthorityFiles.Length > 0) - { - throw new InvalidOperationException( - "The snapshot-world physical payload authority must be deleted, not left available for reinjection: " + - string.Join(", ", survivingObsoletePhysicalPayloadAuthorityFiles)); - } - if (daemonTickRunner.Contains("PhysicalPayloadPhysics", StringComparison.Ordinal)) - throw new InvalidOperationException( - "The daemon tick runner must inject one world physics owner, not a second physical-payload authority."); - - var forbiddenPhysicalPayloadDamageSymbols = new[] - { - "Damage(target, hit.Payload", - "SpawnProjectile(zone, attacker", - "TraceBeam(" - }; - var survivingPhysicalPayloadDamageSymbols = forbiddenPhysicalPayloadDamageSymbols - .Where(symbol => daemonSimulation.Contains(symbol, StringComparison.Ordinal) || - worldPhysicsContract.Contains(symbol, StringComparison.Ordinal) || - ymirWorldPhysics.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (survivingPhysicalPayloadDamageSymbols.Length > 0) - { - throw new InvalidOperationException( - "Ymir physical payload contacts may report physics facts but may not decide ordinary weapon damage: " + - string.Join(", ", survivingPhysicalPayloadDamageSymbols)); - } - - var shotReceiptDocument = File.ReadAllText(snapshotDocumentsPath); - if (daemonSimulation.Contains("var shield = GetStat(target, Shield)", StringComparison.Ordinal) || - daemonSimulation.Contains("SetStat(target, Shield, shield -", StringComparison.Ordinal) || - !daemonSimulation.Contains("damage * energyUsage", StringComparison.Ordinal) || - !daemonSimulation.Contains("damage / efficiency", StringComparison.Ordinal) || - !shotReceiptDocument.Contains("ShieldAbsorbedDamage", StringComparison.Ordinal) || - !shotReceiptDocument.Contains("ShieldEnergyConsumed", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Shield authority must be the equipped energy-funded behavior transaction, not a scalar shield hitpoint pool."); - } - - var requiredArmorTransactionSymbols = new[] - { - "EnsureArmorState(entity, catalog)", - "ResolveDamageCells(target, hull, impactX, impactZ, source, penetration, spread, splash)", - "ApplyGridDamage(armor, cell.X, cell.Y, remaining)", - "FindEquipmentAt(target, catalog, cell.X, cell.Y)", - "for (var distance = 0.5; distance < penetration; distance += 0.5)", - "ArmorAppliedDamage", - "EquipmentAppliedDamage", - "DamageCells = damage.Cells" - }; - var missingArmorTransactionSymbols = requiredArmorTransactionSymbols - .Where(symbol => !daemonSimulation.Contains(symbol, StringComparison.Ordinal) && - !shotReceiptDocument.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (missingArmorTransactionSymbols.Length > 0 || - daemonSimulation.Contains("ResolveDamage(target, weapon.Damage, catalog)", StringComparison.Ordinal) || - daemonSimulation.Contains("ResolveDamage(target, payload.PayloadMagnitude, catalog)", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Daemon damage must resolve shield, armor cells, installed equipment, then hull through one typed transaction: " + - string.Join(", ", missingArmorTransactionSymbols)); - } - - var daemonOperations = File.ReadAllText(Path.Combine( - root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeDaemonOperations.cs")); - var requiredDestructionTransactionSymbols = new[] - { - "CommitDestruction(run, zone, target", - "target.DestructionId = destructionId", - "settings.LootDropProbability", - "settings.LootDropVelocity", - "settings.PickupLifetimeSeconds", - "Kind = \"pickup.dropped\"", - "Kind = \"entity.destroyed\"", - "zone.DroppedPickups = pickups" - }; - var missingDestructionTransactionSymbols = requiredDestructionTransactionSymbols - .Where(symbol => !daemonSimulation.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (missingDestructionTransactionSymbols.Length > 0 || - daemonOperations.Contains("ApplyDestroyEntity(", StringComparison.Ordinal) || - daemonOperations.Contains("case AetheriaRuntimeDaemonCommandKinds.DestroyEntity:", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Lethal daemon damage must own exactly-once destruction, loot drops, and cleanup; clients may not command entity erasure: " + - string.Join(", ", missingDestructionTransactionSymbols)); - } - - if (daemonSimulation.Contains("AetheriaRuntimeRtsSimulation", StringComparison.Ordinal) || - daemonSimulation.Contains("daemon-rts", StringComparison.Ordinal) || - daemonSimulation.Contains("rts-commander", StringComparison.Ordinal) || - daemonSimulation.Contains("Damage(target, damage)", StringComparison.Ordinal) || - daemonSimulation.Contains("Damage(target, ResolveProjectileDamage(attacker)", StringComparison.Ordinal) || - daemonSimulation.Contains("private static void StepProjectiles(", StringComparison.Ordinal) || - daemonSimulation.Contains("private static bool ProjectileHits(", StringComparison.Ordinal) || - daemonSimulation.Contains("private const double PawnSpeed", StringComparison.Ordinal) || - daemonSimulation.Contains("private const double AttackRange", StringComparison.Ordinal) || - daemonSimulation.Contains("private const double ProjectileSpeed", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Daemon combat regressed to runtime-named simulation, hardcoded game feel, direct target damage, or local projectile physics instead of daemon settings plus Ymir-shaped projectile contact damage."); - } - - var requiredFlightAuthoritySymbols = new[] - { - "ConfigureThrusterAxes(", - "StepThruster(", - "ResolveThrusterTorque(", - "AetheriaRuntimeEnergySimulation.TryConsume(", - "AetheriaRuntimeThermalSimulation.AddHeatToEquipment(", - "ApplyThrusterVisibility(", - "StepAetherDrive(", - "entity.HelmStrafe", - "entity.LookDirectionX" - }; - var missingFlightAuthoritySymbols = requiredFlightAuthoritySymbols - .Where(symbol => !flightSimulation.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - var forbiddenDirectFlightSymbols = new[] - { - "ResolveSpeed(entity, settings)", - "entity.VelocityX = normalized.X * speed", - "entity.VelocityY = normalized.Y * speed", - "settings.PawnSpeed", - "settings.RaiderSpeed" - }; - var directFlightHits = forbiddenDirectFlightSymbols - .Where(symbol => daemonSimulation.Contains(symbol, StringComparison.Ordinal) || - flightSimulation.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (missingFlightAuthoritySymbols.Length > 0 || directFlightHits.Length > 0) - { - throw new InvalidOperationException( - "Daemon flight must be owned by installed Thruster/AetherDrive actuators and may not restore faction-speed velocity assignment. Missing: " + - string.Join(", ", missingFlightAuthoritySymbols) + "; forbidden: " + - string.Join(", ", directFlightHits)); - } - - var forbiddenRuntimeSpecificAuthorityPolicySymbols = new[] - { - "ElectronLaunchedDaemon", - "electron-launched-daemon", - "UnityHost(", - "unity-host", - "BrowserWasmHost", - "browser-wasm-host", - "UnityPlayer", - "unity-player", - "BrowserObserver", - "browser-observer", - "BrowserSimulationHost", - "browser-simulation-host", - "aetheria-unity-host", - "aetheria.unity-host.v1" - }; - var runtimeSpecificAuthorityPolicyHits = forbiddenRuntimeSpecificAuthorityPolicySymbols - .Where(symbol => verseAuthorityPolicy.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (runtimeSpecificAuthorityPolicyHits.Length > 0) - { - throw new InvalidOperationException( - "Shared Verse authority policy must describe daemon/trusted authority, not runtime-specific host side paths: " + - string.Join(", ", runtimeSpecificAuthorityPolicyHits)); - } - - if (!daemonTickRunner.Contains("AetheriaRuntimeDaemonSimulation.Step(", StringComparison.Ordinal) || - !daemonTickRunner.Contains("public AetheriaRuntimeDaemonSimulationSettings SimulationSettings { get; set; }", StringComparison.Ordinal) || - !daemonTickRunner.Contains("AetheriaRuntimeDaemonSimulationSettings.AetheriaDefault", StringComparison.Ordinal) || - !daemonTickRunner.Contains("simulationSettings: options.SimulationSettings", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Daemon ticks must route Aetheria simulation through daemon-owned settings, not runtime-local constants."); - } - - if (daemonProgram.Contains("RtsCultMesh", StringComparison.Ordinal) || - daemonProgram.Contains("ReadOption(args, \"--rts-cultmesh", StringComparison.Ordinal) || - daemonProgram.Contains("ReadNonNegativeInt(args, \"--rts-cultmesh", StringComparison.Ordinal) || - daemonProgram.Contains("ReadPositiveInt(args, \"--rts-cultmesh", StringComparison.Ordinal) || - daemonProgram.Contains("RTS CultMesh endpoint", StringComparison.Ordinal) || - daemonProgram.Contains("aetheria-rts-rudp", StringComparison.Ordinal) || - daemonProgram.Contains("daemon:aetheria.rts.viewport", StringComparison.Ordinal) || - daemonProgram.Contains("eve-browser-lowering", StringComparison.Ordinal) || - daemonProgram.Contains("raven-unity", StringComparison.Ordinal) || - daemonProgram.Contains("pilot-client", StringComparison.Ordinal) || - daemonProgram.Contains("starbridge-daemon", StringComparison.Ordinal) || - daemonProgram.Contains("local-rts", StringComparison.Ordinal) || - daemonProgram.Contains("starfire-rts", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Aetheria daemon transport and seeded role documents must be client-neutral; runtime-branded CultMesh flags, transport ids, and placeholder client identities belong outside daemon authority."); - } - if (!daemonProgram.Contains("RejectRemovedOption(args, \"--rts-cultmesh-port\", \"--client-cultmesh-port\")", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Aetheria daemon must explicitly reject the removed RTS-branded CultMesh port flag and point callers at the client-neutral flag."); - } - if (authoritySmoke.Contains("--rts-cultmesh-port", StringComparison.Ordinal) || - authoritySmoke.Contains("rtsPort", StringComparison.Ordinal) || - authoritySmoke.Contains("raven-unity", StringComparison.Ordinal) || - authoritySmoke.Contains("starfire-rts", StringComparison.Ordinal) || - freeze.Contains("--rts-cultmesh-port", StringComparison.Ordinal) || - freeze.Contains("local-rts", StringComparison.Ordinal) || - freeze.Contains("starfire-rts", StringComparison.Ordinal) || - freeze.Contains("const string rts", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Aetheria daemon smoke/freeze callers and fixtures must use client-neutral endpoint, run, and runtime vocabulary instead of preserving RTS-branded transport names."); - } - if (rtsLocalDocuments.Contains("local-rts", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Electron local document projections must not invent RTS-branded run ids when the daemon frame omits run identity."); - } - if (rtsGeneratedBindings.Contains("AetheriaRuntimeRtsQuery", StringComparison.Ordinal) || - rtsGeneratedBindings.Contains("AetheriaRuntimeRtsOperation", StringComparison.Ordinal) || - rtsGeneratedBindings.Contains("AetheriaRuntimeRtsDocument", StringComparison.Ordinal) || - rtsGeneratedBindings.Contains("AetheriaRuntimeRtsVerse", StringComparison.Ordinal) || - rtsElectronCultMesh.Contains("createAetheriaRuntimeRts", StringComparison.Ordinal) || - rtsElectronCultMesh.Contains("describeAetheriaRuntimeRts", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Electron may be the RTS client, but generated shared Aetheria game handles must use runtime-neutral Game vocabulary."); - } - if (stateBoundary.Contains("UnityRuntimeIdOverrideEnvironmentVariable", StringComparison.Ordinal) || - stateBoundary.Contains("AETHERIA_UNITY_RUNTIME_ID", StringComparison.Ordinal) || - clientTargetStore.Contains("raven-unity", StringComparison.Ordinal) || - runtimeClientProvider.Contains("raven-unity", StringComparison.Ordinal) || - !runtimeClientProvider.Contains("AetheriaRuntimeStateBoundary.DefaultClientRuntimeId", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Aetheria shared client-target authority must use generic runtime identity only; Unity-specific defaults and environment fallbacks must not survive as ambient provider defaults."); - } - - var sharedRuntimeText = string.Join( - "\n", - daemonSimulation, - gameDocuments, - gameViewportDocuments); - var forbiddenSharedRuntimeAuthoritySymbols = new[] - { - "AetheriaRuntimeRtsDocuments", - "AetheriaRuntimeRtsViewport", - "RtsViewport", - "gamecult.aetheria.rts_viewport", - "local-rts", - "daemon-rts", - "rts-commander", - "aetheria.asset.sprite.rts." - }; - var sharedRuntimeAuthorityHits = forbiddenSharedRuntimeAuthoritySymbols - .Where(symbol => sharedRuntimeText.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (sharedRuntimeAuthorityHits.Length > 0) - { - throw new InvalidOperationException( - "Aetheria shared runtime still exposes RTS-specific authority vocabulary: " + - string.Join(", ", sharedRuntimeAuthorityHits)); - } - - var staleRuntimeSpecificTestAuthoritySources = new Dictionary - { - ["Assets/Scripts/Tests/DaemonRuntimeDocumentTests.cs"] = daemonRuntimeDocumentTests - } - .Where(pair => - pair.Value.Contains("daemon-rts", StringComparison.Ordinal) || - pair.Value.Contains("rts-commander", StringComparison.Ordinal)) - .Select(pair => pair.Key) - .ToArray(); - if (staleRuntimeSpecificTestAuthoritySources.Length > 0) - { - throw new InvalidOperationException( - "Aetheria daemon tests must assert daemon-owned simulation vocabulary, not stale RTS owner kinds: " + - string.Join(", ", staleRuntimeSpecificTestAuthoritySources)); - } - - var staleRuntimeSpecificAssetSources = new Dictionary - { - ["Aetheria.Rts.Web/Electron/aetheria-rts-local-documents.ts"] = rtsLocalDocuments, - ["Assets/Scripts/Tests/DaemonRuntimeDocumentTests.cs"] = daemonRuntimeDocumentTests - } - .Where(pair => pair.Value.Contains("aetheria.asset.sprite.rts.", StringComparison.Ordinal)) - .Select(pair => pair.Key) - .ToArray(); - if (staleRuntimeSpecificAssetSources.Length > 0) - { - throw new InvalidOperationException( - "Aetheria projectile and map assets must use daemon/shared game asset keys, not runtime-branded sprite keys: " + - string.Join(", ", staleRuntimeSpecificAssetSources)); - } - - if (rtsLocalDocuments.Contains("resources://", StringComparison.Ordinal) || - rtsLocalDocuments.Contains("\"resources\"", StringComparison.Ordinal) || - rtsClientApp.Contains("Assets/Resources", StringComparison.Ordinal) || - rtsClientApp.Contains("resources://", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Electron Eve lowering must not synthesize renderer-local Resources asset refs; assets resolve through CultMesh CDN."); - } - if (rtsLocalDocuments.Contains("entityIconAsset", StringComparison.Ordinal) || - rtsLocalDocuments.Contains("bodyIconAsset", StringComparison.Ordinal) || - rtsLocalDocuments.Contains("itemIconAsset", StringComparison.Ordinal) || - rtsLocalDocuments.Contains("cultMeshAssetUri", StringComparison.Ordinal) || - rtsLocalDocuments.Contains("map.entity.", StringComparison.Ordinal) || - rtsLocalDocuments.Contains("map.body.", StringComparison.Ordinal) || - rtsLocalDocuments.Contains(".icon`)", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Electron document decoding must not fabricate fallback Aetheria asset refs; daemon-authored documents and CultMesh CDN own asset identity."); - } - if (rtsElectronCultMesh.Contains("buildViewportDocumentFromFrame(await this.fetchLatestFrameDocument()", StringComparison.Ordinal) || - rtsElectronCultMesh.Contains("buildObjectsViewportDocumentFromFrame(await this.fetchLatestFrameDocument()", StringComparison.Ordinal) || - rtsElectronCultMesh.Contains("buildGravityViewportDocumentFromFrame(await this.fetchLatestFrameDocument()", StringComparison.Ordinal) || - rtsElectronCultMesh.Contains("buildRenderSplatsViewportDocumentFromFrame(await this.fetchLatestFrameDocument()", StringComparison.Ordinal) || - rtsElectronCultMesh.Contains("buildSelectedObjectDocumentFromFrame(await this.fetchLatestFrameDocument()", StringComparison.Ordinal) || - rtsElectronCultMesh.Contains("buildInventoryDocumentFromFrame(await this.fetchLatestFrameDocument()", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Electron queries must fetch daemon-authored managed documents, not rebuild field/view/selection/inventory documents from daemonFrame."); - } - if (rtsLocalDocuments.Contains("buildViewportDocumentFromFrame", StringComparison.Ordinal) || - rtsLocalDocuments.Contains("buildObjectsViewportDocumentFromFrame", StringComparison.Ordinal) || - rtsLocalDocuments.Contains("buildGravityViewportDocumentFromFrame", StringComparison.Ordinal) || - rtsLocalDocuments.Contains("buildRenderSplatsViewportDocumentFromFrame", StringComparison.Ordinal) || - rtsLocalDocuments.Contains("buildSelectedObjectDocumentFromFrame", StringComparison.Ordinal) || - rtsLocalDocuments.Contains("buildInventoryDocumentFromFrame", StringComparison.Ordinal) || - rtsLocalDocuments.Contains("frameContext(frameDocument", StringComparison.Ordinal) || - rtsLocalDocuments.Contains("RenderSplatBuilder", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Electron local document decoding must not keep dead viewport, selection, or inventory projection builders; daemon managed documents own those fields."); - } - var staleRtsClientDocumentationPhrases = new[] - { - "aetheria-rts-local-projection.ts", - "aetheria-local-publication-reader.ts", - "AetheriaLocalPublicationReader", - "AetheriaRemotePublicationReader", - "projection facade methods backed by local CultCache publication sidecars", - "Electron sends CultMesh commands but still reads local `.cc` publications.", - "TS duplicates projection logic by decoding MessagePack arrays and slot constants." - }; - var staleRtsClientDocumentationHits = activeStage7ArchitectureDocs - .SelectMany((text, index) => staleRtsClientDocumentationPhrases - .Where(phrase => text.Contains(phrase, StringComparison.Ordinal)) - .Select(phrase => $"{Path.GetRelativePath(root, new[] - { - verseAuthorityPlanPath, - stage7ThinClientPlanPath, - stage7ClientSurfaceInventoryPath, - daemonCodeMapPath - }[index])}: {phrase}")) - .ToArray(); - if (staleRtsClientDocumentationHits.Length > 0) - { - throw new InvalidOperationException( - "Active architecture docs must not preserve deleted Electron local projection/publication-reader wrappers as the client contract; Hermodr and Electron consume daemon-authored documents through the Eve/CultMesh contract: " + - string.Join("; ", staleRtsClientDocumentationHits)); - } - if (!rtsElectronCultMesh.Contains("fetchViewportDocument(AetheriaRtsSchemas.gameViewport, \"aetheria.viewport.map\"", StringComparison.Ordinal) || - !rtsElectronCultMesh.Contains("fetchViewportDocument(AetheriaRtsSchemas.objectsViewport, \"aetheria.viewport.objects\"", StringComparison.Ordinal) || - !rtsElectronCultMesh.Contains("fetchViewportDocument(AetheriaRtsSchemas.gravityViewport, \"aetheria.viewport.gravity\"", StringComparison.Ordinal) || - !rtsElectronCultMesh.Contains("fetchViewportDocument(AetheriaRtsSchemas.renderSplatsViewport, \"aetheria.viewport.render_splats\"", StringComparison.Ordinal) || - !rtsElectronCultMesh.Contains("managedViewportRecordKey(", StringComparison.Ordinal) || - !rtsElectronCultMesh.Contains("readRenderSplatsViewportDocument(await this.fetchViewportDocument", StringComparison.Ordinal) || - !rtsElectronCultMesh.Contains("fetchIndexedDocument(AetheriaRtsSchemas.selectedObject, \"aetheria.object.selected\"", StringComparison.Ordinal) || - !rtsElectronCultMesh.Contains("fetchIndexedDocument(AetheriaRtsSchemas.inventory, \"aetheria.inventory\"", StringComparison.Ordinal) || - !rtsElectronCultMesh.Contains("readSelectedObjectDocument(await this.fetchIndexedDocument", StringComparison.Ordinal) || - !rtsElectronCultMesh.Contains("readInventoryDocument(await this.fetchIndexedDocument", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Electron viewport, selected-object, and inventory queries no longer use the daemon managed document path."); - } - if (!daemonProgram.Contains("recordKey.StartsWith(\"aetheria.viewport.map.\"", StringComparison.Ordinal) || - !daemonProgram.Contains("schemaId = AetheriaRuntimeDaemonSchemas.GameViewport", StringComparison.Ordinal) || - !daemonProgram.Contains("candidate.StartsWith(\"aetheria.object.selected.\"", StringComparison.Ordinal) || - !daemonProgram.Contains("candidate.StartsWith(\"aetheria.inventory.\"", StringComparison.Ordinal) || - !daemonProgram.Contains("AetheriaRuntimeGameDocuments.SelectedObject(frame, indexedEntityIndex)", StringComparison.Ordinal) || - !daemonProgram.Contains("AetheriaRuntimeGameDocuments.Inventory(frame, indexedEntityIndex)", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Daemon snapshot handling must expose managed viewport and indexed selected-object/inventory document keys."); - } - - if (daemonTickRunner.Contains("AetheriaRuntimeDaemonFrameStore.PublishFrame", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Shared simulation ticks still write legacy frame witness files instead of returning managed typed documents."); - } -} - -static void RequireUnityPhysicsIsNotGameplayAuthority(string root) -{ - var checkedRoots = new[] - { - Path.Combine(root, "Assets", "Scripts", "Gameplay"), - Path.Combine(root, "Assets", "Scripts", "ServerShared") - }; - - var forbiddenSymbols = new[] - { - "OnTriggerEnter", - "OnTriggerStay", - "OnTriggerExit", - "OnCollisionEnter", - "OnCollisionStay", - "OnCollisionExit", - "OnControllerColliderHit", - "Physics.Raycast", - "Physics.SphereCast", - "Physics.BoxCast", - "Physics.CapsuleCast", - "Physics.OverlapSphere", - "Physics.OverlapBox", - "Physics.OverlapCapsule", - "Physics.CheckSphere", - "Physics.CheckBox", - "Physics.CheckCapsule", - "Rigidbody", - "Rigidbody2D", - "Collider2D" - }; - - var hits = checkedRoots - .Where(Directory.Exists) - .SelectMany(path => Directory.EnumerateFiles(path, "*.cs", SearchOption.AllDirectories)) - .Where(path => !Path.GetFileName(path).EndsWith("Tests.cs", StringComparison.Ordinal)) - .SelectMany(path => File.ReadLines(path) - .Select((line, index) => new { Path = path, LineNumber = index + 1, Line = line })) - .Where(line => forbiddenSymbols.Any(symbol => line.Line.Contains(symbol, StringComparison.Ordinal))) - .Select(line => $"{Path.GetRelativePath(root, line.Path)}:{line.LineNumber}: {line.Line.Trim()}") - .ToArray(); - - if (hits.Length > 0) - { - throw new InvalidOperationException( - "Unity physics/collision callbacks are forbidden as gameplay authority; route queries through Ymir: " + - string.Join("; ", hits)); - } - - var ymirBridgePath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "Physics", "AetheriaYmirPhysicsBridge.cs"); - var projectilePath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "Weapons", "Projectile.cs"); - var clickRaycasterPath = Path.Combine(root, "Assets", "Scripts", "UI", "ClickRaycaster.cs"); - - var requiredSymbols = new Dictionary - { - [ymirBridgePath] = new[] - { - "public bool TryCastClickables(", - "YmirPhysicsQueries.CastSphere(new YmirSphereCastRequest", - "private readonly Dictionary _clickableBodyMap" - }, - [clickRaycasterPath] = new[] - { - "AetheriaYmirPhysicsBridge.Instance.TryCastClickables" - } - }; - - var missingSymbols = requiredSymbols - .Where(pair => !File.Exists(pair.Key) || pair.Value.Any(symbol => !File.ReadAllText(pair.Key).Contains(symbol, StringComparison.Ordinal))) - .SelectMany(pair => - { - var text = File.Exists(pair.Key) ? File.ReadAllText(pair.Key) : ""; - return pair.Value - .Where(symbol => !text.Contains(symbol, StringComparison.Ordinal)) - .Select(symbol => $"{Path.GetRelativePath(root, pair.Key)}: missing {symbol}"); - }) - .ToArray(); - - if (missingSymbols.Length > 0) - { - throw new InvalidOperationException( - "Ymir presentation picking bridge is incomplete: " + - string.Join("; ", missingSymbols)); - } - - var ymirBridge = File.ReadAllText(ymirBridgePath); - var forbiddenYmirTransportSymbols = new[] - { - "public string ServiceUrl", - "public string OverlapSphereUrl", - "public string OverlapCircleUrl", - "public string CastCircleUrl", - "public string CastSphereUrl", - "TimeoutMilliseconds", - "PostJson<", - "HttpWebRequest", - "WebRequest.Create", - "http://127.0.0.1:8877" - }; - var ymirTransportHits = forbiddenYmirTransportSymbols - .Where(symbol => ymirBridge.Contains(symbol, StringComparison.Ordinal)) - .Select(symbol => $"{Path.GetRelativePath(root, ymirBridgePath)}: {symbol}") - .ToArray(); - - if (ymirTransportHits.Length > 0) - { - throw new InvalidOperationException( - "Ymir presentation queries must use the typed query surface, not a local HTTP transport shortcut: " + - string.Join("; ", ymirTransportHits)); - } - - var forbiddenUnityProjectileAuthorityPaths = new[] - { - projectilePath, - Path.Combine(root, "Assets", "Scripts", "Gameplay", "Weapons", "ProjectileManager.cs"), - Path.Combine(root, "Assets", "Scripts", "Gameplay", "Weapons", "GuidedProjectile.cs"), - Path.Combine(root, "Assets", "Scripts", "Gameplay", "Weapons", "GuidedProjectileManager.cs"), - Path.Combine(root, "Assets", "Scripts", "Gameplay", "Physics", "YmirUnityRuntimeProofTests.cs"), - Path.Combine(root, "Assets", "Scripts", "Gameplay", "Weapons", "Laser.cs"), - Path.Combine(root, "Assets", "Scripts", "Gameplay", "Weapons", "LaserManager.cs"), - Path.Combine(root, "Assets", "Scripts", "Gameplay", "Weapons", "Lightning.cs"), - Path.Combine(root, "Assets", "Scripts", "Gameplay", "Weapons", "LightningGunManager.cs"), - Path.Combine(root, "Assets", "Scripts", "Gameplay", "Weapons", "HitscanEffect.cs"), - Path.Combine(root, "Assets", "Scripts", "Gameplay", "Weapons", "HitscanManager.cs"), - Path.Combine(root, "Assets", "Scripts", "Gameplay", "Weapons", "ConstantLaser.cs"), - Path.Combine(root, "Assets", "Scripts", "Gameplay", "Weapons", "ConstantLaserManager.cs"), - Path.Combine(root, "Assets", "Scripts", "Gameplay", "Weapons", "ConstantLightning.cs"), - Path.Combine(root, "Assets", "Scripts", "Gameplay", "Weapons", "ConstantLightningManager.cs"), - Path.Combine(root, "Assets", "Scripts", "Gameplay", "Weapons", "ConstantParticleWeapon.cs"), - Path.Combine(root, "Assets", "Scripts", "Gameplay", "Weapons", "ConstantParticleWeaponManager.cs"), - Path.Combine(root, "Assets", "Scripts", "Gameplay", "Weapons", "ConstantWeaponEffectManager.cs"), - Path.Combine(root, "Assets", "Scripts", "Gameplay", "Weapons", "InstantWeaponEffectManager.cs"), - Path.Combine(root, "Assets", "Scripts", "Gameplay", "Weapons", "Mine.cs"), - Path.Combine(root, "Assets", "Scripts", "Gameplay", "Weapons", "MineManager.cs") - }; - var survivingUnityProjectileAuthority = forbiddenUnityProjectileAuthorityPaths - .Where(File.Exists) - .Select(path => Path.GetRelativePath(root, path)) - .ToArray(); - if (survivingUnityProjectileAuthority.Length > 0 || - File.ReadAllText(ymirBridgePath).Contains("TryStepProjectile", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Ordinary weapon travel is daemon receipt presentation, not Unity/Ymir projectile simulation: " + - string.Join(", ", survivingUnityProjectileAuthority)); - } - - var weaponEffectRoot = Path.Combine(root, "Assets", "Scripts", "Gameplay", "Weapons"); - var forbiddenWeaponCombatSymbols = new[] - { - "public float Damage", - "public float Penetration", - "public float Spread", - "public DamageType", - ".Damage =", - ".Penetration =", - ".Spread =", - ".DamageType =", - ".DamageSpread" - }; - var weaponCombatAuthorityHits = Directory.EnumerateFiles(weaponEffectRoot, "*.cs", SearchOption.TopDirectoryOnly) - .SelectMany(path => File.ReadLines(path) - .Select((line, index) => new { Path = path, LineNumber = index + 1, Line = line })) - .Where(line => forbiddenWeaponCombatSymbols.Any(symbol => line.Line.Contains(symbol, StringComparison.Ordinal))) - .Select(line => $"{Path.GetRelativePath(root, line.Path)}:{line.LineNumber}: {line.Line.Trim()}") - .ToArray(); - - if (weaponCombatAuthorityHits.Length > 0) - { - throw new InvalidOperationException( - "Unity weapon effects are presentation only; combat damage, penetration, spread, and damage type belong to daemon state: " + - string.Join("; ", weaponCombatAuthorityHits)); - } - - var weaponSingletonHits = Directory.EnumerateFiles(weaponEffectRoot, "*.cs", SearchOption.TopDirectoryOnly) - .SelectMany(path => File.ReadLines(path) - .Select((line, index) => new { Path = path, LineNumber = index + 1, Line = line })) - .Where(line => line.Line.Contains("ActionGameManager.Instance", StringComparison.Ordinal)) - .Select(line => $"{Path.GetRelativePath(root, line.Path)}:{line.LineNumber}: {line.Line.Trim()}") - .ToArray(); - - if (weaponSingletonHits.Length > 0) - { - throw new InvalidOperationException( - "Unity weapon effects must receive renderer/input context explicitly instead of reaching through ActionGameManager.Instance: " + - string.Join("; ", weaponSingletonHits)); - } -} - -static async Task RequireTradeValuePolicyEveCommandPersistsAsync() -{ - var tempStatePath = Path.Combine( - Path.GetTempPath(), - $"aetheria-trade-policy-command-{Guid.NewGuid():N}.cc"); - - try - { - await using var commandNode = await AetheriaStateNode.OpenAsync( - tempStatePath, - "aetheria-state-verify-trade-policy", - enableDurableShardLogs: false); - - var policy = AetheriaRuntimeStateMapper.ToTradeValuePolicy( - AetheriaRuntimeTradeValueSettings.Default, - DateTimeOffset.UtcNow.ToString("O")); - await commandNode.MutableDocument(AetheriaStateNode.TradeValuePolicyKey) - .ReplaceAsync(policy) - .ConfigureAwait(false); - var originalMinimum = policy.QualityPriceModifier?.Minimum ?? 0; - var editedMinimum = originalMinimum + 0.125; - - var command = AetheriaRuntimeEveCommandClient.ToDocument( - AetheriaRuntimeEveCommandClient.CreateTradeValuePolicyCommand( - AetheriaRuntimeEveCommandKind.SetTradeValueQualityMinimum, - new AetheriaRuntimeTradeValuePolicyCommandBody - { - Value = editedMinimum - }, - "aetheria-state-verify")); - await commandNode.SubmitEveCommandAsync(command).ConfigureAwait(false); - - var report = await AetheriaEveCommandBridge.AcceptObservedAsync(commandNode).ConfigureAwait(false); - if (report.AcceptedTradeValuePolicyCommands != 1) - { - throw new InvalidOperationException( - "Trade value policy Eve command was not accepted by the typed command bridge."); - } - - var editedPolicy = await commandNode.MutableDocument(AetheriaStateNode.TradeValuePolicyKey).ReadAsync().ConfigureAwait(false) - ?? throw new InvalidOperationException("Trade value policy disappeared after typed Eve command."); - var actualMinimum = editedPolicy.QualityPriceModifier?.Minimum ?? 0; - if (Math.Abs(actualMinimum - editedMinimum) > 0.000001) - { - throw new InvalidOperationException( - $"Trade value policy Eve command did not persist the typed edit. Expected {editedMinimum}, got {actualMinimum}."); - } - } - finally - { - try - { - File.Delete(tempStatePath); - } - catch - { - // Best-effort cleanup for verifier scratch state. - } - } -} - -static async Task RequireMainMenuEveCommandPersistsAsync() -{ - var tempStatePath = Path.Combine( - Path.GetTempPath(), - $"aetheria-main-menu-command-{Guid.NewGuid():N}.cc"); - - try - { - await using var commandNode = await AetheriaStateNode.OpenAsync( - tempStatePath, - "aetheria-state-verify-main-menu", - enableDurableShardLogs: false); - - if (!AetheriaRuntimeEveCommandClient.TryCreateKnownSurfaceCommand( - new EveSurfaceCommandRequest( - AetheriaRuntimeMainMenuSurfaceBuilder.ProviderId, - AetheriaRuntimeMainMenuCommands.RootSurfaceId, - CultMesh.OperationInvocation( - AetheriaRuntimeMainMenuCommands.ShowSettings, - AetheriaRuntimeEveCommandDocument.SchemaId, - new CultMeshRouteHint(CultMeshLocalityKind.Network, "aetheria-eve-command")), - CultMesh.OperationPayload(), - DateTimeOffset.UtcNow, - "aetheria-state-verify"), - out var envelope) || - envelope == null) - { - throw new InvalidOperationException("Main menu Eve surface command did not create a typed command envelope."); - } - - var command = AetheriaRuntimeEveCommandClient.ToDocument(envelope); - await commandNode.SubmitEveCommandAsync(command).ConfigureAwait(false); - - var report = await AetheriaEveCommandBridge.AcceptObservedAsync(commandNode).ConfigureAwait(false); - if (report.AcceptedMainMenuCommands != 1) - { - throw new InvalidOperationException( - "Main menu Eve command was not accepted by the typed command bridge."); - } - - var menuState = await commandNode.MutableDocument(AetheriaStateNode.MainMenuStateKey).ReadAsync().ConfigureAwait(false) - ?? throw new InvalidOperationException("Main menu state was not created by the typed Eve command."); - if (!string.Equals(menuState.ActiveSurfaceId, AetheriaRuntimeMainMenuCommands.SettingsSurfaceId, StringComparison.Ordinal)) - { - throw new InvalidOperationException( - $"Main menu Eve command did not persist daemon-owned panel state. Expected {AetheriaRuntimeMainMenuCommands.SettingsSurfaceId}, got {menuState.ActiveSurfaceId}."); - } - } - finally - { - try - { - File.Delete(tempStatePath); - } - catch - { - // Best-effort cleanup for verifier scratch state. - } - } -} - -static void RequireDeadPropertiesPanelShellDeleted(string root) -{ - var deletedShellPaths = new[] - { - Path.Combine(root, "Assets", "Scripts", "UI", "DropdownMenu.cs"), - Path.Combine(root, "Assets", "Scripts", "UI", "DropdownMenu.cs.meta"), - Path.Combine(root, "Assets", "Scripts", "UI", "Properties Panel", "PropertiesPanel.cs"), - Path.Combine(root, "Assets", "Scripts", "UI", "Properties Panel", "PropertiesPanel.cs.meta"), - Path.Combine(root, "Assets", "Scripts", "UI", "Properties Panel", "PropertiesList.cs"), - Path.Combine(root, "Assets", "Scripts", "UI", "Properties Panel", "PropertiesList.cs.meta"), - Path.Combine(root, "Assets", "Prefabs", "UI", "Dropdown Menu.prefab"), - Path.Combine(root, "Assets", "Prefabs", "UI", "Dropdown Menu.prefab.meta"), - Path.Combine(root, "Assets", "Prefabs", "UI", "Properties Panel", "Properties.prefab"), - Path.Combine(root, "Assets", "Prefabs", "UI", "Properties Panel", "Properties.prefab.meta"), - Path.Combine(root, "Assets", "Prefabs", "UI", "Properties Panel", "Property List.prefab"), - Path.Combine(root, "Assets", "Prefabs", "UI", "Properties Panel", "Property List.prefab.meta"), - Path.Combine(root, "Assets", "Prefabs", "UI", "Properties Panel", "Property List 1.prefab"), - Path.Combine(root, "Assets", "Prefabs", "UI", "Properties Panel", "Property List 1.prefab.meta") - }; - - var survivingShells = deletedShellPaths - .Where(File.Exists) - .Select(path => Path.GetRelativePath(root, path)) - .ToArray(); - - if (survivingShells.Length > 0) - { - throw new InvalidOperationException( - "The dead generic popup inspector shell still survives in source or prefab assets: " + - string.Join(", ", survivingShells)); - } - - var guidChecks = new Dictionary - { - [Path.Combine(root, "Assets", "Scenes", "ARPG.unity")] = new[] - { - "8c2bf4a7080061d42a87046c37bf0c60", - "2544c9c8b6358c54ea58c5fb33bb48b7", - "848ae49aff071f5458dcc0322b8b84eb" - }, - [Path.Combine(root, "Assets", "Scenes", "FieldShieldTest.unity")] = new[] - { - "8c2bf4a7080061d42a87046c37bf0c60", - "2544c9c8b6358c54ea58c5fb33bb48b7", - "848ae49aff071f5458dcc0322b8b84eb" - }, - [Path.Combine(root, "Assets", "Prefabs", "UI", "Main Menu Canvas.prefab")] = new[] - { - "8c2bf4a7080061d42a87046c37bf0c60", - "2544c9c8b6358c54ea58c5fb33bb48b7", - "848ae49aff071f5458dcc0322b8b84eb" - } - }; - - var survivingGuidLinks = guidChecks - .SelectMany(entry => - { - var text = File.Exists(entry.Key) ? File.ReadAllText(entry.Key) : ""; - return entry.Value - .Where(guid => text.Contains(guid, StringComparison.Ordinal)) - .Select(guid => $"{Path.GetRelativePath(root, entry.Key)}: {guid}"); - }) - .ToArray(); - - if (survivingGuidLinks.Length > 0) - { - throw new InvalidOperationException( - "Scene or prefab YAML still serializes the deleted generic popup inspector shell: " + - string.Join("; ", survivingGuidLinks)); - } -} - -static void RequireTypedBehaviorMetadataCoverage(string root) -{ - var metadataPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeBehaviorMetadata.cs"); - var inventoryMenuPath = Path.Combine(root, "Assets", "Scripts", "UI", "Menu", "InventoryMenu.cs"); - var tradeMenuPath = Path.Combine(root, "Assets", "Scripts", "UI", "Menu", "TradeMenu.cs"); - - var metadata = File.Exists(metadataPath) - ? File.ReadAllText(metadataPath) - : throw new InvalidOperationException("Cannot verify typed behavior metadata coverage; AetheriaRuntimeBehaviorMetadata.cs is missing."); - var inventoryMenu = File.Exists(inventoryMenuPath) - ? File.ReadAllText(inventoryMenuPath) - : throw new InvalidOperationException("Cannot verify typed behavior metadata coverage; InventoryMenu.cs is missing."); - var tradeMenu = File.Exists(tradeMenuPath) - ? File.ReadAllText(tradeMenuPath) - : throw new InvalidOperationException("Cannot verify typed behavior metadata coverage; TradeMenu.cs is missing."); - var cargoBuilderPath = Path.Combine( - root, - "Packages", - "org.gamecult.aetheria.state", - "Runtime", - "AetheriaRuntimeCargoItemDetailsSurfaceBuilder.cs"); - var equippedBuilderPath = Path.Combine( - root, - "Packages", - "org.gamecult.aetheria.state", - "Runtime", - "AetheriaRuntimeEquippedItemDetailsSurfaceBuilder.cs"); - var tradeItemBuilderPath = Path.Combine( - root, - "Packages", - "org.gamecult.aetheria.state", - "Runtime", - "AetheriaRuntimeTradeItemDetailsSurfaceBuilder.cs"); - var cargoBuilder = File.Exists(cargoBuilderPath) - ? File.ReadAllText(cargoBuilderPath) - : throw new InvalidOperationException("Cannot verify typed behavior metadata coverage; cargo item details builder is missing."); - var equippedBuilder = File.Exists(equippedBuilderPath) - ? File.ReadAllText(equippedBuilderPath) - : throw new InvalidOperationException("Cannot verify typed behavior metadata coverage; equipped item details builder is missing."); - var tradeItemBuilder = File.Exists(tradeItemBuilderPath) - ? File.ReadAllText(tradeItemBuilderPath) - : throw new InvalidOperationException("Cannot verify typed behavior metadata coverage; trade item details builder is missing."); - - var requiredMetadataSymbols = new[] - { - "AetheriaRuntimeBehaviorFieldValueKind.Temperature", - "Behavior(\"Heat\", \"\", Stat(\"Heat\", 1))", - "Behavior(\"MiningTool\", \"\", Stat(\"DamagePerSecond\", 1), Stat(\"Efficiency\", 2), Stat(\"Penetration\", 3), Stat(\"Range\", 4))", - "Behavior(\"Switch\", \"\")", - "Behavior(\"Thermotoggle\", \"\", Temperature(\"TargetTemperature\", 1))", - "Behavior(\"Trigger\", \"\")", - "Temperature(\"TemperatureFloor\", 3)" - }; - - var missingMetadataSymbols = requiredMetadataSymbols - .Where(symbol => !metadata.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - - if (missingMetadataSymbols.Length > 0) - { - throw new InvalidOperationException( - "Typed behavior metadata no longer covers live heat/mining/thermotoggle payloads and switch/trigger shells: " + - string.Join(", ", missingMetadataSymbols)); - } - - var requiredUiSymbols = new[] - { - "AetheriaRuntimeBehaviorFieldValueKind.Temperature", - "FormatTemperature" - }; - - var inventoryBehaviorProjection = cargoBuilder + "\n" + equippedBuilder; - var missingInventorySymbols = requiredUiSymbols - .Where(symbol => !inventoryBehaviorProjection.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (missingInventorySymbols.Length > 0) - { - throw new InvalidOperationException( - "Inventory item surface builders no longer render typed temperature-bearing behavior metadata: " + - string.Join(", ", missingInventorySymbols)); - } - - var missingTradeSymbols = requiredUiSymbols - .Where(symbol => !tradeItemBuilder.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (missingTradeSymbols.Length > 0) - { - throw new InvalidOperationException( - "Trade item surface builder no longer renders typed temperature-bearing behavior metadata: " + - string.Join(", ", missingTradeSymbols)); - } - - if (inventoryMenu.Contains("ProjectCargoItemBehaviorMetric(", StringComparison.Ordinal) || - inventoryMenu.Contains("ProjectEquippedItemBehaviorMetric(", StringComparison.Ordinal) || - tradeMenu.Contains("ProjectTradeItemBehaviorMetric(", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Unity menus must not own typed behavior metric projection after runtime surface builders take that role."); - } -} - -static void RequireNameToolsUsesUiToolkit(string root) -{ - var nameToolsPath = Path.Combine(root, "Assets", "Scripts", "Editor", "NameTools.cs"); - var source = File.Exists(nameToolsPath) - ? File.ReadAllText(nameToolsPath) - : throw new InvalidOperationException("Cannot verify NameTools editor shell; NameTools.cs is missing."); - - var requiredSymbols = new[] - { - "using UnityEditor.UIElements;", - "using UnityEngine.UIElements;", - "private void CreateGUI()", - "new ObjectField(\"Name File\")", - "new IntegerField(", - "new Toggle(\"Strip Number Tokens\")", - "new Button(CleanNameFile)", - "new Button(ProcessNameFile)", - "new Button(GenerateName)" - }; - - var missingSymbols = requiredSymbols - .Where(symbol => !source.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - - if (missingSymbols.Length > 0) - { - throw new InvalidOperationException( - "NameTools no longer owns a UI Toolkit editor shell for the remaining name helper workflow: " + - string.Join(", ", missingSymbols)); - } - - var forbiddenSymbols = new[] - { - "void OnGUI()", - "EditorGUILayout.", - "GUILayout." - }; - - var hits = forbiddenSymbols - .Where(symbol => source.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - - if (hits.Length > 0) - { - throw new InvalidOperationException( - "NameTools should not regress to IMGUI editor widgets: " + - string.Join(", ", hits)); - } -} - -static void RequireRuntimeStateReaderOwnsUnityStateAcquisition(string root) -{ - var runtimeStateRefResolverPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeStateRefResolver.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"); - var mainMenuPath = Path.Combine(root, "Assets", "Scripts", "UI", "MainMenu.cs"); - var eveSurfacePresenterPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.eve-runtime", "Runtime", "AetheriaEveSurfacePresenter.cs"); - var eveUnitySurfaceHostPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.eve-runtime", "Runtime", "AetheriaEveUnitySurfaceHost.cs"); - - var requiredPaths = new[] - { - runtimeStateRefResolverPath, - runtimeSurfaceDocumentsPath, - unityPackageProjectPath, - actionGameManagerPath, - gameplayBootShellPath, - mainMenuPath, - eveSurfacePresenterPath, - eveUnitySurfaceHostPath - }; - - var missingPaths = requiredPaths - .Where(path => !File.Exists(path)) - .Select(path => Path.GetRelativePath(root, path)) - .ToArray(); - - if (missingPaths.Length > 0) - { - throw new InvalidOperationException( - "Verse client state authority cannot be verified because required files are missing: " + - string.Join(", ", missingPaths)); - } - - var runtimeStateRefResolver = File.ReadAllText(runtimeStateRefResolverPath); - var runtimeSurfaceDocuments = File.ReadAllText(runtimeSurfaceDocumentsPath); - var unityPackageProject = File.ReadAllText(unityPackageProjectPath); - var actionGameManager = File.ReadAllText(actionGameManagerPath); - var gameplayBootShell = File.ReadAllText(gameplayBootShellPath); - var mainMenu = File.ReadAllText(mainMenuPath); - var eveSurfacePresenter = File.ReadAllText(eveSurfacePresenterPath); - var eveUnitySurfaceHost = File.ReadAllText(eveUnitySurfaceHostPath); - - var requiredResolverSymbols = new[] - { - "public static class AetheriaRuntimeStateRefResolver", - "TryResolveDaemonStateRef", - "TryResolveDaemonItemStatRef", - "AetheriaRuntimeDaemonItemStatQueries.StateRefPrefix", - "AetheriaRuntimeDaemonStateRefs.CurrentEntityName", - "public static CultMeshStateRefResolver CreateEveSurfaceCultMeshStateRefResolver(", - "Func frameProvider", - "Func healthProvider", - "Func commandBoundaryProvider", - "frameProvider?.Invoke()", - "catalogProvider?.Invoke()", - "CultMesh.StateRefResolver(", - "FindDaemonItem(" - }; - - var missingResolverSymbols = requiredResolverSymbols - .Where(symbol => !runtimeStateRefResolver.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - - if (missingResolverSymbols.Length > 0) - { - throw new InvalidOperationException( - "Shared runtime state-ref resolver is incomplete: " + - string.Join(", ", missingResolverSymbols)); - } - - if (runtimeStateRefResolver.Contains("public static class AetheriaRuntimeStateReader", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Shared runtime state reader compatibility wrapper still exists; use AetheriaRuntimeStateRefResolver directly."); - } - - if (runtimeStateRefResolver.Contains("ReadEntitySnapshots", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Shared runtime state-ref resolver still exposes file-backed entity snapshot reads; use daemon zone-render EntitySnapshots documents."); - } - - if (runtimeStateRefResolver.Contains("TryReadDaemonRenderView", StringComparison.Ordinal) || - runtimeStateRefResolver.Contains("TryReadDaemonSoaView(", StringComparison.Ordinal) || - runtimeStateRefResolver.Contains("TryReadDaemonFrame(", StringComparison.Ordinal) || - runtimeStateRefResolver.Contains("ResolveEveSurfaceStateRef(", StringComparison.Ordinal) || - runtimeStateRefResolver.Contains("TryResolveEveSurfaceStateRef(", StringComparison.Ordinal) || - runtimeStateRefResolver.Contains("CreateEveSurfaceCultMeshStateRefResolver(\r\n string stateFilePath", StringComparison.Ordinal) || - runtimeStateRefResolver.Contains("CreateEveSurfaceCultMeshStateRefResolver(\n string stateFilePath", StringComparison.Ordinal) || - runtimeStateRefResolver.Contains("ReadEveSurface(", StringComparison.Ordinal) || - runtimeStateRefResolver.Contains("TryReadDaemonGameSurface(", StringComparison.Ordinal) || - runtimeStateRefResolver.Contains("TryReadDaemonGameTuiSurface(", StringComparison.Ordinal) || - runtimeStateRefResolver.Contains("TryReadDaemonEditorSurface(", StringComparison.Ordinal) || - runtimeStateRefResolver.Contains("TryReadDaemonEditorTuiSurface(", StringComparison.Ordinal) || - runtimeStateRefResolver.Contains("OpenRuntimeCatalog(", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Shared runtime state-ref resolver still exposes daemon file reads; use AetheriaClient and managed typed documents."); - } - - if (runtimeStateRefResolver.Contains("ReadRunStates", StringComparison.Ordinal) || - runtimeStateRefResolver.Contains("ReadZoneStates", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Shared runtime state-ref resolver still exposes file-backed run/zone snapshot reads; use managed daemon frame and zone-render documents."); - } - - if (runtimeStateRefResolver.Contains("ReadPlayerSettings", StringComparison.Ordinal) || - runtimeStateRefResolver.Contains("ReadVerseHostSettings", StringComparison.Ordinal) || - runtimeStateRefResolver.Contains("ReadLoadoutTemplates", StringComparison.Ordinal) || - runtimeStateRefResolver.Contains("ReadTradeValuePolicy", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Shared runtime state-ref resolver still exposes file-backed catalog/settings reads; use AetheriaClient managed document handles."); - } - - var runtimeCatalogStorePath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeCatalogStore.cs"); - var runtimeCatalogStore = File.Exists(runtimeCatalogStorePath) - ? File.ReadAllText(runtimeCatalogStorePath) - : throw new InvalidOperationException("Cannot verify deleted file-backed runtime catalog readers; AetheriaRuntimeCatalogStore.cs is missing."); - if (runtimeCatalogStore.Contains("ReadEntitySnapshots", StringComparison.Ordinal) || - runtimeCatalogStore.Contains("ReadEntitySnapshotPayload", StringComparison.Ordinal) || - runtimeCatalogStore.Contains("EntitySnapshotSchema", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Runtime catalog store still exposes file-backed entity snapshot projection; daemon zone-render EntitySnapshots owns runtime entity lowering."); - } - - if (runtimeCatalogStore.Contains("ReadRunStates", StringComparison.Ordinal) || - runtimeCatalogStore.Contains("ReadZoneStates", StringComparison.Ordinal) || - runtimeCatalogStore.Contains("ReadRunStatePayload", StringComparison.Ordinal) || - runtimeCatalogStore.Contains("ReadZoneStatePayload", StringComparison.Ordinal) || - runtimeCatalogStore.Contains("RunStateSchema", StringComparison.Ordinal) || - runtimeCatalogStore.Contains("ZoneStateSchema", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Runtime catalog store still exposes file-backed run/zone snapshot projection; managed daemon checkpoint and zone-render documents own runtime state lowering."); - } - - if (runtimeCatalogStore.Contains("ReadEveSurfaces", StringComparison.Ordinal) || - runtimeCatalogStore.Contains("ReadEveSurface(", StringComparison.Ordinal) || - runtimeCatalogStore.Contains("ProjectStatRecipeSurfaceDocument", StringComparison.Ordinal) || - runtimeCatalogStore.Contains("ProjectTradeValuePolicySurfaceDocument", StringComparison.Ordinal) || - runtimeCatalogStore.Contains("EveSurfaceSchema", StringComparison.Ordinal) || - runtimeCatalogStore.Contains("ReadFieldEveSurface", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Runtime catalog store still exposes Eve-surface parsing/projection helpers; surface access must flow through managed CultMesh EveSurfaceDocument handles."); - } - - if (!unityPackageProject.Contains("AetheriaRuntimeStateRefResolver.cs", StringComparison.Ordinal) || - unityPackageProject.Contains("AetheriaRuntimeStateReader.cs", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "GameCult.Aetheria.State.Unity.csproj must include AetheriaRuntimeStateRefResolver.cs and not the old state reader filename."); - } - - if (!unityPackageProject.Contains("AetheriaRuntimeDaemonStateRefs.cs", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "GameCult.Aetheria.State.Unity.csproj does not include the shared daemon state-ref vocabulary."); - } - - var requiredSurfaceDocumentSymbols = new[] - { - "public static class AetheriaRuntimeSurfaceDocuments", - "public static EveUiSurfaceDocument ToPortableSurface(AetheriaRuntimeSurfaceDocument document)", - "public static EveUiSurfaceDocument ToEveSurfaceDocument(AetheriaRuntimeSurfaceDocument document)", - "public static EveUiSurfaceDocument ToEveSurfaceDocument(EveUiSurfaceDocument document)", - "public static EveUiSurfaceDocument ResolveStateRefs(", - "CultMeshStateRefResolver? stateRefResolver", - "ResolvePropRefs(props, resolveStateRef)", - "ResolvePropRef(props, AetheriaRuntimeSurfaceStateRefs.Source, \"value\", resolveStateRef)", - "IsStatePointerProp(prop.Key)", - "ResolvePointerValueKey(refProp.Key)", - "public static EveUiSurfaceDocument EmptySurface(string surfaceId)", - "new EveUiSurfaceDocument(", - "new EveUiSurfaceComponent(" - }; - var missingSurfaceDocumentSymbols = requiredSurfaceDocumentSymbols - .Where(symbol => !runtimeSurfaceDocuments.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (missingSurfaceDocumentSymbols.Length > 0) - { - throw new InvalidOperationException( - "Shared runtime surface documents are incomplete: " + - string.Join(", ", missingSurfaceDocumentSymbols)); - } - - if (!unityPackageProject.Contains("AetheriaRuntimeSurfaceDocuments.cs", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "GameCult.Aetheria.State.Unity.csproj does not include the shared runtime surface documents."); - } - - var daemonObserverPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "AetheriaDaemonObserver.cs"); - var daemonObserver = File.Exists(daemonObserverPath) - ? File.ReadAllText(daemonObserverPath) - : throw new InvalidOperationException("Cannot verify daemon state acquisition; AetheriaDaemonObserver.cs is missing."); - var observedFrameApplierPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "AetheriaUnityObservedFrameApplier.cs"); - var observedFrameApplier = File.Exists(observedFrameApplierPath) - ? File.ReadAllText(observedFrameApplierPath) - : throw new InvalidOperationException("Cannot verify daemon state acquisition; AetheriaUnityObservedFrameApplier.cs is missing."); - var runtimeClientProviderPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "AetheriaUnityRuntimeClientProvider.cs"); - var runtimeClientProvider = File.Exists(runtimeClientProviderPath) - ? File.ReadAllText(runtimeClientProviderPath) - : throw new InvalidOperationException("Cannot verify daemon state acquisition; AetheriaUnityRuntimeClientProvider.cs is missing."); - var unityDaemonRenderDocumentsPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "AetheriaUnityDaemonRenderDocuments.cs"); - var unityDaemonRenderDocuments = File.Exists(unityDaemonRenderDocumentsPath) - ? File.ReadAllText(unityDaemonRenderDocumentsPath) - : throw new InvalidOperationException("Cannot verify daemon state acquisition; AetheriaUnityDaemonRenderDocuments.cs is missing."); - var unityGameViewportDocumentsPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "AetheriaUnityGameViewportDocuments.cs"); - var unityGameViewportDocuments = File.Exists(unityGameViewportDocumentsPath) - ? File.ReadAllText(unityGameViewportDocumentsPath) - : throw new InvalidOperationException("Cannot verify daemon state acquisition; AetheriaUnityGameViewportDocuments.cs is missing."); - var zoneRendererPath = Path.Combine(root, "Assets", "Scripts", "Zone Display", "ZoneRenderer.cs"); - var zoneRenderer = File.Exists(zoneRendererPath) - ? File.ReadAllText(zoneRendererPath) - : throw new InvalidOperationException("Cannot verify daemon state acquisition; ZoneRenderer.cs is missing."); - var volumeCloudRendererPath = Path.Combine(root, "Assets", "Scripts", "Zone Display", "VolumeCloudRenderer.cs"); - var volumeCloudRenderer = File.Exists(volumeCloudRendererPath) - ? File.ReadAllText(volumeCloudRendererPath) - : throw new InvalidOperationException("Cannot verify daemon state acquisition; VolumeCloudRenderer.cs is missing."); - var renderSplatViewportSourcePath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "AetheriaUnityRenderSplatViewportSource.cs"); - var renderSplatViewportSource = File.Exists(renderSplatViewportSourcePath) - ? File.ReadAllText(renderSplatViewportSourcePath) - : throw new InvalidOperationException("Cannot verify daemon state acquisition; AetheriaUnityRenderSplatViewportSource.cs is missing."); - var schematicDisplayPath = Path.Combine(root, "Assets", "Scripts", "UI", "HUD", "SchematicDisplay.cs"); - var schematicDisplay = File.Exists(schematicDisplayPath) - ? File.ReadAllText(schematicDisplayPath) - : throw new InvalidOperationException("Cannot verify daemon state acquisition; SchematicDisplay.cs is missing."); - var menuPanelPath = Path.Combine(root, "Assets", "Scripts", "UI", "Menu", "MenuPanel.cs"); - var menuPanel = File.Exists(menuPanelPath) - ? File.ReadAllText(menuPanelPath) - : throw new InvalidOperationException("Cannot verify daemon state acquisition; MenuPanel.cs is missing."); - var mapRendererPath = Path.Combine(root, "Assets", "Scripts", "UI", "Menu", "MapRenderer.cs"); - var mapRenderer = File.Exists(mapRendererPath) - ? File.ReadAllText(mapRendererPath) - : throw new InvalidOperationException("Cannot verify daemon state acquisition; MapRenderer.cs is missing."); - var sectorMapPath = Path.Combine(root, "Assets", "Scripts", "UI", "Menu", "SectorMap.cs"); - var sectorMap = File.Exists(sectorMapPath) - ? File.ReadAllText(sectorMapPath) - : throw new InvalidOperationException("Cannot verify daemon state acquisition; SectorMap.cs is missing."); - var localMenuPath = Path.Combine(root, "Assets", "Scripts", "UI", "Menu", "LocalMenu.cs"); - var localMenu = File.Exists(localMenuPath) - ? File.ReadAllText(localMenuPath) - : throw new InvalidOperationException("Cannot verify daemon state acquisition; LocalMenu.cs is missing."); - var sectorRendererPath = Path.Combine(root, "Assets", "Scripts", "UI", "Menu", "SectorRenderer.cs"); - var sectorRenderer = File.Exists(sectorRendererPath) - ? File.ReadAllText(sectorRendererPath) - : throw new InvalidOperationException("Cannot verify daemon state acquisition; SectorRenderer.cs is missing."); - var tradeMenuPath = Path.Combine(root, "Assets", "Scripts", "UI", "Menu", "TradeMenu.cs"); - var tradeMenu = File.Exists(tradeMenuPath) - ? File.ReadAllText(tradeMenuPath) - : throw new InvalidOperationException("Cannot verify daemon state acquisition; TradeMenu.cs is missing."); - var inventoryMenuPath = Path.Combine(root, "Assets", "Scripts", "UI", "Menu", "InventoryMenu.cs"); - var inventoryMenu = File.Exists(inventoryMenuPath) - ? File.ReadAllText(inventoryMenuPath) - : throw new InvalidOperationException("Cannot verify daemon state acquisition; InventoryMenu.cs is missing."); - var inventoryPanelPath = Path.Combine(root, "Assets", "Scripts", "UI", "Menu", "InventoryPanel.cs"); - var inventoryPanel = File.Exists(inventoryPanelPath) - ? File.ReadAllText(inventoryPanelPath) - : throw new InvalidOperationException("Cannot verify daemon state acquisition; InventoryPanel.cs is missing."); - var inputDisplayLayoutPath = Path.Combine(root, "Assets", "Scripts", "UI", "InputScreen", "InputDisplayLayout.cs"); - var inputDisplayLayout = File.Exists(inputDisplayLayoutPath) - ? File.ReadAllText(inputDisplayLayoutPath) - : throw new InvalidOperationException("Cannot verify daemon state acquisition; InputDisplayLayout.cs is missing."); - var aetheriaClientPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaClient.cs"); - var aetheriaClient = File.Exists(aetheriaClientPath) - ? File.ReadAllText(aetheriaClientPath) - : throw new InvalidOperationException("Cannot verify daemon state acquisition; AetheriaClient.cs is missing."); - var aetheriaClientStatePath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaClientState.cs"); - var aetheriaClientState = File.Exists(aetheriaClientStatePath) - ? File.ReadAllText(aetheriaClientStatePath) - : throw new InvalidOperationException("Cannot verify daemon state acquisition; AetheriaClientState.cs is missing."); - var observedDaemonStatePath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeDaemonRenderView.cs"); - var observedDaemonState = File.Exists(observedDaemonStatePath) - ? File.ReadAllText(observedDaemonStatePath) - : throw new InvalidOperationException("Cannot verify daemon state acquisition; AetheriaRuntimeDaemonRenderView.cs is missing."); - var daemonRuntimeDocumentTestsPath = Path.Combine(root, "Assets", "Scripts", "Tests", "DaemonRuntimeDocumentTests.cs"); - var daemonRuntimeDocumentTests = File.Exists(daemonRuntimeDocumentTestsPath) - ? File.ReadAllText(daemonRuntimeDocumentTestsPath) - : throw new InvalidOperationException("Cannot verify daemon state acquisition; DaemonRuntimeDocumentTests.cs is missing."); - - var requiredActionGameManagerSymbols = new[] - { - "private AetheriaUnityGameplayBootShell GameplayBootShell =>", - "var boot = GameplayBootShell.Boot();", - "SceneWiring.ConfigureCurrentEntityPresentation(_currentEntityPresentation, boot.RuntimeCatalog);", - "SceneWiring.ConfigureTargetPresentation(", - "SceneWiring.ConfigureActionBarPresentation(", - "ItemManager = boot.ItemManager;", - "_loadoutItemFactory = boot.LoadoutItemFactory;", - "AetheriaUnityRuntimeClientProvider.PlayerSettings", - "ResolveDaemonObserver()", - "private AetheriaUnityObservedFrameApplier ObservedFrameApplier =>" - }; - - var missingActionGameManagerSymbols = requiredActionGameManagerSymbols - .Where(symbol => !actionGameManager.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - - if (missingActionGameManagerSymbols.Length > 0) - { - throw new InvalidOperationException( - "ActionGameManager does not route typed state reads through the shared Verse client and Unity boot shell: " + - string.Join(", ", missingActionGameManagerSymbols)); - } - - var requiredGameplayBootShellSymbols = new[] - { - "public sealed class AetheriaUnityGameplayBootShell", - "public AetheriaUnityGameplayBootResult Boot()", - "AetheriaRuntimeStateBoot.Inspect(AetheriaUnityRuntimePaths.GameDataDirectory)", - "AetheriaUnityRuntimeClientProvider.RuntimeState(stateBoot, stateBoot.RuntimeId)", - "runtimeState.Catalog.Latest()", - "runtimeState.SectorMap.Latest()", - "new ItemManager(", - "new AetheriaUnityLoadoutItemFactory(itemManager, runtimeCatalog)", - "ZoneRenderer.SetDroppedPickupItemFactory(loadoutItemFactory.CreateLoadoutItem)", - "ZoneRenderer.BodySettingsCollections = Settings.BodySettingsCollections", - "runtimeState?.DaemonFrame?.Latest()", - "render/game-feel settings are daemon authority", - "CockpitHudShell.SetRenderSettings(ZoneRenderer.RenderSettings)", - "AetheriaUnityRuntimeClientProvider.PlayerSettings.GraphicsSettings.ShowAsteroidsInMinimap", - "public readonly struct AetheriaUnityGameplayBootResult" - }; - - var missingGameplayBootShellSymbols = requiredGameplayBootShellSymbols - .Where(symbol => !gameplayBootShell.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - - if (missingGameplayBootShellSymbols.Length > 0) - { - throw new InvalidOperationException( - "Unity gameplay boot shell no longer owns typed runtime boot/catalog/render setup: " + - string.Join(", ", missingGameplayBootShellSymbols)); - } - - if (gameplayBootShell.Contains(".ObserveCatalog()", StringComparison.Ordinal) || - gameplayBootShell.Contains(".ObserveSectorMap()", 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;")) - { - throw new InvalidOperationException( - "AetheriaUnityGameplayBootShell must acquire runtime state through provider-native typed state access, not legacy wrappers or full-client peels."); - } - - var requiredRuntimeClientProviderSymbols = new[] - { - "public static class AetheriaUnityRuntimeClientProvider", - "public static RuntimePlayerSettings PlayerSettings", - "private static AetheriaClient ResolveClient(string stateFilePath, string runtimeId = \"\")", - "private static AetheriaClient ResolveClient(AetheriaRuntimeStateBootReport stateBoot, string runtimeId = \"\")", - "private static AetheriaClient RuntimeClient(string runtimeId = \"\")", - "public static AetheriaClientState RuntimeState(string runtimeId = \"\")", - "public static AetheriaUnityGameViewportDocuments MapViewportDocuments(", - "public static AetheriaUnityGameViewportDocuments ZonePresentationDocuments(", - "public static AetheriaUnityGameViewportDocuments RenderSplatViewportDocuments(", - "public static AetheriaUnityDaemonRenderDocuments DaemonRenderDocuments(", - "public static AetheriaControl Control(string runtimeId = \"\")", - "public static AetheriaUi Ui(string runtimeId = \"\")", - "public static AetheriaUi Ui(", - "public static CultMeshStateRefResolver EveSurfaceCultMeshStateRefResolver(string runtimeId = \"\")", - "public static CultMeshStateRefResolver EveSurfaceCultMeshStateRefResolver(", - "private static readonly Dictionary RuntimeClients", - "RuntimeClients.TryGetValue(cacheKey, out var runtimeClient)", - "RuntimeClients[cacheKey] = runtimeClient", - "AetheriaClient", - ".State", - "OpenAsync(", - "pullOnOpen: true", - "ApplyPlayerSettings(settings, stored)" - }; - var missingRuntimeClientProviderSymbols = requiredRuntimeClientProviderSymbols - .Where(symbol => !runtimeClientProvider.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (missingRuntimeClientProviderSymbols.Length > 0) - { - throw new InvalidOperationException( - "Unity runtime client/player-settings boot must live behind AetheriaUnityRuntimeClientProvider: " + - string.Join(", ", missingRuntimeClientProviderSymbols)); - } - - 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) || - runtimeClientProvider.Contains("_playerSettingsDocument.Current", StringComparison.Ordinal) || - runtimeClientProvider.Contains("_playerSettingsDocument?.Dispose()", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "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) || - runtimeClientProvider.Contains("CurrentClientForStateFile", StringComparison.Ordinal) || - runtimeClientProvider.Contains("StatePathCachePrefix", StringComparison.Ordinal) || - runtimeClientProvider.Contains(".ObservePlayer()", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "AetheriaUnityRuntimeClientProvider must expose narrow state/control/UI handles, not legacy sessions or ambient current-client lookups."); - } - - var providerOwnedClientAccessSources = new Dictionary - { - ["Packages/org.gamecult.aetheria.eve-runtime/Runtime/AetheriaEveSurfacePresenter.cs"] = eveSurfacePresenter, - ["Assets/Scripts/Zone Display/ZoneRenderer.cs"] = zoneRenderer, - ["Assets/Scripts/Zone Display/VolumeCloudRenderer.cs"] = volumeCloudRenderer, - ["Assets/Scripts/Gameplay/AetheriaUnityRenderSplatViewportSource.cs"] = renderSplatViewportSource, - ["Assets/Scripts/UI/Menu/MenuPanel.cs"] = menuPanel, - ["Assets/Scripts/UI/Menu/MapRenderer.cs"] = mapRenderer, - ["Assets/Scripts/UI/Menu/SectorMap.cs"] = sectorMap, - ["Assets/Scripts/UI/Menu/SectorRenderer.cs"] = sectorRenderer, - ["Assets/Scripts/UI/HUD/SchematicDisplay.cs"] = schematicDisplay, - ["Assets/Scripts/UI/Menu/TradeMenu.cs"] = tradeMenu, - ["Assets/Scripts/UI/Menu/InventoryMenu.cs"] = inventoryMenu, - ["Assets/Scripts/UI/Menu/InventoryPanel.cs"] = inventoryPanel, - ["Assets/Scripts/UI/MainMenu.cs"] = mainMenu, - ["Assets/Scripts/UI/InputScreen/InputDisplayLayout.cs"] = inputDisplayLayout, - ["Assets/Scripts/Gameplay/AetheriaDaemonObserver.cs"] = daemonObserver - }; - var directClientOpenHits = providerOwnedClientAccessSources - .Where(pair => - pair.Value.Contains(".OpenAsync(", StringComparison.Ordinal) || - pair.Value.Contains(".OpenLocalAsync(", StringComparison.Ordinal)) - .Select(pair => pair.Key) - .ToArray(); - if (directClientOpenHits.Length > 0) - { - throw new InvalidOperationException( - "Unity runtime readers must acquire managed typed state through AetheriaUnityRuntimeClientProvider instead of opening local clients: " + - string.Join(", ", directClientOpenHits)); - } - - var missingProviderClientAccess = providerOwnedClientAccessSources - .Where(pair => - !pair.Value.Contains("AetheriaUnityRuntimeClientProvider", StringComparison.Ordinal) && - !pair.Value.Contains("AetheriaEveRuntimeUnityHooks", StringComparison.Ordinal)) - .Select(pair => pair.Key) - .ToArray(); - if (missingProviderClientAccess.Length > 0) - { - throw new InvalidOperationException( - "Unity runtime readers no longer use the shared managed runtime client provider: " + - string.Join(", ", missingProviderClientAccess)); - } - - if (!observedFrameApplier.Contains("private AetheriaRuntimeZoneRenderDocument _lastZoneRender;", StringComparison.Ordinal) || - !observedFrameApplier.Contains("ApplyLatestZoneRender()", StringComparison.Ordinal) || - !observedFrameApplier.Contains("observer.LastRenderView?.ZoneRender", StringComparison.Ordinal) || - !observedDaemonState.Contains("public AetheriaRuntimeZoneRenderDocument ZoneRender { get; }", StringComparison.Ordinal) || - !observedDaemonState.Contains("CultMeshReactiveDocument zoneRender", StringComparison.Ordinal) || - !observedDaemonState.Contains("var currentZoneRender = zoneRender?.Current;", StringComparison.Ordinal) || - !observedDaemonState.Contains("new AetheriaRuntimeDaemonRenderView(currentFrame, currentSoaView, currentZoneRender)", StringComparison.Ordinal) || - !unityDaemonRenderDocuments.Contains("public sealed class AetheriaUnityDaemonRenderDocuments : IDisposable", StringComparison.Ordinal) || - !unityDaemonRenderDocuments.Contains("private readonly CultMeshReactiveDocument _frame;", StringComparison.Ordinal) || - !unityDaemonRenderDocuments.Contains("private readonly CultMeshReactiveDocument _soaView;", StringComparison.Ordinal) || - !unityDaemonRenderDocuments.Contains("private readonly CultMeshReactiveDocument _zoneRender;", StringComparison.Ordinal) || - !unityDaemonRenderDocuments.Contains("public AetheriaRuntimeDaemonRenderView Current", StringComparison.Ordinal) || - !unityDaemonRenderDocuments.Contains("AetheriaRuntimeDaemonRenderView.TryCreateCurrent(", StringComparison.Ordinal) || - !daemonObserver.Contains("private AetheriaUnityDaemonRenderDocuments _renderDocuments;", StringComparison.Ordinal) || - !daemonObserver.Contains("_renderDocuments?.Current", StringComparison.Ordinal) || - !daemonObserver.Contains("AetheriaUnityRuntimeClientProvider.DaemonRenderDocuments(stateBoot, clientId)", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Observed zone-render state acquisition must sample managed typed zone-render documents directly."); - } - - if (unityDaemonRenderDocuments.Contains("public CultMeshReactiveDocument<", StringComparison.Ordinal) || - unityGameViewportDocuments.Contains("public CultMeshReactiveDocument<", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Unity managed document holders must not leak raw CultMesh reactive documents through their public surface."); - } - - var requiredDaemonStateFacadeSymbols = new[] - { - "public CultMeshDocumentHandle ProviderAdvertisement { get; }", - "public CultMeshDocumentHandle Health { get; }", - "public CultMeshDocumentHandle CommandBoundary { get; }", - "public CultMeshDocumentHandle AuthorityPolicy { get; }", - "public CultMeshDocumentHandle DaemonFrame { get; }", - "public CultMeshDocumentHandle DaemonSoaView { get; }", - "using EveSurfaceDocument = GameCult.Eve.Surface.EveSurfaceDocument;", - "public CultMeshDocumentHandle GameSurface { get; }", - "public CultMeshDocumentHandle GameTuiSurface { get; }", - "public CultMeshDocumentHandle EditorSurface { get; }", - "public CultMeshDocumentHandle EditorTuiSurface { get; }", - "public CultMeshDocumentHandle Catalog { get; }", - "public CultMeshDocumentHandle LoadoutTemplates { get; }", - "public CultMeshDocumentHandle PlayerSettings { get; }", - "public CultMeshDocumentHandle VerseHostSettings { get; }" - }; - var missingDaemonStateFacadeSymbols = requiredDaemonStateFacadeSymbols - .Where(symbol => !aetheriaClientState.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (missingDaemonStateFacadeSymbols.Length > 0) - { - throw new InvalidOperationException( - "AetheriaClientState must expose daemon service state as managed typed document handles: " + - string.Join(", ", missingDaemonStateFacadeSymbols)); - } - - var forbiddenDaemonStateFacadeSymbols = new[] - { - "public AetheriaClientDaemonState Daemon { get; }", - "public sealed class AetheriaClientDaemonState", - "public AetheriaClientSettingsState Settings { get; }", - "public sealed class AetheriaClientSettingsState", - "public CultMeshDocumentHandle LatestFrame { get; }", - "public CultMeshDocumentHandle LatestSoaView { get; }" - }; - var survivingDaemonStateFacadeSymbols = forbiddenDaemonStateFacadeSymbols - .Where(symbol => aetheriaClientState.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (survivingDaemonStateFacadeSymbols.Length > 0) - { - throw new InvalidOperationException( - "AetheriaClientState still exposes daemon/settings facade wrappers instead of flat managed typed document handles: " + - string.Join(", ", survivingDaemonStateFacadeSymbols)); - } - - var requiredManagedClientConnectionSymbols = new[] - { - "public AetheriaClientState State => _state;", - "var frame = CurrentDaemonFrame();", - "private CultMeshReactiveDocument? _daemonFrame;", - "_daemonFrame ??= State.DaemonFrame.Reactive();" - }; - var missingManagedClientConnectionSymbols = requiredManagedClientConnectionSymbols - .Where(symbol => !aetheriaClient.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (missingManagedClientConnectionSymbols.Length > 0) - { - throw new InvalidOperationException( - "AetheriaClient must expose the managed state object without owning document-catalog shortcuts: " + - string.Join(", ", missingManagedClientConnectionSymbols)); - } - - if (aetheriaClient.Contains("TryReactiveDaemonSoaView()", StringComparison.Ordinal) || - aetheriaClient.Contains("CurrentObservedDaemon()", StringComparison.Ordinal) || - aetheriaClient.Contains("State.CurrentObservedDaemon()", StringComparison.Ordinal) || - aetheriaClient.Contains("State.ReactiveObservedDaemon()", StringComparison.Ordinal) || - aetheriaClient.Contains("Aetheria() => State", StringComparison.Ordinal) || - aetheriaClient.Contains("AetheriaRuntimeDaemonRenderView.TryCreateCurrent(", StringComparison.Ordinal) || - aetheriaClient.Contains("AetheriaRuntimeDaemonRenderView?", StringComparison.Ordinal) || - aetheriaClient.Contains("observedState.TryCurrent(out var current)", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "AetheriaClient command submission still samples observed daemon state through an aggregate reactive wrapper instead of direct managed typed documents."); - } - - var requiredManagedStateAccessSymbols = new[] - { - "public CultMeshDocumentHandle DaemonFrame", - "public CultMeshDocumentHandle Catalog", - "public CultMeshDocumentHandle CurrentEntity", - "public CultMeshDocumentHandle ObjectsViewport(", - "public CultMeshDocumentHandle Inventory(", - "public CultMeshDocumentHandle StarbridgePlayerSeat(" - }; - var missingManagedStateAccessSymbols = requiredManagedStateAccessSymbols - .Where(symbol => !aetheriaClientState.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (missingManagedStateAccessSymbols.Length > 0) - { - throw new InvalidOperationException( - "AetheriaClientState must expose direct named typed document handles instead of AetheriaClient forwarding shortcuts or per-document session wrappers: " + - string.Join(", ", missingManagedStateAccessSymbols)); - } - - if (aetheriaClientState.Contains("public Task LatestAsync", StringComparison.Ordinal) || - aetheriaClientState.Contains("public TDocument Latest", StringComparison.Ordinal) || - aetheriaClientState.Contains("public Observable Watch", StringComparison.Ordinal) || - aetheriaClientState.Contains("public CultMeshDocumentHandle Document", StringComparison.Ordinal) || - aetheriaClientState.Contains("public CultMeshReactiveDocument Reactive", StringComparison.Ordinal) || - aetheriaClientState.Contains("TryGetDocument", StringComparison.Ordinal) || - aetheriaClientState.Contains("CultMeshDocumentCatalog", StringComparison.Ordinal) || - aetheriaClientState.Contains("public Task> ReactiveAsync", StringComparison.Ordinal) || - aetheriaClientState.Contains("DocumentBySchema(", StringComparison.Ordinal) || - aetheriaClientState.Contains("TryGetDocumentBySchema(", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "AetheriaClientState still exposes generic document catalogs, latest/watch, or schema-string shortcuts instead of direct named typed CultMesh document handles."); - } - - if (aetheriaClientState.Contains("public AetheriaRuntimeDaemonRenderView? CurrentObservedDaemon(", StringComparison.Ordinal) || - aetheriaClientState.Contains("AetheriaRuntimeDaemonRenderView.TryCreateCurrent(frame, soaView, zoneRender", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "AetheriaClientState must not expose one-shot observed daemon aggregation; command owners should hold managed typed daemon documents."); - } - - var requiredManagedCommandFrameSymbols = new[] - { - "internal AetheriaRuntimeDaemonFrameDocument? CurrentDaemonFrame()", - "_daemonFrame ??= State.DaemonFrame.Reactive();", - "return _daemonFrame.Current;", - "Func submit", - "frame?.SessionId ?? _sessionId" - }; - var missingManagedCommandFrameSymbols = requiredManagedCommandFrameSymbols - .Where(symbol => !aetheriaClient.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (missingManagedCommandFrameSymbols.Length > 0) - { - throw new InvalidOperationException( - "AetheriaClient command submission must sample only the managed daemon frame document instead of composing an observed render aggregate: " + - string.Join(", ", missingManagedCommandFrameSymbols)); - } - - if (!daemonRuntimeDocumentTests.Contains("ManagedStateSamplesCurrentDaemonRenderViewFromReactiveDocuments()", 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("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."); - } - - var requiredObservedDaemonStateSymbols = new[] - { - "AetheriaRuntimeDaemonSoaViewIndex.Build(soaView)", - "public static bool TryCreateCurrent(", - "CultMeshReactiveDocument frame", - "CultMeshReactiveDocument? soaView", - "CultMeshReactiveDocument zoneRender", - "var currentFrame = frame?.Current;", - "var currentZoneRender = zoneRender?.Current;", - "new AetheriaRuntimeDaemonRenderView(currentFrame, currentSoaView, currentZoneRender)" - }; - var missingObservedDaemonStateSymbols = requiredObservedDaemonStateSymbols - .Where(symbol => !observedDaemonState.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (missingObservedDaemonStateSymbols.Length > 0) - { - throw new InvalidOperationException( - "Observed daemon state must be composed from managed typed daemon documents: " + - string.Join(", ", missingObservedDaemonStateSymbols)); - } - - if (observedDaemonState.Contains("AetheriaRuntimeDaemonFrameSession", StringComparison.Ordinal) || - observedDaemonState.Contains("AetheriaRuntimeDaemonSoaViewSession", StringComparison.Ordinal) || - observedDaemonState.Contains("AetheriaRuntimeZoneRenderSession", StringComparison.Ordinal) || - aetheriaClientState.Contains("TryObserveDaemonSoaView(options)", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Observed daemon state still composes legacy session wrappers instead of direct reactive CultMesh documents."); - } - - var clientStateSessionWrapperSymbols = new[] - { - "Session Observe", - "new AetheriaRuntimeCatalogSession", - "new AetheriaRuntimeDaemonFrameSession", - "new AetheriaRuntimeLoadoutTemplatesSession", - "new AetheriaRuntimeSectorMapSession", - "new AetheriaRuntimeZoneContactsSession", - "new AetheriaRuntimeStationRefitSession", - "new AetheriaRuntimeZoneRenderSession", - "new AetheriaRuntimeCurrentZoneSession", - "new AetheriaRuntimeCurrentEntitySession", - "new AetheriaRuntimeCurrentDockingSession", - "new AetheriaRuntimePlayerSettingsSession", - "new AetheriaRuntimeVerseHostSettingsSession", - "new AetheriaRuntimeObjectsViewportSession", - "new AetheriaRuntimeRenderSplatsViewportSession", - "new AetheriaRuntimeInventorySession", - "new AetheriaRuntimeStarbridgeScenarioSession", - "new AetheriaRuntimeStarbridgeRunSession", - "new AetheriaRuntimeStarbridgeSummarySession", - "new AetheriaRuntimeStarbridgePlayerSeatSession" - }; - var clientStateSessionWrapperHits = clientStateSessionWrapperSymbols - .Where(symbol => aetheriaClientState.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (clientStateSessionWrapperHits.Length > 0) - { - throw new InvalidOperationException( - "AetheriaClientState still exposes per-document Observe* session wrappers instead of direct Reactive* document access: " + - string.Join(", ", clientStateSessionWrapperHits)); - } - - var publicObserveAccessorHits = System.Text.RegularExpressions.Regex - .Matches( - aetheriaClientState, - @"public\s+[^{;=]+\s+Observe[A-Za-z0-9_]*\s*\(", - System.Text.RegularExpressions.RegexOptions.CultureInvariant) - .Select(match => match.Value.Trim()) - .ToArray(); - if (publicObserveAccessorHits.Length > 0) - { - throw new InvalidOperationException( - "AetheriaClientState reintroduced public Observe* state accessors; expose direct Reactive* typed documents instead: " + - string.Join(", ", publicObserveAccessorHits)); - } - - var runtimeStateSessionsPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeStateSessions.cs"); - if (File.Exists(runtimeStateSessionsPath)) - { - throw new InvalidOperationException( - "AetheriaRuntimeStateSessions.cs still exists; per-document session wrappers must be deleted in favor of direct CultMesh reactive documents."); - } - - var forbiddenObservedDaemonStateSymbols = new[] - { - "string statePath", - "FramePath", - "SoaViewPath", - "AetheriaRuntimeDaemonFrameStore.GetFramePath", - "SoaViewStore.GetViewPath", - "state.LatestFrame.ReactiveAsync", - "state.LatestSoaView.ReactiveAsync", - "public static async Task ReadAsync(", - "LatestObservedDaemonAsync(", - "LatestObservedDaemon()", - "public sealed class AetheriaRuntimeReactiveObservedDaemonState", - "ReactiveObservedDaemonAsync", - "ReactiveObservedDaemon(", - "TryCurrent(out AetheriaRuntimeDaemonRenderView? observed)", - "return new AetheriaRuntimeDaemonRenderView(frame, soaView);", - "AetheriaRuntimeGameDocuments.ZoneRender(Frame)", - "zoneRender = null" - }; - var observedDaemonStateBypassHits = forbiddenObservedDaemonStateSymbols - .Where(symbol => observedDaemonState.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (observedDaemonStateBypassHits.Length > 0) - { - throw new InvalidOperationException( - "Observed daemon state still projects managed daemon documents back into witness file paths: " + - string.Join(", ", observedDaemonStateBypassHits)); - } - - var forbiddenDaemonClientBypassSymbols = new[] - { - ".ReadAsync(State)", - "return _verse.GetObservedDaemonStateAsync();", - "return _verse.GetLatestAuthoritativeRunFrameAsync();", - "return _verse.GetHealthAsync();", - "return _verse.GetVerseAuthorityPolicyAsync();", - "return _verse.GetLatestSoaViewAsync();", - "AetheriaRuntimeDaemonPublicationStore.TryReadVerseAuthorityPolicy", - "return _verse.GetDaemonGameSurfaceAsync();", - "return _verse.GetDaemonGameTuiSurfaceAsync();", - "return _verse.GetDaemonEditorSurfaceAsync();", - "return _verse.GetDaemonEditorTuiSurfaceAsync();", - "public AetheriaRuntimeCatalogSnapshot OpenRuntimeCatalog()", - "public async Task PlayerSettingsAsync()", - "public async Task VerseHostSettingsAsync()", - "public async Task> LoadoutTemplatesAsync()", - "public async Task ObserveAsync()", - "public async Task LoadoutTemplateAsync(", - "scenario ??= await _verse.GetStarbridgeScenarioAsync()", - "session ??= await _verse.GetStarbridgeSessionAsync()", - "var frame = await _verse.GetLatestFrameAsync()", - "public async Task LatestAuthoritativeRunFrameAsync()", - "public async Task MapViewportAsync(", - "public async Task ObjectsViewportAsync(", - "public async Task GravityViewportAsync(", - "public async Task RenderSplatsViewportAsync(", - "public async Task AssetManifestAsync()", - "public async Task CurrentZoneAsync()", - "public async Task CurrentEntityAsync()", - "public async Task CurrentDockingAsync()", - "public async Task ZoneContactsAsync()", - "public async Task StationRefitAsync()", - "public async Task SectorMapAsync()", - "public async Task ZoneDetailsAsync(", - "public async Task ZoneRenderAsync()", - "public async Task StarbridgeSessionSummaryAsync(", - "public async Task SelectedObjectAsync(", - "public async Task InventoryAsync(", - "public async Task DaemonHealthAsync()", - "public async Task AuthorityStatusAsync()", - "public async Task SoaViewAsync()", - "public async Task DaemonGameSurfaceAsync()", - "public async Task DaemonGameTuiSurfaceAsync()", - "public async Task DaemonEditorSurfaceAsync()", - "public async Task DaemonEditorTuiSurfaceAsync()", - "public CultMeshStateRefResolver CreateEveSurfaceCultMeshStateRefResolver()", - "public CultMeshDocumentHandle Document()", - "public Observable Watch()", - "public Task> ReactiveAsync(", - "public CultMeshReactiveDocument Reactive(" - }; - var daemonClientBypassHits = forbiddenDaemonClientBypassSymbols - .Where(symbol => aetheriaClient.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (daemonClientBypassHits.Length > 0) - { - throw new InvalidOperationException( - "AetheriaClient still bypasses managed daemon documents for compatibility reads: " + - string.Join(", ", daemonClientBypassHits)); - } - - var forbiddenActionGameManagerReaderSymbols = new[] - { - "AetheriaRuntimeStateReader.ReadPlayerSettings", - "AetheriaRuntimeStateReader.ReadLoadoutTemplates", - "AetheriaRuntimeStateReader.OpenRuntimeCatalog", - "LoadoutTemplatesAsync()", - "ObservedLoadoutTemplates", - "_observedLoadoutTemplates", - "LoadRuntimeLoadoutTemplates", - "AetheriaRuntimeStateBoot.Inspect(", - "ResolveClient(stateBoot.StateFilePath).OpenRuntimeCatalog()", - "ItemManager = new ItemManager(", - "ZoneRenderer.SetDroppedPickupItemFactory(", - "ZoneRenderer.BodySettingsCollections = Settings.BodySettingsCollections", - "ResolveRuntimeVerseClient(", - "PlayerSettingsAsync()", - "ApplyRuntimePlayerSettings(", - "CreateDefaultRuntimePlayerSettings(", - "_runtimeClient" - }; - var actionGameManagerReaderHits = forbiddenActionGameManagerReaderSymbols - .Where(symbol => actionGameManager.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (actionGameManagerReaderHits.Length > 0) - { - throw new InvalidOperationException( - "ActionGameManager still reads typed state through the runtime file reader instead of the shared Aetheria client facade: " + - string.Join(", ", actionGameManagerReaderHits)); - } - - if (!daemonObserver.Contains("AetheriaRuntimeStateBoot.Inspect(AetheriaUnityRuntimePaths.GameDataDirectory)", StringComparison.Ordinal) || - !daemonObserver.Contains("AetheriaRuntimeDaemonRenderView", StringComparison.Ordinal) || - !daemonObserver.Contains("private AetheriaUnityDaemonRenderDocuments _renderDocuments;", StringComparison.Ordinal) || - !daemonObserver.Contains("_renderDocuments?.Current", StringComparison.Ordinal) || - !daemonObserver.Contains("AetheriaUnityRuntimeClientProvider.DaemonRenderDocuments(stateBoot, clientId)", StringComparison.Ordinal) || - !unityDaemonRenderDocuments.Contains("public AetheriaRuntimeDaemonRenderView Current", StringComparison.Ordinal) || - !unityDaemonRenderDocuments.Contains("private readonly CultMeshReactiveDocument _frame;", StringComparison.Ordinal) || - !unityDaemonRenderDocuments.Contains("private readonly CultMeshReactiveDocument _soaView;", StringComparison.Ordinal) || - !unityDaemonRenderDocuments.Contains("private readonly CultMeshReactiveDocument _zoneRender;", StringComparison.Ordinal) || - !daemonObserver.Contains("DisposeRenderViewDocuments()", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "AetheriaDaemonObserver must hold a native managed daemon-render document handle and read its current state."); - } - - if (daemonObserver.Contains("AetheriaRuntimeStateReader.TryReadDaemonRenderView", StringComparison.Ordinal) || - daemonObserver.Contains("public AetheriaClient Client", StringComparison.Ordinal) || - daemonObserver.Contains("public AetheriaControl Control", StringComparison.Ordinal) || - daemonObserver.Contains("AetheriaUnityRuntimeClientProvider.ResolveClient(", StringComparison.Ordinal) || - daemonObserver.Contains("AetheriaClientState ResolveState()", StringComparison.Ordinal) || - daemonObserver.Contains("AetheriaUnityRuntimeClientProvider.RuntimeState(stateBoot, clientId)", StringComparison.Ordinal) || - daemonObserver.Contains("CultMeshReactiveDocument _daemonFrame", StringComparison.Ordinal) || - daemonObserver.Contains("CultMeshReactiveDocument _daemonSoaView", StringComparison.Ordinal) || - daemonObserver.Contains("CultMeshReactiveDocument _zoneRender", StringComparison.Ordinal) || - daemonObserver.Contains("_daemonFrame ??= state.ReactiveDaemonFrame();", StringComparison.Ordinal) || - daemonObserver.Contains("_daemonSoaView ??= state.ReactiveDaemonSoaView();", StringComparison.Ordinal) || - daemonObserver.Contains("_zoneRender ??= state.ReactiveZoneRender();", StringComparison.Ordinal) || - daemonObserver.Contains("AetheriaRuntimeDaemonRenderView.TryCreateCurrent(", StringComparison.Ordinal) || - daemonObserver.Contains("ResolveClient()?.State", StringComparison.Ordinal) || - daemonObserver.Contains(".ReadAsync(client.State)", StringComparison.Ordinal) || - daemonObserver.Contains("AetheriaRuntimeReactiveObservedDaemonState", StringComparison.Ordinal) || - daemonObserver.Contains(".ReactiveObservedDaemon()", StringComparison.Ordinal) || - daemonObserver.Contains("reactive.TryCurrent(out var observed)", StringComparison.Ordinal) || - daemonObserver.Contains("AetheriaRuntimeObservedDaemonSession", StringComparison.Ordinal) || - daemonObserver.Contains(".ObserveDaemon()", StringComparison.Ordinal) || - daemonObserver.Contains("return new AetheriaRuntimeDaemonRenderView(frame, soaView, zoneRender);", StringComparison.Ordinal) || - daemonObserver.Contains("DisposeObservedDaemonSession()", StringComparison.Ordinal) || - daemonObserver.Contains("DisposeReactiveObservedDaemonDocuments()", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "AetheriaDaemonObserver still routes through observed-daemon compatibility wrappers instead of direct reactive documents."); - } - - if (daemonObserver.Contains("ObserveAsync()", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "AetheriaDaemonObserver still reads observed daemon state through the AetheriaClient compatibility helper."); - } - - if (!mainMenu.Contains("AetheriaUnityRuntimeClientProvider", StringComparison.Ordinal) || - !mainMenu.Contains("ResolveMainMenuSurface(string surfaceId)", StringComparison.Ordinal) || - !mainMenu.Contains(".RuntimeState(stateBoot, \"unity-main-menu\")", StringComparison.Ordinal) || - !mainMenu.Contains(".MainMenuSurface(surfaceId, CanOpenRuntimeInputScreen(), InGame)", StringComparison.Ordinal) || - !mainMenu.Contains(".Latest()", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "MainMenu must read menu state through provider-native managed typed documents."); - } - - if (mainMenu.Contains("PrepareDocumentAccess(stateBoot)", StringComparison.Ordinal) || - mainMenu.Contains(".ReactiveSectorMap(", StringComparison.Ordinal) || - mainMenu.Contains(".ReactivePlayerSettingsDocument(", StringComparison.Ordinal) || - mainMenu.Contains(".ReactiveVerseHostSettings(", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "MainMenu still owns reactive document handles instead of sampling named current typed documents."); - } - - if (mainMenu.Contains("AetheriaRuntimeStateReader.TryReadDaemonFrame", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "MainMenu still reads daemon frames through the runtime file reader instead of managed typed documents."); - } - - if (mainMenu.Contains("OpenRuntimeCatalog(", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "MainMenu still names runtime catalog access as a raw catalog open instead of a managed latest document read."); - } - - if (mainMenu.Contains("LatestRuntimeCatalog(", StringComparison.Ordinal) || - mainMenu.Contains("AetheriaUnityObservedRunProjection.Project(", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "MainMenu still owns gameplay catalog/sector projection instead of leaving observed-scene boot to AetheriaUnityGameplayBootShell."); - } - - var requiredGameplayBootSymbols = new[] - { - "AetheriaUnityRuntimeClientProvider.RuntimeState(stateBoot, stateBoot.RuntimeId)", - "runtimeState.Catalog.Latest()", - "runtimeState.SectorMap.Latest()", - "Galaxy.ProjectObservedSectorMap(", - "sectorMap.IsTutorial", - "sectorMap.GenerationSeed", - "new AetheriaUnityGameplayBootResult(", - "ObservedGalaxy" - }; - var missingGameplayBootSymbols = requiredGameplayBootSymbols - .Where(symbol => !gameplayBootShell.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (missingGameplayBootSymbols.Length > 0) - { - throw new InvalidOperationException( - "AetheriaUnityGameplayBootShell must own managed catalog/sector-map projection for the observed gameplay scene: " + - string.Join(", ", missingGameplayBootSymbols)); - } - - if (gameplayBootShell.Contains(".ReactiveCatalogSnapshot()", StringComparison.Ordinal) || - gameplayBootShell.Contains("runtimeCatalogDocument.Current", StringComparison.Ordinal) || - gameplayBootShell.Contains(".ReactiveSectorMap()", StringComparison.Ordinal) || - gameplayBootShell.Contains("sectorMapDocument.Current", 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 the managed runtime state handle for one-shot gameplay boot catalog/sector-map access."); - } - - var observedRunProjectionPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "AetheriaUnityObservedRunProjection.cs"); - if (File.Exists(observedRunProjectionPath)) - { - throw new InvalidOperationException( - "AetheriaUnityObservedRunProjection is legacy projection chaff; gameplay boot should project the managed sector-map document directly."); - } - - var forbiddenMainMenuReaderSymbols = new[] - { - "AetheriaRuntimeStateReader.ReadPlayerSettings", - "AetheriaRuntimeStateReader.ReadVerseHostSettings" - }; - var mainMenuReaderHits = forbiddenMainMenuReaderSymbols - .Where(symbol => mainMenu.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (mainMenuReaderHits.Length > 0) - { - throw new InvalidOperationException( - "MainMenu still reads typed state through the runtime file reader instead of the shared Aetheria client facade: " + - string.Join(", ", mainMenuReaderHits)); - } - - if (!eveSurfacePresenter.Contains(".RequireRuntimeState(stateBoot, \"unity-eve-surface-presenter\")", StringComparison.Ordinal) || - !eveSurfacePresenter.Contains(".EveSurfaceDocument(surfaceId)", StringComparison.Ordinal) || - !eveSurfacePresenter.Contains("?.Reactive()", StringComparison.Ordinal) || - !eveSurfacePresenter.Contains("ReadDaemonSurface(stateBoot)", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Aetheria Eve surface presenter must grab the provider RuntimeState and read the daemon surface through AetheriaClientState reactive typed documents."); - } - - if (!eveSurfacePresenter.Contains("AetheriaEveRuntimeUnityHooks.TryCreateStateRefResolver(", StringComparison.Ordinal) || - !eveUnitySurfaceHost.Contains("AetheriaEveRuntimeUnityHooks.TryCreateDefaultStateRefResolver()", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Unity Eve surface lowering must resolve provider state refs through provider-native managed AetheriaClientState documents."); - } - - if (eveSurfacePresenter.Contains("AetheriaUnityRuntimeClientProvider.ResolveClient(", StringComparison.Ordinal) || - eveSurfacePresenter.Contains("client.State.CreateEveSurfaceCultMeshStateRefResolver()", StringComparison.Ordinal) || - eveSurfacePresenter.Contains("ResolveClient(statePath).State.CreateEveSurfaceCultMeshStateRefResolver()", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Aetheria Eve surface presenter still peels through the raw client instead of using provider-native state/ref handles."); - } - - if (eveSurfacePresenter.Contains("AetheriaRuntimeStateReader.CreateEveSurfaceStateRefResolver", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Unity Eve surface lowering still resolves state refs through the file reader instead of the shared AetheriaClient facade."); - } - - if (eveUnitySurfaceHost.Contains("AetheriaRuntimeStateReader.CreateEveSurfaceStateRefResolver", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Unity Eve surface host still resolves default state refs through the file reader instead of the managed runtime client provider."); - } - - if (!aetheriaClientState.Contains("AetheriaRuntimeStateRefResolver.CreateEveSurfaceCultMeshStateRefResolver(", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Managed client state no longer resolves Eve state refs through the typed runtime state-ref resolver."); - } - - if (aetheriaClientState.Contains("AetheriaRuntimeStateReader.CreateEveSurfaceCultMeshStateRefResolver", StringComparison.Ordinal) || - aetheriaClientState.Contains("AetheriaRuntimeStateReader.CreateEveSurfaceStateRefResolver", StringComparison.Ordinal) || - aetheriaClientState.Contains("public Func CreateEveSurfaceStateRefResolver()", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Managed client state still routes or exposes Eve state-ref resolution through file-backed/delegate compatibility."); - } - - var forbiddenDirectStoreSymbols = new Dictionary - { - [actionGameManagerPath] = new[] - { - "AetheriaRuntimeCatalogStore.ReadPlayerSettings", - "AetheriaRuntimeCatalogStore.ReadLoadoutTemplates", - "AetheriaRuntimeCatalogStore.OpenReadOnly", - "AetheriaRuntimeCatalogStore.ReadZoneStates", - "AetheriaRuntimeCatalogStore.ReadEntitySnapshots" - }, - [mainMenuPath] = new[] - { - "AetheriaRuntimeCatalogStore.ReadRunStates", - "AetheriaRuntimeCatalogStore.ReadVerseHostSettings" - }, - [eveSurfacePresenterPath] = new[] - { - "AetheriaRuntimeCatalogStore.ReadEveSurfaces" - } - }; - - var directStoreHits = forbiddenDirectStoreSymbols - .SelectMany(entry => - { - var source = File.ReadAllText(entry.Key); - return entry.Value - .Where(symbol => source.Contains(symbol, StringComparison.Ordinal)) - .Select(symbol => $"{Path.GetRelativePath(root, entry.Key)} -> {symbol}"); - }) - .ToArray(); - - if (directStoreHits.Length > 0) - { - throw new InvalidOperationException( - "Unity gameplay/UI still reads typed state directly from the raw store instead of the shared runtime state reader: " + - string.Join("; ", directStoreHits)); - } - - var unitySurfaceLowererFiles = Directory - .EnumerateFiles(Path.Combine(root, "Assets", "Scripts", "UI"), "*.cs", SearchOption.AllDirectories) - .ToArray(); - var forbiddenLocalSurfaceAdapterSymbols = new[] - { - "private static EveSurfaceDocument ToEveSurfaceDocument(AetheriaRuntimeSurfaceDocument document)", - "private static EveSurfaceComponent ToEveSurfaceComponent(AetheriaRuntimeSurfaceComponent component)", - "new EveSurfaceDocument(\r\n \"surface-state\"", - "new EveCommandTemplate(command.Command, command.Label, command.Transport)" - }; - var localSurfaceAdapterHits = unitySurfaceLowererFiles - .SelectMany(path => - { - var source = File.ReadAllText(path); - return forbiddenLocalSurfaceAdapterSymbols - .Where(symbol => source.Contains(symbol, StringComparison.Ordinal)) - .Select(symbol => $"{Path.GetRelativePath(root, path)} -> {symbol}"); - }) - .ToArray(); - if (localSurfaceAdapterHits.Length > 0) - { - throw new InvalidOperationException( - "Unity UI lowerers still duplicate runtime-to-Eve surface conversion instead of using the shared runtime surface documents: " + - string.Join("; ", localSurfaceAdapterHits)); - } -} - -static void RequireRuntimeSimulationTuningRequests(string root) -{ - var requiredActionGameManagerPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "ActionGameManager.cs"); - var actionGameManager = File.Exists(requiredActionGameManagerPath) - ? File.ReadAllText(requiredActionGameManagerPath) - : throw new InvalidOperationException("Cannot verify runtime simulation tuning authority; ActionGameManager.cs is missing."); - var inventoryMenuPath = Path.Combine(root, "Assets", "Scripts", "UI", "Menu", "InventoryMenu.cs"); - var inventoryMenu = File.Exists(inventoryMenuPath) - ? File.ReadAllText(inventoryMenuPath) - : throw new InvalidOperationException("Cannot verify runtime simulation tuning authority; InventoryMenu.cs is missing."); - - var requiredInventoryRequestSymbols = new[] - { - "private void RequestEntityShutdownPerformance(string targetEntityKey, float shutdownPerformance)", - "private void RequestEquippedItemOverrideShutdown(EquippedItem item, bool enabled)", - "private void RequestThermotoggleTargetTemperature(", - "operations => operations.SetShutdownPerformance(", - "operations => operations.SetItemOverrideShutdown(", - "operations => operations.SetThermotoggleTargetTemperature(" - }; - - var missingInventoryRequestSymbols = requiredInventoryRequestSymbols - .Where(symbol => !inventoryMenu.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - - if (missingInventoryRequestSymbols.Length > 0) - { - throw new InvalidOperationException( - "Runtime simulation tuning request methods are missing from InventoryMenu's typed daemon operation boundary: " + - string.Join(", ", missingInventoryRequestSymbols)); - } - - var forbiddenUiWrites = new[] - { - "EquippableItem.OverrideShutdown =", - "thermotoggle.TargetTemperature =", - "Settings.ShutdownPerformance =", - "CurrentEntity.OverrideShutdown =" - }; - - var uiRoot = Path.Combine(root, "Assets", "Scripts", "UI"); - var hits = Directory.EnumerateFiles(uiRoot, "*.cs", SearchOption.AllDirectories) - .SelectMany(path => File.ReadLines(path) - .Select((line, index) => new { Path = path, LineNumber = index + 1, Line = line })) - .Where(line => forbiddenUiWrites.Any(symbol => line.Line.Contains(symbol, StringComparison.Ordinal))) - .Select(line => $"{Path.GetRelativePath(root, line.Path)}:{line.LineNumber}: {line.Line.Trim()}") - .Take(10) - .ToArray(); - - if (hits.Length > 0) - { - throw new InvalidOperationException( - "Runtime simulation tuning still has renderer-local UI write authority: " + - string.Join("; ", hits)); - } - - var shutdownRequest = inventoryMenu.IndexOf("RequestEntityShutdownPerformance(", StringComparison.Ordinal); - var staleShipSettingsRender = inventoryMenu.IndexOf("RenderCurrentShipSettingsSurface(entity);", shutdownRequest, StringComparison.Ordinal); - if (shutdownRequest >= 0 && staleShipSettingsRender >= 0) - { - throw new InvalidOperationException( - "InventoryMenu still redraws ship settings immediately after submitting daemon shutdown-performance requests."); - } - - var normalizedInventoryMenu = inventoryMenu.Replace("\r\n", "\n", StringComparison.Ordinal); - var forbiddenEquippedItemTuningRedraws = new[] - { - "GameManager.RequestEquippedItemOverrideShutdown(item, !item.EquippableItem.OverrideShutdown);\n RenderEquippedItemDetailsSurface(item);", - "GameManager.RequestThermotoggleTargetTemperature(thermotoggle, command.TargetTemperature);\n RenderEquippedItemDetailsSurface(item);" - }; - var equippedItemTuningRedrawHits = forbiddenEquippedItemTuningRedraws - .Where(symbol => normalizedInventoryMenu.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (equippedItemTuningRedrawHits.Length > 0) - { - throw new InvalidOperationException( - "InventoryMenu still redraws equipped-item tuning surfaces immediately after daemon submission."); - } - - var forbiddenUnityAcceptanceSymbols = new[] - { - "entity?.Settings == null", - "entity.Settings == null" - }; - var unityAcceptanceHits = FindMethodScopedLineHits(actionGameManager, forbiddenUnityAcceptanceSymbols) - .Where(hit => hit.MethodName == "RequestEntityShutdownPerformance") - .Select(hit => $"ActionGameManager.cs:{hit.LineNumber}: {hit.Line.Trim()}") - .ToArray(); - if (unityAcceptanceHits.Length > 0) - { - throw new InvalidOperationException( - "Unity runtime tuning requests still reject through renderer-local settings checks instead of daemon authority: " + - string.Join(", ", unityAcceptanceHits)); - } - - var daemonOperationsPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeDaemonOperations.cs"); - var daemonOperations = File.Exists(daemonOperationsPath) - ? File.ReadAllText(daemonOperationsPath) - : throw new InvalidOperationException("Cannot verify runtime simulation tuning authority; AetheriaRuntimeDaemonOperations.cs is missing."); - var requiredDaemonValidationSymbols = new[] - { - "case AetheriaRuntimeDaemonCommandKinds.SetShutdownPerformance:", - "command.ScalarValue < 0.0 || command.ScalarValue > 1.0" - }; - var missingDaemonValidationSymbols = requiredDaemonValidationSymbols - .Where(symbol => !daemonOperations.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (missingDaemonValidationSymbols.Length > 0) - { - throw new InvalidOperationException( - "Daemon runtime tuning no longer validates shutdown-performance range: " + - string.Join(", ", missingDaemonValidationSymbols)); - } -} - -static void RequireHullConductivityRequestAuthority(string root) -{ - var inventoryPanelPath = Path.Combine(root, "Assets", "Scripts", "UI", "Menu", "InventoryPanel.cs"); - 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 (!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", - "position.x >= entity.HullConductivity.GetLength(0)", - "position.y >= entity.HullConductivity.GetLength(1)", - "axis < 0", - "axis > 1" - }; - var unityAcceptanceHits = forbiddenUnityAcceptanceSymbols - .Where(symbol => inventoryPanel.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (unityAcceptanceHits.Length > 0) - { - throw new InvalidOperationException( - "Unity hull conductivity requests still reject through renderer-local grid bounds instead of daemon authority: " + - string.Join(", ", unityAcceptanceHits)); - } - - if (inventoryPanel.Contains("public bool RequestHullConductivityToggle(", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Hull conductivity request API still exposes submission as public acceptance state."); - } - - var uiRoot = Path.Combine(root, "Assets", "Scripts", "UI"); - var hits = Directory.EnumerateFiles(uiRoot, "*.cs", SearchOption.AllDirectories) - .SelectMany(path => File.ReadLines(path) - .Select((line, index) => new { Path = path, LineNumber = index + 1, Line = line })) - .Where(line => - line.Line.Contains("HullConductivity[", StringComparison.Ordinal) && - line.Line.Contains("=", StringComparison.Ordinal) && - !line.Line.Contains("=>", StringComparison.Ordinal)) - .Select(line => $"{Path.GetRelativePath(root, line.Path)}:{line.LineNumber}: {line.Line.Trim()}") - .Take(10) - .ToArray(); - - if (hits.Length > 0) - { - throw new InvalidOperationException( - "Hull conductivity still has renderer-local UI write authority: " + - string.Join("; ", hits)); - } - - var submissionAcceptanceHits = Directory.EnumerateFiles(uiRoot, "*.cs", SearchOption.AllDirectories) - .SelectMany(path => File.ReadLines(path) - .Select((line, index) => new { Path = path, LineNumber = index + 1, Line = line })) - .Where(line => - line.Line.Contains("if (GameManager.RequestHullConductivityToggle", StringComparison.Ordinal) || - line.Line.Contains("if (RequestHullConductivityToggle", StringComparison.Ordinal) || - line.Line.Contains("RefreshCells(new []{v,int2", StringComparison.Ordinal)) - .Select(line => $"{Path.GetRelativePath(root, line.Path)}:{line.LineNumber}: {line.Line.Trim()}") - .ToArray(); - - if (submissionAcceptanceHits.Length > 0) - { - throw new InvalidOperationException( - "Hull conductivity UI still treats daemon submission as accepted cell state: " + - string.Join("; ", submissionAcceptanceHits)); - } -} - -static void RequireInventoryEntityRenameRequestAuthority(string root) -{ - var inventoryPanelPath = Path.Combine(root, "Assets", "Scripts", "UI", "Menu", "InventoryPanel.cs"); - var inventoryPanel = File.Exists(inventoryPanelPath) - ? File.ReadAllText(inventoryPanelPath) - : throw new InvalidOperationException("Cannot verify inventory entity rename authority; InventoryPanel.cs is missing."); - - if (!inventoryPanel.Contains("RequestEntityName", StringComparison.Ordinal) || - !inventoryPanel.Contains("operations => operations.SetEntityName", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Entity rename no longer has a typed daemon request primitive."); - } - - if (inventoryPanel.Contains("_displayedEntity.Name =", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "InventoryPanel still renames entities directly instead of using the typed daemon request primitive."); - } - - if (inventoryPanel.Contains("GameManager.RequestEntityName", StringComparison.Ordinal)) - { - throw new InvalidOperationException("InventoryPanel still routes entity rename through ActionGameManager instead of its typed operation client."); - } - - 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."); - } -} - -static void RequireWeaponGroupRequestAuthority(string root) -{ - var inventoryMenuPath = Path.Combine(root, "Assets", "Scripts", "UI", "Menu", "InventoryMenu.cs"); - var inventoryMenu = File.Exists(inventoryMenuPath) - ? File.ReadAllText(inventoryMenuPath) - : throw new InvalidOperationException("Cannot verify weapon group authority; InventoryMenu.cs is missing."); - - if (!inventoryMenu.Contains("RequestWeaponGroupMembership", StringComparison.Ordinal) || - !inventoryMenu.Contains("operations => operations.SetWeaponGroupMembership", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Weapon group membership no longer has a typed daemon request primitive."); - } - - if (inventoryMenu.Contains("WeaponGroupDragObject", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Weapon-group action-bar binding still keeps the dead drag-object path alive instead of routing through live gameplay APIs."); - } - - var forbiddenLocalAcceptanceSymbols = new[] - { - "item?.Entity?.WeaponGroups == null", - "groupIndex >= item.Entity.WeaponGroups.Length" - }; - var localAcceptanceHits = forbiddenLocalAcceptanceSymbols - .Where(symbol => inventoryMenu.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (localAcceptanceHits.Length > 0) - { - throw new InvalidOperationException( - "Unity weapon-group membership still rejects through renderer-local weapon/group checks instead of daemon acceptance: " + - string.Join(", ", localAcceptanceHits)); - } - - var forbiddenPublicAcceptanceApis = new[] - { - "public bool RequestWeaponGroupMembership(" - }; - var publicAcceptanceApiHits = forbiddenPublicAcceptanceApis - .Where(symbol => inventoryMenu.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (publicAcceptanceApiHits.Length > 0) - { - throw new InvalidOperationException( - "Weapon-group request APIs still expose submission as public acceptance state: " + - string.Join(", ", publicAcceptanceApiHits)); - } - - var forbiddenUiSubmissionAcceptanceSymbols = new[] - { - "if (GameManager.RequestWeaponGroupMembership(", - "Unable to toggle equipped-item weapon group membership." - }; - var uiSubmissionAcceptanceHits = forbiddenUiSubmissionAcceptanceSymbols - .Where(symbol => inventoryMenu.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (uiSubmissionAcceptanceHits.Length > 0) - { - throw new InvalidOperationException( - "InventoryMenu still treats weapon-group membership submission as accepted equipment state: " + - string.Join(", ", uiSubmissionAcceptanceHits)); - } - - var assignmentPath = Path.Combine(root, "Assets", "Scripts", "UI", "Menu", "WeaponGroupAssignment.cs"); - var elementPath = Path.Combine(root, "Assets", "Scripts", "UI", "Menu", "WeaponGroupElement.cs"); - var weaponGroupsPrefabPath = Path.Combine(root, "Assets", "Prefabs", "UI", "Properties Panel", "Fancy", "Weapon Groups.prefab"); - var weaponGroupPrefabPath = Path.Combine(root, "Assets", "Prefabs", "UI", "Properties Panel", "Fancy", "Weapon Group.prefab"); - - var survivingLegacyPaths = new[] - { - assignmentPath, - elementPath, - weaponGroupsPrefabPath, - weaponGroupPrefabPath - } - .Where(File.Exists) - .ToArray(); - - if (survivingLegacyPaths.Length > 0) - { - throw new InvalidOperationException( - "Legacy inventory weapon-group uGUI shells still survive after the Eve surface cut: " + - string.Join(", ", survivingLegacyPaths.Select(path => Path.GetRelativePath(root, path)))); - } -} - -static void RequireActionBarBindingRequestAuthority(string root) -{ - var actionGameManagerPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "ActionGameManager.cs"); - var actionGameManager = File.Exists(actionGameManagerPath) - ? File.ReadAllText(actionGameManagerPath) - : throw new InvalidOperationException("Cannot verify action-bar binding authority; ActionGameManager.cs is missing."); - var actionBarPresentationPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "AetheriaUnityActionBarPresentation.cs"); - var actionBarPresentation = File.Exists(actionBarPresentationPath) - ? File.ReadAllText(actionBarPresentationPath) - : throw new InvalidOperationException("Cannot verify action-bar binding authority; AetheriaUnityActionBarPresentation.cs is missing."); - var gameplayInputShellPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "AetheriaUnityGameplayInputShell.cs"); - var gameplayInputShell = File.Exists(gameplayInputShellPath) - ? File.ReadAllText(gameplayInputShellPath) - : throw new InvalidOperationException("Cannot verify action-bar binding authority; AetheriaUnityGameplayInputShell.cs is missing."); - var gameplaySceneWiringPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "AetheriaUnityGameplaySceneWiring.cs"); - var gameplaySceneWiring = File.Exists(gameplaySceneWiringPath) - ? File.ReadAllText(gameplaySceneWiringPath) - : throw new InvalidOperationException("Cannot verify action-bar binding authority; AetheriaUnityGameplaySceneWiring.cs is missing."); - - var requiredManagerSymbols = new[] - { - "ActionBarPresentation = _actionBarPresentation", - "SceneWiring.ConfigureActionBarPresentation(", - "ApplyActionBarBindings = _ => _actionBarPresentation?.ApplyLocalBindings()" - }; - - var missingManagerSymbols = requiredManagerSymbols - .Where(symbol => !actionGameManager.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - - if (missingManagerSymbols.Length > 0) - { - throw new InvalidOperationException( - "ActionGameManager no longer delegates action-bar binding request/restore through the action-bar presentation owner: " + - string.Join(", ", missingManagerSymbols)); - } - - if (actionGameManager.Contains("AetheriaUnityActionBarBindingAdapter", StringComparison.Ordinal) || - File.Exists(Path.Combine(root, "Assets", "Scripts", "Gameplay", "AetheriaUnityActionBarBindingAdapter.cs"))) - { - throw new InvalidOperationException( - "Action-bar binding restore must go straight to AetheriaUnityActionBarPresentation; the old adapter owned no authority."); - } - - var requiredSceneWiringSymbols = new[] - { - "public void ConfigureActionBarPresentation(", - "gameplayInputShell?.ActionBarSlots ?? Array.Empty()", - "actionBarPresentation?.Bind(", - "resolveActionBarControl);" - }; - var missingSceneWiringSymbols = requiredSceneWiringSymbols - .Where(symbol => !gameplaySceneWiring.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (missingSceneWiringSymbols.Length > 0) - { - throw new InvalidOperationException( - "Unity scene wiring no longer injects action-bar slots into the action-bar presentation owner: " + - string.Join(", ", missingSceneWiringSymbols)); - } - - var requiredGameplayInputShellSymbols = new[] - { - "private void RegisterActionBarInput()", - "private void CreateActionBarSlot(string controlPath)", - "var action = new InputAction(binding: controlPath);", - "_actionBarActions.Add(action);", - "_actionBarSlots.Add(slot);", - "DragSession.RegisterTarget(dragAction => ActionBarPresentation.RequestBinding(slot, dragAction));", - "slot.PointerExitTrigger.OnPointerExitAsObservable().Subscribe(_ => DragSession.UnregisterTarget());" - }; - var missingGameplayInputShellSymbols = requiredGameplayInputShellSymbols - .Where(symbol => !gameplayInputShell.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (missingGameplayInputShellSymbols.Length > 0) - { - throw new InvalidOperationException( - "Unity gameplay input shell no longer owns local action-bar control binding and drag target wiring: " + - string.Join(", ", missingGameplayInputShellSymbols)); - } - - var requiredPresentationSymbols = new[] - { - "public void RequestBinding(ActionBarSlot slot, DragObject dragAction)", - "public bool RequestWeaponGroupBinding(int slotIndex, int groupIndex)", - "public bool ClearBinding(int slotIndex)", - "public void ApplyLocalBindings()", - "public void ApplyBindings(", - "private AetheriaUnityActionBarBinding CreateBindingCommit(", - "private ActionBarBinding CreateBinding(", - "private void SetLocalBinding(AetheriaUnityActionBarBinding binding)", - "_localBindings = (_localBindings ?? Array.Empty())", - "SetLocalBinding(binding);", - "ApplyLocalBindings();" - }; - - var missingPresentationSymbols = requiredPresentationSymbols - .Where(symbol => !actionBarPresentation.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - - if (missingPresentationSymbols.Length > 0) - { - throw new InvalidOperationException( - "Action-bar presentation no longer owns typed daemon drag/drop binding requests and observed restore lowering: " + - string.Join(", ", missingPresentationSymbols)); - } - - var forbiddenPresentationSymbols = new[] - { - "_resolveClient()?.Operations.SetActionBarBinding(", - "_resolveClient()?.Operations.ClearActionBarBinding(", - "Failed to send Aetheria daemon action-bar binding operation", - "Failed to send Aetheria daemon action-bar clear operation" - }; - var presentationHits = forbiddenPresentationSymbols - .Where(symbol => actionBarPresentation.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (presentationHits.Length > 0) - { - throw new InvalidOperationException( - "Action-bar presentation still treats local input bindings as daemon gameplay operations: " + - string.Join(", ", presentationHits)); - } - - var forbiddenSymbols = new[] - { - "var newbinds = Enumerable.Range(0, 64)", - ".Zip(\r\n bindings,", - "groupIndex >= CurrentEntity.WeaponGroups.Length", - "ApplyDefaultActionBarBindings", - "ProjectActionBarBindings", - "ProjectActionBarBinding(slot, slot?.Binding)", - "Enumerable.Range(0, CurrentEntity.WeaponGroups.Length)", - "slot == null || groupIndex < 0", - "RequestActionBarBinding(", - "CreateActionBarBinding(", - "CreateActionBarBindingCommit(", - "ResolveActionBarClient()?.Operations.SetActionBarBinding(" - }; - - var legacyHits = forbiddenSymbols - .Where(symbol => actionGameManager.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - - if (legacyHits.Length > 0) - { - throw new InvalidOperationException( - "Action-bar binding still contains the old unconditional weapon-group overwrite path: " + - string.Join(", ", legacyHits)); - } - - var forbiddenPublicAcceptanceApis = new[] - { - "public bool RequestWeaponGroupActionBarBinding(", - "public bool RequestClearActionBarBinding(", - "TryRequestDaemonActionBarBinding(", - "TryRequestDaemonActionBarBindingClear(", - "public bool TryRequestDaemonActionBarConsumable(", - "public bool TryRequestDaemonActionBarBehavior(", - "public bool TryRequestDaemonActionBarWeaponGroup(" - }; - var publicAcceptanceApiHits = forbiddenPublicAcceptanceApis - .Where(symbol => actionGameManager.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (publicAcceptanceApiHits.Length > 0) - { - throw new InvalidOperationException( - "ActionBar binding request APIs still expose submission as public acceptance state: " + - string.Join(", ", publicAcceptanceApiHits)); - } - - var inventoryMenuPath = Path.Combine(root, "Assets", "Scripts", "UI", "Menu", "InventoryMenu.cs"); - var inventoryMenu = File.Exists(inventoryMenuPath) - ? File.ReadAllText(inventoryMenuPath) - : throw new InvalidOperationException("Cannot verify action-bar binding authority; InventoryMenu.cs is missing."); - var requiredInventoryActionBarSymbols = new[] - { - "SetActionBarPresentation(AetheriaUnityActionBarPresentation actionBarPresentation)" - }; - var missingInventoryActionBarSymbols = requiredInventoryActionBarSymbols - .Where(symbol => !inventoryMenu.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (missingInventoryActionBarSymbols.Length > 0) - { - throw new InvalidOperationException( - "InventoryMenu no longer owns typed equipped-item action-bar binding operations: " + - string.Join(", ", missingInventoryActionBarSymbols)); - } - var forbiddenInventoryActionBarSymbols = new[] - { - "ProjectEquippedItemActionBarSlots(", - "RequestWeaponGroupActionBarBinding(", - "RequestClearActionBarBinding(", - "_actionBarPresentation?.RequestWeaponGroupBinding(slotIndex, groupIndex)", - "_actionBarPresentation?.ClearBinding(slotIndex)", - "operations => operations.SetActionBarBinding(", - "operations => operations.ClearActionBarBinding(controlPath)", - "SetActionBarBinding(", - "ClearActionBarBinding(controlPath)" - }; - var inventoryActionBarHits = forbiddenInventoryActionBarSymbols - .Where(symbol => inventoryMenu.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (inventoryActionBarHits.Length > 0) - { - throw new InvalidOperationException( - "InventoryMenu still sends local action-bar binding edits to the daemon: " + - string.Join(", ", inventoryActionBarHits)); - } - var forbiddenUiAcceptanceSymbols = new[] - { - "GameManager.RequestWeaponGroupActionBarBinding(command.SlotIndex, command.GroupIndex))", - "GameManager.RequestClearActionBarBinding(command.SlotIndex))", - "Unable to bind equipped-item weapon group to action bar.", - "Unable to clear equipped-item action bar binding." - }; - var uiAcceptanceHits = forbiddenUiAcceptanceSymbols - .Where(symbol => inventoryMenu.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (uiAcceptanceHits.Length > 0) - { - throw new InvalidOperationException( - "InventoryMenu still treats equipped-item action-bar command submission as accepted binding state: " + - string.Join(", ", uiAcceptanceHits)); - } - - var actionBarSlotPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "ActionBarSlot.cs"); - var actionBarSlot = File.Exists(actionBarSlotPath) - ? File.ReadAllText(actionBarSlotPath) - : throw new InvalidOperationException("Cannot verify action-bar activation authority; ActionBarSlot.cs is missing."); - var forbiddenSlotSymbols = new[] - { - "TryRequestDaemonActionBarConsumable(", - "TryRequestDaemonActionBarBehavior(", - "TryRequestDaemonActionBarWeaponGroup(" - }; - var slotHits = forbiddenSlotSymbols - .Where(symbol => actionBarSlot.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (slotHits.Length > 0) - { - throw new InvalidOperationException( - "ActionBarSlot still calls daemon transport helpers directly instead of submit-only request APIs: " + - string.Join(", ", slotHits)); - } - - if (actionBarSlot.Contains("ActionGameManager.Instance", StringComparison.Ordinal) || - !actionBarSlot.Contains("protected GameSettings Settings { get; }", StringComparison.Ordinal) || - !actionBarPresentation.Contains("new ActionBarGearBinding(entity, slot, _resolveControl, _settings,", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "ActionBarSlot must receive icon presentation settings explicitly and submit through a narrow control handle."); - } -} - -static void RequireInventoryDoubleClickTransferRequestAuthority(string root) -{ - var inventoryPanelPath = Path.Combine(root, "Assets", "Scripts", "UI", "Menu", "InventoryPanel.cs"); - var inventoryPanel = File.Exists(inventoryPanelPath) - ? File.ReadAllText(inventoryPanelPath) - : throw new InvalidOperationException("Cannot verify inventory transfer authority; InventoryPanel.cs is missing."); - - var inventoryMenuPath = Path.Combine(root, "Assets", "Scripts", "UI", "Menu", "InventoryMenu.cs"); - var inventoryMenu = File.Exists(inventoryMenuPath) - ? File.ReadAllText(inventoryMenuPath) - : throw new InvalidOperationException("Cannot verify inventory transfer authority; InventoryMenu.cs is missing."); - - var actionGameManagerPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "ActionGameManager.cs"); - if (!File.Exists(actionGameManagerPath)) - { - throw new InvalidOperationException("Cannot verify inventory transfer authority; ActionGameManager.cs is missing."); - } - - 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", "AetheriaRuntimeDaemonGameSurfaceBuilder.cs"); - var panelSurface = File.Exists(panelSurfacePath) - ? File.ReadAllText(panelSurfacePath) - : throw new InvalidOperationException("Cannot verify inventory transfer authority; AetheriaRuntimeDaemonGameSurfaceBuilder.cs is missing."); - var surfaceCatalogPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeEveSurfaceCatalog.cs"); - var surfaceCatalog = File.Exists(surfaceCatalogPath) - ? File.ReadAllText(surfaceCatalogPath) - : throw new InvalidOperationException("Cannot verify inventory transfer authority; AetheriaRuntimeEveSurfaceCatalog.cs is missing."); - - var requiredDaemonRequests = new[] - { - "TransferCargoItem(", - "EquipItem(", - "StoreItem(", - "AetheriaRuntimeDaemonCommandKinds.TransferCargoItem", - "AetheriaRuntimeDaemonCommandKinds.EquipItem", - "AetheriaRuntimeDaemonCommandKinds.StoreItem" - }; - - var missingDaemonRequests = requiredDaemonRequests - .Where(symbol => !operationClient.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - - if (missingDaemonRequests.Length > 0) - { - throw new InvalidOperationException( - "Inventory transfer no longer has complete typed daemon operation primitives: " + - string.Join(", ", missingDaemonRequests)); - } - - var requiredSurfaceSymbols = new[] - { - "DockedRefitPanel(", - "\"inventory.grid\"", - "\"inventory.item\"", - "\"itemKey\"", - "\"sourceKind\"", - "\"sourceEntityKey\"", - "\"sourceIndex\"", - "\"targetKind\"", - "\"targetEntityKey\"", - "\"targetIndex\"", - "\"dropCommand.cargo\"", - "\"dropCommand.equipment\"", - "\"x\"", - "\"y\"", - "\"quantity\"", - "SurfaceCommand(AetheriaRuntimeDaemonCommandKinds.TransferCargoItem)", - "SurfaceCommand(AetheriaRuntimeDaemonCommandKinds.EquipItem)", - "SurfaceCommand(AetheriaRuntimeDaemonCommandKinds.StoreItem)" - }; - 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)); - } - if (surfaceCatalog.Contains( - "Surface(AetheriaRuntimeInventoryPanelSurfaceBuilder.SurfaceId", - StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "The request-local Unity inventory panel is still advertised as a player gameplay surface."); - } - - var forbiddenUnityInventoryAcceptanceSymbols = new[] - { - "TryFindSpace(", - "ItemFits(", - "Cargo.ContainsKey(", - "Equipment.Contains(", - "origin == destination", - "ReferenceEquals(equippedItem.Entity, origin)", - "!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 UI still owns inventory cell interpretation, transfer validation, or transfer submission: " + - string.Join(", ", unityInventoryAcceptanceHits)); - } - - var forbiddenSymbols = new[] - { - ".DropItem(", - ".CargoBay.Remove(", - ".TryUnequip(", - ".TryEquip(", - ".TryStore(", - ".OriginInventory.Remove(" - }; - - var hits = new[] { inventoryMenuPath, inventoryPanelPath } - .SelectMany(path => File.ReadLines(path) - .Select((line, index) => new { Path = path, LineNumber = index + 1, Line = line })) - .Where(line => forbiddenSymbols.Any(symbol => line.Line.Contains(symbol, StringComparison.Ordinal))) - .Select(line => $"{Path.GetRelativePath(root, line.Path)}:{line.LineNumber}: {line.Line.Trim()}") - .ToArray(); - - if (hits.Length > 0) - { - throw new InvalidOperationException( - "Inventory UI still owns direct inventory mutation: " + - string.Join("; ", hits)); - } - - var forbiddenInventoryUiAcceptanceSymbols = new[] - { - ".ItemFits(", - ".TryFindSpace(" - }; - var inventoryUiAcceptanceHits = File.ReadLines(inventoryPanelPath) - .Select((line, index) => new { LineNumber = index + 1, Line = line }) - .Where(line => forbiddenInventoryUiAcceptanceSymbols.Any(symbol => line.Line.Contains(symbol, StringComparison.Ordinal))) - .Select(line => $"{Path.GetRelativePath(root, inventoryPanelPath)}:{line.LineNumber}: {line.Line.Trim()}") - .ToArray(); - if (inventoryUiAcceptanceHits.Length > 0) - { - throw new InvalidOperationException( - "InventoryPanel still rejects transfer placement through renderer-local fit checks instead of daemon authority: " + - string.Join("; ", inventoryUiAcceptanceHits)); - } - - var forbiddenUnknownOriginSymbols = new[] { "int.MinValue" }; - var unknownOriginHits = new[] { inventoryMenuPath, inventoryPanelPath } - .SelectMany(path => File.ReadLines(path) - .Select((line, index) => new { Path = path, LineNumber = index + 1, Line = line })) - .Where(line => forbiddenUnknownOriginSymbols.Any(symbol => line.Line.Contains(symbol, StringComparison.Ordinal))) - .Select(line => $"{Path.GetRelativePath(root, line.Path)}:{line.LineNumber}: {line.Line.Trim()}") - .ToArray(); - if (unknownOriginHits.Length > 0) - { - throw new InvalidOperationException( - "Inventory cargo/equipment submissions still send unknown origin coordinates instead of typed slot positions: " + - string.Join("; ", unknownOriginHits)); - } - - var forbiddenManagerCommandTargetSymbols = new[] - { - "TryResolveObservedCargoBayCommandTarget", - "TryResolveObservedEquippedItemCommandTarget", - "TryResolveCargoBayCommandTarget", - "TryResolveEquippedItemCommandTarget" - }; - var managerCommandTargetHits = new[] { actionGameManagerPath, inventoryMenuPath, inventoryPanelPath } - .SelectMany(path => File.ReadLines(path) - .Select((line, index) => new { Path = path, LineNumber = index + 1, Line = line })) - .Where(line => forbiddenManagerCommandTargetSymbols.Any(symbol => line.Line.Contains(symbol, StringComparison.Ordinal))) - .Select(line => $"{Path.GetRelativePath(root, line.Path)}:{line.LineNumber}: {line.Line.Trim()}") - .ToArray(); - if (managerCommandTargetHits.Length > 0) - { - throw new InvalidOperationException( - "Inventory cargo/equipment submissions must not depend on ActionGameManager command-target adapters: " + - string.Join("; ", managerCommandTargetHits)); - } - - var forbiddenPublicAcceptanceApis = new[] - { - "public bool RequestCargoItemTransfer(", - "public bool RequestCargoItemEquip(", - "public bool RequestEquippedItemStore(", - "public bool RequestEquippedItemEquip(", - "private bool RequestDraggedItemToEntity(", - "private bool RequestDraggedItemToCargo(", - "private bool RequestCargoItemTransfer(", - "private bool RequestEquippedItemTransfer(", - "public bool EndDrag(", - "Func", - "RegisterDragTarget(Func" - }; - var publicAcceptanceApiHits = new[] { inventoryMenuPath, inventoryPanelPath } - .SelectMany(path => File.ReadLines(path) - .Select((line, index) => new { Path = path, LineNumber = index + 1, Line = line })) - .Where(line => forbiddenPublicAcceptanceApis.Any(symbol => line.Line.Contains(symbol, StringComparison.Ordinal))) - .Select(line => $"{Path.GetRelativePath(root, line.Path)}:{line.LineNumber}: {line.Line.Trim()}") - .ToArray(); - if (publicAcceptanceApiHits.Length > 0) - { - throw new InvalidOperationException( - "Inventory drag/request APIs still expose submission as public acceptance state: " + - string.Join("; ", publicAcceptanceApiHits)); - } -} - -static void RequireTradePurchaseRequestAuthority(string root) -{ - var tradeMenuPath = Path.Combine(root, "Assets", "Scripts", "UI", "Menu", "TradeMenu.cs"); - var tradeMenu = File.Exists(tradeMenuPath) - ? File.ReadAllText(tradeMenuPath) - : throw new InvalidOperationException("Cannot verify trade purchase authority; TradeMenu.cs is missing."); - - var requiredRequests = new[] - { - "RequestTradePurchase", - "operations => operations.TradePurchase(" - }; - - var missingRequests = requiredRequests - .Where(symbol => !tradeMenu.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - - if (missingRequests.Length > 0) - { - throw new InvalidOperationException( - "Trade purchase no longer has a typed daemon request primitive: " + - string.Join(", ", missingRequests)); - } - - var forbiddenUnityTradeAcceptanceSymbols = new[] - { - "price > Credits", - "totalPrice > Credits", - "targetCargo.TryFindSpace(" - }; - var unityTradeAcceptanceHits = forbiddenUnityTradeAcceptanceSymbols - .Where(symbol => tradeMenu.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (unityTradeAcceptanceHits.Length > 0) - { - throw new InvalidOperationException( - "Unity trade/loadout request code still rejects requests through renderer-local credits or cargo-capacity checks: " + - string.Join(", ", unityTradeAcceptanceHits)); - } - - var daemonOperationsPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeDaemonOperations.cs"); - var daemonOperations = File.Exists(daemonOperationsPath) - ? File.ReadAllText(daemonOperationsPath) - : throw new InvalidOperationException("Cannot verify trade purchase authority; daemon operations are missing."); - var requiredDaemonShipPurchaseSymbols = new[] - { - "ApplyCreateDockedShipPurchase(", - "var createsDockedShip = !string.IsNullOrWhiteSpace(typedItem.HullType)", - "ApplyCreateDockedShipPurchase(run, dockParentKey, itemKey, out var purchasedShipKey)", - "run.CurrentEntityKey = purchasedShipKey", - "HullItemKey = itemKey" - }; - var missingDaemonShipPurchaseSymbols = requiredDaemonShipPurchaseSymbols - .Where(symbol => !daemonOperations.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (missingDaemonShipPurchaseSymbols.Length > 0) - { - throw new InvalidOperationException( - "Daemon trade purchase no longer materializes purchased docked ships as typed run state: " + - string.Join(", ", missingDaemonShipPurchaseSymbols)); - } - - var forbiddenSymbols = new[] - { - "GameManager.Credits -=", - "Inventory.TryTransferItem(", - "new Ship(", - ".SetParent(GameManager.DockedEntity)" - }; - - var hits = File.ReadLines(tradeMenuPath) - .Select((line, index) => new { LineNumber = index + 1, Line = line }) - .Where(line => forbiddenSymbols.Any(symbol => line.Line.Contains(symbol, StringComparison.Ordinal))) - .Select(line => $"{Path.GetRelativePath(root, tradeMenuPath)}:{line.LineNumber}: {line.Line.Trim()}") - .ToArray(); - - if (hits.Length > 0) - { - throw new InvalidOperationException( - "TradeMenu still owns direct purchase mutation: " + - string.Join("; ", hits)); - } - - var forbiddenLocalAcceptanceSymbols = new[] - { - "price > GameManager.Credits", - "totalPrice > GameManager.Credits", - "GameManager.Credits / price", - "min(quantity, simpleCommodity.Quantity)", - "quantity = min(q, simpleCommodity.Quantity)", - "\"Insufficient Credits!\"", - "\"Insufficient Cargo Space!\"", - "\"Unable to create ship!\"" - }; - - var localAcceptanceHits = File.ReadLines(tradeMenuPath) - .Select((line, index) => new { LineNumber = index + 1, Line = line }) - .Where(line => forbiddenLocalAcceptanceSymbols.Any(symbol => line.Line.Contains(symbol, StringComparison.Ordinal))) - .Select(line => $"{Path.GetRelativePath(root, tradeMenuPath)}:{line.LineNumber}: {line.Line.Trim()}") - .ToArray(); - - if (localAcceptanceHits.Length > 0) - { - throw new InvalidOperationException( - "TradeMenu still rejects purchases through renderer-local acceptance checks instead of daemon authority: " + - string.Join("; ", localAcceptanceHits)); - } - - if (tradeMenu.Contains("GameManager.RequestTradePurchase", StringComparison.Ordinal)) - { - throw new InvalidOperationException("TradeMenu still routes purchases through ActionGameManager instead of its typed operation client."); - } - - var forbiddenSubmissionAcceptanceSymbols = new[] - { - "if (!GameManager.RequestTradePurchase(", - "\"Purchase request rejected!\"", - "public bool RequestTradePurchase(" - }; - var submissionAcceptanceHits = new[] { tradeMenuPath } - .SelectMany(path => File.ReadLines(path) - .Select((line, index) => new { Path = path, LineNumber = index + 1, Line = line })) - .Where(line => forbiddenSubmissionAcceptanceSymbols.Any(symbol => line.Line.Contains(symbol, StringComparison.Ordinal))) - .Select(line => $"{Path.GetRelativePath(root, line.Path)}:{line.LineNumber}: {line.Line.Trim()}") - .ToArray(); - - if (submissionAcceptanceHits.Length > 0) - { - throw new InvalidOperationException( - "TradeMenu still treats daemon purchase submission as accepted or rejected purchase state: " + - string.Join("; ", submissionAcceptanceHits)); - } - - var firstTradeRequest = tradeMenu.IndexOf("RequestTradePurchase(", StringComparison.Ordinal); - var firstCreditRefreshAfterRequest = tradeMenu.IndexOf("UpdateCreditsLabel();", firstTradeRequest, StringComparison.Ordinal); - if (firstTradeRequest >= 0 && firstCreditRefreshAfterRequest >= 0) - { - throw new InvalidOperationException( - "TradeMenu still refreshes projected credits immediately after submitting a daemon purchase request instead of waiting for daemon-observed state."); - } -} - -static void RequireTradeSaleAuthority(string root) -{ - var runtime = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime"); - var documents = File.ReadAllText(Path.Combine(runtime, "AetheriaRuntimeDaemonDocuments.cs")); - var operations = File.ReadAllText(Path.Combine(runtime, "AetheriaRuntimeDaemonOperations.cs")); - var operationClient = File.ReadAllText(Path.Combine(runtime, "AetheriaRuntimeDaemonOperationClient.cs")); - var surfaceClient = File.ReadAllText(Path.Combine(runtime, "AetheriaRuntimeDaemonOperationsClient.cs")); - var inputCapabilities = File.ReadAllText(Path.Combine(runtime, "AetheriaRuntimeInputCapabilityDocuments.cs")); - - var required = new Dictionary - { - ["command contract"] = - [ - "TradeSale", - "public sealed class AetheriaRuntimeTradeSaleCommand", - "public AetheriaRuntimeTradeSaleCommand TradeSale" - ], - ["daemon transaction"] = - [ - "ApplyTradeSale(run, command, context)", - "AetheriaRuntimeRefitTransactions.TryTransferCargo(", - "run.Credits += (int)payout", - "TradeStationNoFit" - ], - ["operation client"] = - [ - "internal AetheriaRuntimeDaemonCommandEnvelope TradeSale(", - "command.TradeSale.ItemKey", - "command.TradeSale.Quantity" - ], - ["generic Eve translation"] = - [ - "public CultMeshOperationReceipt TradeSale(", - "AetheriaRuntimeDaemonCommandKinds.TradeSale => client.TradeSale(" - ], - ["advertised gameplay lever"] = - [ - "trade.sell.", - "Sell {typedItem.Name}", - "\"TradeSale\"" - ] - }; - var sources = new Dictionary - { - ["command contract"] = documents, - ["daemon transaction"] = operations, - ["operation client"] = operationClient, - ["generic Eve translation"] = surfaceClient, - ["advertised gameplay lever"] = inputCapabilities - }; - var missing = required - .SelectMany(group => group.Value - .Where(symbol => !sources[group.Key].Contains(symbol, StringComparison.Ordinal)) - .Select(symbol => $"{group.Key}: {symbol}")) - .ToArray(); - if (missing.Length > 0) - { - throw new InvalidOperationException( - "Docked sale no longer crosses the complete daemon/Eve authority boundary: " + - string.Join(", ", missing)); - } - - var forbiddenClientOpinions = new[] - { - "TradeSale.UnitPrice", - "TradeSale.TotalPrice", - "TradeSale.StationEntityKey", - "TradeSale.StationCargoIndex" - }; - var clientOpinionHits = forbiddenClientOpinions - .Where(symbol => operationClient.Contains(symbol, StringComparison.Ordinal) || - surfaceClient.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (clientOpinionHits.Length > 0) - { - throw new InvalidOperationException( - "Trade sale client still supplies daemon-owned station or payout opinions: " + - string.Join(", ", clientOpinionHits)); - } - - Console.WriteLine( - "Trade sale authority: generic Eve advertises source-stack intent; daemon owns station placement and payout"); -} - -static void RequireLootPickupRequestAuthority(string root) -{ - var actionGameManagerPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "ActionGameManager.cs"); - var actionGameManager = File.Exists(actionGameManagerPath) - ? File.ReadAllText(actionGameManagerPath) - : throw new InvalidOperationException("Cannot verify loot pickup authority; ActionGameManager.cs is missing."); - - if (actionGameManager.Contains("CommitLootPickup", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Loot pickup should not have a Unity-owned gameplay mutation primitive; daemon snapshots own pickup state."); - } - - var shieldManagerPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "ShieldManager.cs"); - var shieldManager = File.Exists(shieldManagerPath) - ? File.ReadAllText(shieldManagerPath) - : throw new InvalidOperationException("Cannot verify loot pickup authority; ShieldManager.cs is missing."); - - var forbiddenSymbols = new[] - { - ".TryStore(", - ".CargoBays.Any(" - }; - - var hits = File.ReadLines(shieldManagerPath) - .Select((line, index) => new { LineNumber = index + 1, Line = line }) - .Where(line => forbiddenSymbols.Any(symbol => line.Line.Contains(symbol, StringComparison.Ordinal))) - .Select(line => $"{Path.GetRelativePath(root, shieldManagerPath)}:{line.LineNumber}: {line.Line.Trim()}") - .ToArray(); - - if (hits.Length > 0) - { - throw new InvalidOperationException( - "ShieldManager still owns direct loot cargo mutation: " + - string.Join("; ", hits)); - } - - if (shieldManager.Contains("ActionGameManager.Instance.CommitLootPickup", StringComparison.Ordinal)) - throw new InvalidOperationException("ShieldManager still routes loot pickup through Unity gameplay authority."); -} - -static void RequireEntityDestroyedRequestAuthority(string root) -{ - var actionGameManagerPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "ActionGameManager.cs"); - var actionGameManager = File.Exists(actionGameManagerPath) - ? File.ReadAllText(actionGameManagerPath) - : throw new InvalidOperationException("Cannot verify entity destruction authority; ActionGameManager.cs is missing."); - - if (actionGameManager.Contains("CommitEntityDestroyed", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Entity destruction should not have a Unity-owned gameplay mutation primitive; daemon frames own destruction and drops."); - } - - var entityInstancePath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "EntityInstance.cs"); - var entityInstance = File.Exists(entityInstancePath) - ? File.ReadAllText(entityInstancePath) - : throw new InvalidOperationException("Cannot verify entity destruction authority; EntityInstance.cs is missing."); - - var forbiddenSymbols = new[] - { - "ZoneRenderer.DropItem(", - "entity.Zone.Entities.Remove(entity)", - "Random.onUnitSphere", - "LootDropProbability" - }; - - var hits = File.ReadLines(entityInstancePath) - .Select((line, index) => new { LineNumber = index + 1, Line = line }) - .Where(line => forbiddenSymbols.Any(symbol => line.Line.Contains(symbol, StringComparison.Ordinal))) - .Select(line => $"{Path.GetRelativePath(root, entityInstancePath)}:{line.LineNumber}: {line.Line.Trim()}") - .ToArray(); - - if (hits.Length > 0) - { - throw new InvalidOperationException( - "EntityInstance still owns direct destruction/drop mutation: " + - string.Join("; ", hits)); - } - - if (entityInstance.Contains("ActionGameManager.Instance?.CommitEntityDestroyed", StringComparison.Ordinal)) - throw new InvalidOperationException("EntityInstance still routes destruction through Unity gameplay authority."); - - var authorityPolicyPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeVerseAuthorityPolicy.cs"); - var authorityPolicy = File.Exists(authorityPolicyPath) - ? File.ReadAllText(authorityPolicyPath) - : throw new InvalidOperationException("Cannot verify entity destruction authority; Verse authority policy is missing."); - if (authorityPolicy.Contains("case AetheriaRuntimeDaemonCommandKinds.DestroyEntity:", StringComparison.Ordinal)) - throw new InvalidOperationException("The retired DestroyEntity value still participates in command claim or subject routing."); -} - -static void RequireDroppedPickupCheckpointState(string root) -{ - var requiredFiles = new Dictionary - { - [Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeSnapshotDocuments.cs")] = new[] - { - "AetheriaRuntimeDroppedPickupCommit", - "DroppedPickups", - "public double Temperature { get; set; }" - }, - [Path.Combine(root, "Aetheria.State", "Documents", "AetheriaRuntimeStateDocuments.cs")] = new[] - { - "AetheriaDroppedPickupSnapshot", - "DroppedPickups", - "public double Temperature { get; set; }" - }, - [Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeSnapshotDocuments.cs")] = new[] - { - "AetheriaRuntimeDroppedPickupCommit", - "public IReadOnlyList DroppedPickups", - "public double Temperature { get; set; }" - }, - [Path.Combine(root, "Assets", "Scripts", "Gameplay", "ActionGameManager.cs")] = new[] - { - "private AetheriaUnityLoadoutItemFactory _loadoutItemFactory", - "_loadoutItemFactory = boot.LoadoutItemFactory" - }, - [Path.Combine(root, "Assets", "Scripts", "Gameplay", "AetheriaUnityGameplayBootShell.cs")] = new[] - { - "public sealed class AetheriaUnityGameplayBootShell", - "ZoneRenderer.SetDroppedPickupItemFactory(loadoutItemFactory.CreateLoadoutItem)" - }, - [Path.Combine(root, "Assets", "Scripts", "Gameplay", "AetheriaUnityObservedFrameApplier.cs")] = new[] - { - "public sealed class AetheriaUnityObservedFrameApplier", - "zoneRenderer?.RestoreDroppedPickupsFromZoneRender(render)" - }, - [Path.Combine(root, "Assets", "Scripts", "Gameplay", "AetheriaUnityLoadoutItemFactory.cs")] = new[] - { - "public sealed class AetheriaUnityLoadoutItemFactory", - "public ItemInstance CreateLoadoutItem(AetheriaRuntimeLoadoutItemCommit item)", - "_itemManager.CreateSimpleCommodityInstance", - "_itemManager.CreateCraftedInstance" - }, - [Path.Combine(root, "Assets", "Scripts", "Zone Display", "ZoneRenderer.cs")] = new[] - { - "public void SetDroppedPickupItemFactory(Func createDroppedPickupItem)", - "public void RestoreDroppedPickupsFromZoneRender(AetheriaRuntimeZoneRenderDocument render)", - "_createDroppedPickupItem?.Invoke(pickup.Item)", - "AetheriaRuntimeDroppedPickupCommit", - "DroppedPickups", - "DropItem(", - "ActiveLoot" - } - }; - - var missing = requiredFiles - .SelectMany(pair => - { - if (!File.Exists(pair.Key)) - return new[] { $"{Path.GetRelativePath(root, pair.Key)}: missing file" }; - - var text = File.ReadAllText(pair.Key); - return pair.Value - .Where(symbol => !text.Contains(symbol, StringComparison.Ordinal)) - .Select(symbol => $"{Path.GetRelativePath(root, pair.Key)}: missing {symbol}"); - }) - .ToArray(); - - if (missing.Length > 0) - { - throw new InvalidOperationException( - "Dropped pickup checkpoint state is incomplete: " + - string.Join("; ", missing)); - } - - var actionGameManagerPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "ActionGameManager.cs"); - var actionGameManager = File.Exists(actionGameManagerPath) - ? File.ReadAllText(actionGameManagerPath) - : throw new InvalidOperationException("Cannot verify dropped pickup state; ActionGameManager.cs is missing."); - var forbiddenManagerSymbols = new[] - { - "ZoneRenderer.DropItem(", - "AetheriaRuntimeDroppedPickupCommit", - "ClearRenderedLoot(" - }; - var managerHits = forbiddenManagerSymbols - .Where(symbol => actionGameManager.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (managerHits.Length > 0) - { - throw new InvalidOperationException( - "ActionGameManager still lowers daemon dropped pickups into Unity loot instead of delegating renderer presentation: " + - string.Join(", ", managerHits)); - } -} - -static void RequireInventoryLoadoutSaveRequestAuthority(string root) -{ - var actionGameManagerPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "ActionGameManager.cs"); - var actionGameManager = File.Exists(actionGameManagerPath) - ? File.ReadAllText(actionGameManagerPath) - : throw new InvalidOperationException("Cannot verify loadout save authority; ActionGameManager.cs is missing."); - var eveCommandDocumentPath = Path.Combine( - root, - "Packages", - "org.gamecult.aetheria.state", - "Runtime", - "AetheriaRuntimeEveCommandDocument.cs"); - var loadoutCommandsPath = Path.Combine( - root, - "Packages", - "org.gamecult.aetheria.state", - "Runtime", - "AetheriaRuntimeLoadoutTemplateCommands.cs"); - var eveBridgePath = Path.Combine(root, "Aetheria.State", "AetheriaEveCommandBridge.cs"); - var runtimeStateMapperPath = Path.Combine(root, "Aetheria.State", "AetheriaRuntimeStateMapper.cs"); - var inventoryPanelPath = Path.Combine(root, "Assets", "Scripts", "UI", "Menu", "InventoryPanel.cs"); - var loadoutProjectorPath = Path.Combine(root, "Assets", "Scripts", "ServerShared", "AetheriaRuntimeLoadoutProjector.cs"); - var snapshotDocumentsPath = Path.Combine( - root, - "Packages", - "org.gamecult.aetheria.state", - "Runtime", - "AetheriaRuntimeSnapshotDocuments.cs"); - var loadoutSnapshotProjectorPath = Path.Combine( - root, - "Packages", - "org.gamecult.aetheria.state", - "Runtime", - "AetheriaRuntimeLoadoutSnapshotProjector.cs"); - var clientStatePath = Path.Combine( - root, - "Packages", - "org.gamecult.aetheria.state", - "Runtime", - "AetheriaClientState.cs"); - var dragSessionPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "AetheriaUnityDragSession.cs"); - var dragObjectsPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "AetheriaUnityDragObjects.cs"); - var gameplaySceneWiringPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "AetheriaUnityGameplaySceneWiring.cs"); - - if (File.Exists(loadoutProjectorPath)) - { - throw new InvalidOperationException( - "Legacy AetheriaRuntimeLoadoutProjector must stay deleted; loadout-template saves should compose typed commits from managed daemon-frame documents."); - } - - var requiredFiles = new[] { eveCommandDocumentPath, loadoutCommandsPath, eveBridgePath, runtimeStateMapperPath, inventoryPanelPath, snapshotDocumentsPath, clientStatePath, dragSessionPath, gameplaySceneWiringPath }; - var missingFiles = requiredFiles.Where(path => !File.Exists(path)).ToArray(); - if (missingFiles.Length > 0) - { - throw new InvalidOperationException( - "Cannot verify loadout save authority; missing files: " + - string.Join(", ", missingFiles.Select(path => Path.GetRelativePath(root, path)))); - } - - var eveCommandDocument = File.ReadAllText(eveCommandDocumentPath); - var loadoutCommands = File.ReadAllText(loadoutCommandsPath); - var eveBridge = File.ReadAllText(eveBridgePath); - var runtimeStateMapper = File.ReadAllText(runtimeStateMapperPath); - var inventoryPanel = File.ReadAllText(inventoryPanelPath); - var snapshotDocuments = File.ReadAllText(snapshotDocumentsPath); - var clientState = File.ReadAllText(clientStatePath); - var dragSession = File.ReadAllText(dragSessionPath); - var gameplaySceneWiring = File.ReadAllText(gameplaySceneWiringPath); - var dragObjects = File.Exists(dragObjectsPath) - ? File.ReadAllText(dragObjectsPath) - : throw new InvalidOperationException("Cannot verify inventory drag/drop authority; AetheriaUnityDragObjects.cs is missing."); - - if (actionGameManager.Contains("ProjectLoadoutTemplate(Entity entity)", StringComparison.Ordinal) || - inventoryPanel.Contains("ActionGameManager.ProjectLoadoutTemplate", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Loadout-template projection must not live on ActionGameManager; callers should use the managed daemon-frame snapshot path."); - } - - if (actionGameManager.Contains("public DragObject DragObject", StringComparison.Ordinal) || - actionGameManager.Contains("public bool HasDragTarget", StringComparison.Ordinal) || - actionGameManager.Contains("_dragObject", StringComparison.Ordinal) || - actionGameManager.Contains("_endDragCallback", StringComparison.Ordinal) || - actionGameManager.Contains("public abstract class DragObject", StringComparison.Ordinal) || - actionGameManager.Contains("public abstract class ItemDragObject", StringComparison.Ordinal) || - actionGameManager.Contains("public class ItemInstanceDragObject", StringComparison.Ordinal) || - actionGameManager.Contains("public class EquippedItemDragObject", StringComparison.Ordinal) || - inventoryPanel.Contains("GameManager.DragObject", StringComparison.Ordinal) || - inventoryPanel.Contains("GameManager.HasDragTarget", StringComparison.Ordinal) || - inventoryPanel.Contains("GameManager.BeginDrag", StringComparison.Ordinal) || - inventoryPanel.Contains("GameManager.EndDrag", StringComparison.Ordinal) || - inventoryPanel.Contains("GameManager.TryGetDraggedItem", StringComparison.Ordinal) || - inventoryPanel.Contains("GameManager.RegisterDragTarget", StringComparison.Ordinal) || - inventoryPanel.Contains("GameManager.UnregisterDragTarget", StringComparison.Ordinal) || - actionGameManager.Contains("public void BeginDrag(", StringComparison.Ordinal) || - actionGameManager.Contains("public bool TryGetDraggedItem(", StringComparison.Ordinal) || - actionGameManager.Contains("public void RegisterDragTarget(", StringComparison.Ordinal) || - actionGameManager.Contains("public void UnregisterDragTarget(", StringComparison.Ordinal) || - actionGameManager.Contains("public bool EndDrag(", StringComparison.Ordinal) || - !actionGameManager.Contains("private readonly AetheriaUnityDragSession _dragSession", StringComparison.Ordinal) || - !actionGameManager.Contains("SceneWiring.ConfigureInventoryDragSession(_dragSession)", StringComparison.Ordinal) || - !gameplaySceneWiring.Contains("public void ConfigureInventoryDragSession(AetheriaUnityDragSession dragSession)", StringComparison.Ordinal) || - !gameplaySceneWiring.Contains("Inventory?.SetDragSession(dragSession)", StringComparison.Ordinal) || - !gameplaySceneWiring.Contains("ShipPanel?.SetDragSession(dragSession)", StringComparison.Ordinal) || - !gameplaySceneWiring.Contains("TargetShipPanel?.SetDragSession(dragSession)", StringComparison.Ordinal) || - !dragSession.Contains("public sealed class AetheriaUnityDragSession", StringComparison.Ordinal) || - !dragObjects.Contains("public abstract class DragObject", StringComparison.Ordinal) || - !dragObjects.Contains("public abstract class ItemDragObject : DragObject", StringComparison.Ordinal) || - !dragObjects.Contains("public class ItemInstanceDragObject : ItemDragObject", StringComparison.Ordinal) || - !dragObjects.Contains("public class EquippedItemDragObject : ItemDragObject", StringComparison.Ordinal) || - !dragSession.Contains("private DragObject _dragObject", StringComparison.Ordinal) || - !dragSession.Contains("private Action _endDragCallback", StringComparison.Ordinal) || - !inventoryPanel.Contains("public void SetDragSession(AetheriaUnityDragSession dragSession)", StringComparison.Ordinal) || - !inventoryPanel.Contains("private AetheriaUnityDragSession DragSession", StringComparison.Ordinal) || - !inventoryPanel.Contains("DragSession.TryGetDraggedItem(out var itemDragObject)", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Inventory drag/drop must not use ActionGameManager as a drag facade; inject the shared AetheriaUnityDragSession into inventory UI."); - } - - var requiredInventoryPanelSymbols = new[] - { - "RequestLoadoutTemplateSave(string targetEntityKey)", - ".CreateLoadoutTemplate(targetEntityKey ?? \"\")", - ".Ui(\"unity-inventory\")", - ".SaveLoadoutTemplateAsync(loadout, \"unity-inventory\")" - }; - var missingInventoryPanelSymbols = requiredInventoryPanelSymbols - .Where(symbol => !inventoryPanel.Contains(symbol, StringComparison.Ordinal)) - .Concat(new[] { "RuntimeState.DaemonFrame.Latest()" } - .Where(symbol => !inventoryPanel.Contains(symbol, StringComparison.Ordinal))) - .ToArray(); - if (missingInventoryPanelSymbols.Length > 0) - { - throw new InvalidOperationException( - "InventoryPanel no longer sends a typed Eve loadout-template command from the managed current daemon frame: " + - string.Join(", ", missingInventoryPanelSymbols)); - } - - if (inventoryPanel.Contains("AetheriaRuntimeDaemonFrameSession _loadoutFrame", StringComparison.Ordinal) || - inventoryPanel.Contains("CultMeshReactiveDocument _loadoutFrame", StringComparison.Ordinal) || - inventoryPanel.Contains(".ReactiveDaemonFrame(\"unity-inventory\")", StringComparison.Ordinal) || - inventoryPanel.Contains("_loadoutFrame?.Dispose()", StringComparison.Ordinal) || - inventoryPanel.Contains(".ObserveDaemonFrame()", StringComparison.Ordinal) || - inventoryPanel.Contains("AetheriaUnityRuntimeClientProvider.RuntimeClient(\"unity-inventory\")", StringComparison.Ordinal) || - inventoryPanel.Contains(".Ui.SaveLoadoutTemplateAsync(loadout, \"unity-inventory\")", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "InventoryPanel must use managed current typed daemon-frame state and the provider-native UI command handle."); - } - - if (inventoryPanel.Contains(".LoadoutTemplateAsync(", StringComparison.Ordinal) || - inventoryPanel.Contains(".ProjectLoadoutTemplateAsync(client.State, targetEntityKey)", StringComparison.Ordinal) || - inventoryPanel.Contains("AetheriaRuntimeReactiveLoadoutSnapshotProjector", StringComparison.Ordinal) || - inventoryPanel.Contains(".ReactiveLoadoutSnapshotProjector()", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "InventoryPanel still asks an aggregate compatibility helper for loadout templates instead of reading the managed current daemon frame."); - } - - if (File.Exists(loadoutSnapshotProjectorPath)) - { - throw new InvalidOperationException( - "AetheriaRuntimeLoadoutSnapshotProjector is dead access chaff; AetheriaRuntimeRunCheckpointCommit owns loadout-template creation and append."); - } - - var requiredRunCheckpointLoadoutSymbols = new[] - { - "public AetheriaRuntimeLoadoutTemplateCommit CreateLoadoutTemplate(string entityKey)", - "public AetheriaRuntimeLoadoutTemplateCommit CreateLoadoutTemplate(int zoneIndex, int entityIndex)", - "public string AppendLoadoutTemplateToZone(", - "TryParseEntityKey(entityKey, out var zoneIndex, out var entityIndex)", - "CreateLoadoutTemplate(zoneIndex, entityIndex)", - "CreateEntityLoadout(entity, entities)", - "AppendEntity(entities, template.RootEntity, template.Name)" - }; - var missingRunCheckpointLoadoutSymbols = requiredRunCheckpointLoadoutSymbols - .Where(symbol => !snapshotDocuments.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (missingRunCheckpointLoadoutSymbols.Length > 0) - { - throw new InvalidOperationException( - "Loadout template save payloads must be composed by the managed typed run checkpoint document: " + - string.Join(", ", missingRunCheckpointLoadoutSymbols)); - } - - if (snapshotDocuments.Contains("ProjectLoadoutTemplateAsync(", StringComparison.Ordinal) || - snapshotDocuments.Contains("AetheriaClientState state", StringComparison.Ordinal) || - snapshotDocuments.Contains(".Reactive()", StringComparison.Ordinal) || - snapshotDocuments.Contains("frame.Current?.Run", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Loadout snapshot composition must not own managed document sampling; InventoryPanel already caches the reactive daemon frame."); - } - - if (snapshotDocuments.Contains("state.Daemon.LatestFrame.LatestAsync()", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Loadout template save payloads still perform one-shot daemon frame reads instead of using a reactive typed frame document."); - } - - if (snapshotDocuments.Contains("state.Daemon.LatestFrame.ReactiveAsync", StringComparison.Ordinal) || - snapshotDocuments.Contains("state.LatestFrame.ReactiveAsync", StringComparison.Ordinal) || - snapshotDocuments.Contains(".ObserveDaemonFrame()", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Loadout template save payloads must not sample managed daemon-frame state inside snapshot composition."); - } - - if (clientState.Contains("ReactiveLoadoutSnapshotProjector", StringComparison.Ordinal) || - snapshotDocuments.Contains("AetheriaRuntimeReactiveLoadoutSnapshotProjector", StringComparison.Ordinal) || - inventoryPanel.Contains("ProjectLoadoutTemplate(string targetEntityKey)", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Loadout template save payloads must not recreate aggregate reactive projector wrappers; cache or sample the managed typed daemon frame document directly."); - } - - var forbiddenActionSymbols = new[] - { - "AetheriaRuntimeStateCommitLog.QueueLoadoutTemplate", - "TrySendRuntimeLoadoutTemplateCommand(", - "private static bool TrySendRuntimeLoadoutTemplateCommand(", - "RequestLoadoutTemplateSave(EntityConstructionBlueprint blueprint)" - }; - var forbiddenActionHits = forbiddenActionSymbols - .Where(symbol => actionGameManager.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (forbiddenActionHits.Length > 0) - { - throw new InvalidOperationException( - "ActionGameManager still saves loadout templates through local commit or submission-acceptance authority: " + - string.Join(", ", forbiddenActionHits)); - } - - 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)) - { - throw new InvalidOperationException( - "InventoryPanel still captures a Unity EntityConstructionBlueprint before submitting a typed loadout-template save."); - } - - var requiredBridgeSymbols = new[] - { - "AetheriaRuntimeEveCommandKind.SaveLoadoutTemplate", - "ExecuteLoadoutTemplateCommandAsync", - "command.LoadoutTemplate", - "AetheriaRuntimeStateMapper.ToLoadoutTemplate", - "AetheriaRuntimeStateMapper.LoadoutKey" - }; - var missingBridgeSymbols = requiredBridgeSymbols - .Where(symbol => !eveBridge.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (missingBridgeSymbols.Length > 0) - { - throw new InvalidOperationException( - "Eve command bridge no longer owns typed loadout-template persistence: " + - string.Join(", ", missingBridgeSymbols)); - } - - if (!eveCommandDocument.Contains("AetheriaRuntimeLoadoutTemplateCommit? LoadoutTemplate", StringComparison.Ordinal) || - !loadoutCommands.Contains("SurfaceId = \"aetheria.loadout_templates\"", StringComparison.Ordinal) || - !loadoutCommands.Contains("Save = \"aetheria.loadout_templates.save\"", StringComparison.Ordinal) || - !runtimeStateMapper.Contains("public static AetheriaLoadoutTemplate ToLoadoutTemplate", StringComparison.Ordinal) || - !runtimeStateMapper.Contains("public static CultRecordKey LoadoutKey", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Shared loadout-template Eve command contract is incomplete."); - } -} - -static void RequireInventoryLoadoutRestoreRequestAuthority(string root) -{ - var actionGameManagerPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "ActionGameManager.cs"); - var actionGameManager = File.Exists(actionGameManagerPath) - ? File.ReadAllText(actionGameManagerPath) - : throw new InvalidOperationException("Cannot verify loadout restore authority; ActionGameManager.cs is missing."); - var inventoryPanelPath = Path.Combine(root, "Assets", "Scripts", "UI", "Menu", "InventoryPanel.cs"); - var inventoryPanel = File.Exists(inventoryPanelPath) - ? File.ReadAllText(inventoryPanelPath) - : throw new InvalidOperationException("Cannot verify loadout restore authority; InventoryPanel.cs is missing."); - var tradeQueriesPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeDaemonTradeItemQueries.cs"); - var tradeQueries = File.Exists(tradeQueriesPath) - ? File.ReadAllText(tradeQueriesPath) - : throw new InvalidOperationException("Cannot verify loadout restore authority; runtime trade item queries are missing."); - var rtsDocumentsPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeGameViewportDocuments.cs"); - var rtsDocuments = File.Exists(rtsDocumentsPath) - ? File.ReadAllText(rtsDocumentsPath) - : throw new InvalidOperationException("Cannot verify loadout restore authority; RTS viewport documents are missing."); - var rtsDocumentFactoryPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeGameDocuments.cs"); - var rtsDocumentFactory = File.Exists(rtsDocumentFactoryPath) - ? File.ReadAllText(rtsDocumentFactoryPath) - : throw new InvalidOperationException("Cannot verify loadout restore authority; RTS document factory source is missing."); - var clientPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaClient.cs"); - var client = File.Exists(clientPath) - ? File.ReadAllText(clientPath) - : throw new InvalidOperationException("Cannot verify loadout restore authority; AetheriaClient.cs is missing."); - var clientStatePath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaClientState.cs"); - var clientState = File.Exists(clientStatePath) - ? File.ReadAllText(clientStatePath) - : throw new InvalidOperationException("Cannot verify loadout restore authority; AetheriaClientState.cs is missing."); - var verseClientPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeVerseClient.cs"); - var verseClient = File.Exists(verseClientPath) - ? File.ReadAllText(verseClientPath) - : throw new InvalidOperationException("Cannot verify loadout restore authority; AetheriaRuntimeVerseClient.cs is missing."); - - var requiredSymbols = new[] - { - "RequestRuntimeLoadoutRestore", - "operations => operations.RestoreLoadout", - "AetheriaRuntimeStationLoadoutRestoreOption", - "TryResolveStationRefitLoadout(", - "StationRefitSnapshot()?.LoadoutRestoreOptions", - "loadout.TargetEntityKey", - "loadout.TemplateName", - "loadout.Price" - }; - - var missingSymbols = requiredSymbols - .Where(symbol => !inventoryPanel.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - - if (missingSymbols.Length > 0) - { - throw new InvalidOperationException( - "Loadout restore no longer has a typed daemon request primitive: " + - string.Join(", ", missingSymbols)); - } - - if (actionGameManager.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 or availability flags instead of daemon acceptance."); - } - - if (actionGameManager.Contains("ObservedTradeValueSettings()", StringComparison.Ordinal) || - inventoryPanel.Contains("ObservedTradeValueSettings()", StringComparison.Ordinal) || - actionGameManager.Contains("AetheriaUnityProjectionSettings.TradeValueSettings", StringComparison.Ordinal) || - inventoryPanel.Contains("AetheriaUnityProjectionSettings.TradeValueSettings", StringComparison.Ordinal) || - actionGameManager.Contains("blueprint.Price(ItemManager)", StringComparison.Ordinal) || - inventoryPanel.Contains("blueprint.Price(ItemManager)", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Loadout restore must price requests through catalog-owned runtime trade policy instead of Unity settings or ItemManager value authority."); - } - - var forbiddenActionSymbols = new[] - { - "TryRequestDaemonLoadoutRestore", - "private bool TryRequestDaemonLoadoutRestore(" - }; - var forbiddenActionHits = forbiddenActionSymbols - .Where(symbol => actionGameManager.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (forbiddenActionHits.Length > 0) - { - throw new InvalidOperationException( - "Unity loadout restore still exposes daemon request submission as local acceptance state: " + - string.Join(", ", forbiddenActionHits)); - } - - var daemonOperationsPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeDaemonOperations.cs"); - var daemonOperations = File.Exists(daemonOperationsPath) - ? File.ReadAllText(daemonOperationsPath) - : throw new InvalidOperationException("Cannot verify loadout restore authority; daemon operations are missing."); - var missingTemplateCheck = daemonOperations.IndexOf("if (template == null)", StringComparison.Ordinal); - var creditMutation = daemonOperations.IndexOf("run.Credits -= price", StringComparison.Ordinal); - if (missingTemplateCheck < 0 || - creditMutation < 0 || - missingTemplateCheck > creditMutation) - { - throw new InvalidOperationException( - "Daemon loadout restore must reject missing templates before charging credits or mutating run state."); - } - - var forbiddenSymbols = new[] - { - "EntityConstructionBlueprintProjector.InstantiateFromBlueprint", - "EntityConstructionBlueprintProjector.ProjectObservedFromBlueprint", - "EntityConstructionBlueprintProjector.InstantiateAuthoritativeFromBlueprint", - "GameManager.Credits -=", - ".SetParent(GameManager.DockedEntity)" - }; - - var hits = File.ReadLines(inventoryPanelPath) - .Select((line, index) => new { LineNumber = index + 1, Line = line }) - .Where(line => forbiddenSymbols.Any(symbol => line.Line.Contains(symbol, StringComparison.Ordinal))) - .Select(line => $"{Path.GetRelativePath(root, inventoryPanelPath)}:{line.LineNumber}: {line.Line.Trim()}") - .ToArray(); - - if (hits.Length > 0) - { - throw new InvalidOperationException( - "InventoryPanel still owns direct loadout restore mutation: " + - string.Join("; ", hits)); - } - - var forbiddenLocalAcceptanceSymbols = new[] - { - "price < GameManager.Credits", - "price <= GameManager.Credits", - "price > GameManager.Credits", - "price >= GameManager.Credits" - }; - var localAcceptanceHits = File.ReadLines(inventoryPanelPath) - .Select((line, index) => new { LineNumber = index + 1, Line = line }) - .Where(line => forbiddenLocalAcceptanceSymbols.Any(symbol => line.Line.Contains(symbol, StringComparison.Ordinal))) - .Select(line => $"{Path.GetRelativePath(root, inventoryPanelPath)}:{line.LineNumber}: {line.Line.Trim()}") - .ToArray(); - - if (localAcceptanceHits.Length > 0) - { - throw new InvalidOperationException( - "InventoryPanel still filters loadout restore through renderer-local credit checks instead of daemon acceptance: " + - string.Join("; ", localAcceptanceHits)); - } - - 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."); - } - - if (inventoryPanel.Contains("AetheriaRuntimeDaemonTradeItemQueries.TryPriceLoadoutTemplate(", StringComparison.Ordinal) || - inventoryPanel.Contains("AetheriaRuntimeDaemonTradeItemQueries.TryProjectLoadoutTemplatePrice(", StringComparison.Ordinal) || - inventoryPanel.Contains("blueprint.Price(GameManager.ItemManager)", StringComparison.Ordinal) || - inventoryPanel.Contains("LoadoutTemplatesAsync()", StringComparison.Ordinal) || - inventoryPanel.Contains("ResolveLoadoutTemplates", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "InventoryPanel must consume StationRefitAsync loadout restore options instead of pricing or enumerating loadouts locally."); - } - - if (tradeQueries.Contains("TryProjectLoadoutTemplatePrice(", StringComparison.Ordinal) || - tradeQueries.Contains("TryProjectEntityLoadoutPrice(", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Shared runtime trade item queries must price typed loadout templates directly, without projector vocabulary."); - } - - if (!tradeQueries.Contains("public static bool TryPriceLoadoutTemplate(", StringComparison.Ordinal) || - !tradeQueries.Contains("TryPriceEntityLoadout(", StringComparison.Ordinal) || - !tradeQueries.Contains("typedItem.Stackable", StringComparison.Ordinal) || - !tradeQueries.Contains("typedItem.Price * Math.Max(1, item.Quantity)", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Shared runtime trade item queries must own typed loadout template pricing."); - } - - if (!rtsDocuments.Contains("public IReadOnlyList LoadoutRestoreOptions", StringComparison.Ordinal) || - !rtsDocuments.Contains("public sealed class AetheriaRuntimeStationLoadoutRestoreOption", StringComparison.Ordinal) || - !rtsDocuments.Contains("public string TargetEntityKey", StringComparison.Ordinal) || - !rtsDocuments.Contains("public int Price", StringComparison.Ordinal) || - !rtsDocuments.Contains("public bool CanRestore", StringComparison.Ordinal) || - !rtsDocumentFactory.Contains("ProjectLoadoutRestoreOptions(", StringComparison.Ordinal) || - !rtsDocumentFactory.Contains("AetheriaRuntimeDaemonTradeItemQueries.TryPriceLoadoutTemplate(", StringComparison.Ordinal) || - !rtsDocumentFactory.Contains("credits >= price", StringComparison.Ordinal) || - !clientState.Contains("public CultMeshDocumentHandle LoadoutTemplates", StringComparison.Ordinal) || - !verseClient.Contains("RequireManagedLoadoutTemplates().Templates", StringComparison.Ordinal) || - !verseClient.Contains("StationRefit(", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "StationRefitAsync must publish typed loadout restore options with shared runtime pricing and daemon-target identity."); - } - - var loadoutRestoreMatch = System.Text.RegularExpressions.Regex.Match( - inventoryPanel, - @"private void RequestRuntimeLoadoutRestore\(AetheriaRuntimeStationLoadoutRestoreOption loadout\)[\s\S]*?\n \}"); - if (actionGameManager.Contains("RequestRuntimeLoadoutRestore(AetheriaRuntimeLoadoutTemplateSnapshot template, out Entity", StringComparison.Ordinal) || - actionGameManager.Contains("public bool RequestRuntimeLoadoutRestore(", StringComparison.Ordinal) || - inventoryPanel.Contains("RequestRuntimeLoadoutRestore(template, out", StringComparison.Ordinal) || - inventoryPanel.Contains("RequestRuntimeLoadoutRestore(loadoutEntry.template, out", StringComparison.Ordinal) || - !loadoutRestoreMatch.Success || - loadoutRestoreMatch.Value.Contains("Display(", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Loadout restore is still pretending daemon submission synchronously yields accepted Unity state."); - } -} - -static void RequireDockedCurrentShipRequestAuthority(string root) -{ - var inventoryPanelPath = Path.Combine(root, "Assets", "Scripts", "UI", "Menu", "InventoryPanel.cs"); - var inventoryPanel = File.Exists(inventoryPanelPath) - ? File.ReadAllText(inventoryPanelPath) - : throw new InvalidOperationException("Cannot verify docked current-ship authority; InventoryPanel.cs is missing."); - - var requiredSymbols = new[] - { - "RequestDockedCurrentShip", - "operations => operations.SetDockedCurrentShip" - }; - - var missingSymbols = requiredSymbols - .Where(symbol => !inventoryPanel.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - - if (missingSymbols.Length > 0) - { - throw new InvalidOperationException( - "Docked current-ship selection no longer has a typed daemon request primitive: " + - string.Join(", ", missingSymbols)); - } - - if (inventoryPanel.Contains("DockedEntity.Children.Contains(ship)", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Unity docked current-ship selection still rejects through renderer-local child membership instead of daemon acceptance."); - } - - var forbiddenUnityAcceptanceSymbols = new[] - { - "!ship.IsPlayerShip", - "DockedEntity == null", - "DockingBay == null" - }; - var unityAcceptanceHits = FindMethodScopedLineHits(inventoryPanel, forbiddenUnityAcceptanceSymbols) - .Where(hit => hit.MethodName == "RequestDockedCurrentShip") - .Select(hit => $"InventoryPanel.cs:{hit.LineNumber}: {hit.Line.Trim()}") - .ToArray(); - if (unityAcceptanceHits.Length > 0) - { - throw new InvalidOperationException( - "Unity docked current-ship selection still rejects through renderer-local player/docking state instead of daemon acceptance: " + - string.Join(", ", unityAcceptanceHits)); - } - - if (inventoryPanel.Contains("public bool RequestDockedCurrentShip(", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Docked current-ship request API still exposes submission as public acceptance state."); - } - - var forbiddenSymbols = new[] - { - "GameManager.CurrentEntity =", - "GameManager.DockingBay.DockedShip =" - }; - - var hits = File.ReadLines(inventoryPanelPath) - .Select((line, index) => new { LineNumber = index + 1, Line = line }) - .Where(line => forbiddenSymbols.Any(symbol => line.Line.Contains(symbol, StringComparison.Ordinal))) - .Select(line => $"{Path.GetRelativePath(root, inventoryPanelPath)}:{line.LineNumber}: {line.Line.Trim()}") - .ToArray(); - - if (hits.Length > 0) - { - throw new InvalidOperationException( - "InventoryPanel still owns direct docked current-ship mutation: " + - string.Join("; ", hits)); - } - - if (inventoryPanel.Contains("GameManager.RequestDockedCurrentShip", StringComparison.Ordinal) || - !inventoryPanel.Contains("RequestDockedCurrentShip(ship)", StringComparison.Ordinal)) - { - throw new InvalidOperationException("InventoryPanel no longer owns typed current-ship selection operations directly."); - } - - if (!inventoryPanel.Contains("CurrentEntitySnapshot()?.EntityKey ?? \"\"", StringComparison.Ordinal) || - !inventoryPanel.Contains("_displayedEntityKey", StringComparison.Ordinal) || - inventoryPanel.Contains("AetheriaClientReactiveDockingState _reactiveDockingState", StringComparison.Ordinal) || - inventoryPanel.Contains("GameManager.CurrentEntity", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "InventoryPanel must compare current-ship UI state through daemon-observed current entity state instead of peeking GameManager.CurrentEntity directly."); - } - - if (inventoryPanel.Contains("Current.targetGraphic.color = ToggleEnabledColor;", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "InventoryPanel still paints current-ship selection as accepted immediately after daemon submission."); - } -} - -static IEnumerable<(string MethodName, int LineNumber, string Line)> FindMethodScopedLineHits( - string source, - IReadOnlyList symbols) -{ - var currentMethod = ""; - var methodDepth = 0; - var pendingMethod = ""; - var lines = source.Replace("\r\n", "\n", StringComparison.Ordinal).Split('\n'); - for (var index = 0; index < lines.Length; index++) - { - var line = lines[index]; - var trimmed = line.Trim(); - var declaredMethod = TryReadMethodName(trimmed); - if (!string.IsNullOrWhiteSpace(declaredMethod)) - pendingMethod = declaredMethod; - - foreach (var symbol in symbols) - { - if (line.Contains(symbol, StringComparison.Ordinal)) - yield return (currentMethod, index + 1, line); - } - - var opens = line.Count(character => character == '{'); - var closes = line.Count(character => character == '}'); - if (!string.IsNullOrWhiteSpace(pendingMethod) && opens > 0) - { - currentMethod = pendingMethod; - pendingMethod = ""; - methodDepth = opens - closes; - if (methodDepth <= 0) - { - currentMethod = ""; - methodDepth = 0; - } - continue; - } - - if (methodDepth <= 0) - continue; - - methodDepth += opens - closes; - if (methodDepth <= 0) - { - currentMethod = ""; - methodDepth = 0; - } - } -} - -static bool ContainsUnitySettingsMember(string source, string memberName) -{ - return source.Contains($" Settings.{memberName}", StringComparison.Ordinal) || - source.Contains($"(Settings.{memberName}", StringComparison.Ordinal) || - source.Contains($", Settings.{memberName}", StringComparison.Ordinal) || - source.Contains($"\tSettings.{memberName}", StringComparison.Ordinal) || - source.Contains($"\nSettings.{memberName}", StringComparison.Ordinal); -} - -static string CompactSource(string source) -{ - return string.Concat((source ?? "").Where(character => !char.IsWhiteSpace(character))); -} - -static string TryReadMethodName(string trimmedLine) -{ - if (!StartsWithMethodAccessModifier(trimmedLine) || - !trimmedLine.Contains('(') || - trimmedLine.Contains("=>", StringComparison.Ordinal) || - trimmedLine.Contains("=", StringComparison.Ordinal)) - { - return ""; - } - - var beforeParameters = trimmedLine.Substring(0, trimmedLine.IndexOf('(')).Trim(); - var tokens = beforeParameters.Split(new[] { ' ', '\t' }, StringSplitOptions.RemoveEmptyEntries); - return tokens.Length == 0 ? "" : tokens[^1]; -} - -static bool StartsWithMethodAccessModifier(string trimmedLine) -{ - return trimmedLine.StartsWith("private ", StringComparison.Ordinal) || - trimmedLine.StartsWith("public ", StringComparison.Ordinal) || - trimmedLine.StartsWith("protected ", StringComparison.Ordinal) || - trimmedLine.StartsWith("internal ", StringComparison.Ordinal) || - trimmedLine.StartsWith("static ", StringComparison.Ordinal); -} - -static bool ContainsBehaviorValueKind(AetheriaBehaviorValue value, string kind) -{ - return string.Equals(value.Kind, kind, StringComparison.OrdinalIgnoreCase) || - value.Children.Any(child => ContainsBehaviorValueKind(child, kind)) || - value.MapEntries.Any(entry => ContainsBehaviorValueKind(entry.Value, kind)); -} - -static int CountRequiredBehaviorItemRefsMissingItemKeys(AetheriaBehaviorPayload payload) -{ - return payload.Fields.Count(field => - IsBehaviorItemRefField(payload.Kind, field.Key) && - IsNonEmptyLegacyRefMissingItemKey(field.Value)); -} - -static bool IsBehaviorItemRefField(string behaviorKind, int fieldKey) -{ - return (string.Equals(behaviorKind, "ItemUsage", StringComparison.OrdinalIgnoreCase) && fieldKey == 1) || - (IsWeaponBehavior(behaviorKind) && fieldKey == 12); -} - -static bool IsWeaponBehavior(string behaviorKind) -{ - return behaviorKind is "GuidedWeapon" or "InstantWeapon" or "ConstantWeapon" or "ChargedWeapon" or "AutoWeapon" or "LockWeapon"; -} - -static bool IsNonEmptyLegacyRefMissingItemKey(AetheriaBehaviorValue value) -{ - return string.Equals(value.Kind, "legacy-id", StringComparison.OrdinalIgnoreCase) && - !IsEmptyLegacyId(value.LegacyIdValue) && - string.IsNullOrWhiteSpace(value.ItemKeyValue); -} - -static bool IsEmptyLegacyId(string legacyId) -{ - return string.IsNullOrWhiteSpace(legacyId) || - (Guid.TryParse(legacyId, out var parsed) && parsed == Guid.Empty); -} - -static async Task RequireLegacyLookupAsync( - string legacyId, - Func> lookup, - string label) where T : class -{ - if (string.IsNullOrWhiteSpace(legacyId)) - { - throw new InvalidOperationException($"Cannot verify {label}: legacy id is empty."); - } - - var value = await lookup().ConfigureAwait(false); - if (value == null) - { - throw new InvalidOperationException($"Typed legacy-id lookup failed for {label} {legacyId}."); - } -} - -static void RequireManagedContentOnlyAssetDelivery(string root) -{ - var daemonPath = Path.Combine(root, "Aetheria.State.Daemon", "Program.cs"); - var documentsPath = Path.Combine( - root, - "Packages", - "org.gamecult.aetheria.state", - "Runtime", - "AetheriaRuntimeDaemonDocuments.cs"); - var electronPath = Path.Combine(root, "Aetheria.Rts.Web", "Electron", "aetheria-cultmesh.ts"); - var daemon = File.ReadAllText(daemonPath); - var documents = File.ReadAllText(documentsPath); - var electron = File.ReadAllText(electronPath); - var forbidden = new[] - { - "CultMeshCdnAssetBlob", - "gamecult.cultmesh.cdn.asset_blob.v1", - "TryResolveCultMeshCdnAssetPath" - }; - var surviving = forbidden - .Where(symbol => daemon.Contains(symbol, StringComparison.Ordinal) || - documents.Contains(symbol, StringComparison.Ordinal) || - electron.Contains(symbol, StringComparison.Ordinal)) - .ToArray(); - if (surviving.Length > 0) - { - throw new InvalidOperationException( - "Snapshot delivery regained a byte-body authority; managed content sessions must be the sole asset-body transport: " + - string.Join(", ", surviving)); - } - - if (!daemon.Contains("InjectCultMeshCdnManifestSnapshots", StringComparison.Ordinal) || - !daemon.Contains("new CultMeshTcpContentServer(", StringComparison.Ordinal)) - { - throw new InvalidOperationException( - "Daemon asset delivery must publish snapshot manifests and host the dedicated TCP content plane."); - } -} diff --git a/Aetheria.State/Aetheria.State.Dependencies.props b/Aetheria.State/Aetheria.State.Dependencies.props deleted file mode 100644 index df1ae1c37..000000000 --- a/Aetheria.State/Aetheria.State.Dependencies.props +++ /dev/null @@ -1,7 +0,0 @@ - - - $(MSBuildThisFileDirectory)..\..\CultLib - $(MSBuildThisFileDirectory)..\..\EveUnity - $(MSBuildThisFileDirectory)..\..\Ymir - - diff --git a/Aetheria.State/Aetheria.State.Dependencies.targets b/Aetheria.State/Aetheria.State.Dependencies.targets deleted file mode 100644 index fcef6c612..000000000 --- a/Aetheria.State/Aetheria.State.Dependencies.targets +++ /dev/null @@ -1,20 +0,0 @@ - - - - <_GameCultMeshReference Include="@(ReferencePath)" - Condition="'%(ReferencePath.Filename)%(ReferencePath.Extension)' == 'GameCult.Mesh.dll'" /> - - - - - - - - - - - diff --git a/Aetheria.State/Aetheria.State.csproj b/Aetheria.State/Aetheria.State.csproj deleted file mode 100644 index c323cf1fb..000000000 --- a/Aetheria.State/Aetheria.State.csproj +++ /dev/null @@ -1,29 +0,0 @@ - - - - - netstandard2.1 - enable - latest - enable - Typed CultCache and CultMesh state spine for Aetheria. - - - - - - - - - - - - - - - - diff --git a/Aetheria.State/AetheriaCatalogKeys.cs b/Aetheria.State/AetheriaCatalogKeys.cs deleted file mode 100644 index 19770a4fa..000000000 --- a/Aetheria.State/AetheriaCatalogKeys.cs +++ /dev/null @@ -1,31 +0,0 @@ -using GameCult.Caching; - -namespace Aetheria.State; - -public static class AetheriaCatalogKeys -{ - public static CultRecordKey ItemDefinitionFromLegacyId(string legacyId) - { - return CreateLegacyKey("item", legacyId); - } - - public static CultRecordKey CorporationFromLegacyId(string legacyId) - { - return CreateLegacyKey("faction", legacyId); - } - - public static CultRecordKey NameFileFromLegacyId(string legacyId) - { - return CreateLegacyKey("name-file", legacyId); - } - - private static CultRecordKey CreateLegacyKey(string catalogKind, string legacyId) - { - if (string.IsNullOrWhiteSpace(legacyId)) - { - throw new ArgumentException("Legacy catalog id must be non-empty.", nameof(legacyId)); - } - - return new CultRecordKey($"legacy:{catalogKind}:{legacyId.Trim()}"); - } -} diff --git a/Aetheria.State/AetheriaDocumentRegistry.cs b/Aetheria.State/AetheriaDocumentRegistry.cs deleted file mode 100644 index 7ae312b68..000000000 --- a/Aetheria.State/AetheriaDocumentRegistry.cs +++ /dev/null @@ -1,83 +0,0 @@ -using Aetheria.State.Documents; -using GameCult.Caching; -using GameCult.Aetheria.State.Verse; -using GameCult.Eve.Surface; -using GameCult.Eve.PluginFields; -using GameCult.Mesh; -using GameCult.Networking; - -namespace Aetheria.State; - -public static class AetheriaDocumentRegistry -{ - public static CultDocumentRegistry CreateCultCacheRegistry() - { - return CultMesh.CreateCultCacheDocumentRegistry(DocumentTypes); - } - - public static CultNetDocumentRegistry CreateCultNetRegistry(CultDocumentRegistry? cacheRegistry = null) - { - var registry = cacheRegistry ?? CreateCultCacheRegistry(); - return CultMesh.CreateCultNetDocumentRegistry(DocumentTypes, registry); - } - - public static IReadOnlyList DocumentTypes { get; } = - [ - typeof(AetheriaWorldState), - typeof(AetheriaItemDefinition), - typeof(AetheriaCorporation), - typeof(AetheriaNameFile), - typeof(AetheriaPlayerProfile), - typeof(AetheriaHangarState), - typeof(AetheriaRuntimeSession), - typeof(AetheriaMigrationLedger), - typeof(AetheriaLegacyCatalogQuarantine), - typeof(AetheriaTradeValuePolicy), - typeof(AetheriaRuntimeCatalogSnapshot), - typeof(AetheriaRuntimeNameCorpusSnapshot), - typeof(AetheriaPlayerSettings), - typeof(AetheriaLoadoutTemplate), - typeof(AetheriaRunState), - typeof(AetheriaZoneState), - typeof(AetheriaEntitySnapshot), - typeof(AetheriaVerseHostSettings), - typeof(AetheriaMainMenuState), - typeof(AetheriaGameSessionState), - typeof(AetheriaEveCommandAcceptanceStatus), - typeof(AetheriaRuntimeDaemonProviderAdvertisementDocument), - typeof(AetheriaRuntimeDaemonHealthDocument), - typeof(AetheriaRuntimeDaemonCommandBoundaryDocument), - typeof(AetheriaRuntimeAssetManifestDocument), - typeof(AetheriaRuntimeDaemonFrameDocument), - typeof(AetheriaRuntimeGameViewportDocument), - typeof(AetheriaRuntimeObjectsViewportDocument), - typeof(AetheriaRuntimeGravityViewportDocument), - typeof(AetheriaRuntimeCurrentZoneDocument), - typeof(AetheriaRuntimeCurrentEntityDocument), - typeof(AetheriaRuntimeCurrentDockingDocument), - typeof(AetheriaRuntimeZoneContactsDocument), - typeof(AetheriaRuntimeStationRefitDocument), - typeof(AetheriaRuntimeSectorMapDocument), - typeof(AetheriaRuntimeZoneDetailsDocument), - typeof(AetheriaRuntimeZoneRenderDocument), - typeof(AetheriaRuntimeSelectedObjectDocument), - typeof(AetheriaRuntimeInventoryDocument), - typeof(AetheriaRuntimeStarbridgeScenarioDocument), - typeof(AetheriaRuntimeStarbridgeSessionDocument), - typeof(AetheriaRuntimeStarbridgeSessionSummaryDocument), - typeof(EveFieldsSplatsDocument), - typeof(EveInputCapabilityDocument), - typeof(AetheriaRuntimeDaemonSoaViewDocument), - typeof(AetheriaRuntimeVerseAuthorityPolicyDocument), - typeof(AetheriaRuntimeAuthorityLeaseDocument), - typeof(AetheriaRuntimeDaemonCommandDocument), - typeof(AetheriaRuntimeCommittedCommandFactDocument), - typeof(AetheriaRuntimeEveCommandDocument), - typeof(AetheriaRuntimeSurfaceDocument), - typeof(EveProviderAdvertisementDocument), - typeof(EveSurfaceDocument), - typeof(EveSurfaceCommandRequest), - typeof(EveCommandReceiptDocument), - typeof(EveAssetCatalogDocument) - ]; -} diff --git a/Aetheria.State/AetheriaEveCommandBridge.cs b/Aetheria.State/AetheriaEveCommandBridge.cs deleted file mode 100644 index 5d3fa26fb..000000000 --- a/Aetheria.State/AetheriaEveCommandBridge.cs +++ /dev/null @@ -1,478 +0,0 @@ -using Aetheria.State.Documents; -using GameCult.Aetheria.State.Verse; -using GameCult.Mesh; -using EveProviderAdvertisementDocument = GameCult.Eve.Surface.EveProviderAdvertisementDocument; -using EveSurfaceDocument = GameCult.Eve.Surface.EveSurfaceDocument; - -namespace Aetheria.State; - -public sealed class AetheriaEveCommandAcceptanceReport -{ - public int AcceptedCatalogRefreshes { get; set; } - public int AcceptedOperationsRefreshes { get; set; } - public int AcceptedPlayerSettingsCommands { get; set; } - public int AcceptedInputSettingsCommands { get; set; } - public int AcceptedLoadoutTemplateCommands { get; set; } - public int AcceptedVerseHostCommands { get; set; } - public int AcceptedTradeValuePolicyCommands { get; set; } - public int AcceptedMainMenuCommands { get; set; } - public int RejectedCommands { get; set; } - public string[] AcceptedCommandIds { get; set; } = []; - public string[] RejectedCommandIds { get; set; } = []; - public string[] AccountedCommandIds { get; set; } = []; - public string LastRejectedCommand { get; set; } = ""; - public string LastRejectedReason { get; set; } = ""; -} -public static class AetheriaEveCommandBridge -{ - public const string CommandSchema = "gamecult.eve.command.v1"; - - public static async Task AcceptObservedAsync( - AetheriaStateNode node, - IEnumerable? accountedCommandIds = null) - { - if (node == null) throw new ArgumentNullException(nameof(node)); - - var report = new AetheriaEveCommandAcceptanceReport(); - var accepted = new List(); - var rejected = new List(); - var accounted = new HashSet(accountedCommandIds ?? Array.Empty(), StringComparer.Ordinal); - foreach (var command in node.Documents() - .Select(AetheriaRuntimeEveCommandClient.NormalizeDocument) - .OrderBy(command => command.IssuedAtUtc ?? "", StringComparer.Ordinal) - .ThenBy(command => command.CommandId ?? "", StringComparer.Ordinal) - .Where(command => !string.IsNullOrWhiteSpace(command.CommandId)) - .Where(command => !accounted.Contains(command.CommandId))) - { - var rejection = Validate(command); - if (!string.IsNullOrWhiteSpace(rejection)) - { - RecordRejection(report, command, rejection, rejected); - continue; - } - - switch (command.Kind) - { - case AetheriaRuntimeEveCommandKind.CatalogRefresh: - await node.FlushAsync().ConfigureAwait(false); - var catalog = await node.RefreshRuntimeCatalogAsync().ConfigureAwait(false); - await node.MutableDocument(AetheriaStateNode.CatalogSurfaceKey) - .ReplaceAsync(AetheriaEveSurfaceDocuments.BuildCatalogSurface(catalog, command.IssuedAtUtc)) - .ConfigureAwait(false); - report.AcceptedCatalogRefreshes++; - break; - case AetheriaRuntimeEveCommandKind.OperationsRefresh: - var eveStatus = await node.MutableDocument(AetheriaStateNode.EveCommandAcceptanceStatusKey).ReadAsync().ConfigureAwait(false) ?? - EmptyEveCommandAcceptanceStatus(node.StatePath, command.IssuedAtUtc); - var verseHostSettings = await node.MutableDocument(AetheriaStateNode.VerseHostSettingsKey).ReadAsync().ConfigureAwait(false); - var runtimeSession = await node.MutableDocument(AetheriaStateNode.RuntimeSessionKey(eveStatus.RuntimeId)).ReadAsync().ConfigureAwait(false); - await node.MutableDocument(AetheriaStateNode.OperationsSurfaceKey) - .ReplaceAsync(AetheriaEveSurfaceDocuments.BuildOperationsSurface( - eveStatus, - verseHostSettings, - runtimeSession)) - .ConfigureAwait(false); - report.AcceptedOperationsRefreshes++; - break; - case AetheriaRuntimeEveCommandKind.PlayerSettingsRefresh: - case AetheriaRuntimeEveCommandKind.SetPlayerName: - case AetheriaRuntimeEveCommandKind.CycleTemperatureUnit: - case AetheriaRuntimeEveCommandKind.DecrementSignificantDigits: - case AetheriaRuntimeEveCommandKind.IncrementSignificantDigits: - case AetheriaRuntimeEveCommandKind.CycleNebulaQuality: - case AetheriaRuntimeEveCommandKind.ToggleShowAsteroidsInMinimap: - await ExecutePlayerSettingsCommandAsync(node, command).ConfigureAwait(false); - report.AcceptedPlayerSettingsCommands++; - break; - case AetheriaRuntimeEveCommandKind.InputSettingsRefresh: - case AetheriaRuntimeEveCommandKind.SetBindingOverride: - case AetheriaRuntimeEveCommandKind.SetActionBarEnabled: - await ExecuteInputSettingsCommandAsync(node, command).ConfigureAwait(false); - report.AcceptedInputSettingsCommands++; - break; - case AetheriaRuntimeEveCommandKind.SaveLoadoutTemplate: - await ExecuteLoadoutTemplateCommandAsync(node, command).ConfigureAwait(false); - report.AcceptedLoadoutTemplateCommands++; - break; - case AetheriaRuntimeEveCommandKind.VerseHostRefresh: - case AetheriaRuntimeEveCommandKind.CycleVerseHostVisibility: - await ExecuteVerseHostCommandAsync(node, command).ConfigureAwait(false); - report.AcceptedVerseHostCommands++; - break; - case AetheriaRuntimeEveCommandKind.TradeValuePolicyRefresh: - case AetheriaRuntimeEveCommandKind.SetTradeValueQualityMinimum: - case AetheriaRuntimeEveCommandKind.SetTradeValueQualityMaximum: - case AetheriaRuntimeEveCommandKind.SetTradeValueQualityExponent: - case AetheriaRuntimeEveCommandKind.SetTradeValueTierQuality: - await ExecuteTradeValuePolicyCommandAsync(node, command).ConfigureAwait(false); - report.AcceptedTradeValuePolicyCommands++; - break; - case AetheriaRuntimeEveCommandKind.MainMenuContinueRun: - case AetheriaRuntimeEveCommandKind.MainMenuNewGame: - case AetheriaRuntimeEveCommandKind.MainMenuShowSettings: - case AetheriaRuntimeEveCommandKind.MainMenuQuit: - case AetheriaRuntimeEveCommandKind.MainMenuShowPlayerSettings: - case AetheriaRuntimeEveCommandKind.MainMenuShowVerseSettings: - case AetheriaRuntimeEveCommandKind.MainMenuShowInputSettings: - case AetheriaRuntimeEveCommandKind.MainMenuBackToMain: - case AetheriaRuntimeEveCommandKind.MainMenuBackToSettings: - case AetheriaRuntimeEveCommandKind.MainMenuOpenRuntimeInputScreen: - await ExecuteMainMenuCommandAsync(node, command).ConfigureAwait(false); - report.AcceptedMainMenuCommands++; - break; - } - - accepted.Add(command.CommandId ?? ""); - } - - await node.FlushAsync().ConfigureAwait(false); - report.AcceptedCommandIds = accepted.ToArray(); - report.RejectedCommandIds = rejected.ToArray(); - report.AccountedCommandIds = accounted - .Concat(report.AcceptedCommandIds) - .Concat(report.RejectedCommandIds) - .Where(commandId => !string.IsNullOrWhiteSpace(commandId)) - .Distinct(StringComparer.Ordinal) - .ToArray(); - return report; - } - - private static string Validate(AetheriaRuntimeEveCommandDocument command) - { - if (!string.Equals(command.Schema, CommandSchema, StringComparison.Ordinal)) - return $"Unexpected Eve command schema '{command.Schema}'."; - if (!string.Equals(command.ProviderId, AetheriaEveSurfaceDocuments.ProviderId, StringComparison.Ordinal)) - return $"Unexpected Eve provider '{command.ProviderId}'."; - if (command.Kind == AetheriaRuntimeEveCommandKind.Unknown) - return $"Unknown typed Eve command kind for surface '{command.SurfaceId}' command '{command.Command}'."; - if (AetheriaRuntimeEveCommandClient.CommandKindForSurface(command.SurfaceId, command.Command) != command.Kind) - return $"Eve command '{command.Command}' does not match typed command kind '{command.Kind}'."; - - return ""; - } - - private static async Task ExecutePlayerSettingsCommandAsync( - AetheriaStateNode node, - AetheriaRuntimeEveCommandDocument command) - { - var settings = await node.MutableDocument(AetheriaStateNode.PlayerSettingsKey).ReadAsync().ConfigureAwait(false) ?? new AetheriaPlayerSettings(); - settings.Gameplay ??= new AetheriaPlayerGameplaySettings(); - settings.Graphics ??= new AetheriaPlayerGraphicsSettings(); - var persistSettings = false; - - switch (command.Kind) - { - case AetheriaRuntimeEveCommandKind.SetPlayerName: - settings.PlayerName = command.PlayerSettings.PlayerName ?? ""; - persistSettings = true; - break; - case AetheriaRuntimeEveCommandKind.CycleTemperatureUnit: - settings.Gameplay.TemperatureUnit = Cycle( - settings.Gameplay.TemperatureUnit, - "Kelvin", - "Celsius", - "Fahrenheit"); - persistSettings = true; - break; - case AetheriaRuntimeEveCommandKind.DecrementSignificantDigits: - settings.Gameplay.SignificantDigits = Math.Max(0, settings.Gameplay.SignificantDigits - 1); - persistSettings = true; - break; - case AetheriaRuntimeEveCommandKind.IncrementSignificantDigits: - if (settings.Gameplay.SignificantDigits < int.MaxValue) - settings.Gameplay.SignificantDigits++; - persistSettings = true; - break; - case AetheriaRuntimeEveCommandKind.CycleNebulaQuality: - settings.Graphics.NebulaQuality = Cycle( - settings.Graphics.NebulaQuality, - "Low", - "Normal", - "High", - "Ultra"); - persistSettings = true; - break; - case AetheriaRuntimeEveCommandKind.ToggleShowAsteroidsInMinimap: - settings.Graphics.ShowAsteroidsInMinimap = !settings.Graphics.ShowAsteroidsInMinimap; - persistSettings = true; - break; - } - - if (persistSettings) - { - settings.LastUpdatedAtUtc = command.IssuedAtUtc; - await node.MutableDocument(AetheriaStateNode.PlayerSettingsKey) - .ReplaceAsync(settings) - .ConfigureAwait(false); - } - - await node.MutableDocument(AetheriaStateNode.PlayerSettingsSurfaceKey) - .ReplaceAsync(AetheriaEveSurfaceDocuments.BuildPlayerSettingsSurface(settings, command.IssuedAtUtc)) - .ConfigureAwait(false); - } - - private static async Task ExecuteInputSettingsCommandAsync( - AetheriaStateNode node, - AetheriaRuntimeEveCommandDocument command) - { - var settings = await node.MutableDocument(AetheriaStateNode.PlayerSettingsKey).ReadAsync().ConfigureAwait(false) ?? new AetheriaPlayerSettings(); - settings.Input ??= new AetheriaPlayerInputSettings(); - var persistSettings = false; - - switch (command.Kind) - { - case AetheriaRuntimeEveCommandKind.SetBindingOverride: - var actionName = command.InputSettings.ActionName ?? ""; - var inputSystemPath = command.InputSettings.InputSystemPath ?? ""; - var bindingIndex = command.InputSettings.BindingIndex; - if (!string.IsNullOrWhiteSpace(actionName) && bindingIndex >= 0) - { - settings.Input.BindingOverrides = settings.Input.BindingOverrides - .Where(binding => !string.Equals(binding.ActionName, actionName, StringComparison.Ordinal) || - binding.BindingIndex != bindingIndex) - .Concat(new[] - { - new AetheriaInputBindingOverride - { - ActionName = actionName, - BindingIndex = bindingIndex, - BindingPath = inputSystemPath - } - }) - .OrderBy(binding => binding.ActionName, StringComparer.Ordinal) - .ThenBy(binding => binding.BindingIndex) - .ToArray(); - persistSettings = true; - } - break; - case AetheriaRuntimeEveCommandKind.SetActionBarEnabled: - var inputPath = command.InputSettings.InputSystemPath ?? ""; - var enabled = command.InputSettings.Enabled; - if (!string.IsNullOrWhiteSpace(inputPath)) - { - var inputs = settings.Input.ActionBarInputs - .Where(path => !string.Equals(path, inputPath, StringComparison.Ordinal)) - .ToList(); - if (enabled) - inputs.Add(inputPath); - settings.Input.ActionBarInputs = inputs - .OrderBy(path => path, StringComparer.Ordinal) - .ToArray(); - persistSettings = true; - } - break; - } - - if (persistSettings) - { - settings.LastUpdatedAtUtc = command.IssuedAtUtc; - await node.MutableDocument(AetheriaStateNode.PlayerSettingsKey) - .ReplaceAsync(settings) - .ConfigureAwait(false); - } - } - - private static async Task ExecuteVerseHostCommandAsync( - AetheriaStateNode node, - AetheriaRuntimeEveCommandDocument command) - { - var settings = await node.MutableDocument(AetheriaStateNode.VerseHostSettingsKey).ReadAsync().ConfigureAwait(false) ?? new AetheriaVerseHostSettings(); - var normalized = AetheriaVerseHostSettingsNormalizer.Normalize(settings); - var persistSettings = false; - - switch (command.Kind) - { - case AetheriaRuntimeEveCommandKind.CycleVerseHostVisibility: - normalized.Visibility = Cycle(normalized.Visibility, "private", "public"); - persistSettings = true; - break; - } - - if (persistSettings) - { - normalized.LastUpdatedAtUtc = command.IssuedAtUtc; - await node.MutableDocument(AetheriaStateNode.VerseHostSettingsKey) - .ReplaceAsync(normalized) - .ConfigureAwait(false); - } - - var eveStatus = await node.MutableDocument(AetheriaStateNode.EveCommandAcceptanceStatusKey).ReadAsync().ConfigureAwait(false) ?? - EmptyEveCommandAcceptanceStatus(node.StatePath, command.IssuedAtUtc); - var runtimeSession = await node.MutableDocument(AetheriaStateNode.RuntimeSessionKey(eveStatus.RuntimeId)).ReadAsync().ConfigureAwait(false); - - await node.MutableDocument(AetheriaStateNode.OperationsSurfaceKey) - .ReplaceAsync(AetheriaEveSurfaceDocuments.BuildOperationsSurface( - eveStatus, - normalized, - runtimeSession)) - .ConfigureAwait(false); - await node.MutableDocument(AetheriaStateNode.ProviderAdvertisementSurfaceKey) - .ReplaceAsync(AetheriaEveSurfaceDocuments.BuildProviderAdvertisement(normalized, node.StatePath, command.IssuedAtUtc)) - .ConfigureAwait(false); - } - - private static async Task ExecuteLoadoutTemplateCommandAsync( - AetheriaStateNode node, - AetheriaRuntimeEveCommandDocument command) - { - var commit = command.LoadoutTemplate ?? throw new InvalidOperationException( - $"Loadout template command '{command.CommandId}' is missing its typed payload."); - var loadout = AetheriaRuntimeStateMapper.ToLoadoutTemplate(commit, command.IssuedAtUtc); - await node.MutableDocument(AetheriaRuntimeStateMapper.LoadoutKey(loadout.Name)) - .ReplaceAsync(loadout) - .ConfigureAwait(false); - } - - private static async Task ExecuteTradeValuePolicyCommandAsync( - AetheriaStateNode node, - AetheriaRuntimeEveCommandDocument command) - { - var tradeValuePolicy = node.MutableDocument(AetheriaStateNode.TradeValuePolicyKey); - var policy = await tradeValuePolicy.ReadAsync().ConfigureAwait(false) ?? - AetheriaRuntimeStateMapper.ToTradeValuePolicy( - AetheriaRuntimeTradeValueSettings.Default, - command.IssuedAtUtc); - policy.QualityPriceModifier ??= new AetheriaExponentialLerp(); - policy.Tiers ??= Array.Empty(); - var persistPolicy = false; - var value = command.TradeValuePolicy.Value; - - switch (command.Kind) - { - case AetheriaRuntimeEveCommandKind.SetTradeValueQualityMinimum: - policy.QualityPriceModifier.Minimum = ClampFinite(value, 0, double.MaxValue); - persistPolicy = true; - break; - case AetheriaRuntimeEveCommandKind.SetTradeValueQualityMaximum: - policy.QualityPriceModifier.Maximum = ClampFinite(value, 0, double.MaxValue); - persistPolicy = true; - break; - case AetheriaRuntimeEveCommandKind.SetTradeValueQualityExponent: - policy.QualityPriceModifier.Exponent = ClampFinite(value, 0.001, double.MaxValue); - persistPolicy = true; - break; - case AetheriaRuntimeEveCommandKind.SetTradeValueTierQuality: - var tierIndex = command.TradeValuePolicy.TierIndex; - if (tierIndex >= 0 && tierIndex < policy.Tiers.Length) - { - policy.Tiers[tierIndex].Quality = ClampFinite(value, 0, 1); - persistPolicy = true; - } - break; - } - - if (persistPolicy) - { - policy.UpdatedAtUtc = command.IssuedAtUtc; - await tradeValuePolicy.ReplaceAsync(policy).ConfigureAwait(false); - } - } - - private static async Task ExecuteMainMenuCommandAsync( - AetheriaStateNode node, - AetheriaRuntimeEveCommandDocument command) - { - var state = await node.MutableDocument(AetheriaStateNode.MainMenuStateKey).ReadAsync().ConfigureAwait(false) ?? - new AetheriaMainMenuState(); - var activeSurfaceId = ActiveMainMenuSurfaceFor(command.Kind, state.ActiveSurfaceId); - state.ActiveSurfaceId = activeSurfaceId; - state.LastCommandId = command.CommandId ?? ""; - state.LastCommand = command.Command ?? ""; - state.UpdatedAtUtc = string.IsNullOrWhiteSpace(command.IssuedAtUtc) - ? DateTimeOffset.UtcNow.ToString("O") - : command.IssuedAtUtc; - await node.MutableDocument(AetheriaStateNode.MainMenuStateKey) - .ReplaceAsync(state) - .ConfigureAwait(false); - } - - private static string ActiveMainMenuSurfaceFor( - AetheriaRuntimeEveCommandKind kind, - string currentSurfaceId) - { - switch (kind) - { - case AetheriaRuntimeEveCommandKind.MainMenuShowSettings: - case AetheriaRuntimeEveCommandKind.MainMenuBackToSettings: - return AetheriaRuntimeMainMenuCommands.SettingsSurfaceId; - case AetheriaRuntimeEveCommandKind.MainMenuShowPlayerSettings: - return AetheriaRuntimeMainMenuCommands.PlayerSettingsSurfaceId; - case AetheriaRuntimeEveCommandKind.MainMenuShowVerseSettings: - return AetheriaRuntimeMainMenuCommands.VerseSettingsSurfaceId; - case AetheriaRuntimeEveCommandKind.MainMenuShowInputSettings: - case AetheriaRuntimeEveCommandKind.MainMenuOpenRuntimeInputScreen: - return AetheriaRuntimeMainMenuCommands.InputSettingsSurfaceId; - case AetheriaRuntimeEveCommandKind.MainMenuBackToMain: - case AetheriaRuntimeEveCommandKind.MainMenuContinueRun: - case AetheriaRuntimeEveCommandKind.MainMenuNewGame: - return ""; - case AetheriaRuntimeEveCommandKind.MainMenuQuit: - return AetheriaRuntimeMainMenuCommands.RootSurfaceId; - default: - return NormalizeMainMenuSurfaceId(currentSurfaceId); - } - } - - private static string NormalizeMainMenuSurfaceId(string surfaceId) - { - switch (surfaceId ?? "") - { - case AetheriaRuntimeMainMenuCommands.SettingsSurfaceId: - case AetheriaRuntimeMainMenuCommands.InputSettingsSurfaceId: - case AetheriaRuntimeMainMenuCommands.PlayerSettingsSurfaceId: - case AetheriaRuntimeMainMenuCommands.VerseSettingsSurfaceId: - return surfaceId; - default: - return AetheriaRuntimeMainMenuCommands.RootSurfaceId; - } - } - - private static string Cycle(string current, params string[] values) - { - if (values.Length == 0) - return current; - - var index = Array.FindIndex(values, value => string.Equals(value, current, StringComparison.Ordinal)); - if (index < 0) - return values[0]; - - return values[(index + 1) % values.Length]; - } - - private static double ClampFinite(double value, double min, double max) - { - if (double.IsNaN(value) || double.IsInfinity(value)) - return min; - if (value < min) - return min; - if (value > max) - return max; - return value; - } - - private static void RecordRejection( - AetheriaEveCommandAcceptanceReport report, - AetheriaRuntimeEveCommandDocument command, - string reason, - List rejected) - { - report.RejectedCommands++; - report.LastRejectedCommand = string.IsNullOrWhiteSpace(command.Command) - ? command.CommandId - : command.Command; - report.LastRejectedReason = reason; - rejected.Add(command.CommandId ?? ""); - } - - private static AetheriaEveCommandAcceptanceStatus EmptyEveCommandAcceptanceStatus(string statePath, string now) - { - return new AetheriaEveCommandAcceptanceStatus - { - RuntimeId = "aetheria-state", - StatePath = statePath, - LastPollAtUtc = now, - Status = "idle" - }; - } - -} diff --git a/Aetheria.State/AetheriaEveSurfaceDocuments.cs b/Aetheria.State/AetheriaEveSurfaceDocuments.cs deleted file mode 100644 index 891773b52..000000000 --- a/Aetheria.State/AetheriaEveSurfaceDocuments.cs +++ /dev/null @@ -1,379 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Aetheria.State.Documents; -using GameCult.Aetheria.State.Verse; -using GameCult.Mesh; -using EveCommandTemplate = GameCult.Eve.Surface.EveCommandTemplate; -using EveCommandReceiptDocument = GameCult.Eve.Surface.EveCommandReceiptDocument; -using EveAdvertisedCommand = GameCult.Eve.Surface.EveAdvertisedCommand; -using EveAdvertisedSurface = GameCult.Eve.Surface.EveAdvertisedSurface; -using EveProviderAdvertisementDocument = GameCult.Eve.Surface.EveProviderAdvertisementDocument; -using EveProviderFreshness = GameCult.Eve.Surface.EveProviderFreshness; -using EveProviderWitness = GameCult.Eve.Surface.EveProviderWitness; -using EveStyleToken = GameCult.Eve.Surface.EveStyleToken; -using EveSurfaceComponent = GameCult.Eve.Surface.EveSurfaceComponent; -using EveSurfaceDocument = GameCult.Eve.Surface.EveSurfaceDocument; -using EveSurfaceTree = GameCult.Eve.Surface.EveSurfaceTree; -using EveWorldInteractionAdvertisement = GameCult.Eve.Surface.EveWorldInteractionAdvertisement; - -namespace Aetheria.State; - -public static class AetheriaEveSurfaceDocuments -{ - public const string ProviderId = "aetheria"; - public const string ProviderAdvertisementKey = "eve:provider:aetheria"; - public const string CatalogSurfaceKey = "eve:surface:aetheria.catalog.operator"; - public const string OperationsSurfaceKey = "eve:surface:aetheria.operations"; - public const string PlayerSettingsSurfaceKey = "eve:surface:aetheria.player_settings"; - public const string DaemonGameSurfaceKey = "eve:surface:aetheria.daemon.game"; - public const string DaemonGameTuiSurfaceKey = "eve:surface:aetheria.daemon.game.tui"; - public const string DaemonEditorSurfaceKey = "eve:surface:aetheria.daemon.editor"; - public const string DaemonEditorTuiSurfaceKey = "eve:surface:aetheria.daemon.editor.tui"; - - public const string CatalogSurfaceId = AetheriaRuntimeCatalogCommands.SurfaceId; - public const string OperationsSurfaceId = AetheriaRuntimeOperationsCommands.SurfaceId; - public const string PlayerSettingsSurfaceId = AetheriaRuntimePlayerSettingsCommands.SurfaceId; - - private const string DaemonCommandBoundaryId = "aetheria.daemon.commands"; - private const string DaemonRecordTransport = "cultmesh-record"; - - public static EveSurfaceDocument BuildCatalogSurface( - AetheriaRuntimeCatalogSnapshot catalog, - string updatedAtUtc, - long version = 1) - { - var tradeItems = catalog.TradeItems.Take(12).Select(item => - Row( - $"item.{SafeId(item.ItemKey)}", - ("name", item.Name), - ("manufacturer", catalog.GetManufacturer(item)?.Name ?? "GameCult"), - ("price", item.Price.ToString("N0")), - ("size", item.ShapeWidth > 0 && item.ShapeHeight > 0 ? $"{item.ShapeWidth}x{item.ShapeHeight}" : ""))) - .ToArray(); - - var corporations = catalog.Corporations.Take(12).Select(corporation => - Row( - $"corporation.{SafeId(corporation.CorporationKey)}", - ("name", corporation.Name), - ("short", corporation.ShortName), - ("names", catalog.GetNameFile(corporation)?.Name ?? ""), - ("influence", corporation.InfluenceDistance.ToString()))) - .ToArray(); - var refreshCommand = CultMesh.OperationBindingRecord( - CultMesh.OperationBinding( - AetheriaRuntimeCatalogCommands.Refresh, - label: "Refresh", - routeHint: new CultMeshRouteHint( - CultMeshLocalityKind.Automatic, - "cultmesh"))); - - return new EveSurfaceDocument( - ProviderId, - "game.runtime", - "Aetheria Catalog", - version, - updatedAtUtc, - new EveSurfaceTree( - CatalogSurfaceId, - Node( - "aetheria.catalog.root", - "surface", - [], - Node( - "aetheria.catalog.summary", - "grid", - [("columns", "6")], - Metric("summary.items", "Items", catalog.Items.Count.ToString()), - Metric("summary.trade", "Trade Items", catalog.TradeItems.Count().ToString()), - Metric("summary.equipment", "Equipment", catalog.EquipmentItems.Count().ToString()), - Metric("summary.behaviors", "Behavior Kinds", catalog.Items.SelectMany(item => item.BehaviorKinds).Distinct().Count().ToString()), - Metric("summary.corporations", "Corporations", catalog.Corporations.Count.ToString()), - Metric("summary.nameFiles", "Name Files", catalog.NameFiles.Count.ToString())), - Node( - "aetheria.catalog.trade", - "card", - [("title", "Trade Catalog")], - Node("aetheria.catalog.trade.rows", "inspector.kv", [], tradeItems)), - Node( - "aetheria.catalog.corporations", - "card", - [("title", "Corporations")], - Node("aetheria.catalog.corporation.rows", "inspector.kv", [], corporations))), - Array.Empty()), - new[] - { - new EveCommandTemplate(refreshCommand.ToBinding()) - }); - } - - public static EveSurfaceDocument BuildOperationsSurface( - AetheriaEveCommandAcceptanceStatus? eveCommandStatus = null, - AetheriaVerseHostSettings? verseHostSettings = null, - AetheriaRuntimeSession? runtimeSession = null, - long version = 1) - { - var normalizedVerseHost = AetheriaVerseHostSettingsNormalizer.Normalize(verseHostSettings); - var updatedAtUtc = LatestTimestamp( - eveCommandStatus?.LastPollAtUtc, - normalizedVerseHost.LastUpdatedAtUtc, - runtimeSession?.LastSeenAtUtc); - var refreshCommand = CultMesh.OperationBindingRecord( - CultMesh.OperationBinding( - AetheriaRuntimeOperationsCommands.Refresh, - label: "Refresh", - routeHint: new CultMeshRouteHint( - CultMeshLocalityKind.Automatic, - "cultmesh"))); - return new EveSurfaceDocument( - ProviderId, - "game.runtime", - "Aetheria Operations", - version, - updatedAtUtc, - new EveSurfaceTree( - OperationsSurfaceId, - Node( - "aetheria.operations.root", - "surface", - [], - Node( - "aetheria.operations.eveCommandAcceptance", - "card", - [("title", "Eve Request Acceptance")], - Metric("eveCommandAcceptance.status", "Status", eveCommandStatus?.Status ?? "missing"), - Metric("eveCommandAcceptance.observed", "Observed Before Accept", (eveCommandStatus?.ObservedBeforeAccept ?? 0).ToString()), - Metric("eveCommandAcceptance.accepted", "Commands Accepted", (eveCommandStatus?.CommandsAccepted ?? 0).ToString()), - Metric("eveCommandAcceptance.rejected", "Commands Rejected", (eveCommandStatus?.CommandsRejected ?? 0).ToString()), - Metric("eveCommandAcceptance.catalogRefreshes", "Catalog Refreshes", (eveCommandStatus?.AppliedCatalogRefreshes ?? 0).ToString()), - Metric("eveCommandAcceptance.operationsRefreshes", "Operations Refreshes", (eveCommandStatus?.AppliedOperationsRefreshes ?? 0).ToString()), - Metric("eveCommandAcceptance.playerSettings", "Player Settings Commands", (eveCommandStatus?.AppliedPlayerSettingsCommands ?? 0).ToString()), - Metric("eveCommandAcceptance.inputSettings", "Input Settings Commands", (eveCommandStatus?.AppliedInputSettingsCommands ?? 0).ToString()), - Metric("eveCommandAcceptance.loadoutTemplates", "Loadout Template Commands", (eveCommandStatus?.AppliedLoadoutTemplateCommands ?? 0).ToString()), - Metric("eveCommandAcceptance.verseHost", "Verse Host Commands", (eveCommandStatus?.AppliedVerseHostCommands ?? 0).ToString()), - Metric("eveCommandAcceptance.failures", "Consecutive Failures", (eveCommandStatus?.ConsecutiveFailures ?? 0).ToString()), - Row( - "eveCommandAcceptance.last", - ("runtime", eveCommandStatus?.RuntimeId ?? ""), - ("lastPoll", eveCommandStatus?.LastPollAtUtc ?? ""), - ("lastAccepted", eveCommandStatus?.LastAcceptedAtUtc ?? ""), - ("lastRejected", eveCommandStatus?.LastRejectedCommand ?? ""), - ("rejectedReason", eveCommandStatus?.LastRejectedReason ?? ""), - ("error", eveCommandStatus?.LastError ?? ""))), - Node( - "aetheria.operations.verseHost", - "card", - [("title", "Verse Host")], - Metric("verseHost.visibility", "Visibility", normalizedVerseHost.Visibility), - Metric("verseHost.service", "Service", normalizedVerseHost.ServiceId), - Metric("verseHost.verse", "Verse", normalizedVerseHost.VerseId), - Row( - "verseHost.identity", - ("rootVerse", normalizedVerseHost.RootVerse), - ("canonicalService", normalizedVerseHost.CanonicalService), - ("locatedService", normalizedVerseHost.LocatedService), - ("cultMeshAddress", normalizedVerseHost.CultMeshAddress))), - Node( - "aetheria.operations.runtimeSession", - "card", - [("title", "Runtime Session")], - Metric("runtimeSession.status", "Status", runtimeSession?.Status ?? "missing"), - Metric("runtimeSession.role", "Role", runtimeSession?.Role ?? ""), - Row( - "runtimeSession.last", - ("runtime", runtimeSession?.RuntimeId ?? ""), - ("started", runtimeSession?.StartedAtUtc ?? ""), - ("lastSeen", runtimeSession?.LastSeenAtUtc ?? "")))), - Array.Empty()), - new[] - { - new EveCommandTemplate(refreshCommand.ToBinding()) - }); - } - - public static EveSurfaceDocument BuildPlayerSettingsSurface( - AetheriaPlayerSettings? settings, - string updatedAtUtc, - long version = 1) - { - settings ??= new AetheriaPlayerSettings(); - var gameplay = settings.Gameplay ?? new AetheriaPlayerGameplaySettings(); - var graphics = settings.Graphics ?? new AetheriaPlayerGraphicsSettings(); - var publishedAtUtc = !string.IsNullOrWhiteSpace(settings.LastUpdatedAtUtc) - ? settings.LastUpdatedAtUtc - : updatedAtUtc; - - var surface = AetheriaRuntimePlayerSettingsSurfaceBuilder.Build( - settings.PlayerName, - settings.TutorialPassed, - settings.ActiveRunKey, - gameplay.TemperatureUnit, - gameplay.SignificantDigits, - graphics.NebulaQuality, - graphics.ShowAsteroidsInMinimap, - publishedAtUtc, - version); - - return AetheriaRuntimeSurfaceDocuments.ToPortableSurface(surface); - } - - public static EveProviderAdvertisementDocument BuildProviderAdvertisement( - AetheriaVerseHostSettings settings, - string statePath, - string updatedAtUtc) - { - var normalized = AetheriaVerseHostSettingsNormalizer.Normalize(settings); - var schemas = new[] - { - "aetheria.world_state.v1", - "aetheria.item_definition.v1", - "aetheria.corporation.v2", - "aetheria.name_file.v2", - "aetheria.trade_value_policy.v1", - "aetheria.player_settings.v1", - "aetheria.game_session.v1", - "aetheria.loadout_template.v1", - "aetheria.run_state.v1", - "aetheria.zone_state.v1", - "aetheria.entity_snapshot.v1", - "aetheria.verse_host_settings.v1", - "aetheria.runtime_session.v1", - "aetheria.eve_command_acceptance_status.v1", - EveSurfaceDocument.SchemaId, - AetheriaRuntimeEveCommandDocument.SchemaId, - AetheriaRuntimeDaemonSchemas.ProviderAdvertisement, - AetheriaRuntimeDaemonSchemas.Frame, - AetheriaRuntimeDaemonSchemas.SoaView, - AetheriaRuntimeDaemonSchemas.Health, - AetheriaRuntimeDaemonSchemas.CommandBoundary, - AetheriaRuntimeDaemonSchemas.GameSurface, - AetheriaRuntimeDaemonSchemas.EditorSurface, - AetheriaRuntimeDaemonSchemas.Command - }; - var witnesses = new[] - { - Witness("cultcache", statePath, "Aetheria typed CultCache state file"), - Witness(DaemonRecordTransport, AetheriaRuntimeVerseRecordKeys.DaemonProviderAdvertisement.ToString(), - "Aetheria daemon-owned provider advertisement record"), - Witness(DaemonRecordTransport, AetheriaRuntimeVerseRecordKeys.DaemonFrameLatest.ToString(), - "Aetheria daemon latest simulation frame record"), - Witness(DaemonRecordTransport, AetheriaRuntimeVerseRecordKeys.DaemonSoaViewLatest.ToString(), - "Aetheria daemon latest SoA view record for thin clients"), - Witness(DaemonRecordTransport, AetheriaRuntimeVerseRecordKeys.DaemonHealth.ToString(), - "Aetheria daemon health record"), - Witness(DaemonRecordTransport, AetheriaRuntimeVerseRecordKeys.DaemonCommandBoundary.ToString(), - "Aetheria daemon typed command boundary record"), - Witness(DaemonRecordTransport, AetheriaRuntimeVerseRecordKeys.DaemonGameSurface.ToString(), - "Aetheria daemon game Eve GUI surface record"), - Witness(DaemonRecordTransport, AetheriaRuntimeVerseRecordKeys.DaemonGameTuiSurface.ToString(), - "Aetheria daemon game Eve TUI surface record"), - Witness(DaemonRecordTransport, AetheriaRuntimeVerseRecordKeys.DaemonEditorSurface.ToString(), - "Aetheria daemon editor Eve GUI surface record"), - Witness(DaemonRecordTransport, AetheriaRuntimeVerseRecordKeys.DaemonEditorTuiSurface.ToString(), - "Aetheria daemon editor Eve TUI surface record") - }; - var commands = new[] - { - Command(DaemonCommandBoundaryId, "", "Aetheria daemon typed command boundary"), - Command(AetheriaRuntimeCatalogCommands.Refresh, AetheriaRuntimeCatalogCommands.SurfaceId, "Refresh catalog state"), - Command(AetheriaRuntimeOperationsCommands.Refresh, AetheriaRuntimeOperationsCommands.SurfaceId, "Refresh operations state"), - Command(AetheriaRuntimePlayerSettingsCommands.Refresh, AetheriaRuntimePlayerSettingsCommands.SurfaceId, "Refresh player settings state"), - Command(AetheriaRuntimePlayerSettingsCommands.CycleTemperatureUnit, AetheriaRuntimePlayerSettingsCommands.SurfaceId, "Cycle the typed player temperature unit"), - Command(AetheriaRuntimePlayerSettingsCommands.DecrementSignificantDigits, AetheriaRuntimePlayerSettingsCommands.SurfaceId, "Decrease typed player significant digits"), - Command(AetheriaRuntimePlayerSettingsCommands.IncrementSignificantDigits, AetheriaRuntimePlayerSettingsCommands.SurfaceId, "Increase typed player significant digits"), - Command(AetheriaRuntimePlayerSettingsCommands.CycleNebulaQuality, AetheriaRuntimePlayerSettingsCommands.SurfaceId, "Cycle typed player nebula quality"), - Command(AetheriaRuntimePlayerSettingsCommands.ToggleShowAsteroidsInMinimap, AetheriaRuntimePlayerSettingsCommands.SurfaceId, "Toggle typed player minimap asteroid visibility") - }; - - return new EveProviderAdvertisementDocument( - ProviderId, - normalized.ServiceId, - normalized.VerseId, - normalized.Title, - "game.runtime", - normalized.CultMeshAddress, - updatedAtUtc, - new EveProviderFreshness("fresh", updatedAtUtc, 15000), - schemas, - witnesses, - AetheriaRuntimeEveSurfaceCatalog.All.Select(ToProviderSurface).ToArray(), - commands, - new[] { AetheriaRuntimeDaemonSoaFramePublisher.ProducerId }); - } - - private static EveProviderWitness Witness(string kind, string reference, string summary) => - new(kind, reference, summary); - - private static EveAdvertisedCommand Command(string command, string surfaceId, string summary) => - new(command, surfaceId, "cultmesh", summary); - - private static EveAdvertisedSurface ToProviderSurface(AetheriaRuntimeEveSurfaceAdvertisement surface) - { - var interaction = surface.WorldInteraction == null - ? null - : new EveWorldInteractionAdvertisement( - surface.WorldInteraction.ProjectionKind, - surface.WorldInteraction.StateSchemas, - surface.WorldInteraction.CommandBoundary, - AetheriaRuntimeVerseRecordKeys.DaemonCommandBoundary.ToString(), - EveCommandReceiptDocument.SchemaId, - AetheriaRuntimeVerseRecordKeys.EveReceiptRecordPrefix, - AetheriaRuntimeVerseRecordKeys.EveAssetCatalog.ToString(), - surface.WorldInteraction.LoweringTargets, - surface.WorldInteraction.Ownership); - return new EveAdvertisedSurface( - surface.SurfaceId, - EveSurfaceDocument.SchemaId, - surface.RecordRef, - surface.Transport, - surface.Status, - surface.SurfaceKind, - interaction); - } - - - private static string LatestTimestamp(params string?[] timestamps) - { - var latest = ""; - foreach (var timestamp in timestamps) - { - if (!string.IsNullOrWhiteSpace(timestamp) && - (string.IsNullOrWhiteSpace(latest) || string.CompareOrdinal(timestamp, latest) > 0)) - { - latest = timestamp; - } - } - - return latest; - } - - private static EveSurfaceComponent Metric(string id, string label, string value) - { - return Node(id, "metric", [("label", label), ("value", value)]); - } - - private static EveSurfaceComponent Row(string id, params (string Key, string Value)[] props) - { - return Node(id, "row", props); - } - - private static EveSurfaceComponent Node( - string id, - string kind, - (string Key, string Value)[] props, - params EveSurfaceComponent[] children) - { - return new EveSurfaceComponent( - id, - kind, - props.ToDictionary(prop => prop.Key, prop => prop.Value), - children); - } - - private static string SafeId(string value) - { - return string.IsNullOrWhiteSpace(value) - ? "empty" - : new string(value.Select(character => char.IsLetterOrDigit(character) ? character : '.').ToArray()); - } -} diff --git a/Aetheria.State/AetheriaHangar.cs b/Aetheria.State/AetheriaHangar.cs deleted file mode 100644 index 582b03385..000000000 --- a/Aetheria.State/AetheriaHangar.cs +++ /dev/null @@ -1,434 +0,0 @@ -using Aetheria.State.Documents; -using GameCult.Aetheria.State.Verse; - -namespace Aetheria.State; - -public static class AetheriaHangar -{ - private static readonly SemaphoreSlim AdmissionGate = new(1, 1); - - public static async Task AdmitAsync( - AetheriaStateNode node, - AetheriaDeploymentRequest request, - string now) - { - if (node == null) throw new ArgumentNullException(nameof(node)); - if (request == null) throw new ArgumentNullException(nameof(request)); - - await AdmissionGate.WaitAsync().ConfigureAwait(false); - try - { - var pointer = node.MutableDocument(AetheriaStateNode.HangarKey); - var hangar = await pointer.ReadAsync().ConfigureAwait(false) - ?? throw new InvalidOperationException("The canonical Hangar document does not exist."); - var loadout = string.IsNullOrWhiteSpace(request.LoadoutTemplateKey) - ? null - : await node.MutableDocument(new(request.LoadoutTemplateKey)) - .ReadAsync().ConfigureAwait(false); - var updated = Clone(hangar); - var receipt = Admit(updated, request, loadout, now); - if (!receipt.Accepted) - return receipt; - - await pointer.ReplaceAsync(updated).ConfigureAwait(false); - await node.FlushAsync().ConfigureAwait(false); - return receipt; - } - finally - { - AdmissionGate.Release(); - } - } - - public static AetheriaDeploymentReceipt Admit( - AetheriaHangarState hangar, - AetheriaDeploymentRequest request, - AetheriaLoadoutTemplate? loadout, - string now) - { - if (hangar == null) throw new ArgumentNullException(nameof(hangar)); - if (request == null) throw new ArgumentNullException(nameof(request)); - - var prior = (hangar.Deployments ?? []).FirstOrDefault(value => - string.Equals(value.RequestId, request.RequestId, StringComparison.Ordinal)); - if (prior != null) - return prior; - - var rejection = Validate(hangar, request, loadout); - if (rejection != null) - return Receipt(hangar, request, loadout, now, false, rejection, hangar.Revision); - - var ship = hangar.Ships.Single(value => - string.Equals(value.ShipId, request.ShipId, StringComparison.Ordinal)); - var revision = checked(hangar.Revision + 1); - var receipt = Receipt(hangar, request, loadout, now, true, "", revision); - - ship.Status = AetheriaHangarShipStatuses.Deployed; - ship.ActiveDeploymentId = receipt.DeploymentId; - ship.LoadoutTemplateKey = request.LoadoutTemplateKey; - hangar.Revision = revision; - hangar.UpdatedAtUtc = now; - hangar.Deployments = (hangar.Deployments ?? []).Append(receipt).ToArray(); - return receipt; - } - - public static async Task EquipAsync( - AetheriaStateNode node, - string shipId, - string itemKey, - long expectedRevision, - AetheriaRuntimeCatalogSnapshot catalog, - string now) - { - await AdmissionGate.WaitAsync().ConfigureAwait(false); - try - { - var pointer = node.MutableDocument(AetheriaStateNode.HangarKey); - var hangar = await pointer.ReadAsync().ConfigureAwait(false) - ?? throw new InvalidOperationException("The canonical Hangar document does not exist."); - var rejection = ValidateRefit(hangar, shipId, expectedRevision, out var ship); - if (rejection != null) return new(false, rejection, hangar.Revision); - var stack = (hangar.Inventory ?? []).SingleOrDefault(value => - string.Equals(value.ItemKey, itemKey, StringComparison.Ordinal) && value.Quantity > 0); - if (stack == null) return new(false, "item is not available in Hangar inventory", hangar.Revision); - var templatePointer = node.MutableDocument(new(ship!.LoadoutTemplateKey)); - var template = await templatePointer.ReadAsync().ConfigureAwait(false); - if (template == null) return new(false, "ship loadout template is missing", hangar.Revision); - - var destination = ToRuntimeEntity(template.RootEntity); - var source = new AetheriaRuntimeEntitySnapshotCommit - { - Equipment = - [ - new AetheriaRuntimeLoadoutItemSlotCommit - { - Item = new AetheriaRuntimeLoadoutItemCommit - { - ItemKey = itemKey, - Quantity = 1, - Quality = 1, - Durability = 1, - Enabled = true - } - } - ] - }; - if (!AetheriaRuntimeRefitTransactions.TryEquip( - source, - AetheriaRuntimeRefitSourceKinds.Equipment, - 0, - 0, - 0, - destination, - itemKey, - 0, - 0, - false, - catalog, - out rejection)) - return new(false, rejection, hangar.Revision); - - var updatedHangar = Clone(hangar); - var updatedStack = updatedHangar.Inventory.Single(value => string.Equals(value.ItemKey, itemKey, StringComparison.Ordinal)); - updatedStack.Quantity--; - updatedHangar.Inventory = updatedHangar.Inventory.Where(value => value.Quantity > 0).ToArray(); - updatedHangar.Revision = checked(updatedHangar.Revision + 1); - updatedHangar.UpdatedAtUtc = now; - var updatedTemplate = AetheriaRuntimeStateMapper.ToLoadoutTemplate( - new AetheriaRuntimeLoadoutTemplateCommit - { - Name = template.Name, - OwnerPlayerKey = template.OwnerPlayerKey, - RootEntity = ToRuntimeLoadout(destination) - }, - now); - updatedTemplate.CreatedAtUtc = template.CreatedAtUtc; - await templatePointer.ReplaceAsync(updatedTemplate).ConfigureAwait(false); - await pointer.ReplaceAsync(updatedHangar).ConfigureAwait(false); - await node.FlushAsync().ConfigureAwait(false); - return new(true, "", updatedHangar.Revision); - } - finally - { - AdmissionGate.Release(); - } - } - - public static async Task RemoveAsync( - AetheriaStateNode node, - string shipId, - int equipmentIndex, - long expectedRevision, - string now) - { - await AdmissionGate.WaitAsync().ConfigureAwait(false); - try - { - var pointer = node.MutableDocument(AetheriaStateNode.HangarKey); - var hangar = await pointer.ReadAsync().ConfigureAwait(false) - ?? throw new InvalidOperationException("The canonical Hangar document does not exist."); - var rejection = ValidateRefit(hangar, shipId, expectedRevision, out var ship); - if (rejection != null) return new(false, rejection, hangar.Revision); - var templatePointer = node.MutableDocument(new(ship!.LoadoutTemplateKey)); - var template = await templatePointer.ReadAsync().ConfigureAwait(false); - if (template == null) return new(false, "ship loadout template is missing", hangar.Revision); - var equipment = (template.RootEntity.Equipment ?? []).ToList(); - if (equipmentIndex < 0 || equipmentIndex >= equipment.Count) - return new(false, "equipment index is outside the configured loadout", hangar.Revision); - var removed = equipment[equipmentIndex]; - if (string.IsNullOrWhiteSpace(removed.Item?.ItemKey)) - return new(false, "configured equipment item is missing", hangar.Revision); - equipment.RemoveAt(equipmentIndex); - - var updatedTemplate = CloneTemplate(template); - updatedTemplate.RootEntity.Equipment = equipment.ToArray(); - updatedTemplate.RootEntity.WeaponGroups = (updatedTemplate.RootEntity.WeaponGroups ?? []) - .Select(group => (group ?? []) - .Where(index => index != equipmentIndex) - .Select(index => index > equipmentIndex ? index - 1 : index) - .ToArray()) - .ToArray(); - updatedTemplate.UpdatedAtUtc = now; - var updatedHangar = Clone(hangar); - var stack = updatedHangar.Inventory.FirstOrDefault(value => - string.Equals(value.ItemKey, removed.Item.ItemKey, StringComparison.Ordinal)); - if (stack == null) - updatedHangar.Inventory = updatedHangar.Inventory.Append(new AetheriaHangarItemStack - { - ItemKey = removed.Item.ItemKey, - Quantity = 1 - }).ToArray(); - else - stack.Quantity++; - updatedHangar.Revision = checked(updatedHangar.Revision + 1); - updatedHangar.UpdatedAtUtc = now; - await templatePointer.ReplaceAsync(updatedTemplate).ConfigureAwait(false); - await pointer.ReplaceAsync(updatedHangar).ConfigureAwait(false); - await node.FlushAsync().ConfigureAwait(false); - return new(true, "", updatedHangar.Revision); - } - finally - { - AdmissionGate.Release(); - } - } - - private static string? ValidateRefit( - AetheriaHangarState hangar, - string shipId, - long expectedRevision, - out AetheriaHangarShip? ship) - { - ship = null; - if (expectedRevision != hangar.Revision) return "hangar revision mismatch"; - ship = (hangar.Ships ?? []).SingleOrDefault(value => string.Equals(value.ShipId, shipId, StringComparison.Ordinal)); - if (ship == null) return "ship is not owned by Hangar"; - if (!string.Equals(ship.Status, AetheriaHangarShipStatuses.Available, StringComparison.Ordinal)) - return "deployed ship cannot be refit from the Hangar"; - if (string.IsNullOrWhiteSpace(ship.LoadoutTemplateKey)) return "ship has no configured loadout"; - return null; - } - - private static string? Validate( - AetheriaHangarState hangar, - AetheriaDeploymentRequest request, - AetheriaLoadoutTemplate? loadout) - { - if (string.IsNullOrWhiteSpace(request.RequestId)) return "deployment request id is required"; - if (!AetheriaGameModes.IsKnown(request.Mode)) return "unknown game mode"; - if (!string.Equals(hangar.PlayerKey, request.PlayerKey, StringComparison.Ordinal)) return "player does not own hangar"; - if (request.ExpectedHangarRevision != hangar.Revision) return "hangar revision mismatch"; - if (!string.Equals(request.ModePolicyId, AetheriaModePolicies.ForMode(request.Mode), StringComparison.Ordinal)) - return "mode policy does not match game mode"; - - var ships = (hangar.Ships ?? []).Where(value => - string.Equals(value.ShipId, request.ShipId, StringComparison.Ordinal)).ToArray(); - if (ships.Length != 1) return "deployment ship is not uniquely owned by hangar"; - if (!string.Equals(ships[0].Status, AetheriaHangarShipStatuses.Available, StringComparison.Ordinal)) - return "deployment ship is not available"; - if (!(hangar.LoadoutTemplateKeys ?? []).Contains(request.LoadoutTemplateKey, StringComparer.Ordinal)) - return "loadout template is not owned by hangar"; - if (loadout == null) return "loadout template is missing"; - if (!string.Equals(loadout.OwnerPlayerKey, request.PlayerKey, StringComparison.Ordinal)) - return "player does not own loadout template"; - if (!string.Equals(loadout.RootEntity.Hull.ItemKey, ships[0].HullItemKey, StringComparison.Ordinal)) - return "loadout hull does not match deployed ship"; - return null; - } - - private static AetheriaDeploymentReceipt Receipt( - AetheriaHangarState hangar, - AetheriaDeploymentRequest request, - AetheriaLoadoutTemplate? loadout, - string now, - bool accepted, - string diagnostic, - long revision) => new() - { - DeploymentId = accepted ? $"deployment:{hangar.HangarId}:{revision}" : "", - RequestId = request.RequestId, - Accepted = accepted, - Diagnostic = diagnostic, - PlayerKey = request.PlayerKey, - Mode = request.Mode, - ShipId = request.ShipId, - LoadoutTemplateKey = request.LoadoutTemplateKey, - HangarRevision = revision, - ModePolicyId = request.ModePolicyId, - Loadout = accepted ? Clone(loadout!.RootEntity) : new AetheriaRuntimeEntityLoadoutCommit(), - CommittedAtUtc = now - }; - - private static AetheriaRuntimeEntityLoadoutCommit Clone(AetheriaEntityLoadout source) => new() - { - Name = source.Name, - Kind = source.Kind, - FactionKey = source.FactionKey, - Hull = Clone(source.Hull), - Equipment = (source.Equipment ?? []).Select(Clone).ToArray(), - CargoBays = (source.CargoBays ?? []).Select(Clone).ToArray(), - DockingBays = (source.DockingBays ?? []).Select(Clone).ToArray(), - CargoContents = (source.CargoContents ?? []).Select(Clone).ToArray(), - DockingBayContents = (source.DockingBayContents ?? []).Select(Clone).ToArray(), - DockingBayAssignments = (source.DockingBayAssignments ?? []).ToArray(), - WeaponGroups = (source.WeaponGroups ?? []).Select(group => (group ?? []).ToArray()).ToArray(), - Children = (source.Children ?? []).Select(Clone).ToArray() - }; - - private static AetheriaHangarState Clone(AetheriaHangarState source) => new() - { - Name = source.Name, - HangarId = source.HangarId, - PlayerKey = source.PlayerKey, - Revision = source.Revision, - Ships = (source.Ships ?? []).Select(ship => new AetheriaHangarShip - { - ShipId = ship.ShipId, - HullItemKey = ship.HullItemKey, - LoadoutTemplateKey = ship.LoadoutTemplateKey, - Status = ship.Status, - ActiveDeploymentId = ship.ActiveDeploymentId - }).ToArray(), - Inventory = (source.Inventory ?? []).Select(item => new AetheriaHangarItemStack - { - ItemKey = item.ItemKey, - Quantity = item.Quantity - }).ToArray(), - Currencies = (source.Currencies ?? []).Select(currency => new AetheriaHangarCurrency - { - CurrencyKey = currency.CurrencyKey, - Quantity = currency.Quantity - }).ToArray(), - UnlockKeys = (source.UnlockKeys ?? []).ToArray(), - LoadoutTemplateKeys = (source.LoadoutTemplateKeys ?? []).ToArray(), - Deployments = (source.Deployments ?? []).ToArray(), - UpdatedAtUtc = source.UpdatedAtUtc - }; - - private static AetheriaRuntimeLoadoutItemSlotCommit Clone(AetheriaLoadoutItemSlot source) => new() - { - X = source.Position.X, - Y = source.Position.Y, - Rotation = source.Rotation, - Item = Clone(source.Item) - }; - - private static AetheriaRuntimeCargoBayLoadoutCommit Clone(AetheriaCargoBayLoadout source) => new() - { - Items = (source.Items ?? []).Select(Clone).ToArray() - }; - - private static AetheriaRuntimeLoadoutItemCommit Clone(AetheriaLoadoutItem source) => new() - { - ItemKey = source.ItemKey, - Quality = source.Quality, - Durability = source.Durability, - Quantity = source.Quantity, - Enabled = source.Enabled, - OverrideShutdown = source.OverrideShutdown, - Temperature = source.Temperature - }; - - private static AetheriaRuntimeEntitySnapshotCommit ToRuntimeEntity(AetheriaEntityLoadout source) - { - var loadout = ToRuntimeLoadout(source); - return new AetheriaRuntimeEntitySnapshotCommit - { - Name = loadout.Name, - Kind = loadout.Kind, - FactionKey = loadout.FactionKey, - HullItemKey = loadout.Hull.ItemKey, - Equipment = loadout.Equipment, - CargoBays = loadout.CargoBays, - DockingBays = loadout.DockingBays, - CargoContents = loadout.CargoContents, - DockingBayContents = loadout.DockingBayContents, - DockingBayAssignments = loadout.DockingBayAssignments, - WeaponGroups = loadout.WeaponGroups - }; - } - - private static AetheriaRuntimeEntityLoadoutCommit ToRuntimeLoadout(AetheriaRuntimeEntitySnapshotCommit source) => new() - { - Name = source.Name, - Kind = source.Kind, - FactionKey = source.FactionKey, - Hull = new AetheriaRuntimeLoadoutItemCommit - { - ItemKey = source.HullItemKey, - Quantity = 1, - Quality = 1, - Durability = 1, - Enabled = true - }, - Equipment = source.Equipment ?? [], - CargoBays = source.CargoBays ?? [], - DockingBays = source.DockingBays ?? [], - CargoContents = source.CargoContents ?? [], - DockingBayContents = source.DockingBayContents ?? [], - DockingBayAssignments = source.DockingBayAssignments ?? [], - WeaponGroups = source.WeaponGroups ?? [] - }; - - private static AetheriaRuntimeEntityLoadoutCommit ToRuntimeLoadout(AetheriaEntityLoadout source) => new() - { - Name = source.Name, - Kind = source.Kind, - FactionKey = source.FactionKey, - Hull = Clone(source.Hull), - Equipment = (source.Equipment ?? []).Select(Clone).ToArray(), - CargoBays = (source.CargoBays ?? []).Select(Clone).ToArray(), - DockingBays = (source.DockingBays ?? []).Select(Clone).ToArray(), - CargoContents = (source.CargoContents ?? []).Select(Clone).ToArray(), - DockingBayContents = (source.DockingBayContents ?? []).Select(Clone).ToArray(), - DockingBayAssignments = (source.DockingBayAssignments ?? []).ToArray(), - WeaponGroups = (source.WeaponGroups ?? []).Select(group => (IReadOnlyList)(group ?? []).ToArray()).ToArray(), - Children = (source.Children ?? []).Select(ToRuntimeLoadout).ToArray() - }; - - private static AetheriaLoadoutTemplate CloneTemplate(AetheriaLoadoutTemplate source) - { - var clone = AetheriaRuntimeStateMapper.ToLoadoutTemplate(new AetheriaRuntimeLoadoutTemplateCommit - { - Name = source.Name, - OwnerPlayerKey = source.OwnerPlayerKey, - RootEntity = ToRuntimeLoadout(source.RootEntity) - }, source.UpdatedAtUtc); - clone.CreatedAtUtc = source.CreatedAtUtc; - return clone; - } -} - -public sealed class AetheriaHangarMutationResult -{ - public AetheriaHangarMutationResult(bool accepted, string diagnostic, long hangarRevision) - { - Accepted = accepted; - Diagnostic = diagnostic; - HangarRevision = hangarRevision; - } - - public bool Accepted { get; } - public string Diagnostic { get; } - public long HangarRevision { get; } -} diff --git a/Aetheria.State/AetheriaRuntimeStateMapper.cs b/Aetheria.State/AetheriaRuntimeStateMapper.cs deleted file mode 100644 index 847de4f4d..000000000 --- a/Aetheria.State/AetheriaRuntimeStateMapper.cs +++ /dev/null @@ -1,215 +0,0 @@ -using Aetheria.State.Documents; -using GameCult.Aetheria.State.Verse; -using GameCult.Caching; - -namespace Aetheria.State; - -public static class AetheriaRuntimeStateMapper -{ - public static AetheriaTradeValuePolicy ToTradeValuePolicy( - AetheriaRuntimeTradeValueSettings settings, - string updatedAtUtc) - { - return new AetheriaTradeValuePolicy - { - QualityPriceModifier = new AetheriaExponentialLerp - { - Exponent = settings.QualityPriceModifier.Exponent, - Minimum = settings.QualityPriceModifier.Minimum, - Maximum = settings.QualityPriceModifier.Maximum - }, - Tiers = settings.Tiers - .Select(tier => new AetheriaItemRarityTier - { - Name = tier.Name, - Quality = tier.Quality, - Red = tier.Red, - Green = tier.Green, - Blue = tier.Blue - }) - .ToArray(), - UpdatedAtUtc = updatedAtUtc - }; - } - - public static AetheriaRuntimeTradeValueSettings ToRuntimeTradeValueSettings( - AetheriaTradeValuePolicy? policy) - { - if (policy == null) - return AetheriaRuntimeTradeValueSettings.Default; - - return new AetheriaRuntimeTradeValueSettings( - new AetheriaRuntimeExponentialLerp( - policy.QualityPriceModifier?.Exponent ?? 1, - policy.QualityPriceModifier?.Minimum ?? 1, - policy.QualityPriceModifier?.Maximum ?? 1), - (policy.Tiers ?? Array.Empty()) - .Select(tier => new AetheriaRuntimeItemRarityTier( - tier.Name, - tier.Quality, - tier.Red, - tier.Green, - tier.Blue)) - .ToArray()); - } - - public static AetheriaLoadoutTemplate ToLoadoutTemplate( - AetheriaRuntimeLoadoutTemplateCommit loadout, - string updatedAtUtc) - { - return new AetheriaLoadoutTemplate - { - Name = loadout.Name ?? "", - OwnerPlayerKey = loadout.OwnerPlayerKey ?? "", - RootEntity = ToEntityLoadout(loadout.RootEntity), - CreatedAtUtc = updatedAtUtc, - UpdatedAtUtc = updatedAtUtc - }; - } - - public static AetheriaRuntimeLoadoutTemplateCommit ToRuntimeLoadoutTemplate( - AetheriaLoadoutTemplate loadout) - { - if (loadout == null) throw new ArgumentNullException(nameof(loadout)); - return new AetheriaRuntimeLoadoutTemplateCommit - { - Name = loadout.Name, - OwnerPlayerKey = loadout.OwnerPlayerKey, - RootEntity = ToRuntimeEntityLoadout(loadout.RootEntity) - }; - } - - public static CultRecordKey LoadoutKey(string name) - { - return new CultRecordKey($"global:aetheria.loadout_template.{StableToken(name)}.v1"); - } - - private static AetheriaEntityLoadout ToEntityLoadout(AetheriaRuntimeEntityLoadoutCommit? entity) - { - entity ??= new AetheriaRuntimeEntityLoadoutCommit(); - return new AetheriaEntityLoadout - { - Name = entity.Name ?? "", - Kind = entity.Kind ?? "", - FactionKey = entity.FactionKey ?? "", - Hull = ToLoadoutItem(entity.Hull), - Equipment = ToItemSlots(entity.Equipment), - CargoBays = ToItemSlots(entity.CargoBays), - DockingBays = ToItemSlots(entity.DockingBays), - CargoContents = ToCargoBays(entity.CargoContents), - DockingBayContents = ToCargoBays(entity.DockingBayContents), - DockingBayAssignments = ToIntArray(entity.DockingBayAssignments), - WeaponGroups = ToIntArrayArray(entity.WeaponGroups), - Children = (entity.Children ?? Array.Empty()) - .Select(ToEntityLoadout) - .ToArray() - }; - } - - private static AetheriaRuntimeEntityLoadoutCommit ToRuntimeEntityLoadout(AetheriaEntityLoadout entity) => new() - { - Name = entity.Name, - Kind = entity.Kind, - FactionKey = entity.FactionKey, - Hull = ToRuntimeLoadoutItem(entity.Hull), - Equipment = (entity.Equipment ?? []).Select(ToRuntimeItemSlot).ToArray(), - CargoBays = (entity.CargoBays ?? []).Select(ToRuntimeItemSlot).ToArray(), - DockingBays = (entity.DockingBays ?? []).Select(ToRuntimeItemSlot).ToArray(), - CargoContents = (entity.CargoContents ?? []).Select(value => new AetheriaRuntimeCargoBayLoadoutCommit - { - Items = (value.Items ?? []).Select(ToRuntimeItemSlot).ToArray() - }).ToArray(), - DockingBayContents = (entity.DockingBayContents ?? []).Select(value => new AetheriaRuntimeCargoBayLoadoutCommit - { - Items = (value.Items ?? []).Select(ToRuntimeItemSlot).ToArray() - }).ToArray(), - DockingBayAssignments = (entity.DockingBayAssignments ?? []).ToArray(), - WeaponGroups = (entity.WeaponGroups ?? []).Select(value => (IReadOnlyList)(value ?? []).ToArray()).ToArray(), - Children = (entity.Children ?? []).Select(ToRuntimeEntityLoadout).ToArray() - }; - - private static AetheriaRuntimeLoadoutItemSlotCommit ToRuntimeItemSlot(AetheriaLoadoutItemSlot slot) => new() - { - X = slot.Position?.X ?? 0, - Y = slot.Position?.Y ?? 0, - Rotation = slot.Rotation, - Item = ToRuntimeLoadoutItem(slot.Item) - }; - - private static AetheriaRuntimeLoadoutItemCommit ToRuntimeLoadoutItem(AetheriaLoadoutItem item) => new() - { - ItemKey = item.ItemKey, - Quality = item.Quality, - Durability = item.Durability, - Quantity = item.Quantity, - Enabled = item.Enabled, - OverrideShutdown = item.OverrideShutdown, - Temperature = item.Temperature - }; - - private static AetheriaLoadoutItem ToLoadoutItem(AetheriaRuntimeLoadoutItemCommit? item) - { - item ??= new AetheriaRuntimeLoadoutItemCommit(); - return new AetheriaLoadoutItem - { - ItemKey = item.ItemKey ?? "", - Quality = item.Quality, - Durability = item.Durability, - Quantity = item.Quantity, - Enabled = item.Enabled, - OverrideShutdown = item.OverrideShutdown, - Temperature = item.Temperature - }; - } - - private static AetheriaLoadoutItemSlot[] ToItemSlots( - IReadOnlyList? slots) - { - return (slots ?? Array.Empty()) - .Select(slot => new AetheriaLoadoutItemSlot - { - Position = new AetheriaGridCoord - { - X = slot.X, - Y = slot.Y - }, - Rotation = slot.Rotation ?? "None", - Item = ToLoadoutItem(slot.Item) - }) - .ToArray(); - } - - private static AetheriaCargoBayLoadout[] ToCargoBays( - IReadOnlyList? cargoBays) - { - return (cargoBays ?? Array.Empty()) - .Select(bay => new AetheriaCargoBayLoadout - { - Items = ToItemSlots(bay.Items) - }) - .ToArray(); - } - - private static int[] ToIntArray(IReadOnlyList? values) - { - return (values ?? Array.Empty()).ToArray(); - } - - private static int[][] ToIntArrayArray(IReadOnlyList>? values) - { - return (values ?? Array.Empty>()) - .Select(ToIntArray) - .ToArray(); - } - - private static string StableToken(string value) - { - var chars = (string.IsNullOrWhiteSpace(value) ? "unnamed" : value.Trim().ToLowerInvariant()) - .Select(ch => char.IsLetterOrDigit(ch) ? ch : '-') - .ToArray(); - var token = new string(chars).Trim('-'); - while (token.Contains("--", StringComparison.Ordinal)) - token = token.Replace("--", "-", StringComparison.Ordinal); - return string.IsNullOrWhiteSpace(token) ? "unnamed" : token; - } -} diff --git a/Aetheria.State/AetheriaStateNode.cs b/Aetheria.State/AetheriaStateNode.cs deleted file mode 100644 index d31eac525..000000000 --- a/Aetheria.State/AetheriaStateNode.cs +++ /dev/null @@ -1,462 +0,0 @@ -using System; -using System.IO; -using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; -using System.Threading.Tasks; -using Aetheria.State.Documents; -using GameCult.Aetheria.State.Verse; -using GameCult.Caching; -using GameCult.Caching.MessagePack; -using GameCult.Mesh; -using GameCult.Networking; -using R3; -using EveSurfaceDocument = GameCult.Eve.Surface.EveSurfaceDocument; -using EveSurfaceCommandRequest = GameCult.Eve.Surface.EveSurfaceCommandRequest; -using EveCommandReceiptDocument = GameCult.Eve.Surface.EveCommandReceiptDocument; - -namespace Aetheria.State; - -public enum AetheriaStateHydrationProfile -{ - All, - DaemonBoot -} - -public sealed class AetheriaStateNode : IAsyncDisposable, IDisposable -{ - private static readonly string[] DaemonNativeCatalogBootRecordKeys = - [ - AetheriaCatalogKeys.ItemDefinitionFromLegacyId("82efc0a5-1ba5-4ff3-a281-b2e6e247521d").ToString(), // Zenith hull - AetheriaCatalogKeys.ItemDefinitionFromLegacyId("3e930a2c-ac72-4385-98aa-1c5b0b90db46").ToString(), // Medium docking bay - AetheriaCatalogKeys.ItemDefinitionFromLegacyId("ca098005-8cc8-47f4-be99-7bc842805359").ToString(), // Dockyard hull - AetheriaCatalogKeys.ItemDefinitionFromLegacyId("8ec30f8d-8536-48b4-bd64-65f29f229895").ToString() // Dockyard berth - ]; - private readonly CultMeshNode _node; - private CultMeshDocumentHandle? _runtimeCatalog; - private CultMeshDocumentHandle? _runtimeNameCorpus; - private CultMeshDocumentHandle? _catalogSurface; - - private AetheriaStateNode(string statePath, string runtimeId, CultMeshNode node) - { - StatePath = statePath; - RuntimeId = string.IsNullOrWhiteSpace(runtimeId) - ? "aetheria-local" - : runtimeId; - _node = node; - } - - public string StatePath { get; } - - public string RuntimeId { get; } - - public CultMeshNode MeshNode => _node; - - public CultCache Cache => _node.Cache; - - public CultNetDatabase Database => _node.Database; - - public static async Task OpenAsync( - string statePath, - string runtimeId = "aetheria-local", - bool startServer = false, - bool enableDurableShardLogs = true, - bool useDirectoryStore = true, - AetheriaStateHydrationProfile hydrationProfile = AetheriaStateHydrationProfile.All) - { - if (string.IsNullOrWhiteSpace(statePath)) - { - throw new ArgumentException("State path must be non-empty.", nameof(statePath)); - } - - Directory.CreateDirectory(Path.GetDirectoryName(Path.GetFullPath(statePath)) ?? "."); - - var traceStartup = string.Equals( - Environment.GetEnvironmentVariable("AETHERIA_TRACE_STARTUP_PHASES"), - "1", - StringComparison.Ordinal); - var startupPhase = Stopwatch.StartNew(); - void Trace(string phase) - { - if (traceStartup) - Console.WriteLine($"Aetheria state-node phase {phase} took {startupPhase.Elapsed.TotalMilliseconds:0.###}ms."); - startupPhase.Restart(); - } - - var cacheRegistry = AetheriaDocumentRegistry.CreateCultCacheRegistry(); - Trace("cache-registry"); - var hydrationFilter = CreateHydrationFilter(hydrationProfile, cacheRegistry); - Trace("hydration-filter"); - var databaseRegistry = AetheriaDocumentRegistry.CreateCultNetRegistry(cacheRegistry); - Trace("database-registry"); - var node = await CultMesh.CreateNodeAsync( - statePath, - new CultMeshNodeOptions - { - StartServer = startServer, - EnableDurableShardLogs = enableDurableShardLogs, - CacheOptions = new CultCacheOpenOptions - { - Registry = cacheRegistry, - PullOnOpen = true, - StoreFlushOnDispose = true, - UseDirectoryStore = useDirectoryStore, - DirectoryStoreHydrationFilter = hydrationFilter - }, - DatabaseOptions = new CultNetDatabaseOptions - { - RuntimeId = runtimeId, - DocumentRegistry = databaseRegistry - } - }).ConfigureAwait(false); - Trace("cultmesh-node"); - - return new AetheriaStateNode(statePath, runtimeId, node); - } - - private static Func? CreateHydrationFilter( - AetheriaStateHydrationProfile profile, - CultDocumentRegistry registry) - { - if (profile == AetheriaStateHydrationProfile.All) - return null; - if (profile != AetheriaStateHydrationProfile.DaemonBoot) - throw new ArgumentOutOfRangeException(nameof(profile), profile, "Unknown Aetheria state hydration profile."); - - var schemaTypes = new[] - { - typeof(AetheriaLoadoutTemplate), - typeof(AetheriaRuntimeAuthorityLeaseDocument), - typeof(AetheriaRuntimeDaemonCommandDocument), - typeof(AetheriaRuntimeCommittedCommandFactDocument), - typeof(AetheriaRuntimeEveCommandDocument), - typeof(EveSurfaceCommandRequest), - typeof(EveCommandReceiptDocument) - }; - var schemaIds = schemaTypes - .Select(registry.GetRequired) - .SelectMany(descriptor => descriptor.ToCatalogEntry().CompatibleSchemaIds.Append(descriptor.SchemaId)) - .ToHashSet(StringComparer.Ordinal); - var recordKeys = new HashSet(StringComparer.Ordinal) - { - WorldKey.ToString(), - RuntimeCatalogKey.ToString(), - TradeValuePolicyKey.ToString(), - PlayerSettingsKey.ToString(), - HangarKey.ToString(), - VerseHostSettingsKey.ToString(), - GameSessionStateKey.ToString(), - MainMenuStateKey.ToString(), - EveCommandAcceptanceStatusKey.ToString(), - AetheriaRuntimeVerseRecordKeys.DaemonFrameLatest.ToString(), - AetheriaRuntimeVerseRecordKeys.DaemonProviderAdvertisement.ToString(), - AetheriaRuntimeVerseRecordKeys.EveProviderAdvertisement.ToString(), - AetheriaRuntimeVerseRecordKeys.DaemonAssetManifest.ToString(), - AetheriaRuntimeVerseRecordKeys.EveAssetCatalog.ToString(), - AetheriaRuntimeVerseRecordKeys.PilotInputCapability.ToString(), - AetheriaRuntimeVerseRecordKeys.DaemonGameSurface.ToString(), - AetheriaRuntimeVerseRecordKeys.DaemonGameReactiveSurface.ToString(), - AetheriaRuntimeVerseRecordKeys.MapMenuSurface.ToString(), - AetheriaRuntimeVerseRecordKeys.VerseAuthorityPolicy.ToString(), - AetheriaRuntimeVerseRecordKeys.StarbridgeScenarioLatest.ToString(), - AetheriaRuntimeVerseRecordKeys.StarbridgeSessionLatest.ToString() - }; - recordKeys.UnionWith(DaemonNativeCatalogBootRecordKeys); - return record => recordKeys.Contains(record.Key) || schemaIds.Contains(record.SchemaId); - } - - public CultMeshDocumentHandle Document(CultRecordKey key) - where TDocument : class - { - return CultMesh.Document( - key.ToString(), - CultMesh.Verse("aetheria.local", RuntimeId), - async _ => (await Database.GetAsync(key).ConfigureAwait(false))!, - _ => Database.WatchRecord(key) - .Where(change => change.Document != null) - .Select(change => change.Document!), - sources: new[] - { - CultMesh.ProjectionSource(key.ToString()) - }, - routeHint: new CultMeshRouteHint(CultMeshLocalityKind.SharedMemory, "Aetheria typed state node document")); - } - - public CultMeshMutableStatePointer MutableDocument(CultRecordKey key) - where TDocument : class - { - return MutableDocumentPointer(key); - } - - public IReadOnlyList Documents() - where TDocument : class - { - return Cache.GetAll().ToArray(); - } - - public CultMeshDocumentHandle RuntimeCatalog() - { - return _runtimeCatalog ??= CultMesh.Document( - "aetheria.catalog.runtime", - CultMesh.Verse("aetheria.local", RuntimeId), - async _ => await Database.GetAsync(RuntimeCatalogKey) - .ConfigureAwait(false) - ?? throw new InvalidDataException( - "The compiled Aetheria runtime catalog is missing. Refresh it after changing source catalog records."), - _ => Database.WatchRecord(RuntimeCatalogKey) - .Where(change => change.Document != null) - .Select(change => change.Document!), - sources: new[] - { - CultMesh.ProjectionSource("catalog:aetheria.runtime"), - CultMesh.ProjectionSource( - "aetheria.catalog.runtime", - AetheriaRuntimeCatalogSnapshot.SchemaId, - "managed Aetheria runtime catalog document") - }, - routeHint: new CultMeshRouteHint(CultMeshLocalityKind.SharedMemory, "Aetheria typed catalog state")); - } - - public CultMeshDocumentHandle RuntimeNameCorpus() - { - return _runtimeNameCorpus ??= CultMesh.Document( - "aetheria.catalog.runtime-names", - CultMesh.Verse("aetheria.local", RuntimeId), - async _ => await Database.GetAsync(RuntimeNameCorpusKey) - .ConfigureAwait(false) - ?? throw new InvalidDataException("The compiled Aetheria runtime name corpus is missing."), - _ => Database.WatchRecord(RuntimeNameCorpusKey) - .Where(change => change.Document != null) - .Select(change => change.Document!), - sources: new[] - { - CultMesh.ProjectionSource( - RuntimeNameCorpusKey.ToString(), - AetheriaRuntimeNameCorpusSnapshot.SchemaId, - "managed Aetheria runtime name corpus") - }, - routeHint: new CultMeshRouteHint( - CultMeshLocalityKind.SharedMemory, - "Aetheria typed name generation corpus")); - } - - public async Task RuntimeCatalogForGenerationAsync() - { - var catalog = RuntimeCatalog().Latest() - ?? throw new InvalidDataException("The compiled Aetheria runtime catalog is missing."); - if (Cache.Get(RuntimeNameCorpusKey) == null) - { - await Cache.PullBackingStoreRecordsAsync(metadata => - string.Equals(metadata.Key, RuntimeNameCorpusKey.ToString(), StringComparison.Ordinal)).ConfigureAwait(false); - } - var corpus = await RuntimeNameCorpus().LatestAsync().ConfigureAwait(false); - return new AetheriaRuntimeCatalogSnapshot( - catalog.Items.ToArray(), - catalog.Corporations.ToArray(), - corpus.NameFiles.ToArray(), - catalog.TradeValueSettings) - { - CatalogId = catalog.CatalogId, - NameCorpusRecordKey = RuntimeNameCorpusKey.ToString() - }; - } - - public CultMeshDocumentHandle CatalogSurface() - { - var catalog = RuntimeCatalog(); - return _catalogSurface ??= CultMesh.Document( - "aetheria.catalog.surface", - CultMesh.Verse("aetheria.local", RuntimeId), - _ => Task.FromResult(AetheriaEveSurfaceDocuments.BuildCatalogSurface( - catalog.Latest(), - DateTimeOffset.UtcNow.ToString("O"))), - _ => catalog.Watch() - .Select(snapshot => AetheriaEveSurfaceDocuments.BuildCatalogSurface( - snapshot, - DateTimeOffset.UtcNow.ToString("O"))), - sources: new[] - { - CultMesh.ProjectionSource( - "aetheria.catalog.runtime", - AetheriaRuntimeCatalogSnapshot.SchemaId, - "managed Aetheria runtime catalog document"), - CultMesh.ProjectionSource( - CatalogSurfaceKey.ToString(), - "gamecult.eve.surface.v1", - "managed Aetheria catalog Eve surface document") - }, - routeHint: new CultMeshRouteHint(CultMeshLocalityKind.SharedMemory, "Aetheria typed catalog Eve surface")); - } - - public async Task RefreshRuntimeCatalogAsync() - { - var snapshot = AetheriaRuntimeCatalogStore.OpenReadOnly(StatePath); - await Database.PutAsync( - RuntimeNameCorpusKey, - new AetheriaRuntimeNameCorpusSnapshot - { - NameFiles = snapshot.NameFiles.ToArray() - }).ConfigureAwait(false); - snapshot.NameFiles = snapshot.NameFiles - .Select(nameFile => new AetheriaRuntimeNameFile( - nameFile.NameFileKey, - nameFile.Name, - nameFile.NameCount, - nameFile.SampleNames.ToArray(), - Array.Empty())) - .ToArray(); - snapshot.NameCorpusRecordKey = RuntimeNameCorpusKey.ToString(); - await Database.PutAsync(RuntimeCatalogKey, snapshot).ConfigureAwait(false); - return snapshot; - } - - public Task> SubmitDaemonCommandAsync( - AetheriaRuntimeDaemonCommandDocument command) - { - if (command == null) throw new ArgumentNullException(nameof(command)); - command.Schema = AetheriaRuntimeDaemonSchemas.Command; - if (string.IsNullOrWhiteSpace(command.CommandId)) - command.CommandId = Guid.NewGuid().ToString("N"); - if (string.IsNullOrWhiteSpace(command.IssuedAtUtc)) - command.IssuedAtUtc = DateTime.UtcNow.ToString("O"); - - return Database.PutAsync(DaemonCommandKey(command.CommandId), command); - } - - public Task> PutCommittedCommandFactAsync( - AetheriaRuntimeCommittedCommandFactDocument fact) - { - if (fact == null) throw new ArgumentNullException(nameof(fact)); - fact.Schema = AetheriaRuntimeDaemonSchemas.CommittedCommandFact; - if (string.IsNullOrWhiteSpace(fact.FactId)) - fact.FactId = Guid.NewGuid().ToString("N"); - if (string.IsNullOrWhiteSpace(fact.CommittedAtUtc)) - fact.CommittedAtUtc = DateTime.UtcNow.ToString("O"); - - return Database.PutAsync( - new CultRecordKey(AetheriaRuntimeCommittedCommandFactDocument.CreateRecordKey(fact.FactId)), - fact); - } - - private static CultRecordKey DaemonCommandKey(string commandId) - { - return new CultRecordKey($"daemon:commands:{StableToken(commandId)}:gamecult.aetheria.daemon_command.v1"); - } - - public Task> SubmitEveCommandAsync( - AetheriaRuntimeEveCommandDocument command) - { - if (command == null) throw new ArgumentNullException(nameof(command)); - AetheriaRuntimeEveCommandClient.NormalizeDocument(command); - command.Schema = AetheriaRuntimeEveCommandDocument.SchemaId; - if (string.IsNullOrWhiteSpace(command.CommandId)) - command.CommandId = Guid.NewGuid().ToString("N"); - if (string.IsNullOrWhiteSpace(command.IssuedAtUtc)) - command.IssuedAtUtc = DateTime.UtcNow.ToString("O"); - - return Database.PutAsync(EveCommandKey(command.CommandId), command); - } - - private static CultRecordKey EveCommandKey(string commandId) - { - return new CultRecordKey($"eve:commands:{StableToken(commandId)}:gamecult.eve.command.v1"); - } - - public Task FlushAsync(bool soft = false) - { - return _node.FlushAsync(soft); - } - - private static string StableToken(string value) - { - if (string.IsNullOrWhiteSpace(value)) - return "empty"; - - var chars = value - .Select(character => char.IsLetterOrDigit(character) ? character : '-') - .ToArray(); - var token = new string(chars).Trim('-').ToLowerInvariant(); - while (token.Contains("--", StringComparison.Ordinal)) - token = token.Replace("--", "-", StringComparison.Ordinal); - return string.IsNullOrWhiteSpace(token) ? "empty" : token; - } - - public static CultRecordKey RuntimeSessionKey(string runtimeId) - { - return new CultRecordKey($"runtime:{runtimeId}:aetheria.runtime_session.v1"); - } - - public static CultRecordKey WorldKey { get; } = - new("global:aetheria.world_state.v1"); - - public static CultRecordKey MigrationLedgerKey { get; } = - new("global:aetheria.migration_ledger.v1"); - - public static CultRecordKey LegacyCatalogQuarantineKey { get; } = - new("global:aetheria.legacy_catalog_quarantine.v1"); - - public static CultRecordKey CatalogSurfaceKey { get; } = - new(AetheriaEveSurfaceDocuments.CatalogSurfaceKey); - - public static CultRecordKey RuntimeCatalogKey { get; } = - new("global:aetheria.runtime_catalog.v1"); - - public static CultRecordKey RuntimeNameCorpusKey { get; } = - new("global:aetheria.runtime_name_corpus.v1"); - - public static CultRecordKey OperationsSurfaceKey { get; } = - new(AetheriaEveSurfaceDocuments.OperationsSurfaceKey); - - public static CultRecordKey PlayerSettingsSurfaceKey { get; } = - new(AetheriaEveSurfaceDocuments.PlayerSettingsSurfaceKey); - - public static CultRecordKey ProviderAdvertisementSurfaceKey { get; } = - new(AetheriaEveSurfaceDocuments.ProviderAdvertisementKey); - - public static CultRecordKey PlayerSettingsKey { get; } = - new("global:aetheria.player_settings.v1"); - - public static CultRecordKey HangarKey { get; } = - new("global:gamecult.aetheria.hangar.v1"); - - public static CultRecordKey VerseHostSettingsKey { get; } = - new("global:aetheria.verse_host_settings.v1"); - - public static CultRecordKey EveCommandAcceptanceStatusKey { get; } = - new("global:aetheria.eve_command_acceptance_status.v1"); - - public static CultRecordKey MainMenuStateKey { get; } = - new("global:aetheria.main_menu_state.v1"); - - public static CultRecordKey GameSessionStateKey { get; } = - new("global:aetheria.game_session.v1"); - - public static CultRecordKey TradeValuePolicyKey { get; } = - new(AetheriaTradeValuePolicy.RecordKey); - - private CultMeshMutableStatePointer MutableDocumentPointer(CultRecordKey key) where T : class - { - return CultMesh.MutableStatePointer( - key.ToString(), - _ => Database.GetAsync(key), - _ => Database.WatchRecord(key) - .Where(change => change.Document != null) - .Select(change => change.Document!), - async (_, value) => { await Database.PutAsync(key, value).ConfigureAwait(false); }, - sources: - [ - CultMesh.ProjectionSource(key.ToString()) - ]); - } - - public void Dispose() - { - _node.Dispose(); - } - - public async ValueTask DisposeAsync() - { - await FlushAsync().ConfigureAwait(false); - Dispose(); - } -} diff --git a/Aetheria.State/AetheriaStatePaths.cs b/Aetheria.State/AetheriaStatePaths.cs deleted file mode 100644 index 2f5f96dea..000000000 --- a/Aetheria.State/AetheriaStatePaths.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System.IO; - -namespace Aetheria.State; - -public static class AetheriaStatePaths -{ - public const string DefaultStateFileName = "aetheria-world.cc"; - - public static string ResolveDefaultStatePath(string? projectRoot = null) - { - var root = string.IsNullOrWhiteSpace(projectRoot) - ? Directory.GetCurrentDirectory() - : projectRoot!; - return Path.Combine(root, "GameData", DefaultStateFileName); - } -} diff --git a/Aetheria.State/AetheriaVerseCatalogDocuments.cs b/Aetheria.State/AetheriaVerseCatalogDocuments.cs deleted file mode 100644 index 8e8cdbf61..000000000 --- a/Aetheria.State/AetheriaVerseCatalogDocuments.cs +++ /dev/null @@ -1,54 +0,0 @@ -using Aetheria.State.Documents; -using GameCult.Mesh; - -namespace Aetheria.State; - -public static class AetheriaVerseCatalogDocuments -{ - private const string TransportVersion = "cultmesh.v0"; - private const string RulesEpoch = "aetheria-runtime-state.v1"; - - public static CultMeshVerseDescriptor Build(AetheriaVerseHostSettings? settings) - { - var normalized = AetheriaVerseHostSettingsNormalizer.Normalize(settings); - var discoveryEndpoint = BuildDiscoveryEndpoint(normalized); - var rulesHash = CultMeshVerseDescriptor.ComputeRulesHash( - "aetheria", - RulesEpoch, - normalized.RootVerse, - normalized.CanonicalService); - - return new CultMeshVerseDescriptor( - normalized.VerseId, - normalized.Title, - CultMeshVerseAuthorityModel.OperatorCluster, - new CultMeshVerseCompatibility(TransportVersion, rulesHash), - discoveryEndpoints: new[] { discoveryEndpoint }, - authorityRuntimeIds: new[] { normalized.ServiceId }, - parentVerseId: normalized.RootVerse, - description: BuildDescription(normalized)); - } - - public static string BuildDiscoveryEndpoint(AetheriaVerseHostSettings? settings) - { - var normalized = AetheriaVerseHostSettingsNormalizer.Normalize(settings); - var host = normalized.LocatedService; - if (string.IsNullOrWhiteSpace(host)) - host = normalized.CanonicalService; - if (string.IsNullOrWhiteSpace(host)) - host = "localhost"; - - host = host.Trim(); - var slashIndex = host.IndexOf('/', StringComparison.Ordinal); - if (slashIndex >= 0) - host = host.Substring(0, slashIndex); - - return $"cultnet://{host}:3075"; - } - - private static string BuildDescription(AetheriaVerseHostSettings settings) - { - var visibility = string.IsNullOrWhiteSpace(settings.Visibility) ? "private" : settings.Visibility; - return $"{settings.Title} Verse host ({visibility})"; - } -} diff --git a/Aetheria.State/AetheriaVerseDiscoveryHost.cs b/Aetheria.State/AetheriaVerseDiscoveryHost.cs deleted file mode 100644 index 7c4ff53c1..000000000 --- a/Aetheria.State/AetheriaVerseDiscoveryHost.cs +++ /dev/null @@ -1,67 +0,0 @@ -using Aetheria.State.Documents; -using GameCult.Mesh; - -namespace Aetheria.State; - -public sealed class AetheriaVerseDiscoveryHost : IDisposable -{ - private readonly AetheriaStateNode _node; - private CultMeshVerseCatalog? _catalog; - private CultMeshVerseDiscoveryServer? _server; - private string _fingerprint = ""; - - public AetheriaVerseDiscoveryHost(AetheriaStateNode node) - { - _node = node ?? throw new ArgumentNullException(nameof(node)); - } - - public void Update(AetheriaVerseHostSettings? settings) - { - var normalized = AetheriaVerseHostSettingsNormalizer.Normalize(settings); - if (!string.Equals(normalized.Visibility, "public", StringComparison.OrdinalIgnoreCase)) - { - DisposeServer(); - return; - } - - var nextFingerprint = CreateFingerprint(normalized); - if (string.Equals(_fingerprint, nextFingerprint, StringComparison.Ordinal)) - return; - - DisposeServer(); - - _catalog = CultMesh.CreateVerseCatalog(); - _catalog.Upsert(AetheriaVerseCatalogDocuments.Build(normalized)); - _server = CultMesh.ServeVerseCatalog(_node.MeshNode, _catalog); - _fingerprint = nextFingerprint; - } - - public void Dispose() - { - DisposeServer(); - GC.SuppressFinalize(this); - } - - private void DisposeServer() - { - _server?.Dispose(); - _server = null; - _catalog?.Dispose(); - _catalog = null; - _fingerprint = ""; - } - - private static string CreateFingerprint(AetheriaVerseHostSettings settings) - { - return string.Join( - "\u001f", - settings.ServiceId ?? "", - settings.VerseId ?? "", - settings.RootVerse ?? "", - settings.CanonicalService ?? "", - settings.LocatedService ?? "", - settings.CultMeshAddress ?? "", - settings.Title ?? "", - settings.Visibility ?? ""); - } -} diff --git a/Aetheria.State/AetheriaVerseHostSettingsNormalizer.cs b/Aetheria.State/AetheriaVerseHostSettingsNormalizer.cs deleted file mode 100644 index a1ff7ee30..000000000 --- a/Aetheria.State/AetheriaVerseHostSettingsNormalizer.cs +++ /dev/null @@ -1,44 +0,0 @@ -using Aetheria.State.Documents; - -namespace Aetheria.State; - -public static class AetheriaVerseHostSettingsNormalizer -{ - public static AetheriaVerseHostSettings Normalize(AetheriaVerseHostSettings? settings) - { - return new AetheriaVerseHostSettings - { - Schema = NormalizeOrDefault(settings?.Schema, "aetheria.verse_host_settings.v1"), - ServiceId = NormalizeOrDefault(settings?.ServiceId, "aetheria.runtime"), - VerseId = NormalizeOrDefault(settings?.VerseId, "aetheria.local"), - RootVerse = NormalizeOrDefault(settings?.RootVerse, "asgard"), - CanonicalService = NormalizeOrDefault(settings?.CanonicalService, "asgard.aetheria"), - LocatedService = NormalizeOrDefault(settings?.LocatedService, "asgard.local.aetheria"), - CultMeshAddress = NormalizeOrDefault(settings?.CultMeshAddress, "asgard.local.aetheria/eve"), - Title = NormalizeOrDefault(settings?.Title, "Aetheria"), - Visibility = NormalizeOrDefault(settings?.Visibility, "private"), - LastUpdatedAtUtc = settings?.LastUpdatedAtUtc?.Trim() ?? "" - }; - } - - public static bool Equivalent(AetheriaVerseHostSettings? left, AetheriaVerseHostSettings? right) - { - var normalizedLeft = Normalize(left); - var normalizedRight = Normalize(right); - return string.Equals(normalizedLeft.Schema, normalizedRight.Schema, StringComparison.Ordinal) && - string.Equals(normalizedLeft.ServiceId, normalizedRight.ServiceId, StringComparison.Ordinal) && - string.Equals(normalizedLeft.VerseId, normalizedRight.VerseId, StringComparison.Ordinal) && - string.Equals(normalizedLeft.RootVerse, normalizedRight.RootVerse, StringComparison.Ordinal) && - string.Equals(normalizedLeft.CanonicalService, normalizedRight.CanonicalService, StringComparison.Ordinal) && - string.Equals(normalizedLeft.LocatedService, normalizedRight.LocatedService, StringComparison.Ordinal) && - string.Equals(normalizedLeft.CultMeshAddress, normalizedRight.CultMeshAddress, StringComparison.Ordinal) && - string.Equals(normalizedLeft.Title, normalizedRight.Title, StringComparison.Ordinal) && - string.Equals(normalizedLeft.Visibility, normalizedRight.Visibility, StringComparison.Ordinal); - } - - private static string NormalizeOrDefault(string? value, string fallback) - { - var trimmed = value?.Trim(); - return string.IsNullOrWhiteSpace(trimmed) ? fallback : trimmed; - } -} diff --git a/Aetheria.State/AetheriaVerseReplica.cs b/Aetheria.State/AetheriaVerseReplica.cs deleted file mode 100644 index 759fa5414..000000000 --- a/Aetheria.State/AetheriaVerseReplica.cs +++ /dev/null @@ -1,311 +0,0 @@ -using Aetheria.State.Documents; -using GameCult.Aetheria.State.Verse; -using GameCult.Caching; -using GameCult.Caching.MessagePack; -using GameCult.Mesh; -using GameCult.Networking; - -namespace Aetheria.State; - -public static class AetheriaVerseReplica -{ - private const string ReplicaShardId = "primary"; - private const string ReplicaOwnerRuntimeId = "aetheria-remote"; - - public static async Task SyncSnapshotAsync( - string replicaStatePath, - string endpoint, - string runtimeId = "aetheria-verse-replica") - { - using var node = await OpenReplicaNodeAsync( - replicaStatePath, - endpoint, - runtimeId, - pullOnOpen: false).ConfigureAwait(false); - return await SyncRawSnapshotChunksAsync(node, endpoint, runtimeId).ConfigureAwait(false); - } - - public static async Task SyncScopedSnapshotAsync( - string replicaStatePath, - string endpoint, - string runtimeId = "aetheria-verse-replica", - IReadOnlyList? schemaIds = null, - IReadOnlyList? recordKeys = null, - TimeSpan? connectTimeout = null, - TimeSpan? responseTimeout = null) - { - using var node = await OpenReplicaNodeAsync( - replicaStatePath, - endpoint, - runtimeId, - pullOnOpen: false).ConfigureAwait(false); - var result = await CreateSnapshotEndpoint( - endpoint, - runtimeId, - schemaIds, - recordKeys, - connectTimeout, - responseTimeout) - .SyncSnapshotAsync(node) - .ConfigureAwait(false); - await node.FlushAsync().ConfigureAwait(false); - return result.AppliedCount; - } - - public static async Task FetchScopedSnapshotAsync( - string endpoint, - IReadOnlyList? schemaIds = null, - IReadOnlyList? recordKeys = null, - TimeSpan? connectTimeout = null, - TimeSpan? responseTimeout = null) - { - return await CreateSnapshotEndpoint( - endpoint, - "aetheria-verse-replica", - schemaIds, - recordKeys, - connectTimeout, - responseTimeout) - .FetchSnapshotAsync() - .ConfigureAwait(false); - } - - public static async Task ApplyRawSnapshotResponseAsync( - CultMeshNode node, - CultNetSnapshotResponseRawMessage response, - bool flush = true) - { - if (node == null) - throw new ArgumentNullException(nameof(node)); - if (response == null) - throw new ArgumentNullException(nameof(response)); - - var applied = await node.Database.Documents - .ApplyRawSnapshotResponseAsync(node.Cache, response) - .ConfigureAwait(false); - if (flush) - await node.FlushAsync().ConfigureAwait(false); - - return applied.Count; - } - - public static async Task> FetchScopedDocumentsAsync( - string endpoint, - IReadOnlyList? schemaIds = null, - IReadOnlyList? recordKeys = null, - TimeSpan? connectTimeout = null, - TimeSpan? responseTimeout = null) - where T : class - { - var surface = CreateSnapshotEndpoint( - endpoint, - "aetheria-verse-replica", - schemaIds, - recordKeys, - connectTimeout, - responseTimeout); - return await surface.FetchDocumentsAsync(recordKeys, schemaIds).ConfigureAwait(false); - } - - public static async Task> SyncScopedDocumentsAsync( - string replicaStatePath, - string endpoint, - string runtimeId = "aetheria-verse-replica", - IReadOnlyList? schemaIds = null, - IReadOnlyList? recordKeys = null, - TimeSpan? connectTimeout = null, - TimeSpan? responseTimeout = null) - where T : class - { - using var node = await OpenReplicaNodeAsync( - replicaStatePath, - endpoint, - runtimeId, - pullOnOpen: false).ConfigureAwait(false); - return await CreateSnapshotEndpoint( - endpoint, - runtimeId, - schemaIds, - recordKeys, - connectTimeout, - responseTimeout) - .SyncDocumentsAsync(node, recordKeys, schemaIds, flush: true) - .ConfigureAwait(false); - } - - public static async Task RunReplicaAsync( - string replicaStatePath, - string endpoint, - TimeSpan pollInterval, - CancellationToken cancellationToken, - string runtimeId = "aetheria-verse-replica") - { - using var node = await OpenReplicaNodeAsync( - replicaStatePath, - endpoint, - runtimeId, - pullOnOpen: false).ConfigureAwait(false); - var snapshotFetcher = new CultNetSchemaShardSnapshotFetcher(); - var logFetcher = new CultNetSchemaShardLogFetcher(); - - await SyncRawSnapshotChunksAsync(node, endpoint, runtimeId).ConfigureAwait(false); - - using var replicator = new CultNetShardReplicator( - node.Database, - new CultNetShardReplicatorOptions - { - PollInterval = pollInterval, - Fetcher = logFetcher, - SnapshotFetcher = snapshotFetcher - }); - replicator.Start(); - - try - { - await Task.Delay(Timeout.Infinite, cancellationToken).ConfigureAwait(false); - } - catch (OperationCanceledException) - { - } - finally - { - await node.FlushAsync().ConfigureAwait(false); - } - } - - private static CultNetShardDescriptor BuildReplicaShard(string endpoint) - { - if (string.IsNullOrWhiteSpace(endpoint)) - throw new ArgumentException("Replica endpoint must be non-empty.", nameof(endpoint)); - - return new CultNetShardDescriptor( - ReplicaShardId, - ReplicaOwnerRuntimeId, - epoch: 1, - isPrimary: false, - primaryEndpoints: new[] { endpoint.Trim() }); - } - - private static async Task SyncRawSnapshotChunksAsync( - CultMeshNode node, - string endpoint, - string runtimeId) - { - var hot = await SyncHotPublicationsAsync(node, endpoint, runtimeId).ConfigureAwait(false); - await node.FlushAsync().ConfigureAwait(false); - return hot; - } - - private static async Task SyncHotPublicationsAsync( - CultMeshNode node, - string endpoint, - string runtimeId) - { - var recordKeys = new[] - { - AetheriaRuntimeVerseRecordKeys.DaemonProviderAdvertisement.ToString(), - AetheriaRuntimeVerseRecordKeys.DaemonHealth.ToString(), - AetheriaRuntimeVerseRecordKeys.DaemonCommandBoundary.ToString(), - AetheriaRuntimeVerseRecordKeys.DaemonFrameLatest.ToString(), - AetheriaRuntimeVerseAuthorityPolicyDocument.DocumentKey, - "daemon:aetheria.starbridge.session.latest.v1" - }; - - var sequence = 0L; - var surface = CreateSnapshotEndpoint( - endpoint, - runtimeId, - schemaIds: null, - recordKeys: null, - connectTimeout: null, - responseTimeout: TimeSpan.FromSeconds(15)); - foreach (var recordKey in recordKeys) - { - var result = await surface.SyncSnapshotAsync( - node, - schemaIds: null, - recordKeys: new[] { recordKey }).ConfigureAwait(false); - sequence = Math.Max(sequence, result.ShardLogSequence); - } - - return sequence; - } - - private static CultMeshSnapshotRequestOptions CreateSnapshotOptions( - string runtimeId, - IReadOnlyList? schemaIds, - IReadOnlyList? recordKeys, - TimeSpan? connectTimeout, - TimeSpan? responseTimeout) - { - return new CultMeshSnapshotRequestOptions - { - SchemaIds = schemaIds, - RecordKeys = recordKeys, - ShardId = ReplicaShardId, - ShardEpoch = 1, - ConnectTimeout = connectTimeout ?? TimeSpan.FromSeconds(5), - ResponseTimeout = responseTimeout ?? TimeSpan.FromSeconds(5), - MessageIdPrefix = "aetheria-replica", - RudpRuntimeId = string.IsNullOrWhiteSpace(runtimeId) ? "aetheria-verse-replica" : runtimeId, - RudpMaxFragmentBytes = 1200 - }; - } - - private static CultMeshSnapshotEndpoint CreateSnapshotEndpoint( - string endpoint, - string runtimeId, - IReadOnlyList? schemaIds, - IReadOnlyList? recordKeys, - TimeSpan? connectTimeout, - TimeSpan? responseTimeout) - { - var effectiveRuntimeId = string.IsNullOrWhiteSpace(runtimeId) ? "aetheria-verse-replica" : runtimeId; - return CultMesh.SnapshotEndpoint( - endpoint, - new CultMeshSnapshotEndpointOptions - { - Context = CultMesh.Verse("aetheria.remote", effectiveRuntimeId).Context, - DocumentRegistry = AetheriaDocumentRegistry.CreateCultNetRegistry(), - Request = CreateSnapshotOptions( - effectiveRuntimeId, - schemaIds, - recordKeys, - connectTimeout, - responseTimeout) - }); - } - - private static Task OpenReplicaNodeAsync( - string replicaStatePath, - string endpoint, - string runtimeId, - bool pullOnOpen = true) - { - if (string.IsNullOrWhiteSpace(replicaStatePath)) - throw new ArgumentException("Replica state path must be non-empty.", nameof(replicaStatePath)); - - var shard = BuildReplicaShard(endpoint); - var cacheRegistry = AetheriaDocumentRegistry.CreateCultCacheRegistry(); - return CultMesh.CreateNodeAsync( - replicaStatePath, - new CultMeshNodeOptions - { - StartServer = false, - EnableDurableShardLogs = true, - CacheOptions = new CultCacheOpenOptions - { - Registry = cacheRegistry, - PullOnOpen = pullOnOpen, - StoreFlushOnDispose = true, - UseDirectoryStore = true - }, - DatabaseOptions = new CultNetDatabaseOptions - { - RuntimeId = string.IsNullOrWhiteSpace(runtimeId) ? "aetheria-verse-replica" : runtimeId, - Shards = new[] { shard }, - DocumentRegistry = AetheriaDocumentRegistry.CreateCultNetRegistry(cacheRegistry) - } - }); - } -} diff --git a/Aetheria.State/Documents/AetheriaCorporation.cs b/Aetheria.State/Documents/AetheriaCorporation.cs deleted file mode 100644 index 47baf4a08..000000000 --- a/Aetheria.State/Documents/AetheriaCorporation.cs +++ /dev/null @@ -1,79 +0,0 @@ -using GameCult.Caching; -using MessagePack; - -namespace Aetheria.State.Documents; - -[CultDocument("aetheria.corporation", "aetheria.corporation.v2")] -[MessagePackObject] -public sealed class AetheriaCorporation -{ - [Key(0)] - [CultName] - public string Name { get; set; } = ""; - - [Key(1)] - [CultIndex("legacyId")] - public string LegacyId { get; set; } = ""; - - [IgnoreMember] - public string CorporationKey => string.IsNullOrWhiteSpace(LegacyId) - ? "" - : $"aetheria.corporation:legacy:{LegacyId}"; - - [Key(2)] - public string ShortName { get; set; } = ""; - - [Key(3)] - public string Description { get; set; } = ""; - - [Key(4)] - [CultIndex("geonameFileLegacyId")] - public string GeonameFileLegacyId { get; set; } = ""; - - [IgnoreMember] - public string GeonameFileKey => string.IsNullOrWhiteSpace(GeonameFileLegacyId) - ? "" - : $"aetheria.name_file:legacy:{GeonameFileLegacyId}"; - - [Key(5)] - [CultIndex("bossHullLegacyId")] - public string BossHullLegacyId { get; set; } = ""; - - [IgnoreMember] - public string BossHullItemKey => string.IsNullOrWhiteSpace(BossHullLegacyId) - ? "" - : $"aetheria.item_definition:legacy:{BossHullLegacyId}"; - - [Key(6)] - public int InfluenceDistance { get; set; } - - [Key(7)] - public int AllegianceCount { get; set; } - - [Key(8)] - public uint OverworldMusic { get; set; } - - [Key(9)] - public uint CombatMusic { get; set; } - - [Key(10)] - public uint BossMusic { get; set; } - - [Key(11)] - public AetheriaCorporationAllegiance[] Allegiances { get; set; } = []; -} - -[MessagePackObject] -public sealed class AetheriaCorporationAllegiance -{ - [Key(0)] - public string CorporationLegacyId { get; set; } = ""; - - [IgnoreMember] - public string CorporationKey => string.IsNullOrWhiteSpace(CorporationLegacyId) - ? "" - : $"aetheria.corporation:legacy:{CorporationLegacyId}"; - - [Key(1)] - public double Weight { get; set; } -} diff --git a/Aetheria.State/Documents/AetheriaEveCommandAcceptanceStatus.cs b/Aetheria.State/Documents/AetheriaEveCommandAcceptanceStatus.cs deleted file mode 100644 index 5914bd793..000000000 --- a/Aetheria.State/Documents/AetheriaEveCommandAcceptanceStatus.cs +++ /dev/null @@ -1,75 +0,0 @@ -using GameCult.Caching; -using MessagePack; - -namespace Aetheria.State.Documents; - -[CultDocument("aetheria.eve_command_acceptance_status", "aetheria.eve_command_acceptance_status.v1")] -[CultGlobal] -[MessagePackObject] -public sealed class AetheriaEveCommandAcceptanceStatus -{ - [Key(0)] - [CultName] - public string Name { get; set; } = "Aetheria Eve command acceptance"; - - [Key(1)] - [CultIndex("runtimeId")] - public string RuntimeId { get; set; } = ""; - - [Key(2)] - public string StatePath { get; set; } = ""; - - [Key(3)] - public string LastPollAtUtc { get; set; } = ""; - - [Key(4)] - public string LastAcceptedAtUtc { get; set; } = ""; - - [Key(5)] - public int ObservedBeforeAccept { get; set; } - - [Key(6)] - public int CommandsAccepted { get; set; } - - [Key(7)] - public int CommandsRejected { get; set; } - - [Key(8)] - public int AppliedCatalogRefreshes { get; set; } - - [Key(9)] - public int AppliedOperationsRefreshes { get; set; } - - [Key(10)] - public int ConsecutiveFailures { get; set; } - - [Key(11)] - public string LastError { get; set; } = ""; - - [Key(12)] - public string LastRejectedCommand { get; set; } = ""; - - [Key(13)] - public string LastRejectedReason { get; set; } = ""; - - [Key(14)] - public string Status { get; set; } = "ok"; - - [Key(15)] - public int AppliedPlayerSettingsCommands { get; set; } - - [Key(16)] - public int AppliedVerseHostCommands { get; set; } - - [Key(17)] - public int AppliedInputSettingsCommands { get; set; } - - [Key(18)] - public int AppliedLoadoutTemplateCommands { get; set; } - - [Key(19)] - public string[] AccountedCommandIds { get; set; } = []; - - [Key(20)] - public int AppliedMainMenuCommands { get; set; } -} diff --git a/Aetheria.State/Documents/AetheriaGameSessionState.cs b/Aetheria.State/Documents/AetheriaGameSessionState.cs deleted file mode 100644 index f648c535e..000000000 --- a/Aetheria.State/Documents/AetheriaGameSessionState.cs +++ /dev/null @@ -1,26 +0,0 @@ -using GameCult.Caching; -using GameCult.Aetheria.State.Verse; -using MessagePack; - -namespace Aetheria.State.Documents; - -[CultDocument("aetheria.game_session", "aetheria.game_session.v1")] -[CultGlobal] -[MessagePackObject] -public sealed class AetheriaGameSessionState -{ - public const string TerminusMode = AetheriaGameModes.Terminus; - public const string StarbridgeMode = AetheriaGameModes.Starbridge; - public const string ArenaMode = AetheriaGameModes.Arena; - public const string AetheriaMode = "aetheria"; - - [Key(0)] public string Mode { get; set; } = TerminusMode; - [Key(1)] public string SessionId { get; set; } = ""; - [Key(2)] public string RunId { get; set; } = ""; - [Key(3)] public string ControlledEntityKey { get; set; } = ""; - [Key(4)] public string EntrySurfaceId { get; set; } = "aetheria.pilot"; - [Key(5)] public string LastStartCommandId { get; set; } = ""; - [Key(6)] public string UpdatedAtUtc { get; set; } = ""; - [Key(7)] public double SimulationRate { get; set; } = 1; - [Key(8)] public double? EffectiveSimulationRate { get; set; } = 1; -} diff --git a/Aetheria.State/Documents/AetheriaItemDefinition.cs b/Aetheria.State/Documents/AetheriaItemDefinition.cs deleted file mode 100644 index da4ed5926..000000000 --- a/Aetheria.State/Documents/AetheriaItemDefinition.cs +++ /dev/null @@ -1,331 +0,0 @@ -using GameCult.Caching; -using MessagePack; - -namespace Aetheria.State.Documents; - -[CultDocument("aetheria.item_definition", "aetheria.item_definition.v1")] -[MessagePackObject] -public sealed class AetheriaItemDefinition -{ - [Key(0)] - [CultName] - public string Name { get; set; } = ""; - - [Key(1)] - [CultIndex("category")] - public string Category { get; set; } = "uncategorized"; - - [Key(2)] - [CultIndex("legacyId")] - public string LegacyId { get; set; } = ""; - - [IgnoreMember] - public string ItemKey => string.IsNullOrWhiteSpace(LegacyId) - ? "" - : $"aetheria.item_definition:legacy:{LegacyId}"; - - [Key(3)] - public string Description { get; set; } = ""; - - [Key(4)] - public double Mass { get; set; } - - [Key(5)] - public double Volume { get; set; } - - [Key(6)] - public string[] Tags { get; set; } = []; - - [Key(7)] - [CultIndex("manufacturerLegacyId")] - public string ManufacturerLegacyId { get; set; } = ""; - - [IgnoreMember] - public string ManufacturerKey => string.IsNullOrWhiteSpace(ManufacturerLegacyId) - ? "" - : $"aetheria.corporation:legacy:{ManufacturerLegacyId}"; - - [Key(8)] - public int Price { get; set; } - - [Key(9)] - public int ShapeWidth { get; set; } - - [Key(10)] - public int ShapeHeight { get; set; } - - [Key(11)] - public int OccupiedCells { get; set; } - - [Key(12)] - [CultIndex("hardpointType")] - public string HardpointType { get; set; } = ""; - - [Key(13)] - [CultIndex("hullType")] - public string HullType { get; set; } = ""; - - [Key(14)] - public string[] BehaviorKinds { get; set; } = []; - - [Key(15)] - public int BehaviorCount { get; set; } - - [Key(16)] - public int MaxStack { get; set; } - - [Key(17)] - public bool Stackable { get; set; } - - [Key(18)] - public double Duration { get; set; } - - [Key(19)] - public double Durability { get; set; } - - [Key(20)] - public string WeaponRange { get; set; } = ""; - - [Key(21)] - public string WeaponCaliber { get; set; } = ""; - - [Key(22)] - public string WeaponType { get; set; } = ""; - - [Key(23)] - public string WeaponFireTypes { get; set; } = ""; - - [Key(24)] - public string WeaponModifiers { get; set; } = ""; - - [Key(25)] - public AetheriaShapeCell[] ShapeCells { get; set; } = []; - - [Key(26)] - public int InteriorShapeWidth { get; set; } - - [Key(27)] - public int InteriorShapeHeight { get; set; } - - [Key(28)] - public int InteriorOccupiedCells { get; set; } - - [Key(29)] - public AetheriaShapeCell[] InteriorShapeCells { get; set; } = []; - - [Key(30)] - public AetheriaItemHardpoint[] Hardpoints { get; set; } = []; - - [Key(31)] - public AetheriaBehaviorPayload[] BehaviorPayloads { get; set; } = []; - - [Key(32)] - public double MinimumTemperature { get; set; } - - [Key(33)] - public double MaximumTemperature { get; set; } - - [Key(34)] - public AetheriaCurveKey[] ThermalPerformanceCurveKeys { get; set; } = []; - - [Key(35)] - public string HullPrefab { get; set; } = ""; - - [Key(36)] - public string SimpleCommodityCategory { get; set; } = ""; - - [Key(37)] - public string CompoundCommodityCategory { get; set; } = ""; - - [Key(38)] - public double SpecificHeat { get; set; } = 1; - - [Key(39)] - public double Conductivity { get; set; } = 1; - - [Key(40)] - public double HullGridOffset { get; set; } - - [Key(41)] - public double HullArmor { get; set; } - - [Key(42)] - public double HullDrag { get; set; } - - [Key(43)] - public bool HullCanTow { get; set; } - - [Key(44)] - public int DockingMaxSizeX { get; set; } - - [Key(45)] - public int DockingMaxSizeY { get; set; } - - [Key(46)] - public string ActionBarIcon { get; set; } = ""; - - [Key(47)] - public double ThermalResilience { get; set; } = 1; - - [Key(48)] - public AetheriaItemAudioStat[] AudioStats { get; set; } = []; - - [Key(49)] - public AetheriaCurveKey[] EffectivenessCurveKeys { get; set; } = []; -} - -[MessagePackObject] -public sealed class AetheriaShapeCell -{ - [Key(0)] - public int X { get; set; } - - [Key(1)] - public int Y { get; set; } -} - -[MessagePackObject] -public sealed class AetheriaItemHardpoint -{ - [Key(0)] - public string Type { get; set; } = ""; - - [Key(1)] - public int PositionX { get; set; } - - [Key(2)] - public int PositionY { get; set; } - - [Key(3)] - public int ShapeWidth { get; set; } - - [Key(4)] - public int ShapeHeight { get; set; } - - [Key(5)] - public int OccupiedCells { get; set; } - - [Key(6)] - public AetheriaShapeCell[] ShapeCells { get; set; } = []; - - [Key(7)] - public string Transform { get; set; } = ""; - - [Key(8)] - public string Rotation { get; set; } = ""; - - [Key(9)] - public double Armor { get; set; } -} - -[MessagePackObject] -public sealed class AetheriaCurveKey -{ - [Key(0)] - public double Time { get; set; } - - [Key(1)] - public double Value { get; set; } - - [Key(2)] - public double InTangent { get; set; } - - [Key(3)] - public double OutTangent { get; set; } -} - -[MessagePackObject] -public sealed class AetheriaItemAudioStat -{ - [Key(0)] - public uint Parameter { get; set; } - - [Key(1)] - public AetheriaItemPerformanceStat Stat { get; set; } = new(); -} - -[MessagePackObject] -public sealed class AetheriaItemPerformanceStat -{ - [Key(0)] - public double Min { get; set; } - - [Key(1)] - public double Max { get; set; } - - [Key(2)] - public double HeatExponentMultiplier { get; set; } - - [Key(3)] - public double DurabilityExponentMultiplier { get; set; } - - [Key(4)] - public double QualityExponent { get; set; } -} - -[MessagePackObject] -public sealed class AetheriaBehaviorPayload -{ - [Key(0)] - public int UnionKey { get; set; } - - [Key(1)] - public string Kind { get; set; } = ""; - - [Key(2)] - public int Group { get; set; } - - [Key(3)] - public AetheriaBehaviorField[] Fields { get; set; } = []; - - [Key(4)] - public string BehaviorId { get; set; } = ""; -} - -[MessagePackObject] -public sealed class AetheriaBehaviorField -{ - [Key(0)] - public int Key { get; set; } - - [Key(1)] - public AetheriaBehaviorValue Value { get; set; } = new(); -} - -[MessagePackObject] -public sealed class AetheriaBehaviorMapEntry -{ - [Key(0)] - public string Key { get; set; } = ""; - - [Key(1)] - public AetheriaBehaviorValue Value { get; set; } = new(); -} - -[MessagePackObject] -public sealed class AetheriaBehaviorValue -{ - [Key(0)] - public string Kind { get; set; } = "nil"; - - [Key(1)] - public string StringValue { get; set; } = ""; - - [Key(2)] - public double NumberValue { get; set; } - - [Key(3)] - public bool BoolValue { get; set; } - - [Key(4)] - public string LegacyIdValue { get; set; } = ""; - - [Key(5)] - public AetheriaBehaviorValue[] Children { get; set; } = []; - - [Key(6)] - public AetheriaBehaviorMapEntry[] MapEntries { get; set; } = []; - - [Key(7)] - public string ItemKeyValue { get; set; } = ""; -} diff --git a/Aetheria.State/Documents/AetheriaLegacyCatalogQuarantine.cs b/Aetheria.State/Documents/AetheriaLegacyCatalogQuarantine.cs deleted file mode 100644 index ccb169444..000000000 --- a/Aetheria.State/Documents/AetheriaLegacyCatalogQuarantine.cs +++ /dev/null @@ -1,51 +0,0 @@ -using GameCult.Caching; -using MessagePack; - -namespace Aetheria.State.Documents; - -[CultDocument("aetheria.legacy_catalog_quarantine", "aetheria.legacy_catalog_quarantine.v1")] -[CultGlobal] -[MessagePackObject] -public sealed class AetheriaLegacyCatalogQuarantine -{ - [Key(0)] - [CultName] - public string Name { get; set; } = "Aetheria legacy catalog quarantine"; - - [Key(1)] - public string RootPath { get; set; } = ""; - - [Key(2)] - public string CapturedAtUtc { get; set; } = ""; - - [Key(3)] - public string CatalogFile { get; set; } = ""; - - [Key(4)] - public string CatalogFingerprint { get; set; } = ""; - - [Key(5)] - public long CatalogBytes { get; set; } - - [Key(6)] - public AetheriaLegacyCatalogFile[] NameFiles { get; set; } = []; - - [Key(7)] - public string[] Notes { get; set; } = []; - - [Key(8)] - public AetheriaLegacyCatalogFile[] SupplementalCatalogFiles { get; set; } = []; -} - -[MessagePackObject] -public sealed class AetheriaLegacyCatalogFile -{ - [Key(0)] - public string RelativePath { get; set; } = ""; - - [Key(1)] - public string Fingerprint { get; set; } = ""; - - [Key(2)] - public long Bytes { get; set; } -} diff --git a/Aetheria.State/Documents/AetheriaMainMenuState.cs b/Aetheria.State/Documents/AetheriaMainMenuState.cs deleted file mode 100644 index 91b03d344..000000000 --- a/Aetheria.State/Documents/AetheriaMainMenuState.cs +++ /dev/null @@ -1,26 +0,0 @@ -using GameCult.Caching; -using MessagePack; - -namespace Aetheria.State.Documents; - -[CultDocument("aetheria.main_menu_state", "aetheria.main_menu_state.v1")] -[CultGlobal] -[MessagePackObject] -public sealed class AetheriaMainMenuState -{ - [Key(0)] - [CultName] - public string Name { get; set; } = "Aetheria main menu state"; - - [Key(1)] - public string ActiveSurfaceId { get; set; } = ""; - - [Key(2)] - public string LastCommandId { get; set; } = ""; - - [Key(3)] - public string LastCommand { get; set; } = ""; - - [Key(4)] - public string UpdatedAtUtc { get; set; } = ""; -} diff --git a/Aetheria.State/Documents/AetheriaMigrationLedger.cs b/Aetheria.State/Documents/AetheriaMigrationLedger.cs deleted file mode 100644 index bfda94079..000000000 --- a/Aetheria.State/Documents/AetheriaMigrationLedger.cs +++ /dev/null @@ -1,61 +0,0 @@ -using GameCult.Caching; -using MessagePack; - -namespace Aetheria.State.Documents; - -[CultDocument("aetheria.migration_ledger", "aetheria.migration_ledger.v1")] -[CultGlobal] -[MessagePackObject] -public sealed class AetheriaMigrationLedger -{ - [Key(0)] - [CultName] - public string Name { get; set; } = "Aetheria legacy migration ledger"; - - [Key(1)] - public string Source { get; set; } = ""; - - [Key(2)] - public string SourceFingerprint { get; set; } = ""; - - [Key(3)] - public string LastMigrationAtUtc { get; set; } = ""; - - [Key(4)] - public AetheriaMigrationCount[] Counts { get; set; } = []; - - [Key(5)] - public string[] Notes { get; set; } = []; - - [Key(6)] - public AetheriaLegacyCatalogEntrySummary[] LegacyCatalogEntries { get; set; } = []; -} - -[MessagePackObject] -public sealed class AetheriaMigrationCount -{ - [Key(0)] - public string DocumentType { get; set; } = ""; - - [Key(1)] - public int Count { get; set; } -} - -[MessagePackObject] -public sealed class AetheriaLegacyCatalogEntrySummary -{ - [Key(0)] - public string LegacyId { get; set; } = ""; - - [Key(1)] - public int UnionKey { get; set; } - - [Key(2)] - public string DocumentKind { get; set; } = ""; - - [Key(3)] - public string Name { get; set; } = ""; - - [Key(4)] - public string Note { get; set; } = ""; -} diff --git a/Aetheria.State/Documents/AetheriaNameFile.cs b/Aetheria.State/Documents/AetheriaNameFile.cs deleted file mode 100644 index ec70938a0..000000000 --- a/Aetheria.State/Documents/AetheriaNameFile.cs +++ /dev/null @@ -1,31 +0,0 @@ -using GameCult.Caching; -using MessagePack; - -namespace Aetheria.State.Documents; - -[CultDocument("aetheria.name_file", "aetheria.name_file.v2")] -[MessagePackObject] -public sealed class AetheriaNameFile -{ - [Key(0)] - [CultName] - public string Name { get; set; } = ""; - - [Key(1)] - [CultIndex("legacyId")] - public string LegacyId { get; set; } = ""; - - [IgnoreMember] - public string NameFileKey => string.IsNullOrWhiteSpace(LegacyId) - ? "" - : $"aetheria.name_file:legacy:{LegacyId}"; - - [Key(2)] - public int NameCount { get; set; } - - [Key(3)] - public string[] SampleNames { get; set; } = []; - - [Key(4)] - public string[] Names { get; set; } = []; -} diff --git a/Aetheria.State/Documents/AetheriaPlayerProfile.cs b/Aetheria.State/Documents/AetheriaPlayerProfile.cs deleted file mode 100644 index 8dbf87908..000000000 --- a/Aetheria.State/Documents/AetheriaPlayerProfile.cs +++ /dev/null @@ -1,25 +0,0 @@ -using GameCult.Caching; -using MessagePack; - -namespace Aetheria.State.Documents; - -[CultDocument("aetheria.player_profile", "aetheria.player_profile.v1")] -[MessagePackObject] -public sealed class AetheriaPlayerProfile -{ - [Key(0)] - [CultName] - public string Username { get; set; } = ""; - - [Key(1)] - [CultIndex("legacyId")] - public string LegacyId { get; set; } = ""; - - [Key(2)] - [CultIndex("email")] - public string Email { get; set; } = ""; - - [Key(3)] - [CultReference(typeof(AetheriaCorporation))] - public string CorporationKey { get; set; } = ""; -} diff --git a/Aetheria.State/Documents/AetheriaRuntimeSession.cs b/Aetheria.State/Documents/AetheriaRuntimeSession.cs deleted file mode 100644 index c01d51fde..000000000 --- a/Aetheria.State/Documents/AetheriaRuntimeSession.cs +++ /dev/null @@ -1,26 +0,0 @@ -using GameCult.Caching; -using MessagePack; - -namespace Aetheria.State.Documents; - -[CultDocument("aetheria.runtime_session", "aetheria.runtime_session.v1")] -[MessagePackObject] -public sealed class AetheriaRuntimeSession -{ - [Key(0)] - [CultName] - public string RuntimeId { get; set; } = ""; - - [Key(1)] - [CultIndex("role")] - public string Role { get; set; } = "local"; - - [Key(2)] - public string StartedAtUtc { get; set; } = ""; - - [Key(3)] - public string LastSeenAtUtc { get; set; } = ""; - - [Key(4)] - public string Status { get; set; } = "running"; -} diff --git a/Aetheria.State/Documents/AetheriaRuntimeStateDocuments.cs b/Aetheria.State/Documents/AetheriaRuntimeStateDocuments.cs deleted file mode 100644 index 7dfdf77e5..000000000 --- a/Aetheria.State/Documents/AetheriaRuntimeStateDocuments.cs +++ /dev/null @@ -1,1136 +0,0 @@ -using GameCult.Caching; -using MessagePack; - -namespace Aetheria.State.Documents; - -[CultDocument("aetheria.player_settings", "aetheria.player_settings.v1")] -[CultGlobal] -[MessagePackObject] -public sealed class AetheriaPlayerSettings -{ - [Key(0)] - [CultName] - public string Name { get; set; } = "Aetheria player settings"; - - [Key(1)] - [CultReference(typeof(AetheriaRunState))] - public string ActiveRunKey { get; set; } = ""; - - [Key(2)] - public string LastUpdatedAtUtc { get; set; } = ""; - - [Key(3)] - public string PlayerName { get; set; } = ""; - - [Key(4)] - public bool TutorialPassed { get; set; } - - [Key(5)] - public AetheriaStoryFileHash[] StoryFileHashes { get; set; } = []; - - [Key(6)] - public AetheriaPlayerGameplaySettings Gameplay { get; set; } = new(); - - [Key(7)] - public AetheriaPlayerGraphicsSettings Graphics { get; set; } = new(); - - [Key(8)] - public AetheriaPlayerInputSettings Input { get; set; } = new(); -} - -[MessagePackObject] -public sealed class AetheriaStoryFileHash -{ - [Key(0)] - public string StoryPath { get; set; } = ""; - - [Key(1)] - public string Hash { get; set; } = ""; -} - -[MessagePackObject] -public sealed class AetheriaPlayerGameplaySettings -{ - [Key(0)] - public string TemperatureUnit { get; set; } = "Celsius"; - - [Key(1)] - public int SignificantDigits { get; set; } = 3; - - [Key(2)] - public float DefaultShutdownPerformance { get; set; } = 0.25f; -} - -[MessagePackObject] -public sealed class AetheriaPlayerGraphicsSettings -{ - [Key(0)] - public string NebulaQuality { get; set; } = "Normal"; - - [Key(1)] - public bool ShowAsteroidsInMinimap { get; set; } -} - -[MessagePackObject] -public sealed class AetheriaPlayerInputSettings -{ - [Key(0)] - public AetheriaInputBindingOverride[] BindingOverrides { get; set; } = []; - - [Key(1)] - public string[] ActionBarInputs { get; set; } = []; -} - -[MessagePackObject] -public sealed class AetheriaInputBindingOverride -{ - [Key(0)] - public string ActionName { get; set; } = ""; - - [Key(1)] - public int BindingIndex { get; set; } - - [Key(2)] - public string BindingPath { get; set; } = ""; -} - -[CultDocument("aetheria.loadout_template", "aetheria.loadout_template.v1")] -[MessagePackObject] -public sealed class AetheriaLoadoutTemplate -{ - [Key(0)] - [CultName] - public string Name { get; set; } = ""; - - [Key(1)] - public string OwnerPlayerKey { get; set; } = ""; - - [Key(2)] - public AetheriaEntityLoadout RootEntity { get; set; } = new(); - - [Key(3)] - public string CreatedAtUtc { get; set; } = ""; - - [Key(4)] - public string UpdatedAtUtc { get; set; } = ""; -} - -[MessagePackObject] -public sealed class AetheriaEntityLoadout -{ - [Key(0)] - [CultName] - public string Name { get; set; } = ""; - - [Key(1)] - public string Kind { get; set; } = ""; - - [Key(2)] - public string FactionKey { get; set; } = ""; - - [Key(3)] - public AetheriaLoadoutItem Hull { get; set; } = new(); - - [Key(4)] - public AetheriaLoadoutItemSlot[] Equipment { get; set; } = []; - - [Key(5)] - public AetheriaLoadoutItemSlot[] CargoBays { get; set; } = []; - - [Key(6)] - public AetheriaLoadoutItemSlot[] DockingBays { get; set; } = []; - - [Key(7)] - public AetheriaCargoBayLoadout[] CargoContents { get; set; } = []; - - [Key(8)] - public AetheriaCargoBayLoadout[] DockingBayContents { get; set; } = []; - - [Key(9)] - public int[] DockingBayAssignments { get; set; } = []; - - [Key(10)] - public int[][] WeaponGroups { get; set; } = []; - - [Key(11)] - public AetheriaEntityLoadout[] Children { get; set; } = []; -} - -[MessagePackObject] -public sealed class AetheriaLoadoutItem -{ - [Key(0)] - [CultReference(typeof(AetheriaItemDefinition))] - public string ItemKey { get; set; } = ""; - - [Key(1)] - public double Quality { get; set; } = 1.0; - - [Key(2)] - public double Durability { get; set; } = 1.0; - - [Key(3)] - public int Quantity { get; set; } = 1; - - [Key(4)] - public bool Enabled { get; set; } = true; - - [Key(5)] - public bool OverrideShutdown { get; set; } - - [Key(6)] - public double Temperature { get; set; } -} - -[MessagePackObject] -public sealed class AetheriaLoadoutItemSlot -{ - [Key(0)] - public AetheriaGridCoord Position { get; set; } = new(); - - [Key(1)] - public AetheriaLoadoutItem Item { get; set; } = new(); - - [Key(2)] - public string Rotation { get; set; } = "None"; -} - -[MessagePackObject] -public sealed class AetheriaCargoBayLoadout -{ - [Key(0)] - public AetheriaLoadoutItemSlot[] Items { get; set; } = []; -} - -[CultDocument("aetheria.run_state", "aetheria.run_state.v1")] -[MessagePackObject] -public sealed class AetheriaRunState -{ - [Key(0)] - [CultName] - public string RunId { get; set; } = ""; - - [Key(1)] - public bool IsTutorial { get; set; } - - [Key(2)] - public int EntranceZoneIndex { get; set; } - - [Key(3)] - public int ExitZoneIndex { get; set; } - - [Key(4)] - public int CurrentZoneIndex { get; set; } - - [Key(6)] - public int[] DiscoveredZoneIndices { get; set; } = []; - - [Key(7)] - [CultReference(typeof(AetheriaZoneState), many: true)] - public string[] ZoneKeys { get; set; } = []; - - [Key(9)] - public AetheriaFactionRelationshipState[] FactionRelationships { get; set; } = []; - - [Key(10)] - public string UpdatedAtUtc { get; set; } = ""; - - [Key(11)] - public uint GenerationSeed { get; set; } - - [Key(12)] - [CultReference(typeof(AetheriaEntitySnapshot))] - public string CurrentEntityKey { get; set; } = ""; - - [Key(13)] - public string LifecyclePhase { get; set; } = "active"; - - [Key(14)] - public string TerminalReason { get; set; } = ""; - - [Key(15)] - public long TerminalFrameId { get; set; } = -1; - - [Key(16)] - public AetheriaAgentTaskState[] AgentTasks { get; set; } = []; - - [Key(17)] - public string GameMode { get; set; } = AetheriaGameSessionState.AetheriaMode; - - [Key(18)] - public AetheriaFactionZoneState[] HomeZones { get; set; } = []; - - [Key(19)] - public AetheriaFactionZoneState[] BossZones { get; set; } = []; -} - -[MessagePackObject] -public sealed class AetheriaFactionZoneState -{ - [Key(0)] public int FactionIndex { get; set; } = -1; - [Key(1)] public int ZoneIndex { get; set; } = -1; -} - -[MessagePackObject] -public sealed class AetheriaAgentTaskState -{ - [Key(0)] public string TaskId { get; set; } = ""; - [Key(1)] public string CorporationKey { get; set; } = ""; - [Key(2)] public string TaskType { get; set; } = ""; - [Key(3)] public int Priority { get; set; } - [Key(4)] public int ZoneIndex { get; set; } = -1; - [Key(5)] public string Status { get; set; } = "queued"; - [Key(6)] public int AssignedEntityIndex { get; set; } = -1; - [Key(7)] public double CompletionRadius { get; set; } = 10; - [Key(8)] public string[] TargetOrbitKeys { get; set; } = []; - [Key(9)] public int CircuitIndex { get; set; } -} - -[CultDocument("aetheria.zone_state", "aetheria.zone_state.v1")] -[MessagePackObject] -public sealed class AetheriaZoneState -{ - [Key(0)] - [CultName] - public string Name { get; set; } = ""; - - [Key(1)] - public AetheriaVector2 Position { get; set; } = new(); - - [Key(2)] - public int[] AdjacentZoneIndices { get; set; } = []; - - [Key(3)] - public int[] FactionIndices { get; set; } = []; - - [Key(4)] - public int OwnerFactionIndex { get; set; } = -1; - - [Key(5)] - [CultReference(typeof(AetheriaEntitySnapshot), many: true)] - public string[] EntityKeys { get; set; } = []; - - [Key(6)] - public AetheriaOrbitSnapshot[] Orbits { get; set; } = []; - - [Key(7)] - public AetheriaBodySnapshot[] Bodies { get; set; } = []; - - [Key(8)] - public AetheriaDroppedPickupSnapshot[] DroppedPickups { get; set; } = []; - - [Key(9)] - public double GravityTerrainRadius { get; set; } - - [Key(10)] - // Positive magnitude. Terrain projection subtracts it to form a well. - public double GravityTerrainDepth { get; set; } - - [Key(11)] - public double GravityTerrainDepthExponent { get; set; } = 1.0; - - [Key(12)] - public double GravityTerrainBoundaryFog { get; set; } - - [Key(13)] - public double GravityTerrainWaveFrequency { get; set; } = 1.0; - - [Key(14)] - public double SimulationTimeSeconds { get; set; } - - [Key(15)] - public int NextPickupIndex { get; set; } -} - -[MessagePackObject] -public sealed class AetheriaDroppedPickupSnapshot -{ - [Key(0)] - public int PickupIndex { get; set; } = -1; - - [Key(1)] - public AetheriaVector3 Position { get; set; } = new(); - - [Key(2)] - public AetheriaVector3 Velocity { get; set; } = new(); - - [Key(3)] - public AetheriaLoadoutItem Item { get; set; } = new(); -} - -[MessagePackObject] -public sealed class AetheriaOrbitSnapshot -{ - [Key(0)] - public string OrbitKey { get; set; } = ""; - - [Key(1)] - public string ParentOrbitKey { get; set; } = ""; - - [Key(2)] - public double Distance { get; set; } - - [Key(3)] - public double Phase { get; set; } - - [Key(4)] - public AetheriaVector2 FixedPosition { get; set; } = new(); - - [Key(5)] - public double Period { get; set; } -} - -[MessagePackObject] -public sealed class AetheriaBodySnapshot -{ - [Key(0)] - public string BodyKey { get; set; } = ""; - - [Key(1)] - public string Kind { get; set; } = ""; - - [Key(2)] - public string Name { get; set; } = ""; - - [Key(3)] - public string OrbitKey { get; set; } = ""; - - [Key(4)] - public double Mass { get; set; } - - [Key(5)] - public AetheriaBodyResource[] Resources { get; set; } = []; - - [Key(6)] - public double BodyRadiusMultiplier { get; set; } = 1.0; - - [Key(7)] - public double GravityRadiusMultiplier { get; set; } = 1.0; - - [Key(8)] - public double GravityDepthMultiplier { get; set; } = 1.0; - - [Key(9)] - public double GravityDepthExponent { get; set; } = 16.0; - - [Key(10)] - public AetheriaAsteroidSnapshot[] Asteroids { get; set; } = []; - - [Key(11)] - public AetheriaGasGiantVisualState GasGiantVisual { get; set; } = new(); - - [Key(12)] - public AetheriaSunVisualState SunVisual { get; set; } = new(); - - [Key(13)] - public double GravityInfluenceCenterX { get; set; } = double.NaN; - - [Key(14)] - public double GravityInfluenceCenterZ { get; set; } = double.NaN; - - [Key(15)] - public double GravityInfluenceRadius { get; set; } - - [Key(16)] - // Positive magnitude. Ymir radial fields interpret positive strength as attraction. - public double GravityWellDepth { get; set; } - - [Key(17)] - public double GravityWaveRadius { get; set; } - - [Key(18)] - public double GravityWaveDepth { get; set; } - - [Key(19)] - public double GravityWaveSpeed { get; set; } -} - -[MessagePackObject] -public sealed class AetheriaBodyResource -{ - [Key(0)] - public string ItemKey { get; set; } = ""; - - [Key(1)] - public double Amount { get; set; } -} - -[MessagePackObject] -public sealed class AetheriaAsteroidSnapshot -{ - [Key(0)] - public double Distance { get; set; } - - [Key(1)] - public double Phase { get; set; } - - [Key(2)] - public double Size { get; set; } - - [Key(3)] - public double RotationSpeed { get; set; } - - [Key(4)] - public double Damage { get; set; } - - [Key(5)] - public double RespawnTimer { get; set; } - - [Key(6)] - public AetheriaAsteroidMiningAccumulatorSnapshot[] MiningAccumulators { get; set; } = []; -} - -[MessagePackObject] -public sealed class AetheriaAsteroidMiningAccumulatorSnapshot -{ - [Key(0)] - [CultReference(typeof(AetheriaEntitySnapshot))] - public string MinerEntityKey { get; set; } = ""; - - [Key(1)] - public double Amount { get; set; } -} - -[MessagePackObject] -public sealed class AetheriaGasGiantVisualState -{ - [Key(0)] - public double FirstOffsetDomainRotationSpeed { get; set; } = 1.0; - - [Key(1)] - public double FirstOffsetRotationSpeed { get; set; } = 1.0; - - [Key(2)] - public double SecondOffsetDomainRotationSpeed { get; set; } = 1.0; - - [Key(3)] - public double SecondOffsetRotationSpeed { get; set; } = 1.0; - - [Key(4)] - public double AlbedoRotationSpeed { get; set; } = 1.0; - - [Key(5)] - public double WaveRadiusMultiplier { get; set; } = 1.0; - - [Key(6)] - public double WaveDepthMultiplier { get; set; } = 1.0; - - [Key(7)] - public double WaveDepthExponent { get; set; } = 8.0; - - [Key(8)] - public double WaveSpeedMultiplier { get; set; } = 8.0; - - [Key(9)] - public string[] MaterialOverrides { get; set; } = []; - - [Key(10)] - public AetheriaColor[] Colors { get; set; } = []; -} - -[MessagePackObject] -public sealed class AetheriaSunVisualState -{ - [Key(0)] - public AetheriaVector3 LightColor { get; set; } = new(); - - [Key(1)] - public AetheriaVector3 FogTintColor { get; set; } = new(); - - [Key(2)] - public double LightRadiusMultiplier { get; set; } = 1.0; -} - -[MessagePackObject] -public sealed class AetheriaColor -{ - [Key(0)] - public double X { get; set; } - - [Key(1)] - public double Y { get; set; } - - [Key(2)] - public double Z { get; set; } - - [Key(3)] - public double W { get; set; } -} - -[CultDocument("aetheria.entity_snapshot", "aetheria.entity_snapshot.v1")] -[MessagePackObject] -public sealed class AetheriaEntitySnapshot -{ - [Key(0)] - [CultName] - public string Name { get; set; } = ""; - - [Key(1)] - [CultIndex("kind")] - public string Kind { get; set; } = ""; - - [Key(2)] - public AetheriaVector3 Position { get; set; } = new(); - - [Key(3)] - public AetheriaVector2 Direction { get; set; } = new(); - - [Key(4)] - public string FactionKey { get; set; } = ""; - - [Key(5)] - public string HullItemKey { get; set; } = ""; - - [Key(6)] - public AetheriaEntityItemSlot[] Equipment { get; set; } = []; - - [Key(7)] - public AetheriaEntityItemSlot[] CargoBays { get; set; } = []; - - [Key(8)] - public AetheriaEntityItemSlot[] DockingBays { get; set; } = []; - - [Key(9)] - [CultReference(typeof(AetheriaEntitySnapshot), many: true)] - public string[] ChildEntityKeys { get; set; } = []; - - [Key(10)] - public AetheriaWeaponGroupSnapshot[] WeaponGroups { get; set; } = []; - - [Key(11)] - public AetheriaEntityStatGrid[] StatGrids { get; set; } = []; - - [Key(12)] - public AetheriaVector2 Velocity { get; set; } = new(); - - [Key(13)] - [CultReference(typeof(AetheriaEntitySnapshot))] - public string TargetEntityKey { get; set; } = ""; - - [Key(14)] - public bool IsActive { get; set; } - - [Key(15)] - public bool HeatsinksEnabled { get; set; } - - [Key(16)] - public bool OverrideShutdown { get; set; } - - [Key(17)] - public double TractorPower { get; set; } - - [Key(18)] - public double Heatstroke { get; set; } - - [Key(19)] - public double Hypothermia { get; set; } - - [Key(20)] - public AetheriaActiveConsumableSnapshot[] ActiveConsumables { get; set; } = []; - - [Key(21)] - public AetheriaBehaviorProgressSnapshot[] BehaviorProgress { get; set; } = []; - - [Key(22)] - public AetheriaWeaponStateSnapshot[] WeaponStates { get; set; } = []; - - [Key(23)] - public AetheriaBehaviorStateSnapshot[] BehaviorStates { get; set; } = []; - - [Key(24)] - public AetheriaCargoBayLoadout[] CargoContents { get; set; } = []; - - [Key(25)] - public AetheriaCargoBayLoadout[] DockingBayContents { get; set; } = []; - - [Key(26)] - public int[] DockingBayAssignments { get; set; } = []; - - [Key(27)] - public double Visibility { get; set; } - - [Key(28)] - public int VisibilitySourceCount { get; set; } - - [Key(29)] - public AetheriaEntityContactSnapshot[] Contacts { get; set; } = []; - - [Key(30)] - [CultReference(typeof(AetheriaEntitySnapshot))] - public string HomeEntityKey { get; set; } = ""; - - [Key(31)] - public string[] AgentTaskCapabilities { get; set; } = []; - - [Key(32)] - public AetheriaLoadoutGenerationReceipt? LoadoutGeneration { get; set; } - - [Key(33)] - public AetheriaVector2 LookDirection { get; set; } = new(); - - [Key(34)] - public AetheriaVector2 HelmInput { get; set; } = new(); - - [Key(35)] - public string OrbitKey { get; set; } = ""; - - [Key(36)] - public int SecurityLevel { get; set; } - - [Key(37)] - public double SecurityRadius { get; set; } - - [Key(38)] - public string AssignedAgentTaskId { get; set; } = ""; - - [Key(39)] - public AetheriaFogFieldEmitterSnapshot[] FogFieldEmitters { get; set; } = []; -} - -[MessagePackObject] -public sealed class AetheriaFogFieldEmitterSnapshot -{ - [Key(0)] - public bool Enabled { get; set; } = true; - - [Key(1)] - public double Radius { get; set; } - - [Key(2)] - public double Density { get; set; } = 1; - - [Key(3)] - public double OffsetX { get; set; } - - [Key(4)] - public double OffsetZ { get; set; } - - [Key(5)] - public double FalloffExponent { get; set; } = 2; -} - -[MessagePackObject] -public sealed class AetheriaLoadoutGenerationReceipt -{ - [Key(0)] public uint Seed { get; set; } - [Key(1)] public int SourceZoneIndex { get; set; } - [Key(2)] public string AvailabilityFactionKey { get; set; } = ""; - [Key(3)] public double PriceExponent { get; set; } - [Key(4)] public AetheriaLoadoutGenerationSelection[] Selections { get; set; } = []; -} - -[MessagePackObject] -public sealed class AetheriaLoadoutGenerationSelection -{ - [Key(0)] public string Role { get; set; } = ""; - [Key(1)] public string ItemKey { get; set; } = ""; - [Key(2)] public string ManufacturerKey { get; set; } = ""; - [Key(3)] public int Price { get; set; } - [Key(4)] public int ManufacturerDistance { get; set; } - [Key(5)] public double Allegiance { get; set; } -} - -[MessagePackObject] -public sealed class AetheriaEntityContactSnapshot -{ - [Key(0)] - [CultReference(typeof(AetheriaEntitySnapshot))] - public string TargetEntityKey { get; set; } = ""; - - [Key(1)] - public double InfoGathered { get; set; } - - [Key(2)] - public bool Hostile { get; set; } - - [Key(3)] - public bool Visible { get; set; } -} - -[MessagePackObject] -public sealed class AetheriaEntityItemSlot -{ - [Key(0)] - public AetheriaGridCoord Position { get; set; } = new(); - - [Key(1)] - public string ItemKey { get; set; } = ""; - - [Key(2)] - public double Quality { get; set; } = 1.0; - - [Key(3)] - public double Durability { get; set; } = 1.0; - - [Key(4)] - public int Quantity { get; set; } = 1; - - [Key(5)] - public bool Enabled { get; set; } = true; - - [Key(6)] - public bool OverrideShutdown { get; set; } - - [Key(7)] - public double Temperature { get; set; } - - [Key(8)] - public string Rotation { get; set; } = "None"; -} - -[MessagePackObject] -public sealed class AetheriaWeaponGroupSnapshot -{ - [Key(0)] - public int[] EquipmentIndices { get; set; } = []; -} - -[MessagePackObject] -public sealed class AetheriaEntityStatGrid -{ - [Key(0)] - public string Name { get; set; } = ""; - - [Key(1)] - public int Width { get; set; } - - [Key(2)] - public int Height { get; set; } - - [Key(3)] - public double[] Values { get; set; } = []; -} - -[MessagePackObject] -public sealed class AetheriaActiveConsumableSnapshot -{ - [Key(0)] - [CultReference(typeof(AetheriaItemDefinition))] - public string ItemKey { get; set; } = ""; - - [Key(1)] - public double Quality { get; set; } = 1.0; - - [Key(2)] - public double RemainingDuration { get; set; } - - [Key(3)] - public double Duration { get; set; } - - [Key(4)] - public string EffectId { get; set; } = ""; - - [Key(5)] - public AetheriaConsumableBehaviorStateSnapshot[] BehaviorStates { get; set; } = []; -} - -[MessagePackObject] -public sealed class AetheriaConsumableBehaviorStateSnapshot -{ - [Key(0)] - public int BehaviorIndex { get; set; } = -1; - - [Key(1)] - public string BehaviorKind { get; set; } = ""; - - [Key(2)] - public double ScalarState { get; set; } - - [Key(3)] - public string BehaviorId { get; set; } = ""; -} - -[MessagePackObject] -public sealed class AetheriaBehaviorProgressSnapshot -{ - [Key(0)] - public string OwnerKind { get; set; } = ""; - - [Key(1)] - public int OwnerIndex { get; set; } = -1; - - [Key(2)] - public int BehaviorIndex { get; set; } = -1; - - [Key(3)] - public string BehaviorKind { get; set; } = ""; - - [Key(4)] - public double Progress { get; set; } -} - -[MessagePackObject] -public sealed class AetheriaWeaponStateSnapshot -{ - [Key(0)] - public string OwnerKind { get; set; } = ""; - - [Key(1)] - public int OwnerIndex { get; set; } = -1; - - [Key(2)] - public int BehaviorIndex { get; set; } = -1; - - [Key(3)] - public string BehaviorKind { get; set; } = ""; - - [Key(4)] - public bool Firing { get; set; } - - [Key(5)] - public int Ammo { get; set; } - - [Key(6)] - public int BurstRemaining { get; set; } - - [Key(7)] - public double BurstTimer { get; set; } - - [Key(8)] - public double BurstInterval { get; set; } - - [Key(9)] - public double CooldownProgress { get; set; } - - [Key(10)] - public bool CoolingDown { get; set; } - - [Key(11)] - public bool Charging { get; set; } - - [Key(12)] - public bool Charged { get; set; } - - [Key(13)] - public double Charge { get; set; } - - [Key(14)] - public bool Reloading { get; set; } - - [Key(15)] - public double ReloadProgress { get; set; } - - [Key(16)] - public double AmmoIntervalProgress { get; set; } - - [Key(17)] - public double LockProgress { get; set; } - - [Key(18)] - public string LockTargetEntityKey { get; set; } = ""; -} - -[MessagePackObject] -public sealed class AetheriaBehaviorStateSnapshot -{ - [Key(0)] - public string OwnerKind { get; set; } = ""; - - [Key(1)] - public int OwnerIndex { get; set; } = -1; - - [Key(2)] - public int BehaviorIndex { get; set; } = -1; - - [Key(3)] - public string BehaviorKind { get; set; } = ""; - - [Key(4)] - public bool Pinging { get; set; } - - [Key(5)] - public double PingCooldown { get; set; } - - [Key(6)] - public double PingLerp { get; set; } - - [Key(7)] - public double PingRadius { get; set; } - - [Key(8)] - public int PingedEntityCount { get; set; } - - [Key(9)] - public double RadiatorTemperature { get; set; } - - [Key(10)] - public double Emissivity { get; set; } - - [Key(11)] - public double PumpedHeat { get; set; } - - [Key(12)] - public double WasteHeat { get; set; } - - [Key(13)] - public double EnergyUsage { get; set; } - - [Key(14)] - public double ReactorDraw { get; set; } - - [Key(15)] - public double ReactorLoadRatio { get; set; } - - [Key(16)] - public double CapacitorCharge { get; set; } - - [Key(17)] - public double CapacitorCapacity { get; set; } - - [Key(18)] - public double CapacitorEfficiency { get; set; } - - [Key(19)] - public double AetherDriveAxisX { get; set; } - - [Key(20)] - public double AetherDriveAxisY { get; set; } - - [Key(21)] - public double AetherDriveAxisZ { get; set; } - - [Key(22)] - public double AetherDriveThrustX { get; set; } - - [Key(23)] - public double AetherDriveThrustY { get; set; } - - [Key(24)] - public double AetherDriveThrustZ { get; set; } - - [Key(25)] - public double AetherDriveRpmX { get; set; } - - [Key(26)] - public double AetherDriveRpmY { get; set; } - - [Key(27)] - public double AetherDriveRpmZ { get; set; } - - [Key(28)] - public double AetherDriveMaximumRpm { get; set; } - - [Key(29)] - public double AetherDriveThrustDirectionX { get; set; } - - [Key(30)] - public double AetherDriveThrustDirectionY { get; set; } - - [Key(31)] - public string ResourceScannerTargetBodyKey { get; set; } = ""; - - [Key(32)] - public int ResourceScannerAsteroidIndex { get; set; } = -1; - - [Key(33)] - public double ResourceScannerScanTime { get; set; } - - [Key(34)] - public double ResourceScannerRange { get; set; } - - [Key(35)] - public double ResourceScannerMinimumDensity { get; set; } - - [Key(36)] - public double ResourceScannerScanDuration { get; set; } - - [Key(37)] - public string MiningToolAsteroidBeltKey { get; set; } = ""; - - [Key(38)] - public int MiningToolAsteroidIndex { get; set; } = -1; - - [Key(39)] - public double MiningToolRange { get; set; } - - [Key(40)] - public double ThrusterAxis { get; set; } - - [Key(41)] - public double ThrusterThrust { get; set; } - - [Key(42)] - public double ThrusterTorque { get; set; } - - [Key(43)] - public double ShieldEfficiency { get; set; } - - [Key(44)] - public double ShieldEnergyUsage { get; set; } - - [Key(45)] - public double VelocityLimit { get; set; } - - [Key(46)] - public double ThermotoggleTargetTemperature { get; set; } - - [Key(47)] - public bool SwitchActivated { get; set; } - - [Key(48)] - public bool TriggerPulled { get; set; } - - [Key(49)] - public bool StatModifierApplied { get; set; } - - [Key(50)] - public bool StatModifierExecuted { get; set; } - - [Key(51)] - public int StatModifierTargetStatCount { get; set; } - - [Key(52)] - public int TurretControllerWeaponCount { get; set; } - - [Key(53)] - public double TurretControllerShotSpeed { get; set; } - - [Key(54)] - public bool TurretControllerPredictShots { get; set; } -} - -[MessagePackObject] -public sealed class AetheriaFactionRelationshipState -{ - [Key(0)] - public string FactionKey { get; set; } = ""; - - [Key(1)] - public string Relationship { get; set; } = ""; - - [Key(2)] - public double Standing { get; set; } -} - -[MessagePackObject] -public sealed class AetheriaVector2 -{ - [Key(0)] - public double X { get; set; } - - [Key(1)] - public double Y { get; set; } -} - -[MessagePackObject] -public sealed class AetheriaVector3 -{ - [Key(0)] - public double X { get; set; } - - [Key(1)] - public double Y { get; set; } - - [Key(2)] - public double Z { get; set; } -} - -[MessagePackObject] -public sealed class AetheriaGridCoord -{ - [Key(0)] - public int X { get; set; } - - [Key(1)] - public int Y { get; set; } -} diff --git a/Aetheria.State/Documents/AetheriaTradeValuePolicy.cs b/Aetheria.State/Documents/AetheriaTradeValuePolicy.cs deleted file mode 100644 index 34a97c580..000000000 --- a/Aetheria.State/Documents/AetheriaTradeValuePolicy.cs +++ /dev/null @@ -1,36 +0,0 @@ -using GameCult.Caching; -using MessagePack; - -namespace Aetheria.State.Documents; - -[CultDocument("aetheria.trade_value_policy", "aetheria.trade_value_policy.v1")] -[CultGlobal] -[MessagePackObject] -public sealed class AetheriaTradeValuePolicy -{ - public const string RecordKey = "global:aetheria.trade_value_policy.v1"; - - [Key(0)] public string Schema { get; set; } = "aetheria.trade_value_policy.v1"; - [Key(1)] public string Name { get; set; } = "Aetheria trade value policy"; - [Key(2)] public AetheriaExponentialLerp QualityPriceModifier { get; set; } = new(); - [Key(3)] public AetheriaItemRarityTier[] Tiers { get; set; } = []; - [Key(4)] public string UpdatedAtUtc { get; set; } = ""; -} - -[MessagePackObject] -public sealed class AetheriaExponentialLerp -{ - [Key(0)] public double Exponent { get; set; } - [Key(1)] public double Minimum { get; set; } - [Key(2)] public double Maximum { get; set; } -} - -[MessagePackObject] -public sealed class AetheriaItemRarityTier -{ - [Key(0)] public string Name { get; set; } = ""; - [Key(1)] public double Quality { get; set; } - [Key(2)] public double Red { get; set; } - [Key(3)] public double Green { get; set; } - [Key(4)] public double Blue { get; set; } -} diff --git a/Aetheria.State/Documents/AetheriaVerseHostSettings.cs b/Aetheria.State/Documents/AetheriaVerseHostSettings.cs deleted file mode 100644 index d9a8419cd..000000000 --- a/Aetheria.State/Documents/AetheriaVerseHostSettings.cs +++ /dev/null @@ -1,21 +0,0 @@ -using GameCult.Caching; -using MessagePack; - -namespace Aetheria.State.Documents; - -[CultDocument("aetheria.verse_host_settings", "aetheria.verse_host_settings.v1")] -[CultGlobal] -[MessagePackObject] -public sealed class AetheriaVerseHostSettings -{ - [Key(0)] public string Schema { get; set; } = "aetheria.verse_host_settings.v1"; - [Key(1)] public string ServiceId { get; set; } = "aetheria.runtime"; - [Key(2)] public string VerseId { get; set; } = "aetheria.local"; - [Key(3)] public string RootVerse { get; set; } = "asgard"; - [Key(4)] public string CanonicalService { get; set; } = "asgard.aetheria"; - [Key(5)] public string LocatedService { get; set; } = "asgard.local.aetheria"; - [Key(6)] public string CultMeshAddress { get; set; } = "asgard.local.aetheria/eve"; - [Key(7)] public string Title { get; set; } = "Aetheria"; - [Key(8)] public string Visibility { get; set; } = "private"; - [Key(9)] public string LastUpdatedAtUtc { get; set; } = ""; -} diff --git a/Aetheria.State/Documents/AetheriaWorldState.cs b/Aetheria.State/Documents/AetheriaWorldState.cs deleted file mode 100644 index ed56ec046..000000000 --- a/Aetheria.State/Documents/AetheriaWorldState.cs +++ /dev/null @@ -1,27 +0,0 @@ -using GameCult.Caching; -using MessagePack; - -namespace Aetheria.State.Documents; - -[CultDocument("aetheria.world_state", "aetheria.world_state.v1")] -[CultGlobal] -[MessagePackObject] -public sealed class AetheriaWorldState -{ - [Key(0)] - [CultName] - public string Name { get; set; } = "Aetheria"; - - [Key(1)] - [CultIndex("worldId")] - public string WorldId { get; set; } = "aetheria"; - - [Key(2)] - public int SchemaEpoch { get; set; } = 1; - - [Key(3)] - public string CreatedAtUtc { get; set; } = ""; - - [Key(4)] - public string UpdatedAtUtc { get; set; } = ""; -} diff --git a/Aetheria.State/Migration/LegacyMigrationBoundary.cs b/Aetheria.State/Migration/LegacyMigrationBoundary.cs deleted file mode 100644 index b4f13e72d..000000000 --- a/Aetheria.State/Migration/LegacyMigrationBoundary.cs +++ /dev/null @@ -1,21 +0,0 @@ -namespace Aetheria.State.Migration; - -public static class LegacyMigrationBoundary -{ - public const string LegacyCacheNamespace = "Assets/Scripts/ServerShared/CultCache"; - public const string LegacyRethinkNamespace = "Assets/Scripts/ServerShared/NIH/RethinkDb"; - public const string LegacyGameDataFile = "GameData/AetherDB.msgpack"; - public const string LegacyPlayerSettingsFile = "GameData/PlayerSettings.msgpack"; - public const string LegacyLoadoutExtension = ".loadout"; - public const string LegacyZoneExtension = ".zone"; - - public static string[] ForbiddenLiveAuthorities { get; } = - [ - "DatabaseEntry", - "DatabaseCache", - "JsonKnownTypes", - "Newtonsoft.Json", - "RethinkDb.Driver", - "RethinkTableAttribute" - ]; -} diff --git a/Aetheria.State/README.md b/Aetheria.State/README.md deleted file mode 100644 index f52816ba8..000000000 --- a/Aetheria.State/README.md +++ /dev/null @@ -1,463 +0,0 @@ -# Aetheria.State - -`Aetheria.State` is the replacement owner for durable Aetheria game state. - -It uses modern `GameCult.Caching`, `GameCult.Caching.MessagePack`, and -`GameCult.Mesh`. It does not reference the old `Assets/Scripts/ServerShared` -tree, RethinkDB, JsonKnownTypes, or Newtonsoft.Json. - -The first live state files are `GameData/aetheria-world.cc` for daemon-owned -Verse state and `GameData/aetheria-client.cc` for the client-owned active Verse -target. `Aetheria.State.Import` generates the daemon-owned world file from -checked-in legacy catalog inputs without embedding machine-local absolute -paths. A full import emits one self-contained monolithic `.cc` suitable for a -fresh clone or released package. Runtime-created `.cc.records` directories are -mutable materializations, not part of the release contract. Legacy files such -as `GameData/AetherDB.msgpack` are migration inputs only. - -The typed runtime state model is split into player settings, loadout templates, -run state, zone state, entity snapshots, item slots, weapon groups, action-bar -bindings, stat grids, and runtime session documents. Runtime checkpoints -include current action-bar bindings as stable typed targets: input control -path, binding kind, item key, equipment index, behavior index, -or weapon-group index. They also include current faction relationship rows as -typed corporation references plus relationship tokens and numeric standings. -Zone checkpoints include typed orbit and body rows for generated celestial -state: orbit IDs, parent IDs, distance, phase, fixed positions, body kind, -typed resource item keys, gravity/body multipliers, asteroid entries, and gas/sun visual -parameters. Entity checkpoints include typed simulation stat grids for -temperature, thermal mass, armor, max armor, and hull-conductivity axes. -Player settings include player name, tutorial flag, -story-file hash cursors, gameplay formatting, graphics preferences, input -binding overrides, and action-bar inputs. Do not preserve `SavedGame`, Unity -runtime settings projections, or runtime entity blueprints as opaque payloads in -the new store; they are source shapes for projection, not portable state -authority. - -The Unity client no longer writes `PlayerSettings.msgpack`, `.loadout`, or -`.zone` files. `AetheriaPlayerSettings` is the typed Verse replacement for -`PlayerSettings.msgpack`, and `AetheriaLoadoutTemplate` is the typed Verse -replacement for bespoke loadout files, with structured hull/equipment/cargo/ -docking item slots, nested child-entity loadouts, and weapon groups. -`RuntimePlayerSettings` is the Unity session projection for menus, formatting, -and input binding display. Menu and input-screen changes send typed Eve request -documents to the provider-owned Aetheria Verse service; they are not portable -state authority and must not mutate daemon-owned state inside Unity. -The old `.cc.pending` runtime commit document lane is deleted. Gameplay state, -loadout templates, and run checkpoints are accepted by the daemon/Verse owner -through typed state operations and daemon command documents, not by -renderer-side commit authority. -`AetheriaRuntimeSession` is the typed Verse signal for the running daemon. -`Aetheria.State.Daemon` owns the long-running `aetheria-daemon` heartbeat and -publishes `starting`, `running`, and `stopping` states through `Aetheria.State`; -legacy outbox status remains operation telemetry, not daemon-session ownership. -The daemon has one combat writer in `AetheriaRuntimeDaemonSimulation`. -Projectile movement and contact are computed by Ymir through the injected -physics port and committed by that same daemon writer. Combat state remains in -the native run/zone/entity snapshot graph; -there is no separate abstract combat save model or optional second writer. -Unity Eve surfaces construct typed `gamecult.eve.command.v1` command documents. -`AetheriaRuntimeVerseClient` is the shared CultMesh client boundary for -headless, Unity, and non-Unity runtimes. Typed command clients submit directly -to the same Verse records; there is no command-port facade or cached submitter -between client code and the Aetheria state graph. -`AetheriaEveCommandBridge` is the provider-owned command acceptor for those -records: it validates the provider, surface, and command template before -running the narrow refresh handlers for the catalog and operations surfaces -plus the typed player-settings mutation handlers for gameplay/graphics Eve -controls and typed Verse-host visibility commands. Renderer callbacks do not -accept commands or mutate game state locally. -`AetheriaEveRuntimeBootstrap` mounts the `aetheria.operations` surface through a -runtime-created `UIDocument` after scene load, with environment and command-line -switches for diagnostics. UI Toolkit is now a live runtime lowering path, not -only a package sample. -The old `SavedGame`/`SavedZone` DTOs and `Galaxy` loader constructor have also -been deleted; the new document family is live Verse run/zone state, not a -bespoke save-file format. -Daemon run frames project into Unity through the explicit -`EntityConstructionBlueprintProjector.ProjectObservedFromBlueprint` lowering -path; authoritative blueprint construction remains named separately as a -migration target for code that still has to move into the daemon. -Main-menu gameplay boot also projects the daemon run through -`Galaxy.ProjectObservedDaemonRun` rather than constructing or generating a -Unity-owned run. The Unity handle for this shell is -`ActionGameManager.ObservedGalaxy` to keep the client-side object named as an -observation. -The dead `SavedStory` JSON DTO is gone. `ZoneConstructionBlueprint` and -`RuntimeEntityBlueprint` remain in-memory construction/loadout projections for -Unity runtime code, with `RuntimeEntityBlueprintProjector` capturing and -instantiating those projections. They no longer carry save-file or serializer -vocabulary, and they do not declare MessagePack persistence attributes. - -Keyboard layout rendering now builds the static ANSI-104 display as typed -`InputLayout` rows and columns. It no longer reads a checked-in JSON text asset -or uses `SimpleJson` for display geometry. The generated `AetheriaInput` wrapper -still calls Unity's `InputActionAsset.FromJson`; that is an engine-owned -lowering boundary under Aetheria's remapping layer, not a save format or state -owner. The old generated `GameData/KeyboardLayouts/*.msgpack` cache and the -checked-in `ansi104.json` display file are deleted. Remapping and action-bar -edits are owned by typed `AetheriaPlayerSettings` commits, and the live input -screen now lowers through Eve/UI Toolkit instead of the old drag/drop uGUI -shell. Keyboard display layout edits remain runtime-only until Verse owns a -typed layout document. -The unused Unity Localization package and imported Google Sheets localization -sample have been removed; the old transitive Unity Newtonsoft package is no -longer in the manifest or package lock. - -The old IMGUI CultCache database editor has been deleted, and `NameTools` no -longer exports `NameFile/*.msgpack`. `AetherDB.msgpack` and existing name files -remain legacy catalog inputs only; typed catalog records are the durable owner. -Dead legacy catalog roots for user records and galaxy map-layer resources have -been deleted from live Unity source. Surviving runtime DTOs no longer carry -legacy catalog group/table annotations; their remaining metadata is projection -and inspection metadata for the typed runtime bridge. -Item instances now carry `AetheriaRuntimeItemReference`, a typed item-key -facade. `ItemInstance.ItemKey` is the identity surface; the old `ItemId` -legacy-GUID projection has been deleted from item instances. -Cargo inventory indexes, consumable activation lookup, action-bar consumable -quantity/fill, transfer lookup, trade owned counts, weapon ammo references, and -item-use behavior references now use item keys. The old cargo/consumable GUID -lookup overloads and the derived behavior `AmmoType`/`Item` GUID properties -have been deleted. Action-bar consumable bindings now read the target `ItemKey` -directly from typed runtime catalog rows instead of reconstructing it from -legacy item IDs. -Daemon snapshot DTOs carry typed `ItemKey`/`HullItemKey` fields only for -loadout items, action-bar targets, body resources, entity hulls, and active -consumables; daemon command application no longer reconstructs item keys from -legacy item GUID fields. -Daemon snapshot DTOs now carry typed `FactionKey` only for loadout entities, -run entity snapshots, and faction relationship state. The old command-document -`CorporationLegacyId` fallback fields are deleted. -`EntityConstructionBlueprint` also carries `FactionKey` only. Loadout restore, -entity construction, run entity snapshots, and faction relationship commits no -longer rebuild corporation references from legacy `Faction.ID` GUIDs. -Galaxy faction relationship projection is also typed-key-only: the old -`Faction.Allegiance` GUID dictionary, GUID containment helper, and GUID faction -resolver are deleted from the simulation shell. -The remaining temporary `Faction` shell also uses `GeonameFileKey` and -`BossHullItemKey`; it no longer parses name-file or boss-hull legacy IDs into -GUID link fields. -`Faction` equality, hashing, narrative constraints, sector display filtering, -sector-map link/influence rendering, zone security ownership checks, and -runtime faction-relationship ordering now use `FactionKey`. The legacy -`Faction.ID` projection field is deleted from the temporary simulation shell. -Run checkpoint snapshot DTOs carry typed `OrbitKey`, `ParentOrbitKey`, and -`BodyKey` only for zone orbit/body snapshots. The old command-document -`OrbitLegacyId`, `ParentLegacyId`, and `BodyLegacyId` fallback fields are -deleted. Behavior-state daemon snapshots and readback snapshots carry typed -body-key fields for resource scanner targets and mining tool asteroid belts; -the old raw body ID commit/readback field names are deleted. `ResourceScanner` -and `MiningTool` now retain typed body-key runtime state directly, while -`Zone` owns parsing and resolution at the current Unity body runtime boundary. -`Zone`, `Planet`, `AsteroidBelt`, and `Orbit` now own the runtime body/orbit -key surfaces used for projection, and `ActionGameManager` no longer formats or -resolves body/orbit keys locally while checkpointing or restoring state. -Typed `AetheriaZoneState` orbit/body snapshot rows now use `OrbitKey`, -`ParentOrbitKey`, and `BodyKey` property names all the way through the typed -document and runtime catalog readback surface; the old `...Id` names were only -legacy-shaped aliases for those key strings and are gone. -Orbit-targeting agent runtime also now retains typed orbit keys directly: -`PatrolOrbitsTask`, `MoveToOrbitState`, and `StationTowing` no longer own raw -orbit GUID targets, and `Zone` resolves orbit keys for agent orbit movement. -Orbital runtime entities and their construction blueprints now follow the same -rule: `OrbitalEntity` and `OrbitalEntityConstructionBlueprint` retain -`OrbitKey`, runtime loadout generation seeds the empty value as `""`, and -`Zone` resolves orbit keys for orbital entity movement and dock camera parent -lookup. -Renderer and gameplay orbit consumers now stay on that keyed edge too: -intro-cutscene planet selection, zone planet/belt loading, gas-giant parent -lighting, asteroid-belt UI setup, and `ResourceScanner` planet range checks all -read `OrbitKey` from runtime wrappers and ask `Zone` to resolve it instead of -reading wrapper GUID orbit fields directly. -`Planet` and `AsteroidBelt` runtime wrappers no longer publish separate wrapper -orbit GUID fields either: they keep `OrbitKey` plus their `Orbit` runtime -object, and `Zone` lowers asteroid transforms through `belt.Orbit.ParentOrbitKey` -instead of reading a raw wrapper orbit GUID. -`Zone`'s own public key-facing lookup surface is also now natively keyed: -body and orbit lookups resolve through key-indexed runtime dictionaries rather -than reparsing GUIDs from `BodyKey` or `OrbitKey` strings before touching the -Guid-indexed substrate. -The live asteroid helper surface follows the same rule: nearest-asteroid, -asteroid-exists, transform-update, and mining paths now use typed body keys or -runtime `AsteroidBelt` objects directly instead of Guid asteroid-belt helper -overloads. -The package runtime catalog snapshot indexes items by canonical item key; Unity -gameplay, HUD, inventory, trade, ship, and zone helper lookups use `ItemKey` -directly instead of deriving `ItemId` and searching by legacy GUID. -The Unity runtime package snapshot also no longer exposes item, corporation, or -name-file lookup by legacy ID, and its item/corporation/name-file DTOs no -longer publish legacy relationship fields. Canonical legacy-ID lookup remains -only in `Aetheria.State` migration/catalog inspection APIs. -The shared runtime item catalog reader is also item-key-only now: its GUID index, -`GetRuntimeItem(Guid)` entry point, raw GUID string fallback, -`AetheriaRuntimeItemReference(Guid)` constructor, `FromLegacyId`, and reverse -legacy-GUID projections have been deleted. -`ItemManager` now reads typed item rows directly from the shared -`AetheriaRuntimeCatalogSnapshot`; the redundant `AetheriaRuntimeItemCatalog` -adapter layer has been deleted. -The old generic `RuntimeCatalogLink` abstraction, `ItemManager.GetData` -hydration path, vague `ItemInstance.Data` identity field, and old -`RuntimeItemReference.Projection` cache have been deleted; runtime item facts -come from typed catalog rows. -ItemManager's behavior construction no longer exposes a generic GUID reader or -item GUID projection helper. Migrated legacy-id behavior payload fields carry -`LegacyIdValue` as provenance/backfill only; gameplay item behavior reads the -typed `ItemKeyValue` emitted by import/runtime projection. -Loadout generation now applies typed catalog prefilters for item category, -hardpoint type, shape fit, hull type, and behavior kind, then instantiates -generated equipment directly from typed runtime rows. -Typed item definition `Category` values are stable Aetheria tokens such as -`hull`, `weapon`, `gear`, `cargo-bay`, and `docking-bay`; legacy union class -names remain migration provenance, not live category predicates. -The surviving trade menu also applies typed catalog prefilters for row size, -hardpoint type, and behavior kind before legacy buy actions. -The old `ItemData`/`EquippableItemData`/`HullData`/`WeaponItemData` DTO -hierarchy has been deleted from live Unity source. Shared runtime geometry and -stat primitives now live in `RuntimeGeometry.cs`; item facts come from typed -runtime rows. The old `RuntimeCatalogEntry` and `RuntimeItemProjectionEntry` -base names no longer appear in live source. -The dead `InspectableRuntimeCatalogLinkAttribute` inspector metadata is gone; -live Unity source no longer carries `LegacyPayloadKeyAttribute` either. -Catalog import and migration readers own deterministic legacy field layout -through explicit key maps, while runtime behavior construction uses an explicit -typed behavior-kind mapper. -Agent task DTOs no longer inherit the item projection base; they are local -runtime AI work orders until a typed Verse task document exists. - -`Aetheria.State.Import` captures the legacy catalog files into typed quarantine -state: path, size, and SHA-256 fingerprint for `AetherDB.msgpack` plus -`NameFile/*.msgpack`, and a migration ledger update. It also performs a bounded -raw MessagePack union pass that maps stable catalog fields into typed -item/faction/name-file documents without compiling Unity's legacy domain model -into `Aetheria.State`. The current checked-in catalog maps to 115 item -definitions, 12 factions, and 12 name files. Typed item definitions include -legacy manufacturer IDs, price, shape dimensions, occupied cell counts, full -shape-cell masks, interior shape masks for hull/cargo equipment, hull hardpoint -definitions, behavior kind fingerprints, typed recursive behavior payloads, and -stable equipment facets: hardpoint type, hull type, stack size, durability, and -weapon range/caliber/type/fire/modifier classifications. -Typed corporation documents include the legacy short name, the true description -field from key 3, name-file and boss-hull legacy IDs, influence distance, -allegiance count, full allegiance edges, and music bank IDs. Corporation -documents are `aetheria.corporation.v2` records because runtime generation -needs actual allegiance weights, not only display counts. Runtime checkpoints -now persist session faction relationships into typed run state, and runtime -sessions publish typed state-host liveness. Zone checkpoints persist generated -orbit/body rows into `AetheriaZoneState`, so `ZoneConstructionBlueprint` is a -one-shot construction input carrying typed orbit/body keys rather than the retained surface carrying celestial graph facts. Entity checkpoints -persist hull simulation grids into `AetheriaEntitySnapshot`, so -`EntityConstructionBlueprint` is no longer the only surface carrying those live -values. Run checkpoints project the flattened live entity graph, including -docked children, into exact typed entity snapshot records. Continue restore -lowers the current zone entity graph from those records, including position, -velocity, target/contact state, child/docking relationships, -shutdown/heatsink state, thermal exposure, active consumable timers, and typed -stat grids for temperature, thermal mass, armor, max armor, and hull -conductivity. Continue also lowers typed weapon and behavior runtime rows back -into live equipped-item and active-consumable behavior instances through -behavior-owned restore methods; construction/loadout blueprints do not own -behavior replay. Broader runtime object graphs without typed rows remain legacy -until dedicated typed runtime documents exist. Live behavior -instances expose typed behavior kind and group through `Behavior.Kind` and -`Behavior.Group`; behavior construction now reads typed payload fields through -`RuntimeBehaviorDefinition` instead of projecting through config DTOs. Stat -modifiers ask live behavior instances for target stats instead of reading -behavior config objects directly; base `Behavior` captures a runtime stat table -at construction without config reflection. -Heat, EnergyDraw, and Cooldown behavior instances now copy their constructor -stat/scalar inputs into explicit runtime fields and no longer retain their -config subclasses after construction. -Wear, Visibility, Reflector, VelocityLimit, VelocityConversion, and ItemUsage -also copy their constructor stat/key/scalar inputs into explicit runtime fields -instead of retaining config subclasses. -Capacitor, Shield, Thruster, MiningTool, ResourceScanner, Thermotoggle, Switch, -HeatStorage, and Cockpit have also dropped runtime config retention. Thermotoggle -adjustability is exposed on the live behavior instance, not through its config. -Reactor, Radiator, Sensor, and StatModifier now copy their constructor -performance stats, curves, flags, and target strings into explicit runtime -fields; those live behaviors no longer retain their config subclasses. -AetherDrive now follows the same rule for rotor geometry, performance stats, -torque curve, audio parameters, and prefab path. Weapon, InstantWeapon, -ConstantWeapon, ChargedWeapon, and LockWeapon now copy constructor stats, -curves, ammo/reload fields, guided projectile profile fields, burst/cooldown -fields, charge multipliers, and lock parameters into runtime-owned fields. -Behavior instances no longer retain construction config DTOs after -construction. -Cockpit, HeatStorage, Switch, Trigger, and TurretController read directly from -typed runtime behavior definitions. -Heat, EnergyDraw, Cooldown, Wear, Visibility, Reflector, VelocityLimit, -VelocityConversion, and ItemUsage also bypass the temporary config bridge and -read typed behavior fields through `RuntimeBehaviorDefinition`, registering -runtime performance stats explicitly for `StatModifier`. -Capacitor, Shield, MiningTool, ResourceScanner, Thermotoggle, and Thruster now -follow the same direct-definition path. -Reactor, Radiator, Sensor, StatModifier, and AetherDrive also read typed -payload fields through `RuntimeBehaviorDefinition`; AetherDrive registers its -live performance-stat names explicitly for `StatModifier`. -Weapon, InstantWeapon, AutoWeapon, ConstantWeapon, ChargedWeapon, LockWeapon, -Launcher, and GuidedWeapon payloads also construct directly from -`RuntimeBehaviorDefinition`. The old `RuntimeBehaviorConfig` bridge, -`BehaviorPayloadReader`, and weapon config hierarchy are deleted. -Typed name-file documents are `aetheria.name_file.v2` records. They carry the -legacy ID, display name, count, compact sample names for surfaces, and the full -name array used by `Galaxy`/Markov name generation through typed name-file -keys rather than legacy `NameFile.Names`. -Legacy GUID references that are `Guid.Empty` are imported as absent references, -not as resolvable catalog links. -Before materializing state, the importer clears the generated `.cc`, -`.cc.records`, and `.cultmesh` outputs for the selected state path. Legacy -catalog inputs are captured first and are not deleted. Full import writes the -result through the monolithic CultCache store; no ignored directory record may -be required to open the artifact. This lets schema changes rebuild the typed -artifact instead of failing on a stale embedded schema catalog. - -`AetheriaCatalogSnapshot` is the typed read surface over materialized catalog -documents. It exposes trade items, legacy-ID migration lookup, typed -manufacturer/corporation/name-file key lookup, corporation prefix lookup, -equipment items, behavior lookup, and hardpoint lookup without reading runtime -projection DTOs. - -`AetheriaCatalogSurfaceProjector` publishes the first Eve-compatible provider -surface from typed catalog state. The importer materializes -`gamecult.eve.surface.v1` at `eve:surface:aetheria.catalog.operator` with -summary, trade-catalog, and corporation views. This is a typed CultCache -provider document and a local mirror of the Eve contract until the shared Eve -Unity UI Toolkit lowering package exists. - -`Aetheria.State.Unity` is the Unity-facing runtime read facade over typed state. -It resolves the active Verse through the client-owned `aetheria-client.cc` -target document, then opens the selected typed state source. Today that source -is still a local `.cc` file transport, so the facade emits immutable catalog -read models for trade, equipment, behavior, hardpoint, manufacturer, -corporation, and name-file queries, exposes typed item shape masks for layout -and fitting consumers, exposes typed interior masks and hardpoints for -equipment/cargo layout consumers, exposes typed behavior payload read models, -can read typed player settings for Unity boot, and can read the published Eve -catalog surface. It does not deserialize legacy runtime projection objects and -does not write daemon-owned Verse state. Unity can use this as the first -package boundary once CultLib/Eve runtime packaging is available. -Unity `MainMenu` now lowers a typed Verse settings shell over that split: -client-target edits persist locally in `aetheria-client.cc`, while Verse-host -visibility changes append provider-owned Eve request documents against the -selected local Verse state file. -`Galaxy` now consumes this package-owned runtime catalog for faction selection -and name generation, so generated sectors use typed corporation v2 and -`aetheria.name_file.v2` records instead of legacy `Faction`/`NameFile` -catalog documents. Loadout manufacturer-distance weighting also resolves -manufacturers through typed corporation keys rather than parsing manufacturer -legacy IDs. `Galaxy` still projects typed corporations into temporary `Faction` -DTOs, including local GUID equality for the surviving simulation surface, -because the surrounding simulation has not yet been rebuilt around typed -corporation records. The DTOs now carry typed faction/name-file keys and typed -allegiance edges so they cannot reclaim catalog lookup authority. - -`Aetheria.State.Smoke` writes and reopens full typed player settings plus a -typed loadout template in addition to world, catalog, run state, zone state, and -an entity snapshot. It also writes and reopens `AetheriaRuntimeSession`, the -provider-owned `aetheria.player_settings` Eve surface, and player-settings Eve -commands that mutate typed significant-digit and minimap-asteroid settings -through the provider bridge. The smoke checks that the Eve provider advertises -the runtime-session schema and player-settings command surface. The run smoke -proves a typed run can reference a typed zone, the zone can preserve -orbit/body rows and reference typed entity snapshots, and entity snapshots can -preserve equipment slots, weapon groups, and simulation stat grids without -reviving `PlayerSettings.msgpack`, `.loadout`, or `.zone` serialization. -Unity `MainMenu` now lowers that same player-settings Eve document contract -locally through UI Toolkit instead of hand-authoring separate gameplay/graphics -widgets; the menu shell keeps name entry and back-navigation while gameplay -still owns command semantics through `ActionGameManager.RequestRuntimePlayerSettingsCommand`. -The run document also carries `CurrentEntityKey`, so Continue restore binds the -saved player entity by exact typed record identity instead of reconstructing it -from a transient entity-slot index. Canonical typed run state no longer stores -`CurrentZoneEntityIndex`; older stored runs are translated through the package -reader into `CurrentEntityKey`. The older runtime commit importer has -been removed from the first-party state host and Unity package. - -`Aetheria.State.Verify` opens a materialized state file and checks that the -typed migration ledger matches the actual item, corporation, and name-file -records in the `.cc` store. It also verifies the canonical legacy-ID lookup API -for one migrated record of each catalog kind, checks that name-file v2 records -carry their full name arrays, and checks that expanded item and corporation -catalog facts survived materialization. The verifier also exercises the typed -catalog snapshot query surface, shape-mask invariants, the typed behavior -payload tree, and the published Eve catalog surface. The current catalog -materializes 65 behavior payloads, 661 behavior fields, and 18 behavior-side -legacy-ID references. Legacy content currently includes at least one overhanging -hardpoint; the verifier preserves the payload and checks hardpoint local masks -rather than silently converting content repair into migration. The verifier also -fails if live `Assets/Scripts` source grows back serializer or legacy database -symbols such as MessagePack, Newtonsoft, JsonKnownTypes, RethinkDB, or old -`DatabaseEntry`/`DatabaseLink` references; the remaining MessagePack usage is a -named package/import CultCache transport boundary, not gameplay state authority. -The package boundary is also fenced: MessagePack symbols may only appear in the -runtime catalog reader, CultCache document store, and typed runtime/Eve -command document files until generated CultCache serializers replace that -temporary Unity-side codec. The verifier also checks that the Eve runtime -bootstrap exists, mounts the operations surface through the UI Toolkit presenter, -and keeps renderer commands routed through the typed Eve command inbox. -Use it after import when `GameData/aetheria-world.cc` changes. - -`Aetheria.State.Unity.Smoke` opens the materialized state through the runtime -facade and proves read-only Unity-facing catalog access without the legacy -catalog reader. Live Unity code uses typed Eve or daemon request documents -instead of runtime commit outboxes. Runtime and Eve requests are typed `.cc` -records with embedded schema catalogs; their current record payload codec is -still temporary package code until Unity consumes generated CultCache -serializers directly. - -`Aetheria.State.Daemon` opens the CultMesh Verse daemon, publishes daemon-owned -Verse identity/health/game/editor surfaces, accepts observed Eve command -documents on startup, and executes observed daemon command records each tick. -For a bounded one-shot daemon tick: - -```powershell -dotnet run --project .\Aetheria.State.Daemon\Aetheria.State.Daemon.csproj -- --once -``` - -The daemon publishes `AetheriaEveCommandAcceptanceStatus`, the -`aetheria.operations` Eve surface, and the -typed `aetheria.player_settings` Eve surface after request acceptance attempts, -so observed depth, accepted/applied/rejected counts, failures, timestamps, and -player settings controls are typed state rather than console-only status. It also -persists daemon-owned `aetheria.verse_host_settings.v1`, with boot-time -defaults or overrides for service id, Verse id, visibility, and CultMesh -address. When visibility is `public`, the daemon also serves a -derived CultMesh Verse catalog from that same typed host document, using the -located service name as the discoverable `cultnet://...:3075` endpoint. The -operations surface and provider advertisement project from that typed host -document instead of hardcoded daemon identity strings. The provider -advertisement still publishes `gamecult.eve.provider_advertisement.v1` for the -`aetheria` provider, advertising the catalog, operations, and player-settings -surfaces plus the typed schemas witnessed by the local `.cc` state file. - -Remote Unity clients should not pretend a public Verse is local authority. Use -`Aetheria.State.Replica` to pull a cache-only local witness from the daemon: - -```powershell -dotnet run --project .\Aetheria.State.Replica\Aetheria.State.Replica.csproj -- sync --endpoint cultnet://host:3075 --game-data-root .\GameData --verse-id aetheria.public -``` - -That writes `GameData\Verses\.cc` as a replica projection of daemon -truth. The selected remote client target resolves to that replica path. The -daemon still owns accepted state; the replica is only the local read surface -Unity lowers from. - -The Unity Verse-settings shell can also trigger the same one-shot sync through -the `Aetheria.State.Replica` bridge when the repo-local tool is available. That -keeps target selection local while the replica organ owns daemon transport. - -The old standalone `Aetheria.State.DrainCommands` applicator is deleted. Command -acceptance is daemon behavior now; bounded operation uses `Aetheria.State.Daemon ---once` so the same Verse member publishes the same provider, health, command -boundary, and Eve surfaces. - -Current rebuild notes: - -- `MessagePack` 3.1.4 is pulled transitively through CultLib and is currently - flagged by NuGet advisory `GHSA-hv8m-jj95-wg3x`; update CultLib's - `GameCult.Caching.MessagePack` package ownership rather than adding a local - Aetheria override. -- `CultRecordRef` should become the persisted reference shape once the - MessagePack source-generator path handles the GameCult formatter cleanly. - Until then, v1 profile documents store explicit record-key strings at the - persistence boundary. diff --git a/Aetheria.State/docs/client-runtime-conformance.md b/Aetheria.State/docs/client-runtime-conformance.md deleted file mode 100644 index 2842a4c82..000000000 --- a/Aetheria.State/docs/client-runtime-conformance.md +++ /dev/null @@ -1,75 +0,0 @@ -# Aetheria Client Runtime Conformance - -This is the client-runtime authority map for removing Unity as a privileged -gameplay body without replacing it with a new privileged runtime. - -The daemon owns Aetheria game state, simulation, generated level content, -assets, render-field declarations, Eve/CultUI surfaces, and typed operations. -Eve owns the game-agnostic surface language: layout, interaction, state -pointers, world-state lowering, render-field instructions, and 2D/3D -scalar/vector field visualization contracts. Client runtimes lower those -surfaces and submit typed operations. A runtime may own presentation, input -hardware, local caches, native buffers, and frame timing. It must not own -gameplay truth merely because it can draw the game. - -The desired end state is renderless Aetheria. Aetheria should not preserve a -Unity, Godot, Electron, or browser side path for game behavior. If a runtime -needs an Aetheria-specific rendering rule to show the game, that rule belongs in -daemon-authored Eve/CultUI or in a game-agnostic Eve lowering primitive. - -## Runtime Roles - -| Runtime | Role | Pass condition | -| --- | --- | --- | -| Hermodr | Unspecialized Eve/browser lowering sanity check for the RTS surface. | It reconstructs the Aetheria RTS gameplay surface from daemon-advertised Eve surfaces, typed state pointers, typed operations, render-field declarations, and CultMesh CDN assets without Aetheria-specific renderer code. | -| Electron | Player-facing Starbridge RTS client. | It renders the same daemon-authored RTS surface through the shared Eve package, with only app shell, launch, packaging, and runtime ergonomics outside Eve. | -| Unity | Shared pilot reference client for Terminus, Starbridge, and Arena; also a demolition target. | It behaves as a renderer/input shell over daemon state and Eve/CultUI/world-state surfaces; remaining GameObject, UI Toolkit, camera, input, and presentation adapters are classified shims or renderer-owned concerns. It owns no Hangar, loadout, progression, or mode-session truth. | -| Godot | Future unspecialized Eve/runtime parity target for the ARPG surface. | It reconstructs the ARPG gameplay surface from daemon API plus Eve/CultUI/render-field specs and CultMesh CDN assets, not from a Unity port or daemon-side Godot mode. | - -Hermodr is the purity test for the RTS surface. Electron is the shipped RTS -client. Those are different jobs. If Electron can display something Hermodr -cannot reconstruct from Eve plus daemon API, either Electron is using a private -shortcut or Eve/CultMesh is missing a reusable primitive. - -Godot has the same relationship to the ARPG surface that Hermodr has to the RTS -surface. It should prove the ARPG client can be rebuilt from generic Eve, -CultMesh state, daemon-owned assets, and engine-native lowering. It should not -inherit Unity scene authority, Unity coordinate leakage, Unity UI Toolkit -assumptions, or Unity-only gameplay adapters. - -## Forbidden Shapes - -- No daemon-side `Godot` mode, `Electron` mode, `Hermodr` mode, or `Unity` - behavior branch for gameplay state. -- No renderer-local Aetheria compensator that changes game behavior instead of - being promoted into Eve/CultMesh or daemon-authored state. -- No Aetheria-specific Hermodr plugin for RTS map semantics. -- No Godot port of Unity scene code as the ARPG authority. -- No Electron-only field, asset, or command interpretation that Hermodr cannot - consume through shared Eve/CultMesh primitives. -- No Aetheria-specific Unity, Godot, Electron, or Hermodr renderer compensator - for 2D or 3D scalar/vector field visualization. -- No daemon-published Eve surface that requires a named runtime to understand - Aetheria gameplay semantics. -- No Aetheria-owned Unity/Godot/Electron world renderer that should instead be a - game-agnostic Eve lowerer. - -## Acceptance Gates - -1. The daemon publishes the RTS surface as Eve/CultUI plus typed state pointers, - operation handles, render-field declarations, and CultMesh CDN asset refs. -2. Hermodr lowers that RTS surface without Aetheria-specific renderer code. -3. Electron renders the same RTS surface through the shared Eve package. -4. The daemon publishes the ARPG surface with the same kind of portable surface - contract. -5. Unity consumes the ARPG contract as a renderer/input shell, with remaining - shims listed for deletion. -6. Godot lowers the ARPG contract without inheriting Unity authority. -7. Eve lowerers can reconstruct the required 2D and 3D gameplay surfaces from - daemon-authored state pointers, field documents, asset refs, and operation - descriptors without Aetheria runtime branches. -8. World-state lowerers live in Eve or shared runtime packages, while Aetheria - contributes authored surfaces, state documents, assets, and fixtures. - -Until those gates pass, Aetheria is daemon-authoritative in pieces, not free of -Unity as a complete client dependency. diff --git a/Aetheria.State/docs/cultmesh-cross-runtime-primitives.md b/Aetheria.State/docs/cultmesh-cross-runtime-primitives.md deleted file mode 100644 index 30a2017d4..000000000 --- a/Aetheria.State/docs/cultmesh-cross-runtime-primitives.md +++ /dev/null @@ -1,407 +0,0 @@ -# CultMesh Cross-Runtime Primitives - -Aetheria is the shakedown cruise for CultMesh. The Rust rebuild should not turn every useful idea into Aetheria-only glue. If a pattern helps any typed Verse daemon expose state, operations, queries, native views, authority, or UI surfaces across runtimes, it belongs in the shared CultMesh/CultLib layer. CultMesh should be expansive enough that application code feels like it is holding managed native state, while the runtime handles locality, synchronization, authority, codecs, and cache layout. - -The bar is deliberately high: CultMesh should feel managed, cozy, and almost unfairly ergonomic to the developer, while the runtime silently chooses the fastest valid path underneath. In-process calls, shared CultCache slabs, native slices, IPC, remote transport, and browser/WASM views are locality choices, not different application APIs. The developer writes against typed Verse concepts; CultMesh supplies the plush handles, reactive wrappers, generated operations, query surfaces, native views, and diagnostics that make those concepts feel obvious. - -The promotion rule is simple: if a helper would make Aetheria, Brokkr, Odin, -Bifrost, Eve, Ymir, or VoidBot nicer in the same way, it is not an Aetheria -helper. Hoist it into the shared cross-runtime CultMesh/CultLib layer first, -then let Aetheria consume it as ordinary infrastructure. Aetheria may define -domain nouns and policies; CultMesh owns the managed-feeling primitive shape -that makes those nouns feel native from C#, Rust, TS, Unity, browser/WASM, and -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 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 -managed, cozy developer surface that reads like ordinary domain code and -compiles down to the fastest safe path the runtime can prove. - -## Design Bar - -1. Typed state is the default. User code should hold handles, pointers, operations, and query objects, not record keys, schema ids, slot numbers, or raw payload maps. -2. The same semantic surface works in Rust, TS, Unity/C#, browser/WASM, Eve/CultUI, and native tools. -3. Locality is invisible until the developer asks for diagnostics. Co-located runtimes get shared memory or direct calls; remote runtimes get transport; browser runtimes get WASM-compatible views. -4. Authority is a first-class Verse primitive. Policies, leases, runtime roles, claims, and future quorum hooks should be reusable across daemons. -5. UI surfaces can contain typed pointers into daemon state. The UI runtime resolves, watches, invalidates, and invokes typed operations automatically. -6. Native views expose zero-copy or near-zero-copy slices when safe, with the same state semantics as remote query/subscription fallbacks. -7. Schema evolution is part of the developer experience. Bindings and migration manifests come from one source instead of hand-maintained slot maps. - -## Canonical State Before Projection - -CultMesh should make one canonical typed document feel native everywhere. If a -feature's game state is `AetheriaRuntimeFooDocument`, the daemon, Unity, -Electron, browser, tools, and future Rust clients should all hold managed -handles to that document type. Authority, prediction, debouncing, -reconciliation, quorum, locality, and transport are handle/runtime behavior, -not reasons for each application to create a private "truth" object and then a -client projection copy. - -A projection is a different state shape, not the normal visibility mechanism. -Use projected documents or query surfaces for hidden-information filtering, -derived aggregation, viewport/windowing, SoA/native memory layout, lossy UI -summaries, Eve/CultUI surfaces, or temporary compatibility bridges. Do not -project merely because a client needs to read daemon-owned state. - -This rule is cross-runtime. C#, Rust, TypeScript, Unity, browser/WASM, and tool -hosts should all express the same intent: grab a typed document/collection/query -handle, read it for display, or mutate/submit through it when authority policy -allows. CultMesh supplies the managed access conventions and generated handles -that make that simple. - -## Ownership Boundary - -| Belongs in CultMesh/CultLib | Belongs in Aetheria | -| --- | --- | -| Typed document handles, record routing, schema ids, migrations, codecs. | Aetheria world, zones, entities, inventory, stats, equipment, factions, Starbridge scenario concepts. | -| Typed operation handles, receipts, idempotency, authority metadata, prediction hooks. | Pilot, RTS, refit, trade, interaction, construction, combat, and editor operation vocabulary. | -| Typed query surfaces, query planning, locality routing, subscription lifetimes. | Object viewport, gravity viewport, selected object, inventory, stat, station, encounter, and tactical map queries. | -| Reactive state pointers and UI binding semantics. | Aetheria Eve/CultUI surface content and domain-specific panel layout. | -| Native slice descriptors, memory safety contracts, dirty ranges, runtime adapters. | Aetheria render columns, physics body columns, inventory/stat column definitions. | -| Authority primitives: policy modes, leases, claims, runtime roles, diagnostics. | Which Aetheria operation kinds require which claims and which runtime cares most about each thing. | -| CultMath primitives: `Vec2`, `Vec3`, `Rect`, `Circle`, `Sphere`, transforms, deterministic scalar helpers. | Aetheria coordinate conventions, body meanings, sensor ranges, gravity brush semantics. | -| Provider discovery and interface binding conventions for Odin/Bifrost/Eve. | Aetheria provider advertisements, operation/query catalogs, and surface ids. | - -## Primitive Set - -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(...)` 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 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` builds Aetheria runtime surface source data, then lowers it to the canonical `GameCult.Eve.Surface.EveSurfaceDocument` published through CultMesh. CultMesh owns state and operation binding descriptors; Eve owns the surface document and CultUI composition model. Mesh no longer defines a parallel Eve surface DTO. -- `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 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. -- `E:/Projects/CultLib/src/GameCult.Caching.MessagePack/DirectoryMessagePackBackingStore.cs` now recovers readable schema-stamped cold records when the hot directory manifest is missing the record's catalog entry. Aetheria hit this with persisted Verse authority policy state; the fix belongs in CultCache because durable Verse state should feel managed and resilient across runtime/schema refreshes, not like hand-maintained manifest bookkeeping. -- `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: - -```ts -await node.putRaw(recordKey, schemaId, msgpackBytes); -const frame = decodeFrame(await node.get("daemon:aetheria.frame.latest.v1")); -``` - -Desired shape: - -```ts -const run = await verse.aetheria().currentRun().watch(); -const frame = await run.frame().latest(); -``` - -The handle owns record keys, schema ids, codec choice, migration readers, cache hydration, and subscription invalidation. The application code sees the typed value and its stable semantic location in the Verse. - -### Typed Operation Handles - -Current leak: - -```ts -await client.setMoveVector({ - actorEntityKey, - directionX: input.x, - directionY: input.y, - scalar: 1, -}); -``` - -Desired shape: - -```ts -await verse - .aetheria() - .entity(actorEntityId) - .pilot() - .move({ x: input.x, y: input.y }); -``` - -CultMesh should generate operation handles from schema. The handle carries payload shape, authority claim metadata, routing, idempotency, receipts, diagnostics, and optional prediction hooks. The runtime decides whether this is a local direct call, a shared-slab write, a network operation, or a WASM host call. The RTS binding generator now emits `createAetheriaRuntimeRtsOperationHandles(...)` for `setMoveVector` and `setTarget`; the Electron client consumes those generated TS `CultMesh.operation(...)` handles while preserving the current daemon command document as an internal transport detail. - -### Typed Query Surfaces - -Current leak: - -```ts -const frame = await client.latestFrame(); -const objects = projectObjectsViewport(frame, rect, controlledEntityIds); -``` - -Desired shape: - -```ts -const objects = await verse - .aetheria() - .zone(zoneId) - .objects() - .visibleTo(controlledEntityIds) - .within(rect) - .watch(); -``` - -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 - -Current leak: - -```csharp -using var client = await AetheriaClient.OpenAsync(statePath, runtimeId); -var resolver = client.State.CreateEveSurfaceCultMeshStateRefResolver(); -var selected = resolver(surface.StateRef); -``` - -Desired shape: - -```ts -surface.panel("Selection", { - entity: verse.aetheria().selection().entity().pointer(), - actions: verse.aetheria().selection().entity().operations(), -}); -``` - -Eve/CultUI props should be allowed to hold typed state pointers and operation handles. The UI runtime resolves them, subscribes to them, handles unavailable state, invalidates stale values, and invokes typed operations without custom per-daemon adapters. - -### Native Slice Views - -Current leak: - -```csharp -var descriptor = await client.GetSoaViewAsync(); -var map = MemoryMappedFile.OpenExisting(descriptor.MemoryMapName); -``` - -Desired shape: - -```csharp -using var view = await verse.Aetheria() - .Zone(zoneId) - .RenderView() - .AsNativeArrays(); -``` - -CultMesh/CultCache should define native view descriptors, dirty ranges, schema fingerprints, lifetimes, and safety rules. Unity gets `NativeArray`-friendly views. Rust gets slices. Browser/WASM gets typed array views when co-located or a compatible copied view when remote. The semantic surface stays the same. - -### Authority Primitives - -Current leak: - -```csharp -var decision = AetheriaRuntimeAuthorityRouter.Authorize(policy, command, runtimeId, leases); -``` - -Desired shape: - -```rust -let receipt = verse - .aetheria() - .entity(actor) - .pilot() - .move(vec2) - .with_authority(runtime.claims().local_responsiveness()) - .await?; -``` - -Near-term shared-library sugar already exists in C# and TS: - -```csharp -var context = CultMesh.OperationContextFor("unity-raven") - .Claim("pilot-control", shardId: "zone:raven") - .Route(CultMeshLocalityKind.SharedMemory, "co-located Verse") - .Idempotency("move:raven:1") - .Build(); -``` - -```ts -const context = CultMesh.operationContextFor("browser-starfire") - .claim("simulation-authority", { shardId: "zone:starfire" }) - .route("wasm", "browser-local query") - .idempotency("move:starfire:1") - .build(); -``` - -Authority is not topology. CultMesh should provide reusable policy modes, claim kinds, leases, runtime roles, denial reasons, receipts, and diagnostics. Aetheria configures which operation families require which claims. Future server-authoritative, trusted distributed, lease, browser-host, and quorum modes should use the same primitive vocabulary. - -### Geometry And Math Values - -Current leak: - -```ts -viewport({ minX, minY, maxX, maxY }); -``` - -Desired shape: - -```ts -const rect = CultMesh.rect(CultMesh.vec2(x0, y0), CultMesh.vec2(x1, y1)); -await zone.gravity().influences().intersecting(rect); -``` - -CultMath/CultMesh should provide deterministic cross-runtime primitives for vectors, rects, circles, spheres, transforms, scalar tolerances, and spatial query inputs. In CultLib C# this starts as `CultVec2`, `CultVec3`, `CultRect`, `CultCircle`, and `CultSphere` under `GameCult.Geometry`; in TS this starts as `CultMeshVec2`, `CultMeshRect`, and `CultMeshViewportRequest` in `cultmesh-ts`. SoA does not require splitting a vector into unrelated conceptual values; the layout can still be cache-efficient while the API treats position, velocity, and acceleration as meaningful vector values. - -### Locality-Aware Routing - -Current leak: - -```ts -await fetch("http://127.0.0.1:39217/ymir/overlap-sphere", ...); -``` - -Desired shape: - -```ts -const hits = await verse - .ymir() - .world(worldId) - .overlapSphere({ center, radius }) - .layers("gameplay") - .all(); -``` - -The caller asks for a typed operation or query. CultMesh chooses in-process, shared slab, IPC, network, RUDP, browser/WASM bridge, or remote Verse execution. The choice is observable for diagnostics, but it is not encoded into application logic. - -### Schema Evolution - -Current leak: - -```ts -const entityId = payload[ENTITY_ID_SLOT]; -``` - -Desired shape: - -```ts -const entity = frame.entities.byId(entityId); -``` - -Bindings should be generated from one schema source into Rust, TS, Unity/C#, browser/WASM, Eve/CultUI metadata, and migration manifests. MessagePack key order, schema ids, deprecated fields, compatibility readers, and version negotiation are shared machinery. - -### Surface Bindings - -Current leak: - -```json -{ "kind": "button", "command": "dock", "payload": { "entityId": 42 } } -``` - -Desired shape: - -```ts -button({ - icon: "anchor", - enabled: selected.docking().canDock(), - onPress: selected.docking().dock(), -}); -``` - -CultUI should bind to typed state and operation handles. A surface can be rendered in Unity, browser, terminal, or an MCP host without rewriting command payloads or state-ref resolution. Bifrost exposes daemon Verse tools through these provider-owned interfaces; it is not a Brokkr-specific socket wrapper. - -The TS side now has the primitive shape too: - -```ts -const selected = CultMesh.statePointer( - "aetheria.selection.current", - () => selection.resolve(), - (emit) => selection.watch(emit), -); -``` - -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. - -The RTS generated bindings now create state pointer handles for the daemon -frame, health, authority policy, and Starbridge session documents, and the RTS -surface catalog advertises them with the same shared-memory route and schema -source metadata as the query handles. This is the proof shape Eve/CultUI and -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 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 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 queries, and quorum-backed -authorities are configuration details behind that handle. Generated Aetheria -RTS handles should target this shape instead of emitting schema dictionaries, -transport-specific lookups, or hand-written state-file readers. - -## Aetheria Proofs - -Aetheria should prove these primitives by forcing each client to use the same shared shape: - -1. Rust Aetheria loads the measured fixture world and publishes typed state through CultMesh. -2. Rust Ymir answers step, overlap, cast, contact, broadphase, sparse-cluster, and viewport-intersection queries through typed query surfaces. -3. The browser RTS client renders object and gravity viewports through query handles and submits operations through typed handles. -4. Unity consumes render and physics views as native slices while keeping gameplay authority out of the scene hierarchy. -5. Eve/CultUI panels bind to state pointers and operation handles with no daemon-specific resolver. -6. Bifrost MCP tools discover provider interfaces through Odin and invoke typed Verse operations/queries without knowing whether the live daemon is Brokkr, Aetheria, or something else. -7. Authority diagnostics explain which runtime may author which claim without changing operation schemas. - -## Performance Contract - -The cozy API must not be a slow API. - -- Handles should be allocation-light after binding. -- Query parameter objects should be value-shaped and cacheable. -- Native views should expose contiguous slabs and dirty ranges where co-location allows it. -- Remote fallbacks should keep the same semantic API but may return copied values or streamed deltas. -- Hot paths must support generated code, stable layout, pooled codecs, and deterministic math. -- The runtime should expose diagnostics for route choice, copy count, slab identity, schema version, authority decision, and subscription churn. - -## Build Implication - -When the Rust rebuild needs a new adapter, ask whether that adapter is actually a missing CultMesh primitive. Aetheria should contain the game. CultMesh/CultLib should contain the magic that makes the game feel native from every runtime. diff --git a/Aetheria.State/docs/cultmesh-ergonomics-staged-migration-plan.md b/Aetheria.State/docs/cultmesh-ergonomics-staged-migration-plan.md deleted file mode 100644 index 21ea100b6..000000000 --- a/Aetheria.State/docs/cultmesh-ergonomics-staged-migration-plan.md +++ /dev/null @@ -1,641 +0,0 @@ -# CultMesh Ergonomics Staged Migration Plan - -This is the staged plan for getting CultMesh ergonomics from "usable transport and replicated records" to the ideal developer experience: typed state that feels local, reactive UI surfaces that resolve daemon pointers automatically, native slab views for fast clients, and authority semantics that are explicit without making every client hand-roll routing code. - -The Rust/WASM rebuild remains valuable, but it is not the MVP path. The near-term goal is to build the perfect machine in C# first, freeze the API shape we actually want, then let any later Rust body implement that shape rather than inherit the current leaks. - -## North Star - -C# clients should read like domain code: - -```csharp -var aetheria = verse.Aetheria(); - -var currentDockingBay = await aetheria.Current - .DockingBay - .LatestAsync(); - -using var inventorySubscription = aetheria.Current - .Inventory - .Watch(inventory => RenderInventory(inventory)); - -await aetheria.Entity(actorEntityId) - .Pilot - .MoveAsync(CultMesh.Vec2(input.X, input.Y)); - -var objects = await aetheria.Zone(zoneId) - .Objects - .VisibleTo(controlledEntityIds) - .Within(rect) - .LatestAsync(); - -using var renderView = await aetheria.Zone(zoneId) - .RenderView - .AsNativeArraysAsync(); -``` - -TypeScript clients should have the same semantic shape: - -```ts -const aetheria = verse.aetheria(); - -const dockingBay = await aetheria.current() - .dockingBay() - .latest(); - -const stopInventory = aetheria.current() - .inventory() - .watch(renderInventory); - -await aetheria.entity(actorEntityId) - .pilot.move(CultMesh.vec2(input.x, input.y)); - -const objects = await aetheria.zone(zoneId) - .objects.visibleTo(controlledEntityIds) - .within(rect) - .latest(); - -const gravity = await aetheria.zone(zoneId) - .gravity.influences() - .intersecting(rect) - .latest(); -``` - -Eve/CultUI surfaces should carry typed state pointers and operation handles, not stringly command documents: - -```ts -surface.panel("Selection", { - entity: verse.aetheria().selection().entity().pointer(), - stats: verse.aetheria().selection().entity().stats().pointer(), - actions: verse.aetheria().selection().entity().operations(), -}); -``` - -Ymir should be reachable through the same typed query surface pattern: - -```ts -const hits = await verse.ymir() - .world(worldId) - .overlapSphere({ center, radius }) - .layers("gameplay") - .all(); -``` - -Authority should be claimed through typed operation context sugar, with CultMesh owning the generic primitive and Aetheria owning the policy: - -```csharp -await aetheria.Entity(actorEntityId) - .Pilot - .WithAuthority("pilot-control", shardId: $"entity:{actorEntityId}") - .MoveAsync(CultMesh.Vec2(input.X, input.Y)); -``` - -## Current Baseline - -The project already has the first layer of primitive sugar: - -- `CultMeshOperationHandle`, `CultMeshOperationBindingDescriptor`, `CultMeshOperationBindingRecord`, `CultMeshOperationInvocationDescriptor`, `CultMeshOperationInvocationRecord`, and `CultMeshOperationPayload` exist in CultLib. -- `CultMeshStatePointer`, state binding descriptors/records, route hints, authority claims, query surfaces, viewport requests, native slice descriptors, and context builders exist in C# and TypeScript. -- CultMath/CultGeometry now has vector and rect primitives, including XY viewport vocabulary. -- RTS bindings are generated from the Aetheria schema and expose a typed-ish Verse facade. -- Aetheria Eve surfaces persist CultMesh binding/invocation records and keep legacy fields as compatibility mirrors. -- `AetheriaClient`, `AetheriaControl`, and `AetheriaUi` expose ergonomic C# entry points, but the domain facade is still incomplete. - -The remaining problem is that the sugar is not yet the only obvious path. There are still public or semi-public seams where clients can fall back into raw envelopes, record keys, ad hoc frame projection, manual state resolution, locality-specific wiring, and renderer-side facade adaptation. - -The architectural correction is simple and strict: a normal gameplay feature has -one canonical typed document. The daemon may be the authority for that document, -but daemon authority is not a second state model. The shared runtime assembly -defines the document, the daemon mutates/publishes it, and every runtime grabs a -managed handle to that same type for display, input, prediction, or testing. -Projection documents exist only when the state shape is intentionally different: -hidden-information filtering, expensive aggregation, viewport/windowing, -SoA/native layout, lossy UI summaries, Eve/CultUI surfaces, or compatibility -bridges with a removal stage. - -The worst current pattern is not one bad helper. It is a repeated protocol walk: - -1. read one projected document; -2. extract a record key, index, schema-specific row, or state reference; -3. read another projected document or local frame; -4. resolve the key through a renderer-local facade index; -5. adapt the result into a domain object; -6. wire manual refresh or blocking reads around the whole path. - -That entire stack must collapse behind one typed CultMesh document, collection, query, or operation handle. If a client wants current docking, current inventory, current target, current stats, zone contacts, station refit state, or visible render objects, the caller should make one semantic CultMesh call and receive a typed reactive value. CultMesh owns cache hydration, routing, sync, invalidation, watch lifetimes, schema binding, derived-state execution, and locality choice. - -The facade/projector/adapter/surface-builder chain is no longer an acceptable -style. A single adapter at a real boundary can be honest; a chain of translation -objects to recover one domain value is heretek architecture. Replace it with a -canonical document handle, a generated typed query/operation handle, a state -pointer, or a native view descriptor. - -## Migration Rules - -1. Repeated Aetheria-specific glue becomes a generated Aetheria facade. -2. Repeated cross-runtime glue becomes CultMesh/CultLib. -3. Public client APIs must expose typed domain operations, typed query surfaces, typed pointers, or native views. -4. Raw payload dictionaries, command names, schema ids, record keys, slot ids, and transport-local route details are allowed inside generated/runtime internals only. -5. Compatibility mirrors are temporary scaffolding. They must have a named removal stage. -6. Unity is a renderer/input client. It should not own gameplay state, zone hierarchy, simulation cadence, physics, or entity authority by accident. -7. The browser/RTS client and Unity client must use the same semantic Verse facade, even when their locality/runtime routes differ. -8. Client code must not manually compose state access from multiple protocol layers. A semantic state read is one CultMesh call from the caller's point of view. -9. Renderer-local object facades are presentation caches. They are not the API for game state, inventory, docking, targeting, or station services. -10. Do not split "daemon truth" from "CultMesh typed state" for ordinary gameplay. Use one canonical document, then let authority policy decide who can mutate, predict, or only observe it. -11. Add projections only for a distinct derived/filter/window/native/UI shape. "A client needs to read it" is not a projection justification. -12. Do not add facade/projector/adapter/surface-builder chains. One real boundary adapter is allowed; stacked translation layers are a missing CultMesh primitive. - -## Stage 0: Freeze The Vocabulary - -Status: mostly done, keep tightening. - -Goal: make the desired surface explicit enough that every later stage can delete ad hoc code instead of inventing new abstractions in place. - -Targets: - -- Keep `Aetheria.State/docs/cultmesh-cross-runtime-primitives.md` as the primitive ownership boundary. -- Keep `Aetheria.State/docs/daemon-code-map-for-rust-port.md` as the current daemon/control-flow map. -- Treat this document as the staged migration checklist. -- Define canonical names for: - - document handles - - operation handles - - operation invocation records - - query surfaces - - state pointers - - native slice views - - authority claims - - route hints - - geometry values - -Gates: - -- No new public API may introduce `Apply(command, payload)` style call sites. -- No new client API may require a caller to know transport record keys or raw schema slots. -- No new client API may require a caller to join projected documents, frame rows, record keys, and renderer-local facades to obtain one domain value. -- No new feature guide, example, or verifier rule may describe separate daemon - truth and client typed state when one canonical document would work. -- No verifier may require a facade/projector/adapter/surface-builder chain as - proof of correctness. It should require the semantic typed handle and reject - the translation chain. -- All new examples in docs must use the North Star shape. - -## Stage 1: Harden Shared CultMesh Primitives - -Status: started. - -Goal: make CultMesh primitives boring, portable, serializable, and pleasant enough that Aetheria does not need local substitutes. - -Targets: - -- C# primitives in `E:/Projects/CultLib/src/GameCult.Mesh/CultMeshPrimitives.cs`. -- Geometry primitives in `E:/Projects/CultLib/src/GameCult.Geometry/CultGeometryPrimitives.cs`. -- TS primitives in `E:/Projects/Aetheria/packages/cultmesh-ts/src/index.ts`. -- MessagePack/JSON-compatible record forms for operation/state/query/native/authority values. -- Typed payload readers and builders so domain facades do not pass `Dictionary` in public. -- Stable route and authority record forms that can cross process/runtime boundaries. - -Work: - -- Add typed scalar/vector/list readers to operation payloads. -- Add generated or handwritten conversion helpers between descriptor objects and record objects. -- Add invariant tests for C#/TS round trips. -- Keep primitive names runtime-neutral. Aetheria, Brokkr, Eve, Odin, VoidBot, and Ymir should all be able to use the same vocabulary. - -Gates: - -```powershell -dotnet test E:\Projects\CultLib\tests\GameCult.Mesh.Tests\GameCult.Mesh.Tests.csproj --no-restore -v:minimal -npm test --workspace packages/cultmesh-ts -``` - -## Stage 2: Generate Domain Facades - -Status: started in RTS bindings; incomplete across C#/Unity/Eve. - -Goal: make the typed facade the public shape in every runtime. - -Targets: - -- C# facade: `verse.Aetheria().Entity(id).Pilot.MoveAsync(...)`. -- TS facade: `verse.aetheria().entity(id).pilot.move(...)`. -- Eve/CultUI bindings: surface actions are operation handles, not command strings. -- Unity facade: Unity gameplay/input shell calls typed operations and typed queries only. - -Work: - -- Promote the current RTS binding generator into a general Aetheria CultMesh facade generator. -- Generate C# and TS operation handles from the same schema. -- Generate query builders for objects, gravity, inventory, selection, stats, action availability, and map/RTS visibility. -- Generate state pointer builders for selected object, selected inventory, selected stats, UI panels, and live frame metadata. -- Add a verifier that fails if public client code exposes command-name/payload pairs. - -Desired C# shape: - -```csharp -await client.Aetheria() - .Entity(actorEntityId) - .Equipment(slot) - .ActivateAsync(target); -``` - -Desired TS shape: - -```ts -await client.aetheria() - .entity(actorEntityId) - .equipment(slot) - .activate(target); -``` - -Gates: - -```powershell -dotnet run --project E:\Projects\Aetheria\Aetheria.State.Verify\Aetheria.State.Verify.csproj --no-restore -npm run generate:rts-bindings -npm run check:rts-bindings -``` - -## Stage 3: Make Query Surfaces The Client Contract - -Status: partially started; local projection still leaks. - -Goal: clients ask CultMesh for typed query surfaces or projected documents when -the requested shape is derived, filtered, windowed, or native-layout-specific. -They do not reconstruct world truth from frames unless they are inside a -generated/internal query executor, and they do not add projections for canonical -state that a typed document handle can expose directly. - -Targets: - -- Objects viewport. -- Gravity viewport. -- Visible set for RTS: union of entities visible to controlled units. -- Selected object. -- Current stats. -- Inventory. -- Equipment/action availability. -- Station/service status. -- Ymir overlap/cast queries. - -Work: - -- Move remaining browser-local projection paths behind generated query executors or daemon query endpoints. -- Make viewport queries use `CultRect`/XY everywhere outside Unity adapter boundaries. -- Give each query a stable typed request and typed response. -- Add watch/latest semantics consistently: - - `latest()` for one-shot reads. - - `watch()` for reactive subscriptions. - - `asNativeArrays()` where co-located slab views are available. - -Gates: - -- RTS client rendering uses query surfaces, not frame spelunking. -- Unity rendering asks for render/query views, not a mirrored zone hierarchy. -- `ZoneRenderer.LoadZone` and ActionGameManager-owned zone state become vestigial or disappear. -- Public client code has no direct dependency on current daemon frame layout except through generated/internal projection code. - -## Stage 3A: Collapse State Access To Typed Reactive Handles - -Status: started; `AetheriaRuntimeVerseClient.Aetheria()` now exposes the first -C# domain facade for current, station, and zone projected documents, -`AetheriaClient` delegates to that shared facade, and the broad Unity -menu/HUD/map/render projected-state reads now use typed state handles instead -of transitional `Current*Async()`, `StationRefitAsync()`, `SectorMapAsync()`, -`ZoneContactsAsync()`, or `ZoneRenderAsync()` helpers. `AetheriaClientState` -also indexes projected state documents by shared document type so callers can -retrieve or watch a known typed document with one call while CultMesh owns the -bound live feed. The facade also indexes those handles by `CultDocument` -schema name/version and can adapt a handle to a different local CLR document -type with the same schema identity, so caller code can start treating shared -schema identity as the contract even before CultMesh owns alias conversion -directly. - -Goal: every client-facing state access becomes a single CultMesh document, collection, query, or pointer call. The caller names the domain state it wants and gets a typed reactive value. It does not manually inspect daemon frames, current-state projections, station rows, record keys, local facade indexes, or transport route details. - -Targets: - -- Current entity. -- Current docking. -- Current docking bay. -- Current inventory. -- Current target and target details. -- Current ship settings. -- Station refit state. -- Trade cargo targets. -- Zone contacts. -- Selected object. -- Runtime catalog collections. -- Player settings and input bindings. - -Desired C# shape: - -```csharp -var current = client.Aetheria().Current; - -using var docking = current.Docking.Watch(RenderDocking); -using var bay = current.DockingBay.Watch(RenderDockingBay); -using var inventory = current.Inventory.Watch(RenderInventory); - -var refit = await client.Aetheria() - .Station - .Refit - .LatestAsync(); - -var contacts = await client.Aetheria() - .LatestAsync(); - -var contactsHandle = client.Aetheria() - .DocumentBySchema(AetheriaRuntimeDaemonSchemas.ZoneContacts); - -using var render = client - .Watch(RenderZone); -``` - -Desired TS shape: - -```ts -const current = client.aetheria().current(); - -const stopDocking = current.docking().watch(renderDocking); -const stopBay = current.dockingBay().watch(renderDockingBay); -const stopInventory = current.inventory().watch(renderInventory); - -const refit = await client.aetheria() - .station() - .refit() - .latest(); -``` - -Work: - -- Keep pushing shared CultMesh typed reactive document and collection handles down. Typed document handles and schema-compatible CLR alias lookup now live in CultMesh through `CultMeshDocumentHandle` and `CultMeshDocumentCatalog`; remaining work should remove client-side joins and generated facade gaps rather than rebuilding schema dictionaries in Aetheria. -- Generate Aetheria current/station/zone/catalog accessors from schema metadata. -- Move derived-state joins, such as current docking bay from current docking plus station refit plus entity records, into generated/internal query executors. -- Replace remaining Unity menu/HUD/render calls to `TryResolve*` and `_observedFacadeIndex` with typed handles or generated/internal render adapters. -- Replace blocking `GetAwaiter().GetResult()` reads in client presentation code with watch/latest handles that own lifetimes. -- Keep renderer-local facade indexes only inside render adapter internals while native/query views are still being migrated. -- Add verifiers that treat multi-hop state reads in client code as failures. - -Gates: - -- `InventoryMenu`, `InventoryPanel`, `TradeMenu`, `LocalMenu`, `SchematicDisplay`, `SectorRenderer`, `MapRenderer`, and `ZoneRenderer` do not manually join state projections to obtain domain values. -- Shared projected documents are reachable through `client.Aetheria().Document()`, including same-schema CLR aliases, `client.Aetheria().DocumentBySchema(schemaVersion)`, `client.Aetheria().LatestAsync()`, and `client.Aetheria().Watch()`; the underlying type/schema lookup is delegated to CultMesh rather than an Aetheria-owned schema registry. -- Client-facing code contains no `_observedFacadeIndex.TryResolve*` outside Unity render adapter internals. -- Client-facing code contains no `ResolveClient().Current*Async().GetAwaiter().GetResult()` or equivalent blocking state reads. -- `TryGetTypedCurrentDockingBayFacade` and similar transitional helpers are deleted rather than renamed. -- Verifiers assert the semantic API shape, not the transitional facade-adaptation scaffolding. - -## Stage 4: Resolve Eve State Pointers Automatically - -Status: primitive records exist; runtime resolution is not yet the whole story. - -Goal: an Eve surface can contain a typed pointer into daemon state and the UI runtime resolves it, watches it, and binds it without custom per-panel plumbing. - -Targets: - -- Eve surface documents carry `CultMeshStateBindingRecord`. -- Operation buttons carry `CultMeshOperationBindingRecord`. -- User actions emit `CultMeshOperationInvocationRecord`. -- CultUI runtime resolves pointers via the current Verse. -- Unity UIToolkit lowerer consumes the same surface semantics as browser/Electron. - -Work: - -- Add a pointer resolver interface to CultMesh/CultUI rather than Aetheria-specific code. -- Add watch lifetime management to UI runtime. -- Add loading/error/stale states as generic pointer states. -- Remove manual state-ref resolver glue from Aetheria UI code once the generic runtime path exists. - -Gates: - -- Selection panel can bind selected entity/stats/inventory from pointers only. -- No Aetheria panel needs to manually decode a state reference. -- Surface command persistence contains CultMesh invocation records as the authoritative form. - -## Stage 5: Native View Sugar For Unity And Co-Located Clients - -Status: designed; implementation still thin. - -Goal: Unity becomes a thin, brutally fast renderer/input client over Aetheria/Ymir state. - -Targets: - -- `AsNativeArraysAsync()` over CultCache slabs. -- Unity-safe lifetime wrappers around shared memory/native slice descriptors. -- Burst-readable views for render columns and physics state. -- Dirty ranges/version stamps so clients can skip unchanged slabs. -- Render-oriented projections generated by Aetheria, not maintained as a Unity hierarchy. - -Work: - -- Define native slice descriptors for Aetheria render views and Ymir physics views. -- Wrap descriptors in Unity `NativeArray`/`NativeSlice` adapters with explicit lifetime ownership. -- Add schema/version validation before exposing a slab as a native view. -- Move camera/frustum queries to daemon query surfaces. -- Keep Unity-specific transform/material/sprite concerns in Unity, but pull entity membership, object location, gravity influence, physics state, inventory, and stats from Aetheria/Ymir. - -Desired Unity shape: - -```csharp -using var view = await verse.Aetheria() - .Zone(zoneId) - .RenderView - .ForCamera(cameraQuery) - .AsNativeArraysAsync(); - -RenderJobs.Schedule(view.Objects, view.Sprites, view.Gravity); -``` - -Gates: - -```powershell -dotnet build E:\Projects\Aetheria\GameCult.Aetheria.State.Unity.csproj --no-restore -v:minimal -dotnet build E:\Projects\Aetheria\Aetheria.State.Unity.Smoke\Aetheria.State.Unity.Smoke.csproj --no-restore -v:minimal -``` - -## Stage 6: Authority Sugar And Locality Policies - -Status: primitive claims exist; gameplay policy needs first-class wiring. - -Goal: authority is explicit and configurable without leaking into every gameplay call. - -Targets: - -- Server-authoritative mode. -- Trusted co-op distributed simulation mode. -- Future quorum/re-sim mode, without blocking MVP. -- Per-shard/per-entity authority claims. -- Route hints chosen by Verse/runtime policy. - -Work: - -- Add Aetheria authority policy config as data, not hardcoded branch logic. -- Add facade sugar: - - `WithAuthority(...)` - - `PreferLocality(...)` - - `AsNode(...)` - - `WithIdempotency(...)` -- Attach operation context automatically where a facade knows the actor/entity/shard. -- Add diagnostics that explain why an operation routed to local/shared-memory/network/host. -- Keep CultMesh generic: it owns claims, leases, routes, receipts, and diagnostics; Aetheria owns which operations require which claims. - -Gates: - -- Unity player can own simulation authority for its own pilot state in trusted co-op config. -- RTS player can own hostile pawns or commanded pawns in trusted co-op config. -- Server-authoritative config can force daemon/host-only simulation authority. -- No public gameplay API requires manually constructing a command port. - -## Stage 7: Ymir Query Facade And Physics Ownership - -Status: Ymir owns more physics directionally, but query sugar and benchmark discipline need to catch up. - -Goal: physics is a typed service with the same CultMesh ergonomics as Aetheria state. - -Targets: - -- `OverlapSphere`. -- Brush/viewport intersection for gravity influence queries. -- Collision/cast queries needed by abilities and movement. -- Gravity terrain influence queries. -- Physics benchmark suite with deterministic sparse clustered scenes. - -Work: - -- Add Ymir typed query handles using CultMesh query surface primitives. -- Add C# and TS facade generation for Ymir. -- Add Aetheria-to-Ymir state references rather than duplicating physics ownership in Unity. -- Add benchmarks that compare vector SoA, scalar SoA, and spatial acceleration structures. -- Keep Unity physics out of gameplay state. Unity may render/debug physics but not authoritatively simulate it. - -Gates: - -- Gameplay queries use Ymir facade operations. -- Gravity viewport queries can ask for every body whose influence brush intersects an XY viewport. -- Benchmarks are deterministic and run in CI or a documented local perf lane. - -## Stage 8: One Schema Pipeline - -Status: pieces exist; still too hand-maintained. - -Goal: one schema source generates cross-runtime facades, record serializers, pointer/query/operation metadata, and docs. - -Targets: - -- C# facade generation. -- TS facade generation. -- Eve/CultUI binding metadata. -- Unity native view adapters. -- Query request/response records. -- Operation payload records. -- Schema evolution manifests. -- Legacy readers only where explicitly needed. - -Work: - -- Replace one-off RTS binding generation with a CultMesh schema generator. -- Generate operation payload types instead of exposing generic payload maps. -- Generate query result types and pointer target types. -- Generate docs/snippets from schema metadata so examples cannot rot. -- Add schema version compatibility checks to native view and network decode paths. - -Gates: - -- Adding a new Aetheria operation once creates C#, TS, Eve operation binding, verifier metadata, and docs. -- Adding a new query once creates C#, TS, Eve pointer/query metadata, and query executor stubs. -- No runtime has a hand-authored duplicate of another runtime's binding vocabulary. - -## Stage 9: Delete Compatibility Shells - -Status: not started. - -Goal: remove scaffolding once the generated typed path is the only supported path. - -Remove: - -- Public command-name/payload APIs. -- Cached command port concepts. -- Public raw frame spelunking for client projections. -- Public record-key/schema-slot access in client code. -- Manual state-ref resolution in UI panels. -- Legacy XZ vocabulary outside Unity adapter names. -- Unity-owned zone hierarchy/gameplay simulation state. -- Browser-local bespoke gameplay behavior. - -Keep only as internals where required: - -- Wire-level records. -- Legacy readers for persisted documents that predate the migration. -- Diagnostic tools that intentionally expose raw records. -- Tests that assert legacy decode behavior until the migration window closes. - -Gates: - -- Verifier blocks new public stringly operation surfaces. -- `rg` sweeps show no client-facing `Apply(command`, `commandName`, `payload`, `recordKey`, or raw slot access outside generated/runtime internals. -- Unity, RTS/Electron, and Eve all consume the same semantic facade. - -## Stage 10: MVP Acceptance - -The migration is MVP-complete when: - -- Aetheria daemon owns gameplay simulation state. -- Aetheria owns committed physics state and performs gameplay physics queries - and integration through Ymir. -- Unity is rendering/input focused. -- RTS/Electron is a thin CultMesh client, not a parallel gameplay implementation. -- Eve/CultUI surfaces use typed state pointers and typed operation handles. -- Aetheria exposes current stats, inventory, equipment, selection, objects viewport, gravity viewport, and authority-aware operations as typed surfaces. -- Unity and browser clients can point at the same Verse and observe/interact with the same simulation according to authority policy. -- Hermodr can reconstruct the RTS gameplay surface as an unspecialized Eve - lowering from daemon API, Eve/CultUI, render-field declarations, and CultMesh - CDN assets; Electron may be the shipped RTS shell but cannot own private - gameplay interpretation. -- Godot can become the ARPG equivalent conformance target by lowering the - daemon-authored ARPG Eve/CultUI contract, not by inheriting Unity scene - authority or triggering daemon-side runtime branches. -- Co-located clients can use native slab views where available. -- Remote clients use the same facade shape over network routes. -- Locality is visible in diagnostics, not application logic. - -## Non-Goals For This MVP - -- Rebuilding the daemon in Rust. -- Making the browser host the full simulation in WASM. -- Quorum/re-sim consensus implementation. -- Perfect zero-copy support across every runtime. -- Exact parity with every historical Unity behavior. - -These are future expansion paths. The migration must not close the door on them, but it should not block the MVP on them. - -## Verification Lane - -Use this lane when changing the sugar surface: - -```powershell -dotnet test E:\Projects\CultLib\tests\GameCult.Mesh.Tests\GameCult.Mesh.Tests.csproj --no-restore -v:minimal -dotnet run --project E:\Projects\Aetheria\Aetheria.State.Verify\Aetheria.State.Verify.csproj --no-restore -dotnet build E:\Projects\Aetheria\GameCult.Aetheria.State.Unity.csproj --no-restore -v:minimal -dotnet build E:\Projects\Aetheria\Aetheria.State.Unity.Smoke\Aetheria.State.Unity.Smoke.csproj --no-restore -v:minimal -npm run generate:rts-bindings -npm run check:rts-bindings -npm run build -``` - -Add Ymir benchmark and authority smoke commands to this lane as those stages land. - -## Sugar Targets Checklist - -- [ ] Typed Aetheria C# domain facade is complete. -- [ ] Typed Aetheria TS domain facade is complete. -- [ ] Typed Ymir C# and TS query facades exist. -- [ ] Eve/CultUI resolves CultMesh state pointers automatically. -- [ ] Eve/CultUI invokes operation records without Aetheria-specific command glue. -- [ ] Query surfaces cover objects viewport, gravity viewport, selection, stats, inventory, equipment, and services. -- [ ] Unity render path consumes native views or query results, not gameplay-owned mirrored state. -- [ ] RTS render path consumes query surfaces, not bespoke frame projection as public client behavior. -- [ ] Authority policy is data-driven per Verse. -- [ ] Operation context sugar attaches route, claim, and idempotency without caller boilerplate. -- [ ] Schema generator emits C#, TS, Eve metadata, query records, operation payload records, and docs. -- [ ] Public APIs do not expose stringly command names or raw payload dictionaries. -- [ ] Compatibility mirrors are internal, named, and scheduled for deletion. diff --git a/Aetheria.State/docs/daemon-code-map-for-rust-port.md b/Aetheria.State/docs/daemon-code-map-for-rust-port.md deleted file mode 100644 index cf5af95e1..000000000 --- a/Aetheria.State/docs/daemon-code-map-for-rust-port.md +++ /dev/null @@ -1,570 +0,0 @@ -# Aetheria Daemon Code Map for Rust Rebuild - -This is a map of the current C# daemon as a reference API to document, measure, and deprecate before the rebuild. This is not an Agile migration plan and not a request to preserve the current architecture. The C# code is the shakedown cruise: it taught us where Unity, TS, browser, Eve/CultUI, Rust, and native clients need clean CultMesh ergonomics, where the daemon leaked local-process assumptions, and where the API wants to become magical instead of merely serviceable. - -The rebuild target is a clean cross-runtime Aetheria surface: a Rust-native daemon and Ymir body, with useful abstractions hoisted into CultMesh/CultLib so typed state, operations, queries, native slices, authority, and UI state pointers are shared library primitives. Clients should feel like they are holding reactive, native Aetheria state, not hand-pulling snapshots, decoding array slots, reading local publication files, or building bespoke command packets. - -This also means Ymir needs a Rust body. Aetheria cannot become a Rust-native simulation daemon while spatial computation remains a C# or Unity-shaped side service. The C# Ymir contracts and Unity bridge are useful reference material, but the rebuilt daemon needs an embedded Rust Ymir kernel behind its injected physics ports. - -## Rebuild Doctrine - -1. Document the current C# daemon API and client behavior. -2. Measure it as the deprecated compatibility reference. -3. Build the daemon and client semantics in the shape we actually want, then hoist reusable abstractions into CultMesh/CultLib instead of trapping them in Aetheria glue. -4. Let Unity, TS, browser, native, Eve/CultUI, Rust, and future runtimes consume the same typed Verse semantics. -5. Use this rebuild to make CultMesh expansive: cozy managed developer experience on top, very fast cross-runtime primitives underneath. - -Compatibility means the new cross-runtime surface can understand enough of the old contract to migrate clients and compare intentional behavior. It does not mean the new architecture inherits old seams, and it does not mean client APIs keep their current awkward shape. - -The test for a good abstraction is whether it belongs to Aetheria or to CultMesh. If a pattern is useful to any daemon with typed state, reactive UI surfaces, native view sharing, authority routing, or colocated query execution, it should move up into the shared cross-runtime library layer. - -The concrete shared-layer target is tracked in `Aetheria.State/docs/cultmesh-cross-runtime-primitives.md`. Treat that note as the CultMesh/CultLib primitive checklist for this rebuild: Aetheria proves the shape, but the reusable machinery belongs in the cross-runtime library layer. - -## Current C# Shape - -```mermaid -flowchart TD - Host["Program.cs\nprocess host, CLI, tick loop, seeding"] - Node["AetheriaStateNode\nCultMesh/CultCache/CultNet wrapper"] - Commands["Daemon command docs\nEve command docs"] - Authority["Authority router\npolicy + leases"] - Tick["Tick runner"] - Ops["Operations executor\nmutates run checkpoint"] - Sim["Daemon sim\nmovement, AI, combat, contacts"] - Frame["Frame document\nrun snapshot + facts"] - Projection["Game viewport documents\nviewport, gravity, inventory"] - Publications["Publications\nCultNet records, .cc files, RUDP snapshots, Eve surfaces"] - Unity["Unity client\nobserver + SoA mmap"] - TS["Electron/TS client\nCultMesh send, local reads"] - - Host --> Node - Node --> Commands - Commands --> Authority - Authority --> Tick - Tick --> Ops - Ops --> Sim - Sim --> Frame - Frame --> Projection - Frame --> Publications - Projection --> Publications - Publications --> Unity - Publications --> TS -``` - -The current daemon is one C# executable around a CultMesh node. It ensures seed documents, reads typed command documents, authorizes them, applies operations, steps daemon-owned Aetheria simulation, publishes a new frame, and emits several secondary publications for clients. - -That shape proved the core idea, but it also leaked implementation details into every edge: - -- Unity observes daemon state through a C# facade and memory-mapped SoA view. -- Electron sends CultMesh commands and reads daemon-authored managed documents - through the CultMesh document catalog. -- TS decodes the daemon-authored document slot layout, but must not duplicate - projection authority or rebuild view/state from raw daemon frames. -- RUDP snapshot handling injects ad hoc viewport projection records. -- Eve/CultUI surface generation is mixed into daemon publication. -- The host loop seeds scenario content and durable docs directly. -- The API is typed in places, but not magical enough: clients still know too much about transport, record keys, snapshots, and projection plumbing. - -## Measured C# API Surface - -These files define the deprecated reference surface. They should be treated as API documentation and shakedown evidence first, not as design inspiration or strict parity law. - -| Area | Source | What to freeze | -| --- | --- | --- | -| Host behavior | `Aetheria.State.Daemon/Program.cs` | CLI options, startup behavior, seed behavior, tick cadence, RUDP endpoint behavior, and the obsolete remote committed-fact import behavior that must become Pilot candidate selection plus Commander finality. | -| CultMesh node wrapper | `Aetheria.State/AetheriaStateNode.cs` | Current record reads/writes, observed command query behavior, flush semantics. | -| Registry | `Aetheria.State/AetheriaDocumentRegistry.cs` | C# document set currently exposed through CultCache/CultNet. | -| Replica sync | `Aetheria.State/AetheriaVerseReplica.cs` | Scoped snapshot/document fetch to preserve; remote committed-fact import is migration evidence, not target authority. | -| Local publications | `Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeCultCacheDocumentStore.cs` | `.cc` compatibility files and their payload layout. | -| Daemon documents | `Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeDaemonDocuments.cs` | Schema ids, command enum, frame, command, committed fact, viewport, health, provider, command boundary, surface docs. | -| Snapshot documents | `Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeSnapshotDocuments.cs` | Run, zone, entity, body, equipment, cargo, loadout, stat grids. | -| Authority documents | `Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeVerseAuthorityPolicy.cs` | Policy, rules, leases, modes, roles, deployment vocabulary, current authorization decisions. | -| SoA documents | `Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeDaemonSoaDocuments.cs` | SoA backends, sync modes, column ids, dirty ranges, render groups. | -| Command application | `Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeDaemonOperations.cs` | Current meaning of every gameplay command and rejection. | -| Tick composition | `Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeDaemonTickRunner.cs` | Current order of command filtering, operation execution, sim step, frame creation, publication payloads. | -| Daemon sim | `Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeDaemonSimulation.cs` | Gameplay intents, hostile AI, combat meaning, heat, and projection of mandatory Ymir world results. | -| Ymir world adapter | `Aetheria.State.Daemon/AetheriaYmirWorldPhysics.cs` | Projects daemon state into Ymir. Positive daemon gravity-depth magnitudes become positive attracting radial strengths; the adapter returns body, contact, position, and velocity facts for the daemon to commit without owning sign policy. | -| Game viewport documents | `Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeGameViewportDocuments.cs` | Existing map, object, gravity, selected object, docking, refit, sector, and inventory projections. | -| Client facade | `Packages/org.gamecult.aetheria.state/Runtime/AetheriaClient.cs` | C# client observation and typed operation ergonomics. | -| Verse client | `Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeVerseClient.cs` | Current lower-level typed document reads/watches and command submission. | -| Operation builders | `Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeDaemonOperationClient.cs` and `AetheriaRuntimeDaemonOperationsClient.cs` | Existing typed operation vocabulary. | -| Eve bridge | `Aetheria.State/AetheriaEveCommandBridge.cs` | Existing Eve command acceptance behavior for settings, catalog, policies, and surfaces. | -| Unity consumption | `Assets/Scripts/Gameplay/AetheriaDaemonObserver.cs` | Current Unity daemon observation, SoA remapping, and render native view handoff. | -| TS consumption | `Aetheria.Rts.Web/Electron/aetheria-cultmesh.ts`, `Aetheria.Rts.Web/Electron/aetheria-rts-local-documents.ts` | Current Electron command sending, CultMesh document catalog/query reads, and typed daemon document decoding. | -| Ymir C# contracts | `Assets/Scripts/ServerShared/YmirPhysicsContracts.cs` | Current body/world/query DTOs plus reference implementation for step, overlap, and cast queries. | -| Ymir Unity bridge | `Assets/Scripts/Gameplay/Physics/AetheriaYmirPhysicsBridge.cs` | Current Unity presentation adapter that maps daemon SoA bodies into typed Ymir query worlds. | -| Ymir query tests | `Assets/Scripts/Tests/YmirPhysicsQueryTests.cs` | Current expectations for integration, radial fields, contacts, overlap sphere/circle, and cast sphere/circle; useful semantics should survive, DTO/endpoint shape should not. | - -## Combat Ownership - -Combat has one live writer in `AetheriaRuntimeDaemonSimulation`. Projectile -movement and contact are delegated through `IAetheriaRuntimeProjectilePhysics` -to Ymir. The abandoned abstract pressure-model kernel was deleted rather than -ported; it had no production caller and would have created a second authority -over contacts, damage, heat, and weapon cooldowns. - -## Current Control Flow - -The C# host does this today: - -1. Parse `AetheriaDaemonHostOptions`. -2. Open `AetheriaStateNode` with a CultMesh node, CultCache registry, and CultNet registry. -3. Ensure baseline world, trade policy, playable run, Starbridge scenario/session, verse host settings, and authority policy. -4. Publish runtime session and Eve surfaces. -5. Start a loopback CultNet RUDP server for RTS clients. -6. Tick immediately, then tick by interval: - - Accept Eve commands. - - Choose next frame id and simulation time. - - Resolve current run from previous frame or durable documents. - - Read loadout templates, daemon commands, authority policy, Starbridge state, and leases. - - Filter commands through authority rules. - - Execute daemon operations. - - Step the daemon-owned Aetheria simulation. - - Build a daemon frame. - - Publish committed command facts. - - Import remote committed command facts (current behavior to delete or - quarantine; target Pilot output enters before finality as candidate - evidence and Commander correction/replay selects one canonical fact). - - Publish frame, health, command boundary, provider docs, Starbridge summary, SoA view, and Eve surfaces. - -This is behavior to measure and learn from. It is not the desired architecture. - -## Current Public Data Contract - -The Rust daemon must be able to read or bridge these old records during deprecation: - -| Record | Current key | -| --- | --- | -| Provider advertisement | `daemon:aetheria.provider_advertisement.v1` | -| Health | `daemon:aetheria.health.v1` | -| Command boundary | `daemon:aetheria.command_boundary.v1` | -| Latest frame | `daemon:aetheria.frame.latest.v1` | -| Latest SoA view | `daemon:aetheria.soa_view.latest.v1` | -| Starbridge scenario | `starbridge:aetheria.scenario.latest.v1` | -| Starbridge session | `starbridge:aetheria.session.latest.v1` | -| Game Eve surface | `eve:surface:aetheria.daemon.game` | -| Game Eve TUI surface | `eve:surface:aetheria.daemon.game.tui` | -| Editor Eve surface | `eve:surface:aetheria.daemon.editor` | -| Editor Eve TUI surface | `eve:surface:aetheria.daemon.editor.tui` | -| Daemon command | `daemon:commands:{stable-command-id}:gamecult.aetheria.daemon_command.v1` | -| Eve command | `eve:commands:{stable-command-id}:gamecult.eve.command.v1` | - -Current schema roots: - -| Contract | Source | -| --- | --- | -| Daemon command/frame/fact/projection/provider/health/boundary/surface docs | `AetheriaRuntimeDaemonDocuments.cs` | -| Runtime run/zone/entity/body/equipment/cargo/loadout docs | `AetheriaRuntimeSnapshotDocuments.cs` | -| SoA view docs and columns | `AetheriaRuntimeDaemonSoaDocuments.cs` | -| Authority policies and leases | `AetheriaRuntimeVerseAuthorityPolicy.cs` | -| Eve command docs | `AetheriaRuntimeEveCommandDocument.cs` | - -## Current Gameplay Command Vocabulary - -These are the gameplay operations exposed by the C# command document. The Rust rebuild should re-express them as typed native operations, not as public stringly transport calls. - -| Area | Commands | -| --- | --- | -| Targeting | `SetTarget`, `ClearTarget`, `TargetNearest`, `TargetNext`, `TargetPrevious`, `TargetReticle` | -| Pilot control | `SetMoveVector`, `SetLookDirection`, `SetTractorPower` | -| Combat and equipment | `FireWeaponGroup`, `SetWeaponGroupActive`, `SetWeaponGroupMembership`, `SetBehaviorActive`, `ActivateConsumable`, `SensorPing`, `SetHeatsinksEnabled`, `SetOverrideShutdown`, `SetShutdownPerformance`, `SetItemEnabled`, `ToggleShieldEnabled`, `SetItemOverrideShutdown`, `SetThermotoggleTargetTemperature` | -| Inventory, refit, economy | `TransferCargoItem`, `EquipItem`, `StoreItem`, `PickUpLoot`, `TradePurchase`, `RestoreLoadout`, `SetDockedCurrentShip` | -| Interaction and travel | `Dock`, `DockNearest`, `Undock`, `Interact`, `EnterWormhole`, `TowToStation` | -| Metadata and destruction | `SetEntityName`, `DestroyEntity`, `ToggleHullConductivity` | - -## Rust Daemon Perfect Shape - -The Rust daemon should be designed around typed state, typed operations, reactive subscriptions, and a Rust-native Ymir physics body. - -```mermaid -flowchart TD - Verse["CultMesh Verse\nschema, authority, replication"] - State["Aetheria State\nWorld, zones, entities, inventory, stats"] - Ops["Typed Operations\npilot, RTS, inventory, economy, editor"] - Ymir["Ymir Rust\nphysics body, queries, spatial acceleration"] - Sim["Aetheria Simulation\ncombat, heat, AI, sensors"] - Queries["Typed Queries\nviewports, gravity, selection, inventory, stats"] - Views["Native Views\nSoA slabs, WASM views, render views"] - Surfaces["Eve/CultUI Surfaces\nstate pointers + typed commands"] - Unity["Unity renderer/input shell"] - Browser["Browser/RTS client"] - Native["Native tools and daemons"] - - Verse --> State - Ops --> State - State --> Sim - Sim --> Ymir - Ymir --> Sim - Sim --> State - State --> Queries - Queries --> Views - State --> Surfaces - Views --> Unity - Views --> Browser - Views --> Native - Surfaces --> Unity - Surfaces --> Browser - Surfaces --> Native -``` - -Desired properties: - -- A client can ask for typed state or a typed projection without knowing record-key plumbing. -- A client can submit a typed operation without constructing transport payloads. -- A UI surface can point at daemon state and let the CultUI runtime resolve it automatically. -- Unity receives native render views and typed state handles, not gameplay ownership. -- Browser clients can run as observers, controllers, or simulation hosts depending on Verse authority configuration. -- The daemon owns simulation and projections as state-native behavior, not as publication side effects. -- The Aetheria simulation owns committed physical truth and performs stepping, overlaps, casts, contacts, broadphase, and spatial-field computation through Rust Ymir. -- CultMesh authority is explicit per Verse, with server-authoritative, trusted distributed, lease, quorum, and browser/WASM host structures remaining open. - -## Ymir Rust Body - -Ymir should be rebuilt as a Rust-native physics foundation used by Aetheria, Unity, browser/WASM, and future runtimes. Its current C# implementation is behavior evidence, not the long-term body and not strict parity law. - -Current Ymir shape: - -| Area | Current source | Notes | -| --- | --- | --- | -| Contracts and reference implementation | `Assets/Scripts/ServerShared/YmirPhysicsContracts.cs` | Defines `YmirWorld`, `YmirPhysicsBody`, radial fields, contacts, 2D circle queries, 3D sphere queries, and reference step/query math. | -| Unity adapter | `Assets/Scripts/Gameplay/Physics/AetheriaYmirPhysicsBridge.cs` | Converts Unity/daemon render views into Ymir request DTOs and posts JSON to local query endpoints. This is presentation glue, not future architecture. | -| Tests | `Assets/Scripts/Tests/YmirPhysicsQueryTests.cs` | Current checks for stepping, radial fields, overlap/cast ordering, contact output, and invalid input handling. | - -The initial freeze exporter now emits `ymir-queries` probes from the current C# implementation: step integration, radial field acceleration, contact separation, overlap circle/sphere sorting, cast circle/sphere hits, and invalid-input behavior. These are typed measurement probes with explicit vector, world, body, field, hit, and contact records in JSON plus MessagePack. The next version should expand them with CultMath-shaped rect, circle, sphere, broadphase, sparse-cluster, and viewport-intersection fixtures. - -Rust Ymir should provide: - -- canonical `Vec2`, `Vec3`, `Rect`, `Circle`, `Sphere`, and body handles from CultMath-compatible primitives -- deterministic step for Aetheria body motion -- contacts and collision response for the calling simulation transaction -- `overlap_circle`, `cast_circle`, `overlap_sphere`, `cast_sphere` -- viewport/intersection queries for gravity brushes and render/query visibility -- sparse-cluster-friendly broadphase and spatial acceleration -- SoA-friendly body storage that can be viewed by Aetheria and render clients -- WASM-compatible query and step surface -- CultMesh-friendly typed query/operation bindings where a runtime can ask for physical facts without knowing transport details - -The C# JSON endpoint shape should be understood only for migration and comparison. The Rust API should feel native: - -```rust -let hits = ymir - .world(zone_id) - .overlap_sphere(Sphere::new(center, radius)) - .filter(FactionMask::hostile_to(actor)) - .collect(); -``` - -That is the bar. Unity can keep a bridge for click affordances and presentation, -but committed physical truth belongs to the active Aetheria simulation -authority. Its Rust daemon invokes Ymir through a narrow in-process port and -commits the result in the same tick. A remote implementation may preserve that -port, but must not create a parallel Aetheria world or writer. - -## CultMesh Ergonomic Lessons - -See `Aetheria.State/docs/cultmesh-cross-runtime-primitives.md` for the expanded primitive design, ownership boundary, code-shape examples, and performance contract. This section is the short form kept inside the daemon map. - -Aetheria exposed where CultMesh should become more magical: - -| Current leak | Better CultMesh shape | -| --- | --- | -| Clients manually know record keys. | CultMesh provides generated typed document handles and query handles. | -| TS decodes MessagePack arrays by slot constants. | CultMesh provides generated typed TS/Rust/C#/WASM bindings with transparent encode/decode. | -| Commands are documents pushed to known records. | CultMesh provides typed Verse operations with transport chosen by runtime. | -| Local `.cc` files are used as client read path. | CultMesh subscriptions and scoped queries are the read path. | -| Viewport projections are injected into snapshot responses. | CultMesh supports first-class typed query surfaces for derived state. | -| Eve surfaces require manual state-ref resolution. | CultUI/CultMesh surface props can hold typed state pointers resolved by the runtime. | -| Unity needs special observer glue. | CultMesh gives Unity the same typed state handles plus native render views. | -| SoA view sharing is daemon-specific. | CultCache/CultMesh exposes typed native slices where co-location allows it. | -| Authority logic is app-specific glue. | CultMesh exposes reusable authority policy, lease, claim, and runtime-role primitives. | -| Colocated services still use bespoke endpoints. | CultMesh routes typed local calls, remote calls, WASM calls, and shared-memory/native-slice access behind one semantic API. | - -This rebuild should feed back into CultMesh itself. If Aetheria needs a hand-written adapter to do an obvious typed-state thing, that is probably a missing CultMesh primitive. The target is managed and cozy for the developer, but not soft underneath: the runtime should still pick the fastest available path, whether that is an in-process view, a shared CultCache slab, a WASM view, or a remote Verse route. - -### CultMesh Primitives To Hoist - -These are not Aetheria conveniences. They are shared library capabilities that should make future daemons feel native from every runtime: - -| Primitive | Shared capability | -| --- | --- | -| Typed document handles | Generated handles for records, schema ids, keys, subscriptions, and mutations. | -| Typed operation handles | Method-shaped operations with generated payloads, authority metadata, routing, idempotency, and acknowledgements. | -| Typed query surfaces | Composable derived-state queries that can run colocated, remote, or WASM without changing client code. | -| Reactive state pointers | Values that UI surfaces can hold directly, with automatic resolution, invalidation, and subscription lifetimes. | -| Native slice views | Safe typed views over CultCache/SoA slabs for Unity jobs, Rust, native clients, and WASM memory where possible. | -| Authority primitives | Reusable policy modes, leases, claims, runtime roles, and future quorum/consensus hooks. | -| Geometry/math values | Shared `Vec2`, `Vec3`, `Rect`, `Circle`, `Sphere`, transforms, and deterministic scalar helpers. | -| Locality-aware routing | Runtime chooses shared-memory, in-process, IPC, network, or WASM transport while the API remains semantic. | -| Schema evolution | Generated migration manifests, compatibility readers, and versioned operation/query contracts. | -| Surface bindings | Eve/CultUI components bind to typed state handles and operation handles instead of string refs and command names. | - -### Sugar Targets - -These examples are aspirational API targets. They are not promises about exact names; they show how little transport code a client should need to express ordinary Aetheria work. - -Current TS RTS client command path: - -```ts -const commandId = crypto.randomUUID(); -const issuedAtUtc = new Date().toISOString(); -await client.sendCommandDocument( - commandId, - issuedAtUtc, - encodeSetMoveVectorCommand(commandId, issuedAtUtc, runtimeId, { - actorEntityKey, - directionX, - directionY, - scalarValue, - })); -``` - -Desired TS CultMesh operation sugar: - -```ts -await verse - .aetheria() - .entity(actor) - .pilot() - .move({ direction: vec2(x, y), throttle }); -``` - -Current TS projection path: - -```ts -const frame = await publications.readDaemonFrame(); -const objects = projectObjectsViewportFromFrame(frame, { - minX, - minY, - maxX, - maxY, - controlledEntityIndices, -}); -``` - -Desired TS query sugar: - -```ts -const objects = await verse - .aetheria() - .zone(zoneId) - .objects() - .visibleTo(controlledUnits) - .within(rect.xy(min, max)) - .watch(); -``` - -Current Unity observation path: - -```csharp -var observed = ResolveClient().ObserveAsync().GetAwaiter().GetResult(); -if (observed != null && observed.HasSoaView) -{ - AetheriaDaemonSoaMemoryMap.TryOpen(observed.SoaIndex, out var map, out _); - AetheriaDaemonRenderNativeView.TryCreate(observed.SoaIndex, map, out var view); -} -``` - -Desired Unity renderer sugar: - -```csharp -await foreach (var view in verse - .Aetheria() - .CurrentZone() - .RenderView() - .AsNativeArrays()) -{ - ShipRenderJobs.Schedule(view.Entities); -} -``` - -Current Ymir query bridge shape: - -```csharp -var request = new YmirCircleOverlapRequest -{ - world = world, - center = ToVec2(center), - radius = radius -}; -var hits = YmirPhysicsQueries.OverlapCircle(request).hits; -``` - -Desired Rust Ymir/Aetheria query sugar: - -```rust -let hits = verse - .aetheria() - .zone(zone) - .physics() - .overlap_circle(Circle::new(center, radius)) - .exclude(actor) - .collect() - .await?; -``` - -Current Eve surface state-ref resolution: - -```csharp -using var client = await AetheriaClient.OpenAsync(statePath, runtimeId); -var resolver = client.State.CreateEveSurfaceCultMeshStateRefResolver(); -var label = resolver("aetheria.daemon/frame/currentEntity/name"); -``` - -Desired CultUI state pointer sugar: - -```ts -surface.label({ - text: verse.aetheria().currentEntity().name, -}); - -surface.button({ - icon: "crosshair", - command: verse.aetheria().currentEntity().targetNearest, -}); -``` - -Current C# operation facade is close, but still hides snapshot/transport setup behind a Unity-owned observer: - -```csharp -observer.Operations.SetTarget(targetEntityKey); -observer.Operations.FireWeaponGroup(0); -``` - -Desired cross-runtime typed operation sugar: - -```csharp -await verse.Aetheria() - .Entity(actor) - .Combat - .Target(target) - .Fire(group: 0); -``` - -Current refit/economy fixture shape: - -```csharp -command.TextValue = "repair-parts"; -command.CargoTransfer.OriginEntityKey = station; -command.CargoTransfer.OriginCargoIndex = 0; -command.CargoTransfer.DestinationEntityKey = actor; -command.CargoTransfer.DestinationCargoIndex = 0; -command.CargoTransfer.SourceX = 0; -command.CargoTransfer.SourceY = 0; -command.CargoTransfer.DestinationX = 2; -command.CargoTransfer.DestinationY = 0; -command.CargoTransfer.HasDestinationPosition = true; -``` - -Desired inventory operation sugar: - -```ts -await verse - .aetheria() - .entity(actor) - .inventory() - .cargo() - .takeFrom(station.inventory().cargo()) - .item("repair-parts") - .placeAt(grid(2, 0)); -``` - -Desired trade sugar: - -```rust -verse - .aetheria() - .entity(actor) - .inventory() - .buy_from(station) - .item("reactor-fuel") - .quantity(1) - .place_in(CargoBay::primary()) - .await?; -``` - -The common pattern: state access starts from a typed Verse handle, operations hang from typed state objects, queries compose like native collections, and subscriptions return reactive state or native views. Record keys, schema ids, endpoint selection, MessagePack slots, command ids, and local/remote routing are CultMesh runtime concerns. - -## Deprecation Boundary - -The C# daemon becomes deprecated when these artifacts exist: - -1. A written measurement snapshot of current schemas, record keys, operation meanings, client semantics, and projection outputs. -2. Migration probe fixtures for: - - seeded run state - - command batches - - authority acceptance/rejection - - post-tick frames - - objects viewport - - gravity viewport - - selected object - - inventory - - SoA view descriptor -3. A Rust daemon design that does not inherit local publication files, TS projection duplication, Unity coordinate leakage, or host-loop scenario seeding. -4. A CultMesh ergonomic checklist identifying shared cross-runtime primitives to hoist before the daemon and clients harden around local adapters. The detailed checklist lives in `Aetheria.State/docs/cultmesh-cross-runtime-primitives.md`. - -After that, the C# daemon should be deprecated except for critical migration fixes. - -The concrete artifact and fixture checklist lives in `Aetheria.State/docs/rust-rebuild-freeze-checklist.md`. - -## Rust Rebuild Modules - -These are product-shape modules, not incremental migration slices: - -| Module | Responsibility | -| --- | --- | -| `aetheria_schema` | Canonical schemas, generated bindings, record/query/operation identities, and legacy MessagePack migration support. | -| `aetheria_world` | World, zones, entities, bodies, inventory, loadouts, stats, keys, deterministic state layout. | -| `aetheria_authority` | Verse authority policies, leases, roles, claims, and runtime participation rules. | -| `aetheria_ops` | Typed gameplay operations and validation. | -| `ymir_math` | CultMath-compatible vectors, rects, circles, spheres, transforms, deterministic scalar helpers. | -| `ymir_physics` | Rust-native bodies, fields, broadphase, stepping, contacts, overlap/cast queries, spatial acceleration. | -| `aetheria_sim` | Aetheria rules over Ymir physics: combat, heat, AI, sensors, stat evaluation, deterministic tick logic. | -| `aetheria_query` | First-class typed queries for objects viewport, gravity viewport, current object, inventory, stats, Starbridge state, and client-visible sets. | -| `aetheria_views` | SoA/native/WASM render views, dirty ranges, and cross-runtime native slice semantics. | -| `aetheria_surfaces` | Eve/CultUI state-backed surfaces, state pointer resolution, and operation bindings. | -| `aetheria_host` | Native daemon process, CultMesh runtime, persistence, authority mode configuration. | -| `aetheria_wasm` | Browser simulation host and query runtime. | - -The module boundary should stay honest: Aetheria modules define Aetheria concepts, while CultMesh/CultLib absorbs the general machinery for typed state, generated handles, authority routing, native views, geometry values, reactive surfaces, transport selection, and schema evolution. - -## Design Constraints For The New Daemon - -- Gameplay plane is XY. Unity XZ conversion belongs in Unity adapters. -- State schema should model vectors and rects as first-class values. -- Ymir primitives should be Rust-first and CultMath-compatible; C# DTOs are compatibility mirrors. -- SoA columns may store vector values as vector columns; they do not need to split every component unless the query actually benefits. -- The simulation receives explicit tick time. Wall clock is publication metadata only. -- Queries are derived state. Clients should ask for what they need: visible objects, gravity influences, stats, inventory, render views. -- Rendering views are not authority. Unity, browser, and native clients consume views and submit typed operations. -- The browser can be a simulation host when authority policy grants it that role. -- Local co-deployment can expose native slices, but remote runtimes must see the same semantic state through CultMesh. -- The new API should make the common path short enough that client code reads like native Aetheria code. - -## Things To Bury With The C# Daemon - -- Local `.cc` publication files as normal client API. -- C# or Unity-shaped Ymir as the authority path. -- JSON HTTP Ymir calls as the normal in-process/co-deployed path. -- TS projection copies that parse raw MessagePack slots. -- RUDP snapshot projection injection as the query model. -- Unity coordinate debt in shared schema. -- Host-loop seeding as world definition. -- UI surfaces that require external manual state-ref resolution. -- Client code that knows transport boundaries for ordinary state access. -- Any public `Apply(command, payload)` style operation surface. - -## First Rebuild Milestone - -The first Rust milestone should demonstrate the final shape, not a half-port: - -1. Define canonical schema and hoisted CultMesh primitives for Rust, TS, Unity/C#, browser/WASM, and Eve/CultUI, using `Aetheria.State/docs/cultmesh-cross-runtime-primitives.md` as the shared-layer target. -2. Launch a Rust Aetheria Verse node. -3. Load the measured C# fixture world as a migration sample. -4. Accept typed operations from a TS client through CultMesh. -5. Run simulation authority in Rust. -6. Answer typed viewport and gravity queries. -7. Publish a native/WASM-friendly render view. -8. Let a browser client render and command the world without local `.cc` reads or duplicated projection code. -9. Keep Unity as an optional renderer/input shell over the same typed state and render view semantics. - -That is the bar: C# is documented, measured, and deprecated; the rebuild is not the old daemon in a new language, it is the daemon, client API, and CultMesh surface Aetheria was trying to become. diff --git a/Aetheria.State/docs/rust-rebuild-freeze-checklist.md b/Aetheria.State/docs/rust-rebuild-freeze-checklist.md deleted file mode 100644 index 4323467ff..000000000 --- a/Aetheria.State/docs/rust-rebuild-freeze-checklist.md +++ /dev/null @@ -1,106 +0,0 @@ -# Aetheria Rust Rebuild Freeze Checklist - -This checklist turns the current C# daemon and client integrations into a measured, deprecated reference before the rebuild begins in earnest. The goal is not to migrate the C# shape piece by piece, and it is not strict parity with every current wart. The goal is to make current behavior visible enough that the new daemon, client APIs, and shared CultMesh/CultLib primitives can deliberately preserve, improve, or discard it while building the clean Aetheria/Ymir/CultMesh shape. - -## Freeze Artifacts - -| Artifact | Source of truth | Output | -| --- | --- | --- | -| Daemon schema catalog | `Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeDaemonDocuments.cs` | Generated schema manifest with schema ids, document names, MessagePack key order, enum ids, and record key conventions. | -| Runtime snapshot schema catalog | `Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeSnapshotDocuments.cs` | Generated schema manifest for run, zone, entity, body, inventory, loadout, cargo, equipment, and stat grids. | -| Authority schema catalog | `Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeVerseAuthorityPolicy.cs` | Generated schema manifest plus policy-mode behavior table. | -| Ymir C# reference sample | `Assets/Scripts/ServerShared/YmirPhysicsContracts.cs` | Current DTO and query behavior evidence for Rust Ymir design. Preserve only semantics we still want. | -| Current TS generated contract | `Aetheria.Rts.Web/scripts/generate-rts-bindings.mjs` and `Aetheria.Rts.Web/Electron/aetheria-rts-generated-bindings.ts` | Evidence that schema extraction exists today; replace it with shared CultMesh/CultLib generation for typed handles, operations, queries, state pointers, native views, authority, and migration manifests. | -| Shared CultMesh/CultLib primitive target | `Aetheria.State/docs/cultmesh-cross-runtime-primitives.md` | Design bar and acceptance vocabulary for hoisting typed documents, operations, queries, state pointers, native views, authority, geometry, locality routing, schema evolution, and surface bindings out of Aetheria glue. | -| Initial daemon fixture exporter | `Aetheria.State.Freeze/Program.cs` | Runs the C# daemon once, reads current daemon publications, emits normalized JSON plus MessagePack bytes for frame, public docs, SoA descriptor, projection fixtures, authority decisions, Ymir query probes, and seeded pilot/interaction/refit command-batch fixtures. | -| Current client verifier gates | `Aetheria.Rts.Web/scripts/verify-stage7b-rts-client.ps1`, `verify-stage7c-local-runtime.ps1`, `verify-stage7c-electron-shell.ps1`, `Aetheria.State/scripts/verify-stage7d-unity-parity.ps1` | Current smoke gates while C# is still live. They prove today still runs; they do not define the future daemon or client API surface. | - -## Golden Fixtures - -The C# daemon and clients are measured when these fixtures can be generated from the current implementation. The rebuild should consume them as migration probes: green where current behavior is intentional, red or replaced where current behavior is accidental, and redesigned wherever the client semantics should become simpler. - -| Fixture | Must contain | Proves | -| --- | --- | --- | -| `seeded-local-rts-world` | The daemon-seeded `local-rts` run with zones, entities, bodies, inventory, loadouts, stat grids, current entity, and authority policy. | Rust can load current world semantics without inheriting the host seeding code. | -| `command-batch-pilot` | Targeting, movement, look, tractor, sensor ping, shield toggle, weapon group fire. Initial exporter emits this batch and captures applied/rejected command accounting. | Rust operations cover the same gameplay intents through typed operations. | -| `command-batch-refit` | Rename, transfer cargo, trade purchase, equip from cargo, store equipped item, set docked current ship, pick up loot, restore loadout. Initial exporter emits this batch and captures applied/rejected command accounting: rename/transfer/trade/equip/store currently apply; current-ship selection, pickup without a seeded drop, and restore without a template currently reject. | Rust operations cover inventory/economy/refit intent without inheriting command-document ergonomics or accidental rejection behavior. | -| `command-batch-interaction` | Dock, dock nearest, undock, interact, tow to station. Initial exporter emits this batch and captures applied/rejected command accounting. | Rust operations cover interaction/travel intent and may simplify ambiguous current behavior. | -| `authority-decisions` | Allowed and rejected commands for host-authoritative, delegated runtime, owning runtime, and lease cases. | Rust authority has explicit behavior for currently implemented modes and explicit design choices for modes C# only stubs. | -| `post-tick-frame` | Frame after a fixed command batch and fixed delta, including command accounting and committed facts. | Rust tick composition can be compared to current output where useful, then diverge where the new simulation model is better. | -| `objects-viewport` | XY rect, controlled unit set, visible objects, statuses, inventory summaries. | Rust query surface can replace TS local projection copies. | -| `gravity-viewport` | XY rect, intersecting gravity brushes, body views. | Rust query surface can replace Unity/TS gravity reconstruction. | -| `selected-object` | Entity selection, status, target, faction, inventory identity. | Rust selected-object query supports current UI expectations through cleaner typed state. | -| `inventory` | Equipment and cargo rows, source identity, item stats, quantity, quality, durability. | Rust inventory projection can drive Unity and RTS panels. | -| `soa-view` | Current-zone render columns, column kinds, vector columns, dirty ranges, native view descriptor. | Rust view publication can feed Unity and native clients. | -| `ymir-queries` | Step integration, radial field acceleration, contact separation, overlap circle, cast circle, overlap sphere, cast sphere, and invalid-input cases. Initial exporter emits typed request/result probes with explicit vector, world, body, field, hit, and contact records in JSON plus MessagePack. Vector fields now use shared CultLib `GameCult.Geometry` primitives (`CultVec2`/`CultVec3`) instead of fixture-local DTOs. | Rust Ymir preserves useful physical semantics while replacing C# endpoint shape with Rust/CultMath primitives. | - -## Rust Rebuild Acceptance Gates - -The first rebuild milestone is accepted only when the new architecture demonstrates the intended cross-runtime shape. - -1. A Rust Verse node can load the measured fixture world and publish typed Aetheria state. -2. A TS client can submit typed operations through shared CultMesh operation handles without constructing command documents, record keys, or raw MessagePack payloads. -3. A browser client can render map state by watching shared typed object/gravity query surfaces, not by reading local `.cc` files or duplicating projection code. -4. A Unity client can consume a shared typed render view as native arrays without owning gameplay state or running Unity physics for authority. -5. The Rust daemon owns committed physical truth and performs step, overlap, - cast, contact, and broadphase computation through embedded Rust Ymir. -6. Eve/CultUI surfaces can bind to shared CultMesh state pointers and typed operations without manual state-ref resolver glue in client code. -7. Authority policy is configured through shared CultMesh authority primitives and changes who may author claims without changing operation schemas. -8. The API can run in native daemon mode and expose a WASM-compatible surface for browser simulation host mode. -9. The old C# daemon can be turned off for the browser RTS proof and the world still runs, renders, and accepts commands. - -## CultMesh Sugar Gates - -These are not cosmetic. If these fail, the architecture is still leaking. - -The expanded primitive spec is `Aetheria.State/docs/cultmesh-cross-runtime-primitives.md`. Use it as the shared-library ownership boundary whenever a new adapter starts appearing in Aetheria-specific code. - -| Gate | Bad smell | Desired shape | -| --- | --- | --- | -| Typed operations | Client code constructs `CommandId`, `recordKey`, `schemaId`, or `document_put_raw`. | CultMesh operation handles expose `verse.aetheria().entity(actor).pilot().move(...)`. | -| Typed queries | Client code fetches a frame and calls local projection helpers. | CultMesh query surfaces expose `verse.aetheria().zone(zone).objects().visibleTo(units).within(rect)`. | -| Typed state pointers | UI code resolves string state refs manually. | CultMesh state pointers are first-class values resolved by CultUI/CultMesh runtime. | -| Native views | Unity code manually opens memory maps by daemon-specific paths. | CultMesh native slice views expose `RenderView().AsNativeArrays()` from the typed Verse handle. | -| Authority | Each daemon invents policy, lease, claim, and runtime-role glue. | CultMesh authority primitives carry policy modes, claims, leases, runtime roles, and future quorum hooks. | -| Geometry | Each runtime reinvents vectors, rects, circles, spheres, and coordinate conversions. | CultMath/CultMesh provide shared geometry values and deterministic scalar helpers. | -| Locality | Co-deployed services still talk through bespoke URLs or files. | CultMesh chooses in-process, shared slab, IPC, network, or WASM transport behind one semantic API. | -| Ymir queries | Unity or TS reintroduces bespoke endpoint transport for normal co-deployed queries. | Aetheria/Ymir queries are typed, colocated when possible, and remotely routable when needed. | -| Schema generation | TS, C#, and Rust each hand-maintain slot maps. | One schema source generates cross-runtime CultMesh bindings and legacy migration manifests. | -| Client semantics | Each runtime gets bespoke helper layers and transport assumptions. | Unity, TS, browser, Rust, native, and Eve/CultUI share the same operation/query/state semantics with runtime-specific ergonomic sugar. | - -## Current Verification Commands - -These are current-state gates, not proof that the Rust rebuild is complete. They are useful while freezing the C# contract. - -```powershell -dotnet build .\GameCult.Aetheria.State.Unity.csproj /clp:ErrorsOnly /p:UseSharedCompilation=false -dotnet build .\Aetheria.State.Daemon\Aetheria.State.Daemon.csproj /clp:ErrorsOnly /p:UseSharedCompilation=false -dotnet run --project .\Aetheria.State.Verify\Aetheria.State.Verify.csproj -.\Aetheria.State\scripts\verify-stage7d-unity-parity.ps1 -cd .\Aetheria.Rts.Web -npm run check:rts-bindings -npm run build -npm run verify:stage7b -npm run verify:stage7c -npm run verify:stage7c:electron -``` - -## Immediate Build Work - -1. Expand `Aetheria.State.Freeze` beyond baseline plus pilot/interaction/refit/authority/Ymir probes into post-tick, richer authority, and richer Ymir fixture families. -2. Expand `generate-rts-bindings.mjs` or replace it with shared CultMesh/CultLib schema and binding generation for Rust, TS, Unity/C#, browser/WASM, and Eve/CultUI, including legacy migration manifests. -3. Expand the initial CultMath-shaped Ymir fixture cases with explicit rect, circle, sphere, broadphase, sparse-cluster, and viewport-intersection schemas. -4. Begin the shared primitive implementation from `cultmesh-cross-runtime-primitives.md` before the Rust daemon hardens around one-off Aetheria adapters. -5. Create an initial Rust workspace with `ymir_math`, `ymir_physics`, `aetheria_schema`, and `aetheria_world`. -6. Make Rust Ymir satisfy the intentional Ymir fixture cases before wiring it into Aetheria; document any deliberate improvements over C# behavior. -7. Make Rust Aetheria load the measured world fixture and answer object/gravity viewport queries through hoisted CultMesh query/state primitives shared by TS, Unity, browser, and Eve/CultUI. - -Generate the initial daemon fixture set with: - -```powershell -dotnet run --project .\Aetheria.State.Freeze\Aetheria.State.Freeze.csproj -- --root . --state .\obj\freeze\aetheria-freeze-state.cc --out .\Aetheria.State\fixtures\rust-rebuild-freeze -``` - -For smoke runs that should not touch checked-in fixtures, write to `.\obj\freeze-smoke\fixtures`. - -The rebuild begins once the C# behavior is measured enough to learn from it without copying its leaks. diff --git a/Aetheria.State/docs/stage-7-client-surface-inventory.md b/Aetheria.State/docs/stage-7-client-surface-inventory.md deleted file mode 100644 index 18986fb4b..000000000 --- a/Aetheria.State/docs/stage-7-client-surface-inventory.md +++ /dev/null @@ -1,298 +0,0 @@ -# Stage 7A Client Surface Inventory - -Date: 2026-06-24 - -Stage 7A exists to stop the client migration from turning into Jenga. This is -the reviewed inventory for Unity, Electron, and daemon client-facing surfaces. -Stage 7D.1 refreshed the Unity half of this ledger before Unity parity code -movement began. - -## Gate - -Current gate: Stage 7D.4 Unity projection reroute. - -Implementation runbook: - -- `Aetheria.State/docs/stage-7-thin-client-staged-implementation-plan.md` - -Stage 7A is complete. Stage 7D.1 reuses this artifact as the edit queue: every -remaining Unity-facing mutation and gameplay read/projection path must be -assigned to `7D.2 facade`, `7D.3 commands`, `7D.4 reads`, `7D.5 renderer`, or -`8 demolition`. Stage 8 Unity gameplay shell demolition may not start until -Stage 7E passes. - -## Target Shape - -```text -client input -> local typed Verse command document -> local daemon -local daemon state -> local typed projection/read -> renderer or UI panel -Pilot daemon candidate evidence -> Commander selection/finality -> shared local state -``` - -Forbidden as public client shape: - -- `command(kind, payload)`; -- browser or Unity code constructing transport payloads directly; -- hand-encoded MessagePack array layouts in client code; -- remote gameplay viewport reads used as state authority; -- cached command-port facades or ad-hoc buses between input and the local Verse - command document. - -## Electron Mutation Surfaces - -| Surface | Evidence | Current Shape | Decision | Stage | -| --- | --- | --- | --- | --- | -| Browser right-click move/target | `Aetheria.Rts.Web/Client/app.ts:478`, `:490` | Browser now calls typed `window.aetheriaRts.setTarget(...)` and `setMoveVector(...)`; it no longer constructs `{ kind: ... }` transport payloads. | Keep as temporary typed facade. Replace with shared generated TS Verse bindings when available. | 7B/7C | -| Preload command bridge | `Aetheria.Rts.Web/Electron/preload.cjs:5`, `:6` | Exposes typed `setMoveVector` and `setTarget` IPC calls. | Keep until the broader typed runtime facade replaces preload method wiring. | 7B | -| Electron IPC command handler | `Aetheria.Rts.Web/Electron/main.ts:98`, `:101` | Accepts `RtsSetMoveVectorRequest` and `RtsSetTargetRequest` and forwards to typed client methods. | Keep as Stage 7B shim. | 7B | -| TypeScript CultMesh command writer | `Aetheria.Rts.Web/Electron/aetheria-cultmesh.ts`; `Aetheria.Rts.Web/Electron/aetheria-rts-bindings.ts`; `Aetheria.Rts.Web/Electron/aetheria-rts-generated-bindings.ts` | Transport wrapper exposes typed methods; generated TS contract metadata owns schema ids, command kind ids, and MessagePack slot maps derived from the C# `[Key]` declarations, including the current 30-slot daemon command document. | Keep the semantic encode helpers here until the broader CultMesh TS typed document writer lands; do not reintroduce manual slot maps. | 7B | -| Daemon local command receive hook | `Aetheria.State.Daemon/Program.cs:454` | RUDP handler accepts raw command documents and submits them to the local node. | Quarantine for 7B, then replace or narrow. It can remain as an internal local typed document transport endpoint, but it must not define the public client API. | 7B | - -## Electron Read And Projection Surfaces - -| Surface | Evidence | Current Shape | Decision | Stage | -| --- | --- | --- | --- | --- | -| Browser viewport polling | `Aetheria.Rts.Web/Client/app.ts:166` | Browser requests `mapViewport` every 50 ms and treats the returned document as the main map projection. | Keep as named projection shim, but replace underlying remote viewport request with local typed projection reads. | 7B/7C | -| Preload viewport bridge | `Aetheria.Rts.Web/Electron/preload.cjs:4` | Exposes typed `mapViewport` projection request. | Keep until the broader typed runtime facade replaces preload method wiring. | 7B | -| Electron IPC viewport handler | `Aetheria.Rts.Web/Electron/main.ts:95` | Accepts typed viewport bounds and forwards to `AetheriaCultMeshClient.mapViewport`. | Keep as Stage 7B shim. | 7B | -| TypeScript CultMesh viewport reader | `Aetheria.Rts.Web/Electron/aetheria-cultmesh.ts`; `Aetheria.Rts.Web/Electron/aetheria-rts-bindings.ts`; `Aetheria.Rts.Web/Electron/aetheria-rts-generated-bindings.ts` | Transport wrapper asks for the named RTS viewport schema; generated TS contract metadata owns viewport slot maps while semantic decode helpers produce ergonomic UI types. | Replace the remote viewport request with local typed projection reads. | 7B/7C | -| Daemon local viewport hook | `Aetheria.State.Daemon/Program.cs:420`, `:501`; `Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeGameViewportDocuments.cs` | Daemon snapshot hook now delegates viewport construction to the shared typed `AetheriaRuntimeGameViewportDocuments` API. The hook still serves it over RUDP for Electron. | Keep only as temporary transport plumbing. Replace Electron's RUDP viewport read with local projection reads, then delete or narrow this hook. | 7B/7C | - -## Unity Mutation Surfaces - -| Surface | Evidence | Current Shape | Decision | Stage | -| --- | --- | --- | --- | --- | -| Runtime input settings commands | `Assets/Scripts/UI/InputScreen/InputDisplayLayout.cs` | `InputDisplayLayout` now owns an explicit `AetheriaClient` and submits typed input settings commands through the shared facade. The old static `ActionGameManager` input-settings ingress has been deleted. | Keep as the Stage 7D direct-panel pattern: panel owns presentation/input state, facade owns typed Verse submission. | 7D | -| Loadout restore | `Assets/Scripts/Gameplay/ActionGameManager.cs:1110`; caller at `Assets/Scripts/UI/Menu/InventoryPanel.cs:258` | Unity UI calls manager method for daemon-owned loadout operation. | Replace with typed client facade method. | 7D | -| Daemon operation submitter | `Assets/Scripts/Gameplay/AetheriaDaemonObserver.cs:40`, `:44`; `Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeDaemonOperationClient.cs:49`, `:54` | Unity uses typed operation wrappers that open `AetheriaRuntimeVerseClient` and submit `AetheriaRuntimeDaemonCommandDocument`. | Keep as the semantic source for Stage 7B typed commands, but remove per-call client open churn when the shared runtime wrapper lands. | 7B/7D | -| Surface command helpers | `Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeDaemonSurfaceCommands.cs:23`, `:37` | Eve/CultUI surface commands submit typed daemon operations. | Keep, but ensure surface command templates point at typed documents or typed state refs rather than generic payload maps. | 7D | - -## Unity Read And Projection Surfaces - -| Surface | Evidence | Current Shape | Decision | Stage | -| --- | --- | --- | --- | --- | -| Daemon observer polling | `Assets/Scripts/Gameplay/AetheriaDaemonObserver.cs:130`, `:139` | Opens `AetheriaRuntimeVerseClient` and reads observed daemon state. | Keep. This is the closest current Unity surface to the target contract. | 7D | -| SoA native view mapping | `Assets/Scripts/Gameplay/AetheriaDaemonObserver.cs:83`, `:153` | Maps daemon SoA view into `AetheriaDaemonRenderNativeView`. | Keep. This remains the rendering fast path. | 7D/8 | -| `ActionGameManager` frame lowering | `Assets/Scripts/Gameplay/ActionGameManager.cs`; `Assets/Scripts/Gameplay/AetheriaUnityObservedFrameApplier.cs` | Unity loop delegates `ApplyLatestZoneRender` to the observed frame applier, which reads the typed `ZoneRenderAsync()` facade. | Quarantine. Stage 7D may keep it as a shell; Stage 8 should delete or reduce it once Unity renders from projection/native views. | 7D/8 | -| Observed Galaxy facade | `Assets/Scripts/Gameplay/AetheriaUnityObservedRunProjection.cs`; created from typed sector-map boot in `Assets/Scripts/UI/MainMenu.cs` | Unity maintains an observed facade graph only as a legacy scene-construction adapter. `ActionGameManager` no longer exposes or stores it. | Quarantine for Stage 7D, demolish in Stage 8 where clients can use equivalent typed projections. | 7D/8 | -| `ZoneRenderer` frame projection | `Assets/Scripts/Zone Display/ZoneRenderer.cs:239` | Persistent GameObject projection cache lowers typed `zone_render`, viewport, and contact feeds. | Keep as presentation cache only until Stage 8. It must not become an authority or mirrored hierarchy owner. | 7D/8 | -| Menu reads from manager singletons | Examples: `Assets/Scripts/UI/Menu/SectorMap.cs:94`, `Assets/Scripts/UI/Menu/InventoryMenu.cs:411`, `Assets/Scripts/UI/Menu/TradeMenu.cs:530` | UI panels read catalog/settings/state through `ActionGameManager`. | Replace with shared client projection/state refs as panels are migrated. | 7D/8 | - -## Shared Runtime Contract Surfaces To Keep - -| Surface | Evidence | Decision | -| --- | --- | --- | -| `AetheriaRuntimeVerseClient` | `Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeVerseClient.cs:153`, `:177`, `:508` | Keep and make it the shape both Unity and Electron mirror. TS needs generated/manual bindings for the same typed records. | -| Typed operation wrappers | `Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeDaemonOperationClient.cs:7`; `AetheriaRuntimeDaemonOperationsClient` wrappers | Keep as semantic command catalog. Stage 7B should expose equivalent TS typed operations. | -| Render queries | `Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeDaemonRenderQueries.cs:645`, `:974`, `:1079`, `:1165` | Keep as projection internals. Unity consumes typed zone/body/contact/viewport rows; asteroid instance poses are now carried by `zone_render` instead of recomputed from snapshots. | -| Authority policy and health docs | `Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeVerseAuthorityPolicy.cs:45`; `AetheriaRuntimeVerseClient.GetHealthAsync` at `:237`; generated TS metadata in `Aetheria.Rts.Web/Electron/aetheria-rts-generated-bindings.ts` | Keep. Stage 7 client startup must show policy id, peer health, daemon endpoint, frame id/time, and import counters. | - -## Ad-Hoc Bus Search Results - -Reviewed search terms: - -```text -command, viewport, Apply(command, payload, Queue, ConcurrentQueue, Channel, -Bus, EventBus, CommandPort, cached command, port -``` - -Actionable hits now: - -- TypeScript raw CultMesh document put and snapshot request helpers. -- Daemon local command/viewport request handlers. - -Retired hits: - -- Electron IPC `aetheria-rts:command` and `aetheria-rts:viewport`; -- browser `window.aetheriaRts.command` and `window.aetheriaRts.viewport`; -- public `RtsCommandRequest` command union; -- `createCommandDocument`. - -Non-actionable or already-quarantined hits: - -- shader/material `Queue` metadata; -- ordinary import/export/transport wording; -- daemon peer endpoint option parsing; -- command receipt/import bookkeeping from Stage 6. - -No current `CommandPort`, `CachedCommandPort`, `EventBus`, or public -`Apply(command, payload)` implementation was found in the scoped search. - -## Stage 7D.1 Unity Edit Queue - -Date: 2026-06-24 - -Stage 7D.0 verifier baseline passed after the Electron app-shell smoke was -hardened. Unity parity may now begin, but only through the following queue. - -Reviewed source search: - -```powershell -rg -n "ActionGameManager|AetheriaDaemonObserver|AetheriaRuntimeDaemonOperationClient|AetheriaRuntimeDaemonSurfaceCommands|ZoneRenderer|Rigidbody|Collider|Physics\.|SubmitDaemonCommand|CommandPort|CachedCommandPort|EventBus|Apply\(" Assets/Scripts Packages/org.gamecult.aetheria.state/Runtime -g "*.cs" -S -rg -n "ObservedGalaxy|LoadZone|ApplyDaemonFrame|GameObject\.Find|FindObjects|GetComponent<" Assets/Scripts -g "*.cs" -S -rg -n "Queue|Channel|ConcurrentQueue|Bus" Assets/Scripts Packages/org.gamecult.aetheria.state/Runtime -g "*.cs" -S -``` - -### 7D.2 Facade Candidates - -Status on 2026-06-24: initial shared facade is in place. -`AetheriaClient` owns the local Verse client lifetime, exposes typed daemon -operations, and provides named local reads for map viewports, selected object, -inventory, daemon health, authority policy, and SoA view data. -`AetheriaDaemonObserver` now exposes that shared client instead of keeping a -separate operation client and Verse client. The remaining rows are either -carried into 7D.3 command reroute or 7D.4 projection reroute. - -| Surface | Evidence | Current Shape | Stage 7D Action | -| --- | --- | --- | --- | -| Unity local daemon observer | `Assets/Scripts/Gameplay/AetheriaDaemonObserver.cs` | Now owns a shared `AetheriaClient`, local observation cursor, and SoA render mapping. | Keep as Unity presentation shell. 7D.3 should move remaining command ingress through `observer.Client`/`observer.Operations`; 7D.4 should move read sites to named facade projections. | -| Runtime daemon operation client | `Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeDaemonOperationClient.cs` | Typed command helper. The shared facade can now inject a submit delegate so sends reuse its long-lived Verse client instead of opening a new client per command. | Keep as typed command document builder behind `AetheriaClient`. Do not expose generic command APIs above it. | -| Generated operation catalog | `Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeDaemonOperationsClient.cs` | Ergonomic typed wrappers over `AetheriaRuntimeDaemonOperationClient`; nullable observed state is now explicit for pre-frame command issue. | Keep as public typed operation vocabulary exposed through `AetheriaClient.Operations`. | -| Eve/CultUI surface commands | `Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeDaemonSurfaceCommands.cs:8`, `:37`; catalog at `AetheriaRuntimeDaemonSurfaceCommandCatalog.cs:58` | Surface command ingress now routes through `AetheriaClient` and facade-backed daemon command document submission. | Keep as typed Eve ingress behind the facade. Do not let UI callers submit daemon documents directly. | -| Runtime Verse client submit | `Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeVerseClient.cs:508`, `:528` | Canonical typed command document submission. | Keep as the low-level primitive used by the facade. Do not expose stringly command APIs above it. | - -### 7D.3 Command Reroute Queue - -| Surface | Evidence | Current Shape | Stage 7D Action | -| --- | --- | --- | --- | -| Input settings commands | `Assets/Scripts/UI/InputScreen/InputDisplayLayout.cs` | Panel now resolves and owns an `AetheriaClient`, then submits typed input settings values through `AetheriaClient.SubmitInputSettingsCommandAsync`. | Done for 7D.3. Use this as the pattern for remaining direct UI command reroutes. | -| Loadout template save/delete | `Assets/Scripts/UI/Menu/InventoryPanel.cs`; projection helper at `Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeLoadoutSnapshotProjector.cs` | `InventoryPanel` owns loadout-template save submission through `AetheriaClient.SubmitLoadoutTemplateCommandAsync` and now asks `AetheriaClient.LoadoutTemplateAsync(entityKey)` to project the target daemon entity into a template. Save no longer serializes the Unity `Entity` facade. | Done for save. The remaining Unity loadout projector is renderer/bootstrap adapter code for constructing Unity presentation objects from saved templates, not the save path. | -| Loadout restore/current ship/name/hull toggles | `Assets/Scripts/UI/Menu/InventoryPanel.cs` | `InventoryPanel` owns an explicit `AetheriaClient` and submits `RestoreLoadout`, `SetDockedCurrentShip`, `SetEntityName`, and `ToggleHullConductivity` through `AetheriaClient.Operations`. The old manager request shims are gone; the panel uses the manager only to resolve observed Unity facade entities to daemon record keys and local price context. | Done for this dropdown/current-ship slice. Price/context logic that another runtime needs should move into daemon/state code before Stage 8. | -| Inventory drag/drop and double-click item transfer | `Assets/Scripts/UI/Menu/InventoryPanel.cs`; `Assets/Scripts/UI/Menu/InventoryMenu.cs` | Inventory panels/menus own explicit `AetheriaClient` instances and submit `TransferCargoItem`, `EquipItem`, and `StoreItem` through typed facade operations. `ActionGameManager` no longer exposes cargo/equipment transfer request shims; it only resolves observed Unity facade cargo/equipment objects to daemon record keys and indices. | Done for this transfer slice. | -| Trade purchases | `Assets/Scripts/UI/Menu/TradeMenu.cs` | `TradeMenu` owns an explicit `AetheriaClient` and submits typed `TradePurchase` operations for station stock, cargo purchases, commodity quantity purchases, and docked ship hull purchases. `ActionGameManager` no longer exposes trade purchase request shims. | Done for current trade purchase surface. Station-stock authoring/refit verbs still need first-class typed surfaces where they are not already represented by trade commands. | -| Equipped-item details and action-bar binding commands | `Assets/Scripts/UI/Menu/InventoryMenu.cs`; `Assets/Scripts/Gameplay/ActionBarSlot.cs` | `InventoryMenu` submits equipped-item override shutdown, thermotoggle target temperature, weapon-group membership, action-bar bind, and action-bar clear through its explicit `AetheriaClient`. Restored action-bar bindings hold an explicit `AetheriaClient` and submit consumable, behavior-active, and weapon-group-active typed operations. `ActionGameManager` no longer exposes public request shims for these menu commands; it only resolves observed item identity and action-bar control paths. | Done for current detailed item/action-bar surface. Drag-to-action-bar still originates in the manager because drag registration currently lives there, but it submits through the shared facade rather than a daemon shim. | -| Runtime target/control commands | `Assets/Scripts/Gameplay/ActionGameManager.cs` plus `AetheriaDaemonObserver.Operations` | `ActionGameManager` remains the local input gesture router for movement, look, tractor power, target selection/cycling, reticle targeting, override shutdown, sensor ping, heatsinks, shields, interact, tow, dock, and undock. It now submits those through a shared typed facade operation helper instead of private daemon shim methods. | Done for current pilot input ingress. Stage 8 should contract this to input orchestration only after equivalent projections replace manager-owned reads. | -| Starbridge commander and pilot verbs | Starbridge design target in `E:/Projects/AetheriaLore/Aetheria/Game Design/Aetheria Starbridge.md` | Station stock, docking/refit, salvage, construction anchoring, target marks, cooling, repair, drone/turret orders, infrastructure placement, fabrication, wave/hostile control, and survival-pod recovery are not all first-class command surfaces yet. | Add them only as typed daemon/state operations or typed projections. Unity may issue pilot-local intent; RTS may issue commander intent; neither client may own bespoke gameplay behavior for these verbs. | -| Menu startup and Eve commands | `Assets/Scripts/UI/MainMenu.cs:20`, `:461` | Main menu now owns an `AetheriaClient`; frame/settings reads and known Eve command submission route through the facade. Client-target file edits still use the typed local target utility. | Keep as facade-backed UI shell. Stage 8 should delete the menu-owned compatibility shims once panels can hold explicit client references. | -| Eve surface presenter | `Packages/org.gamecult.aetheria.eve-runtime/Runtime/AetheriaEveSurfacePresenter.cs` | Presenter now owns an `AetheriaClient`; daemon Eve surface reads, state-ref resolution, and surface command submission use the facade or file-backed state reader. | Keep as Unity presentation infrastructure. It must remain render/input plumbing, not gameplay authority. | - -### 7D.4 Projection And Read Queue - -| Surface | Evidence | Current Shape | Stage 7D Action | -| --- | --- | --- | --- | -| Observed daemon state polling | `Assets/Scripts/Gameplay/AetheriaDaemonObserver.cs:74`, `:120` | Polls local Verse state and emits changed observations. | Keep as local read primitive, but expose named typed projections through 7D.2 facade rather than leaking raw observed state to gameplay shell callers. | -| Observed galaxy facade | `Assets/Scripts/Gameplay/AetheriaUnityObservedRunProjection.cs`; booted from typed sector-map state in `Assets/Scripts/UI/MainMenu.cs` | Unity builds a projected Galaxy facade only as a legacy scene-construction adapter. `ActionGameManager` no longer stores or exposes it. | Quarantine for Stage 7D. Replace reads with local projection/state access where possible. Delete or collapse in Stage 8 after equivalent projections exist. | -| Runtime catalog/settings reads | scoped search across `Assets/Scripts` and `Packages/org.gamecult.aetheria.state` | No direct `ActionGameManager.RuntimeCatalog` or `ActionGameManager.RuntimePlayerSettings` reads remain. `InventoryMenu`, `InventoryPanel`, `TradeMenu`, `ActionBarSlot`, `ZoneRenderer`, `EntityInstance`, `ShipInstance`, `InputDisplayLayout`, `VolumeCloudRenderer`, `MainMenu`, and `SchematicDisplay` have moved their local catalog/formatting/settings reads to explicit `AetheriaClient` instances or a renderer-owned client cache. | Done for direct manager-global catalog/settings cleanup. Continue replacing observed-galaxy/entity facade reads with portable typed projections. | -| Map screen zone/settings reads | `Assets/Scripts/UI/Menu/MapRenderer.cs` | `MapRenderer` now owns an explicit `AetheriaClient` and reads zone title from `ObjectsViewportAsync` plus minimap asteroid visibility from `PlayerSettingsAsync`. The unused `ActionGameManager` reference was removed. | Done for this focused 7D.4 read slice. Continue moving map/sector reads off the temporary observed galaxy and global runtime settings where typed projections exist. | -| Sector zone details reads | `Assets/Scripts/UI/Menu/SectorRenderer.cs` | The zone-details Eve surface now reads sector topology from `SectorMapAsync`, daemon zone facts from `ZoneDetailsAsync`, hull type lookups from `OpenRuntimeCatalog`, and formatting settings from `PlayerSettingsAsync` through an explicit `AetheriaClient`. It no longer asks `ActionGameManager` for zone snapshots, runtime catalog, or player formatting, and no longer reads the whole authoritative frame for this surface. | Done for zone-details surface. The sector map layout/reveal path still uses the legacy `GalaxyZone` facade until every remaining renderer signal is represented by portable typed projections. | -| Target/contact presentation reads | `Assets/Scripts/Gameplay/AetheriaUnityObservedTargetQuery.cs`; `Assets/Scripts/Gameplay/AetheriaUnityTargetPresentation.cs`; projection in `Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeGameViewportDocuments.cs` | Unity target presentation now reads `gamecult.aetheria.zone_contacts.v1` through `AetheriaClient.ZoneContactsAsync()`. The feed carries typed target/contact identity, visibility, hostility, target position, deltas, and distance for the current zone. Target HUD, target-distance renderer cache, minimap compass markers, and renderer visibility fades no longer call snapshot render-query helpers for those facts. | Done for current target HUD indicators, target-distance renderer cache, compass markers, and visibility fade state. Remaining target presentation objects are Unity affordances over typed contact rows. | -| Docked current-entity binding | `Assets/Scripts/Gameplay/AetheriaUnityObservedDockingIndex.cs`; `Assets/Scripts/Gameplay/AetheriaUnityCurrentEntityBinder.cs`; `Assets/Scripts/Gameplay/AetheriaUnityCurrentEntityPresentation.cs` | Docking state now comes from `AetheriaClient.CurrentDockingAsync()` and resolves the Unity docking-bay facade by typed dock parent key plus bay index. Dock camera body focus uses `AetheriaClient.ZoneRenderAsync()` body poses rather than passing a raw current-zone snapshot into presentation. | Done for current docked binding path. The remaining Unity facade entity/docking-bay objects are presentation adapters, not portable gameplay state. | -| Inventory/trade item detail reads | `Assets/Scripts/UI/Menu/InventoryMenu.cs`; `Assets/Scripts/UI/Menu/InventoryPanel.cs`; `Assets/Scripts/UI/Menu/TradeMenu.cs` | These panels already own explicit `AetheriaClient` instances for typed command submission. They now also read runtime catalog/manufacturer data and player formatting settings through those local clients for ship settings, cargo item details, equipped item details, trade table columns, trade behavior fields, trade item details, dropdown loadout pricing, inventory hull/item geometry, hardpoint typing, durability lookup, and temperature labels. | Done for current detail/dropdown/panel slice. Remaining HUD schematic, entity prefab presentation, and action-bar icon/name reads still use manager globals until migrated. | -| Current ship settings facts | `Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeGameViewportDocuments.cs`; `Assets/Scripts/UI/Menu/InventoryMenu.cs` | `CurrentEntityAsync` now publishes the current entity shutdown-performance threshold. `InventoryMenu` renders the ship-settings Eve surface from the typed current-entity document and submits shutdown changes by daemon entity key instead of storing a Unity `Entity` facade as the settings source. | Done for current ship-settings read slice. The daemon still validates range and applies authority; Unity only renders the typed row and submits intent. | -| Current cockpit HUD facts | `Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeGameViewportDocuments.cs`; `Assets/Scripts/UI/HUD/SchematicDisplay.cs` | `CurrentEntityAsync` now publishes a typed current-entity HUD status row for override shutdown, shield activity, heatsinks, heat/hypothermia exposure, visibility, hull ratio, radiator range, sensor cooldown, reactor draw, capacitor charge, and Aether-drive RPM. The player schematic HUD reads that row through its local `AetheriaClient` instead of pulling those facts from the Unity `Entity` facade. | Done for the current player HUD status slice. Target/enemy schematic rows still adapt Unity presentation objects until a target-entity projection replaces that display path. | -| Station refit docking-bay rows | `Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeGameViewportDocuments.cs`; `Assets/Scripts/UI/Menu/InventoryPanel.cs`; `Assets/Scripts/UI/Menu/InventoryMenu.cs` | `StationRefitAsync` now publishes typed docking-bay rows with slot identity, occupied entity identity, current-entity match, hull key, and cargo items. Inventory menu/panel docking-bay display validates the current row before adapting the Unity docking-bay facade. | Done for the current docking-bay display slice. Continue promoting station stock/refit eligibility and remaining cargo presentation facts into typed station/refit projections before Stage 8. | -| Loadout restore options | `Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeGameViewportDocuments.cs`; `Packages/org.gamecult.aetheria.state/Runtime/AetheriaClient.cs`; `Assets/Scripts/UI/Menu/InventoryPanel.cs` | `StationRefitAsync` now composes daemon frame state, Verse loadout templates, and runtime catalog policy into typed restore options. Each row carries template name, daemon target entity key, price, and `CanRestore`. InventoryPanel renders and submits those rows instead of reading `LoadoutTemplatesAsync` or pricing templates locally. | Done for current restore dropdown slice. The daemon still validates restore acceptance on command apply; clients only display the projected row and submit intent. | -| Trade cargo target rows | `Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeGameViewportDocuments.cs`; `Assets/Scripts/UI/Menu/TradeMenu.cs` | `StationRefitAsync` now publishes typed cargo target rows for the current docking bay and player ship bays. Each row carries target kind, label, entity key, bay index, hull key, and cargo items. TradeMenu builds the target selector and owned counts from those rows instead of reconstructing targets from available entities or treating station stock as docking-bay cargo. | Done for current target cargo/count slice. Remaining trade row filtering and purchase submission stay typed; Stage 8 can delete more Unity presentation adapters once row rendering is fully projection-native. | -| Station stock trade facts | `Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeGameViewportDocuments.cs`; `Assets/Scripts/UI/Menu/TradeMenu.cs` | `StationRefitAsync` enriches each station stock row with shared-policy price, affordability, and owned quantity. Hull ownership is counted from typed available entities; item ownership is counted from typed cargo target rows. TradeMenu renders the `OwnedQuantity` row fact instead of counting Unity ships or cargo dictionaries locally. | Done for current stock row fact slice. The daemon projection owns trade row display facts; command apply still revalidates purchase acceptance. | -| Action-bar and renderer catalog reads | `Assets/Scripts/Gameplay/ActionBarSlot.cs`; `Assets/Scripts/Zone Display/ZoneRenderer.cs`; `Assets/Scripts/Gameplay/EntityInstance.cs`; `Assets/Scripts/Gameplay/ShipInstance.cs` | Action-bar bindings resolve gear icon catalog data through their explicit `AetheriaClient`. `ZoneRenderer` owns a local `AetheriaClient` catalog cache used for entity hull prefabs, pickup labels, pickup tier color, entity hull binding, and ship durability normalization. | Done for current presentation catalog slice. Remaining HUD schematic catalog/formatting and main-menu observed-galaxy boot still need separate treatment. | -| Input and nebula settings reads | `Assets/Scripts/UI/InputScreen/InputDisplayLayout.cs`; `Assets/Scripts/Zone Display/VolumeCloudRenderer.cs` | The input screen reads action-bar input visibility from `AetheriaClient.PlayerSettingsAsync()`. The volume cloud renderer reads nebula quality from `AetheriaClient.PlayerSettingsAsync()` and falls back to its serialized quality if local state cannot be read. | Done for current settings slice. | -| Main-menu and HUD catalog/settings reads | `Assets/Scripts/UI/MainMenu.cs`; `Assets/Scripts/UI/HUD/SchematicDisplay.cs` | Main menu reads the runtime catalog and typed sector-map boot state through its local `AetheriaClient`; the legacy observed-galaxy boot projection now lowers from `SectorMapAsync` rather than a whole authoritative daemon frame. Schematic HUD reads item catalog data and player formatting settings through its local `AetheriaClient`. | Done for direct manager-global read cleanup and whole-frame boot read removal. `SchematicDisplay.cs` was transcoded to UTF-8 before editing because it contained legacy non-UTF8 degree-symbol bytes. | -| Trade value settings projection | `Aetheria.State/Documents/AetheriaTradeValuePolicy.cs`; `Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeCatalogSnapshot.cs`; `Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeCatalogStore.cs`; `InventoryMenu.cs`; `InventoryPanel.cs`; `TradeMenu.cs`; `ZoneRenderer.cs` | Trade value projection is now authored typed state. The daemon seeds `global:aetheria.trade_value_policy.v1`, provider advertisements include `aetheria.trade_value_policy.v1`, and runtime catalog snapshots read that policy into `AetheriaRuntimeCatalogSnapshot.TradeValueSettings`. Unity panels/renderers consume it through explicit `AetheriaClient` catalog caches. The Unity `GameSettings` projection helper and `ActionGameManager.ObservedTradeValueSettings` are gone. | Done for manager, Unity-settings source, and code-only default cleanup. Next policy work is designer-facing Eve/CultUI authoring, not another Unity settings bridge. | -| Sector map reads | `Assets/Scripts/UI/Menu/SectorMap.cs`; `Assets/Scripts/UI/Menu/SectorRenderer.cs`; `Assets/Scripts/UI/MainMenu.cs` | Sector map renders from `AetheriaClient.SectorMapAsync`, emits clicked zone indices, and resolves details from `SectorMapAsync` plus daemon zone contents. Main-menu observed-game boot also uses `SectorMapAsync` for run id, frame id, tutorial mode, generation seed, zones, links, discovery, and faction relationships. It no longer reads `ActionGameManager.TryGetObservedGalaxy`, passes `GalaxyZone` objects through UI events, or reads whole daemon frames for Unity observed-galaxy boot. | Done for the current topology/detail/boot surface. Remaining work is richer faction/home/boss icon metadata as typed sector-map fields if designers still need those signals. | -| Frame lowering | `Assets/Scripts/Gameplay/AetheriaUnityObservedFrameApplier.cs`; `Assets/Scripts/Gameplay/AetheriaUnityObservedRunProjection.cs`; `Assets/Scripts/Gameplay/AetheriaUnityObservedZoneContextFactory.cs`; `Assets/Scripts/Gameplay/ActionGameManager.cs` | Frame handoff is now named as `ApplyLatestZoneRender`: it reads `ZoneRenderAsync()` and adapts only the target zone needed for the Unity presentation shell. The frame applier no longer receives the whole observed galaxy facade, the temporary projected-zone lookup lives beside the observed-run projection holder, and `ActionGameManager` no longer keeps an observed-galaxy property. The remaining whole-graph dependency is lazily resolved inside the quarantined `Zone` construction adapter because the legacy `Zone` constructor still requires it. | Keep narrowing toward typed projection/native render consumption. Persistent gameplay mirror belongs to Stage 8 demolition. | - -### 7D.5 Renderer/Input Shell Queue - -| Surface | Evidence | Current Shape | Stage 7D Action | -| --- | --- | --- | --- | -| Zone renderer frame application | `Assets/Scripts/Zone Display/ZoneRenderer.cs:220`, `:239` | `LoadDaemonZoneView` clears/rebuilds a Unity hierarchy; `ApplyZoneRender` updates render-time zone data, caches target/contact rows from `ZoneContactsAsync()`, loads presentation entities from `ObjectsViewportAsync(...)`, and discovers body views plus terrain sampling facts from `GravityViewportAsync(...)` for the current XY bounds. Unity no longer recomputes target, compass, visibility, presentation entity, visible body-view, terrain-height, terrain-band, asteroid-instance, dropped-pickup, entity-facade, orbit, or body facts from raw zone snapshot render queries. | Keep only as render cache. `ApplyZoneRender` is acceptable as per-frame projection consumption; `LoadDaemonZoneView` is now the remaining Stage 8 hierarchy rebuild shim once renderer data is native/projection-driven. | -| Entity instance cache | `ZoneRenderer.cs`; `AetheriaUnityObservedFacadeIndex.cs`; target presentation wiring in `AetheriaUnityGameplaySceneWiring.cs` | Unity keeps daemon-indexed presentation objects for rendering, docking, target HUD, and camera binding. Daemon-index entity lookup lives in the explicit facade index/scene wiring instead of an `ActionGameManager` shim. | Classify as temporary renderer shell. It must not become gameplay authority; replace with render/native views as Stage 8 proceeds. | -| SoA render fast path | `Assets/Scripts/Gameplay/AetheriaDaemonRenderBuffer.cs:50`, `AetheriaDaemonIndirectRenderer.cs:40`, `AetheriaDaemonObserver.LastRenderNativeView` | Reads observer SoA/native view for rendering. | Keep. This is aligned with thin renderer direction. Attach it to the 7D.2 facade instead of direct observer singleton lookup. | -| Ymir click/physics bridge | `Assets/Scripts/Gameplay/Physics/AetheriaYmirPhysicsBridge.cs` | Uses Ymir/clickable/hull mappings, not `UnityEngine.Physics` as simulation authority. | Keep as presentation/query bridge, then narrow behind facade/render shell. Any remaining Unity collider components are click/render affordances, not gameplay physics authority. | -| `GetComponent` and prefab collider hits | many source/prefab hits | Mostly Unity presentation wiring, object pooling, click affordances, and imported asset metadata. | Non-actionable for 7D unless the component participates in gameplay authority. Recheck during Stage 8 demolition. | - -### Non-Actionable Queue Hits - -| Hit | Evidence | Decision | -| --- | --- | --- | -| Event log queue | `Assets/Scripts/EventLog.cs:13` | UI log buffer; not a command bus. Keep. | -| Sector reveal queue | `Assets/Scripts/UI/Menu/SectorMap.cs:54`, `:79` | Presentation animation queue; not a gameplay command bus. Keep until the sector map projection is replaced. | -| KD-tree query queue | `Assets/Scripts/ServerShared/NIH/KDTree/KDQuery/*.cs` | Internal spatial query implementation. Keep unless replaced by CultMath/Ymir spatial structures in a later performance pass. | -| `System.Threading.Channels.dll` asmdef reference | `Packages/org.gamecult.aetheria.state/Runtime/GameCult.Aetheria.State.Unity.asmdef` | Assembly reference only; no ad hoc runtime channel found in scoped source. | -| `Physics.` source search | scoped C# search | No actionable `UnityEngine.Physics.*` simulation authority call found in `Assets/Scripts` or state runtime during this pass. | - -## Stage 7B Build Order - -Done: - -- public renderer API no longer exposes `window.aetheriaRts.command` or - `window.aetheriaRts.viewport`; -- Electron IPC no longer exposes `aetheria-rts:command` or - `aetheria-rts:viewport`; -- browser code now calls typed methods `setMoveVector`, `setTarget`, and - `mapViewport`; -- `aetheria-cultmesh.ts` is now transport-focused and no longer owns - MessagePack slot layout; -- local generated-style bindings in `aetheria-rts-bindings.ts` encode/decode - named slots for command documents, including the 30-slot daemon command - layout; -- `scripts/generate-rts-bindings.mjs` derives schema ids, command kind ids, and - slot maps from the C# MessagePack document declarations into - `aetheria-rts-generated-bindings.ts` for daemon command, daemon frame, daemon - health, game viewport payloads, nested daemon snapshot payloads, Verse - authority policy, authority rule, and authority lease; -- `AetheriaRuntimeGameViewportDocuments` owns typed viewport projection over local - daemon frame state, including controlled-unit visibility union, status, - inventory, and gravity influence intersections; -- daemon RUDP snapshot handling delegates game viewport construction to - `AetheriaRuntimeGameViewportDocuments` instead of carrying map projection logic - inline; -- `Aetheria.State.AuthoritySmoke` verifies the projection behavior against a - local daemon frame; -- Electron map reads now fetch daemon-authored managed viewport documents - through CultMesh query handles instead of rebuilding viewports locally from - the latest daemon frame; -- Electron selected-object, inventory/cargo, daemon health, and authority status - panels now decode typed daemon documents instead of deriving panel state - sideways from the map payload; -- Electron map/panel reads use the CultMesh document catalog and managed query - records instead of loopback `sendSnapshotRequest` or local publication reader - wrappers; -- the daemon publishes the Verse authority policy to a local `.authority.policy.cc` - sidecar so Electron can read authority status locally alongside frame and - health; -- `Aetheria.Rts.Web/scripts/verify-stage7c-local-runtime.ps1` starts a one-shot - daemon and proves the compiled Electron runtime facade can read map, - selected-object, inventory, health, and authority projections from local - CultCache publications; -- `Aetheria.Rts.Web/scripts/verify-stage7c-electron-shell.ps1` launches the real - Electron app shell against a disposable runtime directory and proves the - renderer can refresh through preload IPC using the typed projection facade; -- Electron preload/main expose typed projection channels: - `aetheria-rts:selected-object`, `aetheria-rts:inventory`, - `aetheria-rts:daemon-health`, and `aetheria-rts:authority-status`; -- `Aetheria.Rts.Web/scripts/verify-stage7b-rts-client.ps1` fails if the old - public generic API returns or if the transport wrapper regains document - layout/codec ownership, fails if stale remote viewport decoder helpers return, - and also fails if the generated bindings are stale. - -Remaining: - -1. Move Unity command/read paths onto the same typed local client shape proven by - Electron. -2. Add a separate peer sync health projection if daemon health stops being the - canonical peer health surface. -3. Replace the remaining semantic command construction arrays with a CultMesh - TS typed document writer once that primitive is available locally. -4. Narrow Electron preload/main IPC behind a typed runtime facade. -5. Keep the daemon local viewport handler only as temporary compatibility plumbing - until no client or diagnostic path depends on it. -6. Keep the verifier checking for old public generic surfaces and transport - wrapper layout leaks: - - `window.aetheriaRts.command`; - - `window.aetheriaRts.viewport`; - - `ipcMain.handle("aetheria-rts:command"`; - - `ipcMain.handle("aetheria-rts:viewport"`; - - `function createCommandDocument`; - - `decodeViewportDocument`; - - `viewportRecordKey`; - - public TS command union `{ kind: ... }` used as transport payload; - - hand-indexed MessagePack document layout in `aetheria-cultmesh.ts`. - -## Stage 7A Exit Check - -This inventory is the Stage 7A artifact. Stage 7A has exited. Electron's Stage -7B/7C proving surface now has generated binding metadata, typed command facade -methods, local map/panel projections, local CultCache publication reads, and -runtime/app-shell verifiers. The next use of this file is Stage 7D.1: refresh -the Unity entries into a concrete edit queue before changing Unity behavior. diff --git a/Aetheria.State/docs/stage-7-thin-client-staged-implementation-plan.md b/Aetheria.State/docs/stage-7-thin-client-staged-implementation-plan.md deleted file mode 100644 index 06d01fa71..000000000 --- a/Aetheria.State/docs/stage-7-thin-client-staged-implementation-plan.md +++ /dev/null @@ -1,1311 +0,0 @@ -# Stage 7 Thin Client Staged Implementation Plan - -This is the build map for turning Unity and Electron into thin CultMesh clients -without building Jenga. The broader Verse authority plan explains why the -architecture exists; this document explains the exact order to build it in. - -Current gate: Stage 7D.4. Stage 7C must be re-verified after any local runtime -script or daemon launch change, but no new architecture is allowed there unless -the verifier fails. - -Stage rule: a stage may consume only the artifacts listed as inputs, may create -only the artifacts listed as outputs, and must remove or quarantine its -demolition target before the next stage starts. - -## How To Use This Plan - -This is the migration runbook. When working on the thin-client split, pick the -first incomplete slice in the current stage and do only that slice. A slice is -complete only when its proof passes and its demolition target is gone, -quarantined, or explicitly carried forward by the next slice. - -Every implementation move must answer five questions before code changes: - -1. Which stage and slice does this advance? -2. Which existing surface is being replaced or narrowed? -3. Which typed document, projection, or facade owns the new shape? -4. Which verifier proves the old path did not grow back? -5. Which later slice is now unlocked? - -If an edit cannot answer those questions, it is not Stage 7 work. Put it in a -parking note or defer it. - -## Build Tracks - -Stage 7 has four tracks. Only the active track may create new runtime shape. - -| Track | Purpose | Current state | Owner | -| --- | --- | --- | --- | -| Contract | Define typed command and projection vocabulary shared by clients. | Electron slice exists; Unity parity next. | `AetheriaRuntimeVerseClient`, generated TS bindings, projection modules | -| Publication | Make daemon state visible as typed local state, not remote viewport fetches. | Electron frame/health/policy sidecars exist. | daemon publication store and local readers | -| Client shell | Bind UI/input/rendering to the typed facade. | Electron proven; Unity next. | Electron main/preload/client and Unity presentation shell | -| Demolition | Delete or quarantine legacy gameplay ownership. | Waiting for Unity parity and cross-runtime smoke. | Stage 8 only | - -Do not mix tracks inside one slice unless the slice explicitly says so. - -## Target Shape - -Every client runtime is a local Verse participant: - -1. The UI/runtime process owns input, rendering, and local presentation state. -2. The local Verse node owns typed document exchange with the local daemon and - peers. -3. The daemon owns simulation and publishes committed typed facts. -4. Reads are local projections over local Verse state. -5. Writes are typed command/fact documents. -6. No public client API accepts `command(kind, payload)`, raw document bags, or - viewport strings as gameplay semantics. - -The ergonomic layer for that shape must graduate into CultMesh/CultLib when it -is reusable. Aetheria may prove the API with domain sugar, but shared concepts -belong in the cross-runtime primitive layer: typed state pointers, typed -operation handles, typed query surfaces, projection recipes, authority claims, -reactive watches, and native slab descriptors. The current shared roadmap is -`E:/Projects/CultLib/src/GameCult.Mesh/docs/cross-runtime-primitives-roadmap.md`. - -Electron and Unity must converge on this shape: - -```text -UI input - -> typed client facade - -> typed command document - -> local Verse node - -> local daemon command gate - -> simulation - -> committed fact/frame documents - -> local Verse replica - -> typed local projection - -> renderer/UI panels -``` - -The map viewport is just one projection. Selected object state, inventory, -cargo, authority status, and peer health must follow the same pattern. - -Starbridge is the current Stage 7 mixed-authority pressure for this shape. One -RTS commander and one to four Unity pilots must share one Commander-finalized -Verse. The Commander player authors outside Pilot jurisdiction; Pilot daemons -publish candidates inside assigned jurisdictions; the Commander daemon owns -default simulation, correction/replay, and finality. - -The product source for that pressure is -`E:/Projects/AetheriaLore/Aetheria/Game Design/Aetheria Starbridge.md`. Stage 7 -does not own the whole release, but every client-parity slice should make one -of Starbridge's shared surfaces more portable: session/base facts, tactical map -projections, station/refit state, commander verbs, pilot field verbs, recovery, -waves, or episode/progression data. - -The staged Starbridge implementation slices live in -`Aetheria.State/docs/verse-authority-implementation-plan.md` under -`Starbridge Slice Map`. Treat that map as the product-facing build order for -new co-op verbs. This Stage 7 runbook remains the client-parity/demolition -runbook: it decides when Unity and Electron are thin enough to consume those -verbs without creating client-owned gameplay branches. - -Commander-facing Starbridge surfaces: - -- map, fog, wave, hostile, and base-system projections; -- infrastructure placement, power routing, fabrication, drone/turret orders, - station-stock, recovered-technology, construction-ghost, target-mark, and - commander support operations; -- authority diagnostics for which runtime owns each claim kind. - -Pilot-facing Starbridge surfaces: - -- responsive ship movement, local combat, salvage, docking, refit, anchoring, - cooling, repair, target-mark, survival-pod, cargo, equipment, and status - operations/projections; -- support-gear validity derived from equipped daemon state, not from client - role assumptions. - -None of those are allowed to become client-local gameplay branches. If the RTS -client can do it, Unity or another runtime must be able to inspect the same -typed state and issue the same typed operation when policy allows it. If Unity -can do it, the RTS client must be able to observe the committed result through -local Verse state rather than through a peer viewport. - -## Current Baseline - -Completed and usable migration evidence: - -- Stage 6 scoped two-daemon fact transport passes, but committed peer-fact - import is not the target Starbridge finality model. It must become Pilot - candidate transport plus Commander selection/replay. -- Electron public API exposes typed methods: `mapViewport`, `setMoveVector`, - and `setTarget`. -- Electron IPC exposes typed channels: - `aetheria-rts:map-viewport`, `aetheria-rts:set-move-vector`, and - `aetheria-rts:set-target`. -- `Aetheria.Rts.Web/Electron/aetheria-cultmesh.ts` is transport-focused. -- `Aetheria.Rts.Web/Electron/aetheria-rts-generated-bindings.ts` is generated - from C# `[Key]` declarations. -- `Aetheria.Rts.Web/Electron/aetheria-rts-bindings.ts` keeps ergonomic command - encoders and public TS request/response types. -- `Aetheria.Rts.Web/Electron/aetheria-cultmesh.ts` reads daemon-authored - managed documents through CultMesh document catalog/query handles. -- `Aetheria.Rts.Web/Electron/aetheria-rts-local-documents.ts` only decodes - those managed documents into TypeScript values. -- `AetheriaRuntimeGameViewportDocuments` owns the matching C# map projection and - the daemon delegates the temporary RUDP compatibility hook to it. -- `AetheriaRuntimeStarbridgeDocuments` owns the first S0 session-summary - projection, and `AetheriaClient.StarbridgeSessionSummaryAsync` exposes it as - a semantic managed read without routing through map viewport state. Scenario - and session facts now have latest-record keys in local Verse, so clients can - ask the client state surface for the active Starbridge summary without passing - documents around. -- The daemon boot path seeds the first Starbridge scenario/session facts from - daemon-owned local state. A fresh once-mode daemon publishes `Frontier - Fabricator Defense` as typed scenario/session documents, and the authority - smoke reads the resulting summary through `AetheriaClient` instead of Unity - scene authoring. -- Selected-object and inventory reads are now named projection documents rather - than hidden whole-zone viewport reads. The map viewport remains a map - projection; entity inspection and inventory panels have their own typed - facade results. -- Stage 7D.4/S1 has started splitting the map projection itself. Objects visible - to the union of controlled units and XY gravity influences now have separate - typed documents and facade reads: `ObjectsViewportAsync` and - `GravityViewportAsync`. `MapViewportAsync` remains as a compatibility - composition over those two projections while Unity/Electron callers migrate. -- The Electron renderer now asks for `objectsViewport` and `gravityViewport` - through preload IPC and composes its local draw model from those typed - projections. `mapViewport` remains available for compatibility and simple - diagnostics, but the app-shell path exercises the split projection contract. -- Unity's `MapRenderer` now refreshes its map header through the same - `ObjectsViewportAsync` and `GravityViewportAsync` facade reads instead of - reaching straight for the whole authoritative frame. This is only the first - Unity parity cut: the legacy scene renderer still consumes whole-zone daemon - snapshots until Stage 8 demolition replaces that shell. -- `Aetheria.State.AuthoritySmoke` verifies the C# projection. -- `npm run verify:stage7b` fails if public generic APIs return, if the transport - wrapper regains layout ownership, if stale viewport decoders return, or if the - generated binding metadata is stale. - -Remaining risk: - -- Electron command writes still use CultMesh RUDP document puts. That is - acceptable for the command lane, but replacing the semantic command arrays - with a typed TS document writer remains open. -- Unity does not yet consume the same thin client facade. -- Peer health is currently represented by daemon health; a divergent peer sync - health document can be added to the projection catalog when the daemon - publishes it. - -## Stage Graph - -```text -7B.0 inventory freeze - -> 7B.1 generated contract metadata - -> 7B.2 typed command facade - -> 7B.3 local map projection - -> 7B.4 local projection catalog - -> 7B.5 local replica read path - -> 7C Electron thin client - -> 7D Unity thin client parity - -> 7E cross-runtime co-op smoke - -> 8 Unity gameplay shell demolition -``` - -Do not skip 7B.5. If Unity parity is built on top of loopback viewport/frame -requests, the bridge becomes the new legacy shell. - -## 7B.0 Inventory Freeze - -Status: complete. - -Inputs: - -- Stage 6 scoped transport and the target Pilot-candidate/Commander-finality - contract. -- Existing Unity shell. -- Existing Electron RTS shell. - -Build: - -- Inventory every client mutation path. -- Inventory every gameplay read/projection path. -- Classify each path as keep, replace, or delete. - -Outputs: - -- `Aetheria.State/docs/stage-7-client-surface-inventory.md` - -Demolition target: - -- Unknown public command/read surfaces. - -Verifier: - -```powershell -rg -n "command\(|viewport\(|Apply\(command|CommandPort|CachedCommandPort|EventBus|Bus" Assets Packages Aetheria.Rts.Web -``` - -Every hit must be listed in the inventory before implementation continues. - -Stop line: - -- No new runtime abstraction can be added until this inventory names the surface - it replaces. - -## 7B.1 Generated Contract Metadata - -Status: complete for current command/frame/health/authority/snapshot slice. - -Inputs: - -- C# MessagePack document declarations in - `Packages/org.gamecult.aetheria.state/Runtime`. - -Build: - -- Generate TS schema ids, enum ids, and slot maps from C# `[Key]` - declarations. -- Treat generated metadata as read-only. -- Expand source coverage whenever a projection consumes a new nested document. - -Outputs: - -- `Aetheria.Rts.Web/scripts/generate-rts-bindings.mjs` -- `Aetheria.Rts.Web/Electron/aetheria-rts-generated-bindings.ts` - -Demolition target: - -- Hand-maintained TS copies of C# document layout. - -Verifier: - -```powershell -cd Aetheria.Rts.Web -npm run check:rts-bindings -``` - -Stop line: - -- No TS projection or command writer may index a MessagePack array without slot - metadata from this generator. - -## 7B.2 Typed Command Facade - -Status: complete for movement and target commands. - -Inputs: - -- Generated command metadata from 7B.1. -- Existing daemon typed command document. - -Build: - -- Expose typed TS request objects for command semantics. -- Keep command id, runtime id, issue time, and local session wiring inside the - client runtime wrapper. -- Keep UI components unaware of transport payload shape. - -Outputs: - -- `Aetheria.Rts.Web/Electron/aetheria-rts-bindings.ts` -- `Aetheria.Rts.Web/Electron/aetheria-cultmesh.ts` -- `Aetheria.Rts.Web/Electron/main.ts` -- `Aetheria.Rts.Web/Electron/preload.cjs` -- `Aetheria.Rts.Web/Client/app.ts` - -Demolition target: - -- Public `command(kind, payload)` and generic command IPC. - -Verifier: - -```powershell -cd Aetheria.Rts.Web -npm run verify:stage7b -``` - -Stop line: - -- Do not add station, inventory, or combat commands until the command catalog is - typed from UI to daemon gate. - -## 7B.3 Local Map Projection - -Status: complete for Electron map viewport shape; direct local replica reads -remain 7B.5. - -Inputs: - -- Generated daemon frame and nested snapshot metadata from 7B.1. -- Typed map request/response model from 7B.2. -- C# projection semantics in `AetheriaRuntimeGameViewportDocuments`. - -Build: - -- Fetch the latest local daemon frame document. -- Project the map viewport in TS from the frame document. -- Use controlled-unit visibility union for fog of war. -- Include objects, status, inventory, gravity influences, and body views. -- Keep remote game viewport document handling as daemon compatibility only. - -Outputs: - -- daemon-authored managed viewport/indexed documents; -- `Aetheria.Rts.Web/Electron/aetheria-rts-local-documents.ts` -- `Aetheria.Rts.Web/Electron/aetheria-cultmesh.ts` -- `Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeGameViewportDocuments.cs` -- `Aetheria.State.AuthoritySmoke/Program.cs` - -Demolition target: - -- Electron requesting `gamecult.aetheria.game_viewport.v1` from the daemon. -- Stale `decodeViewportDocument` and `viewportRecordKey` TS binding helpers. - -Verifier: - -```powershell -cd Aetheria.Rts.Web -npm run verify:stage7b -npm run build - -cd .. -dotnet run --project .\Aetheria.State.AuthoritySmoke\Aetheria.State.AuthoritySmoke.csproj -``` - -Stop line: - -- Do not add selected panels on top of the old viewport document. They must use - the projection catalog in 7B.4. - -## 7B.4 Local Projection Catalog - -Status: complete for Electron selected object, inventory/cargo, daemon health, -and authority status. - -Inputs: - -- Local frame projection module from 7B.3. -- Generated snapshot metadata from 7B.1. - -Build: - -- Add typed local projections for: - - selected object summary; - - selected object stats/status; - - equipment loadout; - - cargo inventory; - - peer sync health; - - authority policy/status. -- Keep projection functions pure over local frame/health/policy documents. -- Give Electron UI panels typed facade methods instead of reading map payloads - sideways. - -Outputs: - -- `Aetheria.Rts.Web/Electron/aetheria-rts-local-documents.ts` -- `Aetheria.Rts.Web/Electron/aetheria-rts-bindings.ts` -- `Aetheria.Rts.Web/Electron/aetheria-cultmesh.ts` -- `Aetheria.Rts.Web/Client/app.ts` -- `Aetheria.Rts.Web/Electron/main.ts` -- `Aetheria.Rts.Web/Electron/preload.cjs` -- optional C# parity projection smoke additions in - `Aetheria.State.AuthoritySmoke/Program.cs` - -Demolition target: - -- UI panels deriving selected-object details from ad hoc map object shape. -- Any new bespoke RTS gameplay behavior in the browser client. - -Verifier: - -```powershell -cd Aetheria.Rts.Web -npm run verify:stage7b -npm run build -``` - -Additional acceptance: - -- Selecting an owned pawn or station shows status and inventory from typed local - projections. -- The browser client still contains no simulation rules beyond presentation, - selection, and command intent creation. -- `npm run verify:stage7b` requires the typed projection symbols and IPC - channels. - -Stop line: - -- Do not move Unity onto the facade until 7B.5 replaces Electron's loopback - frame/health/policy fetches with local replica reads. - -## 7B.5 Local Replica Read Path - -Status: complete for Electron frame, health, and authority-policy reads. - -Inputs: - -- Projection catalog from 7B.4. -- CultMesh/CultCache local replica/runtime APIs. - -Build: - -- Replace loopback frame, health, and authority-policy fetches with direct local - CultCache publication reads. -- Keep the projection function signatures stable. -- Leave transport code responsible only for sync, not projection semantics. - -Outputs: - -- updated `AetheriaCultMeshClient` document catalog and managed query read - path; -- typed document decoding in - `Aetheria.Rts.Web/Electron/aetheria-rts-local-documents.ts`; -- verifier proving map reads no longer issue snapshot requests for local frame - data. -- authority policy sidecar publication through - `AetheriaRuntimeDaemonPublicationStore`. - -Demolition target: - -- Electron map/panel reads that depend on loopback daemon snapshot requests. -- Missing local authority policy publication. - -Verifier: - -```powershell -rg -n "sendSnapshotRequest|recordKeys|schemaIds" Aetheria.Rts.Web/Electron -cd Aetheria.Rts.Web -npm run verify:stage7b -npm run build -``` - -Allowed remaining hits must be sync/bootstrap plumbing outside the Electron -client runtime. `aetheria-cultmesh.ts` must not contain snapshot request code. - -Stop line: - -- Do not begin Unity parity until Electron launch proves the local publication - files exist and can drive map/panel projections. - -## 7C Electron Thin Client - -Status: complete for automated local-runtime and Electron app-shell smoke. - -Inputs: - -- Typed command facade from 7B.2. -- Local projection catalog from 7B.4. -- Local replica read path from 7B.5. - -Build: - -- Electron launches or attaches to the local daemon. -- Browser UI renders map, selected panels, health, and authority status from - typed facade methods. -- Browser UI submits only typed command intents. -- Enemy/pawn/station behavior remains daemon simulation, not RTS client code. - -Demolition target: - -- Browser-side gameplay simulation. -- Renderer access to raw CultMesh transport calls. - -Verifier: - -```powershell -cd Aetheria.Rts.Web -npm run build -npm run verify:stage7b -npm run verify:stage7c -npm run verify:stage7c:electron -``` - -Automated smoke: - -- builds the RTS renderer and Electron main process; -- starts the Aetheria daemon once with a disposable state file; -- reads frame, selected object, inventory, daemon health, and authority policy - through the compiled Electron runtime facade; -- fails if map/panel projections cannot be driven from local CultCache - publication files. - -Electron shell smoke: - -- launches the real Electron app with a disposable runtime directory; -- starts the daemon through the app's normal launch path; -- waits for the renderer to refresh through preload IPC; -- checks status, selected object details, equipment/cargo, daemon health, - authority policy, controlled list, and canvas sizing; -- exits non-zero if the app shell cannot drive the same typed projection path. - -Optional manual smoke: - -- Launch Electron. -- Select owned pawn. -- Observe status/inventory. -- Issue movement/target command. -- Confirm daemon frame changes and UI updates from local projection reads. - -Stop line: - -- Do not add RTS features that require browser-owned gameplay state. -- Unity parity may begin only by reusing the typed local client shape proven by - Electron. - -## 7D Unity Thin Client Parity - -Status: next. - -Inputs: - -- Electron-proven typed client facade. -- Unity shell inventory from 7B.0. - -Build principle: - -Unity does not get a bespoke bridge. Unity gets the same local-client shape -Electron proved: typed command submission, typed local projection reads, peer -health, and authority status. The Unity-specific code may adapt data into -render buffers and input gestures, but it must not define new gameplay -semantics. - -### 7D.0 Re-verify Electron Baseline - -Status: complete. - -Purpose: - -- Make sure Stage 7D starts from a working thin-client proof rather than a - remembered one. - -Allowed edits: - -- Verifier flake fixes only. -- No new runtime abstraction. - -Proof: - -```powershell -cd Aetheria.Rts.Web -npm run verify:stage7b -npm run verify:stage7c -npm run verify:stage7c:electron - -cd .. -dotnet run --project .\Aetheria.State.AuthoritySmoke\Aetheria.State.AuthoritySmoke.csproj -``` - -Demolition target: - -- Any Stage 7C verifier that can pass while the renderer uses generic - command/viewport APIs or loopback viewport snapshots. - -Unlocks: - -- 7D.1. - -### 7D.1 Unity Surface Triage - -Status: complete. - -Purpose: - -- Turn the Unity inventory into an edit queue with owners. This is not another - broad modeling pass; it is the last stop before code movement. - -Allowed edits: - -- Documentation and search harness updates. -- No runtime behavior changes except verifier-only assertions. - -Map these surfaces into keep/replace/delete: - -- `ActionGameManager` input methods; -- `AetheriaDaemonObserver` command and read paths; -- `AetheriaRuntimeDaemonOperationClient`; -- `AetheriaRuntimeDaemonSurfaceCommands`; -- `ZoneRenderer` zone/frame loading; -- Unity physics entry points: `Rigidbody`, `Collider`, `Physics.`; -- UI panel reads that go through manager singletons. - -Output: - -- Update `Aetheria.State/docs/stage-7-client-surface-inventory.md` with a - slice assignment for each still-actionable Unity hit: - `7D.2 facade`, `7D.3 commands`, `7D.4 reads`, `7D.5 renderer`, or - `8 demolition`. - -Proof: - -```powershell -rg -n "ActionGameManager|AetheriaDaemonObserver|AetheriaRuntimeDaemonOperationClient|AetheriaRuntimeDaemonSurfaceCommands|ZoneRenderer|Rigidbody|Collider|Physics\\.|SubmitDaemonCommand|Queue|Channel|ConcurrentQueue|Bus|CommandPort|Apply\\(" Assets Packages/org.gamecult.aetheria.state -``` - -Every actionable hit must have an owner in the inventory. - -Demolition target: - -- Unknown Unity gameplay ownership. - -Unlocks: - -- 7D.2. - -### 7D.2 Shared Client Facade Shape - -Status: complete for initial facade. Stage 7D.3 may now reroute Unity command -callers through this facade; Stage 7D.4 may add narrower projections as specific -read sites move. - -Purpose: - -- Give C#/Unity an ergonomic facade matching the Electron-facing shape without - copying Electron's IPC shell. - -Allowed edits: - -- Add or narrow C# facade types in - `Packages/org.gamecult.aetheria.state/Runtime`. -- Keep facade methods typed and named by game intent or projection: - `SetMoveVector`, `SetTarget`, `MapViewport`, `SelectedObject`, `Inventory`, - `DaemonHealth`, `AuthorityStatus`. -- The facade may wrap `AetheriaRuntimeVerseClient` and publication stores. - -Forbidden edits: - -- No `Apply(command, payload)`. -- No public `Command(string kind, object payload)`. -- No cached command port. -- No queue between input and the authority gate. -- No Unity-only command vocabulary. - -Output: - -- `AetheriaClient` in - `Packages/org.gamecult.aetheria.state/Runtime/AetheriaClient.cs`. -- The facade owns a long-lived `AetheriaRuntimeVerseClient`, exposes the typed - `AetheriaRuntimeDaemonOperationsClient`, and reads map viewport, selected - object, inventory, daemon health, authority policy, and SoA view data through - named methods. -- `AetheriaDaemonObserver` now resolves an `AetheriaClient` and exposes - `Client`/`Operations` from that shared facade instead of owning separate - operation and Verse clients. -- Unity package project/asmdef now includes the new facade/projection files and - the CultMath dependency required by render/object viewport queries. - -Proof: - -```powershell -rg -n "Apply\\(command|Command\\(string|payload|CachedCommandPort|CommandPort|Queue|Channel|ConcurrentQueue|EventBus|Bus" Assets Packages/org.gamecult.aetheria.state -dotnet run --project .\Aetheria.State.AuthoritySmoke\Aetheria.State.AuthoritySmoke.csproj -dotnet build .\GameCult.Aetheria.State.Unity.csproj -``` - -Proof status on 2026-06-24: authority smoke passed; Unity package build passed. -`Aetheria.State.Unity.Smoke` is not a clean 7D.2 verifier yet because the -default workspace state has a corrupt CultNet shard log, while a fresh state -copy lacks the expected typed Eve surface fixture. - -Demolition target: - -- Unity-facing generic command wrappers. - -Unlocks: - -- 7D.3 and 7D.4. - -### 7D.3 Unity Command Reroute - -Status: complete for the currently known Unity command ingress. - -Completed slice: - -- `ActionGameManager` no longer owns a cached runtime - `AetheriaRuntimeVerseClient` for boot/catalog/settings/loadout template - paths. -- Runtime catalog opening, player settings reads, loadout template reads, input - settings commands, and loadout template save/delete commands now route through - the shared `AetheriaClient` facade. -- `InputDisplayLayout` now owns an explicit `AetheriaClient` for input settings - changes and the old `ActionGameManager` input-settings ingress has been - removed. -- `MainMenu` daemon-frame reads, player-settings reads, Verse-host settings - reads, and known Eve surface command submission now route through - `AetheriaClient` instead of a menu-owned raw `AetheriaRuntimeVerseClient`. -- `AetheriaEveSurfacePresenter` now reads daemon Eve surfaces and submits Eve - surface commands through `AetheriaClient`. -- `AetheriaEveUnitySurfaceHost` uses the file-backed runtime state reader for - default state-ref resolution instead of constructing a raw Verse client. -- Low-level operation/Eve command compatibility fallbacks now open - `AetheriaClient`; `AetheriaRuntimeVerseClient` is confined to the facade - implementation, its own type, and tests. -- `AetheriaDaemonObserver` already exposes facade-backed typed operations, so - existing observer command calls reuse the shared local Verse client instead of - opening per-command runtime clients. -- Action-bar activation bindings now receive an explicit `AetheriaClient` when - restored, and submit consumable, behavior-active, and weapon-group-active - typed operations directly through the facade. The old public - `ActionGameManager.RequestActionBar...` activation shims are gone. -- `InventoryPanel` now owns an explicit `AetheriaClient` for the dropdown/current - ship slice: loadout restore, set docked current ship, entity rename, and hull - conductivity toggles submit typed daemon operations directly from the panel. - The matching `ActionGameManager` request shims were deleted; the manager only - exposes observed entity record-key resolution for the panel's Unity facade - entities. -- Inventory drag/drop and double-click transfers now submit typed - `TransferCargoItem`, `EquipItem`, and `StoreItem` operations directly from - `InventoryPanel`/`InventoryMenu` through explicit `AetheriaClient` instances. - `ActionGameManager` no longer exposes cargo/equipment transfer request shims; - it only resolves observed Unity facade cargo/equipment objects to daemon - record keys and indices. -- `TradeMenu` now submits typed `TradePurchase` operations through its own - `AetheriaClient` for station stock, commodity quantity purchases, cargo - purchases, and docked ship hull purchases. The old - `ActionGameManager.RequestTradePurchase` shim is gone. -- `InventoryMenu` now submits detailed equipped-item controls through its own - `AetheriaClient`: item override shutdown, thermotoggle target temperature, - weapon-group membership, action-bar binding, and action-bar clearing. The old - public manager request shims for those menu commands are gone; the manager - only resolves observed item identity and action-bar control paths. -- `InventoryPanel` now owns loadout-template save submission through - `AetheriaClient.SubmitLoadoutTemplateCommandAsync`; `ActionGameManager` only - exposes the temporary Unity-facade loadout projection helper. -- Remaining pilot input ingress in `ActionGameManager` now submits through a - shared typed facade operation helper: movement, look direction, tractor power, - targeting, reticle targeting, override shutdown, sensor ping, heatsinks, - shields, interact, tow, dock, and undock. The private `TryRequestDaemon...` - pilot command shims are gone; the manager remains the input gesture router - until Stage 7D.4/7D.5 replace its read/projection dependencies. - -Carried forward: - -- New Starbridge verbs may expose new command surfaces only as typed - daemon/state operations. The next command-expansion slice should cover - station-stock authoring/refit, construction anchoring, target marks, - support-gear cooling/repair, fabrication, drone/turret orders, and commander - support. If a verb is not yet represented in daemon/state code, build it there - first; do not hang it from Unity or Electron UI code. - -Purpose: - -- Move Unity input/UI mutations onto the typed local facade. - -Allowed edits: - -- Change `ActionGameManager` and UI callers so they create typed intent values - and call the shared facade. -- Keep runtime id, command id, issue time, and endpoint wiring inside the - facade/runtime wrapper. -- Leave old methods only as thin forwarding shims if removing them would - explode call-site churn; mark each shim for Stage 8 deletion in the - inventory. - -Forbidden edits: - -- Do not add gameplay logic to Unity to make rerouting easier. -- Do not make UI panels construct daemon command documents directly. - -Output: - -- Unity movement, target, loadout, and surface commands route through typed - local command submission. - -Proof: - -```powershell -rg -n "SubmitDaemonCommand|RestoreLoadout|SetTarget|SetMove|Apply\\(|commandKind|payload" Assets Packages/org.gamecult.aetheria.state -dotnet run --project .\Aetheria.State.AuthoritySmoke\Aetheria.State.AuthoritySmoke.csproj -``` - -Search hits must show either typed facade usage or an inventory-listed Stage 8 -shim. - -Demolition target: - -- Unity command paths that bypass the local daemon authority gate. - -Unlocks: - -- 7D.5 command input side. - -### 7D.4 Unity Projection Reroute - -Status: active. The first focused read reroute is complete for the map screen's -zone title and minimap-asteroid setting reads. - -Purpose: - -- Move Unity gameplay reads onto local typed projections/state. Unity can cache - render data, but the cache must be presentation-only. - -Allowed edits: - -- Read daemon frame, health, authority policy, render SoA, selected object, and - inventory through the shared C# client facade or existing typed state readers. -- Add projection helpers only if they are pure over local state/publications. -- Keep projection results portable enough for another runtime to ask for the - same semantic data. - -Forbidden edits: - -- No remote viewport as game truth. -- No persistent Unity hierarchy as game truth. -- No `ZoneRenderer.LoadZone` ownership of level contents. - -Output: - -- Unity read sites stop depending on manager singleton gameplay state where a - typed local projection exists. -- `ZoneRenderer` consumes per-frame projection/render data only. - -Completed slice: - -- `Assets/Scripts/UI/Menu/MapRenderer.cs` now owns an explicit `AetheriaClient` - and refreshes through `ObjectsViewportAsync` and `GravityViewportAsync` for - its current XY bounds, with minimap asteroid visibility still read from - `PlayerSettingsAsync`. Its unused `ActionGameManager` dependency was removed. -- `Assets/Scripts/UI/Menu/SectorRenderer.cs` now owns an explicit - `AetheriaClient` for the zone-details Eve surface. It reads daemon zone - facts from `ZoneDetailsAsync`, sector topology from `SectorMapAsync`, hull - type lookups from `OpenRuntimeCatalog`, and formatting settings from - `PlayerSettingsAsync` instead of asking `ActionGameManager` for zone - snapshots, runtime catalog, or player formatting. It also centers the sector - view from the typed - `CurrentZoneAsync` projection instead of carrying a serialized - `ActionGameManager` dependency for `TryGetObservedRunZone`. -- `gamecult.aetheria.sector_map.v1` now defines a portable sector topology - projection over local daemon frame state: current/entrance/exit zones, - discovered zones, zone XY positions, adjacency links, and faction index - markers. `AetheriaClient.SectorMapAsync` exposes this as the next replacement - for `SectorMap`'s legacy `ObservedGalaxy` graph reads. -- `Assets/Scripts/UI/Menu/SectorMap.cs` now renders from - `AetheriaClient.SectorMapAsync` and emits clicked zone indices instead of - `GalaxyZone` objects. `SectorRenderer` resolves its zone details from the - same typed sector projection plus daemon zone contents, so the sector menu no - longer calls `ActionGameManager.TryGetObservedGalaxy`. -- `Assets/Scripts/UI/MainMenu.cs` now boots the observed Unity galaxy from the - typed `SectorMapAsync` projection instead of `LatestAuthoritativeRunFrameAsync`. - The sector-map feed carries tutorial mode, generation seed, and faction - relationships so Unity no longer needs a whole daemon frame just to enter the - observer scene. -- `gamecult.aetheria.zone_render.v1` now names the transitional Unity zone - renderer feed. `AetheriaRuntimeGameViewportDocuments.ProjectZoneRender` owns the - projection, `AetheriaClient.ZoneRenderAsync` exposes it as a facade read, and - `ActionGameManager` now restores the Unity shell from - `AetheriaRuntimeZoneRenderDocument` rather than directly from a raw daemon - run snapshot. The feed no longer carries the whole run or current zone - snapshot into Unity; - it carries explicit run-level render facts such as current entity key, - credits, action-bar bindings, simulation time, render radius, adjacent-zone - summaries, body poses, asteroid-belt poses, asteroid-instance poses, dropped - pickups, entity facade rows, orbits, and bodies. `ZoneRenderer` no longer - derives render radius from a zone snapshot, stores simulation time inside it, - or recomputes render facts from it. This is still a Stage 8 shim because - Unity rebuilds a GameObject hierarchy from typed facade rows; the value is - making the boundary explicit and shared before replacing that payload with - native/projection slabs. -- `gamecult.aetheria.current_entity.v1` now names the portable current-subject - feed. `AetheriaRuntimeGameViewportDocuments.ProjectCurrentEntity` derives the active - entity from the typed run current entity key and returns entity identity, - status, inventory, equipment, and cargo without requiring callers to know the - entity index. `AetheriaClient.CurrentEntityAsync` exposes the feed for Unity - and Electron parity work. -- `Assets/Scripts/UI/Menu/TradeMenu.cs` now uses its explicit `AetheriaClient` - for trade item catalog/manufacturer lookups and player formatting settings. - Its spreadsheet behavior columns and trade item details Eve surface no longer - read `ActionGameManager.RuntimeCatalog` or `RuntimePlayerSettings`. -- `Assets/Scripts/UI/Menu/InventoryMenu.cs` now uses its explicit - `AetheriaClient` for item catalog/manufacturer lookups and player formatting - settings. Ship settings, cargo item details, equipped item details, and typed - shape-cell lookups no longer read `ActionGameManager.RuntimeCatalog` or - `RuntimePlayerSettings`. -- `Assets/Scripts/UI/Menu/InventoryPanel.cs` now uses its explicit - `AetheriaClient` for loadout restore pricing, hull/item shape and durability - lookups, hardpoint color typing, and temperature label formatting. The panel - no longer reads `ActionGameManager.RuntimeCatalog` or - `RuntimePlayerSettings` for those surfaces. -- `Assets/Scripts/Gameplay/ActionBarSlot.cs` now uses the binding's explicit - `AetheriaClient` for gear action-bar icon catalog lookup instead of reading - `ActionGameManager.RuntimeCatalog`. -- `Assets/Scripts/Zone Display/ZoneRenderer.cs`, - `Assets/Scripts/Gameplay/EntityInstance.cs`, and - `Assets/Scripts/Gameplay/ShipInstance.cs` now resolve hull, pickup, and - durability catalog data through a `ZoneRenderer`-owned `AetheriaClient` - catalog cache instead of `ActionGameManager.RuntimeCatalog`. -- `Assets/Scripts/UI/InputScreen/InputDisplayLayout.cs` now reads action-bar - input visibility from `AetheriaClient.PlayerSettingsAsync()` instead of - `ActionGameManager.RuntimePlayerSettings`. -- `Assets/Scripts/Zone Display/VolumeCloudRenderer.cs` now reads nebula quality - from `AetheriaClient.PlayerSettingsAsync()` instead of - `ActionGameManager.RuntimePlayerSettings`, while preserving the serialized - renderer quality as fallback if local state is unavailable. -- `Assets/Scripts/UI/MainMenu.cs` now reads the runtime catalog through its - local `AetheriaClient` when bootstrapping the legacy observed-galaxy - projection. This does not make the observed-galaxy projection a desired final - shape; it removes the direct manager-global catalog dependency while that - projection remains quarantined. -- `Assets/Scripts/UI/HUD/SchematicDisplay.cs` now reads item catalog data and - player formatting settings through a local `AetheriaClient`. The direct - `ActionGameManager.RuntimeCatalog` and `RuntimePlayerSettings` reads are gone - from the Unity codebase. -- `global:aetheria.trade_value_policy.v1` now owns the authored typed trade - value policy used by inventory, trade, loadout pricing, and pickup rendering. - `AetheriaRuntimeCatalogSnapshot.TradeValueSettings` reads that policy through - the runtime catalog store. The old `ActionGameManager.ObservedTradeValueSettings` - service-locator method and Unity `AetheriaUnityProjectionSettings` helper have - both been deleted. -- Inventory and trade docking/refit affordances now validate Unity facade cargo - objects against `gamecult.aetheria.current_docking.v1` before presenting or - selecting the current docking bay. `InventoryMenu`, `InventoryPanel`, and - `TradeMenu` all read `CurrentDockingAsync`. `InventoryPanel` loadout restore - targets and `TradeMenu` docked-ship purchase targets now resolve from - `DockParentEntityKey` instead of `ActionGameManager.DockedEntity`. -- `gamecult.aetheria.station_refit.v1` now owns the typed station/refit selector - model for currently docked play: dock parent, docking bay, available docked - entities, player-ship flags, cargo bay counts, and hull item keys. - `InventoryPanel` and `TradeMenu` build their entity/cargo selector options - from `AetheriaClient.StationRefitAsync`; `TradeMenu` also derives available - ship purchase counts from the same typed projection. Their remaining Unity - facade lookups are key-validated adapters used only to call existing - `Display(...)` methods. -- `ActionGameManager.ObservedAvailableEntities()` has been deleted. Inventory - dropdown selection now resolves a single typed station/refit entity key - through `TryResolveObservedEntityFacadeByRecordKey`, keeping the Unity facade - bridge key-scoped while the old `Display(Entity)` UI remains a Stage 8 shim. -- Inventory menu/panel docking-bay display now reads - `gamecult.aetheria.current_docking.v1` first and adapts the Unity docking bay - facade by `DockParentEntityKey` plus `DockingBayIndex` through - `TryResolveObservedDockingBayFacadeByRecordKey`. The menu code no longer asks - `ActionGameManager` for a broad current docking bay before validation. -- `gamecult.aetheria.station_refit.v1` now also publishes typed docking-bay - rows: slot identity, occupied entity key/name/hull, current-entity match, and - bay cargo items. `InventoryPanel` and `InventoryMenu` must resolve the current - `AetheriaRuntimeStationDockingBayRow` before adapting the legacy Unity docking - bay facade for display, so station/refit truth lives in the daemon projection - instead of the facade graph. -- `gamecult.aetheria.station_refit.v1` now publishes loadout restore options - with template identity, daemon target entity key, shared trade-policy price, - and restore eligibility. `InventoryPanel` no longer enumerates loadout - templates or computes restore prices locally; it renders and submits the typed - row that came from `StationRefitAsync`. -- `gamecult.aetheria.station_refit.v1` now publishes trade cargo target rows for - the current docking bay and each player ship cargo bay, including the target - label, entity key, bay index, hull key, and cargo items. `TradeMenu` no longer - reconstructs cargo selector targets from available entity options or reads the - docking bay's cargo from station stock; target counts use the explicit row. -- `gamecult.aetheria.station_refit.v1` now enriches station stock rows with - shared-policy price, affordability, and owned quantity. `TradeMenu` renders - ownership from `OwnedQuantity`; it no longer counts player ships or target - cargo locally. -- Inventory menu current-entity display now reads - `gamecult.aetheria.current_entity.v1` first and adapts the Unity entity facade - by daemon record key through `TryResolveObservedEntityFacadeByRecordKey`. The - menu code no longer asks `ActionGameManager` for a broad current entity before - validation. -- `gamecult.aetheria.current_entity.v1` now carries the current entity shutdown - threshold. The inventory ship-settings Eve surface renders and updates that - typed fact by daemon entity key instead of storing a Unity `Entity` facade as - settings authority. -- `gamecult.aetheria.current_entity.v1` now carries typed player HUD status: - override shutdown, shield activity, heatsinks, heat exposure, visibility, - hull ratio, radiator range, sensor cooldown, reactor draw, capacitor charge, - and Aether-drive RPM. `SchematicDisplay` reads those facts from - `AetheriaClient.CurrentEntityAsync()` for the player schematic instead of - reading those gameplay facts from the Unity `Entity` facade. -- Shared RTS/current-entity inventory rows now publish `SourceIndex`, `X`, and - `Y` beside the source kind and item key. Cargo and equipment operations can - validate against typed projection slot identity instead of searching Unity - facade graphs for the command target. -- `InventoryPanel` and `InventoryMenu` now validate cargo/equipment submissions - against those typed inventory rows before sending daemon operations. The - remaining Unity facade adapter only supplies the presentation object's entity - key/source index, and cargo submissions now include the origin grid cell - instead of sentinel coordinates. -- `ActionGameManager` no longer exposes cargo/equipment command-target adapter - methods. Inventory UI derives the presentation object's owner/index locally, - validates that identity against typed projection rows, and only then submits - through `AetheriaClient.Operations`. -- `LocalMenu` no longer asks `ActionGameManager` for docked local-story state. - It reads `gamecult.aetheria.current_docking.v1`, adapts the story-bearing - docking parent by typed parent key and bay index, and then renders the shared - local-story Eve surface. -- `ActionGameManager.TryGetObservedCurrentEntity` and - `TryGetObservedDockingBay` have been deleted. Current subject and docking - state must be read through typed projections first; Unity facade access is - limited to key-scoped adapters. -- `ActionGameManager.ObservedLoadoutTemplates` and its preload cache have been - deleted. Loadout templates are read through `AetheriaClient.LoadoutTemplatesAsync` - by the UI that needs them instead of being copied into manager-global state. -- `ActionGameManager.TryGetObservedGalaxy`, `TryGetObservedZoneSnapshot`, and - `TryGetObservedRunZone` have been deleted as public read shims. Sector/map - callers must use typed projections; the remaining observed galaxy object is a - quarantined scene bootstrap adapter and Stage 8 renderer adapter, not a - portable client API. -- `AetheriaUnityObservedFrameApplier` now consumes - `AetheriaClient.ZoneRenderAsync()` for the per-frame Unity shell handoff - instead of rebuilding `gamecult.aetheria.zone_render.v1` from - `LastObservedState.Frame`. Its current-zone lookup also reads the typed - zone-render feed, and the applier no longer receives the whole observed - galaxy facade just to apply a frame. The temporary projected-zone lookup now - lives beside `AetheriaUnityObservedRunProjection`, and `ActionGameManager` - no longer keeps an observed-galaxy property. The remaining whole-graph - dependency is lazily resolved inside the quarantined Unity `Zone` - construction adapter until Stage 8 replaces the persistent scene graph with - projection/native render consumption. The handoff is named - `ApplyLatestZoneRender` so Unity code no longer describes this path as whole - authoritative daemon frame application. -- `gamecult.aetheria.zone_details.v1` now owns the portable sector-zone details - feed used by Unity's sector details panel: zone identity, mass, radius, body - kinds, entity hull item keys, and contents availability. `SectorRenderer` - reads it through `AetheriaClient.ZoneDetailsAsync(zoneIndex)` instead of - pulling `LatestAuthoritativeRunFrameAsync()` and searching the whole run for - a raw `AetheriaRuntimeZoneSnapshotCommit`. -- `gamecult.aetheria.zone_contacts.v1` now owns the portable current-zone - target/contact feed used by Unity target presentation. `AetheriaUnityObservedTargetQuery` - reads it through `AetheriaClient.ZoneContactsAsync()` and - `AetheriaUnityTargetPresentation` consumes typed contact rows instead of - asking raw zone snapshots or `AetheriaRuntimeDaemonRenderQueries` for target - facts. -- `gamecult.aetheria.zone_contacts.v1` also carries target/contact position, - delta, and distance facts. `ZoneRenderer.TryGetDaemonTargetDistance` now - refreshes a typed target-row cache from `ZoneContactsAsync()` instead of - querying `_daemonZoneSnapshot` through `TryQueryEntityTarget`, so weapon - convergence no longer depends on a raw snapshot read. `ZoneRenderer` also - derives minimap compass markers and visible-entity fade state from cached - typed contact rows instead of `QueryCompassMarkers` or - `QueryVisibleEntityIndices`, and it loads presentation entities through - `ObjectsViewportAsync(...)` for the current XY bounds instead of - `QueryPresentationEntityIndices`. `GravityViewportAsync(...)` now also - carries viewport-scoped body rows with the body payload Unity needs for - prefab/material lowering, so `ZoneRenderer` no longer calls `QueryBodyViews` - over the raw zone snapshot. The gravity viewport also carries the terrain - radius/depth/exponent/wave-frequency scalars used by the local render sampler, - so terrain heights and minimap gravity bands no longer ask raw zone snapshots. -- `ActionGameManager.DockedEntity` and `DockingBay` have been removed as public - properties. Docking presentation now sets the renderer perspective directly, - while menu/refit/local-story callers read typed current-docking state and use - only key-scoped facade adapters when they still need Unity objects. -- Docked current-entity binding now reads `CurrentDockingAsync()` through - `AetheriaUnityObservedDockingIndex` instead of scanning the current zone - snapshot for docking-bay parent relationships. The dock camera receives body - look-at context from the typed `ZoneRenderAsync()` body-pose feed, so docked - presentation no longer accepts a raw `AetheriaRuntimeZoneSnapshotCommit`. -- `ActionGameManager.TowingStation` is now private renderer/input context, and - the unused `AvailableCargoBays` public enumeration has been deleted. Tow - requests still lower through typed daemon operations; available cargo choices - belong to typed station/refit projections, not manager-local enumerators. -- `ActionGameManager.Credits` has been deleted. Credits are projected through - typed client state such as `StationRefitAsync`; Unity must not mirror run - currency into manager-global state. -- `ActionGameManager.RuntimeCatalog` is now a private boot cache for - manager-internal adapter construction only. Client UI must open typed catalog - views through `AetheriaClient`, not through manager-global Unity state. -- `ActionGameManager` no longer keeps an `ObservedGalaxy` property. The - remaining galaxy projection lives in `AetheriaUnityObservedRunProjection`, - is populated from typed `SectorMapAsync` state, and exists only as a - quarantined renderer/legacy `Zone` construction adapter. Portable clients - must use typed daemon projections rather than raw Unity galaxy state. -- `ActionGameManager.IsTutorial` has been deleted. Tutorial/run mode is read - from typed daemon run/frame projections at the call site instead of mirrored - into public manager-global Unity state. -- `ActionGameManager.CurrentEnvironment` has been deleted. The volume renderer - reads its own presentation settings directly; environment visuals must not be - exposed as manager-global gameplay state. -- Save-loadout projection now runs through - `AetheriaClient.LoadoutTemplateAsync(entityKey)` and - `AetheriaRuntimeLoadoutSnapshotProjector`. Unity submits the typed template - command after resolving the displayed facade to a daemon entity key; it no - longer serializes the Unity `Entity` facade as the source of save truth. -- `ActionGameManager.DragObject` and `HasDragTarget` are no longer public. - Inventory drag/drop uses `TryGetDraggedItem` and `EndDrag`'s consumed result - instead of peeking raw manager gesture state. -- Raw inventory drag gesture state now lives in `AetheriaUnityDragSession`. - `ActionGameManager` keeps only a narrow facade for scene UI callbacks instead - of owning the drag object and target callback fields directly. -- `ActionGameManager.RuntimePlayerSettings` is now private boot/input context. - UI and render surfaces must read player settings through their own - `AetheriaClient` projections instead of the gameplay manager. -- `ActionGameManager.GameDataDirectory` and `RuntimeStateFilePath` have moved - to `AetheriaUnityRuntimePaths`. Unity runtime path discovery is boot plumbing, - not gameplay-manager state. -- `AetheriaRuntimeTradeValuePolicySurfaceBuilder` now exposes the authored - `aetheria.trade_value_policy.v1` state as a provider-owned Eve/CultUI - designer surface. The daemon editor publishes it beside the stat recipe - surface, so trade value, loadout pricing, station stock, and future RTS - economy tooling inspect the same typed policy instead of reviving Unity - settings as gameplay truth. -- The trade value policy designer surface now has typed Eve/CultUI edit - commands for the quality price curve and rarity tier thresholds. The command - body is `AetheriaRuntimeTradeValuePolicyCommandBody`, and the daemon bridge - persists edits through `AetheriaTradeValuePolicy` instead of a string payload - or Unity settings object. - -Next slice: - -- Continue with station/refit read surfaces that still depend on the legacy - Unity facade graph rather than manager-global catalog/settings. The direct - `ActionGameManager.RuntimeCatalog` and `RuntimePlayerSettings` cleanup is - complete, trade/pricing policy is now authored and edited as daemon state, - and the remaining 7D.4 work is replacing station stock, cargo, inventory, and - loadout reads with portable typed projections. - -Active 7D.4 implementation queue: - -This queue is ordered. Do not pick a later row because it is easier. Each row -must either delete its legacy read path or name the exact Stage 8 shim that -still owns the remaining Unity object adaptation. Starbridge-facing reads that -appear in Unity must become named `AetheriaClient` facade reads before they are -allowed to become Electron/RTS features. - -| Order | Slice | Build | Delete or quarantine | Proof | -| --- | --- | --- | --- | --- | -| 0 | Policy/editor typed state | Trade value policy is authored and edited through `aetheria.trade_value_policy.v1`, `AetheriaRuntimeTradeValuePolicySurfaceBuilder`, typed Eve command bodies, and `AetheriaEveCommandBridge` persistence. | No Unity `GameSettings` or public string command/payload path may own pricing policy. | Unity compile, `verify-stage7d-unity-parity.ps1`, and `Aetheria.State.Verify` require typed commands, editable controls, and `PutTradeValuePolicyAsync`. | -| 1 | Sector topology | `SectorMap`, `SectorRenderer`, and main-menu observed-game boot consume `SectorMapAsync`, `CurrentZoneAsync`, daemon zone contents, catalog, and player settings through explicit clients. `SectorMapAsync` carries tutorial mode, generation seed, and faction relationships so Unity can lower the temporary observed galaxy without a whole daemon frame. | `SectorMap` no longer reads `ActionGameManager.TryGetObservedGalaxy`, passes `GalaxyZone`, or owns a legacy faction graph. `MainMenu` may not call `LatestAuthoritativeRunFrameAsync` for observed-game boot. | `verify-stage7d-unity-parity.ps1`; scoped search for `TryGetObservedGalaxy`, `GalaxyZone`, `Dictionary