Restore daemon-backed Unity launch

This commit is contained in:
Meta Vens 2026-08-07 21:10:44 +01:00
commit 023a35944c
28 changed files with 130 additions and 93 deletions

View file

@ -1,6 +1,7 @@
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;
@ -1277,7 +1278,6 @@ static async Task ProveDirectSoaPublicationPipeline()
}
};
using var liveBodies = new CultMeshNetworkBodyStore();
using var publisher = new AetheriaRuntimeDaemonSoaFramePublisher(producerEpoch: 9);
var built = publisher.BuildCurrentZoneEntities(frame);
var payloadIdentity = built.View.Identities.SingleOrDefault(identity =>
@ -1301,7 +1301,7 @@ static async Task ProveDirectSoaPublicationPipeline()
if (cache.Get<EveEntitySoaViewDocument>(AetheriaRuntimeVerseRecordKeys.EveEntitySoaViewLatest) != null)
throw new InvalidOperationException("SoA view became visible before its body publication.");
var published = await publisher.PublishAsync(built);
var published = await publisher.PublishAsync(built, includeRealtimePayload: true);
if (cache.Get<EveEntitySoaViewDocument>(AetheriaRuntimeVerseRecordKeys.EveEntitySoaViewLatest) != null)
throw new InvalidOperationException("Building a CultMesh body publication made the Eve view visible before commit.");
if (cache.GetAll<CultMeshCdnArtifactManifest>().Any() || cache.GetAll<CultMeshCdnArtifactChunk>().Any())
@ -1353,11 +1353,10 @@ static async Task ProveDirectSoaPublicationPipeline()
?? throw new InvalidOperationException("Typed Eve SoA view did not become visible after body publication.");
var localRepresentation = body.Representations.SingleOrDefault(candidate =>
candidate.TransportKind == CultMeshBodyTransportKind.SharedMemory);
var networkRepresentation = body.Representations.SingleOrDefault(candidate =>
candidate.TransportKind == CultMeshBodyTransportKind.Network);
if (localRepresentation == null || networkRepresentation == null ||
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 direct network representations.");
throw new InvalidOperationException("Direct SoA publication did not expose shared memory plus an explicit realtime payload.");
var request = new CultMeshBodyValidationRequest
{
@ -1373,34 +1372,25 @@ static async Task ProveDirectSoaPublicationPipeline()
using var localLease = new CultMeshBodyPublicationResolver(new CultMeshBodyTransportService(
new ICultMeshBodyTransportAdapter[]
{
new CultMeshSharedMemoryBodyAdapter(),
new CultMeshNetworkBodyAdapter(descriptor => ReadLiveBody(liveBodies, descriptor))
},
(producerId, _) => producerId == AetheriaRuntimeDaemonSoaFramePublisher.ProducerId))
.ResolveReadOnly(body, request);
using var networkLease = new CultMeshBodyPublicationResolver(new CultMeshBodyTransportService(
new ICultMeshBodyTransportAdapter[]
{
new CultMeshNetworkBodyAdapter(descriptor => ReadLiveBody(liveBodies, descriptor))
new CultMeshSharedMemoryBodyAdapter()
},
(producerId, _) => producerId == AetheriaRuntimeDaemonSoaFramePublisher.ProducerId))
.ResolveReadOnly(body, request);
var localEntityIndex = ReadInt32(view, localLease, "entity.index", 0);
var networkEntityIndex = ReadInt32(view, networkLease, "entity.index", 0);
var networkEntityIndex = ReadInt32Bytes(view, published.RealtimePayload.Span, "entity.index", 0);
var localPosition = ReadFloat3(view, localLease, "transform.position", 0);
var networkPosition = ReadFloat3(view, networkLease, "transform.position", 0);
var networkPosition = ReadFloat3Bytes(view, published.RealtimePayload.Span, "transform.position", 0);
var localPayloadEntityIndex = ReadInt32(view, localLease, "entity.index", 2);
var networkPayloadEntityIndex = ReadInt32(view, networkLease, "entity.index", 2);
var networkPayloadEntityIndex = ReadInt32Bytes(view, published.RealtimePayload.Span, "entity.index", 2);
var localPayloadPosition = ReadFloat3(view, localLease, "transform.position", 2);
var networkPayloadPosition = ReadFloat3(view, networkLease, "transform.position", 2);
var networkPayloadPosition = ReadFloat3Bytes(view, published.RealtimePayload.Span, "transform.position", 2);
var localSunPosition = ReadFloat3(view, localLease, "transform.position", 3);
var networkSunPosition = ReadFloat3(view, networkLease, "transform.position", 3);
var networkSunPosition = ReadFloat3Bytes(view, published.RealtimePayload.Span, "transform.position", 3);
var localAsteroidPosition = ReadFloat3(view, localLease, "transform.position", 4);
var networkAsteroidPosition = ReadFloat3(view, networkLease, "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 ||
networkLease.TransportKind != CultMeshBodyTransportKind.Network ||
localEntityIndex != 12 || networkEntityIndex != localEntityIndex ||
localPosition != (1.25f, -2.5f, 7.75f) || networkPosition != localPosition ||
localPayloadEntityIndex != payloadIdentity.EntityIndex ||
@ -1410,7 +1400,7 @@ static async Task ProveDirectSoaPublicationPipeline()
localAsteroidPosition.X != 45f || localAsteroidPosition.Z != 50f ||
networkAsteroidPosition != localAsteroidPosition ||
identity.EntityId != "ship:soa-witness" || identity.Label != "SoA Witness")
throw new InvalidOperationException("Local and network SoA views were not logically equivalent.");
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);
@ -1419,25 +1409,6 @@ static async Task ProveDirectSoaPublicationPipeline()
throw new InvalidOperationException("Inactive physical payload remained in SoA or disturbed a surviving synthetic identity.");
}
static byte[] ReadLiveBody(CultMeshNetworkBodyStore bodies, CultMeshBodyDescriptor descriptor)
{
var request = new CultMeshBodyReadRequestMessage
{
MessageId = Guid.NewGuid().ToString("N"),
CapabilityToken = descriptor.CapabilityToken,
BodyId = descriptor.BodyId,
BodySchemaId = descriptor.SchemaId,
LayoutVersion = descriptor.LayoutVersion,
ProducerEpoch = descriptor.ProducerEpoch,
Sequence = descriptor.Sequence,
ExpectedSizeBytes = descriptor.ByteSize,
SemanticHash = descriptor.SemanticHash
};
if (!bodies.TryRead(request, DateTimeOffset.UtcNow, out _, out var body))
throw new InvalidOperationException("Ephemeral SoA network generation could not be resolved.");
return body;
}
static IEnumerable<AetheriaRuntimeSurfaceComponent> Flatten(AetheriaRuntimeSurfaceComponent component)
{
yield return component;
@ -1453,6 +1424,14 @@ static int ReadInt32(EveEntitySoaViewDocument view, ICultMeshBodyReadLease lease
return lease.ReadInt32(buffer.ByteOffset + column.ByteOffset + (long)row * column.ElementStride);
}
static int ReadInt32Bytes(EveEntitySoaViewDocument view, ReadOnlySpan<byte> 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,
@ -1464,3 +1443,18 @@ static (float X, float Y, float Z) ReadFloat3(
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<byte> 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)))));
}

View file

@ -1,6 +1,5 @@
{
"dependencies": {
"com.gamecult.brokkr": "file:E:/Projects/Brokkr/surfaces/unity/Packages/com.gamecult.brokkr",
"com.unity.ide.rider": "3.0.39",
"com.unity.test-framework": "1.6.0",
"com.unity.modules.assetbundle": "1.0.0",

View file

@ -1,13 +1,5 @@
{
"dependencies": {
"com.gamecult.brokkr": {
"version": "file:E:/Projects/Brokkr/surfaces/unity/Packages/com.gamecult.brokkr",
"depth": 0,
"source": "local",
"dependencies": {
"org.gamecult.cultlib": "1.0.16"
}
},
"com.unity.burst": {
"version": "1.8.29",
"depth": 2,

View file

@ -36,8 +36,11 @@ namespace Aetheria.Editor
foreach (var stale in Directory.GetFiles(output, "*", SearchOption.TopDirectoryOnly))
File.Delete(stale);
var catalog = AetheriaRuntimeCatalogStore.OpenReadOnly(
Path.GetFullPath(Path.Combine("GameData", "aetheria-world.cc")));
var configuredCatalog = Environment.GetEnvironmentVariable("AETHERIA_ASSET_CATALOG_STATE");
var catalogPath = string.IsNullOrWhiteSpace(configuredCatalog)
? Path.GetFullPath(Path.Combine("GameData", "aetheria-world.cc"))
: Path.GetFullPath(configuredCatalog);
var catalog = AetheriaRuntimeCatalogStore.OpenReadOnly(catalogPath);
var assets = AetheriaRuntimeAssets.ProjectManifest(catalog).Assets;
foreach (var entry in assets.Where(entry =>
string.Equals(entry.Ref.Kind, AetheriaRuntimeAssetKinds.Prefab, StringComparison.Ordinal)))

View file

@ -1,6 +1,5 @@
{
"dependencies": {
"com.gamecult.brokkr": "file:../../Brokkr/surfaces/unity/Packages/com.gamecult.brokkr/",
"com.unity.2d.sprite": "1.0.0",
"com.unity.2d.tilemap": "1.0.0",
"com.unity.ai.navigation": "2.0.12",
@ -20,7 +19,7 @@
"com.unity.xr.legacyinputhelpers": "3.0.1",
"org.gamecult.aetheria.eve-runtime": "file:org.gamecult.aetheria.eve-runtime",
"org.gamecult.aetheria.state": "file:org.gamecult.aetheria.state",
"org.gamecult.cultmath": "https://github.com/GameCult/CultMath.git?path=/unity/org.gamecult.cultmath#cultmath-unity-v0.1.1",
"org.gamecult.cultmath": "https://github.com/GameCult/CultMath.git?path=/unity/org.gamecult.cultmath#cultmath-unity-v0.1.2",
"org.gamecult.cultlib": "https://github.com/GameCult/CultLib.git?path=/unity/org.gamecult.cultlib#cultlib-unity-v1.0.43",
"org.gamecult.eve.surface": "file:../../EveUnity/packages/org.gamecult.eve.surface",
"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-v0.2.3",

View file

@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 5f127e58b7fe83d41aae3d2c07e92b93

View file

@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: c014edb10e374064983c53c270e56b25

View file

@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 65b37579114393648b8c881ae488399b

View file

@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: aeffd152980489d42a76727ed45cadcb

View file

@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 479d0ef759b333b4dae8052bad70eca7

View file

@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 7a04293019e69804188e0fd89165928e

View file

@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 56b9ad6cb6412fb43a3bca09816f3c1e

View file

@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: d6d7dacec60ca5541b27d4edc3e71b67

View file

@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 59cfb14aacc657e42840ff07db266e6d

View file

@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: efde765b290564a448c25d1d21635889

View file

@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: e4f402f9be2534446ae0532a197acd5c

View file

@ -1,2 +0,0 @@
fileFormatVersion: 2
guid: 5437add74c5d2fb45b81299b41aa999d

View file

@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: aa7945f476afeed42887ab43de504d89

View file

@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 7250563f73fb3f0489c35f3bac88a8ce

View file

@ -256,6 +256,9 @@ namespace GameCult.Aetheria.State.Verse
var fullPath = Path.GetFullPath(statePath);
var effectiveRuntimeId = string.IsNullOrWhiteSpace(runtimeId) ? DefaultRuntimeId : runtimeId;
var registry = AetheriaRuntimeVerseContractRegistry.CreateCultCacheRegistry();
var runtimeSchemaIds = new HashSet<string>(
registry.AllDescriptors.Select(descriptor => descriptor.SchemaId),
StringComparer.Ordinal);
var node = await CultMesh.CreateNodeAsync(
fullPath,
new CultMeshNodeOptions
@ -267,7 +270,12 @@ namespace GameCult.Aetheria.State.Verse
Registry = registry,
PullOnOpen = pullOnOpen,
StoreFlushOnDispose = true,
UseDirectoryStore = true
UseDirectoryStore = true,
// A thin runtime shares the daemon's physical CultCache, but it does not
// own or understand catalog, migration, Hangar, or other daemon schemas.
// Hydrate only the contract it can deserialize; untouched records remain
// owned by the backing store and are not projected into this client cache.
DirectoryStoreHydrationFilter = metadata => runtimeSchemaIds.Contains(metadata.SchemaId)
},
DatabaseOptions = new CultNetDatabaseOptions
{

View file

@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 13dee70f7b1b8ef4eabd7f3d572b02f1

View file

@ -14,6 +14,7 @@
"overrideReferences": true,
"precompiledReferences": [
"ConcurrentCollections.dll",
"CultMath.dll",
"GameCult.Caching.MessagePack.dll",
"GameCult.Caching.dll",
"GameCult.Logging.dll",
@ -28,6 +29,7 @@
"Microsoft.NET.StringTools.dll",
"MessagePack.Annotations.dll",
"MessagePack.CultMesh.dll",
"MessagePack.dll",
"R3.dll",
"System.Collections.Immutable.dll",
"System.ComponentModel.Annotations.dll",

View file

@ -5,7 +5,6 @@
"unity": "2021.3",
"description": "Unity runtime boundary for Aetheria typed CultCache/CultMesh state.",
"dependencies": {
"com.gamecult.brokkr": "0.1.0",
"org.gamecult.eve.surface": "0.1.0"
},
"keywords": [

View file

@ -1,13 +1,5 @@
{
"dependencies": {
"com.gamecult.brokkr": {
"version": "file:../../Brokkr/surfaces/unity/Packages/com.gamecult.brokkr/",
"depth": 0,
"source": "local",
"dependencies": {
"org.gamecult.cultlib": "1.0.16"
}
},
"com.unity.2d.sprite": {
"version": "1.0.0",
"depth": 0,
@ -308,7 +300,6 @@
"depth": 0,
"source": "embedded",
"dependencies": {
"com.gamecult.brokkr": "0.1.0",
"org.gamecult.eve.surface": "0.1.0"
}
},
@ -320,11 +311,11 @@
"hash": "f67f5122ed1bd11da016e7b820ed60145ccd0299"
},
"org.gamecult.cultmath": {
"version": "https://github.com/GameCult/CultMath.git?path=/unity/org.gamecult.cultmath#cultmath-unity-v0.1.1",
"version": "https://github.com/GameCult/CultMath.git?path=/unity/org.gamecult.cultmath#cultmath-unity-v0.1.2",
"depth": 0,
"source": "git",
"dependencies": {},
"hash": "c4543b9443e96d848260ef00254d2d3c0fa6a9d2"
"hash": "fcdddda72f6aa33ae6eeff3bce6a2d3e26eb16f3"
},
"org.gamecult.eve.plugin-fields": {
"version": "https://github.com/GameCult/EvePlugins.git?path=/plugins/eve-plugin-fields/unity/org.gamecult.eve.plugin-fields#eve-plugin-fields-unity-v0.2.3",

View file

@ -3,7 +3,7 @@
Status: the shared Hangar, deployment admission, and portable Hangar surface
contracts exist. Mode session bootstrap still has to consume accepted deployment
receipts; the old generic New Game path remains live until that cut is complete.
The state assembly and viewport queries compile against CultMath `0.1.1`, whose
The state assembly and viewport queries compile against CultMath `0.1.2`, whose
canonical `rect` stores normalized `min`/`max` bounds.
The Hangar is the launcher and durable preparation space for Terminus,

View file

@ -15,37 +15,41 @@ $state = Join-Path $project "Build\aetheria-unity.cc"
$stateRecords = "$state.records"
New-Item -ItemType Directory -Force $artifacts | Out-Null
if (-not $SkipBuild) {
$assetLog = Join-Path $artifacts "asset-bundles.log"
$assets = Start-Process $UnityExe -ArgumentList @(
"-batchmode", "-quit", "-projectPath", $root,
"-executeMethod", "Aetheria.Editor.EveAssetBundleBuilder.BuildWindows",
"-logFile", $assetLog
) -PassThru -WindowStyle Hidden
Write-Host "Asset build PID: $($assets.Id)"
Write-Host "Asset build log: $assetLog"
if (-not $assets.WaitForExit(240000)) { Stop-Process $assets.Id -Force; throw "Asset build timed out." }
if ($assets.ExitCode -ne 0) { Get-Content $assetLog -Tail 120; throw "Asset build failed." }
$clientLog = Join-Path $artifacts "client-build.log"
$build = Start-Process $UnityExe -ArgumentList @(
"-batchmode", "-quit", "-projectPath", $project,
"-executeMethod", "AetheriaUnityBuild.BuildWindows",
"-logFile", $clientLog
) -PassThru -WindowStyle Hidden
Write-Host "Client build PID: $($build.Id)"
Write-Host "Client build log: $clientLog"
if (-not $build.WaitForExit(300000)) { Stop-Process $build.Id -Force; throw "Client build timed out." }
if ($build.ExitCode -ne 0 -or -not (Test-Path $clientExe)) { Get-Content $clientLog -Tail 120; throw "Client build failed." }
}
if (-not (Test-Path $clientExe)) { throw "Client executable not found. Run without -SkipBuild first." }
if (-not (Test-Path $stateRecords)) {
dotnet run --project $importProject -- $root $state
if ($LASTEXITCODE -ne 0 -or -not (Test-Path $stateRecords)) {
throw "Aetheria typed state import failed with exit code $LASTEXITCODE"
}
}
if (-not $SkipBuild) {
$env:AETHERIA_ASSET_CATALOG_STATE = $state
$assetLog = Join-Path $artifacts "asset-bundles.log"
$assets = Start-Process $UnityExe -ArgumentList @(
"-batchmode", "-quit", "-projectPath", $root,
"-cacheServerEnableDownload", "false", "-cacheServerEnableUpload", "false",
"-executeMethod", "Aetheria.Editor.EveAssetBundleBuilder.BuildWindows",
"-logFile", $assetLog
) -PassThru -WindowStyle Hidden
Write-Host "Asset build PID: $($assets.Id)"
Write-Host "Asset build log: $assetLog"
if (-not $assets.WaitForExit(600000)) { Stop-Process $assets.Id -Force; throw "Asset build timed out." }
if ($assets.ExitCode -ne 0) { Get-Content $assetLog -Tail 120; throw "Asset build failed." }
$clientLog = Join-Path $artifacts "client-build.log"
$build = Start-Process $UnityExe -ArgumentList @(
"-batchmode", "-quit", "-projectPath", $project,
"-cacheServerEnableDownload", "false", "-cacheServerEnableUpload", "false",
"-executeMethod", "AetheriaUnityBuild.BuildWindows",
"-logFile", $clientLog
) -PassThru -WindowStyle Hidden
Write-Host "Client build PID: $($build.Id)"
Write-Host "Client build log: $clientLog"
if (-not $build.WaitForExit(600000)) { Stop-Process $build.Id -Force; throw "Client build timed out." }
if ($build.ExitCode -ne 0 -or -not (Test-Path $clientExe)) { Get-Content $clientLog -Tail 120; throw "Client build failed." }
}
if (-not (Test-Path $clientExe)) { throw "Client executable not found. Run without -SkipBuild first." }
$daemonLog = Join-Path $artifacts "daemon.log"
$daemon = Start-Process dotnet -ArgumentList @(
"run", "--project", $daemonProject, "--",
@ -56,6 +60,7 @@ $daemon = Start-Process dotnet -ArgumentList @(
"--client-cultmesh-port", $Port,
"--tick-interval-ms", 20,
"--fixed-delta-ms", 20,
"--terminus-scenario", "standard",
"--no-odin-announcements"
) -PassThru -WindowStyle Hidden -RedirectStandardOutput $daemonLog -RedirectStandardError "$daemonLog.error"
Write-Host "Daemon PID: $($daemon.Id)"
@ -63,7 +68,11 @@ Write-Host "Daemon log: $daemonLog"
try {
$ready = $false
for ($i = 0; $i -lt 60; $i++) {
# A cold daemon build can take several minutes because the state schema
# generator and the Ymir/CultMesh dependency graph compile before Program
# publishes the endpoint. Readiness belongs to the endpoint, not an
# arbitrary 30-second process lifetime.
for ($i = 0; $i -lt 600; $i++) {
if ($daemon.HasExited) { throw "Aetheria daemon exited. See $daemonLog" }
if ((Test-Path $daemonLog) -and (Select-String $daemonLog -Pattern "Aetheria client CultMesh endpoint: cultnet\+tcp://127.0.0.1:$Port" -Quiet)) { $ready = $true; break }
Start-Sleep -Milliseconds 500

View file

@ -1,11 +1,15 @@
param(
[string] $CultLibRoot = "E:\Projects\CultLib",
[string] $EveUnityRoot = "E:\Projects\EveUnity",
[string] $YmirRoot = "E:\Projects\Ymir"
[string] $CultLibRoot = "",
[string] $EveUnityRoot = "",
[string] $YmirRoot = ""
)
$ErrorActionPreference = "Stop"
$repoRoot = Split-Path -Parent $PSScriptRoot
$projectsRoot = Split-Path -Parent $repoRoot
if ([string]::IsNullOrWhiteSpace($CultLibRoot)) { $CultLibRoot = Join-Path $projectsRoot "CultLib" }
if ([string]::IsNullOrWhiteSpace($EveUnityRoot)) { $EveUnityRoot = Join-Path $projectsRoot "EveUnity" }
if ([string]::IsNullOrWhiteSpace($YmirRoot)) { $YmirRoot = Join-Path $projectsRoot "Ymir" }
$project = Join-Path $repoRoot "Aetheria.State.Daemon.Smoke\Aetheria.State.Daemon.Smoke.csproj"
foreach ($dependency in @(
@ -18,6 +22,17 @@ foreach ($dependency in @(
}
}
$ymirSession = Join-Path $YmirRoot "src\Ymir.Core\YmirSession.cs"
$box2DProject = Join-Path $YmirRoot "src\Ymir.Box3D\Ymir.Box3D.csproj"
$box2DSource = Join-Path $YmirRoot "extern\box3d\CMakeLists.txt"
if (-not (Test-Path -LiteralPath $ymirSession -PathType Leaf) -or
-not (Test-Path -LiteralPath $box2DProject -PathType Leaf)) {
throw "Ymir does not expose the retained Box2D session API required by Aetheria. Use the current Aetheria integration revision."
}
if (-not (Test-Path -LiteralPath $box2DSource -PathType Leaf)) {
throw "Ymir's pinned Box2D submodule is missing. Run 'git -C $YmirRoot submodule update --init --recursive'."
}
$properties = @(
"-p:CultLibRoot=$CultLibRoot",
"-p:EveUnityRoot=$EveUnityRoot",

View file

@ -14,14 +14,14 @@ $manifest = Get-Content (Join-Path $ProjectPath "Packages/manifest.json") -Raw |
$lock = Get-Content (Join-Path $ProjectPath "Packages/packages-lock.json") -Raw | ConvertFrom-Json
$expected = @{
"org.gamecult.cultlib" = @(
"https://github.com/GameCult/CultLib.git?path=/unity/org.gamecult.cultlib#cultlib-unity-v1.0.30",
"0ef59b50c27ac019929f5e68bcd602983a190062")
"https://github.com/GameCult/CultLib.git?path=/unity/org.gamecult.cultlib#cultlib-unity-v1.0.43",
"f67f5122ed1bd11da016e7b820ed60145ccd0299")
"org.gamecult.eve.surface" = @(
"https://github.com/GameCult/EveUnity.git?path=/packages/org.gamecult.eve.surface#eveunity-surface-v0.2.4",
"e08fa08335f99e9edddeb706912eecfad07cb281")
"org.gamecult.eve.unity-scene" = @(
"https://github.com/GameCult/EveUnity.git?path=/packages/org.gamecult.eve.unity-scene#eveunity-scene-v0.3.88",
"3ce813e29dcc5342b51985187de26c62eb8c90b4")
"https://github.com/GameCult/EveUnity.git?path=/packages/org.gamecult.eve.unity-scene#eveunity-scene-v0.3.103",
"050b2019bd6faa4275013516870d1ded245ee391")
}
foreach ($packageName in $expected.Keys) {
@ -34,9 +34,9 @@ foreach ($packageName in $expected.Keys) {
$cultLibPackage = Get-ChildItem (Join-Path $ProjectPath "Library/PackageCache") -Directory |
Where-Object Name -Like "org.gamecult.cultlib@*" |
Where-Object { (Get-Content (Join-Path $_.FullName "package.json") -Raw | ConvertFrom-Json).version -eq "1.0.30" } |
Where-Object { (Get-Content (Join-Path $_.FullName "package.json") -Raw | ConvertFrom-Json).version -eq "1.0.43" } |
Select-Object -First 1
if (-not $cultLibPackage) { throw "Resolved CultLib package is missing from Library/PackageCache." }
if (-not $cultLibPackage) { throw "Resolved CultLib 1.0.43 package is missing from Library/PackageCache." }
$meshAssembly = Join-Path $cultLibPackage.FullName "Runtime/Plugins/GameCult.Mesh.dll"
if (-not (Test-Path $meshAssembly)) { throw "Resolved GameCult.Mesh.dll is missing." }
@ -46,8 +46,8 @@ $requiredMeshApiNames = @(
"CultMeshBodyPublicationDocument",
"CultMeshBodyPublicationHandle",
"CultMeshFrameBodyPublisher",
"CultMeshContentServer",
"CultMeshSessionContentProviderOptions")
"CultMeshTcpContentServer",
"CultMeshSessionContentProvider")
foreach ($apiName in $requiredMeshApiNames) {
if (-not ($meshApi | Where-Object { $_ -eq $apiName })) {
throw "Resolved GameCult.Mesh.dll does not expose $apiName."
@ -68,9 +68,9 @@ foreach ($requiredInputContract in @("PayloadKeys", "CurrentValue", "ActionBar",
$scenePackage = Get-ChildItem (Join-Path $ProjectPath "Library/PackageCache") -Directory |
Where-Object Name -Like "org.gamecult.eve.unity-scene@*" |
Where-Object { (Get-Content (Join-Path $_.FullName "package.json") -Raw | ConvertFrom-Json).version -eq "0.3.88" } |
Where-Object { (Get-Content (Join-Path $_.FullName "package.json") -Raw | ConvertFrom-Json).version -eq "0.3.103" } |
Select-Object -First 1
if (-not $scenePackage) { throw "Resolved Eve Unity scene 0.3.88 package is missing from Library/PackageCache." }
if (-not $scenePackage) { throw "Resolved Eve Unity scene 0.3.103 package is missing from Library/PackageCache." }
$advertisedInputSource = Get-Content (Join-Path $scenePackage.FullName "Runtime/EveUnityAdvertisedInputAction.cs") -Raw
$inputDriverSource = Get-Content (Join-Path $scenePackage.FullName "Runtime/EveUnityPlayableWorldInputDriver.cs") -Raw
$actionBarSource = Get-Content (Join-Path $scenePackage.FullName "Runtime/EveUnityInputActionBar.cs") -Raw