diff --git a/docs/ISSUES.md b/docs/ISSUES.md
index 8223e4c9..1c0d88fd 100644
--- a/docs/ISSUES.md
+++ b/docs/ISSUES.md
@@ -147,6 +147,10 @@ assessment. `SelectRight` is now a release-completed configurable click with
the shared three-pixel drag threshold, then pulses, selects, and appraises the
picked world object through the same owners as the magnifying glass and E key.
RMB camera drags and empty-world releases do not issue appraisal requests.
+The separate retained ItemList path now does the same for occupied backpack,
+side-bag, loot-container, paperdoll-slot, and physical shortcut cells; it
+selects the item before entering the shared appraisal owner, and RMB movement
+neither appraises nor lifts the item.
**Files:** `src/AcDream.Core.Net/Messages/AppraiseInfoParser.cs`;
`src/AcDream.App/UI/RetailUiRuntime.cs`;
@@ -170,6 +174,8 @@ the top and assessed melee/missile/armor/magic items show their retail-ordered
stats and full DAT spell descriptions.
Right-clicking a visible world object opens the same examination window;
right-dragging the camera does not.
+Right-clicking an occupied retained item cell selects and examines it without
+using, equipping, looting, or dragging it.
The Black Phyntos Hive specifically reads `Value: ???`, `Burden: Unknown`,
then its description after one retail paragraph break, without the bogus
255-item/255-container line.
diff --git a/docs/plans/2026-04-11-roadmap.md b/docs/plans/2026-04-11-roadmap.md
index d1bc8206..1f3ffe11 100644
--- a/docs/plans/2026-04-11-roadmap.md
+++ b/docs/plans/2026-04-11-roadmap.md
@@ -33,7 +33,9 @@ presentation; its connected gate passed. The existing magnifying-glass Assess
command was already retail-faithful. World right-click assessment now follows
the configurable `SelectRight` action's retail release-completed gesture,
including drag cancellation, then reuses the canonical
-picker/selection/appraisal path. Slice 3's first connected gate exposed
+picker/selection/appraisal path. The parallel retained ItemList branch now
+selects and appraises occupied inventory, external-container, paperdoll, and
+physical-shortcut cells through the same request owner. Slice 3's first connected gate exposed
three deeper defects, now corrected: the response enum mislabeled ACE's
creature bit `0x0100` and dropped monster packets while retaining the busy
cursor; the imported 310 x 400 examination layout was incorrectly hosted as
diff --git a/docs/plans/2026-05-12-milestones.md b/docs/plans/2026-05-12-milestones.md
index 855dcb2f..7b18b7a5 100644
--- a/docs/plans/2026-05-12-milestones.md
+++ b/docs/plans/2026-05-12-milestones.md
@@ -101,6 +101,9 @@ passed. The pre-existing Assess command has been reconciled against retail.
The same slice now includes retail world right-click assessment through the
configurable `SelectRight` complete-click gesture; RMB orbit drags cancel
instead of appraising the release point.
+Retail's separate retained ItemList right-click branch now covers physical
+backpack, loot, paperdoll, and toolbar item cells through the same selection
+and appraisal owner.
Slice 3's first connected gate found and corrected the real remaining defects:
ACE creature flag `0x0100` was mislabeled as a weapon profile and left the
appraisal busy reference held, retail's examination UI had been mounted as a
diff --git a/docs/plans/2026-07-23-world-interaction-completion.md b/docs/plans/2026-07-23-world-interaction-completion.md
index 657a4bfd..5074e262 100644
--- a/docs/plans/2026-07-23-world-interaction-completion.md
+++ b/docs/plans/2026-07-23-world-interaction-completion.md
@@ -236,6 +236,11 @@ routes through the existing world picker, lighting pulse, canonical
no-op, right-drag camera orbit does not appraise its release point, and the
independent configurable `SelectionExamine` action now reaches the same
request/target-mode path.
+The retained follow-up ports the separate
+`UIElement_ItemList::ListenToElementMessage @ 0x004E4D50` branch: an occupied
+backpack, side-bag, loot, paperdoll-slot, or physical toolbar cell now selects
+its item and enters that same appraisal owner on a completed right-click.
+Right-button movement cancels the click and can never begin an item drag.
## Slice 1 — spell-bar overflow arrows
diff --git a/docs/research/2026-07-17-retail-world-selection-pseudocode.md b/docs/research/2026-07-17-retail-world-selection-pseudocode.md
index f1377b6b..873166ba 100644
--- a/docs/research/2026-07-17-retail-world-selection-pseudocode.md
+++ b/docs/research/2026-07-17-retail-world-selection-pseudocode.md
@@ -221,6 +221,40 @@ camera drag does not appraise the object under the release point. Acdream maps
the configurable `SelectRight` action to the same complete-click gesture and
keeps `SelectionExamine` independently configurable (retail default E).
+### Retained physical-item lists
+
+`UIElement_ItemList::ListenToElementMessage @ 0x004E4D50`
+
+```text
+on ItemList right-click:
+ item = UIItem under the mouse
+ if item.itemID != 0:
+ if this list uses single selection:
+ update the list selection
+ SetSelectedObject(item.itemID)
+ ClientUISystem.ExamineObject(item.itemID)
+ else if item.spellID != 0:
+ ClientUISystem.ExamineSpell(item.spellID)
+```
+
+`gmPaperDollUI::ListenToElementMessage @ 0x004A5C30` repeats the physical-item
+branch for a worn item resolved through the authored paperdoll click map:
+
+```text
+on paperdoll right-click:
+ itemID = GetPaperDollItemUnderMouse(mouse)
+ if itemID != 0:
+ SetSelectedObject(itemID)
+ ClientUISystem.ExamineObject(itemID)
+```
+
+Acdream's shared retained `UiItemList` owns the corresponding physical-item
+notice. Backpack, side-bag, external-container, paperdoll-slot, and physical
+toolbar cells provide their selection source and route the request through the
+same `ItemInteractionController` appraisal owner used by world SmartBox and
+the toolbar magnifying glass. RMB motion beyond the shared three-pixel click
+threshold neither appraises nor begins an item drag.
+
## SmartBox click lighting pulse
`UIElement_SmartBoxWrapper::RecvNotice_SmartBoxObjectFound @ 0x004E5AD0`
diff --git a/src/AcDream.App/UI/Layout/ExternalContainerController.cs b/src/AcDream.App/UI/Layout/ExternalContainerController.cs
index 35371745..117bf7cb 100644
--- a/src/AcDream.App/UI/Layout/ExternalContainerController.cs
+++ b/src/AcDream.App/UI/Layout/ExternalContainerController.cs
@@ -83,6 +83,9 @@ public sealed class ExternalContainerController : IItemListDragHandler, IRetaine
ConfigureList(_topContainer, ContainerCellSize, horizontalScroll: false, containerEmptySprite);
ConfigureList(_containerList, ContainerCellSize, horizontalScroll: true, containerEmptySprite);
ConfigureList(_contentsList, ItemCellSize, horizontalScroll: true, contentsEmptySprite);
+ _topContainer.ExamineItemRequested = ExamineItem;
+ _containerList.ExamineItemRequested = ExamineItem;
+ _contentsList.ExamineItemRequested = ExamineItem;
_contentsList.RegisterDragHandler(this);
if (layout.FindElement(ContentsScrollbarId) is UiScrollbar scrollbar)
@@ -375,6 +378,12 @@ public sealed class ExternalContainerController : IItemListDragHandler, IRetaine
private void Select(uint guid)
=> _selection.Select(guid, SelectionChangeSource.ExternalContainer);
+ private void ExamineItem(uint guid)
+ {
+ Select(guid);
+ _itemInteraction.ExamineSelectedOrEnterMode(guid);
+ }
+
private void HandlePrimaryClick(uint guid, Action fallback)
{
if (_itemInteraction.OfferPrimaryClick(guid) != ItemPrimaryClickResult.NotActive)
@@ -564,5 +573,8 @@ public sealed class ExternalContainerController : IItemListDragHandler, IRetaine
_objects.Cleared -= OnObjectsCleared;
_selection.Changed -= OnSelectionChanged;
_itemInteraction.StateChanged -= OnInteractionStateChanged;
+ _topContainer.ExamineItemRequested = null;
+ _containerList.ExamineItemRequested = null;
+ _contentsList.ExamineItemRequested = null;
}
}
diff --git a/src/AcDream.App/UI/Layout/InventoryController.cs b/src/AcDream.App/UI/Layout/InventoryController.cs
index 02e660a6..774678ee 100644
--- a/src/AcDream.App/UI/Layout/InventoryController.cs
+++ b/src/AcDream.App/UI/Layout/InventoryController.cs
@@ -153,6 +153,9 @@ public sealed class InventoryController : IItemListDragHandler, IRetainedPanelCo
_contentsGrid?.RegisterDragHandler(this);
_containerList?.RegisterDragHandler(this);
_topContainer?.RegisterDragHandler(this);
+ if (_contentsGrid is not null) _contentsGrid.ExamineItemRequested = ExamineItem;
+ if (_containerList is not null) _containerList.ExamineItemRequested = ExamineItem;
+ if (_topContainer is not null) _topContainer.ExamineItemRequested = ExamineItem;
// Burden meter: vertical 11×58 bar (gmBackpackUI m_burdenMeter, retail direction 4).
_burdenMeter = layout.FindElement(BurdenMeterId) as UiMeter;
@@ -778,6 +781,17 @@ public sealed class InventoryController : IItemListDragHandler, IRetainedPanelCo
_selection.Select(guid, SelectionChangeSource.Inventory);
}
+ ///
+ /// Retail ItemList right-click: select the physical item, then enter the
+ /// shared appraisal request owner. This is intentionally independent from
+ /// left-click target mode and double-click Use/AutoWield.
+ ///
+ private void ExamineItem(uint guid)
+ {
+ SelectItem(guid);
+ _itemInteraction?.ExamineSelectedOrEnterMode(guid);
+ }
+
/// Open a container (side bag or main pack): it becomes both the selected item and the
/// open container. Sends Use to open a side bag server-side (ViewContents arrives async).
/// Owned packs are not ClientUISystem::groundObject and never send
@@ -917,6 +931,9 @@ public sealed class InventoryController : IItemListDragHandler, IRetainedPanelCo
_objects.ObjectUpdated -= OnObjectChanged;
_objects.Cleared -= OnObjectsCleared;
_selection.Changed -= OnSelectionChanged;
+ if (_contentsGrid is not null) _contentsGrid.ExamineItemRequested = null;
+ if (_containerList is not null) _containerList.ExamineItemRequested = null;
+ if (_topContainer is not null) _topContainer.ExamineItemRequested = null;
if (_itemInteraction is not null)
{
_itemInteraction.StateChanged -= OnInteractionStateChanged;
diff --git a/src/AcDream.App/UI/Layout/PaperdollController.cs b/src/AcDream.App/UI/Layout/PaperdollController.cs
index c68b2ddc..f1dac2fd 100644
--- a/src/AcDream.App/UI/Layout/PaperdollController.cs
+++ b/src/AcDream.App/UI/Layout/PaperdollController.cs
@@ -84,6 +84,7 @@ public sealed class PaperdollController : IItemListDragHandler, IRetainedPanelCo
var (element, mask, _, unlockBit) = PaperdollSlotBackgrounds.Definitions[i];
if (layout.FindElement(element) is not UiItemList list) continue;
list.RegisterDragHandler(this);
+ list.ExamineItemRequested = ExamineItem;
list.Cell.SourceKind = ItemDragSource.Equipment;
list.Cell.SlotIndex = i; // definition position = equipped drag-payload SourceSlot
list.Cell.EmptySprite = emptySlotSprites is not null
@@ -350,6 +351,12 @@ public sealed class PaperdollController : IItemListDragHandler, IRetainedPanelCo
foreach (var a in _armorSlots) a.Visible = _viewState.ArmorSlotsVisible;
}
+ private void ExamineItem(uint itemId)
+ {
+ _selection.Select(itemId, SelectionChangeSource.Paperdoll);
+ _itemInteraction.ExamineSelectedOrEnterMode(itemId);
+ }
+
/// Detach event handlers (idempotent).
public void Dispose()
{
@@ -361,6 +368,8 @@ public sealed class PaperdollController : IItemListDragHandler, IRetainedPanelCo
_objects.ObjectUpdated -= OnObjectChanged;
_objects.Cleared -= OnObjectsCleared;
_selection.Changed -= OnSelectionChanged;
+ foreach (var (_, list) in _slots)
+ list.ExamineItemRequested = null;
if (_dollViewport is UiViewport doll)
doll.ClickedAt = null;
switch (_dollDragMask)
diff --git a/src/AcDream.App/UI/Layout/ToolbarController.cs b/src/AcDream.App/UI/Layout/ToolbarController.cs
index 5e1fd48a..4df5a9b3 100644
--- a/src/AcDream.App/UI/Layout/ToolbarController.cs
+++ b/src/AcDream.App/UI/Layout/ToolbarController.cs
@@ -138,6 +138,7 @@ public sealed class ToolbarController : IItemListDragHandler, IRetainedPanelCont
if (_slots[i] is { } list)
{
WireClick(list);
+ list.ExamineItemRequested = ExamineItem;
// B.1 drag-drop spine: this controller is the drop handler for every
// toolbar slot list; each cell knows its slot index + that it's a
// shortcut-bar source (retail UIElement_ItemList::RegisterItemListDragHandler).
@@ -531,6 +532,15 @@ public sealed class ToolbarController : IItemListDragHandler, IRetainedPanelCont
};
}
+ private void ExamineItem(uint itemId)
+ {
+ if (_selectItem is not null)
+ _selectItem(itemId);
+ else
+ _selection?.Select(itemId, SelectionChangeSource.Toolbar);
+ _itemInteraction?.ExamineSelectedOrEnterMode(itemId);
+ }
+
private static ItemDragAcceptance InventoryButtonDragOver(ItemDragPayload payload)
=> payload.ObjId != 0 && payload.SourceKind != ItemDragSource.ShortcutBar
? ItemDragAcceptance.Accept
@@ -765,6 +775,9 @@ public sealed class ToolbarController : IItemListDragHandler, IRetainedPanelCont
_combatState.CombatModeChanged -= SetCombatMode;
if (_selection is not null)
_selection.Changed -= OnSelectionChanged;
+ foreach (UiItemList? list in _slots)
+ if (list is not null)
+ list.ExamineItemRequested = null;
_repo.ObjectAdded -= OnRepositoryObjectChanged;
_repo.ObjectUpdated -= OnRepositoryObjectChanged;
_repo.ObjectRemoved -= OnRepositoryObjectChanged;
diff --git a/src/AcDream.App/UI/UiItemList.cs b/src/AcDream.App/UI/UiItemList.cs
index 3ddfa90e..50b38c36 100644
--- a/src/AcDream.App/UI/UiItemList.cs
+++ b/src/AcDream.App/UI/UiItemList.cs
@@ -73,6 +73,15 @@ public sealed class UiItemList : UiElement
///
public Action