Prove cold lowering and destruction loot lifecycle
This commit is contained in:
parent
7c8442e122
commit
8d628a6599
7 changed files with 290 additions and 123 deletions
|
|
@ -210,6 +210,21 @@ namespace GameCult.EveUnity.GenericClient.PlayModeTests
|
|||
int pickupCollectionEventCount = 0;
|
||||
int initialPickupEntityCount = 0;
|
||||
int finalPickupEntityCount = -1;
|
||||
bool destructionPickupObserved = false;
|
||||
int maximumTrajectoryCount = 0;
|
||||
bool hitMarkerObserved = false;
|
||||
var witnessProfile = Environment.GetEnvironmentVariable("EVEUNITY_WITNESS_PROFILE") ??
|
||||
"full-session-gameplay";
|
||||
Assert.That(witnessProfile,
|
||||
Is.EqualTo("cold-start-lowering").Or.EqualTo("full-session-gameplay"),
|
||||
"The witness profile must name an explicit proof boundary.");
|
||||
var requireSessionGameplay = string.Equals(
|
||||
witnessProfile,
|
||||
"full-session-gameplay",
|
||||
StringComparison.Ordinal);
|
||||
EveUnityAimPresentationRenderer aimRenderer = null;
|
||||
EveUnityBeamPresentation beamPresentation = null;
|
||||
EveUnityBeamPresentationRenderer beamRenderer = null;
|
||||
|
||||
try
|
||||
{
|
||||
|
|
@ -280,8 +295,9 @@ namespace GameCult.EveUnity.GenericClient.PlayModeTests
|
|||
Assert.That(host.PresentedEntities.CurrentGeneration.Entities.Count, Is.GreaterThan(0));
|
||||
initialPickupEntityCount = host.PresentedEntities.CurrentGeneration.Entities.Count(entity =>
|
||||
string.Equals(entity.EntityKind, "pickup", StringComparison.Ordinal));
|
||||
Assert.That(initialPickupEntityCount, Is.GreaterThan(0),
|
||||
"The daemon fixture did not publish its physical starter pickup through the entity SoA.");
|
||||
if (requireSessionGameplay)
|
||||
Assert.That(initialPickupEntityCount, Is.Zero,
|
||||
"The witness scenario must obtain salvage from daemon-owned destruction, not a boot-seeded pickup.");
|
||||
runtime.FeedbackAvailable += feedback =>
|
||||
{
|
||||
if (!string.Equals(feedback.Kind, "pickup.collected", StringComparison.Ordinal)) return;
|
||||
|
|
@ -343,6 +359,8 @@ namespace GameCult.EveUnity.GenericClient.PlayModeTests
|
|||
AssertReconciledProviderReceipt(runtime.LastReceipt, release.CommandId,
|
||||
provider.Selection.ProviderId, provider.Selection.SurfaceId, releaseVersion);
|
||||
|
||||
if (requireSessionGameplay)
|
||||
{
|
||||
var movementVersion = runtime.ActiveVersion;
|
||||
var focus = runtime.SubmitFocusIntent(playerId);
|
||||
var focusDeadline = Time.realtimeSinceStartup + 12f;
|
||||
|
|
@ -399,10 +417,10 @@ namespace GameCult.EveUnity.GenericClient.PlayModeTests
|
|||
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 aimRenderer = root.GetComponent<EveUnityAimPresentationRenderer>();
|
||||
aimRenderer = root.GetComponent<EveUnityAimPresentationRenderer>();
|
||||
Assert.That(aimRenderer, Is.Not.Null);
|
||||
|
||||
var beamPresentation = EveUnityBeamPresentation.FindAll(runtime.ActiveProjection).SingleOrDefault();
|
||||
beamPresentation = EveUnityBeamPresentation.FindAll(runtime.ActiveProjection).SingleOrDefault();
|
||||
Assert.That(beamPresentation, Is.Not.Null,
|
||||
"The provider did not advertise its continuous beam presentation.");
|
||||
Assert.That(beamPresentation.ActivationActionId, Is.Not.Empty,
|
||||
|
|
@ -429,7 +447,7 @@ namespace GameCult.EveUnity.GenericClient.PlayModeTests
|
|||
Assert.That(provider.ResolvePrefab(new EveUnityPlayableWorldAssetBinding(
|
||||
beamPresentation.AssetRole, beamPresentation.AssetRole, "provider-asset-ref")), Is.Not.Null,
|
||||
$"The provider asset catalog did not resolve beam role '{beamPresentation.AssetRole}'.");
|
||||
var beamRenderer = root.GetComponent<EveUnityBeamPresentationRenderer>();
|
||||
beamRenderer = root.GetComponent<EveUnityBeamPresentationRenderer>();
|
||||
Assert.That(beamRenderer, Is.Not.Null);
|
||||
beamRenderer.RefreshNow();
|
||||
Assert.That(beamRenderer.ActiveBeamCount, Is.EqualTo(1));
|
||||
|
|
@ -439,28 +457,6 @@ namespace GameCult.EveUnity.GenericClient.PlayModeTests
|
|||
tractorBeamParticleSystemCount = beamRenderer.ActiveParticleSystemCount;
|
||||
Assert.That(tractorBeamParticleSystemCount, Is.GreaterThan(0),
|
||||
"The generic beam lowerer did not instantiate the provider-owned effect prefab.");
|
||||
var pickupDeadline = Time.realtimeSinceStartup + 12f;
|
||||
while (Time.realtimeSinceStartup < pickupDeadline &&
|
||||
(pickupCollection == null ||
|
||||
host.PresentedEntities.CurrentGeneration.Entities.Any(entity =>
|
||||
string.Equals(entity.EntityKind, "pickup", StringComparison.Ordinal))))
|
||||
{
|
||||
yield return new WaitForSecondsRealtime(0.1f);
|
||||
runtime.Refresh();
|
||||
}
|
||||
finalPickupEntityCount = host.PresentedEntities.CurrentGeneration.Entities.Count(entity =>
|
||||
string.Equals(entity.EntityKind, "pickup", StringComparison.Ordinal));
|
||||
Assert.That(pickupCollection, Is.Not.Null,
|
||||
"The released client did not observe provider-owned feedback for Ymir contact collection.");
|
||||
Assert.That(pickupCollectionEventCount, Is.EqualTo(1),
|
||||
"One Ymir contact transaction must emit one client-visible collection event.");
|
||||
Assert.That(pickupCollection.ItemKey, Is.EqualTo("scrap-metal"));
|
||||
Assert.That(pickupCollection.ScalarValue, Is.EqualTo(1));
|
||||
Assert.That(pickupCollection.CargoQuantityBefore, Is.Zero);
|
||||
Assert.That(pickupCollection.CargoQuantityAfter, Is.EqualTo(1));
|
||||
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
|
||||
|
|
@ -511,22 +507,74 @@ namespace GameCult.EveUnity.GenericClient.PlayModeTests
|
|||
Assert.That(runtime.ActiveVersion, Is.GreaterThanOrEqualTo(runtime.LastReceipt.SourceVersion));
|
||||
actionReceipt = WitnessReceipt.From("action", runtime.LastReceipt);
|
||||
|
||||
var shotDeadline = Time.realtimeSinceStartup + 15f;
|
||||
while (Time.realtimeSinceStartup < shotDeadline && observedShot == null)
|
||||
{
|
||||
yield return new WaitForSecondsRealtime(0.05f);
|
||||
runtime.Refresh();
|
||||
}
|
||||
Assert.That(observedShot, Is.Not.Null,
|
||||
"A reconciled fire request never reached daemon-owned lock acquisition and shot resolution.");
|
||||
observedCombat = EveUnityCombatPresentation.Find(runtime.ActiveProjection);
|
||||
Assert.That(observedShot.TargetEntityIndex, Is.EqualTo(targetEntityIndex));
|
||||
Assert.That(observedShot.LockQuality, Is.GreaterThanOrEqualTo(0.99));
|
||||
var trajectories = root.GetComponent<EveUnityShotTrajectoryRenderer>();
|
||||
var combatRenderer = root.GetComponent<EveUnityCombatPresentationRenderer>();
|
||||
Assert.That(trajectories, Is.Not.Null);
|
||||
Assert.That(trajectories.ActiveTrajectoryCount, Is.GreaterThan(0));
|
||||
Assert.That(combatRenderer, Is.Not.Null);
|
||||
var shotDeadline = Time.realtimeSinceStartup + 20f;
|
||||
while (Time.realtimeSinceStartup < shotDeadline &&
|
||||
(observedShot == null ||
|
||||
host.PresentedEntities.CurrentGeneration.Entities.Any(entity =>
|
||||
entity.EntityId == targetEntityId) ||
|
||||
(!destructionPickupObserved && pickupCollection == null)))
|
||||
{
|
||||
yield return new WaitForSecondsRealtime(0.05f);
|
||||
runtime.Refresh();
|
||||
destructionPickupObserved |= host.PresentedEntities.CurrentGeneration.Entities.Any(entity =>
|
||||
string.Equals(entity.EntityKind, "pickup", StringComparison.Ordinal));
|
||||
maximumTrajectoryCount = Math.Max(maximumTrajectoryCount, trajectories.ActiveTrajectoryCount);
|
||||
combatRenderer.RefreshNow();
|
||||
hitMarkerObserved |= combatRenderer.HitMarkerVisible;
|
||||
}
|
||||
Assert.That(observedShot, Is.Not.Null,
|
||||
"A reconciled fire request never reached daemon-owned lock acquisition and shot resolution.");
|
||||
Assert.That(host.PresentedEntities.CurrentGeneration.Entities.Any(entity =>
|
||||
entity.EntityId == targetEntityId), Is.False,
|
||||
"The low-hull witness raider was not destroyed by daemon combat.");
|
||||
Assert.That(destructionPickupObserved || pickupCollection != null, Is.True,
|
||||
"Daemon destruction did not publish its guaranteed cargo as a pickup.");
|
||||
var collectionDeadline = Time.realtimeSinceStartup + 15f;
|
||||
var nextTractorAimAt = 0f;
|
||||
while (Time.realtimeSinceStartup < collectionDeadline && pickupCollection == null)
|
||||
{
|
||||
yield return new WaitForSecondsRealtime(0.05f);
|
||||
runtime.Refresh();
|
||||
maximumTrajectoryCount = Math.Max(maximumTrajectoryCount, trajectories.ActiveTrajectoryCount);
|
||||
combatRenderer.RefreshNow();
|
||||
hitMarkerObserved |= combatRenderer.HitMarkerVisible;
|
||||
var pickupFact = host.PresentedEntities.CurrentGeneration.Entities.FirstOrDefault(entity =>
|
||||
string.Equals(entity.EntityKind, "pickup", StringComparison.Ordinal));
|
||||
if (pickupFact == null || Time.realtimeSinceStartup < nextTractorAimAt)
|
||||
continue;
|
||||
playerFact = host.PresentedEntities.CurrentGeneration.Entities
|
||||
.Single(entity => entity.EntityId == playerId);
|
||||
var tractorAim = pickupFact.Position - playerFact.Position;
|
||||
tractorAim.y = 0;
|
||||
if (tractorAim.sqrMagnitude <= 0.0001f)
|
||||
continue;
|
||||
tractorAim.Normalize();
|
||||
runtime.SubmitLookDirectionIntent(playerId, tractorAim.x, 0f, tractorAim.z);
|
||||
nextTractorAimAt = Time.realtimeSinceStartup + 0.25f;
|
||||
}
|
||||
finalPickupEntityCount = host.PresentedEntities.CurrentGeneration.Entities.Count(entity =>
|
||||
string.Equals(entity.EntityKind, "pickup", StringComparison.Ordinal));
|
||||
Assert.That(pickupCollection, Is.Not.Null,
|
||||
"Destruction-created salvage never reached cargo through a Ymir contact fact.");
|
||||
Assert.That(pickupCollectionEventCount, Is.EqualTo(1),
|
||||
"One destruction-loot Ymir contact transaction must emit one collection event.");
|
||||
Assert.That(pickupCollection.ItemKey, Is.Not.Empty,
|
||||
"Destruction loot must retain its canonical typed item identity.");
|
||||
Assert.That(pickupCollection.EventId, Does.StartWith("ymir-fact:"),
|
||||
"Collection feedback must retain the consumed Ymir fact identity.");
|
||||
Assert.That(pickupCollection.ScalarValue, Is.EqualTo(1));
|
||||
Assert.That(pickupCollection.CargoQuantityBefore, Is.Zero);
|
||||
Assert.That(pickupCollection.CargoQuantityAfter, Is.EqualTo(1));
|
||||
Assert.That(finalPickupEntityCount, Is.Zero,
|
||||
"Collected destruction loot remained in the provider-authored entity generation.");
|
||||
observedCombat = EveUnityCombatPresentation.Find(runtime.ActiveProjection);
|
||||
Assert.That(observedShot.TargetEntityIndex, Is.EqualTo(targetEntityIndex));
|
||||
Assert.That(observedShot.LockQuality, Is.GreaterThanOrEqualTo(0.99));
|
||||
Assert.That(maximumTrajectoryCount, Is.GreaterThan(0));
|
||||
combatRenderer.RefreshNow();
|
||||
if (observedCombat != null && !string.IsNullOrWhiteSpace(observedCombat.SelectedTargetEntityId))
|
||||
{
|
||||
|
|
@ -534,7 +582,16 @@ namespace GameCult.EveUnity.GenericClient.PlayModeTests
|
|||
Assert.That(combatRenderer.LockVisible, Is.True);
|
||||
}
|
||||
if (observedShot.Hit && (observedShot.AppliedDamage > 0 || observedShot.ShieldAbsorbedDamage > 0))
|
||||
Assert.That(combatRenderer.HitMarkerVisible, Is.True);
|
||||
Assert.That(hitMarkerObserved, Is.True);
|
||||
}
|
||||
|
||||
aimRenderer ??= root.GetComponent<EveUnityAimPresentationRenderer>();
|
||||
Assert.That(aimRenderer, Is.Not.Null);
|
||||
beamPresentation ??= EveUnityBeamPresentation.FindAll(runtime.ActiveProjection).SingleOrDefault();
|
||||
Assert.That(beamPresentation, Is.Not.Null,
|
||||
"The provider did not advertise its continuous beam presentation.");
|
||||
beamRenderer ??= root.GetComponent<EveUnityBeamPresentationRenderer>();
|
||||
finalPickupEntityCount = Math.Max(0, finalPickupEntityCount);
|
||||
|
||||
var camera = cameraObject.AddComponent<Camera>();
|
||||
camera.clearFlags = CameraClearFlags.SolidColor;
|
||||
|
|
@ -715,6 +772,7 @@ namespace GameCult.EveUnity.GenericClient.PlayModeTests
|
|||
Assert.That(fieldViewport, Is.Not.Null);
|
||||
Assert.That(fieldSplats, Is.Not.Null);
|
||||
WriteWitnessFacts(
|
||||
witnessProfile,
|
||||
initialVersion,
|
||||
runtime.ActiveVersion,
|
||||
movementDistance,
|
||||
|
|
@ -739,7 +797,7 @@ namespace GameCult.EveUnity.GenericClient.PlayModeTests
|
|||
observedCombat?.ShieldRatio ?? shieldRatioBefore,
|
||||
hullRatioBefore,
|
||||
observedCombat?.HullRatio ?? hullRatioBefore,
|
||||
(float)observedShot.LockQuality,
|
||||
(float)(observedShot?.LockQuality ?? 0),
|
||||
aimDotDistance,
|
||||
playerViewportWidth,
|
||||
playerViewportHeight,
|
||||
|
|
@ -754,6 +812,7 @@ namespace GameCult.EveUnity.GenericClient.PlayModeTests
|
|||
initialPickupEntityCount,
|
||||
finalPickupEntityCount,
|
||||
pickupCollectionEventCount,
|
||||
destructionPickupObserved,
|
||||
pickupCollection,
|
||||
camera.transform.position,
|
||||
camera.transform.forward,
|
||||
|
|
@ -783,6 +842,7 @@ namespace GameCult.EveUnity.GenericClient.PlayModeTests
|
|||
}
|
||||
|
||||
private static void WriteWitnessFacts(
|
||||
string witnessProfile,
|
||||
long initialVersion,
|
||||
long finalVersion,
|
||||
float movementDistance,
|
||||
|
|
@ -822,6 +882,7 @@ namespace GameCult.EveUnity.GenericClient.PlayModeTests
|
|||
int initialPickupEntityCount,
|
||||
int finalPickupEntityCount,
|
||||
int pickupCollectionEventCount,
|
||||
bool destructionPickupObserved,
|
||||
EveUnityFeedbackEvent pickupCollection,
|
||||
Vector3 cameraPosition,
|
||||
Vector3 cameraForward,
|
||||
|
|
@ -838,6 +899,7 @@ namespace GameCult.EveUnity.GenericClient.PlayModeTests
|
|||
if (string.IsNullOrWhiteSpace(path)) return;
|
||||
var document = new WitnessFacts
|
||||
{
|
||||
witnessProfile = witnessProfile,
|
||||
providerAdvertisement = true,
|
||||
providerAssets = true,
|
||||
environmentPresentation = environmentPresentation,
|
||||
|
|
@ -879,9 +941,12 @@ namespace GameCult.EveUnity.GenericClient.PlayModeTests
|
|||
tractorBeamParticleSystemCount = tractorBeamParticleSystemCount,
|
||||
pickupCollection = pickupCollection != null && pickupCollectionEventCount == 1 &&
|
||||
pickupCollection.CargoQuantityAfter - pickupCollection.CargoQuantityBefore == pickupCollection.ScalarValue,
|
||||
destructionLoot = destructionPickupObserved && pickupCollection != null &&
|
||||
pickupCollection.EventId.StartsWith("ymir-fact:", StringComparison.Ordinal),
|
||||
initialPickupEntityCount = initialPickupEntityCount,
|
||||
finalPickupEntityCount = finalPickupEntityCount,
|
||||
pickupCollectionEventCount = pickupCollectionEventCount,
|
||||
pickupEventId = pickupCollection?.EventId ?? "",
|
||||
pickupItemKey = pickupCollection?.ItemKey ?? "",
|
||||
pickupQuantity = (float)(pickupCollection?.ScalarValue ?? 0),
|
||||
cargoQuantityBeforePickup = (float)(pickupCollection?.CargoQuantityBefore ?? 0),
|
||||
|
|
@ -909,6 +974,7 @@ namespace GameCult.EveUnity.GenericClient.PlayModeTests
|
|||
[Serializable]
|
||||
private sealed class WitnessFacts
|
||||
{
|
||||
public string witnessProfile;
|
||||
public bool providerAdvertisement;
|
||||
public bool providerAssets;
|
||||
public bool environmentPresentation;
|
||||
|
|
@ -949,9 +1015,11 @@ namespace GameCult.EveUnity.GenericClient.PlayModeTests
|
|||
public float tractorReleasedPower;
|
||||
public int tractorBeamParticleSystemCount;
|
||||
public bool pickupCollection;
|
||||
public bool destructionLoot;
|
||||
public int initialPickupEntityCount;
|
||||
public int finalPickupEntityCount;
|
||||
public int pickupCollectionEventCount;
|
||||
public string pickupEventId;
|
||||
public string pickupItemKey;
|
||||
public float pickupQuantity;
|
||||
public float cargoQuantityBeforePickup;
|
||||
|
|
|
|||
|
|
@ -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.13",
|
||||
"org.gamecult.cultlib": "https://github.com/GameCult/CultLib.git?path=/unity/org.gamecult.cultlib#cultlib-unity-v1.0.14",
|
||||
"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.2",
|
||||
"org.gamecult.eve.unity-scene": "https://github.com/GameCult/EveUnity.git?path=/packages/org.gamecult.eve.unity-scene#eveunity-scene-v0.3.49",
|
||||
"org.gamecult.eve.unity-scene": "https://github.com/GameCult/EveUnity.git?path=/packages/org.gamecult.eve.unity-scene#eveunity-scene-v0.3.50",
|
||||
"org.gamecult.eve.unity-uitoolkit": "https://github.com/GameCult/EveUnity.git?path=/packages/org.gamecult.eve.unity-uitoolkit#eveunity-uitoolkit-v0.1.1"
|
||||
},
|
||||
"testables": [
|
||||
|
|
|
|||
|
|
@ -119,11 +119,11 @@
|
|||
}
|
||||
},
|
||||
"org.gamecult.cultlib": {
|
||||
"version": "https://github.com/GameCult/CultLib.git?path=/unity/org.gamecult.cultlib#cultlib-unity-v1.0.13",
|
||||
"version": "https://github.com/GameCult/CultLib.git?path=/unity/org.gamecult.cultlib#cultlib-unity-v1.0.14",
|
||||
"depth": 0,
|
||||
"source": "git",
|
||||
"dependencies": {},
|
||||
"hash": "feb5c71513e71d681699f462fe3682b3168c6f73"
|
||||
"hash": "4b7162022a8976f7941b5a7a69acf50f1b6d532b"
|
||||
},
|
||||
"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": "140e1bd963a0033e66777a3b2c5fe6e9c97dfe32"
|
||||
},
|
||||
"org.gamecult.eve.unity-scene": {
|
||||
"version": "https://github.com/GameCult/EveUnity.git?path=/packages/org.gamecult.eve.unity-scene#eveunity-scene-v0.3.49",
|
||||
"version": "https://github.com/GameCult/EveUnity.git?path=/packages/org.gamecult.eve.unity-scene#eveunity-scene-v0.3.50",
|
||||
"depth": 0,
|
||||
"source": "git",
|
||||
"dependencies": {
|
||||
"org.gamecult.cultlib": "1.0.13",
|
||||
"org.gamecult.cultlib": "1.0.14",
|
||||
"org.gamecult.eve.plugin-fields": "0.2.3",
|
||||
"org.gamecult.eve.surface": "0.2.2",
|
||||
"com.unity.mathematics": "1.3.2",
|
||||
"com.unity.render-pipelines.universal": "17.4.0",
|
||||
"com.unity.modules.particlesystem": "1.0.0"
|
||||
},
|
||||
"hash": "e6ba1a36f6f0bce8df2c9834c25a8a980f03413c"
|
||||
"hash": "7c8442e12264806a53a9e0e08b7b090dcddb8c90"
|
||||
},
|
||||
"org.gamecult.eve.unity-uitoolkit": {
|
||||
"version": "https://github.com/GameCult/EveUnity.git?path=/packages/org.gamecult.eve.unity-uitoolkit#eveunity-uitoolkit-v0.1.1",
|
||||
|
|
|
|||
|
|
@ -210,6 +210,21 @@ namespace GameCult.EveUnity.GenericClient.PlayModeTests
|
|||
int pickupCollectionEventCount = 0;
|
||||
int initialPickupEntityCount = 0;
|
||||
int finalPickupEntityCount = -1;
|
||||
bool destructionPickupObserved = false;
|
||||
int maximumTrajectoryCount = 0;
|
||||
bool hitMarkerObserved = false;
|
||||
var witnessProfile = Environment.GetEnvironmentVariable("EVEUNITY_WITNESS_PROFILE") ??
|
||||
"full-session-gameplay";
|
||||
Assert.That(witnessProfile,
|
||||
Is.EqualTo("cold-start-lowering").Or.EqualTo("full-session-gameplay"),
|
||||
"The witness profile must name an explicit proof boundary.");
|
||||
var requireSessionGameplay = string.Equals(
|
||||
witnessProfile,
|
||||
"full-session-gameplay",
|
||||
StringComparison.Ordinal);
|
||||
EveUnityAimPresentationRenderer aimRenderer = null;
|
||||
EveUnityBeamPresentation beamPresentation = null;
|
||||
EveUnityBeamPresentationRenderer beamRenderer = null;
|
||||
|
||||
try
|
||||
{
|
||||
|
|
@ -280,8 +295,9 @@ namespace GameCult.EveUnity.GenericClient.PlayModeTests
|
|||
Assert.That(host.PresentedEntities.CurrentGeneration.Entities.Count, Is.GreaterThan(0));
|
||||
initialPickupEntityCount = host.PresentedEntities.CurrentGeneration.Entities.Count(entity =>
|
||||
string.Equals(entity.EntityKind, "pickup", StringComparison.Ordinal));
|
||||
Assert.That(initialPickupEntityCount, Is.GreaterThan(0),
|
||||
"The daemon fixture did not publish its physical starter pickup through the entity SoA.");
|
||||
if (requireSessionGameplay)
|
||||
Assert.That(initialPickupEntityCount, Is.Zero,
|
||||
"The witness scenario must obtain salvage from daemon-owned destruction, not a boot-seeded pickup.");
|
||||
runtime.FeedbackAvailable += feedback =>
|
||||
{
|
||||
if (!string.Equals(feedback.Kind, "pickup.collected", StringComparison.Ordinal)) return;
|
||||
|
|
@ -343,6 +359,8 @@ namespace GameCult.EveUnity.GenericClient.PlayModeTests
|
|||
AssertReconciledProviderReceipt(runtime.LastReceipt, release.CommandId,
|
||||
provider.Selection.ProviderId, provider.Selection.SurfaceId, releaseVersion);
|
||||
|
||||
if (requireSessionGameplay)
|
||||
{
|
||||
var movementVersion = runtime.ActiveVersion;
|
||||
var focus = runtime.SubmitFocusIntent(playerId);
|
||||
var focusDeadline = Time.realtimeSinceStartup + 12f;
|
||||
|
|
@ -399,10 +417,10 @@ namespace GameCult.EveUnity.GenericClient.PlayModeTests
|
|||
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 aimRenderer = root.GetComponent<EveUnityAimPresentationRenderer>();
|
||||
aimRenderer = root.GetComponent<EveUnityAimPresentationRenderer>();
|
||||
Assert.That(aimRenderer, Is.Not.Null);
|
||||
|
||||
var beamPresentation = EveUnityBeamPresentation.FindAll(runtime.ActiveProjection).SingleOrDefault();
|
||||
beamPresentation = EveUnityBeamPresentation.FindAll(runtime.ActiveProjection).SingleOrDefault();
|
||||
Assert.That(beamPresentation, Is.Not.Null,
|
||||
"The provider did not advertise its continuous beam presentation.");
|
||||
Assert.That(beamPresentation.ActivationActionId, Is.Not.Empty,
|
||||
|
|
@ -429,7 +447,7 @@ namespace GameCult.EveUnity.GenericClient.PlayModeTests
|
|||
Assert.That(provider.ResolvePrefab(new EveUnityPlayableWorldAssetBinding(
|
||||
beamPresentation.AssetRole, beamPresentation.AssetRole, "provider-asset-ref")), Is.Not.Null,
|
||||
$"The provider asset catalog did not resolve beam role '{beamPresentation.AssetRole}'.");
|
||||
var beamRenderer = root.GetComponent<EveUnityBeamPresentationRenderer>();
|
||||
beamRenderer = root.GetComponent<EveUnityBeamPresentationRenderer>();
|
||||
Assert.That(beamRenderer, Is.Not.Null);
|
||||
beamRenderer.RefreshNow();
|
||||
Assert.That(beamRenderer.ActiveBeamCount, Is.EqualTo(1));
|
||||
|
|
@ -439,28 +457,6 @@ namespace GameCult.EveUnity.GenericClient.PlayModeTests
|
|||
tractorBeamParticleSystemCount = beamRenderer.ActiveParticleSystemCount;
|
||||
Assert.That(tractorBeamParticleSystemCount, Is.GreaterThan(0),
|
||||
"The generic beam lowerer did not instantiate the provider-owned effect prefab.");
|
||||
var pickupDeadline = Time.realtimeSinceStartup + 12f;
|
||||
while (Time.realtimeSinceStartup < pickupDeadline &&
|
||||
(pickupCollection == null ||
|
||||
host.PresentedEntities.CurrentGeneration.Entities.Any(entity =>
|
||||
string.Equals(entity.EntityKind, "pickup", StringComparison.Ordinal))))
|
||||
{
|
||||
yield return new WaitForSecondsRealtime(0.1f);
|
||||
runtime.Refresh();
|
||||
}
|
||||
finalPickupEntityCount = host.PresentedEntities.CurrentGeneration.Entities.Count(entity =>
|
||||
string.Equals(entity.EntityKind, "pickup", StringComparison.Ordinal));
|
||||
Assert.That(pickupCollection, Is.Not.Null,
|
||||
"The released client did not observe provider-owned feedback for Ymir contact collection.");
|
||||
Assert.That(pickupCollectionEventCount, Is.EqualTo(1),
|
||||
"One Ymir contact transaction must emit one client-visible collection event.");
|
||||
Assert.That(pickupCollection.ItemKey, Is.EqualTo("scrap-metal"));
|
||||
Assert.That(pickupCollection.ScalarValue, Is.EqualTo(1));
|
||||
Assert.That(pickupCollection.CargoQuantityBefore, Is.Zero);
|
||||
Assert.That(pickupCollection.CargoQuantityAfter, Is.EqualTo(1));
|
||||
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
|
||||
|
|
@ -511,22 +507,74 @@ namespace GameCult.EveUnity.GenericClient.PlayModeTests
|
|||
Assert.That(runtime.ActiveVersion, Is.GreaterThanOrEqualTo(runtime.LastReceipt.SourceVersion));
|
||||
actionReceipt = WitnessReceipt.From("action", runtime.LastReceipt);
|
||||
|
||||
var shotDeadline = Time.realtimeSinceStartup + 15f;
|
||||
while (Time.realtimeSinceStartup < shotDeadline && observedShot == null)
|
||||
{
|
||||
yield return new WaitForSecondsRealtime(0.05f);
|
||||
runtime.Refresh();
|
||||
}
|
||||
Assert.That(observedShot, Is.Not.Null,
|
||||
"A reconciled fire request never reached daemon-owned lock acquisition and shot resolution.");
|
||||
observedCombat = EveUnityCombatPresentation.Find(runtime.ActiveProjection);
|
||||
Assert.That(observedShot.TargetEntityIndex, Is.EqualTo(targetEntityIndex));
|
||||
Assert.That(observedShot.LockQuality, Is.GreaterThanOrEqualTo(0.99));
|
||||
var trajectories = root.GetComponent<EveUnityShotTrajectoryRenderer>();
|
||||
var combatRenderer = root.GetComponent<EveUnityCombatPresentationRenderer>();
|
||||
Assert.That(trajectories, Is.Not.Null);
|
||||
Assert.That(trajectories.ActiveTrajectoryCount, Is.GreaterThan(0));
|
||||
Assert.That(combatRenderer, Is.Not.Null);
|
||||
var shotDeadline = Time.realtimeSinceStartup + 20f;
|
||||
while (Time.realtimeSinceStartup < shotDeadline &&
|
||||
(observedShot == null ||
|
||||
host.PresentedEntities.CurrentGeneration.Entities.Any(entity =>
|
||||
entity.EntityId == targetEntityId) ||
|
||||
(!destructionPickupObserved && pickupCollection == null)))
|
||||
{
|
||||
yield return new WaitForSecondsRealtime(0.05f);
|
||||
runtime.Refresh();
|
||||
destructionPickupObserved |= host.PresentedEntities.CurrentGeneration.Entities.Any(entity =>
|
||||
string.Equals(entity.EntityKind, "pickup", StringComparison.Ordinal));
|
||||
maximumTrajectoryCount = Math.Max(maximumTrajectoryCount, trajectories.ActiveTrajectoryCount);
|
||||
combatRenderer.RefreshNow();
|
||||
hitMarkerObserved |= combatRenderer.HitMarkerVisible;
|
||||
}
|
||||
Assert.That(observedShot, Is.Not.Null,
|
||||
"A reconciled fire request never reached daemon-owned lock acquisition and shot resolution.");
|
||||
Assert.That(host.PresentedEntities.CurrentGeneration.Entities.Any(entity =>
|
||||
entity.EntityId == targetEntityId), Is.False,
|
||||
"The low-hull witness raider was not destroyed by daemon combat.");
|
||||
Assert.That(destructionPickupObserved || pickupCollection != null, Is.True,
|
||||
"Daemon destruction did not publish its guaranteed cargo as a pickup.");
|
||||
var collectionDeadline = Time.realtimeSinceStartup + 15f;
|
||||
var nextTractorAimAt = 0f;
|
||||
while (Time.realtimeSinceStartup < collectionDeadline && pickupCollection == null)
|
||||
{
|
||||
yield return new WaitForSecondsRealtime(0.05f);
|
||||
runtime.Refresh();
|
||||
maximumTrajectoryCount = Math.Max(maximumTrajectoryCount, trajectories.ActiveTrajectoryCount);
|
||||
combatRenderer.RefreshNow();
|
||||
hitMarkerObserved |= combatRenderer.HitMarkerVisible;
|
||||
var pickupFact = host.PresentedEntities.CurrentGeneration.Entities.FirstOrDefault(entity =>
|
||||
string.Equals(entity.EntityKind, "pickup", StringComparison.Ordinal));
|
||||
if (pickupFact == null || Time.realtimeSinceStartup < nextTractorAimAt)
|
||||
continue;
|
||||
playerFact = host.PresentedEntities.CurrentGeneration.Entities
|
||||
.Single(entity => entity.EntityId == playerId);
|
||||
var tractorAim = pickupFact.Position - playerFact.Position;
|
||||
tractorAim.y = 0;
|
||||
if (tractorAim.sqrMagnitude <= 0.0001f)
|
||||
continue;
|
||||
tractorAim.Normalize();
|
||||
runtime.SubmitLookDirectionIntent(playerId, tractorAim.x, 0f, tractorAim.z);
|
||||
nextTractorAimAt = Time.realtimeSinceStartup + 0.25f;
|
||||
}
|
||||
finalPickupEntityCount = host.PresentedEntities.CurrentGeneration.Entities.Count(entity =>
|
||||
string.Equals(entity.EntityKind, "pickup", StringComparison.Ordinal));
|
||||
Assert.That(pickupCollection, Is.Not.Null,
|
||||
"Destruction-created salvage never reached cargo through a Ymir contact fact.");
|
||||
Assert.That(pickupCollectionEventCount, Is.EqualTo(1),
|
||||
"One destruction-loot Ymir contact transaction must emit one collection event.");
|
||||
Assert.That(pickupCollection.ItemKey, Is.Not.Empty,
|
||||
"Destruction loot must retain its canonical typed item identity.");
|
||||
Assert.That(pickupCollection.EventId, Does.StartWith("ymir-fact:"),
|
||||
"Collection feedback must retain the consumed Ymir fact identity.");
|
||||
Assert.That(pickupCollection.ScalarValue, Is.EqualTo(1));
|
||||
Assert.That(pickupCollection.CargoQuantityBefore, Is.Zero);
|
||||
Assert.That(pickupCollection.CargoQuantityAfter, Is.EqualTo(1));
|
||||
Assert.That(finalPickupEntityCount, Is.Zero,
|
||||
"Collected destruction loot remained in the provider-authored entity generation.");
|
||||
observedCombat = EveUnityCombatPresentation.Find(runtime.ActiveProjection);
|
||||
Assert.That(observedShot.TargetEntityIndex, Is.EqualTo(targetEntityIndex));
|
||||
Assert.That(observedShot.LockQuality, Is.GreaterThanOrEqualTo(0.99));
|
||||
Assert.That(maximumTrajectoryCount, Is.GreaterThan(0));
|
||||
combatRenderer.RefreshNow();
|
||||
if (observedCombat != null && !string.IsNullOrWhiteSpace(observedCombat.SelectedTargetEntityId))
|
||||
{
|
||||
|
|
@ -534,7 +582,16 @@ namespace GameCult.EveUnity.GenericClient.PlayModeTests
|
|||
Assert.That(combatRenderer.LockVisible, Is.True);
|
||||
}
|
||||
if (observedShot.Hit && (observedShot.AppliedDamage > 0 || observedShot.ShieldAbsorbedDamage > 0))
|
||||
Assert.That(combatRenderer.HitMarkerVisible, Is.True);
|
||||
Assert.That(hitMarkerObserved, Is.True);
|
||||
}
|
||||
|
||||
aimRenderer ??= root.GetComponent<EveUnityAimPresentationRenderer>();
|
||||
Assert.That(aimRenderer, Is.Not.Null);
|
||||
beamPresentation ??= EveUnityBeamPresentation.FindAll(runtime.ActiveProjection).SingleOrDefault();
|
||||
Assert.That(beamPresentation, Is.Not.Null,
|
||||
"The provider did not advertise its continuous beam presentation.");
|
||||
beamRenderer ??= root.GetComponent<EveUnityBeamPresentationRenderer>();
|
||||
finalPickupEntityCount = Math.Max(0, finalPickupEntityCount);
|
||||
|
||||
var camera = cameraObject.AddComponent<Camera>();
|
||||
camera.clearFlags = CameraClearFlags.SolidColor;
|
||||
|
|
@ -715,6 +772,7 @@ namespace GameCult.EveUnity.GenericClient.PlayModeTests
|
|||
Assert.That(fieldViewport, Is.Not.Null);
|
||||
Assert.That(fieldSplats, Is.Not.Null);
|
||||
WriteWitnessFacts(
|
||||
witnessProfile,
|
||||
initialVersion,
|
||||
runtime.ActiveVersion,
|
||||
movementDistance,
|
||||
|
|
@ -739,7 +797,7 @@ namespace GameCult.EveUnity.GenericClient.PlayModeTests
|
|||
observedCombat?.ShieldRatio ?? shieldRatioBefore,
|
||||
hullRatioBefore,
|
||||
observedCombat?.HullRatio ?? hullRatioBefore,
|
||||
(float)observedShot.LockQuality,
|
||||
(float)(observedShot?.LockQuality ?? 0),
|
||||
aimDotDistance,
|
||||
playerViewportWidth,
|
||||
playerViewportHeight,
|
||||
|
|
@ -754,6 +812,7 @@ namespace GameCult.EveUnity.GenericClient.PlayModeTests
|
|||
initialPickupEntityCount,
|
||||
finalPickupEntityCount,
|
||||
pickupCollectionEventCount,
|
||||
destructionPickupObserved,
|
||||
pickupCollection,
|
||||
camera.transform.position,
|
||||
camera.transform.forward,
|
||||
|
|
@ -783,6 +842,7 @@ namespace GameCult.EveUnity.GenericClient.PlayModeTests
|
|||
}
|
||||
|
||||
private static void WriteWitnessFacts(
|
||||
string witnessProfile,
|
||||
long initialVersion,
|
||||
long finalVersion,
|
||||
float movementDistance,
|
||||
|
|
@ -822,6 +882,7 @@ namespace GameCult.EveUnity.GenericClient.PlayModeTests
|
|||
int initialPickupEntityCount,
|
||||
int finalPickupEntityCount,
|
||||
int pickupCollectionEventCount,
|
||||
bool destructionPickupObserved,
|
||||
EveUnityFeedbackEvent pickupCollection,
|
||||
Vector3 cameraPosition,
|
||||
Vector3 cameraForward,
|
||||
|
|
@ -838,6 +899,7 @@ namespace GameCult.EveUnity.GenericClient.PlayModeTests
|
|||
if (string.IsNullOrWhiteSpace(path)) return;
|
||||
var document = new WitnessFacts
|
||||
{
|
||||
witnessProfile = witnessProfile,
|
||||
providerAdvertisement = true,
|
||||
providerAssets = true,
|
||||
environmentPresentation = environmentPresentation,
|
||||
|
|
@ -879,9 +941,12 @@ namespace GameCult.EveUnity.GenericClient.PlayModeTests
|
|||
tractorBeamParticleSystemCount = tractorBeamParticleSystemCount,
|
||||
pickupCollection = pickupCollection != null && pickupCollectionEventCount == 1 &&
|
||||
pickupCollection.CargoQuantityAfter - pickupCollection.CargoQuantityBefore == pickupCollection.ScalarValue,
|
||||
destructionLoot = destructionPickupObserved && pickupCollection != null &&
|
||||
pickupCollection.EventId.StartsWith("ymir-fact:", StringComparison.Ordinal),
|
||||
initialPickupEntityCount = initialPickupEntityCount,
|
||||
finalPickupEntityCount = finalPickupEntityCount,
|
||||
pickupCollectionEventCount = pickupCollectionEventCount,
|
||||
pickupEventId = pickupCollection?.EventId ?? "",
|
||||
pickupItemKey = pickupCollection?.ItemKey ?? "",
|
||||
pickupQuantity = (float)(pickupCollection?.ScalarValue ?? 0),
|
||||
cargoQuantityBeforePickup = (float)(pickupCollection?.CargoQuantityBefore ?? 0),
|
||||
|
|
@ -909,6 +974,7 @@ namespace GameCult.EveUnity.GenericClient.PlayModeTests
|
|||
[Serializable]
|
||||
private sealed class WitnessFacts
|
||||
{
|
||||
public string witnessProfile;
|
||||
public bool providerAdvertisement;
|
||||
public bool providerAssets;
|
||||
public bool environmentPresentation;
|
||||
|
|
@ -949,9 +1015,11 @@ namespace GameCult.EveUnity.GenericClient.PlayModeTests
|
|||
public float tractorReleasedPower;
|
||||
public int tractorBeamParticleSystemCount;
|
||||
public bool pickupCollection;
|
||||
public bool destructionLoot;
|
||||
public int initialPickupEntityCount;
|
||||
public int finalPickupEntityCount;
|
||||
public int pickupCollectionEventCount;
|
||||
public string pickupEventId;
|
||||
public string pickupItemKey;
|
||||
public float pickupQuantity;
|
||||
public float cargoQuantityBeforePickup;
|
||||
|
|
|
|||
|
|
@ -2,22 +2,24 @@
|
|||
|
||||
## Current result
|
||||
|
||||
The warm-cache released-package witness passes with:
|
||||
The released-package witness passes in separate cold-lowering and warm-gameplay
|
||||
profiles with:
|
||||
|
||||
- `org.gamecult.eve.unity-scene` `0.3.44`, commit
|
||||
`03ce78031f28d7bfae4dcdc477a7273c6ecae55b`;
|
||||
- `org.gamecult.eve.unity-scene` `0.3.50`, commit
|
||||
`7c8442e12264806a53a9e0e08b7b090dcddb8c90`;
|
||||
- `org.gamecult.eve.plugin-fields` `0.2.3`, commit
|
||||
`c5a4a75c1b727499b16c2dae1895f29e2a9f72f0`;
|
||||
- `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
|
||||
`feb5c71513e71d681699f462fe3682b3168c6f73`;
|
||||
- `org.gamecult.cultlib` `1.0.14`, commit
|
||||
`4b7162022a8976f7941b5a7a69acf50f1b6d532b`;
|
||||
- the generic `ReleaseConsumerProject` client connected directly to the
|
||||
Aetheria daemon.
|
||||
|
||||
Evidence is in `artifacts/aetheria-daemon-fossil-fog-producers`. The PlayMode test
|
||||
Evidence is in `artifacts/aetheria-daemon-cultlib-1014-cold-final` and
|
||||
`artifacts/aetheria-daemon-cultlib-1014-warm-final`. The warm 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,
|
||||
|
|
@ -50,7 +52,7 @@ Fields splat layers. Its surface contract contains logical ports such as
|
|||
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 `21` pilot-camera frames from daemon frame `223`.
|
||||
four layers, and composited `91` pilot-camera frames through daemon frame `643`.
|
||||
The provider variant advertises the fossil shader's raymarch, temporal-history,
|
||||
and composite passes plus their logical ports. EveUnity allocates and resets the
|
||||
history targets generically; a partial temporal ABI fails closed.
|
||||
|
|
@ -64,19 +66,20 @@ 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: `191,353`;
|
||||
- pilot average luminance: `0.0283105`;
|
||||
- pilot bright pixels: `705`;
|
||||
- map-channel renderers: `11`;
|
||||
- map changed pixels: `4,444`;
|
||||
- pilot changed pixels: `196,179`;
|
||||
- pilot average luminance: `0.0993295`;
|
||||
- pilot bright pixels: `1,348`;
|
||||
- map-channel renderers: `10`;
|
||||
- map changed pixels: `4,627`;
|
||||
- native cockpit progress bars: `7`;
|
||||
- daemon tractor power at capture: `1.0` after the held input completed its
|
||||
authored ramp;
|
||||
- daemon tractor power after the advertised release: `0`;
|
||||
- provider tractor particle systems: `1`;
|
||||
- pickup entities before/after collection: `1` / `0`;
|
||||
- pickup entities before/after destruction and collection: `0` / `0`;
|
||||
- provider `pickup.collected` events: exactly `1`;
|
||||
- collected item and quantity: `scrap-metal`, `1`;
|
||||
- collected item and quantity: one canonical `aetheria.item_definition:*` key,
|
||||
quantity `1`;
|
||||
- authoritative cargo quantity before/after: `0` / `1`;
|
||||
- the pilot camera excludes the advertised map layer;
|
||||
- the player prefab's embedded layer-14 map icon contributes exactly `0` pilot
|
||||
|
|
@ -87,7 +90,7 @@ Visual inspection confirms that map glyphs are absent from the pilot frame and
|
|||
present in the map-only frame. This is transport, authority, field-production,
|
||||
and lowering proof, not visual parity: the current pilot capture is nearly black
|
||||
with sparse clipped geometry instead of the fossil's bright blue gravity-shaped
|
||||
fog sea. Release `0.3.44` adds generic lowering for Eve Fields `0.2.3`'s
|
||||
fog sea. Release `0.3.50` includes generic lowering for Eve Fields `0.2.3`'s
|
||||
`AnimatedRadialCosine` source alongside `PowerPulse`, dither-scale,
|
||||
temporal-history, and finite-look-at semantics. Aetheria publishes the fossil
|
||||
global animated simplex/cellular/ambient producer stack, body-owned radial wave
|
||||
|
|
@ -119,10 +122,12 @@ build removes the fossil's embedded tractor object from player/ship prefabs, so
|
|||
the standalone `beam.presentation` prefab is the only tractor renderer. Its
|
||||
cyan/yellow dotted band is visible in the pilot capture and absent from the map
|
||||
capture.
|
||||
The same released run begins with one provider-owned pickup in the entity SoA.
|
||||
Held tractor input produces a Ymir Begin contact fact; the daemon consumes that
|
||||
fact once, removes the pickup once, changes cargo from `0` to `1`, and emits one
|
||||
`pickup.collected` feedback event carrying the exact delta. Aetheria's Ymir body
|
||||
The warm gameplay run begins with no pickup. A daemon-authoritative shot destroys
|
||||
the deterministic salvage target and creates one provider-owned pickup carrying
|
||||
a canonical generated cargo item. Held tractor input produces a Ymir Begin
|
||||
contact fact; the daemon consumes that fact once, removes the pickup once,
|
||||
changes cargo from `0` to `1`, and emits one `pickup.collected` feedback event
|
||||
whose identity begins with `ymir-fact:`. Aetheria's Ymir body
|
||||
mapping excludes stations and non-ship world bodies from pickup collision facts,
|
||||
so ambient station contact cannot become a cargo writer. Capacity rejection and
|
||||
its `cargo-capacity` reason are covered by the daemon smoke, not claimed as a
|
||||
|
|
@ -138,19 +143,23 @@ Primary artifacts:
|
|||
- `aetheria-daemon-world.png`: pilot-camera capture;
|
||||
- `aetheria-daemon-map.png`: map-only capture.
|
||||
|
||||
## Cold delivery is not currently proven
|
||||
## Cold delivery proof
|
||||
|
||||
The current `46,412,252` byte Unity bundle (SHA-256
|
||||
`bc45d84b54666e813bfccca12496ba4a3533ed08896dfc57de726749574fa796`)
|
||||
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
|
||||
transport fault and is not an accepted fix.
|
||||
The `cold-start-lowering` profile starts with zero bodies and zero partials. It
|
||||
receives the `46,412,384` byte Unity bundle through the managed
|
||||
`cultmesh.content.v1` session, verifies SHA-256
|
||||
`f5960f240ecfd56de417e8e51fde34cfb962fc6fcf0840cc373190e7dbc6048f`,
|
||||
atomically promotes one `.body`, and leaves zero partials under the unchanged
|
||||
300-second deadline. The full witness took `84.991` seconds; the Unity test took
|
||||
`43.212` seconds. It then lowered provider assets, movement, environment, four
|
||||
field layers across `127` composites, and both camera channels. The pilot camera
|
||||
excluded map objects and the map camera included them.
|
||||
|
||||
A cold witness may be claimed only when an empty cache receives the bundle over
|
||||
the intended body transport, atomically promotes the hash-addressed `.body`,
|
||||
leaves no `.partial` file, and completes the same released-client assertions.
|
||||
|
||||
`artifacts/render-channel-witness-gravity-cold-4` records a historical 0.3.18
|
||||
cold run. It remains useful regression evidence, but it does not describe the
|
||||
health of the current transport path.
|
||||
Cold asset acquisition and transient gameplay are deliberately separate proof
|
||||
profiles. Starting Unity before the provider means download time is not a
|
||||
stable lifetime for a boot-seeded enemy or pickup. The cold profile proves
|
||||
delivery and lowering; `full-session-gameplay` starts a fresh warm authoritative
|
||||
session and proves combat, destruction-created loot, Ymir contact collection,
|
||||
and exactly-once receipts. Neither profile claims negotiated mapped/zero-copy
|
||||
CDN delivery: the released path is the managed network content session and
|
||||
still copies/fragments bytes in process.
|
||||
|
|
|
|||
|
|
@ -13,10 +13,10 @@ param(
|
|||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
$expectedEveUnityCommit = "e6ba1a36f6f0bce8df2c9834c25a8a980f03413c"
|
||||
$expectedEveUnityCommit = "7c8442e12264806a53a9e0e08b7b090dcddb8c90"
|
||||
$expectedEveFieldsCommit = "c5a4a75c1b727499b16c2dae1895f29e2a9f72f0"
|
||||
$expectedEveUnityUiToolkitCommit = "4d0cbe0185bdc4fc65eb63503a7c5cb578539669"
|
||||
$expectedCultLibCommit = "feb5c71513e71d681699f462fe3682b3168c6f73"
|
||||
$expectedCultLibCommit = "4b7162022a8976f7941b5a7a69acf50f1b6d532b"
|
||||
$repoRoot = Split-Path -Parent $PSScriptRoot
|
||||
$projectRoot = $ClientProject
|
||||
$outputRoot = if ([IO.Path]::IsPathRooted($OutputDirectory)) { $OutputDirectory } else { Join-Path $repoRoot $OutputDirectory }
|
||||
|
|
@ -65,6 +65,11 @@ if ($CacheState -eq "warm" -and -not $cacheWasWarm) {
|
|||
throw "Warm witness requested without an existing verified bundle cache at $assetCachePath"
|
||||
}
|
||||
$observedCacheState = if ($cacheWasWarm) { "warm" } else { "cold" }
|
||||
$witnessProfile = if ($observedCacheState -eq "cold") {
|
||||
"cold-start-lowering"
|
||||
} else {
|
||||
"full-session-gameplay"
|
||||
}
|
||||
$witnessStartedAt = [DateTimeOffset]::UtcNow
|
||||
foreach ($ephemeralPath in @(
|
||||
$replicaPath,
|
||||
|
|
@ -161,7 +166,7 @@ $daemonArguments = @(
|
|||
"--client-cultmesh-host", "127.0.0.1",
|
||||
"--client-cultmesh-advertise-host", "127.0.0.1",
|
||||
"--client-cultmesh-port", $Port,
|
||||
"--tick-interval-ms", 250,
|
||||
"--tick-interval-ms", 20,
|
||||
"--fixed-delta-ms", 20,
|
||||
"--no-odin-announcements"
|
||||
)
|
||||
|
|
@ -177,6 +182,7 @@ $env:EVEUNITY_DISABLE_AUTO_LAUNCHER = "1"
|
|||
$env:EVEUNITY_ASSET_CACHE_PATH = $assetCachePath
|
||||
$env:EVEUNITY_WITNESS_FACTS_PATH = $factsPath
|
||||
$env:EVEUNITY_PROVIDER_READY_PATH = $providerReadyPath
|
||||
$env:EVEUNITY_WITNESS_PROFILE = $witnessProfile
|
||||
$arguments = @(
|
||||
"-batchmode", "-projectPath", $projectRoot,
|
||||
"-runTests", "-testPlatform", "PlayMode",
|
||||
|
|
@ -231,9 +237,20 @@ try {
|
|||
}
|
||||
}
|
||||
$facts = Get-Content -LiteralPath $factsPath -Raw | ConvertFrom-Json
|
||||
if (-not $facts.combatPresentation -or [string]::IsNullOrWhiteSpace($facts.shotId) -or
|
||||
[double]$facts.lockProgress -le 0.99) {
|
||||
throw "Live witness did not observe daemon-owned selection, completed lock, and a resolved shot."
|
||||
if ($facts.witnessProfile -ne $witnessProfile) {
|
||||
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.mapCameraIncludesMapChannel -or [int]$facts.fieldVolumeLayerCount -le 0 -or
|
||||
[long]$facts.fieldVolumeCompositeCount -le 0) {
|
||||
throw "Live witness did not prove provider assets, movement, Fields lowering, and camera-channel separation."
|
||||
}
|
||||
if ($witnessProfile -eq "full-session-gameplay" -and
|
||||
(-not $facts.combatPresentation -or [string]::IsNullOrWhiteSpace($facts.shotId) -or
|
||||
[double]$facts.lockProgress -le 0.99 -or -not $facts.pickupCollection -or
|
||||
-not $facts.destructionLoot -or [int]$facts.pickupCollectionEventCount -ne 1)) {
|
||||
throw "Full-session witness did not prove combat and exactly-once destruction-loot Ymir contact collection."
|
||||
}
|
||||
$releaseLock = Get-Content -LiteralPath (Join-Path $repoRoot "ReleaseConsumerProject\Packages\packages-lock.json") -Raw | ConvertFrom-Json
|
||||
$releasedPackageClient = $projectRoot -eq "ReleaseConsumerProject"
|
||||
|
|
@ -297,6 +314,7 @@ try {
|
|||
generatedAtUtc = [DateTimeOffset]::UtcNow.ToString("O")
|
||||
execution = [ordered]@{
|
||||
cacheState = $observedCacheState
|
||||
profile = $witnessProfile
|
||||
durationMs = $durationMs
|
||||
testDurationMs = $testDurationMs
|
||||
}
|
||||
|
|
@ -335,7 +353,11 @@ try {
|
|||
sizeBytes = $resultsArtifact.Length
|
||||
}
|
||||
)
|
||||
authority = "released-generic-runtime-observes-provider-assets-authoritative-combat-receipts-and-camera-channel-separation"
|
||||
authority = if ($witnessProfile -eq "full-session-gameplay") {
|
||||
"released-generic-runtime-observes-provider-assets-authoritative-gameplay-receipts-ymir-contact-collection-and-camera-channel-separation"
|
||||
} else {
|
||||
"released-generic-runtime-cold-loads-provider-assets-lowers-playable-world-and-preserves-camera-channel-separation"
|
||||
}
|
||||
}
|
||||
$witness | ConvertTo-Json -Depth 12 | Set-Content -LiteralPath $witnessPath -Encoding UTF8
|
||||
$stateWitnessPath = Join-Path $outputRoot "runtime-witness.$observedCacheState.json"
|
||||
|
|
@ -349,6 +371,6 @@ try {
|
|||
finally {
|
||||
if ($null -ne $unity -and -not $unity.HasExited) { Stop-Process -Id $unity.Id -Force -ErrorAction SilentlyContinue }
|
||||
if ($null -ne $daemon -and -not $daemon.HasExited) { Stop-Process -Id $daemon.Id -Force }
|
||||
Remove-Item Env:EVEUNITY_RENDEZVOUS_ENDPOINT, Env:EVEUNITY_PROVIDER_ENDPOINT, Env:EVEUNITY_PROVIDER_ID, Env:EVEUNITY_SURFACE_ID, Env:EVEUNITY_REPLICA_PATH, Env:EVEUNITY_AETHERIA_CAPTURE_PATH, Env:EVEUNITY_AETHERIA_MAP_CAPTURE_PATH, Env:EVEUNITY_DISABLE_AUTO_LAUNCHER, Env:EVEUNITY_ASSET_CACHE_PATH, Env:EVEUNITY_WITNESS_FACTS_PATH, Env:EVEUNITY_PROVIDER_READY_PATH -ErrorAction SilentlyContinue
|
||||
Remove-Item Env:EVEUNITY_RENDEZVOUS_ENDPOINT, Env:EVEUNITY_PROVIDER_ENDPOINT, Env:EVEUNITY_PROVIDER_ID, Env:EVEUNITY_SURFACE_ID, Env:EVEUNITY_REPLICA_PATH, Env:EVEUNITY_AETHERIA_CAPTURE_PATH, Env:EVEUNITY_AETHERIA_MAP_CAPTURE_PATH, Env:EVEUNITY_DISABLE_AUTO_LAUNCHER, Env:EVEUNITY_ASSET_CACHE_PATH, Env:EVEUNITY_WITNESS_FACTS_PATH, Env:EVEUNITY_PROVIDER_READY_PATH, Env:EVEUNITY_WITNESS_PROFILE -ErrorAction SilentlyContinue
|
||||
Remove-Item Env:AETHERIA_TRACE_EVE_SNAPSHOTS -ErrorAction SilentlyContinue
|
||||
}
|
||||
|
|
|
|||
|
|
@ -85,7 +85,7 @@ $daemon = Start-Process dotnet -ArgumentList @(
|
|||
"--client-cultmesh-host", "127.0.0.1",
|
||||
"--client-cultmesh-advertise-host", "127.0.0.1",
|
||||
"--client-cultmesh-port", $Port,
|
||||
"--tick-interval-ms", 250,
|
||||
"--tick-interval-ms", 20,
|
||||
"--fixed-delta-ms", 20,
|
||||
"--no-odin-announcements"
|
||||
) -PassThru -WindowStyle Hidden -RedirectStandardOutput $daemonLog -RedirectStandardError "$daemonLog.error"
|
||||
|
|
|
|||
Reference in a new issue