fix(items): dispatch carried use without world approach

This commit is contained in:
Erik 2026-07-23 09:50:28 +02:00
parent f71f947475
commit 19e8863f4e
7 changed files with 99 additions and 18 deletions

View file

@ -19,10 +19,14 @@ status hand follows canonical selection/object notices, ghosts empty and
explicitly unusable selections, and routes enabled weapons, direct-use items, explicitly unusable selections, and routes enabled weapons, direct-use items,
and use-on-target tools through the existing interaction transaction and and use-on-target tools through the existing interaction transaction and
cursor owners. Its connected state gate passed. A named-retail correction now cursor owners. Its connected state gate passed. A named-retail correction now
treats reset/absent useability zero as usable, allowing Blackmoor's Favor to treats reset/absent useability zero as usable. The connected gate then exposed
send ordinary Use, while shared AutoWear reports the exact overlapping worn the delivery defect beneath that classifier: carried Use had been routed
item that must be removed. That correction awaits its connected gate; the through the world-object approach query and silently cancelled because
magnifying-glass Assess command follows in the same slice. 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.
--- ---

View file

@ -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 active: the status hand's selected-object availability and existing
Use/AutoWield/use-on-target command routing passed their connected gate. Use/AutoWield/use-on-target command routing passed their connected gate.
The follow-up retail correction for zero-valued direct-use items and named 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. Assess half continues.
Carried: Carried:

View file

@ -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 / solution build, 3,474 App tests / 3 skips, and 7,848 complete-solution tests /
5 skips pass. 5 skips pass.
The connected selection-state gate passed. Its follow-up exposed two deeper The connected selection-state gate passed. Its follow-up exposed three deeper
shared-policy defects, now corrected from named retail plus the matching shared-policy/delivery defects, now corrected from named retail plus the
binary. `ItemUses::IsUseable @ 0x004FCCC0` tests only `USEABLE_NO`; reset/absent matching binary. `ItemUses::IsUseable @ 0x004FCCC0` tests only `USEABLE_NO`;
value zero is usable, so Blackmoor's Favor and other zero-valued direct-use reset/absent value zero is usable. `ItemHolder::UseObject @ 0x00588A80` sends a
items now emit the ordinary Use request. AutoWear now applies 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 `CPlayerSystem::AutoWearIsLegal @ 0x0055EF40` through the same double-click and
toolbar-hand path, resolves the overlapping worn object from the toolbar-hand path, resolves the overlapping worn object from the
retail-ordered equipment projection, and emits the exact system line 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). [`../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 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 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 ## Slice 1 — spell-bar overflow arrows

View file

@ -10,6 +10,7 @@ Date: 2026-07-23
- `PublicWeenieDesc::UnPack @ 0x005AD470` - `PublicWeenieDesc::UnPack @ 0x005AD470`
- `ItemHolder::DetermineUseResult @ 0x00588460` - `ItemHolder::DetermineUseResult @ 0x00588460`
- `ItemHolder::UseObject @ 0x00588A80` - `ItemHolder::UseObject @ 0x00588A80`
- `CPlayerSystem::UsingItem @ 0x00562F70`
- `CPlayerSystem::AutoWearIsLegal @ 0x0055EF40` - `CPlayerSystem::AutoWearIsLegal @ 0x0055EF40`
- `CPlayerSystem::AutoWear @ 0x005601C0` - `CPlayerSystem::AutoWear @ 0x005601C0`
- `ACCWeenieObject::GetObjectAtLocation @ 0x0058CE00` - `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 and an explicitly packed zero both have retail `USEABLE_UNDEF` value zero and
both pass `IsUseable`. 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 ```text
AutoWearIsLegal(itemId, out alreadyWorn, silent): AutoWearIsLegal(itemId, out alreadyWorn, silent):
alreadyWorn = false alreadyWorn = false
@ -101,8 +110,12 @@ AutoWear(itemId, out alreadyWorn, silent):
rule. Toolbar enablement, item activation, and world interaction consume it. rule. Toolbar enablement, item activation, and world interaction consume it.
- `ItemInteractionController` remains the shared double-click and toolbar-hand - `ItemInteractionController` remains the shared double-click and toolbar-hand
path. A zero-valued carried gem such as Blackmoor's Favor therefore emits the path. A zero-valued carried gem such as Blackmoor's Favor therefore emits the
ordinary Use request; ACE remains authoritative for its spell and ordinary Use request directly from its inventory identity; it is never sent
consumption outcome. 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 - `AutoWieldController` remains the shared inventory/paperdoll equip owner. It
resolves AutoWear blockers from `ClientObjectTable`'s retail-ordered resolves AutoWear blockers from `ClientObjectTable`'s retail-ordered
equipment projection and emits the retail literal through system chat. equipment projection and emits the retail literal through system chat.

View file

@ -58,7 +58,16 @@ BeginAcceptedUse(object):
RequestUse(object, reservation) RequestUse(object, reservation)
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() reservation.CancelBeforeDispatch()
return return

View file

@ -211,6 +211,18 @@ internal sealed class SelectionInteractionController
reservation?.CancelBeforeDispatch(); reservation?.CancelBeforeDispatch();
return; 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) if (!_query.IsUseable(serverGuid)
|| !_query.TryGetApproach(serverGuid, out InteractionApproach approach)) || !_query.TryGetApproach(serverGuid, out InteractionApproach approach))
{ {
@ -240,15 +252,21 @@ internal sealed class SelectionInteractionController
} }
_movement.BeginApproach(approach); _movement.BeginApproach(approach);
SendUseNow(serverGuid, reservation);
}
private void SendUseNow(
uint serverGuid,
ItemUseRequestReservation? reservation)
{
if (_transport.TrySendUse(serverGuid, out uint sequence)) if (_transport.TrySendUse(serverGuid, out uint sequence))
{ {
reservation?.MarkDispatched(); reservation?.MarkDispatched();
Console.WriteLine($"[B.4b] use guid=0x{serverGuid:X8} seq={sequence}"); 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) public void SendPickup(uint itemGuid, uint destinationContainerId, int placement)

View file

@ -361,6 +361,33 @@ public sealed class SelectionInteractionControllerTests
Assert.Equal(new[] { Target }, h.Transport.Uses); 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] [Fact]
public void HiddenOrStaleIncarnationCancelsDeferredAction() public void HiddenOrStaleIncarnationCancelsDeferredAction()
{ {