fix(runtime): restore interaction completion ownership

Preserve prepublication local motion completion, require the PartArray enter-world lifecycle port, and balance deferred Use busy ownership across dispatch and cancellation. Reconcile the completed GameWindow connected gates and add regression coverage.

Co-authored-by: Codex <codex@openai.com>
This commit is contained in:
Erik 2026-07-23 05:51:51 +02:00
parent 5c955c36ec
commit f9736ece6c
26 changed files with 675 additions and 68 deletions

View file

@ -37,7 +37,8 @@ public sealed class ItemInteractionControllerTests
public uint GroundObject;
public long Now = 1_000;
public Harness()
public Harness(
Action<uint, ItemUseRequestReservation>? requestUse = null)
{
Objects.AddOrUpdate(new ClientObject
{
@ -57,7 +58,7 @@ public sealed class ItemInteractionControllerTests
Controller = new ItemInteractionController(
Objects,
playerGuid: () => Player,
sendUse: Uses.Add,
sendUse: requestUse is null ? Uses.Add : null,
sendExamine: Examines.Add,
sendUseWithTarget: (source, target) => UseWithTarget.Add((source, target)),
sendWield: (item, mask) => Wields.Add((item, mask)),
@ -84,7 +85,8 @@ public sealed class ItemInteractionControllerTests
ExternalRequests.Add(id);
},
combatState: Combat,
sendChangeCombatMode: CombatModeRequests.Add);
sendChangeCombatMode: CombatModeRequests.Add,
requestUse: requestUse);
}
public ItemInteractionController Controller { get; }
@ -1446,6 +1448,66 @@ public sealed class ItemInteractionControllerTests
Assert.Equal(2, h.Uses.Count);
}
[Fact]
public void DeferredUseCancellationReleasesExactlyItsBusyReference()
{
ItemUseRequestReservation? pending = null;
var h = new Harness((_, reservation) => pending = reservation);
h.AddContained(
0x50000A0Bu,
item => item.Useability = ItemUseability.Contained);
Assert.True(h.Controller.ActivateItem(0x50000A0Bu));
Assert.Equal(1, h.Controller.BusyCount);
Assert.NotNull(pending);
pending.CancelBeforeDispatch();
pending.CancelBeforeDispatch();
Assert.Equal(0, h.Controller.BusyCount);
Assert.True(h.Controller.CanMakeInventoryRequest);
}
[Fact]
public void DispatchedUseReservationIsReleasedOnlyByUseDone()
{
ItemUseRequestReservation? pending = null;
var h = new Harness((_, reservation) => pending = reservation);
h.AddContained(
0x50000A0Cu,
item => item.Useability = ItemUseability.Contained);
Assert.True(h.Controller.ActivateItem(0x50000A0Cu));
Assert.NotNull(pending);
pending.MarkDispatched();
pending.CancelBeforeDispatch();
Assert.Equal(1, h.Controller.BusyCount);
h.Controller.CompleteUse(0u);
Assert.Equal(0, h.Controller.BusyCount);
}
[Fact]
public void SessionResetInvalidatesLateUseReservationResolution()
{
ItemUseRequestReservation? stale = null;
var h = new Harness((_, reservation) => stale = reservation);
h.AddContained(
0x50000A0Du,
item => item.Useability = ItemUseability.Contained);
Assert.True(h.Controller.ActivateItem(0x50000A0Du));
Assert.Equal(1, h.Controller.BusyCount);
h.Controller.ResetSession();
stale!.CancelBeforeDispatch();
Assert.Equal(0, h.Controller.BusyCount);
Assert.True(h.Controller.CanMakeInventoryRequest);
}
[Fact]
public void KeyboardPickup_PublishesPendingDestinationBeforeRequest()
{