fix(interaction): close selection lifecycle review gaps

Bind queued actions and pending inventory requests to exact live incarnations, separate optimistic placement from authoritative responses, and serialize retail-style inventory ownership across UI surfaces.

Co-authored-by: OpenAI Codex <codex@openai.com>
This commit is contained in:
Erik 2026-07-21 09:01:02 +02:00
parent d2bb5af453
commit 5acc3f01cf
23 changed files with 2635 additions and 168 deletions

View file

@ -263,4 +263,81 @@ public sealed class ExternalContainerControllerTests
Assert.Equal(Player, h.Objects.Get(Item)!.ContainerId);
Assert.Empty(h.Puts);
}
[Fact]
public void PendingPickupRejectsOwnedDropIntoChest()
{
using var h = new Harness();
h.Open();
h.Objects.AddOrUpdate(new ClientObject { ObjectId = Item, Type = ItemType.Misc });
h.Objects.MoveItem(Item, Player, 0);
var source = new UiItemSlot { SourceKind = ItemDragSource.Inventory };
source.SetItem(Item, 0u);
Assert.True(h.Interaction.PlaceWorldItemInBackpack(0x70000A01u));
h.Controller.HandleDropRelease(
h.Contents,
h.Contents.GetItem(0)!,
new ItemDragPayload(Item, ItemDragSource.Inventory, 0, source));
Assert.Empty(h.Puts);
Assert.Equal(Player, h.Objects.Get(Item)!.ContainerId);
Assert.True(h.Interaction.TryGetPendingBackpackPlacement(0x70000A01u, out _));
}
[Fact]
public void PendingPickupRejectsPartialStackSplitIntoChest()
{
using var h = new Harness();
h.Open();
h.Objects.AddOrUpdate(new ClientObject
{
ObjectId = Item,
StackSize = 5,
StackSizeMax = 100,
Type = ItemType.Misc,
});
h.Objects.MoveItem(Item, Player, 0);
h.Selection.Select(Item, SelectionChangeSource.Inventory);
h.Split.Reset(5u, 2u);
var source = new UiItemSlot { SourceKind = ItemDragSource.Inventory };
source.SetItem(Item, 0u);
Assert.True(h.Interaction.PlaceWorldItemInBackpack(0x70000A02u));
h.Controller.HandleDropRelease(
h.Contents,
h.Contents.GetItem(0)!,
new ItemDragPayload(Item, ItemDragSource.Inventory, 0, source));
Assert.Empty(h.Splits);
Assert.Equal(Player, h.Objects.Get(Item)!.ContainerId);
Assert.True(h.Interaction.TryGetPendingBackpackPlacement(0x70000A02u, out _));
}
[Fact]
public void ExternalPutOwnsGlobalGateUntilMatchingServerMove()
{
using var h = new Harness();
const uint pickup = 0x70000A03u;
h.Open();
h.Objects.AddOrUpdate(new ClientObject { ObjectId = Item, Type = ItemType.Misc });
h.Objects.MoveItem(Item, Player, 0);
var source = new UiItemSlot { SourceKind = ItemDragSource.Inventory };
source.SetItem(Item, 0u);
h.Controller.HandleDropRelease(
h.Contents,
h.Contents.GetItem(0)!,
new ItemDragPayload(Item, ItemDragSource.Inventory, 0, source));
Assert.True(h.Interaction.TryGetPendingInventoryRequest(out var pending));
Assert.Equal(Item, pending.ItemId);
Assert.True(h.Interaction.PlaceWorldItemInBackpack(pickup));
Assert.Empty(h.Pickups);
h.Objects.ApplyConfirmedServerMove(Item, Chest, 0u, 0);
Assert.False(h.Interaction.TryGetPendingInventoryRequest(out _));
Assert.True(h.Interaction.PlaceWorldItemInBackpack(pickup));
Assert.Equal(new uint[] { pickup }, h.Pickups);
}
}