fix(interaction): restore retail loot placement and world-drop projection

This commit is contained in:
Erik 2026-07-27 00:03:15 +02:00
parent 4d095be286
commit 921712f412
24 changed files with 1581 additions and 34 deletions

View file

@ -17,6 +17,7 @@ public sealed class SelectionInteractionControllerTests
{
private const uint Player = 0x5000_0001u;
private const uint Target = 0x7000_0001u;
private const uint GroundContainer = 0x7000_0010u;
private sealed class Query : IWorldSelectionQuery
{
@ -140,6 +141,7 @@ public sealed class SelectionInteractionControllerTests
public readonly List<PendingBackpackPlacement> CancelledPlacements = new();
public readonly ItemInteractionController Items;
public readonly SelectionInteractionController Controller;
public uint GroundObjectId { get; set; }
public Harness()
{
@ -173,6 +175,7 @@ public sealed class SelectionInteractionControllerTests
Query.Events.Add("examine");
Examines.Add(guid);
},
groundObjectId: () => GroundObjectId,
placeInBackpack: (item, container, placement) =>
controller!.SendPickup(item, container, placement),
requestUse: (guid, reservation) =>
@ -495,6 +498,32 @@ public sealed class SelectionInteractionControllerTests
Assert.Equal(new[] { (Target, Player, 0) }, h.Transport.Pickups);
}
[Fact]
public void CorpseChildDoubleClickDispatchesWithoutAWorldProjection()
{
const uint loot = 0x7000_0011u;
var h = new Harness
{
GroundObjectId = GroundContainer,
};
h.Objects.AddOrUpdate(new ClientObject
{
ObjectId = loot,
Name = "Corpse loot",
Type = ItemType.Misc,
ContainerId = GroundContainer,
});
h.Query.Pickupable = false;
h.Query.Approach = null;
Assert.True(h.Items.ActivateItem(loot));
Assert.Empty(h.Movement.Approaches);
Assert.Equal(new[] { (loot, Player, 0) }, h.Transport.Pickups);
Assert.Single(h.PendingPlacements);
Assert.Empty(h.CancelledPlacements);
}
[Fact]
public void SessionResetClearsQueuedAndDeferredWork()
{

View file

@ -319,6 +319,75 @@ public sealed class WorldSelectionQueryTests
Assert.Equal(4f, radius);
}
[Fact]
public void VividTargetMarkerWaitsForFreshProjectionWhenDropMovePrecedesPosition()
{
var h = new Harness();
WorldEntity entity =
h.Add(Target, new Vector3(3f, 4f, 0f), ItemType.Misc);
Assert.NotNull(h.Query.ResolveVividTargetInfo(Target));
Assert.True(h.Objects.MoveItem(Target, Player, newSlot: 0));
Assert.True(h.Runtime.WithdrawLiveEntityProjection(Target));
Assert.Null(h.Query.ResolveVividTargetInfo(Target));
// ACE may confirm InventoryPutObjectIn3D before sending the fresh
// Position. The retained entity still has the old ground pose here.
Assert.True(h.Objects.MoveItem(Target, 0u, newSlot: -1));
Assert.Null(h.Query.ResolveVividTargetInfo(Target));
var newPosition = new Vector3(30f, 40f, 2f);
entity.SetPosition(newPosition);
Assert.True(h.Runtime.RebucketLiveEntity(Target, 0x0101_0001u));
var marker = h.Query.ResolveVividTargetInfo(Target);
Assert.NotNull(marker);
Assert.Equal(newPosition + Vector3.UnitX, marker.Value.SelectionSphereCenter);
}
[Fact]
public void VividTargetMarkerUsesFreshPoseWhenPositionPrecedesDropMove()
{
var h = new Harness();
WorldEntity entity =
h.Add(Target, new Vector3(3f, 4f, 0f), ItemType.Misc);
Assert.True(h.Objects.MoveItem(Target, Player, newSlot: 0));
Assert.True(h.Runtime.WithdrawLiveEntityProjection(Target));
var newPosition = new Vector3(30f, 40f, 2f);
entity.SetPosition(newPosition);
Assert.True(h.Runtime.RebucketLiveEntity(Target, 0x0101_0001u));
// The spatial pose is current, but ownership continues to suppress the
// marker until ServerSaysMoveItem publishes the world placement.
Assert.Null(h.Query.ResolveVividTargetInfo(Target));
Assert.True(h.Objects.MoveItem(Target, 0u, newSlot: -1));
var marker = h.Query.ResolveVividTargetInfo(Target);
Assert.NotNull(marker);
Assert.Equal(newPosition + Vector3.UnitX, marker.Value.SelectionSphereCenter);
}
[Fact]
public void VividTargetMarkerRejectsRetainedProjectionInsideExternalContainer()
{
var h = new Harness();
const uint corpse = 0x7000_0040u;
h.Add(Target, new Vector3(3f, 4f, 0f), ItemType.Misc);
h.Objects.AddOrUpdate(new ClientObject
{
ObjectId = corpse,
Name = "Corpse",
Type = ItemType.Container,
});
Assert.True(h.Objects.MoveItem(Target, corpse, newSlot: 0));
Assert.False(h.Objects.IsOwnedByObject(Target, Player));
Assert.Null(h.Query.ResolveVividTargetInfo(Target));
}
private static SelectionCameraSnapshot Camera()
{
Matrix4x4 view = Matrix4x4.CreateLookAt(