From b274bb0cf85053c41e5e239c16d8a58bad6df9bd Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Mon, 20 Jul 2026 00:22:32 +0100 Subject: [PATCH 01/58] Prove deterministic released Aetheria session --- .../PlayMode/GenericWorldCaptureTests.cs | 44 ++++++++++++------- ReleaseConsumerProject/Packages/manifest.json | 2 +- .../Packages/packages-lock.json | 6 +-- .../PlayMode/GenericWorldCaptureTests.cs | 44 ++++++++++++------- scripts/run-aetheria-daemon-world-witness.ps1 | 2 +- 5 files changed, 59 insertions(+), 39 deletions(-) diff --git a/ReleaseConsumerProject/Assets/Tests/PlayMode/GenericWorldCaptureTests.cs b/ReleaseConsumerProject/Assets/Tests/PlayMode/GenericWorldCaptureTests.cs index 950713e..6745b04 100644 --- a/ReleaseConsumerProject/Assets/Tests/PlayMode/GenericWorldCaptureTests.cs +++ b/ReleaseConsumerProject/Assets/Tests/PlayMode/GenericWorldCaptureTests.cs @@ -384,30 +384,26 @@ namespace GameCult.EveUnity.GenericClient.PlayModeTests } }; - 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}'."); - Assert.That(provider.CurrentAssetBodyTransportKind, - Is.EqualTo(CultMeshBodyTransportKind.SharedFileMapping), - "The first requested provider bundle was not negotiated as a mapped body."); var marker = FindMarker(root, playerId); - Assert.That( - marker.GetComponentsInChildren(includeInactive: true).Length, - Is.GreaterThan(1), - "The generic client substituted its primitive fallback instead of the provider-authored AssetBundle prefab."); if (requireSessionGameplay) { + EveUnitySceneCommandReceipt observedDockReceipt = null; + string dockCommandId = null; + runtime.ReceiptAvailable += receipt => + { + if (receipt.CommandId == dockCommandId) + observedDockReceipt = receipt; + }; + host.SubmitAdvertisedActionIntent(playerId, "simulation.step"); var dockVersion = runtime.ActiveVersion; var dock = host.SubmitAdvertisedActionIntent(playerId, "pilot.dock"); + dockCommandId = dock.CommandId; var dockDeadline = Time.realtimeSinceStartup + 12f; while (Time.realtimeSinceStartup < dockDeadline && - (runtime.LastReceipt == null || runtime.LastReceipt.CommandId != dock.CommandId || - !string.Equals(runtime.LastReceipt.State, "reconciled", StringComparison.OrdinalIgnoreCase) || - runtime.ActiveVersion < runtime.LastReceipt.SourceVersion || + (observedDockReceipt == null || + !string.Equals(observedDockReceipt.State, "reconciled", StringComparison.OrdinalIgnoreCase) || + runtime.ActiveVersion < observedDockReceipt.SourceVersion || !(host.InputCapability?.Actions ?? Array.Empty()).Any(candidate => candidate != null && candidate.ActionId.StartsWith("trade.buy.", StringComparison.Ordinal) && @@ -416,7 +412,7 @@ namespace GameCult.EveUnity.GenericClient.PlayModeTests yield return new WaitForSecondsRealtime(0.1f); runtime.Refresh(); } - AssertReconciledProviderReceipt(runtime.LastReceipt, dock.CommandId, + AssertReconciledProviderReceipt(observedDockReceipt, dock.CommandId, provider.Selection.ProviderId, provider.Selection.SurfaceId, dockVersion); var buyAction = (host.InputCapability?.Actions ?? Array.Empty()) .FirstOrDefault(candidate => candidate != null && @@ -516,6 +512,20 @@ namespace GameCult.EveUnity.GenericClient.PlayModeTests provider.Selection.ProviderId, provider.Selection.SurfaceId, undockVersion); } + 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}'."); + Assert.That(provider.CurrentAssetBodyTransportKind, + Is.EqualTo(CultMeshBodyTransportKind.SharedFileMapping), + "The first requested provider bundle was not negotiated as a mapped body."); + Assert.That( + marker.GetComponentsInChildren(includeInactive: true).Length, + Is.GreaterThan(1), + "The generic client substituted its primitive fallback instead of the provider-authored AssetBundle prefab."); + var initialPosition = marker.transform.position; initialVersion = runtime.ActiveVersion; var request = runtime.SubmitMoveVectorIntent(playerId, 1f, 0f, 1f); diff --git a/ReleaseConsumerProject/Packages/manifest.json b/ReleaseConsumerProject/Packages/manifest.json index 8cfc7c7..480877b 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.38", "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", "org.gamecult.eve.surface": "https://github.com/GameCult/EveUnity.git?path=/packages/org.gamecult.eve.surface#eveunity-surface-v0.2.4", - "org.gamecult.eve.unity-scene": "https://github.com/GameCult/EveUnity.git?path=/packages/org.gamecult.eve.unity-scene#eveunity-scene-v0.3.97", + "org.gamecult.eve.unity-scene": "https://github.com/GameCult/EveUnity.git?path=/packages/org.gamecult.eve.unity-scene#eveunity-scene-v0.3.98", "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 20ab53d..0653f4f 100644 --- a/ReleaseConsumerProject/Packages/packages-lock.json +++ b/ReleaseConsumerProject/Packages/packages-lock.json @@ -144,18 +144,18 @@ "hash": "e08fa08335f99e9edddeb706912eecfad07cb281" }, "org.gamecult.eve.unity-scene": { - "version": "https://github.com/GameCult/EveUnity.git?path=/packages/org.gamecult.eve.unity-scene#eveunity-scene-v0.3.97", + "version": "https://github.com/GameCult/EveUnity.git?path=/packages/org.gamecult.eve.unity-scene#eveunity-scene-v0.3.98", "depth": 0, "source": "git", "dependencies": { - "org.gamecult.cultlib": "1.0.36", + "org.gamecult.cultlib": "1.0.38", "org.gamecult.eve.plugin-fields": "0.2.3", "org.gamecult.eve.surface": "0.2.4", "com.unity.mathematics": "1.3.2", "com.unity.render-pipelines.universal": "17.4.0", "com.unity.modules.particlesystem": "1.0.0" }, - "hash": "44e338bd9eb4997f2235f7aa711b79bc5d5c12ff" + "hash": "002fbdf3570f533a8728d2a8f808025065d99532" }, "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 950713e..6745b04 100644 --- a/TestProject/Assets/Tests/PlayMode/GenericWorldCaptureTests.cs +++ b/TestProject/Assets/Tests/PlayMode/GenericWorldCaptureTests.cs @@ -384,30 +384,26 @@ namespace GameCult.EveUnity.GenericClient.PlayModeTests } }; - 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}'."); - Assert.That(provider.CurrentAssetBodyTransportKind, - Is.EqualTo(CultMeshBodyTransportKind.SharedFileMapping), - "The first requested provider bundle was not negotiated as a mapped body."); var marker = FindMarker(root, playerId); - Assert.That( - marker.GetComponentsInChildren(includeInactive: true).Length, - Is.GreaterThan(1), - "The generic client substituted its primitive fallback instead of the provider-authored AssetBundle prefab."); if (requireSessionGameplay) { + EveUnitySceneCommandReceipt observedDockReceipt = null; + string dockCommandId = null; + runtime.ReceiptAvailable += receipt => + { + if (receipt.CommandId == dockCommandId) + observedDockReceipt = receipt; + }; + host.SubmitAdvertisedActionIntent(playerId, "simulation.step"); var dockVersion = runtime.ActiveVersion; var dock = host.SubmitAdvertisedActionIntent(playerId, "pilot.dock"); + dockCommandId = dock.CommandId; var dockDeadline = Time.realtimeSinceStartup + 12f; while (Time.realtimeSinceStartup < dockDeadline && - (runtime.LastReceipt == null || runtime.LastReceipt.CommandId != dock.CommandId || - !string.Equals(runtime.LastReceipt.State, "reconciled", StringComparison.OrdinalIgnoreCase) || - runtime.ActiveVersion < runtime.LastReceipt.SourceVersion || + (observedDockReceipt == null || + !string.Equals(observedDockReceipt.State, "reconciled", StringComparison.OrdinalIgnoreCase) || + runtime.ActiveVersion < observedDockReceipt.SourceVersion || !(host.InputCapability?.Actions ?? Array.Empty()).Any(candidate => candidate != null && candidate.ActionId.StartsWith("trade.buy.", StringComparison.Ordinal) && @@ -416,7 +412,7 @@ namespace GameCult.EveUnity.GenericClient.PlayModeTests yield return new WaitForSecondsRealtime(0.1f); runtime.Refresh(); } - AssertReconciledProviderReceipt(runtime.LastReceipt, dock.CommandId, + AssertReconciledProviderReceipt(observedDockReceipt, dock.CommandId, provider.Selection.ProviderId, provider.Selection.SurfaceId, dockVersion); var buyAction = (host.InputCapability?.Actions ?? Array.Empty()) .FirstOrDefault(candidate => candidate != null && @@ -516,6 +512,20 @@ namespace GameCult.EveUnity.GenericClient.PlayModeTests provider.Selection.ProviderId, provider.Selection.SurfaceId, undockVersion); } + 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}'."); + Assert.That(provider.CurrentAssetBodyTransportKind, + Is.EqualTo(CultMeshBodyTransportKind.SharedFileMapping), + "The first requested provider bundle was not negotiated as a mapped body."); + Assert.That( + marker.GetComponentsInChildren(includeInactive: true).Length, + Is.GreaterThan(1), + "The generic client substituted its primitive fallback instead of the provider-authored AssetBundle prefab."); + var initialPosition = marker.transform.position; initialVersion = runtime.ActiveVersion; var request = runtime.SubmitMoveVectorIntent(playerId, 1f, 0f, 1f); diff --git a/scripts/run-aetheria-daemon-world-witness.ps1 b/scripts/run-aetheria-daemon-world-witness.ps1 index ab0a40e..f51bcae 100644 --- a/scripts/run-aetheria-daemon-world-witness.ps1 +++ b/scripts/run-aetheria-daemon-world-witness.ps1 @@ -17,7 +17,7 @@ param( ) $ErrorActionPreference = "Stop" -$expectedEveUnityCommit = "44e338bd9eb4997f2235f7aa711b79bc5d5c12ff" +$expectedEveUnityCommit = "002fbdf3570f533a8728d2a8f808025065d99532" $expectedEveFieldsCommit = "c5a4a75c1b727499b16c2dae1895f29e2a9f72f0" $expectedEveUnityUiToolkitCommit = "4d0cbe0185bdc4fc65eb63503a7c5cb578539669" $expectedCultLibCommit = "c6cf04a342a013afab52063a9b5445a3683700d4" From 91eaa95ad45897f4322a8823a1bc5cddcee59649 Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Mon, 20 Jul 2026 01:02:47 +0100 Subject: [PATCH 02/58] Release eveunity-scene 0.3.99 --- packages/org.gamecult.eve.unity-scene/package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/org.gamecult.eve.unity-scene/package.json b/packages/org.gamecult.eve.unity-scene/package.json index a0b18db..29d7e74 100644 --- a/packages/org.gamecult.eve.unity-scene/package.json +++ b/packages/org.gamecult.eve.unity-scene/package.json @@ -1,11 +1,11 @@ { "name": "org.gamecult.eve.unity-scene", "displayName": "Eve Unity Scene Lowering", - "version": "0.3.98", + "version": "0.3.99", "unity": "6000.0", "description": "Provider-agnostic Unity scene shell for gamecult.eve.surface.v1 interactive world surfaces.", "dependencies": { - "org.gamecult.cultlib": "1.0.38", + "org.gamecult.cultlib": "1.0.39", "org.gamecult.eve.plugin-fields": "0.2.3", "org.gamecult.eve.surface": "0.2.4", "com.unity.mathematics": "1.3.2", From e48243c236874dab02d45d7faed2b1e206a9cb15 Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Mon, 20 Jul 2026 01:04:40 +0100 Subject: [PATCH 03/58] Pin release consumer to CultLib 1.0.39 --- ReleaseConsumerProject/Packages/manifest.json | 4 ++-- ReleaseConsumerProject/Packages/packages-lock.json | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/ReleaseConsumerProject/Packages/manifest.json b/ReleaseConsumerProject/Packages/manifest.json index 480877b..0e5d8bf 100644 --- a/ReleaseConsumerProject/Packages/manifest.json +++ b/ReleaseConsumerProject/Packages/manifest.json @@ -6,10 +6,10 @@ "com.unity.modules.physics": "1.0.0", "com.unity.modules.uielements": "1.0.0", "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.38", + "org.gamecult.cultlib": "https://github.com/GameCult/CultLib.git?path=/unity/org.gamecult.cultlib#cultlib-unity-v1.0.39", "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", "org.gamecult.eve.surface": "https://github.com/GameCult/EveUnity.git?path=/packages/org.gamecult.eve.surface#eveunity-surface-v0.2.4", - "org.gamecult.eve.unity-scene": "https://github.com/GameCult/EveUnity.git?path=/packages/org.gamecult.eve.unity-scene#eveunity-scene-v0.3.98", + "org.gamecult.eve.unity-scene": "https://github.com/GameCult/EveUnity.git?path=/packages/org.gamecult.eve.unity-scene#eveunity-scene-v0.3.99", "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 0653f4f..d0ebf86 100644 --- a/ReleaseConsumerProject/Packages/packages-lock.json +++ b/ReleaseConsumerProject/Packages/packages-lock.json @@ -119,11 +119,11 @@ } }, "org.gamecult.cultlib": { - "version": "https://github.com/GameCult/CultLib.git?path=/unity/org.gamecult.cultlib#cultlib-unity-v1.0.38", + "version": "https://github.com/GameCult/CultLib.git?path=/unity/org.gamecult.cultlib#cultlib-unity-v1.0.39", "depth": 0, "source": "git", "dependencies": {}, - "hash": "c6cf04a342a013afab52063a9b5445a3683700d4" + "hash": "54e2e69ff7f4f5e0a1316bb047c315e65ed2a185" }, "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", @@ -144,18 +144,18 @@ "hash": "e08fa08335f99e9edddeb706912eecfad07cb281" }, "org.gamecult.eve.unity-scene": { - "version": "https://github.com/GameCult/EveUnity.git?path=/packages/org.gamecult.eve.unity-scene#eveunity-scene-v0.3.98", + "version": "https://github.com/GameCult/EveUnity.git?path=/packages/org.gamecult.eve.unity-scene#eveunity-scene-v0.3.99", "depth": 0, "source": "git", "dependencies": { - "org.gamecult.cultlib": "1.0.38", + "org.gamecult.cultlib": "1.0.39", "org.gamecult.eve.plugin-fields": "0.2.3", "org.gamecult.eve.surface": "0.2.4", "com.unity.mathematics": "1.3.2", "com.unity.render-pipelines.universal": "17.4.0", "com.unity.modules.particlesystem": "1.0.0" }, - "hash": "002fbdf3570f533a8728d2a8f808025065d99532" + "hash": "91eaa95ad45897f4322a8823a1bc5cddcee59649" }, "org.gamecult.eve.unity-uitoolkit": { "version": "https://github.com/GameCult/EveUnity.git?path=/packages/org.gamecult.eve.unity-uitoolkit#eveunity-uitoolkit-v0.1.1", From f0441f8990f53db53703ab4cee405bf8f6c27f0a Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Mon, 20 Jul 2026 01:05:40 +0100 Subject: [PATCH 04/58] Advance Aetheria witness package proof --- scripts/run-aetheria-daemon-world-witness.ps1 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/run-aetheria-daemon-world-witness.ps1 b/scripts/run-aetheria-daemon-world-witness.ps1 index f51bcae..8817b4e 100644 --- a/scripts/run-aetheria-daemon-world-witness.ps1 +++ b/scripts/run-aetheria-daemon-world-witness.ps1 @@ -17,10 +17,10 @@ param( ) $ErrorActionPreference = "Stop" -$expectedEveUnityCommit = "002fbdf3570f533a8728d2a8f808025065d99532" +$expectedEveUnityCommit = "91eaa95ad45897f4322a8823a1bc5cddcee59649" $expectedEveFieldsCommit = "c5a4a75c1b727499b16c2dae1895f29e2a9f72f0" $expectedEveUnityUiToolkitCommit = "4d0cbe0185bdc4fc65eb63503a7c5cb578539669" -$expectedCultLibCommit = "c6cf04a342a013afab52063a9b5445a3683700d4" +$expectedCultLibCommit = "54e2e69ff7f4f5e0a1316bb047c315e65ed2a185" $repoRoot = Split-Path -Parent $PSScriptRoot $projectRoot = $ClientProject $outputRoot = if ([IO.Path]::IsPathRooted($OutputDirectory)) { $OutputDirectory } else { Join-Path $repoRoot $OutputDirectory } From dfc886a96ccc7266d5ad2f75fa726235bbc1648f Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Mon, 20 Jul 2026 03:58:03 +0100 Subject: [PATCH 05/58] Correlate commands before receipt delivery --- .../EveUnityCultMeshLiveProviderTransport.cs | 36 +++++++++++-------- 1 file changed, 22 insertions(+), 14 deletions(-) diff --git a/packages/org.gamecult.eve.unity-scene/Runtime/CultMesh/EveUnityCultMeshLiveProviderTransport.cs b/packages/org.gamecult.eve.unity-scene/Runtime/CultMesh/EveUnityCultMeshLiveProviderTransport.cs index 404701b..edf1e38 100644 --- a/packages/org.gamecult.eve.unity-scene/Runtime/CultMesh/EveUnityCultMeshLiveProviderTransport.cs +++ b/packages/org.gamecult.eve.unity-scene/Runtime/CultMesh/EveUnityCultMeshLiveProviderTransport.cs @@ -387,21 +387,29 @@ namespace GameCult.Eve.UnityScene if (string.IsNullOrWhiteSpace(commandId)) throw new InvalidOperationException("Eve command invocations require an idempotency key."); - var recordKey = ChildRecordKey(interaction.CommandRecordRef, commandId); - var message = _networkRegistry!.CreateRawDocumentPutMessage( - $"eve-unity-{commandId}", - new CultRecordHandle(new CultRecordKey(recordKey)), - request, - new CultNetDocumentMessageOptions - { - SourceRuntimeId = _runtimeId, - SourceRole = "eve-unity" - }); - new CultNetSchemaWriteForwarder(new CultNetSchemaWriteForwarderOptions()) - .ForwardPutAsync(_replicaShard!, message) - .GetAwaiter() - .GetResult(); _pendingCommandIds.Add(commandId); + try + { + var recordKey = ChildRecordKey(interaction.CommandRecordRef, commandId); + var message = _networkRegistry!.CreateRawDocumentPutMessage( + $"eve-unity-{commandId}", + new CultRecordHandle(new CultRecordKey(recordKey)), + request, + new CultNetDocumentMessageOptions + { + SourceRuntimeId = _runtimeId, + SourceRole = "eve-unity" + }); + new CultNetSchemaWriteForwarder(new CultNetSchemaWriteForwarderOptions()) + .ForwardPutAsync(_replicaShard!, message) + .GetAwaiter() + .GetResult(); + } + catch + { + _pendingCommandIds.Remove(commandId); + throw; + } } public void Dispose() From 3632bfb3354065b78a90b921d7025c4c3fa92be4 Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Mon, 20 Jul 2026 03:58:17 +0100 Subject: [PATCH 06/58] Release eveunity-scene 0.3.100 --- packages/org.gamecult.eve.unity-scene/package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/org.gamecult.eve.unity-scene/package.json b/packages/org.gamecult.eve.unity-scene/package.json index 29d7e74..16751a7 100644 --- a/packages/org.gamecult.eve.unity-scene/package.json +++ b/packages/org.gamecult.eve.unity-scene/package.json @@ -1,11 +1,11 @@ { "name": "org.gamecult.eve.unity-scene", "displayName": "Eve Unity Scene Lowering", - "version": "0.3.99", + "version": "0.3.100", "unity": "6000.0", "description": "Provider-agnostic Unity scene shell for gamecult.eve.surface.v1 interactive world surfaces.", "dependencies": { - "org.gamecult.cultlib": "1.0.39", + "org.gamecult.cultlib": "1.0.43", "org.gamecult.eve.plugin-fields": "0.2.3", "org.gamecult.eve.surface": "0.2.4", "com.unity.mathematics": "1.3.2", From 45b828d5b368994c974af1ba23abd246fc7f8ca0 Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Mon, 20 Jul 2026 04:18:13 +0100 Subject: [PATCH 07/58] Prove released split-bundle Aetheria sessions --- .../PlayMode/GenericWorldCaptureTests.cs | 111 ++++++++++-------- ReleaseConsumerProject/Packages/manifest.json | 4 +- .../Packages/packages-lock.json | 10 +- .../PlayMode/GenericWorldCaptureTests.cs | 111 ++++++++++-------- docs/aetheria-released-package-witness.md | 104 +++++++--------- scripts/run-aetheria-daemon-world-witness.ps1 | 75 +++++++----- 6 files changed, 214 insertions(+), 201 deletions(-) diff --git a/ReleaseConsumerProject/Assets/Tests/PlayMode/GenericWorldCaptureTests.cs b/ReleaseConsumerProject/Assets/Tests/PlayMode/GenericWorldCaptureTests.cs index 6745b04..8cb8b1d 100644 --- a/ReleaseConsumerProject/Assets/Tests/PlayMode/GenericWorldCaptureTests.cs +++ b/ReleaseConsumerProject/Assets/Tests/PlayMode/GenericWorldCaptureTests.cs @@ -526,45 +526,48 @@ namespace GameCult.EveUnity.GenericClient.PlayModeTests Is.GreaterThan(1), "The generic client substituted its primitive fallback instead of the provider-authored AssetBundle prefab."); - var initialPosition = marker.transform.position; initialVersion = runtime.ActiveVersion; - var request = runtime.SubmitMoveVectorIntent(playerId, 1f, 0f, 1f); - - var deadline = Time.realtimeSinceStartup + 12f; - while (Time.realtimeSinceStartup < deadline && - (runtime.LastReceipt == null || runtime.LastReceipt.CommandId != request.CommandId || - !string.Equals(runtime.LastReceipt.State, "reconciled", StringComparison.OrdinalIgnoreCase) || - runtime.ActiveVersion <= initialVersion || - Vector3.Distance(FindMarker(root, playerId).transform.position, initialPosition) < 0.01f)) + if (requireSessionGameplay) { - yield return new WaitForSecondsRealtime(0.1f); - runtime.Refresh(); - } + var initialPosition = marker.transform.position; + var request = runtime.SubmitMoveVectorIntent(playerId, 1f, 0f, 1f); - AssertReconciledProviderReceipt( - runtime.LastReceipt, - request.CommandId, - provider.Selection.ProviderId, - provider.Selection.SurfaceId, - initialVersion); - Assert.That(runtime.ActiveVersion, Is.GreaterThan(initialVersion)); - Assert.That(runtime.ActiveVersion, Is.GreaterThanOrEqualTo(runtime.LastReceipt.SourceVersion)); - movementDistance = Vector3.Distance(FindMarker(root, playerId).transform.position, initialPosition); - Assert.That(movementDistance, Is.GreaterThan(0.01f)); - movementReceipt = WitnessReceipt.From("movement", runtime.LastReceipt); + var deadline = Time.realtimeSinceStartup + 12f; + while (Time.realtimeSinceStartup < deadline && + (runtime.LastReceipt == null || runtime.LastReceipt.CommandId != request.CommandId || + !string.Equals(runtime.LastReceipt.State, "reconciled", StringComparison.OrdinalIgnoreCase) || + runtime.ActiveVersion <= initialVersion || + Vector3.Distance(FindMarker(root, playerId).transform.position, initialPosition) < 0.01f)) + { + yield return new WaitForSecondsRealtime(0.1f); + runtime.Refresh(); + } - var releaseVersion = runtime.ActiveVersion; - var release = runtime.SubmitMoveVectorIntent(playerId, 0f, 0f, 0f); - var releaseDeadline = Time.realtimeSinceStartup + 12f; - while (Time.realtimeSinceStartup < releaseDeadline && - (runtime.LastReceipt == null || runtime.LastReceipt.CommandId != release.CommandId || - !string.Equals(runtime.LastReceipt.State, "reconciled", StringComparison.OrdinalIgnoreCase))) - { - yield return new WaitForSecondsRealtime(0.1f); - runtime.Refresh(); + AssertReconciledProviderReceipt( + runtime.LastReceipt, + request.CommandId, + provider.Selection.ProviderId, + provider.Selection.SurfaceId, + initialVersion); + Assert.That(runtime.ActiveVersion, Is.GreaterThan(initialVersion)); + Assert.That(runtime.ActiveVersion, Is.GreaterThanOrEqualTo(runtime.LastReceipt.SourceVersion)); + movementDistance = Vector3.Distance(FindMarker(root, playerId).transform.position, initialPosition); + Assert.That(movementDistance, Is.GreaterThan(0.01f)); + movementReceipt = WitnessReceipt.From("movement", runtime.LastReceipt); + + var releaseVersion = runtime.ActiveVersion; + var release = runtime.SubmitMoveVectorIntent(playerId, 0f, 0f, 0f); + var releaseDeadline = Time.realtimeSinceStartup + 12f; + while (Time.realtimeSinceStartup < releaseDeadline && + (runtime.LastReceipt == null || runtime.LastReceipt.CommandId != release.CommandId || + !string.Equals(runtime.LastReceipt.State, "reconciled", StringComparison.OrdinalIgnoreCase))) + { + yield return new WaitForSecondsRealtime(0.1f); + runtime.Refresh(); + } + AssertReconciledProviderReceipt(runtime.LastReceipt, release.CommandId, + provider.Selection.ProviderId, provider.Selection.SurfaceId, releaseVersion); } - AssertReconciledProviderReceipt(runtime.LastReceipt, release.CommandId, - provider.Selection.ProviderId, provider.Selection.SurfaceId, releaseVersion); if (requireSessionGameplay) { @@ -924,7 +927,8 @@ namespace GameCult.EveUnity.GenericClient.PlayModeTests .Where(material => material != null) .Distinct() .Count(HasAnyTexture); - Assert.That(texturedPlayerMaterialCount, Is.GreaterThanOrEqualTo(8), + var expectedTexturedPlayerMaterialCount = requireSessionGameplay ? 8 : 7; + Assert.That(texturedPlayerMaterialCount, Is.GreaterThanOrEqualTo(expectedTexturedPlayerMaterialCount), "The provider bundle must retain its advertised native material textures."); var directionalLightIntensity = UnityEngine.Object.FindObjectsOfType() .Where(light => light != null && light.enabled && light.type == LightType.Directional) @@ -1015,25 +1019,28 @@ namespace GameCult.EveUnity.GenericClient.PlayModeTests File.WriteAllBytes(mapCapturePath, pixels.EncodeToPNG()); Assert.That(new FileInfo(mapCapturePath).Length, Is.GreaterThan(1024)); Debug.Log($"EveUnity witness phase map-capture took {mapCaptureElapsed.Elapsed.TotalMilliseconds:0.###}ms."); - var tractorReleaseVersion = runtime.ActiveVersion; - var tractorRelease = host.SubmitAdvertisedActionValueIntent( - playerId, - beamPresentation.ActivationActionId, - 0f); - var tractorReleaseDeadline = Time.realtimeSinceStartup + 12f; - while (Time.realtimeSinceStartup < tractorReleaseDeadline && - (runtime.LastReceipt == null || runtime.LastReceipt.CommandId != tractorRelease.CommandId || - !string.Equals(runtime.LastReceipt.State, "reconciled", StringComparison.OrdinalIgnoreCase) || - runtime.ActiveVersion < runtime.LastReceipt.SourceVersion)) + if (requireSessionGameplay) { - yield return new WaitForSecondsRealtime(0.1f); - runtime.Refresh(); + var tractorReleaseVersion = runtime.ActiveVersion; + var tractorRelease = host.SubmitAdvertisedActionValueIntent( + playerId, + beamPresentation.ActivationActionId, + 0f); + var tractorReleaseDeadline = Time.realtimeSinceStartup + 12f; + while (Time.realtimeSinceStartup < tractorReleaseDeadline && + (runtime.LastReceipt == null || runtime.LastReceipt.CommandId != tractorRelease.CommandId || + !string.Equals(runtime.LastReceipt.State, "reconciled", StringComparison.OrdinalIgnoreCase) || + runtime.ActiveVersion < runtime.LastReceipt.SourceVersion)) + { + yield return new WaitForSecondsRealtime(0.1f); + runtime.Refresh(); + } + AssertReconciledProviderReceipt(runtime.LastReceipt, tractorRelease.CommandId, + provider.Selection.ProviderId, provider.Selection.SurfaceId, tractorReleaseVersion); + tractorReleaseReceipt = WitnessReceipt.From("beam-release", runtime.LastReceipt); + beamRenderer.RefreshNow(); + Assert.That(beamRenderer.TryGetPower(beamPresentation.Id, out tractorReleasedPower), Is.True); } - AssertReconciledProviderReceipt(runtime.LastReceipt, tractorRelease.CommandId, - provider.Selection.ProviderId, provider.Selection.SurfaceId, tractorReleaseVersion); - tractorReleaseReceipt = WitnessReceipt.From("beam-release", runtime.LastReceipt); - beamRenderer.RefreshNow(); - Assert.That(beamRenderer.TryGetPower(beamPresentation.Id, out tractorReleasedPower), Is.True); var fieldVolume = root.GetComponent(); Assert.That(fieldVolume, Is.Not.Null, "The generic client did not install its Fields volume lowerer."); Assert.That(fieldVolume.PresentedFrameId, Is.GreaterThanOrEqualTo(0), diff --git a/ReleaseConsumerProject/Packages/manifest.json b/ReleaseConsumerProject/Packages/manifest.json index 0e5d8bf..0a14d7d 100644 --- a/ReleaseConsumerProject/Packages/manifest.json +++ b/ReleaseConsumerProject/Packages/manifest.json @@ -6,10 +6,10 @@ "com.unity.modules.physics": "1.0.0", "com.unity.modules.uielements": "1.0.0", "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.39", + "org.gamecult.cultlib": "https://github.com/GameCult/CultLib.git?path=/unity/org.gamecult.cultlib#cultlib-unity-v1.0.43", "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", "org.gamecult.eve.surface": "https://github.com/GameCult/EveUnity.git?path=/packages/org.gamecult.eve.surface#eveunity-surface-v0.2.4", - "org.gamecult.eve.unity-scene": "https://github.com/GameCult/EveUnity.git?path=/packages/org.gamecult.eve.unity-scene#eveunity-scene-v0.3.99", + "org.gamecult.eve.unity-scene": "https://github.com/GameCult/EveUnity.git?path=/packages/org.gamecult.eve.unity-scene#eveunity-scene-v0.3.100", "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 d0ebf86..f2d0ff9 100644 --- a/ReleaseConsumerProject/Packages/packages-lock.json +++ b/ReleaseConsumerProject/Packages/packages-lock.json @@ -119,11 +119,11 @@ } }, "org.gamecult.cultlib": { - "version": "https://github.com/GameCult/CultLib.git?path=/unity/org.gamecult.cultlib#cultlib-unity-v1.0.39", + "version": "https://github.com/GameCult/CultLib.git?path=/unity/org.gamecult.cultlib#cultlib-unity-v1.0.43", "depth": 0, "source": "git", "dependencies": {}, - "hash": "54e2e69ff7f4f5e0a1316bb047c315e65ed2a185" + "hash": "f67f5122ed1bd11da016e7b820ed60145ccd0299" }, "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", @@ -144,18 +144,18 @@ "hash": "e08fa08335f99e9edddeb706912eecfad07cb281" }, "org.gamecult.eve.unity-scene": { - "version": "https://github.com/GameCult/EveUnity.git?path=/packages/org.gamecult.eve.unity-scene#eveunity-scene-v0.3.99", + "version": "https://github.com/GameCult/EveUnity.git?path=/packages/org.gamecult.eve.unity-scene#eveunity-scene-v0.3.100", "depth": 0, "source": "git", "dependencies": { - "org.gamecult.cultlib": "1.0.39", + "org.gamecult.cultlib": "1.0.43", "org.gamecult.eve.plugin-fields": "0.2.3", "org.gamecult.eve.surface": "0.2.4", "com.unity.mathematics": "1.3.2", "com.unity.render-pipelines.universal": "17.4.0", "com.unity.modules.particlesystem": "1.0.0" }, - "hash": "91eaa95ad45897f4322a8823a1bc5cddcee59649" + "hash": "3632bfb3354065b78a90b921d7025c4c3fa92be4" }, "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 6745b04..8cb8b1d 100644 --- a/TestProject/Assets/Tests/PlayMode/GenericWorldCaptureTests.cs +++ b/TestProject/Assets/Tests/PlayMode/GenericWorldCaptureTests.cs @@ -526,45 +526,48 @@ namespace GameCult.EveUnity.GenericClient.PlayModeTests Is.GreaterThan(1), "The generic client substituted its primitive fallback instead of the provider-authored AssetBundle prefab."); - var initialPosition = marker.transform.position; initialVersion = runtime.ActiveVersion; - var request = runtime.SubmitMoveVectorIntent(playerId, 1f, 0f, 1f); - - var deadline = Time.realtimeSinceStartup + 12f; - while (Time.realtimeSinceStartup < deadline && - (runtime.LastReceipt == null || runtime.LastReceipt.CommandId != request.CommandId || - !string.Equals(runtime.LastReceipt.State, "reconciled", StringComparison.OrdinalIgnoreCase) || - runtime.ActiveVersion <= initialVersion || - Vector3.Distance(FindMarker(root, playerId).transform.position, initialPosition) < 0.01f)) + if (requireSessionGameplay) { - yield return new WaitForSecondsRealtime(0.1f); - runtime.Refresh(); - } + var initialPosition = marker.transform.position; + var request = runtime.SubmitMoveVectorIntent(playerId, 1f, 0f, 1f); - AssertReconciledProviderReceipt( - runtime.LastReceipt, - request.CommandId, - provider.Selection.ProviderId, - provider.Selection.SurfaceId, - initialVersion); - Assert.That(runtime.ActiveVersion, Is.GreaterThan(initialVersion)); - Assert.That(runtime.ActiveVersion, Is.GreaterThanOrEqualTo(runtime.LastReceipt.SourceVersion)); - movementDistance = Vector3.Distance(FindMarker(root, playerId).transform.position, initialPosition); - Assert.That(movementDistance, Is.GreaterThan(0.01f)); - movementReceipt = WitnessReceipt.From("movement", runtime.LastReceipt); + var deadline = Time.realtimeSinceStartup + 12f; + while (Time.realtimeSinceStartup < deadline && + (runtime.LastReceipt == null || runtime.LastReceipt.CommandId != request.CommandId || + !string.Equals(runtime.LastReceipt.State, "reconciled", StringComparison.OrdinalIgnoreCase) || + runtime.ActiveVersion <= initialVersion || + Vector3.Distance(FindMarker(root, playerId).transform.position, initialPosition) < 0.01f)) + { + yield return new WaitForSecondsRealtime(0.1f); + runtime.Refresh(); + } - var releaseVersion = runtime.ActiveVersion; - var release = runtime.SubmitMoveVectorIntent(playerId, 0f, 0f, 0f); - var releaseDeadline = Time.realtimeSinceStartup + 12f; - while (Time.realtimeSinceStartup < releaseDeadline && - (runtime.LastReceipt == null || runtime.LastReceipt.CommandId != release.CommandId || - !string.Equals(runtime.LastReceipt.State, "reconciled", StringComparison.OrdinalIgnoreCase))) - { - yield return new WaitForSecondsRealtime(0.1f); - runtime.Refresh(); + AssertReconciledProviderReceipt( + runtime.LastReceipt, + request.CommandId, + provider.Selection.ProviderId, + provider.Selection.SurfaceId, + initialVersion); + Assert.That(runtime.ActiveVersion, Is.GreaterThan(initialVersion)); + Assert.That(runtime.ActiveVersion, Is.GreaterThanOrEqualTo(runtime.LastReceipt.SourceVersion)); + movementDistance = Vector3.Distance(FindMarker(root, playerId).transform.position, initialPosition); + Assert.That(movementDistance, Is.GreaterThan(0.01f)); + movementReceipt = WitnessReceipt.From("movement", runtime.LastReceipt); + + var releaseVersion = runtime.ActiveVersion; + var release = runtime.SubmitMoveVectorIntent(playerId, 0f, 0f, 0f); + var releaseDeadline = Time.realtimeSinceStartup + 12f; + while (Time.realtimeSinceStartup < releaseDeadline && + (runtime.LastReceipt == null || runtime.LastReceipt.CommandId != release.CommandId || + !string.Equals(runtime.LastReceipt.State, "reconciled", StringComparison.OrdinalIgnoreCase))) + { + yield return new WaitForSecondsRealtime(0.1f); + runtime.Refresh(); + } + AssertReconciledProviderReceipt(runtime.LastReceipt, release.CommandId, + provider.Selection.ProviderId, provider.Selection.SurfaceId, releaseVersion); } - AssertReconciledProviderReceipt(runtime.LastReceipt, release.CommandId, - provider.Selection.ProviderId, provider.Selection.SurfaceId, releaseVersion); if (requireSessionGameplay) { @@ -924,7 +927,8 @@ namespace GameCult.EveUnity.GenericClient.PlayModeTests .Where(material => material != null) .Distinct() .Count(HasAnyTexture); - Assert.That(texturedPlayerMaterialCount, Is.GreaterThanOrEqualTo(8), + var expectedTexturedPlayerMaterialCount = requireSessionGameplay ? 8 : 7; + Assert.That(texturedPlayerMaterialCount, Is.GreaterThanOrEqualTo(expectedTexturedPlayerMaterialCount), "The provider bundle must retain its advertised native material textures."); var directionalLightIntensity = UnityEngine.Object.FindObjectsOfType() .Where(light => light != null && light.enabled && light.type == LightType.Directional) @@ -1015,25 +1019,28 @@ namespace GameCult.EveUnity.GenericClient.PlayModeTests File.WriteAllBytes(mapCapturePath, pixels.EncodeToPNG()); Assert.That(new FileInfo(mapCapturePath).Length, Is.GreaterThan(1024)); Debug.Log($"EveUnity witness phase map-capture took {mapCaptureElapsed.Elapsed.TotalMilliseconds:0.###}ms."); - var tractorReleaseVersion = runtime.ActiveVersion; - var tractorRelease = host.SubmitAdvertisedActionValueIntent( - playerId, - beamPresentation.ActivationActionId, - 0f); - var tractorReleaseDeadline = Time.realtimeSinceStartup + 12f; - while (Time.realtimeSinceStartup < tractorReleaseDeadline && - (runtime.LastReceipt == null || runtime.LastReceipt.CommandId != tractorRelease.CommandId || - !string.Equals(runtime.LastReceipt.State, "reconciled", StringComparison.OrdinalIgnoreCase) || - runtime.ActiveVersion < runtime.LastReceipt.SourceVersion)) + if (requireSessionGameplay) { - yield return new WaitForSecondsRealtime(0.1f); - runtime.Refresh(); + var tractorReleaseVersion = runtime.ActiveVersion; + var tractorRelease = host.SubmitAdvertisedActionValueIntent( + playerId, + beamPresentation.ActivationActionId, + 0f); + var tractorReleaseDeadline = Time.realtimeSinceStartup + 12f; + while (Time.realtimeSinceStartup < tractorReleaseDeadline && + (runtime.LastReceipt == null || runtime.LastReceipt.CommandId != tractorRelease.CommandId || + !string.Equals(runtime.LastReceipt.State, "reconciled", StringComparison.OrdinalIgnoreCase) || + runtime.ActiveVersion < runtime.LastReceipt.SourceVersion)) + { + yield return new WaitForSecondsRealtime(0.1f); + runtime.Refresh(); + } + AssertReconciledProviderReceipt(runtime.LastReceipt, tractorRelease.CommandId, + provider.Selection.ProviderId, provider.Selection.SurfaceId, tractorReleaseVersion); + tractorReleaseReceipt = WitnessReceipt.From("beam-release", runtime.LastReceipt); + beamRenderer.RefreshNow(); + Assert.That(beamRenderer.TryGetPower(beamPresentation.Id, out tractorReleasedPower), Is.True); } - AssertReconciledProviderReceipt(runtime.LastReceipt, tractorRelease.CommandId, - provider.Selection.ProviderId, provider.Selection.SurfaceId, tractorReleaseVersion); - tractorReleaseReceipt = WitnessReceipt.From("beam-release", runtime.LastReceipt); - beamRenderer.RefreshNow(); - Assert.That(beamRenderer.TryGetPower(beamPresentation.Id, out tractorReleasedPower), Is.True); var fieldVolume = root.GetComponent(); Assert.That(fieldVolume, Is.Not.Null, "The generic client did not install its Fields volume lowerer."); Assert.That(fieldVolume.PresentedFrameId, Is.GreaterThanOrEqualTo(0), diff --git a/docs/aetheria-released-package-witness.md b/docs/aetheria-released-package-witness.md index a8a7925..b38f763 100644 --- a/docs/aetheria-released-package-witness.md +++ b/docs/aetheria-released-package-witness.md @@ -4,84 +4,64 @@ The generic `ReleaseConsumerProject` is pinned to released Git packages: -- `org.gamecult.eve.unity-scene` `0.3.94`, commit - `0463ddff4e39c9cca14e632b7e46b6b0081c67cc`, and +- `org.gamecult.eve.unity-scene` `0.3.100`, commit + `3632bfb3354065b78a90b921d7025c4c3fa92be4`, and `org.gamecult.eve.surface` `0.2.4`, commit `e08fa08335f99e9edddeb706912eecfad07cb281`; - `org.gamecult.eve.plugin-fields` `0.2.3`, commit `c5a4a75c1b727499b16c2dae1895f29e2a9f72f0`; - `org.gamecult.eve.unity-uitoolkit` `0.1.1`, commit `4d0cbe0185bdc4fc65eb63503a7c5cb578539669`; -- `org.gamecult.cultlib` `1.0.34`, commit - `c148d891c1d8713285ae15b2f17b59c106fb9426`. +- `org.gamecult.cultlib` `1.0.43`, commit + `f67f5122ed1bd11da016e7b820ed60145ccd0299`. -The hand-run integration gate is the warm released-package witness: +The hand-run integration gates are the cold lowering proof and the warm +full-session proof: ```powershell pwsh -File .\scripts\run-aetheria-daemon-world-witness.ps1 ` - -CacheState warm ` - -AssetCacheDirectory E:\Projects\Aetheria\Aetheria.Unity\Build\AssetCache ` - -OutputDirectory artifacts\dockyard-trade-warm-8 ` + -CultLibRoot E:\Projects\CultLib-codex-cultmesh-reliability ` + -CacheState cold ` + -AssetCacheDirectory artifacts\cultlib-1043-scene-03100-cold-cache ` + -OutputDirectory artifacts\cultlib-1043-scene-03100-cold ` -SkipAssetBundleBuild ``` -It connects directly to the Aetheria daemon and must produce one passing -PlayMode result, a pilot capture, a map-only capture, and typed witness facts. -The current assertions require provider-owned assets, movement, camera-channel -isolation, combat, destruction-created loot, daemon-owned XZ proximity -collection or capacity refusal, held tractor input, and authoritative receipts. -They do not require or accept Ymir contact identity as the owner of looting. -The explicit prime flag rebuilds the provider bundle and installs that exact -content-hashed body into the local warm cache. This is convenient local setup, -not CDN-transfer evidence. On later runs, use `-SkipAssetBundleBuild` and omit -the prime flag when neither provider assets nor their catalog changed. +Both connect directly to the Aetheria daemon. Control, subscriptions, commands, +and receipts use `cultnet+tcp`; provider files use `cultmesh-content+tcp`; +same-machine entity state uses a mapped SoA body; and the released native QUIC +client receives the advertised latest-only realtime body. No Odin hop or +Aetheria-specific gameplay code participates in the Unity runtime. -The split transport proof now passes warm and cold. Control/discovery, -subscriptions, commands, and receipts use `cultnet+tcp`; the same-machine entity -body resolves to `SharedFileMapping`; CDN bytes use a separate -`cultmesh-content+tcp` endpoint. The empty-cache run in -`artifacts/aetheria-daemon-tcp-cold` began with zero bodies and zero partials, -transferred the 56,204,750-byte provider bundle in 215 TCP content chunks, -verified hash `f2c8acf938998703799e909cbf0c6e3ed9ad5d6f83b708a0b7b6e76ee33f7160`, -and atomically promoted one body with no partial left behind. The cold-start -Unity test completed in 28.4 seconds and the complete wrapper in 82.1 seconds. -The prior path that carried bundle chunks through batched snapshot records -timed out and remains rejected; this passing evidence belongs only to the -dedicated TCP content plane. +The current cold proof is +`artifacts/cultlib-1043-scene-03100-cold5/runtime-witness.cold.json`. It began +with no bodies or partials and passed in 57.8 seconds end to end (14.9 seconds +inside PlayMode). The client materialized nine requested bundles totaling +56,872,521 bytes: shaders, core resources, UI, Longinus, tractor beam, the three +visible celestial types, and the visible asteroid. Every body matches the +current provider file by SHA-256 and length; no partial remained. Unused ships, +stations, shield, mine, turret, pickup, lightning, and explosion bundles stayed +unloaded. This cold profile owns content transfer, world lowering, fog and +Stardust execution, and pilot/map camera isolation. It deliberately issues no +gameplay command into the paused Terminus fixture. -The July 19 released-package run in `artifacts/aetheria-daemon-tcp-warm` passes the -full-session gate. The generic client lowers the provider-owned bundle through -`SharedFileMapping`, docks at the daemon-authored 12-berth Zenith Dockyard, -buys and sells one typed item, undocks, moves, targets, aims, runs a held tractor -beam, destroys the sole proof hostile, and collects the resulting loot exactly -once through daemon-owned proximity. All eight retained witness receipts are -provider-owned and reconciled. The run records 65,536 Stardust particles, 396 -field composites/dispatches/draws, and both camera-channel invariants. The pilot -capture contains the 3D fog/Stardust/ship frame and no map icons; the map capture -contains the map icons against its map background. The pilot image remains -visually overexposed and is integration evidence, not final art approval. -The wrapper starts the client before the daemon so package import time does not -advance the live proof world. EveUnity 0.3.94 reports a refused TCP rendezvous as -a retryable provider-preparation failure; once the daemon binds, the same generic -preparation path connects without an Aetheria-specific reconnect shim. +The current warm full-session proof is +`artifacts/cultlib-1043-scene-03100-warm/runtime-witness.warm.json`. It passed in +70.7 seconds end to end (27.9 seconds inside PlayMode). The generic client docks, +buys and sells one typed item, resumes and undocks, moves, targets, aims, holds +and releases the tractor beam, destroys the proof hostile, and collects the +resulting loot exactly once through daemon-owned XZ proximity. All eight +retained receipts are provider-owned and reconciled. Eleven on-demand bundles +totaling 74,191,724 bytes were present by the end; gameplay loaded only the +additional content it exercised. The pilot camera excludes all eleven map +renderers and the map camera includes them. -The July 19 transport-boundary run in `artifacts/demanded-fields-warm-6` -passed its released-consumer PlayMode test and produced both camera captures. -The provider published the requested Fields viewport only after CultMesh -observed the client's exact record/schema demand. Entity state used the -same-machine mapped SoA body, while the one-second Eve surface remained a -separate reactive control/presentation stream. The facts record 65,536 -Stardust particles, 145 field composites and draws, pilot/map camera isolation, -movement, combat, exactly-once destruction-loot collection, tractor activation, -and a reconciled held-input release. Terminus attention had paused simulation, -so the released target latch coexists honestly with frozen ramped beam power -`0.84`; the generic client does not manufacture a visual zero. - -The wrapper currently rejects this otherwise passing run after Unity exits -because its full-session gate requires docked purchase/sale fields that the -generic capture test does not yet emit. Consequently this run is transport and -gameplay evidence, not a completed full-session readiness witness, and no -`runtime-witness.json` was issued. +The pilot capture proves that provider fog, Stardust, celestial meshes, and ship +meshes reach URP, but it is not final visual approval: hulls remain crushed to +black and the cloud frame remains too hot and cyan-grey. Those are provider +visual-lever and shader-calibration issues, not concealed transport failures. +The map capture is clean and contains only map-channel glyphs on its dark map +background. ## Historical witness ledger diff --git a/scripts/run-aetheria-daemon-world-witness.ps1 b/scripts/run-aetheria-daemon-world-witness.ps1 index 8817b4e..337469e 100644 --- a/scripts/run-aetheria-daemon-world-witness.ps1 +++ b/scripts/run-aetheria-daemon-world-witness.ps1 @@ -17,10 +17,10 @@ param( ) $ErrorActionPreference = "Stop" -$expectedEveUnityCommit = "91eaa95ad45897f4322a8823a1bc5cddcee59649" +$expectedEveUnityCommit = "3632bfb3354065b78a90b921d7025c4c3fa92be4" $expectedEveFieldsCommit = "c5a4a75c1b727499b16c2dae1895f29e2a9f72f0" $expectedEveUnityUiToolkitCommit = "4d0cbe0185bdc4fc65eb63503a7c5cb578539669" -$expectedCultLibCommit = "54e2e69ff7f4f5e0a1316bb047c315e65ed2a185" +$expectedCultLibCommit = "f67f5122ed1bd11da016e7b820ed60145ccd0299" $repoRoot = Split-Path -Parent $PSScriptRoot $projectRoot = $ClientProject $outputRoot = if ([IO.Path]::IsPathRooted($OutputDirectory)) { $OutputDirectory } else { Join-Path $repoRoot $OutputDirectory } @@ -140,13 +140,15 @@ if (-not $SkipAssetBundleBuild) { throw "Aetheria AssetBundle build failed with exit code $($bundleBuilder.ExitCode)" } } +$providerBundlePaths = @(Get-ChildItem -LiteralPath $providerBundleDirectory -Filter "aetheria-*" -File -ErrorAction SilentlyContinue | + Where-Object { $_.Extension -ne ".manifest" } | Select-Object -ExpandProperty FullName) +if ($providerBundlePaths.Count -eq 0) { + throw "Provider-owned Aetheria bundles are missing: $providerBundleDirectory" +} if ($PrimeWarmCacheFromProviderBundle) { if ($CacheState -ne "warm") { throw "Local provider-bundle priming is only valid for an explicitly warm witness." } - $providerBundlePaths = @(Get-ChildItem -LiteralPath $providerBundleDirectory -Filter "aetheria-*" -File -ErrorAction SilentlyContinue | - Where-Object { $_.Extension -ne ".manifest" } | Select-Object -ExpandProperty FullName) - if ($providerBundlePaths.Count -eq 0) { throw "Provider-owned Aetheria bundles are missing: $providerBundleDirectory" } New-Item -ItemType Directory -Force -Path $assetCachePath | Out-Null foreach ($providerBundlePath in $providerBundlePaths) { $primeHash = (Get-FileHash -LiteralPath $providerBundlePath -Algorithm SHA256).Hash.ToLowerInvariant() @@ -164,16 +166,9 @@ if ($PrimeWarmCacheFromProviderBundle) { Write-Host "Warm cache primed locally from $($providerBundlePaths.Count) provider bundles; this is not CDN-transfer evidence: $assetCachePath" } if ($CacheState -eq "warm") { - $providerBundlePaths = @(Get-ChildItem -LiteralPath $providerBundleDirectory -Filter "aetheria-*" -File -ErrorAction SilentlyContinue | - Where-Object { $_.Extension -ne ".manifest" } | Select-Object -ExpandProperty FullName) - if ($providerBundlePaths.Count -eq 0) { throw "Provider-owned Aetheria bundles are missing: $providerBundleDirectory" } - foreach ($providerBundlePath in $providerBundlePaths) { - $currentBundleHash = (Get-FileHash -LiteralPath $providerBundlePath -Algorithm SHA256).Hash.ToLowerInvariant() - $currentWarmBody = Get-ChildItem -LiteralPath $assetCachePath -Filter "$currentBundleHash.body" -File -Recurse -ErrorAction SilentlyContinue | - Select-Object -First 1 - if ($null -eq $currentWarmBody) { - throw "Warm witness cache does not contain current provider bundle '$providerBundlePath'. expected=$currentBundleHash cache=$assetCachePath" - } + $currentWarmBodies = @(Get-ChildItem -LiteralPath $assetCachePath -Filter *.body -File -Recurse -ErrorAction SilentlyContinue) + if ($currentWarmBodies.Count -eq 0) { + throw "Warm witness cache contains no promoted provider bodies: $assetCachePath" } } @@ -283,14 +278,14 @@ try { throw "Live witness ran the wrong proof profile. expected=$witnessProfile actual=$($facts.witnessProfile)" } if (-not $facts.providerAssets -or -not $facts.environmentPresentation -or - -not $facts.movement -or -not $facts.pilotCameraExcludesMapChannel -or + -not $facts.pilotCameraExcludesMapChannel -or -not $facts.mapCameraIncludesMapChannel -or [int]$facts.fieldVolumeLayerCount -le 0 -or [long]$facts.fieldVolumeCompositeCount -le 0 -or [int]$facts.fieldParticleCount -ne 65536 -or [int]$facts.fieldParticleDispatchCount -le 0 -or [int]$facts.fieldParticleDrawCount -le 0 -or -not $facts.fieldParticleMapCameraIsolated) { - throw "Live witness did not prove provider assets, movement, Fields lowering, Stardust dispatch/draw, and camera-channel separation." + throw "Live witness did not prove provider assets, Fields lowering, Stardust dispatch/draw, and camera-channel separation." } $gridCenterXCells = [double]$facts.fieldParticleGridCenter.x / 6.0 $gridCenterYCells = [double]$facts.fieldParticleGridCenter.y / 6.0 @@ -314,6 +309,9 @@ try { throw "Live witness did not prove provider-owned celestial bodies and asteroids reached the generic scene and intersected the pilot frustum. bodies=$($presentedBodies.Count) asteroids=$($presentedAsteroids.Count) invalid=$($invalidCelestials.Count)" } if ($witnessProfile -eq "full-session-gameplay") { + if (-not $facts.movement) { + throw "Full-session witness did not prove authoritative movement and its receipt." + } if (-not $facts.tradeRoundTrip -or [string]::IsNullOrWhiteSpace($facts.tradeItemKey)) { throw "Full-session witness did not prove docked purchase and sale through provider-advertised generic actions." } @@ -351,17 +349,37 @@ try { } $finalBodies = @(Get-ChildItem -LiteralPath $assetCachePath -Filter *.body -File -Recurse -ErrorAction SilentlyContinue) $finalPartials = @(Get-ChildItem -LiteralPath $assetCachePath -Filter *.partial -File -Recurse -ErrorAction SilentlyContinue) - if (-not (Test-Path -LiteralPath $providerBundlePath)) { - throw "Provider-owned Aetheria bundle is missing: $providerBundlePath" + if ($finalPartials.Count -ne 0) { + throw "Provider bundle promotion left partial bodies behind. bodies=$($finalBodies.Count) partials=$($finalPartials.Count)" } - $bodyHash = (Get-FileHash -LiteralPath $providerBundlePath -Algorithm SHA256).Hash.ToLowerInvariant() - $promotedBundleBodies = @($finalBodies | Where-Object { $_.BaseName -eq $bodyHash }) - if ($promotedBundleBodies.Count -ne 1 -or $finalPartials.Count -ne 0) { - throw "Provider bundle promotion is incomplete. matchingBodies=$($promotedBundleBodies.Count) bodies=$($finalBodies.Count) partials=$($finalPartials.Count) hash=$bodyHash" + $providerBundlesByHash = @{} + foreach ($providerBundlePath in $providerBundlePaths) { + $providerBundle = Get-Item -LiteralPath $providerBundlePath + $bodyHash = (Get-FileHash -LiteralPath $providerBundle.FullName -Algorithm SHA256).Hash.ToLowerInvariant() + $providerBundlesByHash[$bodyHash] = $providerBundle } - $promotedBodyHash = (Get-FileHash -LiteralPath $promotedBundleBodies[0].FullName -Algorithm SHA256).Hash.ToLowerInvariant() - if ($promotedBodyHash -ne $bodyHash -or $promotedBundleBodies[0].Length -ne (Get-Item -LiteralPath $providerBundlePath).Length) { - throw "Promoted provider bundle does not match its authoritative body. expected=$bodyHash actual=$promotedBodyHash" + $contentArtifacts = @() + foreach ($body in $finalBodies) { + $bodyHash = $body.BaseName.ToLowerInvariant() + if (-not $providerBundlesByHash.ContainsKey($bodyHash)) { + throw "Asset cache contains a body not owned by the current Aetheria bundle set. body=$($body.FullName) hash=$bodyHash" + } + $providerBundle = $providerBundlesByHash[$bodyHash] + $promotedBodyHash = (Get-FileHash -LiteralPath $body.FullName -Algorithm SHA256).Hash.ToLowerInvariant() + if ($promotedBodyHash -ne $bodyHash -or $body.Length -ne $providerBundle.Length) { + throw "Promoted provider bundle does not match its authoritative body. bundle=$($providerBundle.Name) expected=$bodyHash actual=$promotedBodyHash" + } + $contentArtifacts += [pscustomobject][ordered]@{ + name = $providerBundle.Name + contentHash = $bodyHash + sizeBytes = $providerBundle.Length + } + } + $requiredFoundationBundles = @('aetheria-shaders', 'aetheria-core', 'aetheria-ui') + $promotedBundleNames = @($contentArtifacts | ForEach-Object { $_.name }) + $missingFoundationBundles = @($requiredFoundationBundles | Where-Object { $_ -notin $promotedBundleNames }) + if ($missingFoundationBundles.Count -ne 0) { + throw "The generic client did not materialize every provider foundation bundle. missing=[$($missingFoundationBundles -join ',')] promoted=[$($promotedBundleNames -join ',')]" } $facts | Add-Member -NotePropertyName releasedPackageClient -NotePropertyValue $releasedPackageClient $facts | Add-Member -NotePropertyName clientProject -NotePropertyValue $projectRoot @@ -374,8 +392,9 @@ try { initialPartialCount = $initialPartialCount finalBodyCount = $finalBodies.Count finalPartialCount = $finalPartials.Count - contentHash = $bodyHash - sizeBytes = $promotedBundleBodies[0].Length + bundleCount = $contentArtifacts.Count + sizeBytes = ($contentArtifacts | Measure-Object -Property sizeBytes -Sum).Sum + artifacts = $contentArtifacts }) $capture = Get-Item -LiteralPath $capturePath $mapCapture = Get-Item -LiteralPath $mapCapturePath From 44390549775ec535ee31fc60a5bc594f372f4147 Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Mon, 20 Jul 2026 04:49:45 +0100 Subject: [PATCH 08/58] Lower live Eve surfaces without rebuilding UI state --- .../Runtime/EveUiToolkitSurfaceLowerer.cs | 32 +++- .../Runtime/EveUiToolkitSurfacePresenter.cs | 153 ++++++++++++++++++ .../EveUiToolkitSurfacePresenter.cs.meta | 2 + .../Editor/EveUiToolkitSurfaceLowererTests.cs | 77 ++++++++- .../package.json | 4 +- 5 files changed, 261 insertions(+), 7 deletions(-) create mode 100644 packages/org.gamecult.eve.unity-uitoolkit/Runtime/EveUiToolkitSurfacePresenter.cs create mode 100644 packages/org.gamecult.eve.unity-uitoolkit/Runtime/EveUiToolkitSurfacePresenter.cs.meta diff --git a/packages/org.gamecult.eve.unity-uitoolkit/Runtime/EveUiToolkitSurfaceLowerer.cs b/packages/org.gamecult.eve.unity-uitoolkit/Runtime/EveUiToolkitSurfaceLowerer.cs index 9cdc677..00809f2 100644 --- a/packages/org.gamecult.eve.unity-uitoolkit/Runtime/EveUiToolkitSurfaceLowerer.cs +++ b/packages/org.gamecult.eve.unity-uitoolkit/Runtime/EveUiToolkitSurfaceLowerer.cs @@ -46,6 +46,9 @@ namespace GameCult.Eve.UnityUIToolkit element.userData = component; ApplyGeneratedLayout(element, component); + if (IsHidden(component) || IsExternalProjectionRoot(component.Kind)) + return element; + foreach (var child in component.Children) { var loweredChild = LowerComponent(child, document, commandSink); @@ -129,7 +132,11 @@ namespace GameCult.Eve.UnityUIToolkit card.style.flexDirection = FlexDirection.Column; var title = component.GetProp("title"); if (!string.IsNullOrWhiteSpace(title)) - card.Add(TitleLabel(title)); + { + var titleLabel = TitleLabel(title); + titleLabel.name = "title"; + card.Add(titleLabel); + } return card; } case "metric": @@ -137,8 +144,12 @@ namespace GameCult.Eve.UnityUIToolkit var metric = new VisualElement(); metric.AddToClassList("eve-metric"); metric.style.flexDirection = FlexDirection.Column; - metric.Add(MutedLabel(component.GetProp("label"))); - metric.Add(ValueLabel(component.GetProp("value"))); + var label = MutedLabel(component.GetProp("label")); + label.name = "label"; + metric.Add(label); + var value = ValueLabel(component.GetProp("value")); + value.name = "value"; + metric.Add(value); return metric; } case "progress": @@ -741,6 +752,21 @@ namespace GameCult.Eve.UnityUIToolkit return kind; } + internal static bool IsHidden(EveSurfaceComponent component) => + component.Layout != null && + component.Layout.TryGetValue("display", out var display) && + string.Equals(display, "none", StringComparison.OrdinalIgnoreCase); + + internal static bool IsExternalProjectionRoot(string kind) + { + kind = NormalizeKind(kind); + return string.Equals(kind, "world.scene3d", StringComparison.Ordinal) || + string.Equals(kind, "world.scene2d", StringComparison.Ordinal) || + string.Equals(kind, "field.volume3d", StringComparison.Ordinal) || + string.Equals(kind, "field.particles3d", StringComparison.Ordinal) || + string.Equals(kind, "layer.reactive", StringComparison.Ordinal); + } + private static string SafeName(string value) { return string.IsNullOrWhiteSpace(value) ? "eve.component" : value; diff --git a/packages/org.gamecult.eve.unity-uitoolkit/Runtime/EveUiToolkitSurfacePresenter.cs b/packages/org.gamecult.eve.unity-uitoolkit/Runtime/EveUiToolkitSurfacePresenter.cs new file mode 100644 index 0000000..812ef72 --- /dev/null +++ b/packages/org.gamecult.eve.unity-uitoolkit/Runtime/EveUiToolkitSurfacePresenter.cs @@ -0,0 +1,153 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using GameCult.Eve.Surface; +using UnityEngine.UIElements; + +#nullable enable + +namespace GameCult.Eve.UnityUIToolkit +{ + public sealed class EveUiToolkitSurfacePresenter + { + private readonly EveUiToolkitSurfaceLowerer _lowerer; + private string _structure = ""; + + public EveUiToolkitSurfacePresenter(EveUiToolkitSurfaceOptions? options = null) + { + _lowerer = new EveUiToolkitSurfaceLowerer(options); + } + + public VisualElement? Root { get; private set; } + + public bool Present( + EveSurfaceDocument document, + Action? commandSink = null) + { + if (document == null) throw new ArgumentNullException(nameof(document)); + var structure = StructureOf(document); + if (Root == null || !string.Equals(_structure, structure, StringComparison.Ordinal)) + { + Root = _lowerer.Lower(document, commandSink); + _structure = structure; + return true; + } + + var elements = Root.Query() + .ToList() + .Where(element => element.userData is EveSurfaceComponent) + .ToDictionary(element => ((EveSurfaceComponent)element.userData).Id, StringComparer.Ordinal); + if (Root.userData is EveSurfaceComponent rootComponent) + elements[rootComponent.Id] = Root; + ApplyDynamic(document.Surface.Root, elements); + return false; + } + + private static void ApplyDynamic( + EveSurfaceComponent component, + IReadOnlyDictionary elements) + { + if (!elements.TryGetValue(component.Id, out var element)) + return; + element.userData = component; + switch (NormalizeKind(component.Kind)) + { + case "pane": + case "modal": + case "card": + var title = element.Q