diff --git a/ReleaseConsumerProject/Assets/Tests/PlayMode/GenericWorldCaptureTests.cs b/ReleaseConsumerProject/Assets/Tests/PlayMode/GenericWorldCaptureTests.cs index 6408580..3bc6852 100644 --- a/ReleaseConsumerProject/Assets/Tests/PlayMode/GenericWorldCaptureTests.cs +++ b/ReleaseConsumerProject/Assets/Tests/PlayMode/GenericWorldCaptureTests.cs @@ -198,6 +198,10 @@ namespace GameCult.EveUnity.GenericClient.PlayModeTests WitnessReceipt tractorReceipt = null; WitnessReceipt tractorReleaseReceipt = null; WitnessReceipt actionReceipt = null; + WitnessReceipt tradePurchaseReceipt = null; + WitnessReceipt tradeSaleReceipt = null; + bool tradeRoundTrip = false; + string tradeItemKey = ""; EveUnityShotReceipt observedShot = null; EveUnityCombatPresentation observedCombat = null; float shieldRatioBefore = 0f; @@ -376,6 +380,124 @@ namespace GameCult.EveUnity.GenericClient.PlayModeTests marker.GetComponentsInChildren(includeInactive: true).Length, Is.GreaterThan(1), "The generic client substituted its primitive fallback instead of the provider-authored AssetBundle prefab."); + + if (requireSessionGameplay) + { + var dockVersion = runtime.ActiveVersion; + var dock = host.SubmitAdvertisedActionIntent(playerId, "pilot.dock"); + 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 || + !(host.InputCapability?.Actions ?? Array.Empty()).Any(candidate => + candidate != null && + candidate.ActionId.StartsWith("trade.buy.", StringComparison.Ordinal) && + string.Equals(candidate.Availability, "available", StringComparison.OrdinalIgnoreCase)))) + { + yield return new WaitForSecondsRealtime(0.1f); + runtime.Refresh(); + } + AssertReconciledProviderReceipt(runtime.LastReceipt, dock.CommandId, + provider.Selection.ProviderId, provider.Selection.SurfaceId, dockVersion); + var buyAction = (host.InputCapability?.Actions ?? Array.Empty()) + .FirstOrDefault(candidate => candidate != null && + candidate.ActionId.StartsWith("trade.buy.", StringComparison.Ordinal) && + string.Equals(candidate.Availability, "available", StringComparison.OrdinalIgnoreCase)); + Assert.That(buyAction, Is.Not.Null, + "Docking did not expose an available provider-authored purchase action."); + Assert.That(buyAction.Payload.TryGetValue("itemKey", out tradeItemKey), Is.True); + Assert.That(tradeItemKey, Is.Not.Empty); + + var purchaseVersion = runtime.ActiveVersion; + var purchase = host.SubmitAdvertisedActionIntent(playerId, buyAction.ActionId); + var purchaseDeadline = Time.realtimeSinceStartup + 12f; + while (Time.realtimeSinceStartup < purchaseDeadline && + (runtime.LastReceipt == null || runtime.LastReceipt.CommandId != purchase.CommandId || + !string.Equals(runtime.LastReceipt.State, "reconciled", StringComparison.OrdinalIgnoreCase) || + runtime.ActiveVersion < runtime.LastReceipt.SourceVersion || + !(host.InputCapability?.Actions ?? Array.Empty()).Any(candidate => + candidate != null && + candidate.ActionId.StartsWith("trade.sell.", StringComparison.Ordinal) && + candidate.Payload.TryGetValue("itemKey", out var itemKey) && + string.Equals(itemKey, tradeItemKey, StringComparison.Ordinal) && + string.Equals(candidate.Availability, "available", StringComparison.OrdinalIgnoreCase)))) + { + yield return new WaitForSecondsRealtime(0.1f); + runtime.Refresh(); + } + AssertReconciledProviderReceipt(runtime.LastReceipt, purchase.CommandId, + provider.Selection.ProviderId, provider.Selection.SurfaceId, purchaseVersion); + tradePurchaseReceipt = WitnessReceipt.From("trade-purchase", runtime.LastReceipt); + var sellAction = (host.InputCapability?.Actions ?? Array.Empty()) + .FirstOrDefault(candidate => candidate != null && + candidate.ActionId.StartsWith("trade.sell.", StringComparison.Ordinal) && + candidate.Payload.TryGetValue("itemKey", out var itemKey) && + string.Equals(itemKey, tradeItemKey, StringComparison.Ordinal) && + string.Equals(candidate.Availability, "available", StringComparison.OrdinalIgnoreCase)); + Assert.That(sellAction, Is.Not.Null, + "The authoritative purchase did not expose the purchased cargo as an available sale action."); + + var saleCapabilityVersion = host.InputCapability.Version; + var saleVersion = runtime.ActiveVersion; + var sale = host.SubmitAdvertisedActionIntent(playerId, sellAction.ActionId); + var saleDeadline = Time.realtimeSinceStartup + 12f; + while (Time.realtimeSinceStartup < saleDeadline && + (runtime.LastReceipt == null || runtime.LastReceipt.CommandId != sale.CommandId || + !string.Equals(runtime.LastReceipt.State, "reconciled", StringComparison.OrdinalIgnoreCase) || + runtime.ActiveVersion < runtime.LastReceipt.SourceVersion || + host.InputCapability.Version <= saleCapabilityVersion || + (host.InputCapability.Actions ?? Array.Empty()).Any(candidate => + candidate != null && + candidate.ActionId.StartsWith("trade.sell.", StringComparison.Ordinal) && + candidate.Payload.TryGetValue("itemKey", out var itemKey) && + string.Equals(itemKey, tradeItemKey, StringComparison.Ordinal)))) + { + yield return new WaitForSecondsRealtime(0.1f); + runtime.Refresh(); + } + AssertReconciledProviderReceipt(runtime.LastReceipt, sale.CommandId, + provider.Selection.ProviderId, provider.Selection.SurfaceId, saleVersion); + tradeSaleReceipt = WitnessReceipt.From("trade-sale", runtime.LastReceipt); + tradeRoundTrip = !(host.InputCapability.Actions ?? Array.Empty()).Any(candidate => + candidate != null && + candidate.ActionId.StartsWith("trade.sell.", StringComparison.Ordinal) && + candidate.Payload.TryGetValue("itemKey", out var itemKey) && + string.Equals(itemKey, tradeItemKey, StringComparison.Ordinal)); + Assert.That(tradeRoundTrip, Is.True, + "The sold unit remained in the daemon-authored cargo action catalog."); + + var resumeVersion = runtime.ActiveVersion; + var resume = host.SubmitAdvertisedActionIntent(playerId, "simulation.rate.realtime"); + var resumeDeadline = Time.realtimeSinceStartup + 12f; + while (Time.realtimeSinceStartup < resumeDeadline && + (runtime.LastReceipt == null || runtime.LastReceipt.CommandId != resume.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, resume.CommandId, + provider.Selection.ProviderId, provider.Selection.SurfaceId, resumeVersion); + + var undockVersion = runtime.ActiveVersion; + var undock = host.SubmitAdvertisedActionIntent(playerId, "pilot.undock"); + var undockDeadline = Time.realtimeSinceStartup + 12f; + while (Time.realtimeSinceStartup < undockDeadline && + (runtime.LastReceipt == null || runtime.LastReceipt.CommandId != undock.CommandId || + !string.Equals(runtime.LastReceipt.State, "reconciled", StringComparison.OrdinalIgnoreCase) || + runtime.ActiveVersion < runtime.LastReceipt.SourceVersion || + (host.InputCapability?.Actions ?? Array.Empty()).Any(candidate => + candidate != null && candidate.ActionId.StartsWith("trade.", StringComparison.Ordinal)))) + { + yield return new WaitForSecondsRealtime(0.1f); + runtime.Refresh(); + } + AssertReconciledProviderReceipt(runtime.LastReceipt, undock.CommandId, + provider.Selection.ProviderId, provider.Selection.SurfaceId, undockVersion); + } + var initialPosition = marker.transform.position; initialVersion = runtime.ActiveVersion; var request = runtime.SubmitMoveVectorIntent(playerId, 1f, 0f, 1f); @@ -977,6 +1099,10 @@ namespace GameCult.EveUnity.GenericClient.PlayModeTests tractorReceipt, tractorReleaseReceipt, actionReceipt, + tradePurchaseReceipt, + tradeSaleReceipt, + tradeRoundTrip, + tradeItemKey, observedShot, shieldRatioBefore, observedCombat?.ShieldRatio ?? shieldRatioBefore, @@ -1060,6 +1186,10 @@ namespace GameCult.EveUnity.GenericClient.PlayModeTests WitnessReceipt tractor, WitnessReceipt tractorRelease, WitnessReceipt action, + WitnessReceipt tradePurchase, + WitnessReceipt tradeSale, + bool tradeRoundTrip, + string tradeItemKey, EveUnityShotReceipt shot, float shieldRatioBefore, float shieldRatioAfter, @@ -1128,6 +1258,8 @@ namespace GameCult.EveUnity.GenericClient.PlayModeTests beamPresentation = tractor != null && tractorBeamPower > 0.01f && tractorBeamParticleSystemCount > 0, beamRelease = tractorRelease != null, action = action != null, + tradeRoundTrip = tradeRoundTrip && tradePurchase != null && tradeSale != null, + tradeItemKey = tradeItemKey ?? "", combatPresentation = shot != null, shotId = shot?.ShotId ?? "", shotHit = shot?.Hit ?? false, @@ -1191,7 +1323,7 @@ namespace GameCult.EveUnity.GenericClient.PlayModeTests mapChangedPixels = mapChangedPixels, pilotCameraExcludesMapChannel = pilotCameraExcludesMapChannel, mapCameraIncludesMapChannel = mapCameraIncludesMapChannel, - receipts = new[] { movement, look, targeting, tractor, tractorRelease, action } + receipts = new[] { movement, look, targeting, tractor, tractorRelease, action, tradePurchase, tradeSale } }; Directory.CreateDirectory(Path.GetDirectoryName(Path.GetFullPath(path))); File.WriteAllText(path, JsonUtility.ToJson(document, true)); @@ -1225,6 +1357,8 @@ namespace GameCult.EveUnity.GenericClient.PlayModeTests public bool beamPresentation; public bool beamRelease; public bool action; + public bool tradeRoundTrip; + public string tradeItemKey; public bool combatPresentation; public string shotId; public bool shotHit; diff --git a/TestProject/Assets/Tests/PlayMode/GenericWorldCaptureTests.cs b/TestProject/Assets/Tests/PlayMode/GenericWorldCaptureTests.cs index 6408580..3bc6852 100644 --- a/TestProject/Assets/Tests/PlayMode/GenericWorldCaptureTests.cs +++ b/TestProject/Assets/Tests/PlayMode/GenericWorldCaptureTests.cs @@ -198,6 +198,10 @@ namespace GameCult.EveUnity.GenericClient.PlayModeTests WitnessReceipt tractorReceipt = null; WitnessReceipt tractorReleaseReceipt = null; WitnessReceipt actionReceipt = null; + WitnessReceipt tradePurchaseReceipt = null; + WitnessReceipt tradeSaleReceipt = null; + bool tradeRoundTrip = false; + string tradeItemKey = ""; EveUnityShotReceipt observedShot = null; EveUnityCombatPresentation observedCombat = null; float shieldRatioBefore = 0f; @@ -376,6 +380,124 @@ namespace GameCult.EveUnity.GenericClient.PlayModeTests marker.GetComponentsInChildren(includeInactive: true).Length, Is.GreaterThan(1), "The generic client substituted its primitive fallback instead of the provider-authored AssetBundle prefab."); + + if (requireSessionGameplay) + { + var dockVersion = runtime.ActiveVersion; + var dock = host.SubmitAdvertisedActionIntent(playerId, "pilot.dock"); + 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 || + !(host.InputCapability?.Actions ?? Array.Empty()).Any(candidate => + candidate != null && + candidate.ActionId.StartsWith("trade.buy.", StringComparison.Ordinal) && + string.Equals(candidate.Availability, "available", StringComparison.OrdinalIgnoreCase)))) + { + yield return new WaitForSecondsRealtime(0.1f); + runtime.Refresh(); + } + AssertReconciledProviderReceipt(runtime.LastReceipt, dock.CommandId, + provider.Selection.ProviderId, provider.Selection.SurfaceId, dockVersion); + var buyAction = (host.InputCapability?.Actions ?? Array.Empty()) + .FirstOrDefault(candidate => candidate != null && + candidate.ActionId.StartsWith("trade.buy.", StringComparison.Ordinal) && + string.Equals(candidate.Availability, "available", StringComparison.OrdinalIgnoreCase)); + Assert.That(buyAction, Is.Not.Null, + "Docking did not expose an available provider-authored purchase action."); + Assert.That(buyAction.Payload.TryGetValue("itemKey", out tradeItemKey), Is.True); + Assert.That(tradeItemKey, Is.Not.Empty); + + var purchaseVersion = runtime.ActiveVersion; + var purchase = host.SubmitAdvertisedActionIntent(playerId, buyAction.ActionId); + var purchaseDeadline = Time.realtimeSinceStartup + 12f; + while (Time.realtimeSinceStartup < purchaseDeadline && + (runtime.LastReceipt == null || runtime.LastReceipt.CommandId != purchase.CommandId || + !string.Equals(runtime.LastReceipt.State, "reconciled", StringComparison.OrdinalIgnoreCase) || + runtime.ActiveVersion < runtime.LastReceipt.SourceVersion || + !(host.InputCapability?.Actions ?? Array.Empty()).Any(candidate => + candidate != null && + candidate.ActionId.StartsWith("trade.sell.", StringComparison.Ordinal) && + candidate.Payload.TryGetValue("itemKey", out var itemKey) && + string.Equals(itemKey, tradeItemKey, StringComparison.Ordinal) && + string.Equals(candidate.Availability, "available", StringComparison.OrdinalIgnoreCase)))) + { + yield return new WaitForSecondsRealtime(0.1f); + runtime.Refresh(); + } + AssertReconciledProviderReceipt(runtime.LastReceipt, purchase.CommandId, + provider.Selection.ProviderId, provider.Selection.SurfaceId, purchaseVersion); + tradePurchaseReceipt = WitnessReceipt.From("trade-purchase", runtime.LastReceipt); + var sellAction = (host.InputCapability?.Actions ?? Array.Empty()) + .FirstOrDefault(candidate => candidate != null && + candidate.ActionId.StartsWith("trade.sell.", StringComparison.Ordinal) && + candidate.Payload.TryGetValue("itemKey", out var itemKey) && + string.Equals(itemKey, tradeItemKey, StringComparison.Ordinal) && + string.Equals(candidate.Availability, "available", StringComparison.OrdinalIgnoreCase)); + Assert.That(sellAction, Is.Not.Null, + "The authoritative purchase did not expose the purchased cargo as an available sale action."); + + var saleCapabilityVersion = host.InputCapability.Version; + var saleVersion = runtime.ActiveVersion; + var sale = host.SubmitAdvertisedActionIntent(playerId, sellAction.ActionId); + var saleDeadline = Time.realtimeSinceStartup + 12f; + while (Time.realtimeSinceStartup < saleDeadline && + (runtime.LastReceipt == null || runtime.LastReceipt.CommandId != sale.CommandId || + !string.Equals(runtime.LastReceipt.State, "reconciled", StringComparison.OrdinalIgnoreCase) || + runtime.ActiveVersion < runtime.LastReceipt.SourceVersion || + host.InputCapability.Version <= saleCapabilityVersion || + (host.InputCapability.Actions ?? Array.Empty()).Any(candidate => + candidate != null && + candidate.ActionId.StartsWith("trade.sell.", StringComparison.Ordinal) && + candidate.Payload.TryGetValue("itemKey", out var itemKey) && + string.Equals(itemKey, tradeItemKey, StringComparison.Ordinal)))) + { + yield return new WaitForSecondsRealtime(0.1f); + runtime.Refresh(); + } + AssertReconciledProviderReceipt(runtime.LastReceipt, sale.CommandId, + provider.Selection.ProviderId, provider.Selection.SurfaceId, saleVersion); + tradeSaleReceipt = WitnessReceipt.From("trade-sale", runtime.LastReceipt); + tradeRoundTrip = !(host.InputCapability.Actions ?? Array.Empty()).Any(candidate => + candidate != null && + candidate.ActionId.StartsWith("trade.sell.", StringComparison.Ordinal) && + candidate.Payload.TryGetValue("itemKey", out var itemKey) && + string.Equals(itemKey, tradeItemKey, StringComparison.Ordinal)); + Assert.That(tradeRoundTrip, Is.True, + "The sold unit remained in the daemon-authored cargo action catalog."); + + var resumeVersion = runtime.ActiveVersion; + var resume = host.SubmitAdvertisedActionIntent(playerId, "simulation.rate.realtime"); + var resumeDeadline = Time.realtimeSinceStartup + 12f; + while (Time.realtimeSinceStartup < resumeDeadline && + (runtime.LastReceipt == null || runtime.LastReceipt.CommandId != resume.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, resume.CommandId, + provider.Selection.ProviderId, provider.Selection.SurfaceId, resumeVersion); + + var undockVersion = runtime.ActiveVersion; + var undock = host.SubmitAdvertisedActionIntent(playerId, "pilot.undock"); + var undockDeadline = Time.realtimeSinceStartup + 12f; + while (Time.realtimeSinceStartup < undockDeadline && + (runtime.LastReceipt == null || runtime.LastReceipt.CommandId != undock.CommandId || + !string.Equals(runtime.LastReceipt.State, "reconciled", StringComparison.OrdinalIgnoreCase) || + runtime.ActiveVersion < runtime.LastReceipt.SourceVersion || + (host.InputCapability?.Actions ?? Array.Empty()).Any(candidate => + candidate != null && candidate.ActionId.StartsWith("trade.", StringComparison.Ordinal)))) + { + yield return new WaitForSecondsRealtime(0.1f); + runtime.Refresh(); + } + AssertReconciledProviderReceipt(runtime.LastReceipt, undock.CommandId, + provider.Selection.ProviderId, provider.Selection.SurfaceId, undockVersion); + } + var initialPosition = marker.transform.position; initialVersion = runtime.ActiveVersion; var request = runtime.SubmitMoveVectorIntent(playerId, 1f, 0f, 1f); @@ -977,6 +1099,10 @@ namespace GameCult.EveUnity.GenericClient.PlayModeTests tractorReceipt, tractorReleaseReceipt, actionReceipt, + tradePurchaseReceipt, + tradeSaleReceipt, + tradeRoundTrip, + tradeItemKey, observedShot, shieldRatioBefore, observedCombat?.ShieldRatio ?? shieldRatioBefore, @@ -1060,6 +1186,10 @@ namespace GameCult.EveUnity.GenericClient.PlayModeTests WitnessReceipt tractor, WitnessReceipt tractorRelease, WitnessReceipt action, + WitnessReceipt tradePurchase, + WitnessReceipt tradeSale, + bool tradeRoundTrip, + string tradeItemKey, EveUnityShotReceipt shot, float shieldRatioBefore, float shieldRatioAfter, @@ -1128,6 +1258,8 @@ namespace GameCult.EveUnity.GenericClient.PlayModeTests beamPresentation = tractor != null && tractorBeamPower > 0.01f && tractorBeamParticleSystemCount > 0, beamRelease = tractorRelease != null, action = action != null, + tradeRoundTrip = tradeRoundTrip && tradePurchase != null && tradeSale != null, + tradeItemKey = tradeItemKey ?? "", combatPresentation = shot != null, shotId = shot?.ShotId ?? "", shotHit = shot?.Hit ?? false, @@ -1191,7 +1323,7 @@ namespace GameCult.EveUnity.GenericClient.PlayModeTests mapChangedPixels = mapChangedPixels, pilotCameraExcludesMapChannel = pilotCameraExcludesMapChannel, mapCameraIncludesMapChannel = mapCameraIncludesMapChannel, - receipts = new[] { movement, look, targeting, tractor, tractorRelease, action } + receipts = new[] { movement, look, targeting, tractor, tractorRelease, action, tradePurchase, tradeSale } }; Directory.CreateDirectory(Path.GetDirectoryName(Path.GetFullPath(path))); File.WriteAllText(path, JsonUtility.ToJson(document, true)); @@ -1225,6 +1357,8 @@ namespace GameCult.EveUnity.GenericClient.PlayModeTests public bool beamPresentation; public bool beamRelease; public bool action; + public bool tradeRoundTrip; + public string tradeItemKey; public bool combatPresentation; public string shotId; public bool shotHit; diff --git a/docs/aetheria-released-package-witness.md b/docs/aetheria-released-package-witness.md index dc77944..1a081da 100644 --- a/docs/aetheria-released-package-witness.md +++ b/docs/aetheria-released-package-witness.md @@ -4,16 +4,16 @@ The generic `ReleaseConsumerProject` is pinned to released Git packages: -- `org.gamecult.eve.unity-scene` `0.3.88`, commit - `3ce813e29dcc5342b51985187de26c62eb8c90b4`, and +- `org.gamecult.eve.unity-scene` `0.3.90`, commit + `f3d4ef7a015fcdfdcb70e163e124f61534f8c252`, 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.30`, commit - `0ef59b50c27ac019929f5e68bcd602983a190062`. +- `org.gamecult.cultlib` `1.0.32`, commit + `d018bb1fb5a8ce73da6f5579e2f475750d901d8b`. The hand-run integration gate is the warm released-package witness: @@ -21,7 +21,7 @@ The hand-run integration gate is the warm released-package witness: pwsh -File .\scripts\run-aetheria-daemon-world-witness.ps1 ` -CacheState warm ` -AssetCacheDirectory E:\Projects\Aetheria\Aetheria.Unity\Build\AssetCache ` - -OutputDirectory artifacts\aetheria-daemon-0372-warm5 ` + -OutputDirectory artifacts\dockyard-trade-warm-8 ` -SkipAssetBundleBuild ``` @@ -36,11 +36,24 @@ 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. -Cold transfer is not a passing integration gate. The current bundle is -`56,204,750` bytes and recent cold runs still exceed the 300-second witness -deadline while asset bytes travel through batched snapshot records. Do not -increase the timeout or describe this as proven; the intended repair is mapped -or network-body transport. `-CacheState cold` remains a diagnostic run. +Cold transfer is not a passing integration gate. The provider bundle is +`56,204,750` bytes on disk; the previously observed roughly 13 MB transfer still +exceeds the witness deadline while its bytes travel through batched snapshot +records. Do not increase the timeout or describe this as proven; the intended +repair is mapped or network-body transport. `-CacheState cold` remains a +diagnostic run. + +The July 19 released-package run in `artifacts/dockyard-trade-warm-8` 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, 147 +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 July 19 transport-boundary run in `artifacts/demanded-fields-warm-6` passed its released-consumer PlayMode test and produced both camera captures.