Project physical pickups through Eve world scenes

This commit is contained in:
Meta Vens 2026-07-11 19:14:38 +01:00
commit e2e08b399a
3 changed files with 44 additions and 0 deletions

View file

@ -1,5 +1,6 @@
using Aetheria.State.Daemon;
using GameCult.Aetheria.State.Verse;
using System.Globalization;
var checks = new AetheriaDaemonYmirSmokeChecks();
checks.Run();
@ -814,6 +815,15 @@ internal sealed class AetheriaDaemonYmirSmokeChecks
Require(pickup.VelocityX < 0 && pickup.PositionX < 60, "Ymir must pull a pickup inside the forward tractor volume toward the ship");
RequireEqual(1, pickup.Item.Quantity, "tractor force must not consume the pickup item");
RequireEqual(0, CargoQuantity(ship, "salvage"), "scooping must remain a separate capacity-checked transaction");
var frame = new AetheriaRuntimeDaemonFrameDocument { FrameId = 1, Run = run };
var surface = AetheriaRuntimeDaemonGameSurfaceBuilder.Build(frame, new AetheriaRuntimeDaemonHealthDocument(), AetheriaRuntimeDaemonCommandBoundaryDocument.Create("daemon"));
var pickupNode = Flatten(surface.Surface.Root).Single(node => node.Id == "aetheria.daemon.game.world.pickup.3");
RequireEqual("prefab.entity.pickup", pickupNode.Props["assetRef"], "Eve world must reference provider-owned pickup asset semantics");
RequireEqual("salvage", pickupNode.Props["itemKey"], "Eve pickup must expose item identity");
Require(double.Parse(pickupNode.Props["remainingLifetime"], CultureInfo.InvariantCulture) < 30,
"Eve pickup must expose daemon-owned remaining lifetime");
Require(AetheriaRuntimeAssets.ProjectManifest(null).Assets.Any(asset => asset.Ref.AssetKey == "prefab.entity.pickup"),
"provider asset manifest must advertise the pickup visual used by Eve");
}
private static void PickupIsCapacityCheckedExactlyOnceAndExpires()

View file

@ -27,6 +27,7 @@ namespace GameCult.Aetheria.State.Verse
Add(entries, MapPrefab("prefab.entity.station", "Station", "Prefabs/Stations/AsteroidOutpost"));
Add(entries, MapPrefab("prefab.entity.orbital", "Orbital", "Prefabs/Stations/Zenith"));
Add(entries, MapPrefab("prefab.entity.projectile", "Projectile", "Prefabs/Lightning"));
Add(entries, MapPrefab("prefab.entity.pickup", "Pickup", "Assets/Prefabs/RPG/Pickups/Tetrahedron"));
foreach (var inventoryAsset in InventoryUiAssets())
Add(entries, inventoryAsset);

View file

@ -461,6 +461,9 @@ namespace GameCult.Aetheria.State.Verse
.Concat((zone.Projectiles ?? Array.Empty<AetheriaRuntimeProjectileCommit>())
.Where(projectile => projectile != null && projectile.LifetimeSeconds > 0)
.Select(projectile => PlayableWorldProjectile(projectile, run, zone)))
.Concat((zone.DroppedPickups ?? Array.Empty<AetheriaRuntimeDroppedPickupCommit>())
.Where(pickup => pickup != null && pickup.AgeSeconds < pickup.LifetimeSeconds)
.Select(pickup => PlayableWorldPickup(pickup, run, zone)))
.ToArray();
return new AetheriaRuntimeSurfaceComponent(
@ -589,6 +592,36 @@ namespace GameCult.Aetheria.State.Verse
Array.Empty<AetheriaRuntimeSurfaceComponent>());
}
private static AetheriaRuntimeSurfaceComponent PlayableWorldPickup(
AetheriaRuntimeDroppedPickupCommit pickup,
AetheriaRuntimeRunCheckpointCommit run,
AetheriaRuntimeZoneSnapshotCommit zone)
{
var pickupId = $"{run.RunId}:zone:{zone.ZoneIndex}:pickup:{pickup.PickupIndex}";
var props = new Dictionary<string, string>(StringComparer.Ordinal)
{
["entityId"] = pickupId,
["entityKind"] = "pickup",
["label"] = pickup.Item?.ItemKey ?? "Pickup",
["itemKey"] = pickup.Item?.ItemKey ?? "",
["quantity"] = Math.Max(1, pickup.Item?.Quantity ?? 1).ToString(CultureInfo.InvariantCulture),
["assetRef"] = "prefab.entity.pickup",
["position"] = string.Join(",", new[] { pickup.PositionX.ToString("0.###", CultureInfo.InvariantCulture), pickup.PositionY.ToString("0.###", CultureInfo.InvariantCulture), pickup.PositionZ.ToString("0.###", CultureInfo.InvariantCulture) }),
["velocity"] = string.Join(",", new[] { pickup.VelocityX.ToString("0.###", CultureInfo.InvariantCulture), pickup.VelocityY.ToString("0.###", CultureInfo.InvariantCulture), pickup.VelocityZ.ToString("0.###", CultureInfo.InvariantCulture) }),
["radius"] = "5",
["age"] = FormatNumber(pickup.AgeSeconds),
["lifetime"] = FormatNumber(pickup.LifetimeSeconds),
["remainingLifetime"] = FormatNumber(Math.Max(0, pickup.LifetimeSeconds - pickup.AgeSeconds)),
["selectable"] = "false",
["controllable"] = "false"
};
return new AetheriaRuntimeSurfaceComponent(
$"aetheria.daemon.game.world.pickup.{pickup.PickupIndex}",
"world.entity3d",
props,
Array.Empty<AetheriaRuntimeSurfaceComponent>());
}
private static string PlayableWorldAssetRef(AetheriaRuntimeEntitySnapshotCommit entity)
{
if (entity == null)