diff --git a/src/AcDream.App/Rendering/Scene/CurrentRenderSceneOracle.cs b/src/AcDream.App/Rendering/Scene/CurrentRenderSceneOracle.cs index 5eb873b2..1499b446 100644 --- a/src/AcDream.App/Rendering/Scene/CurrentRenderSceneOracle.cs +++ b/src/AcDream.App/Rendering/Scene/CurrentRenderSceneOracle.cs @@ -522,13 +522,22 @@ internal sealed class CurrentRenderSceneOracle : tuple.LandblockId); if (!_dispatcherProjectionByEntity.TryGetValue( tuple.Entity, - out CurrentRenderProjectionFingerprint projection) - || projection.LandblockId != cacheLandblockId) + out CurrentRenderProjectionFingerprint projection)) { - projection = CreateFingerprint( - cacheLandblockId, - tuple.Entity, - ProjectionClassOf(tuple.Entity)); + // Logical projection identity remains the canonical owner + // captured by the partition. The dispatcher's cache hint is + // a separate routing fact and can intentionally be the + // temporary player-bucket landblock for outdoor objects. + // Conflating the two rewrote identity at this observer edge. + if (!_projectionByEntity.TryGetValue( + tuple.Entity, + out projection)) + { + projection = CreateFingerprint( + cacheLandblockId, + tuple.Entity, + ProjectionClassOf(tuple.Entity)); + } _dispatcherProjectionByEntity[tuple.Entity] = projection; } var candidate = new CurrentRenderDispatcherFingerprint( diff --git a/tests/AcDream.App.Tests/Rendering/CurrentRenderSceneOracleTests.cs b/tests/AcDream.App.Tests/Rendering/CurrentRenderSceneOracleTests.cs index b9ec14c5..47b9f946 100644 --- a/tests/AcDream.App.Tests/Rendering/CurrentRenderSceneOracleTests.cs +++ b/tests/AcDream.App.Tests/Rendering/CurrentRenderSceneOracleTests.cs @@ -344,6 +344,34 @@ public sealed class CurrentRenderSceneOracleTests Assert.NotEqual(first.DispatcherDigest, reset.DispatcherDigest); } + [Fact] + public void DispatcherTemporaryBucket_DoesNotRewriteProjectionOwner() + { + WorldEntity outdoor = Entity( + id: 61, + serverGuid: 0, + parentCell: null); + var oracle = new CurrentRenderSceneOracle(); + var result = new InteriorEntityPartition.Result(); + InteriorEntityPartition.Partition( + result, + [], + [Entry(LandblockA, outdoor)], + oracle); + + oracle.BeginDispatcherFrame(); + oracle.ObserveDispatcherDraw( + WbDrawDispatcher.EntitySet.All, + entitiesWalked: 1, + [(outdoor, 0, LandblockB)]); + + CurrentRenderDispatcherFingerprint candidate = + Assert.Single(oracle.DispatcherCandidates); + Assert.Equal(LandblockA, candidate.Projection.LandblockId); + Assert.Equal(LandblockB, candidate.TupleLandblockId); + Assert.Equal(LandblockB, candidate.CacheLandblockId); + } + [Fact] public void DispatcherAbortDiscardsIncompleteCandidates() {