diff --git a/ReleaseConsumerProject/Assets/Tests/PlayMode/GenericWorldCaptureTests.cs b/ReleaseConsumerProject/Assets/Tests/PlayMode/GenericWorldCaptureTests.cs index d5dc99b..9269482 100644 --- a/ReleaseConsumerProject/Assets/Tests/PlayMode/GenericWorldCaptureTests.cs +++ b/ReleaseConsumerProject/Assets/Tests/PlayMode/GenericWorldCaptureTests.cs @@ -2,6 +2,7 @@ using System; using System.Collections; using System.Collections.Generic; using System.IO; +using System.Linq; using GameCult.Caching; using GameCult.Caching.MessagePack; using GameCult.Eve.Surface; @@ -175,12 +176,16 @@ namespace GameCult.EveUnity.GenericClient.PlayModeTests Path.Combine(Application.temporaryCachePath, $"eve-unity-{Guid.NewGuid():N}.cc"); var capturePath = Environment.GetEnvironmentVariable("EVEUNITY_AETHERIA_CAPTURE_PATH") ?? Path.Combine(Application.temporaryCachePath, "aetheria-daemon-world.png"); + var mapCapturePath = Environment.GetEnvironmentVariable("EVEUNITY_AETHERIA_MAP_CAPTURE_PATH") ?? + Path.Combine(Application.temporaryCachePath, "aetheria-daemon-map.png"); var root = new GameObject("Generic Eve World Projection"); var cameraObject = new GameObject("Generic Eve Capture Camera"); + var mapCameraObject = new GameObject("Generic Eve Map Camera"); var lightObject = new GameObject("Generic Eve World Light"); RenderTexture target = null; Texture2D pixels = null; EveUnityCultMeshPlayableWorldProvider provider = null; + EveUnityPlayableWorldClientHost host = null; EveUnityPlayableWorldRuntime runtime = null; WitnessReceipt movementReceipt = null; WitnessReceipt focusReceipt = null; @@ -217,18 +222,38 @@ namespace GameCult.EveUnity.GenericClient.PlayModeTests Assert.That(provider.Selection.VerseId, Is.Not.Empty); Assert.That(provider.Selection.ProviderId, Is.Not.Empty); Assert.That(provider.Selection.SurfaceId, Is.Not.Empty); - runtime = EveUnityPlayableWorldRuntime.CreateForGameObjectScene( + host = root.AddComponent(); + host.Configure( root.transform, provider, provider, - null, + provider, provider, provider); - var initial = runtime.Connect(); - Assert.That(initial.ActiveEntities, Is.GreaterThan(0)); + host.Connect(); + runtime = host.Runtime; + Assert.That(runtime, Is.Not.Null); Assert.That(runtime.ActiveWorld.PlayerEntityId, Is.Not.Empty); + var entityDeadline = Time.realtimeSinceStartup + 12f; + while (Time.realtimeSinceStartup < entityDeadline && + (host.PresentedEntities?.CurrentGeneration == null || + host.PresentedEntities.CurrentGeneration.Entities.Count == 0)) + { + yield return new WaitForSecondsRealtime(0.1f); + host.Refresh(); + } + Assert.That(host.PresentedEntities?.CurrentGeneration, Is.Not.Null); + Assert.That(host.PresentedEntities.CurrentGeneration.Entities.Count, Is.GreaterThan(0)); var playerId = runtime.ActiveWorld.PlayerEntityId; + var playerFact = host.PresentedEntities.CurrentGeneration.Entities + .Single(entity => entity.EntityId == playerId); + var playerPrefab = provider.ResolvePrefab(new EveUnityPlayableWorldAssetBinding( + playerFact.AssetRef, + playerFact.EntityKind, + "provider-asset-ref")); + Assert.That(playerPrefab, Is.Not.Null, + $"Provider asset catalog did not resolve player AssetRef '{playerFact.AssetRef}'."); var marker = FindMarker(root, playerId); Assert.That( marker.GetComponentsInChildren(includeInactive: true).Length, @@ -302,6 +327,7 @@ namespace GameCult.EveUnity.GenericClient.PlayModeTests var camera = cameraObject.AddComponent(); camera.clearFlags = CameraClearFlags.SolidColor; camera.backgroundColor = new Color(0.035f, 0.055f, 0.08f, 1f); + camera.cullingMask = -1; foreach (var entityMarker in root.GetComponentsInChildren()) { foreach (var renderer in entityMarker.GetComponentsInChildren()) @@ -316,15 +342,25 @@ namespace GameCult.EveUnity.GenericClient.PlayModeTests var playerMarker = FindMarker(root, playerId); var playerRenderers = playerMarker.GetComponentsInChildren(); Assert.That(playerRenderers, Is.Not.Empty, "The provider-authored player prefab has no renderable visual."); - var playerBounds = playerRenderers[0].bounds; - foreach (var renderer in playerRenderers) - playerBounds.Encapsulate(renderer.bounds); - var halfFovRadians = camera.fieldOfView * 0.5f * Mathf.Deg2Rad; - var cameraDistance = Mathf.Max(8f, playerBounds.extents.magnitude / Mathf.Tan(halfFovRadians) * 1.35f); - camera.nearClipPlane = Mathf.Max(0.01f, cameraDistance * 0.001f); - camera.farClipPlane = Mathf.Max(1000f, cameraDistance + playerBounds.size.magnitude * 4f); - camera.transform.position = playerBounds.center + new Vector3(1f, 0.65f, -1.2f).normalized * cameraDistance; - camera.transform.LookAt(playerBounds.center); + Assert.That(provider.TryGetRenderChannelLayer("map", out var mapLayer), Is.True); + var mapRenderers = new List(); + foreach (var renderer in root.GetComponentsInChildren(includeInactive: true)) + { + if (renderer.gameObject.layer == mapLayer) + mapRenderers.Add(renderer); + } + Assert.That(mapRenderers, Is.Not.Empty, "The live provider prefab has no renderer assigned to its advertised map channel."); + + var cameraRig = cameraObject.AddComponent(); + cameraRig.Host = host; + cameraRig.CameraTransform = camera.transform; + cameraRig.RenderPolicySource = provider; + Assert.That(cameraRig.ApplyRig(0f), Is.True); + Assert.That(camera.cullingMask & (1 << mapLayer), Is.Zero); + + var mapCamera = mapCameraObject.AddComponent(); + mapCamera.cullingMask = 1 << mapLayer; + Assert.That(mapCamera.cullingMask & (1 << mapLayer), Is.Not.Zero); target = new RenderTexture(640, 360, 24, RenderTextureFormat.ARGB32); camera.targetTexture = target; yield return null; @@ -336,10 +372,36 @@ namespace GameCult.EveUnity.GenericClient.PlayModeTests Directory.CreateDirectory(Path.GetDirectoryName(Path.GetFullPath(capturePath))); File.WriteAllBytes(capturePath, pixels.EncodeToPNG()); Assert.That(new FileInfo(capturePath).Length, Is.GreaterThan(1024)); + + var mapBounds = mapRenderers[0].bounds; + foreach (var renderer in mapRenderers) + mapBounds.Encapsulate(renderer.bounds); + mapCamera.clearFlags = CameraClearFlags.SolidColor; + mapCamera.backgroundColor = camera.backgroundColor; + mapCamera.orthographic = true; + mapCamera.orthographicSize = Mathf.Max(0.1f, mapBounds.extents.x, mapBounds.extents.z) * 1.25f; + mapCamera.nearClipPlane = 0.01f; + mapCamera.farClipPlane = Mathf.Max(10f, mapBounds.size.magnitude * 4f); + mapCamera.transform.position = mapBounds.center + Vector3.up * (mapCamera.farClipPlane * 0.25f); + mapCamera.transform.rotation = Quaternion.Euler(90f, 0f, 0f); + mapCamera.targetTexture = target; + mapCamera.Render(); + RenderTexture.active = target; + pixels.ReadPixels(new Rect(0, 0, 640, 360), 0, 0); + pixels.Apply(); + var mapChangedPixels = CountChangedPixels(pixels, mapCamera.backgroundColor); + Assert.That(mapChangedPixels, Is.GreaterThan(25), "The map-only camera rendered no provider-authored map glyph pixels."); + Directory.CreateDirectory(Path.GetDirectoryName(Path.GetFullPath(mapCapturePath))); + File.WriteAllBytes(mapCapturePath, pixels.EncodeToPNG()); + Assert.That(new FileInfo(mapCapturePath).Length, Is.GreaterThan(1024)); WriteWitnessFacts( initialVersion, runtime.ActiveVersion, movementDistance, + mapRenderers.Count, + mapChangedPixels, + (camera.cullingMask & (1 << mapLayer)) == 0, + (mapCamera.cullingMask & (1 << mapLayer)) != 0, movementReceipt, focusReceipt, actionReceipt); @@ -349,10 +411,13 @@ namespace GameCult.EveUnity.GenericClient.PlayModeTests RenderTexture.active = null; var camera = cameraObject.GetComponent(); if (camera != null) camera.targetTexture = null; + var mapCamera = mapCameraObject.GetComponent(); + if (mapCamera != null) mapCamera.targetTexture = null; if (target != null) UnityEngine.Object.DestroyImmediate(target); if (pixels != null) UnityEngine.Object.DestroyImmediate(pixels); - runtime?.Dispose(); + host?.Disconnect(); UnityEngine.Object.DestroyImmediate(lightObject); + UnityEngine.Object.DestroyImmediate(mapCameraObject); UnityEngine.Object.DestroyImmediate(cameraObject); UnityEngine.Object.DestroyImmediate(root); } @@ -362,6 +427,10 @@ namespace GameCult.EveUnity.GenericClient.PlayModeTests long initialVersion, long finalVersion, float movementDistance, + int mapChannelRendererCount, + int mapChangedPixels, + bool pilotCameraExcludesMapChannel, + bool mapCameraIncludesMapChannel, WitnessReceipt movement, WitnessReceipt targeting, WitnessReceipt action) @@ -378,6 +447,10 @@ namespace GameCult.EveUnity.GenericClient.PlayModeTests initialVersion = initialVersion, finalVersion = finalVersion, movementDistance = movementDistance, + mapChannelRendererCount = mapChannelRendererCount, + mapChangedPixels = mapChangedPixels, + pilotCameraExcludesMapChannel = pilotCameraExcludesMapChannel, + mapCameraIncludesMapChannel = mapCameraIncludesMapChannel, receipts = new[] { movement, targeting, action } }; Directory.CreateDirectory(Path.GetDirectoryName(Path.GetFullPath(path))); @@ -395,6 +468,10 @@ namespace GameCult.EveUnity.GenericClient.PlayModeTests public long initialVersion; public long finalVersion; public float movementDistance; + public int mapChannelRendererCount; + public int mapChangedPixels; + public bool pilotCameraExcludesMapChannel; + public bool mapCameraIncludesMapChannel; public WitnessReceipt[] receipts; } @@ -428,6 +505,19 @@ namespace GameCult.EveUnity.GenericClient.PlayModeTests throw new InvalidOperationException($"Projected world has no entity marker '{entityId}'."); } + private static int CountChangedPixels(Texture2D texture, Color background) + { + var changed = 0; + foreach (var pixel in texture.GetPixels()) + { + if (Mathf.Abs(pixel.r - background.r) + + Mathf.Abs(pixel.g - background.g) + + Mathf.Abs(pixel.b - background.b) > 0.08f) + changed++; + } + return changed; + } + private static EveUnitySceneProviderSurfaceDocument WorldDocument() { var entities = new[] diff --git a/ReleaseConsumerProject/Packages/manifest.json b/ReleaseConsumerProject/Packages/manifest.json index 8bea128..d4aefd5 100644 --- a/ReleaseConsumerProject/Packages/manifest.json +++ b/ReleaseConsumerProject/Packages/manifest.json @@ -5,10 +5,11 @@ "com.unity.modules.imageconversion": "1.0.0", "com.unity.modules.physics": "1.0.0", "com.unity.modules.uielements": "1.0.0", - "org.gamecult.cultlib": "https://github.com/GameCult/CultLib.git?path=/unity/org.gamecult.cultlib#cultlib-unity-v1.0.8", + "com.unity.render-pipelines.universal": "17.4.0", + "org.gamecult.cultlib": "https://github.com/GameCult/CultLib.git?path=/unity/org.gamecult.cultlib#cultlib-unity-v1.0.10", "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.1.1", - "org.gamecult.eve.surface": "https://github.com/GameCult/EveUnity.git?path=/packages/org.gamecult.eve.surface#eveunity-surface-v0.2.0", - "org.gamecult.eve.unity-scene": "https://github.com/GameCult/EveUnity.git?path=/packages/org.gamecult.eve.unity-scene#eveunity-scene-v0.2.0", + "org.gamecult.eve.surface": "https://github.com/GameCult/EveUnity.git?path=/packages/org.gamecult.eve.surface#eveunity-surface-v0.2.1", + "org.gamecult.eve.unity-scene": "https://github.com/GameCult/EveUnity.git?path=/packages/org.gamecult.eve.unity-scene#eveunity-scene-v0.3.15", "org.gamecult.eve.unity-uitoolkit": "https://github.com/GameCult/EveUnity.git?path=/packages/org.gamecult.eve.unity-uitoolkit#eveunity-uitoolkit-v0.1.0" }, "testables": [ diff --git a/ReleaseConsumerProject/Packages/packages-lock.json b/ReleaseConsumerProject/Packages/packages-lock.json index 8d56d72..66c5fbc 100644 --- a/ReleaseConsumerProject/Packages/packages-lock.json +++ b/ReleaseConsumerProject/Packages/packages-lock.json @@ -1,5 +1,27 @@ { "dependencies": { + "com.unity.burst": { + "version": "1.8.29", + "depth": 2, + "source": "registry", + "dependencies": { + "com.unity.mathematics": "1.2.1", + "com.unity.modules.jsonserialize": "1.0.0" + }, + "url": "https://packages.unity.com" + }, + "com.unity.collections": { + "version": "6.4.0", + "depth": 2, + "source": "builtin", + "dependencies": { + "com.unity.burst": "1.8.23", + "com.unity.mathematics": "1.3.2", + "com.unity.nuget.mono-cecil": "1.11.5", + "com.unity.test-framework": "1.4.6", + "com.unity.test-framework.performance": "3.0.3" + } + }, "com.unity.ext.nunit": { "version": "2.0.5", "depth": 1, @@ -13,6 +35,60 @@ "dependencies": {}, "url": "https://packages.unity.com" }, + "com.unity.nuget.mono-cecil": { + "version": "1.11.6", + "depth": 3, + "source": "registry", + "dependencies": {}, + "url": "https://packages.unity.com" + }, + "com.unity.render-pipelines.core": { + "version": "17.4.0", + "depth": 1, + "source": "builtin", + "dependencies": { + "com.unity.burst": "1.8.14", + "com.unity.mathematics": "1.3.2", + "com.unity.ugui": "2.0.0", + "com.unity.collections": "2.4.3", + "com.unity.modules.terrain": "1.0.0", + "com.unity.modules.jsonserialize": "1.0.0" + } + }, + "com.unity.render-pipelines.universal": { + "version": "17.4.0", + "depth": 0, + "source": "builtin", + "dependencies": { + "com.unity.render-pipelines.core": "17.4.0", + "com.unity.shadergraph": "17.4.0", + "com.unity.render-pipelines.universal-config": "17.4.0" + } + }, + "com.unity.render-pipelines.universal-config": { + "version": "17.4.0", + "depth": 1, + "source": "builtin", + "dependencies": { + "com.unity.render-pipelines.core": "17.4.0" + } + }, + "com.unity.searcher": { + "version": "4.9.4", + "depth": 2, + "source": "registry", + "dependencies": {}, + "url": "https://packages.unity.com" + }, + "com.unity.shadergraph": { + "version": "17.4.0", + "depth": 1, + "source": "builtin", + "dependencies": { + "com.unity.render-pipelines.core": "17.4.0", + "com.unity.searcher": "4.9.3" + } + }, "com.unity.test-framework": { "version": "1.6.0", "depth": 0, @@ -23,12 +99,31 @@ "com.unity.modules.jsonserialize": "1.0.0" } }, + "com.unity.test-framework.performance": { + "version": "3.4.0", + "depth": 3, + "source": "registry", + "dependencies": { + "com.unity.test-framework": "1.1.33", + "com.unity.modules.jsonserialize": "1.0.0" + }, + "url": "https://packages.unity.com" + }, + "com.unity.ugui": { + "version": "2.0.0", + "depth": 2, + "source": "builtin", + "dependencies": { + "com.unity.modules.ui": "1.0.0", + "com.unity.modules.imgui": "1.0.0" + } + }, "org.gamecult.cultlib": { - "version": "https://github.com/GameCult/CultLib.git?path=/unity/org.gamecult.cultlib#cultlib-unity-v1.0.7", + "version": "https://github.com/GameCult/CultLib.git?path=/unity/org.gamecult.cultlib#cultlib-unity-v1.0.10", "depth": 0, "source": "git", "dependencies": {}, - "hash": "1f8f832a788e2bdf59df90fb91da583574d3a42d" + "hash": "085aa2c0756328770623474c4e9fbacd7218b390" }, "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.1.1", @@ -38,25 +133,25 @@ "hash": "0b099274a4e2b4c301eced4322166837792f585a" }, "org.gamecult.eve.surface": { - "version": "https://github.com/GameCult/EveUnity.git?path=/packages/org.gamecult.eve.surface#b6f001b300a88aff0bc13372b5c1dbc2002954f8", + "version": "https://github.com/GameCult/EveUnity.git?path=/packages/org.gamecult.eve.surface#eveunity-surface-v0.2.1", "depth": 0, "source": "git", "dependencies": { - "org.gamecult.cultlib": "1.0.6" + "org.gamecult.cultlib": "1.0.8" }, - "hash": "b6f001b300a88aff0bc13372b5c1dbc2002954f8" + "hash": "d0aa50d31f3b6da96bffc194fdc3aa358f37e332" }, "org.gamecult.eve.unity-scene": { - "version": "https://github.com/GameCult/EveUnity.git?path=/packages/org.gamecult.eve.unity-scene#b6f001b300a88aff0bc13372b5c1dbc2002954f8", + "version": "https://github.com/GameCult/EveUnity.git?path=/packages/org.gamecult.eve.unity-scene#eveunity-scene-v0.3.15", "depth": 0, "source": "git", "dependencies": { - "org.gamecult.cultlib": "1.0.6", + "org.gamecult.cultlib": "1.0.10", "org.gamecult.eve.plugin-fields": "0.1.1", - "org.gamecult.eve.surface": "0.1.0", + "org.gamecult.eve.surface": "0.2.1", "com.unity.mathematics": "1.3.2" }, - "hash": "b6f001b300a88aff0bc13372b5c1dbc2002954f8" + "hash": "474cadac1c173e24b2c9b9ddde7b781059dbae4c" }, "org.gamecult.eve.unity-uitoolkit": { "version": "https://github.com/GameCult/EveUnity.git?path=/packages/org.gamecult.eve.unity-uitoolkit#eveunity-uitoolkit-v0.1.0", @@ -103,6 +198,12 @@ "source": "builtin", "dependencies": {} }, + "com.unity.modules.terrain": { + "version": "1.0.0", + "depth": 2, + "source": "builtin", + "dependencies": {} + }, "com.unity.modules.ui": { "version": "1.0.0", "depth": 1, diff --git a/docs/aetheria-released-package-witness.md b/docs/aetheria-released-package-witness.md new file mode 100644 index 0000000..cb64561 --- /dev/null +++ b/docs/aetheria-released-package-witness.md @@ -0,0 +1,18 @@ +# Aetheria released-package witness + +The warm-cache witness passes against `org.gamecult.eve.unity-scene` `0.3.15` +at commit `474cadac1c173e24b2c9b9ddde7b781059dbae4c`. + +Evidence is in `artifacts/render-channel-witness-41`: + +- `results.xml`: one passing PlayMode witness. +- `aetheria-daemon-world.png`: provider-authored 3D pilot view. +- `aetheria-daemon-map.png`: map-channel-only view. +- `witness-facts.json`: movement, targeting, action, receipts, and camera-mask facts. +- `runtime-witness.warm.json`: released package and screenshot hashes. + +The cold CDN path is not proven. `artifacts/render-channel-witness-21/results.xml` +records a timeout while fetching the 13,006,384-byte bundle through batches of +32 `gamecult.mesh.cdn_artifact_chunk.v1` snapshot records. The bundle must move +through the intended mapped/network body transport; increasing the snapshot +timeout is not an accepted fix. diff --git a/scripts/run-aetheria-daemon-world-witness.ps1 b/scripts/run-aetheria-daemon-world-witness.ps1 index 137d7c4..e706ee5 100644 --- a/scripts/run-aetheria-daemon-world-witness.ps1 +++ b/scripts/run-aetheria-daemon-world-witness.ps1 @@ -1,6 +1,8 @@ param( [string] $UnityExe = "C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Unity.exe", [string] $AetheriaRoot = "E:\Projects\Aetheria", + [string] $CultLibRoot = "E:\Projects\CultLib", + [string] $EveUnityRoot = "E:\Projects\EveUnity", [string] $ClientProject = "ReleaseConsumerProject", [int] $Port = 3076, [string] $OutputDirectory = "artifacts\aetheria-daemon", @@ -18,6 +20,7 @@ $unityLogPath = Join-Path $outputRoot "unity.log" $bundleBuildLogPath = Join-Path $outputRoot "asset-bundle-build.log" $daemonLogPath = Join-Path $outputRoot "aetheria-daemon.log" $capturePath = Join-Path $outputRoot "aetheria-daemon-world.png" +$mapCapturePath = Join-Path $outputRoot "aetheria-daemon-map.png" $factsPath = Join-Path $outputRoot "witness-facts.json" $witnessPath = Join-Path $outputRoot "runtime-witness.json" $replicaPath = Join-Path $outputRoot "eve-unity-replica.cc" @@ -55,13 +58,24 @@ foreach ($ephemeralPath in @( if (Test-Path -LiteralPath $resolved) { Remove-Item -LiteralPath $resolved -Recurse -Force } } -$import = Start-Process -FilePath "dotnet" -ArgumentList @( - "run", "--project", $importProject, "--", $AetheriaRoot, $statePath -) -PassThru -WindowStyle Hidden -Wait ` - -RedirectStandardOutput (Join-Path $outputRoot "aetheria-import.log") ` - -RedirectStandardError (Join-Path $outputRoot "aetheria-import.error.log") -if ($import.ExitCode -ne 0) { - throw "Aetheria state import failed with exit code $($import.ExitCode). See $outputRoot\aetheria-import.error.log" +$importArguments = @( + "run", "--project", $importProject, + "-p:CultLibRoot=$CultLibRoot", "-p:EveUnityRoot=$EveUnityRoot", + "--", $AetheriaRoot, $statePath +) +$importLogPath = Join-Path $outputRoot "aetheria-import.log" +$importErrorLogPath = Join-Path $outputRoot "aetheria-import.error.log" +$previousErrorActionPreference = $ErrorActionPreference +try { + $ErrorActionPreference = "Continue" + & dotnet @importArguments 1> $importLogPath 2> $importErrorLogPath + $importExitCode = $LASTEXITCODE +} +finally { + $ErrorActionPreference = $previousErrorActionPreference +} +if ($importExitCode -ne 0) { + throw "Aetheria state import failed with exit code $importExitCode. See $importErrorLogPath" } if (-not $SkipAssetBundleBuild) { $bundleBuilder = Start-Process -FilePath $UnityExe -ArgumentList @( @@ -83,7 +97,9 @@ if (-not $SkipAssetBundleBuild) { } $daemonArguments = @( - "run", "--project", $daemonProject, "--", + "run", "--project", $daemonProject, + "-p:CultLibRoot=$CultLibRoot", "-p:EveUnityRoot=$EveUnityRoot", + "--", "--root", $AetheriaRoot, "--state", $statePath, "--client-cultmesh-host", "127.0.0.1", @@ -112,10 +128,12 @@ try { if (-not $ready) { throw "Aetheria daemon did not open CultMesh port $Port. See $daemonLogPath" } $env:EVEUNITY_RENDEZVOUS_ENDPOINT = "rudp://127.0.0.1:$Port" - Remove-Item Env:EVEUNITY_PROVIDER_ID -ErrorAction SilentlyContinue + $env:EVEUNITY_PROVIDER_ID = "aetheria" Remove-Item Env:EVEUNITY_SURFACE_ID -ErrorAction SilentlyContinue $env:EVEUNITY_REPLICA_PATH = $replicaPath $env:EVEUNITY_AETHERIA_CAPTURE_PATH = $capturePath + $env:EVEUNITY_AETHERIA_MAP_CAPTURE_PATH = $mapCapturePath + $env:EVEUNITY_DISABLE_AUTO_LAUNCHER = "1" $env:EVEUNITY_ASSET_CACHE_PATH = $assetCachePath $env:EVEUNITY_WITNESS_FACTS_PATH = $factsPath $arguments = @( @@ -135,6 +153,7 @@ try { $run = $results.SelectSingleNode("//test-run") if ($null -eq $run -or [int]$run.passed -ne 1 -or [int]$run.failed -ne 0) { throw "Live world witness did not pass: $resultsPath" } if (-not (Test-Path $capturePath) -or (Get-Item $capturePath).Length -lt 1024) { throw "Live world capture is missing: $capturePath" } + if (-not (Test-Path $mapCapturePath) -or (Get-Item $mapCapturePath).Length -lt 1024) { throw "Live map-channel capture is missing: $mapCapturePath" } if (-not (Test-Path $factsPath)) { throw "Live world witness facts are missing: $factsPath" } $facts = Get-Content -LiteralPath $factsPath -Raw | ConvertFrom-Json $releaseLock = Get-Content -LiteralPath (Join-Path $repoRoot "ReleaseConsumerProject\Packages\packages-lock.json") -Raw | ConvertFrom-Json @@ -144,6 +163,7 @@ try { $facts | Add-Member -NotePropertyName eveUnityPackageCommit -NotePropertyValue $releaseLock.dependencies.'org.gamecult.eve.unity-scene'.hash $facts | Add-Member -NotePropertyName cultLibPackageCommit -NotePropertyValue $releaseLock.dependencies.'org.gamecult.cultlib'.hash $capture = Get-Item -LiteralPath $capturePath + $mapCapture = Get-Item -LiteralPath $mapCapturePath $resultsArtifact = Get-Item -LiteralPath $resultsPath $durationMs = [Math]::Round(([DateTimeOffset]::UtcNow - $witnessStartedAt).TotalMilliseconds, 3) $testDurationMs = [Math]::Round(([double]$run.duration) * 1000, 3) @@ -152,7 +172,7 @@ try { witnessId = "eveunity.aetheria.game.$observedCacheState" runtimeId = "unity-scene" runtimeOwnerRepo = "EveUnity" - providerId = "aetheria.daemon" + providerId = "aetheria" surfaceId = "aetheria.game" projectionKind = "provider-authored-world-surface" status = "pass" @@ -169,6 +189,9 @@ try { height = 360 encodedSizeBytes = $capture.Length sha256 = (Get-FileHash -LiteralPath $capture.FullName -Algorithm SHA256).Hash.ToLowerInvariant() + mapEncodedSizeBytes = $mapCapture.Length + mapSha256 = (Get-FileHash -LiteralPath $mapCapture.FullName -Algorithm SHA256).Hash.ToLowerInvariant() + mapChangedPixels = $facts.mapChangedPixels } artifacts = @( [ordered]@{ @@ -179,6 +202,14 @@ try { width = 640 height = 360 }, + [ordered]@{ + kind = "unity-map-channel-png" + path = $mapCapture.Name + sha256 = (Get-FileHash -LiteralPath $mapCapture.FullName -Algorithm SHA256).Hash.ToLowerInvariant() + sizeBytes = $mapCapture.Length + width = 640 + height = 360 + }, [ordered]@{ kind = "unity-test-results" path = $resultsArtifact.Name @@ -193,11 +224,12 @@ try { $witness | ConvertTo-Json -Depth 12 | Set-Content -LiteralPath $stateWitnessPath -Encoding UTF8 Write-Host "Aetheria daemon world witness: passed" Write-Host "Capture: $capturePath" + Write-Host "Map capture: $mapCapturePath" Write-Host "Runtime witness: $witnessPath" Write-Host "Cache-state witness: $stateWitnessPath" } finally { if ($null -ne $daemon -and -not $daemon.HasExited) { Stop-Process -Id $daemon.Id -Force } - Remove-Item Env:EVEUNITY_RENDEZVOUS_ENDPOINT, Env:EVEUNITY_PROVIDER_ENDPOINT, Env:EVEUNITY_PROVIDER_ID, Env:EVEUNITY_SURFACE_ID, Env:EVEUNITY_REPLICA_PATH, Env:EVEUNITY_AETHERIA_CAPTURE_PATH, Env:EVEUNITY_ASSET_CACHE_PATH, Env:EVEUNITY_WITNESS_FACTS_PATH -ErrorAction SilentlyContinue + Remove-Item Env:EVEUNITY_RENDEZVOUS_ENDPOINT, Env:EVEUNITY_PROVIDER_ENDPOINT, Env:EVEUNITY_PROVIDER_ID, Env:EVEUNITY_SURFACE_ID, Env:EVEUNITY_REPLICA_PATH, Env:EVEUNITY_AETHERIA_CAPTURE_PATH, Env:EVEUNITY_AETHERIA_MAP_CAPTURE_PATH, Env:EVEUNITY_DISABLE_AUTO_LAUNCHER, Env:EVEUNITY_ASSET_CACHE_PATH, Env:EVEUNITY_WITNESS_FACTS_PATH -ErrorAction SilentlyContinue Remove-Item Env:AETHERIA_TRACE_EVE_SNAPSHOTS -ErrorAction SilentlyContinue }