diff --git a/packages/org.gamecult.eve.unity-scene/Runtime/EveUnityPlayableWorldPresenter.cs b/packages/org.gamecult.eve.unity-scene/Runtime/EveUnityPlayableWorldPresenter.cs index b77695b..e6a8b29 100644 --- a/packages/org.gamecult.eve.unity-scene/Runtime/EveUnityPlayableWorldPresenter.cs +++ b/packages/org.gamecult.eve.unity-scene/Runtime/EveUnityPlayableWorldPresenter.cs @@ -23,14 +23,17 @@ namespace GameCult.Eve.UnityScene { private readonly IEveUnityPlayableWorldSceneSink _sceneSink; private readonly IEveUnityPlayableWorldAssetResolver _assetResolver; + private readonly bool _liveEntityBodyOwner; private readonly HashSet _knownEntityIds = new HashSet(StringComparer.Ordinal); public EveUnityPlayableWorldPresenter( IEveUnityPlayableWorldSceneSink sceneSink, - IEveUnityPlayableWorldAssetResolver assetResolver) + IEveUnityPlayableWorldAssetResolver assetResolver, + bool liveEntityBodyOwner = false) { _sceneSink = sceneSink ?? throw new ArgumentNullException(nameof(sceneSink)); _assetResolver = assetResolver ?? throw new ArgumentNullException(nameof(assetResolver)); + _liveEntityBodyOwner = liveEntityBodyOwner; } public EveUnityPlayableWorldPresentation Apply(EveUnitySceneProjection projection) @@ -46,9 +49,26 @@ namespace GameCult.Eve.UnityScene { if (world == null) throw new ArgumentNullException(nameof(world)); - var nextEntityIds = new HashSet(StringComparer.Ordinal); _sceneSink.ConfigureWorld(world); + // A world that advertises a live entity body has one transform and + // membership owner: the SoA generation presenter. The durable Eve + // surface remains configuration/topology and must not periodically + // overwrite realtime body state. + if (_liveEntityBodyOwner && + (!string.IsNullOrWhiteSpace(world.EntityViewPointerId) || + !string.IsNullOrWhiteSpace(world.EntityBodyId))) + return new EveUnityPlayableWorldPresentation( + world.WorldRootId, + world.PlayerEntityId, + world.InputProfile, + world.CameraRig, + 0, + 0, + _knownEntityIds.Count); + + var nextEntityIds = new HashSet(StringComparer.Ordinal); + var upserted = 0; foreach (var entity in world.Entities) { diff --git a/packages/org.gamecult.eve.unity-scene/Runtime/EveUnityPlayableWorldRuntime.cs b/packages/org.gamecult.eve.unity-scene/Runtime/EveUnityPlayableWorldRuntime.cs index 94ca473..d0b2c43 100644 --- a/packages/org.gamecult.eve.unity-scene/Runtime/EveUnityPlayableWorldRuntime.cs +++ b/packages/org.gamecult.eve.unity-scene/Runtime/EveUnityPlayableWorldRuntime.cs @@ -41,7 +41,10 @@ namespace GameCult.Eve.UnityScene _connection = new EveUnitySceneProviderConnection(_surfaceSource, commandSink); _client = new EveUnityPlayableWorldLiveClient( _connection, - new EveUnityPlayableWorldPresenter(sceneSink, assetResolver ?? new EveUnityAssetRefResolver()), + new EveUnityPlayableWorldPresenter( + sceneSink, + assetResolver ?? new EveUnityAssetRefResolver(), + liveEntityBodyOwner: _entityViews != null), receiptSource); if (assetManifestDocuments != null) diff --git a/packages/org.gamecult.eve.unity-scene/Tests/Editor/EveUnitySceneSurfaceLowererTests.cs b/packages/org.gamecult.eve.unity-scene/Tests/Editor/EveUnitySceneSurfaceLowererTests.cs index a65771c..356809a 100644 --- a/packages/org.gamecult.eve.unity-scene/Tests/Editor/EveUnitySceneSurfaceLowererTests.cs +++ b/packages/org.gamecult.eve.unity-scene/Tests/Editor/EveUnitySceneSurfaceLowererTests.cs @@ -1856,7 +1856,7 @@ namespace GameCult.Eve.UnityScene.Tests var presenter = new EveUnityPlayableWorldPresenter(sink, new EveUnityAssetRefResolver()); var firstProjection = lowerer.Lower(PlayableArpgDocument(), Advertisement("aetheria.daemon.game")); - var firstPresentation = presenter.Apply(firstProjection); + var firstPresentation = presenter.Apply(SnapshotWorld(firstProjection.PlayableWorld!)); Assert.That(firstPresentation.WorldRootId, Is.EqualTo("aetheria.daemon.game.playable")); Assert.That(firstPresentation.PlayerEntityId, Is.EqualTo("player-vanguard")); @@ -1875,7 +1875,7 @@ namespace GameCult.Eve.UnityScene.Tests var secondProjection = lowerer.Lower( PlayableArpgDocument(includeRaider: false, playerPosition: "5,0,2"), Advertisement("aetheria.daemon.game")); - var secondPresentation = presenter.Apply(secondProjection); + var secondPresentation = presenter.Apply(SnapshotWorld(secondProjection.PlayableWorld!)); Assert.That(secondPresentation.UpsertedEntities, Is.EqualTo(2)); Assert.That(secondPresentation.RemovedEntities, Is.EqualTo(1)); @@ -1888,6 +1888,27 @@ namespace GameCult.Eve.UnityScene.Tests Assert.That(sink.Upserts[3].entity.PositionZ, Is.EqualTo(2f)); } + [Test] + public void PlayableWorldSurfaceCannotOverwriteLiveBodyEntities() + { + var projection = new EveUnitySceneSurfaceLowerer() + .Lower(PlayableArpgDocument(), Advertisement("aetheria.daemon.game")); + var sink = new FakePlayableWorldSceneSink(); + var presenter = new EveUnityPlayableWorldPresenter( + sink, + new EveUnityAssetRefResolver(), + liveEntityBodyOwner: true); + + var presentation = presenter.Apply(projection); + + Assert.That(projection.PlayableWorld!.EntityBodyId, Is.Not.Empty); + Assert.That(sink.ConfiguredWorlds.Count, Is.EqualTo(1)); + Assert.That(sink.Upserts, Is.Empty); + Assert.That(sink.RemovedEntityIds, Is.Empty); + Assert.That(presentation.UpsertedEntities, Is.Zero); + Assert.That(presentation.RemovedEntities, Is.Zero); + } + [Test] public void LivePlayableWorldClientPresentsProviderSnapshotsAndKeepsCommandsProviderOwned() { @@ -2950,6 +2971,21 @@ namespace GameCult.Eve.UnityScene.Tests } } + private static EveUnityPlayableWorldProjection SnapshotWorld(EveUnityPlayableWorldProjection source) => + new EveUnityPlayableWorldProjection( + source.WorldRootId, + source.StatePointerId, + source.AssetManifest, + source.InputProfile, + source.CameraRig, + source.ViewId, + source.PlayerEntityId, + source.MovementCommand, + source.FocusCommand, + source.TargetCommand, + source.ActionCommand, + source.Entities); + private static EveUnityPlayableWorldEntity PlayableEntityModel( string entityId, string assetRef, diff --git a/packages/org.gamecult.eve.unity-scene/package.json b/packages/org.gamecult.eve.unity-scene/package.json index c84f62f..5a4e87d 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.103", + "version": "0.3.104", "unity": "6000.0", "description": "Provider-agnostic Unity scene shell for gamecult.eve.surface.v1 interactive world surfaces.", "dependencies": {