From 19e8863f4ee16c95281a0dc941f0e49b29def3fd Mon Sep 17 00:00:00 2001 From: Erik Date: Thu, 23 Jul 2026 09:50:28 +0200 Subject: [PATCH] fix(items): dispatch carried use without world approach --- docs/plans/2026-04-11-roadmap.md | 12 ++++++--- docs/plans/2026-05-12-milestones.md | 6 ++++- ...2026-07-23-world-interaction-completion.md | 18 ++++++++----- ...retail-item-use-and-autowear-pseudocode.md | 17 ++++++++++-- ...23-retail-use-busy-ownership-pseudocode.md | 11 +++++++- .../SelectionInteractionController.cs | 26 +++++++++++++++--- .../SelectionInteractionControllerTests.cs | 27 +++++++++++++++++++ 7 files changed, 99 insertions(+), 18 deletions(-) diff --git a/docs/plans/2026-04-11-roadmap.md b/docs/plans/2026-04-11-roadmap.md index bb9e4a5e..5c3e6169 100644 --- a/docs/plans/2026-04-11-roadmap.md +++ b/docs/plans/2026-04-11-roadmap.md @@ -19,10 +19,14 @@ status hand follows canonical selection/object notices, ghosts empty and explicitly unusable selections, and routes enabled weapons, direct-use items, and use-on-target tools through the existing interaction transaction and cursor owners. Its connected state gate passed. A named-retail correction now -treats reset/absent useability zero as usable, allowing Blackmoor's Favor to -send ordinary Use, while shared AutoWear reports the exact overlapping worn -item that must be removed. That correction awaits its connected gate; the -magnifying-glass Assess command follows in the same slice. +treats reset/absent useability zero as usable. The connected gate then exposed +the delivery defect beneath that classifier: carried Use had been routed +through the world-object approach query and silently cancelled because +inventory objects have no spatial entity. Owned direct-use now sends +immediately, while the registered AD-27 approach seam remains world-only; +shared AutoWear reports the exact overlapping worn item that must be removed. +That correction awaits its connected gate; the magnifying-glass Assess command +follows in the same slice. --- diff --git a/docs/plans/2026-05-12-milestones.md b/docs/plans/2026-05-12-milestones.md index db7126b6..34fa9844 100644 --- a/docs/plans/2026-05-12-milestones.md +++ b/docs/plans/2026-05-12-milestones.md @@ -93,7 +93,11 @@ Slice 1's 18-cell favorite-spell overflow bar is user-accepted. Slice 2 is active: the status hand's selected-object availability and existing Use/AutoWield/use-on-target command routing passed their connected gate. The follow-up retail correction for zero-valued direct-use items and named -AutoWear blockers is code-complete and awaiting its connected gate before the +AutoWear blockers is code-complete. Its first connected gate exposed and +corrected the lower delivery boundary: carried direct-use had been sent through +the world approach query even though inventory objects have no spatial entity. +Owned Use now sends immediately; world Use alone retains the registered AD-27 +approach seam. The combined correction awaits its connected gate before the Assess half continues. Carried: diff --git a/docs/plans/2026-07-23-world-interaction-completion.md b/docs/plans/2026-07-23-world-interaction-completion.md index 9216b058..3bd013a1 100644 --- a/docs/plans/2026-07-23-world-interaction-completion.md +++ b/docs/plans/2026-07-23-world-interaction-completion.md @@ -70,11 +70,14 @@ AP-122. The 14-test Core interaction focus, 49-test toolbar focus, Release solution build, 3,474 App tests / 3 skips, and 7,848 complete-solution tests / 5 skips pass. -The connected selection-state gate passed. Its follow-up exposed two deeper -shared-policy defects, now corrected from named retail plus the matching -binary. `ItemUses::IsUseable @ 0x004FCCC0` tests only `USEABLE_NO`; reset/absent -value zero is usable, so Blackmoor's Favor and other zero-valued direct-use -items now emit the ordinary Use request. AutoWear now applies +The connected selection-state gate passed. Its follow-up exposed three deeper +shared-policy/delivery defects, now corrected from named retail plus the +matching binary. `ItemUses::IsUseable @ 0x004FCCC0` tests only `USEABLE_NO`; +reset/absent value zero is usable. `ItemHolder::UseObject @ 0x00588A80` sends a +carried direct-use item immediately; acdream had incorrectly routed that +packet through a world approach lookup, where Blackmoor's Favor has no spatial +entity and was silently cancelled. Owned Use now bypasses that query while +world-object Use retains the registered AD-27 approach seam. AutoWear applies `CPlayerSystem::AutoWearIsLegal @ 0x0055EF40` through the same double-click and toolbar-hand path, resolves the overlapping worn object from the retail-ordered equipment projection, and emits the exact system line @@ -82,7 +85,10 @@ retail-ordered equipment projection, and emits the exact system line [`../research/2026-07-23-retail-item-use-and-autowear-pseudocode.md`](../research/2026-07-23-retail-item-use-and-autowear-pseudocode.md). The focused 25-test Core and 85-test App sets pass, as do the warning-free Release solution build, 3,476 App tests / 3 skips, and 7,857 complete-solution -tests / 5 skips. +tests / 5 skips. The carried-use delivery correction adds an end-to-end App +pin from Favor activation through wire dispatch and authoritative UseDone busy +release. The Release build retains the 17 tracked test-project warnings; +3,477 App tests / 3 skips and 7,858 complete-solution tests / 5 skips pass. ## Slice 1 — spell-bar overflow arrows diff --git a/docs/research/2026-07-23-retail-item-use-and-autowear-pseudocode.md b/docs/research/2026-07-23-retail-item-use-and-autowear-pseudocode.md index e74c1f74..6435025c 100644 --- a/docs/research/2026-07-23-retail-item-use-and-autowear-pseudocode.md +++ b/docs/research/2026-07-23-retail-item-use-and-autowear-pseudocode.md @@ -10,6 +10,7 @@ Date: 2026-07-23 - `PublicWeenieDesc::UnPack @ 0x005AD470` - `ItemHolder::DetermineUseResult @ 0x00588460` - `ItemHolder::UseObject @ 0x00588A80` +- `CPlayerSystem::UsingItem @ 0x00562F70` - `CPlayerSystem::AutoWearIsLegal @ 0x0055EF40` - `CPlayerSystem::AutoWear @ 0x005601C0` - `ACCWeenieObject::GetObjectAtLocation @ 0x0058CE00` @@ -52,6 +53,14 @@ when the optional useability word is present. Consequently an absent wire word and an explicitly packed zero both have retail `USEABLE_UNDEF` value zero and both pass `IsUseable`. +For a direct-use item, `ItemHolder::UseObject` then sends +`CM_Inventory::Event_UseEvent`, increments the UI busy count, and calls +`CPlayerSystem::UsingItem`. That latter presentation function handles local +pickup/wield/container state; it does not require a carried source to have a +world position. Therefore an owned inventory object must cross the wire +directly. Only world objects participate in acdream's registered AD-27 local +approach compatibility seam. + ```text AutoWearIsLegal(itemId, out alreadyWorn, silent): alreadyWorn = false @@ -101,8 +110,12 @@ AutoWear(itemId, out alreadyWorn, silent): rule. Toolbar enablement, item activation, and world interaction consume it. - `ItemInteractionController` remains the shared double-click and toolbar-hand path. A zero-valued carried gem such as Blackmoor's Favor therefore emits the - ordinary Use request; ACE remains authoritative for its spell and - consumption outcome. + ordinary Use request directly from its inventory identity; it is never sent + through the world-approach query. ACE remains authoritative for its spell + and consumption outcome. +- `SelectionInteractionController` keeps the AD-27 turn/approach seam only for + non-owned spatial objects. Owned self, equipped, and nested-container sources + have no live world pose and send immediately. - `AutoWieldController` remains the shared inventory/paperdoll equip owner. It resolves AutoWear blockers from `ClientObjectTable`'s retail-ordered equipment projection and emits the retail literal through system chat. diff --git a/docs/research/2026-07-23-retail-use-busy-ownership-pseudocode.md b/docs/research/2026-07-23-retail-use-busy-ownership-pseudocode.md index dce2a361..4cc583be 100644 --- a/docs/research/2026-07-23-retail-use-busy-ownership-pseudocode.md +++ b/docs/research/2026-07-23-retail-use-busy-ownership-pseudocode.md @@ -58,7 +58,16 @@ BeginAcceptedUse(object): RequestUse(object, reservation) RequestUse(object, reservation): - if session/identity/approach is invalid: + if session is invalid: + reservation.CancelBeforeDispatch() + return + + if object is carried or equipped by the player: + send Use immediately + transfer reservation to UseDone + return + + if world identity/approach is invalid: reservation.CancelBeforeDispatch() return diff --git a/src/AcDream.App/Interaction/SelectionInteractionController.cs b/src/AcDream.App/Interaction/SelectionInteractionController.cs index 860f276f..b635f654 100644 --- a/src/AcDream.App/Interaction/SelectionInteractionController.cs +++ b/src/AcDream.App/Interaction/SelectionInteractionController.cs @@ -211,6 +211,18 @@ internal sealed class SelectionInteractionController reservation?.CancelBeforeDispatch(); return; } + + // ItemHolder::UseObject @ 0x00588A80 sends Event_UseEvent for an + // accepted carried-item activation before CPlayerSystem::UsingItem. + // A carried source has no world pose to approach: routing it through + // TryGetApproach drops the packet because inventory objects are not + // live spatial entities. + if (_items.IsOwnedByPlayer(serverGuid)) + { + SendUseNow(serverGuid, reservation); + return; + } + if (!_query.IsUseable(serverGuid) || !_query.TryGetApproach(serverGuid, out InteractionApproach approach)) { @@ -240,15 +252,21 @@ internal sealed class SelectionInteractionController } _movement.BeginApproach(approach); + SendUseNow(serverGuid, reservation); + } + + private void SendUseNow( + uint serverGuid, + ItemUseRequestReservation? reservation) + { if (_transport.TrySendUse(serverGuid, out uint sequence)) { reservation?.MarkDispatched(); Console.WriteLine($"[B.4b] use guid=0x{serverGuid:X8} seq={sequence}"); + return; } - else - { - reservation?.CancelBeforeDispatch(); - } + + reservation?.CancelBeforeDispatch(); } public void SendPickup(uint itemGuid, uint destinationContainerId, int placement) diff --git a/tests/AcDream.App.Tests/Interaction/SelectionInteractionControllerTests.cs b/tests/AcDream.App.Tests/Interaction/SelectionInteractionControllerTests.cs index 344c7bed..db49235e 100644 --- a/tests/AcDream.App.Tests/Interaction/SelectionInteractionControllerTests.cs +++ b/tests/AcDream.App.Tests/Interaction/SelectionInteractionControllerTests.cs @@ -361,6 +361,33 @@ public sealed class SelectionInteractionControllerTests Assert.Equal(new[] { Target }, h.Transport.Uses); } + [Fact] + public void CarriedDirectUseBypassesWorldApproachAndWaitsForUseDone() + { + const uint favor = 0x8000_0606u; + var h = new Harness(); + h.Query.Useable = false; + h.Objects.AddOrUpdate(new ClientObject + { + ObjectId = favor, + Name = "Blackmoor's Favor", + Type = ItemType.Gem, + Useability = ItemUseability.Undef, + }); + Assert.True(h.Objects.MoveItem(favor, Player, newSlot: 1)); + + Assert.True(h.Items.ActivateItem(favor)); + + Assert.Empty(h.Movement.Approaches); + Assert.Equal(new[] { favor }, h.Transport.Uses); + Assert.Equal(1, h.Items.BusyCount); + + h.Items.CompleteUse(0u); + + Assert.Equal(0, h.Items.BusyCount); + Assert.True(h.Items.CanMakeInventoryRequest); + } + [Fact] public void HiddenOrStaleIncarnationCancelsDeferredAction() {