fix(world): restore portal and distant use lifecycles

This commit is contained in:
Erik 2026-07-25 09:59:25 +02:00
parent c730632075
commit 1a14812c44
18 changed files with 401 additions and 196 deletions

View file

@ -295,15 +295,15 @@ public sealed class SelectionInteractionControllerTests
}
[Fact]
public void CloseUseTurnsFirstThenSendsExactlyOnceOnNaturalCompletion()
public void CloseUseSendsImmediatelyWithoutSpeculativeMovement()
{
var h = new Harness();
h.SetApproach(closeRange: true);
h.Controller.SendUse(Target);
Assert.Single(h.Movement.Approaches);
Assert.Empty(h.Transport.Uses);
Assert.Empty(h.Movement.Approaches);
Assert.Equal(new[] { Target }, h.Transport.Uses);
h.Controller.OnNaturalMoveToComplete();
h.Controller.OnNaturalMoveToComplete();
@ -312,7 +312,7 @@ public sealed class SelectionInteractionControllerTests
}
[Fact]
public void CancelledDeferredUseReleasesBusyWithoutServerCompletion()
public void AcceptedUseRemainsBusyUntilAuthoritativeUseDone()
{
var h = new Harness();
h.SetApproach(closeRange: true);
@ -322,18 +322,22 @@ public sealed class SelectionInteractionControllerTests
h.Controller.DrainOutbound();
Assert.Equal(1, h.Items.BusyCount);
Assert.Empty(h.Transport.Uses);
Assert.Equal(new[] { Target }, h.Transport.Uses);
h.CompletionLifetime.PublishCancellation(WeenieError.ActionCancelled);
h.Controller.DrainOutbound();
Assert.Equal(1, h.Items.BusyCount);
Assert.False(h.Items.CanMakeInventoryRequest);
h.Items.CompleteUse(0u);
Assert.Equal(0, h.Items.BusyCount);
Assert.True(h.Items.CanMakeInventoryRequest);
Assert.Empty(h.Transport.Uses);
}
[Fact]
public void DispatchedDeferredUseRemainsBusyUntilAuthoritativeUseDone()
public void DispatchedWorldUseRemainsBusyUntilAuthoritativeUseDone()
{
var h = new Harness();
h.SetApproach(closeRange: true);
@ -341,8 +345,6 @@ public sealed class SelectionInteractionControllerTests
h.Controller.HandleInputAction(InputAction.UseSelected);
h.Controller.DrainOutbound();
h.CompletionLifetime.PublishNaturalCompletion();
h.Controller.DrainOutbound();
Assert.Equal(new[] { Target }, h.Transport.Uses);
Assert.Equal(1, h.Items.BusyCount);
@ -354,7 +356,7 @@ public sealed class SelectionInteractionControllerTests
}
[Fact]
public void FailedDeferredUseStartReleasesItsBusyReservation()
public void WorldUseDoesNotDependOnLocalApproachInstallation()
{
var h = new Harness();
h.SetApproach(closeRange: true);
@ -364,32 +366,27 @@ public sealed class SelectionInteractionControllerTests
h.Controller.HandleInputAction(InputAction.UseSelected);
h.Controller.DrainOutbound();
Assert.Equal(0, h.Items.BusyCount);
Assert.True(h.Items.CanMakeInventoryRequest);
Assert.Empty(h.Transport.Uses);
Assert.Equal(1, h.Items.BusyCount);
Assert.Equal(new[] { Target }, h.Transport.Uses);
Assert.Empty(h.Movement.Approaches);
}
[Fact]
public void StaleDeferredUseReleasesItsBusyReservation()
public void RejectedWorldUseReleasesItsBusyReservation()
{
var h = new Harness();
h.SetApproach(closeRange: true);
h.Query.Useable = false;
h.Selection.Select(Target, SelectionChangeSource.World);
h.Controller.HandleInputAction(InputAction.UseSelected);
h.Controller.DrainOutbound();
Assert.Equal(1, h.Items.BusyCount);
h.Query.Current = false;
h.CompletionLifetime.PublishNaturalCompletion();
h.Controller.DrainOutbound();
Assert.Equal(0, h.Items.BusyCount);
Assert.Empty(h.Transport.Uses);
}
[Fact]
public void SynchronousTurnCompletionTransfersBusyOwnershipToUseDone()
public void SynchronousMovementCallbackCannotDuplicateWorldUse()
{
var h = new Harness();
h.SetApproach(closeRange: true);
@ -408,7 +405,7 @@ public sealed class SelectionInteractionControllerTests
}
[Fact]
public void FarUseSendsImmediatelyAndNaturalCompletionDoesNotRetry()
public void FarUseSendsImmediatelyWithoutClientApproachAndDoesNotRetry()
{
var h = new Harness();
h.SetApproach(closeRange: false);
@ -416,7 +413,7 @@ public sealed class SelectionInteractionControllerTests
h.Controller.SendUse(Target);
h.Controller.OnNaturalMoveToComplete();
Assert.Single(h.Movement.Approaches);
Assert.Empty(h.Movement.Approaches);
Assert.Equal(new[] { Target }, h.Transport.Uses);
}
@ -448,21 +445,21 @@ public sealed class SelectionInteractionControllerTests
}
[Fact]
public void HiddenOrStaleIncarnationCancelsDeferredAction()
public void HiddenOrStaleNotificationsCannotRetractOrDuplicateDispatchedUse()
{
var hidden = new Harness();
hidden.SetApproach(closeRange: true);
hidden.Controller.SendUse(Target);
hidden.Controller.OnEntityHidden(Target);
hidden.Controller.OnNaturalMoveToComplete();
Assert.Empty(hidden.Transport.Uses);
Assert.Equal(new[] { Target }, hidden.Transport.Uses);
var stale = new Harness();
stale.SetApproach(closeRange: true);
stale.Controller.SendUse(Target);
stale.Query.Current = false;
stale.Controller.OnNaturalMoveToComplete();
Assert.Empty(stale.Transport.Uses);
Assert.Equal(new[] { Target }, stale.Transport.Uses);
}
[Fact]
@ -487,7 +484,7 @@ public sealed class SelectionInteractionControllerTests
h.SetApproach(closeRange: true);
h.Selection.Select(Target, SelectionChangeSource.World);
h.Controller.HandleInputAction(InputAction.SelectionPickUp);
h.Controller.SendUse(Target);
h.Controller.HandleInputAction(InputAction.UseSelected);
h.Items.InteractionState.EnterExamine();
h.Controller.ResetSession();
@ -501,12 +498,12 @@ public sealed class SelectionInteractionControllerTests
}
[Fact]
public void OldRemovalClearsCapturedActionButDoesNotClearReplacementSelection()
public void OldRemovalClearsCapturedPickupButDoesNotClearReplacementSelection()
{
var h = new Harness();
h.SetApproach(closeRange: true, localEntityId: 101u);
h.Selection.Select(Target, SelectionChangeSource.World);
h.Controller.SendUse(Target);
Assert.True(h.Items.PlaceWorldItemInBackpack(Target));
var oldRecord = new LiveEntityRecord(Spawn(Target, instance: 1));
oldRecord.WorldEntity = new WorldEntity
{
@ -522,11 +519,12 @@ public sealed class SelectionInteractionControllerTests
h.Controller.OnNaturalMoveToComplete();
Assert.Equal(Target, h.Selection.SelectedObjectId);
Assert.Empty(h.Transport.Uses);
Assert.Empty(h.Transport.Pickups);
Assert.Equal(new[] { Target }, h.CancelledPlacements.Select(p => p.ItemId));
}
[Fact]
public void SynchronousNaturalCompletionObservesTheArmedCloseAction()
public void SynchronousNaturalCompletionCannotDuplicateUse()
{
var h = new Harness();
h.SetApproach(closeRange: true);
@ -539,16 +537,16 @@ public sealed class SelectionInteractionControllerTests
}
[Fact]
public void SynchronousResetCannotResurrectTheCloseAction()
public void SynchronousResetCannotResurrectACancelledPickup()
{
var h = new Harness();
h.SetApproach(closeRange: true);
h.Movement.AfterArm = h.Controller.ResetSession;
h.Controller.SendUse(Target);
Assert.True(h.Items.PlaceWorldItemInBackpack(Target));
h.Controller.OnNaturalMoveToComplete();
Assert.Empty(h.Transport.Uses);
Assert.Empty(h.Transport.Pickups);
}
[Fact]
@ -569,7 +567,7 @@ public sealed class SelectionInteractionControllerTests
}
[Fact]
public void ErrorCompletionClearsCloseActionBeforeALaterSuccess()
public void ErrorCompletionCannotRetractOrDuplicateDispatchedUse()
{
var h = new Harness();
h.SetApproach(closeRange: true);
@ -578,48 +576,45 @@ public sealed class SelectionInteractionControllerTests
h.Controller.OnMoveToCancelled(WeenieError.NoObject);
h.Controller.OnNaturalMoveToComplete();
Assert.Empty(h.Transport.Uses);
Assert.Equal(new[] { Target }, h.Transport.Uses);
}
[Theory]
[InlineData(false)]
[InlineData(true)]
public void DeferredCompletionFromPriorApproachCannotAffectReplacement(
bool priorCompletedNaturally)
[Fact]
public void CompletionFromPriorPickupCannotAffectReplacement()
{
var h = new Harness();
h.SetApproach(closeRange: true);
h.Controller.SendUse(Target);
if (priorCompletedNaturally)
h.CompletionLifetime.PublishNaturalCompletion();
else
h.CompletionLifetime.PublishCancellation(WeenieError.ActionCancelled);
Assert.True(h.Items.PlaceWorldItemInBackpack(Target));
h.CompletionLifetime.PublishCancellation(WeenieError.ActionCancelled);
h.Controller.SendUse(Target);
h.Selection.Select(Target, SelectionChangeSource.World);
h.Controller.HandleInputAction(InputAction.UseSelected);
h.Controller.DrainOutbound();
Assert.Empty(h.Transport.Uses);
Assert.Equal(new[] { Target }, h.Transport.Uses);
Assert.Empty(h.Transport.Pickups);
h.CompletionLifetime.PublishNaturalCompletion();
h.Controller.DrainOutbound();
Assert.Equal(new[] { Target }, h.Transport.Uses);
Assert.Empty(h.Transport.Pickups);
}
[Fact]
public void RetiringControllerLifetimeInvalidatesQueuedArrival()
public void RetiringControllerLifetimeInvalidatesQueuedPickupArrival()
{
var h = new Harness();
h.SetApproach(closeRange: true);
h.Controller.SendUse(Target);
Assert.True(h.Items.PlaceWorldItemInBackpack(Target));
h.CompletionLifetime.PublishNaturalCompletion();
h.Completions.RetireControllerLifetime(h.CompletionLifetime);
h.Controller.DrainOutbound();
Assert.Empty(h.Transport.Uses);
Assert.Empty(h.Transport.Pickups);
h.Controller.OnNaturalMoveToComplete();
Assert.Empty(h.Transport.Uses);
Assert.Empty(h.Transport.Pickups);
}
[Fact]