Query compass markers from daemon contacts

This commit is contained in:
Meta Vens 2026-06-21 15:57:11 +01:00
commit 9af4f4f2da
4 changed files with 239 additions and 9 deletions

View file

@ -1827,7 +1827,11 @@ static void RequireDaemonRenderQueryAuthority(string root)
"public static AetheriaRuntimeDaemonAsteroidInstancePose[] QueryAsteroidInstancePoses(",
"public static int QueryAsteroidInstancePoses(",
"TryResolveAsteroidBeltCenter(body, orbitPositions, orbits, out var center)",
"ResolveAsteroidBeltRadius(body)"
"ResolveAsteroidBeltRadius(body)",
"AetheriaRuntimeDaemonCompassMarker",
"public static AetheriaRuntimeDaemonCompassMarker[] QueryCompassMarkers(",
"public static int QueryCompassMarkers(",
"BuildEntityMap(zone)"
};
var missingQuerySymbols = requiredQuerySymbols
@ -1934,6 +1938,12 @@ static void RequireDaemonRenderQueryAuthority(string root)
"ZoneRenderer must reconcile rendered entities from daemon frame snapshots instead of subscribing to mirrored Unity entity collection mutations.");
}
if (zoneRenderer.Contains("PerspectiveEntity.EntityInfoGathered", StringComparison.Ordinal))
{
throw new InvalidOperationException(
"ZoneRenderer must query daemon compass markers instead of reading mirrored Unity entity visibility state.");
}
var requiredZoneRendererSymbols = new[]
{
"private AetheriaRuntimeZoneSnapshotCommit _daemonZoneSnapshot;",
@ -1941,6 +1951,8 @@ static void RequireDaemonRenderQueryAuthority(string root)
"private readonly Dictionary<string, AetheriaRuntimeDaemonBodyPose> _daemonBodyPosesByBodyKey",
"private readonly List<AetheriaRuntimeDaemonAsteroidBeltPose> _daemonAsteroidBeltPoses",
"private readonly Dictionary<string, AetheriaRuntimeDaemonAsteroidBeltPose> _daemonAsteroidBeltPosesByBodyKey",
"private readonly List<AetheriaRuntimeDaemonCompassMarker> _daemonCompassMarkers",
"private readonly Dictionary<int, AetheriaRuntimeDaemonCompassMarker> _daemonCompassMarkersByEntityIndex",
"public void LoadZone(Zone zone, AetheriaRuntimeZoneSnapshotCommit daemonZone = null)",
"_daemonZoneSnapshot = daemonZone;",
"AetheriaRuntimeDaemonRenderQueries.EvaluateGravityTerrainHeight(",
@ -1955,6 +1967,8 @@ static void RequireDaemonRenderQueryAuthority(string root)
"AetheriaRuntimeDaemonRenderQueries.QueryBodyPoses(_daemonZoneSnapshot, _daemonBodyPoses);",
"AetheriaRuntimeDaemonRenderQueries.QueryAsteroidBeltPoses(_daemonZoneSnapshot, _daemonAsteroidBeltPoses);",
"AetheriaRuntimeDaemonRenderQueries.QueryAsteroidInstancePoses(",
"AetheriaRuntimeDaemonRenderQueries.QueryCompassMarkers(",
"_daemonCompassMarkersByEntityIndex.TryGetValue(entityInstance.Entity.DaemonEntityIndex, out var marker)",
"_daemonBodyPosesByBodyKey.TryGetValue(planet.Key, out var pose)",
"_daemonAsteroidBeltPosesByBodyKey.TryGetValue(key, out var beltPose)",
"pose.GravityWaveSpeed"

View file

@ -933,6 +933,100 @@ public class DaemonRuntimeDocumentTests
Assert.AreEqual(0, poses[2].Size, 0.0001);
}
[Test]
public void DaemonRenderQueriesPublishCompassMarkersFromVisibleContacts()
{
var zone = new AetheriaRuntimeZoneSnapshotCommit
{
Entities = new[]
{
new AetheriaRuntimeEntitySnapshotCommit
{
EntityIndex = 0,
PositionX = 10,
PositionZ = 20,
Contacts = new[]
{
new AetheriaRuntimeEntityContactCommit
{
TargetEntityIndex = 1,
InfoGathered = 0.8,
Visible = true,
Hostile = true
},
new AetheriaRuntimeEntityContactCommit
{
TargetEntityIndex = 2,
InfoGathered = 0.2,
Visible = true
},
new AetheriaRuntimeEntityContactCommit
{
TargetEntityIndex = 3,
InfoGathered = 0.9,
Visible = false
},
new AetheriaRuntimeEntityContactCommit
{
TargetEntityIndex = 4,
InfoGathered = 0.9,
Visible = true
},
new AetheriaRuntimeEntityContactCommit
{
TargetEntityIndex = 99,
InfoGathered = 0.9,
Visible = true
}
}
},
new AetheriaRuntimeEntitySnapshotCommit
{
EntityIndex = 1,
PositionX = 13,
PositionZ = 24
},
new AetheriaRuntimeEntitySnapshotCommit
{
EntityIndex = 2,
PositionX = 100,
PositionZ = 20
},
new AetheriaRuntimeEntitySnapshotCommit
{
EntityIndex = 3,
PositionX = 10,
PositionZ = 200
},
new AetheriaRuntimeEntitySnapshotCommit
{
EntityIndex = 4,
PositionX = 11,
PositionZ = 20
}
}
};
var markers = new List<AetheriaRuntimeDaemonCompassMarker>();
var count = AetheriaRuntimeDaemonRenderQueries.QueryCompassMarkers(
zone,
0,
0.5,
2,
markers);
Assert.AreEqual(1, count);
Assert.AreEqual(1, markers.Count);
Assert.AreEqual(1, markers[0].TargetEntityIndex);
Assert.AreEqual(13, markers[0].PositionX, 0.0001);
Assert.AreEqual(24, markers[0].PositionZ, 0.0001);
Assert.AreEqual(3, markers[0].DeltaX, 0.0001);
Assert.AreEqual(4, markers[0].DeltaZ, 0.0001);
Assert.AreEqual(5, markers[0].Distance, 0.0001);
Assert.AreEqual(0.8, markers[0].InfoGathered, 0.0001);
Assert.IsTrue(markers[0].Hostile);
}
[Test]
public void SoaViewIndexRejectsInvalidRenderGroups()
{

View file

@ -94,6 +94,10 @@ public class ZoneRenderer : MonoBehaviour
new Dictionary<string, AetheriaRuntimeDaemonAsteroidBeltPose>(StringComparer.Ordinal);
private readonly List<AetheriaRuntimeDaemonAsteroidInstancePose> _visibleAsteroidInstancePoses =
new List<AetheriaRuntimeDaemonAsteroidInstancePose>();
private readonly List<AetheriaRuntimeDaemonCompassMarker> _daemonCompassMarkers =
new List<AetheriaRuntimeDaemonCompassMarker>();
private readonly Dictionary<int, AetheriaRuntimeDaemonCompassMarker> _daemonCompassMarkersByEntityIndex =
new Dictionary<int, AetheriaRuntimeDaemonCompassMarker>();
public Dictionary<Wormhole, (GameObject gravity, CompassIcon icon)> WormholeInstances = new Dictionary<Wormhole, (GameObject, CompassIcon)>();
private List<ItemPickup> _loot = new List<ItemPickup>();
@ -471,6 +475,7 @@ public class ZoneRenderer : MonoBehaviour
Shader.SetGlobalFloat("_AsteroidVerticalOffset", ActionGameManager.Instance.Settings.PlanetSettings.AsteroidVerticalOffset);
RefreshDaemonBodyPoses();
RefreshDaemonAsteroidBeltPoses();
RefreshDaemonCompassMarkers();
Plane[] planes = GeometryUtility.CalculateFrustumPlanes(MainCamera);
bool isVisible(Bounds bounds) => GeometryUtility.TestPlanesAABB(planes, bounds);
@ -556,14 +561,16 @@ public class ZoneRenderer : MonoBehaviour
{
if(entityInstance.CompassIcon)
{
var difference = entityInstance.Entity.CultPositionXZ - PerspectiveEntity.CultPositionXZ;
var distance = CultMath.math.length(difference);
entityInstance.CompassIcon.gameObject.SetActive(
PerspectiveEntity.EntityInfoGathered.ContainsKey(entityInstance.Entity) &&
PerspectiveEntity.EntityInfoGathered[entityInstance.Entity] > Settings.GameplaySettings.TargetDetectionInfoThreshold &&
distance > _minimapDistance);
entityInstance.CompassIcon.transform.rotation = Quaternion.Euler(0, 0, Mathf.Atan2(difference.y, difference.x) * Mathf.Rad2Deg - 90);
var active = entityInstance.Entity != null &&
_daemonCompassMarkersByEntityIndex.TryGetValue(entityInstance.Entity.DaemonEntityIndex, out var marker);
entityInstance.CompassIcon.gameObject.SetActive(active);
if (active)
{
entityInstance.CompassIcon.transform.rotation = Quaternion.Euler(
0,
0,
Mathf.Atan2((float)marker.DeltaZ, (float)marker.DeltaX) * Mathf.Rad2Deg - 90);
}
}
}
@ -616,6 +623,19 @@ public class ZoneRenderer : MonoBehaviour
}
}
private void RefreshDaemonCompassMarkers()
{
AetheriaRuntimeDaemonRenderQueries.QueryCompassMarkers(
_daemonZoneSnapshot,
PerspectiveEntity?.DaemonEntityIndex ?? -1,
Settings.GameplaySettings.TargetDetectionInfoThreshold,
_minimapDistance,
_daemonCompassMarkers);
_daemonCompassMarkersByEntityIndex.Clear();
foreach (var marker in _daemonCompassMarkers)
_daemonCompassMarkersByEntityIndex[marker.TargetEntityIndex] = marker;
}
private float GetTerrainHeight(float2 position)
{
return (float)AetheriaRuntimeDaemonRenderQueries.EvaluateGravityTerrainHeight(

View file

@ -156,6 +156,38 @@ namespace GameCult.Aetheria.State.Verse
public double Size { get; }
}
public readonly struct AetheriaRuntimeDaemonCompassMarker
{
public AetheriaRuntimeDaemonCompassMarker(
int targetEntityIndex,
double positionX,
double positionZ,
double deltaX,
double deltaZ,
double distance,
double infoGathered,
bool hostile)
{
TargetEntityIndex = targetEntityIndex;
PositionX = positionX;
PositionZ = positionZ;
DeltaX = deltaX;
DeltaZ = deltaZ;
Distance = Math.Max(0, distance);
InfoGathered = infoGathered;
Hostile = hostile;
}
public int TargetEntityIndex { get; }
public double PositionX { get; }
public double PositionZ { get; }
public double DeltaX { get; }
public double DeltaZ { get; }
public double Distance { get; }
public double InfoGathered { get; }
public bool Hostile { get; }
}
public readonly struct AetheriaRuntimeGravityTerrainBand
{
public AetheriaRuntimeGravityTerrainBand(double startDepth, double depthRange)
@ -358,6 +390,64 @@ namespace GameCult.Aetheria.State.Verse
return poses.Count;
}
public static AetheriaRuntimeDaemonCompassMarker[] QueryCompassMarkers(
AetheriaRuntimeZoneSnapshotCommit? zone,
int observerEntityIndex,
double minimumInfoGathered,
double minimumDistance)
{
var markers = new List<AetheriaRuntimeDaemonCompassMarker>();
QueryCompassMarkers(zone, observerEntityIndex, minimumInfoGathered, minimumDistance, markers);
return markers.Count == 0 ? Array.Empty<AetheriaRuntimeDaemonCompassMarker>() : markers.ToArray();
}
public static int QueryCompassMarkers(
AetheriaRuntimeZoneSnapshotCommit? zone,
int observerEntityIndex,
double minimumInfoGathered,
double minimumDistance,
List<AetheriaRuntimeDaemonCompassMarker> markers)
{
if (markers == null) throw new ArgumentNullException(nameof(markers));
markers.Clear();
if (zone == null || observerEntityIndex < 0)
return 0;
var entities = BuildEntityMap(zone);
if (!entities.TryGetValue(observerEntityIndex, out var observer))
return 0;
var requiredDistance = Math.Max(0, minimumDistance);
foreach (var contact in observer.Contacts ?? Array.Empty<AetheriaRuntimeEntityContactCommit>())
{
if (contact == null ||
!contact.Visible ||
contact.InfoGathered <= minimumInfoGathered ||
!entities.TryGetValue(contact.TargetEntityIndex, out var target))
{
continue;
}
var deltaX = target.PositionX - observer.PositionX;
var deltaZ = target.PositionZ - observer.PositionZ;
var distance = Math.Sqrt(deltaX * deltaX + deltaZ * deltaZ);
if (distance <= requiredDistance)
continue;
markers.Add(new AetheriaRuntimeDaemonCompassMarker(
target.EntityIndex,
target.PositionX,
target.PositionZ,
deltaX,
deltaZ,
distance,
contact.InfoGathered,
contact.Hostile));
}
return markers.Count;
}
public static AetheriaRuntimeDaemonRenderGroupDocument[] QueryRenderGroups(
AetheriaRuntimeDaemonSoaViewIndex? index,
double minX,
@ -464,6 +554,18 @@ namespace GameCult.Aetheria.State.Verse
zone.GravityTerrainDepth - startDepth + maxDepth);
}
private static Dictionary<int, AetheriaRuntimeEntitySnapshotCommit> BuildEntityMap(AetheriaRuntimeZoneSnapshotCommit zone)
{
var entities = new Dictionary<int, AetheriaRuntimeEntitySnapshotCommit>();
foreach (var entity in zone.Entities ?? Array.Empty<AetheriaRuntimeEntitySnapshotCommit>())
{
if (entity != null && entity.EntityIndex >= 0)
entities[entity.EntityIndex] = entity;
}
return entities;
}
private static Dictionary<string, AetheriaRuntimeXzPoint> BuildOrbitPositions(AetheriaRuntimeZoneSnapshotCommit zone)
{
var source = BuildOrbitMap(zone);