diff --git a/packages/org.gamecult.eve.unity-scene/Runtime/CultMesh/EveUnityCultMeshLiveProviderTransport.cs b/packages/org.gamecult.eve.unity-scene/Runtime/CultMesh/EveUnityCultMeshLiveProviderTransport.cs index ae746ec..d6960c5 100644 --- a/packages/org.gamecult.eve.unity-scene/Runtime/CultMesh/EveUnityCultMeshLiveProviderTransport.cs +++ b/packages/org.gamecult.eve.unity-scene/Runtime/CultMesh/EveUnityCultMeshLiveProviderTransport.cs @@ -1601,7 +1601,8 @@ namespace GameCult.Eve.UnityScene receipt.Navigation.ProviderId, receipt.Navigation.SurfaceId, receipt.Navigation.SurfaceKind, - receipt.Navigation.RendezvousEndpoints))); + receipt.Navigation.RendezvousEndpoints, + receipt.Navigation.AuthorityRuntimeId))); } private sealed class ReceiptSubscription : IDisposable diff --git a/packages/org.gamecult.eve.unity-scene/Runtime/CultMesh/EveUnityCultMeshPlayableWorldProvider.cs b/packages/org.gamecult.eve.unity-scene/Runtime/CultMesh/EveUnityCultMeshPlayableWorldProvider.cs index cee9cc2..fdc20d6 100644 --- a/packages/org.gamecult.eve.unity-scene/Runtime/CultMesh/EveUnityCultMeshPlayableWorldProvider.cs +++ b/packages/org.gamecult.eve.unity-scene/Runtime/CultMesh/EveUnityCultMeshPlayableWorldProvider.cs @@ -170,6 +170,7 @@ namespace GameCult.Eve.UnityScene target.SurfaceId, requiredSurfaceKind, target.VerseId, + target.AuthorityRuntimeId, _navigationAuthorityTrust, CancellationToken.None).ConfigureAwait(false); @@ -352,6 +353,7 @@ namespace GameCult.Eve.UnityScene surfaceFilter, surfaceKind, verseFilter, + "", _authorityTrust, cancellationToken).ConfigureAwait(false); TraceStartup($"discovery and transport preparation {elapsed.Elapsed.TotalMilliseconds:0.###}ms"); @@ -364,6 +366,7 @@ namespace GameCult.Eve.UnityScene string requiredSurfaceId, string requiredSurfaceKind, string requiredVerseId, + string requiredAuthorityRuntimeId, CultMeshAuthorityTrustPolicy trust, CancellationToken cancellationToken) { @@ -379,7 +382,8 @@ namespace GameCult.Eve.UnityScene requiredSurfaceId, requiredSurfaceKind, requiredVerseId, - cancellationToken).ConfigureAwait(false); + cancellationToken, + requiredAuthorityRuntimeId).ConfigureAwait(false); var resolvedCachePath = string.IsNullOrWhiteSpace(cacheDirectory) ? Path.Combine(Application.temporaryCachePath, $"eve-unity-{GetInstanceID()}") : cacheDirectory; diff --git a/packages/org.gamecult.eve.unity-scene/Runtime/CultMesh/EveUnityCultMeshProviderDiscovery.cs b/packages/org.gamecult.eve.unity-scene/Runtime/CultMesh/EveUnityCultMeshProviderDiscovery.cs index fd43418..7772dbc 100644 --- a/packages/org.gamecult.eve.unity-scene/Runtime/CultMesh/EveUnityCultMeshProviderDiscovery.cs +++ b/packages/org.gamecult.eve.unity-scene/Runtime/CultMesh/EveUnityCultMeshProviderDiscovery.cs @@ -53,7 +53,8 @@ namespace GameCult.Eve.UnityScene string surfaceId = "", string surfaceKind = "interactive-world", string verseId = "", - CancellationToken cancellationToken = default) + CancellationToken cancellationToken = default, + string requiredAuthorityRuntimeId = "") { if (string.IsNullOrWhiteSpace(rendezvousEndpoint)) throw new ArgumentException("Rendezvous endpoint must be non-empty.", nameof(rendezvousEndpoint)); @@ -101,10 +102,9 @@ namespace GameCult.Eve.UnityScene }); foreach (var candidate in candidates) { - foreach (var authorityRuntimeId in (candidate.AuthorityRuntimeIds ?? Array.Empty()) - .Where(id => !string.IsNullOrWhiteSpace(id)) - .Distinct(StringComparer.Ordinal) - .OrderBy(id => id, StringComparer.Ordinal)) + foreach (var authorityRuntimeId in EligibleAuthorityRuntimeIds( + candidate.AuthorityRuntimeIds, + requiredAuthorityRuntimeId)) { try { @@ -146,11 +146,22 @@ namespace GameCult.Eve.UnityScene } } - var filter = $"provider='{providerId}', surface='{surfaceId}', kind='{surfaceKind}'"; + var filter = $"provider='{providerId}', surface='{surfaceId}', kind='{surfaceKind}', authority='{requiredAuthorityRuntimeId}'"; var detail = failures.Count == 0 ? "" : $" Endpoint failures: {string.Join(" | ", failures)}"; var observedDetail = observed.Count == 0 ? "" : $" Observed: {string.Join(" | ", observed)}"; throw new InvalidOperationException($"No advertised Eve surface matched {filter}.{detail}{observedDetail}"); } + internal static IReadOnlyList EligibleAuthorityRuntimeIds( + IEnumerable? authorityRuntimeIds, + string requiredAuthorityRuntimeId) => + (authorityRuntimeIds ?? Array.Empty()) + .Where(id => !string.IsNullOrWhiteSpace(id)) + .Where(id => string.IsNullOrWhiteSpace(requiredAuthorityRuntimeId) || + string.Equals(id, requiredAuthorityRuntimeId, StringComparison.Ordinal)) + .Distinct(StringComparer.Ordinal) + .OrderBy(id => id, StringComparer.Ordinal) + .ToArray(); + } } diff --git a/packages/org.gamecult.eve.unity-scene/Runtime/EveUnityPlayableWorldLiveClient.cs b/packages/org.gamecult.eve.unity-scene/Runtime/EveUnityPlayableWorldLiveClient.cs index 4767284..f8d7182 100644 --- a/packages/org.gamecult.eve.unity-scene/Runtime/EveUnityPlayableWorldLiveClient.cs +++ b/packages/org.gamecult.eve.unity-scene/Runtime/EveUnityPlayableWorldLiveClient.cs @@ -253,9 +253,11 @@ namespace GameCult.Eve.UnityScene string providerId, string surfaceId, string surfaceKind, - IReadOnlyList? rendezvousEndpoints = null) + IReadOnlyList? rendezvousEndpoints = null, + string authorityRuntimeId = "") { VerseId = verseId ?? ""; + AuthorityRuntimeId = authorityRuntimeId ?? ""; ProviderId = providerId ?? ""; SurfaceId = surfaceId ?? ""; SurfaceKind = surfaceKind ?? ""; @@ -263,6 +265,7 @@ namespace GameCult.Eve.UnityScene } public string VerseId { get; } + public string AuthorityRuntimeId { get; } public string ProviderId { get; } public string SurfaceId { get; } public string SurfaceKind { get; } diff --git a/packages/org.gamecult.eve.unity-scene/Tests/Editor/EveUnityCultMeshAssetLookupTests.cs b/packages/org.gamecult.eve.unity-scene/Tests/Editor/EveUnityCultMeshAssetLookupTests.cs index 8e70be8..36d906a 100644 --- a/packages/org.gamecult.eve.unity-scene/Tests/Editor/EveUnityCultMeshAssetLookupTests.cs +++ b/packages/org.gamecult.eve.unity-scene/Tests/Editor/EveUnityCultMeshAssetLookupTests.cs @@ -168,6 +168,24 @@ namespace GameCult.Eve.UnityScene.Tests Assert.That(typeof(EveUnityCultMeshProviderSelection).GetProperty("Endpoint"), Is.Null); } + [Test] + public void NavigationAuthorityCannotBeReplacedByAlphabeticallyEarlierPeer() + { + var target = new EveUnitySceneNavigationTarget( + "gamecult.aetheria", + "aetheria.daemon", + "aetheria.pilot", + "interactive-world", + new[] { "cultnet+tcp://odin.example:3075" }, + "z-owner"); + var eligible = EveUnityCultMeshProviderDiscovery.EligibleAuthorityRuntimeIds( + new[] { "a-decoy", "z-owner" }, + target.AuthorityRuntimeId); + + Assert.That(target.AuthorityRuntimeId, Is.EqualTo("z-owner")); + CollectionAssert.AreEqual(new[] { "z-owner" }, eligible); + } + private static EveSurfaceCommandRequest Request(string id, string command) => new EveSurfaceCommandRequest( "test.provider", "test.surface", diff --git a/packages/org.gamecult.eve.unity-scene/package.json b/packages/org.gamecult.eve.unity-scene/package.json index d3a5fa1..ecf856e 100644 --- a/packages/org.gamecult.eve.unity-scene/package.json +++ b/packages/org.gamecult.eve.unity-scene/package.json @@ -1,7 +1,7 @@ { "name": "org.gamecult.eve.unity-scene", "displayName": "Eve Unity Scene Lowering", - "version": "0.3.119", + "version": "0.3.120", "unity": "6000.0", "description": "Provider-agnostic Unity scene shell for gamecult.eve.surface.v1 interactive world surfaces.", "dependencies": {