Aetheria/tests/Aetheria.Shared.Tests/LayerTests.cs
Meta Vens be7f2b0787 Layers: one owner puts orbitals above the ship plane and floaters below; per-hull GridOffset is deleted
GameplaySettings carries ShipLayerHeight 3, OrbitalLayerHeight 15, FloaterLayerHeight 0 (authored in Settings.asset).
Entity.LayerHeight (Ship, OrbitalEntity) is the one offset RideHeight adds; DrawAhead.FloaterHeight is the floater
height ItemPickup and MineInstance draw at. HullData.GridOffset (Key 26) and the pickup/mine/prefab GridOffset fields
are gone with every reader. Fixtures (DrawnPose, FireControl*, ImpactFeedback) take heights from the layer owner;
LayerTests pin each kind's layer. FloatingItemsDrawnSmoke follows. ItemPickup/MineInstance.Bind take the settings from
ZoneRenderer. Builds as one commit with its tests; not compile-verified until the cut's head run.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 02:35:07 +02:00

126 lines
5.7 KiB
C#

using System;
using System.IO;
using System.Linq;
using System.Reflection;
using CultMath;
using GameCult.Caching;
using GameCult.Caching.MessagePack;
using Xunit;
using float2 = CultMath.float2;
using float3 = CultMath.float3;
using int2 = CultMath.int2;
// Layers (aetheria-gravity cut orbital-layering; ruling orbitals-above-floaters-below): GameplaySettings' three
// heights decide where each kind of thing sits above the field. A ship rides the ship plane, every orbital entity
// (station, turret) the orbital layer above it, a floater (loot, mine) the floater layer below. No hull carries a
// height of its own.
public sealed class LayerTests : IDisposable
{
private const float ShipHeight = 2.5f, OrbitalHeight = 9f, FloaterHeight = -1.25f;
private readonly string _root = Path.Combine(Path.GetTempPath(), "aetheria-layers-" + Guid.NewGuid().ToString("N"));
private readonly CultCache _cache;
public LayerTests()
{
Directory.CreateDirectory(_root);
_cache = AetheriaStores.Open(Path.Combine(_root, "layers.cc"), catalogWritable: true);
}
public void Dispose()
{
_cache.Dispose();
Directory.Delete(_root, true);
}
private static GameplaySettings Settings() => new GameplaySettings
{
DefaultEntitySettings = new EntitySettings(),
Tiers = new[] { new RarityTier { Name = "Common", Quality = .5f, Rarity = 0, Color = new float3(1, 1, 1) } },
QualityPriceModifier = new ExponentialLerp(),
ShipLayerHeight = ShipHeight, OrbitalLayerHeight = OrbitalHeight, FloaterLayerHeight = FloaterHeight
};
private (ItemManager Items, Zone Zone) Build()
{
// Hull sizes and masses are spread so no kind coincides with a size or a mass: a layer chosen by either fails.
Shape Solid(int side)
{
var shape = new Shape(side, side);
foreach (var cell in shape.AllCoordinates) shape[cell] = true;
return shape;
}
_cache.Upsert(new VerseGrammar { Revision = 1 });
_cache.Upsert(new HullData { Name = "Ship", HullType = HullType.Ship, Shape = Solid(5), Durability = 1, Mass = 1000 });
_cache.Upsert(new HullData { Name = "Station", HullType = HullType.Station, Shape = Solid(3), Durability = 1, Mass = 1000000 });
_cache.Upsert(new HullData { Name = "Turret", HullType = HullType.Turret, Shape = Solid(7), Durability = 1, Mass = 50 });
_cache.FlushAsync().Wait();
var items = new ItemManager(_cache, new ProvenanceLedger(), Settings(), _ => { });
var zone = new Zone(items, new PlanetSettings(), new ZonePack { Radius = 0 }, new GalaxyZone { Name = "Layers", Owner = null }, null);
return (items, zone);
}
private static EquippableItem Hull(ItemManager items, string name, int lot) => new EquippableItem
{
Data = items.ItemData.RefOf<ItemData>(items.ItemData.GetByName<HullData>(name)), Durability = 1, Lot = lot
};
[Fact]
public void EachKindRidesItsLayer()
{
var (items, zone) = Build();
var ship = new Ship(items, zone, Hull(items, "Ship", 1), new EntitySettings());
var station = new OrbitalEntity(items, zone, Hull(items, "Station", 2), default, new EntitySettings());
var turret = new OrbitalEntity(items, zone, Hull(items, "Turret", 3), default, new EntitySettings());
foreach (var e in new Entity[] { ship, station, turret }) zone.Admit(e, piloted: false);
ship.Position = float3(40, 0, 10);
station.Position = float3(-30, 0, 20);
turret.Position = float3(5, 0, -60);
zone.Update(.1f);
Assert.Equal(zone.GetHeight(ship.Position.xz) + ShipHeight, ship.Position.y);
Assert.Equal(zone.GetHeight(station.Position.xz) + OrbitalHeight, station.Position.y);
Assert.Equal(zone.GetHeight(turret.Position.xz) + OrbitalHeight, turret.Position.y);
const float lead = .05f;
foreach (var (e, layer) in new (Entity, float)[] { (ship, ShipHeight), (station, OrbitalHeight), (turret, OrbitalHeight) })
{
var drawn = DrawAhead.Position(e, lead);
Assert.Equal(zone.GetHeight(drawn.xz, DrawAhead.Time(zone, lead)) + layer, drawn.y);
}
}
[Fact]
public void OrbitalsAboveShipsAboveFloaters()
{
var authored = AuthoredSettings.Load(RunStartTests.FindRepoRoot()).Read<GameplaySettings>("GameplaySettings");
Assert.True(authored.OrbitalLayerHeight > authored.ShipLayerHeight);
Assert.True(authored.ShipLayerHeight > authored.FloaterLayerHeight);
}
[Fact]
public void AFloaterSitsOnTheFloaterLayer()
{
var (items, zone) = Build();
var settings = items.GameplaySettings;
foreach (var at in new[] { float2(0, 0), float2(37, -12) })
foreach (var lead in new[] { 0f, .05f })
Assert.Equal(DrawAhead.Height(zone, at, lead) + FloaterHeight, DrawAhead.FloaterHeight(zone, settings, at, lead));
}
[Fact]
public void HullKey26IsRetired()
{
var keys = new System.Collections.Generic.HashSet<int>();
for (var t = typeof(HullData); t != null; t = t.BaseType)
foreach (var m in t.GetMembers(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly))
foreach (var k in m.GetCustomAttributes(typeof(MessagePack.KeyAttribute), false).Cast<MessagePack.KeyAttribute>())
if (k.IntKey.HasValue) keys.Add(k.IntKey.Value);
Assert.NotEmpty(keys);
Assert.DoesNotContain(26, keys);
using var shipped = new CultCache(TestCatalog.Registry());
shipped.AddBackingStore(new SingleFileMessagePackBackingStore(TestCatalog.Repo, true), AetheriaStores.CatalogTypes);
Assert.NotEmpty(shipped.GetAll<HullData>());
}
}