diff --git a/ReleaseConsumerProject/Assets/Tests/PlayMode/GenericWorldCaptureTests.cs b/ReleaseConsumerProject/Assets/Tests/PlayMode/GenericWorldCaptureTests.cs index ed3531c..f39c7ed 100644 --- a/ReleaseConsumerProject/Assets/Tests/PlayMode/GenericWorldCaptureTests.cs +++ b/ReleaseConsumerProject/Assets/Tests/PlayMode/GenericWorldCaptureTests.cs @@ -461,6 +461,31 @@ namespace GameCult.EveUnity.GenericClient.PlayModeTests Assert.That(finalPickupEntityCount, Is.EqualTo(initialPickupEntityCount - 1), "Collected pickup remained in the provider-authored entity generation."); + targetFact = host.PresentedEntities.CurrentGeneration.Entities + .Single(entity => entity.EntityId == targetEntityId); + playerFact = host.PresentedEntities.CurrentGeneration.Entities + .Single(entity => entity.EntityId == playerId); + aim = targetFact.Position - playerFact.Position; + aim.y = 0; + Assert.That(aim.sqrMagnitude, Is.GreaterThan(0.0001f)); + aim.Normalize(); + lookVersion = runtime.ActiveVersion; + look = runtime.SubmitLookDirectionIntent(playerId, aim.x, 0f, aim.z); + lookDeadline = Time.realtimeSinceStartup + 12f; + while (Time.realtimeSinceStartup < lookDeadline && + (runtime.LastReceipt == null || runtime.LastReceipt.CommandId != look.CommandId || + !string.Equals(runtime.LastReceipt.State, "reconciled", StringComparison.OrdinalIgnoreCase) || + runtime.ActiveVersion < runtime.LastReceipt.SourceVersion || + Vector3.Dot(FindMarker(root, playerId).transform.forward, aim) < 0.99f)) + { + yield return new WaitForSecondsRealtime(0.1f); + runtime.Refresh(); + } + AssertReconciledProviderReceipt(runtime.LastReceipt, look.CommandId, + provider.Selection.ProviderId, provider.Selection.SurfaceId, lookVersion); + Assert.That(Vector3.Dot(FindMarker(root, playerId).transform.forward, aim), Is.GreaterThan(0.99f)); + lookReceipt = WitnessReceipt.From("look", runtime.LastReceipt); + var focusVersion = runtime.ActiveVersion; var inputDriver = root.GetComponent() ?? root.AddComponent(); @@ -605,6 +630,8 @@ 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)); + WriteFieldLayerCaptures(root, host.Runtime.ActiveWorld, capturePath); + WriteFieldCloudCapture(root, capturePath); MeasureRendererOutputs( camera, target, @@ -1098,6 +1125,11 @@ namespace GameCult.EveUnity.GenericClient.PlayModeTests renderer.enabled = false; camera.clearFlags = CameraClearFlags.SolidColor; camera.backgroundColor = Color.black; + camera.Render(); + RenderTexture.active = target; + pixels.ReadPixels(new Rect(0, 0, target.width, target.height), 0, 0); + pixels.Apply(); + var baseline = pixels.GetPixels(); for (var index = 0; index < measuredRenderers.Count; index++) { var fact = facts[index]; @@ -1106,9 +1138,10 @@ namespace GameCult.EveUnity.GenericClient.PlayModeTests RenderTexture.active = target; pixels.ReadPixels(new Rect(0, 0, target.width, target.height), 0, 0); pixels.Apply(); - fact.renderedPixelCount = CountChangedPixels(pixels, Color.black); - MeasureLuminance( - pixels, + MeasureDifference( + pixels.GetPixels(), + baseline, + out fact.renderedPixelCount, out fact.renderedAverageLuminance, out fact.renderedBrightPixelCount); measuredRenderers[index].enabled = false; @@ -1123,6 +1156,32 @@ namespace GameCult.EveUnity.GenericClient.PlayModeTests } } + private static void MeasureDifference( + IReadOnlyList actual, + IReadOnlyList baseline, + out int changedPixelCount, + out float averageLuminance, + out int brightPixelCount) + { + changedPixelCount = 0; + brightPixelCount = 0; + double total = 0; + var count = Math.Min(actual.Count, baseline.Count); + for (var index = 0; index < count; index++) + { + var delta = new Color( + Mathf.Abs(actual[index].r - baseline[index].r), + Mathf.Abs(actual[index].g - baseline[index].g), + Mathf.Abs(actual[index].b - baseline[index].b)); + var magnitude = delta.r + delta.g + delta.b; + if (magnitude > 0.08f) changedPixelCount++; + var luminance = delta.r * 0.2126f + delta.g * 0.7152f + delta.b * 0.0722f; + total += luminance; + if (luminance >= 0.2f) brightPixelCount++; + } + averageLuminance = count == 0 ? 0f : (float)(total / count); + } + private static int CountChangedPixels(Texture2D texture, Color background) { var changed = 0; @@ -1136,6 +1195,59 @@ namespace GameCult.EveUnity.GenericClient.PlayModeTests return changed; } + private static void WriteFieldLayerCaptures(GameObject root, EveUnityPlayableWorldProjection world, string capturePath) + { + var renderer = root.GetComponent(); + if (renderer == null || world == null || string.IsNullOrWhiteSpace(capturePath)) return; + var directory = Path.GetDirectoryName(Path.GetFullPath(capturePath)); + foreach (var field in world.FieldVolumes) + { + if (!field.Props.TryGetValue("layerBindings", out var bindings)) continue; + foreach (var pair in (bindings ?? "").Split(';')) + { + var separator = pair.IndexOf('='); + if (separator <= 0) continue; + var layerKey = pair.Substring(0, separator).Trim(); + if (!renderer.TryGetTexture(layerKey, out var texture) || texture == null) continue; + var previous = RenderTexture.active; + RenderTexture.active = texture; + var copy = new Texture2D(texture.width, texture.height, TextureFormat.RGBA32, false, true); + copy.ReadPixels(new Rect(0, 0, texture.width, texture.height), 0, 0); + copy.Apply(); + RenderTexture.active = previous; + File.WriteAllBytes(Path.Combine(directory, $"field-{layerKey}.png"), copy.EncodeToPNG()); + UnityEngine.Object.DestroyImmediate(copy); + + RenderTexture.active = texture; + var floatCopy = new Texture2D(texture.width, texture.height, TextureFormat.RGBAFloat, false, true); + floatCopy.ReadPixels(new Rect(0, 0, texture.width, texture.height), 0, 0); + floatCopy.Apply(); + RenderTexture.active = previous; + File.WriteAllBytes(Path.Combine(directory, $"field-{layerKey}.exr"), floatCopy.EncodeToEXR()); + UnityEngine.Object.DestroyImmediate(floatCopy); + } + } + } + + private static void WriteFieldCloudCapture(GameObject root, string capturePath) + { + var renderer = root.GetComponent(); + var field = typeof(EveUnityFieldsVolumeRenderer).GetField( + "_cloudTexture", + System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic); + var texture = field?.GetValue(renderer) as RenderTexture; + if (texture == null || string.IsNullOrWhiteSpace(capturePath)) return; + var previous = RenderTexture.active; + RenderTexture.active = texture; + var copy = new Texture2D(texture.width, texture.height, TextureFormat.RGBAFloat, false, true); + copy.ReadPixels(new Rect(0, 0, texture.width, texture.height), 0, 0); + copy.Apply(); + RenderTexture.active = previous; + var directory = Path.GetDirectoryName(Path.GetFullPath(capturePath)); + File.WriteAllBytes(Path.Combine(directory, "field-volume-cloud.exr"), copy.EncodeToEXR()); + UnityEngine.Object.DestroyImmediate(copy); + } + private static EveUnitySceneProviderSurfaceDocument WorldDocument() { var entities = new[] diff --git a/ReleaseConsumerProject/Packages/manifest.json b/ReleaseConsumerProject/Packages/manifest.json index a7553f4..1d48aef 100644 --- a/ReleaseConsumerProject/Packages/manifest.json +++ b/ReleaseConsumerProject/Packages/manifest.json @@ -9,7 +9,7 @@ "org.gamecult.cultlib": "https://github.com/GameCult/CultLib.git?path=/unity/org.gamecult.cultlib#cultlib-unity-v1.0.13", "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.0", "org.gamecult.eve.surface": "https://github.com/GameCult/EveUnity.git?path=/packages/org.gamecult.eve.surface#eveunity-surface-v0.2.2", - "org.gamecult.eve.unity-scene": "https://github.com/GameCult/EveUnity.git?path=/packages/org.gamecult.eve.unity-scene#eveunity-scene-v0.3.33", + "org.gamecult.eve.unity-scene": "https://github.com/GameCult/EveUnity.git?path=/packages/org.gamecult.eve.unity-scene#eveunity-scene-v0.3.37", "org.gamecult.eve.unity-uitoolkit": "https://github.com/GameCult/EveUnity.git?path=/packages/org.gamecult.eve.unity-uitoolkit#eveunity-uitoolkit-v0.1.1" }, "testables": [ diff --git a/ReleaseConsumerProject/Packages/packages-lock.json b/ReleaseConsumerProject/Packages/packages-lock.json index 9f23367..13f2a6f 100644 --- a/ReleaseConsumerProject/Packages/packages-lock.json +++ b/ReleaseConsumerProject/Packages/packages-lock.json @@ -144,7 +144,7 @@ "hash": "140e1bd963a0033e66777a3b2c5fe6e9c97dfe32" }, "org.gamecult.eve.unity-scene": { - "version": "https://github.com/GameCult/EveUnity.git?path=/packages/org.gamecult.eve.unity-scene#eveunity-scene-v0.3.33", + "version": "https://github.com/GameCult/EveUnity.git?path=/packages/org.gamecult.eve.unity-scene#eveunity-scene-v0.3.37", "depth": 0, "source": "git", "dependencies": { @@ -154,7 +154,7 @@ "com.unity.mathematics": "1.3.2", "com.unity.modules.particlesystem": "1.0.0" }, - "hash": "852458dade1ae535d2ff3cfc9f8fe430e341ce2a" + "hash": "5ce654800760203154132d4b0fd1475294283ba6" }, "org.gamecult.eve.unity-uitoolkit": { "version": "https://github.com/GameCult/EveUnity.git?path=/packages/org.gamecult.eve.unity-uitoolkit#eveunity-uitoolkit-v0.1.1", diff --git a/TestProject/Assets/Tests/PlayMode/GenericWorldCaptureTests.cs b/TestProject/Assets/Tests/PlayMode/GenericWorldCaptureTests.cs index ed3531c..f39c7ed 100644 --- a/TestProject/Assets/Tests/PlayMode/GenericWorldCaptureTests.cs +++ b/TestProject/Assets/Tests/PlayMode/GenericWorldCaptureTests.cs @@ -461,6 +461,31 @@ namespace GameCult.EveUnity.GenericClient.PlayModeTests Assert.That(finalPickupEntityCount, Is.EqualTo(initialPickupEntityCount - 1), "Collected pickup remained in the provider-authored entity generation."); + targetFact = host.PresentedEntities.CurrentGeneration.Entities + .Single(entity => entity.EntityId == targetEntityId); + playerFact = host.PresentedEntities.CurrentGeneration.Entities + .Single(entity => entity.EntityId == playerId); + aim = targetFact.Position - playerFact.Position; + aim.y = 0; + Assert.That(aim.sqrMagnitude, Is.GreaterThan(0.0001f)); + aim.Normalize(); + lookVersion = runtime.ActiveVersion; + look = runtime.SubmitLookDirectionIntent(playerId, aim.x, 0f, aim.z); + lookDeadline = Time.realtimeSinceStartup + 12f; + while (Time.realtimeSinceStartup < lookDeadline && + (runtime.LastReceipt == null || runtime.LastReceipt.CommandId != look.CommandId || + !string.Equals(runtime.LastReceipt.State, "reconciled", StringComparison.OrdinalIgnoreCase) || + runtime.ActiveVersion < runtime.LastReceipt.SourceVersion || + Vector3.Dot(FindMarker(root, playerId).transform.forward, aim) < 0.99f)) + { + yield return new WaitForSecondsRealtime(0.1f); + runtime.Refresh(); + } + AssertReconciledProviderReceipt(runtime.LastReceipt, look.CommandId, + provider.Selection.ProviderId, provider.Selection.SurfaceId, lookVersion); + Assert.That(Vector3.Dot(FindMarker(root, playerId).transform.forward, aim), Is.GreaterThan(0.99f)); + lookReceipt = WitnessReceipt.From("look", runtime.LastReceipt); + var focusVersion = runtime.ActiveVersion; var inputDriver = root.GetComponent() ?? root.AddComponent(); @@ -605,6 +630,8 @@ 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)); + WriteFieldLayerCaptures(root, host.Runtime.ActiveWorld, capturePath); + WriteFieldCloudCapture(root, capturePath); MeasureRendererOutputs( camera, target, @@ -1098,6 +1125,11 @@ namespace GameCult.EveUnity.GenericClient.PlayModeTests renderer.enabled = false; camera.clearFlags = CameraClearFlags.SolidColor; camera.backgroundColor = Color.black; + camera.Render(); + RenderTexture.active = target; + pixels.ReadPixels(new Rect(0, 0, target.width, target.height), 0, 0); + pixels.Apply(); + var baseline = pixels.GetPixels(); for (var index = 0; index < measuredRenderers.Count; index++) { var fact = facts[index]; @@ -1106,9 +1138,10 @@ namespace GameCult.EveUnity.GenericClient.PlayModeTests RenderTexture.active = target; pixels.ReadPixels(new Rect(0, 0, target.width, target.height), 0, 0); pixels.Apply(); - fact.renderedPixelCount = CountChangedPixels(pixels, Color.black); - MeasureLuminance( - pixels, + MeasureDifference( + pixels.GetPixels(), + baseline, + out fact.renderedPixelCount, out fact.renderedAverageLuminance, out fact.renderedBrightPixelCount); measuredRenderers[index].enabled = false; @@ -1123,6 +1156,32 @@ namespace GameCult.EveUnity.GenericClient.PlayModeTests } } + private static void MeasureDifference( + IReadOnlyList actual, + IReadOnlyList baseline, + out int changedPixelCount, + out float averageLuminance, + out int brightPixelCount) + { + changedPixelCount = 0; + brightPixelCount = 0; + double total = 0; + var count = Math.Min(actual.Count, baseline.Count); + for (var index = 0; index < count; index++) + { + var delta = new Color( + Mathf.Abs(actual[index].r - baseline[index].r), + Mathf.Abs(actual[index].g - baseline[index].g), + Mathf.Abs(actual[index].b - baseline[index].b)); + var magnitude = delta.r + delta.g + delta.b; + if (magnitude > 0.08f) changedPixelCount++; + var luminance = delta.r * 0.2126f + delta.g * 0.7152f + delta.b * 0.0722f; + total += luminance; + if (luminance >= 0.2f) brightPixelCount++; + } + averageLuminance = count == 0 ? 0f : (float)(total / count); + } + private static int CountChangedPixels(Texture2D texture, Color background) { var changed = 0; @@ -1136,6 +1195,59 @@ namespace GameCult.EveUnity.GenericClient.PlayModeTests return changed; } + private static void WriteFieldLayerCaptures(GameObject root, EveUnityPlayableWorldProjection world, string capturePath) + { + var renderer = root.GetComponent(); + if (renderer == null || world == null || string.IsNullOrWhiteSpace(capturePath)) return; + var directory = Path.GetDirectoryName(Path.GetFullPath(capturePath)); + foreach (var field in world.FieldVolumes) + { + if (!field.Props.TryGetValue("layerBindings", out var bindings)) continue; + foreach (var pair in (bindings ?? "").Split(';')) + { + var separator = pair.IndexOf('='); + if (separator <= 0) continue; + var layerKey = pair.Substring(0, separator).Trim(); + if (!renderer.TryGetTexture(layerKey, out var texture) || texture == null) continue; + var previous = RenderTexture.active; + RenderTexture.active = texture; + var copy = new Texture2D(texture.width, texture.height, TextureFormat.RGBA32, false, true); + copy.ReadPixels(new Rect(0, 0, texture.width, texture.height), 0, 0); + copy.Apply(); + RenderTexture.active = previous; + File.WriteAllBytes(Path.Combine(directory, $"field-{layerKey}.png"), copy.EncodeToPNG()); + UnityEngine.Object.DestroyImmediate(copy); + + RenderTexture.active = texture; + var floatCopy = new Texture2D(texture.width, texture.height, TextureFormat.RGBAFloat, false, true); + floatCopy.ReadPixels(new Rect(0, 0, texture.width, texture.height), 0, 0); + floatCopy.Apply(); + RenderTexture.active = previous; + File.WriteAllBytes(Path.Combine(directory, $"field-{layerKey}.exr"), floatCopy.EncodeToEXR()); + UnityEngine.Object.DestroyImmediate(floatCopy); + } + } + } + + private static void WriteFieldCloudCapture(GameObject root, string capturePath) + { + var renderer = root.GetComponent(); + var field = typeof(EveUnityFieldsVolumeRenderer).GetField( + "_cloudTexture", + System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic); + var texture = field?.GetValue(renderer) as RenderTexture; + if (texture == null || string.IsNullOrWhiteSpace(capturePath)) return; + var previous = RenderTexture.active; + RenderTexture.active = texture; + var copy = new Texture2D(texture.width, texture.height, TextureFormat.RGBAFloat, false, true); + copy.ReadPixels(new Rect(0, 0, texture.width, texture.height), 0, 0); + copy.Apply(); + RenderTexture.active = previous; + var directory = Path.GetDirectoryName(Path.GetFullPath(capturePath)); + File.WriteAllBytes(Path.Combine(directory, "field-volume-cloud.exr"), copy.EncodeToEXR()); + UnityEngine.Object.DestroyImmediate(copy); + } + private static EveUnitySceneProviderSurfaceDocument WorldDocument() { var entities = new[] diff --git a/TestProject/Packages/packages-lock.json b/TestProject/Packages/packages-lock.json index 1f1ebaf..eb007d8 100644 --- a/TestProject/Packages/packages-lock.json +++ b/TestProject/Packages/packages-lock.json @@ -33,7 +33,9 @@ "version": "file:../../../EvePlugins/plugins/eve-plugin-fields/unity/org.gamecult.eve.plugin-fields", "depth": 0, "source": "local", - "dependencies": {} + "dependencies": { + "org.gamecult.cultlib": "1.0.13" + } }, "org.gamecult.eve.surface": { "version": "file:../../packages/org.gamecult.eve.surface", @@ -49,7 +51,7 @@ "source": "local", "dependencies": { "org.gamecult.cultlib": "1.0.13", - "org.gamecult.eve.plugin-fields": "0.1.1", + "org.gamecult.eve.plugin-fields": "0.2.0", "org.gamecult.eve.surface": "0.2.2", "com.unity.mathematics": "1.3.2", "com.unity.modules.particlesystem": "1.0.0" diff --git a/docs/aetheria-released-package-witness.md b/docs/aetheria-released-package-witness.md index 475d987..441c8ae 100644 --- a/docs/aetheria-released-package-witness.md +++ b/docs/aetheria-released-package-witness.md @@ -4,9 +4,12 @@ The warm-cache released-package witness passes with: -- `org.gamecult.eve.unity-scene` `0.3.32`, commit - `817912a041ac1911e1e27a4b88617754d90ad4b7`; -- `org.gamecult.eve.surface` `0.2.2`, from the same commit; +- `org.gamecult.eve.unity-scene` `0.3.37`, commit + `5ce654800760203154132d4b0fd1475294283ba6`; +- `org.gamecult.eve.plugin-fields` `0.2.0`, commit + `382a23d8f8a07e4b5eef5f81f84655861a858367`; +- `org.gamecult.eve.surface` `0.2.2`, commit + `140e1bd963a0033e66777a3b2c5fe6e9c97dfe32`; - `org.gamecult.eve.unity-uitoolkit` `0.1.1`, commit `4d0cbe0185bdc4fc65eb63503a7c5cb578539669`; - `org.gamecult.cultlib` `1.0.13`, commit @@ -14,7 +17,7 @@ The warm-cache released-package witness passes with: - the generic `ReleaseConsumerProject` client connected directly to the Aetheria daemon. -Evidence is in `artifacts/aetheria-daemon-native-0326-warm`. The PlayMode test +Evidence is in `artifacts/aetheria-daemon-gravity-fog-warm`. The PlayMode test passed and recorded provider-owned reconciled movement, look, targeting, tractor press, tractor release, contact-gated cargo collection, and action receipts. The daemon-owned look direction reached the SoA body rotation, @@ -37,6 +40,14 @@ with `display: none` in the pilot presentation. The runnable generic-client launcher mounts the same advertised document and forwards its typed commands; it contains no Aetheria gameplay or client-authored world light. +The same surface publishes a `field.volume3d` node over four canonical Eve +Fields splat layers. Its surface contract contains logical ports such as +`surfaceHeight`, `patch`, and `tint`; it contains no Unity `_Nebula*` property +names or pass indices. The selected `unity-scene` provider asset variant owns +that concrete shader ABI through `unity.volume.*` metadata. The released +generic lowerer resolved the provider shader and dither texture, rasterized all +four layers, and composited `22` pilot-camera frames from daemon frame `220`. + The two PNGs below are deliberate camera-channel captures, not a composited screen capture, so they prove world/map isolation rather than HUD pixels. The native cockpit proof is the asserted UI Toolkit visual tree recorded as @@ -46,11 +57,11 @@ daemon lock progress `1.0`; Unity does not manufacture or smooth that value. Camera-channel facts: - provider-authored player renderers: `13`, including the lowered tractor effect; -- pilot changed pixels: `230,302`; -- pilot average luminance: `0.002843`; -- pilot bright pixels: `1,040`; +- pilot changed pixels: `230,323`; +- pilot average luminance: `0.0532403`; +- pilot bright pixels: `954`; - map-channel renderers: `11`; -- map changed pixels: `3,921`; +- map changed pixels: `4,115`; - native cockpit progress bars: `7`; - daemon tractor power at capture: `1.0` after the held input completed its authored ramp; @@ -66,12 +77,12 @@ Camera-channel facts: - the map camera renders exactly the advertised map layer. Visual inspection confirms that map glyphs are absent from the pilot frame and -present in the map-only frame. Restoring the authored ARPG camera in place of -the copied TestScene tuning increased hull-renderer pixel coverage by roughly -four times and bright pilot pixels from `502` to `1,319`. The pilot presentation -remains dark. Renderer-isolation facts prove that the hull draws under the -provider-owned light, so the next native-art work is available pre-generated -provider textures, not a client-side lighting fallback. +present in the map-only frame. The pilot frame also shows the provider-owned +gravity-shaped fog basin rather than a client-authored background effect. Its +current dark, broad shape is transport/lowering proof, not visual-parity proof; +restoring the fossil's cloud structure, scale, tint range, and scene readability +remains provider art work. EveUnity must not compensate with Aetheria-specific +lighting or fog rules. Substance is not part of this path; later texture baking belongs in Blender. Aetheria currently bundles ambientCG's 1K `Metal012` color, normal, and metalness maps under CC0. The same pre-generated maps now replace dead @@ -112,8 +123,8 @@ Primary artifacts: ## Cold delivery is not currently proven -The current `46,131,569` byte Unity bundle (SHA-256 -`a4b26fb234ae7d82eda26fb342d7f77a236e3bc1107420175eb1c626785db212`) +The current `46,413,003` byte Unity bundle (SHA-256 +`229a7833120d9b4fe7e4270f13adc8ce8e553246a99efcbf20bb50d3ca18494d`) times out from an empty cache. Its bytes are still transported through batched snapshot records rather than the intended mapped/network body transport. Increasing the Unity timeout would hide the diff --git a/docs/authority-map.md b/docs/authority-map.md index 635cd36..d55e25c 100644 --- a/docs/authority-map.md +++ b/docs/authority-map.md @@ -17,6 +17,7 @@ drivers, asset-provider hooks, package release, runtime tests, and captures. - `gamecult.eve.provider_advertisement.v1` - `gamecult.eve.surface.v1` - provider asset-manifest documents +- runtime-selected asset-variant metadata, including native rendering-program bindings - provider command receipts - semantic render-channel inclusion and exclusion policy - Unity input and local presentation assets @@ -31,15 +32,18 @@ drivers, asset-provider hooks, package release, runtime tests, and captures. ## Derived State `ActiveWorld`, scene objects, transforms, markers, camera pose, native -render-channel layer mappings, asset caches, presentation counters, and receipt -display state are projections. They are not world truth. +render-channel layer mappings, resolved native shader ports/passes, asset caches, +presentation counters, and receipt display state are projections. They are not +world truth. ## Forbidden Writers Unity transforms, input drivers, camera rigs, scene sinks, asset caches, and pending receipts may not mutate or simulate provider world state. Plugin projection shells may not implement Sai, Norn, or TeX semantics. Portable -surfaces may not carry Unity layer numbers or camera culling masks. +surfaces may not carry Unity layer numbers, camera culling masks, shader property +names, shader keywords, or native pass indices. Those bindings belong to the +selected runtime asset variant. ## Shared Paths diff --git a/scripts/run-aetheria-daemon-world-witness.ps1 b/scripts/run-aetheria-daemon-world-witness.ps1 index b412222..492bea3 100644 --- a/scripts/run-aetheria-daemon-world-witness.ps1 +++ b/scripts/run-aetheria-daemon-world-witness.ps1 @@ -13,7 +13,8 @@ param( ) $ErrorActionPreference = "Stop" -$expectedEveUnityCommit = "817912a041ac1911e1e27a4b88617754d90ad4b7" +$expectedEveUnityCommit = "5ce654800760203154132d4b0fd1475294283ba6" +$expectedEveFieldsCommit = "382a23d8f8a07e4b5eef5f81f84655861a858367" $expectedEveUnityUiToolkitCommit = "4d0cbe0185bdc4fc65eb63503a7c5cb578539669" $expectedCultLibCommit = "feb5c71513e71d681699f462fe3682b3168c6f73" $repoRoot = Split-Path -Parent $PSScriptRoot @@ -118,8 +119,27 @@ if (-not $SkipAssetBundleBuild) { } } +$daemonBuildLogPath = Join-Path $outputRoot "aetheria-daemon-build.log" +$daemonBuildErrorLogPath = Join-Path $outputRoot "aetheria-daemon-build.error.log" +$daemonBuildArguments = @( + "build", $daemonProject, + "-p:CultLibRoot=$CultLibRoot", "-p:EveUnityRoot=$EveUnityRoot", "-p:YmirRoot=$YmirRoot" +) +$previousErrorActionPreference = $ErrorActionPreference +try { + $ErrorActionPreference = "Continue" + & dotnet @daemonBuildArguments 1> $daemonBuildLogPath 2> $daemonBuildErrorLogPath + $daemonBuildExitCode = $LASTEXITCODE +} +finally { + $ErrorActionPreference = $previousErrorActionPreference +} +if ($daemonBuildExitCode -ne 0) { + throw "Aetheria daemon prebuild failed with exit code $daemonBuildExitCode. See $daemonBuildErrorLogPath" +} + $daemonArguments = @( - "run", "--project", $daemonProject, + "run", "--project", $daemonProject, "--no-build", "-p:CultLibRoot=$CultLibRoot", "-p:EveUnityRoot=$EveUnityRoot", "-p:YmirRoot=$YmirRoot", "--", "--root", $AetheriaRoot, @@ -204,17 +224,19 @@ try { $releaseLock = Get-Content -LiteralPath (Join-Path $repoRoot "ReleaseConsumerProject\Packages\packages-lock.json") -Raw | ConvertFrom-Json $releasedPackageClient = $projectRoot -eq "ReleaseConsumerProject" $sceneLock = $releaseLock.dependencies.'org.gamecult.eve.unity-scene' + $fieldsLock = $releaseLock.dependencies.'org.gamecult.eve.plugin-fields' $uiToolkitLock = $releaseLock.dependencies.'org.gamecult.eve.unity-uitoolkit' $cultLibLock = $releaseLock.dependencies.'org.gamecult.cultlib' if (-not $releasedPackageClient) { throw "Released witness must run ReleaseConsumerProject, got '$projectRoot'." } - if ($sceneLock.source -ne "git" -or $uiToolkitLock.source -ne "git" -or $cultLibLock.source -ne "git" -or - $sceneLock.version -like "file:*" -or $uiToolkitLock.version -like "file:*" -or $cultLibLock.version -like "file:*") { + if ($sceneLock.source -ne "git" -or $fieldsLock.source -ne "git" -or $uiToolkitLock.source -ne "git" -or $cultLibLock.source -ne "git" -or + $sceneLock.version -like "file:*" -or $fieldsLock.version -like "file:*" -or $uiToolkitLock.version -like "file:*" -or $cultLibLock.version -like "file:*") { throw "Released witness resolved a non-git/local package dependency." } if ($sceneLock.hash -ne $expectedEveUnityCommit -or + $fieldsLock.hash -ne $expectedEveFieldsCommit -or $uiToolkitLock.hash -ne $expectedEveUnityUiToolkitCommit -or $cultLibLock.hash -ne $expectedCultLibCommit) { - throw "Released package commits do not match the witnessed releases. scene=$($sceneLock.hash) uitoolkit=$($uiToolkitLock.hash) cultlib=$($cultLibLock.hash)" + throw "Released package commits do not match the witnessed releases. scene=$($sceneLock.hash) fields=$($fieldsLock.hash) uitoolkit=$($uiToolkitLock.hash) cultlib=$($cultLibLock.hash)" } $finalBodies = @(Get-ChildItem -LiteralPath $assetCachePath -Filter *.body -File -Recurse -ErrorAction SilentlyContinue) $finalPartials = @(Get-ChildItem -LiteralPath $assetCachePath -Filter *.partial -File -Recurse -ErrorAction SilentlyContinue) @@ -234,6 +256,7 @@ try { $facts | Add-Member -NotePropertyName releasedPackageClient -NotePropertyValue $releasedPackageClient $facts | Add-Member -NotePropertyName clientProject -NotePropertyValue $projectRoot $facts | Add-Member -NotePropertyName eveUnityPackageCommit -NotePropertyValue $sceneLock.hash + $facts | Add-Member -NotePropertyName eveFieldsPackageCommit -NotePropertyValue $fieldsLock.hash $facts | Add-Member -NotePropertyName eveUnityUiToolkitPackageCommit -NotePropertyValue $uiToolkitLock.hash $facts | Add-Member -NotePropertyName cultLibPackageCommit -NotePropertyValue $cultLibLock.hash $facts | Add-Member -NotePropertyName contentDelivery -NotePropertyValue ([ordered]@{