Slice 4 made a remote character's wielded weapon selectable, which made the
pickup chain reachable end to end for the first time: SelectionPickUp on
another player's weapon captured identity, passed ValidatePickupTarget (which
checked only the Stuck flag and the small-item mask, and a MeleeWeapon clears
both), installed a real non-autonomous approach through
PlayerInteractionMovementSink, and then sent a pickup request the server
rejects. Retail does none of that.
ItemHolder::AttemptToPlaceInContainer @ 0x00588140 runs
AttemptToPlaceInContainer_IsItemLegal @ 0x005870C0 first, at 0x00588173 --
ahead of container legality, auto-merge, the container walk, and the only
CM_Inventory::Event_PutItemInContainer emitter
(ACCWeenieObject::UIAttemptPutInContainer @ 0x0058D680). IsItemLegal's arm at
0x005872B7 rejects `!ACCWeenieObject::IsOwnedByPlayer(item) &&
item->pwd._location != 0` with one local
ECM_UI::SendNotice_DisplayStringInfo(0x1a, ...), and
CPlayerSystem::PlaceInBackpack @ 0x0055D8C0 then withdraws the waiting slot it
had published (SetWaitingState(obj, 0) + SendNotice_EndPendingInPlayer at
0x0055D918). No request, no movement. acdream had never ported that arm; it
was harmless while wielded children were unpickable and stopped being harmless
at f6db964f.
The notice is data_7e2228, "The %s is being wielded by someone else!" -- WITH
the exclamation mark. IsItemLegal's six strings occupy one contiguous literal
block, 0x007e21f0 through 0x007e234c, one per arm in reverse code order, and
the two neighbours already ported here (0x007e227c "The %s cannot be picked
up!" at 0x00587264, 0x007e22b4 "You cannot pick up creatures!" at 0x005871f4)
pin it. The punctuation-free 0x007cd350 variant belongs to the wield/wear
block and is emitted from a different function at 0x00560aef.
pwd._location is the PublicWeenieDesc CurrentWieldedLocation field
(acclient.h:37175), which acdream projects as
ClientObject.CurrentlyEquippedLocation, and ACCWeenieObject::IsOwnedByPlayer
@ 0x0058D160 is IsOwnedByObject(this, player_id) -- already ported as
ClientObjectTable.IsOwnedByObject @ 0x0058CEB0 and reached here through the
existing ItemInteractionController.IsOwnedByPlayer. The arm reads pwd._location
verbatim rather than adding a WielderId belt-and-braces test, because retail's
predicate is the thing being ported.
The player's OWN wielded item is IsOwnedByPlayer, so retail passes it and takes
a different route. ACCWeenieObject::DeterminePositionState @ 0x0058BE70 gives
it PositionState.WIELDED (acclient.h:6802) rather than IN_3D_VIEW, and
UIAttemptPutInContainer records IR_PICK_UP only for IN_3D_VIEW, treating
WIELDED and IN_CONTAINER alike as a plain IR_PUT_IN_CONTAINER transfer. So an
own-wielded item is unwielded in place: the request goes out immediately with
no approach, joining the existing current-ground-object shortcut. The shortcut
carries an ownership conjunct so it can never outrun the 0x005872B7 gate.
TryGetApproach now refuses attached children outright, for the same
IN_3D_VIEW reason. An Attached projection's bookkeeping WorldEntity.Position
carries the PARENT's composed root (EquippedChildRenderController
.ApplyParentWorldPose), not the child frame CPhysicsObj::UpdateChild @
0x00512D50 composes, so an approach built from it walked toward the wielder.
Slice 4 de-parented the marker anchor but left this one parent-derived; no
approach can anchor on a wielder now.
The pick predicates are deliberately untouched. Picking, selecting, examining,
lighting-pulse identity, and the vivid-marker anchor on a remote's wielded
weapon all behave exactly as Slice 4 shipped them -- retail's sr_Select and
sr_Examine branches of RecvNotice_SmartBoxObjectFound @ 0x004E5AD0 never
consult IsItemLegal. The gate is the transaction, not the pick.
f6db964f's message asserted the slice introduced no deviation and owed no
retail-divergence-register row. That was wrong: the unported 0x005872B7 arm
was a deviation it made reachable. This commit ports the arm in full, matches
retail on the own-wielded path, and removes the parent-derived approach
anchor, so the record is corrected here and no register row is owed.
Gates: dotnet build green; AcDream.App.Tests 3,960 passed / 3 skipped;
complete Release solution 9,792 passed / 5 skipped;
tools\run-connected-world-lifecycle-gate.ps1 -SkipBuild RESULT=PASS.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
553 lines
34 KiB
Markdown
553 lines
34 KiB
Markdown
# World interaction completion — pre-M4 program
|
||
|
||
**Status:** Slice 1 user-accepted 2026-07-23. Slice 2 Use-hand selection,
|
||
zero-useability, carried direct-use, AutoWear correction, and the requested
|
||
shared item-cooldown follow-up passed their connected gates. Slice 3's first
|
||
connected gate exposed an incorrect IdentifyResponse flag table, a non-retail
|
||
shared-panel mount, and its missing inscription transaction. All three are
|
||
corrected and live-confirmed. The creature-page follow-up now has retail's
|
||
ordered stat rows and animated private preview. The follow-up item report and
|
||
authored 310 x 400 layout correction are implemented and user-accepted.
|
||
Favorite-spell press-time selection and right-click local SpellPanel
|
||
examination are implemented and user-accepted. The follow-up maps
|
||
component-disabled ACE characters to the modern scarab/prismatic formula,
|
||
resolves formula icons by their DAT icon DIDs, installs those icons as each
|
||
authored template root's own foreground image, and migrates stale examination
|
||
dimensions once to the authored 310 x 400 extent. The final connected
|
||
assessment gate passed on 2026-07-24. Slices 1–3 are complete; resume at Slice
|
||
4, equipped-child world picking.
|
||
**Milestone:** M4 prerequisite/preamble.
|
||
**Architecture:** retained gameplay UI over shared selection, object, and
|
||
interaction state. `GameWindow` remains a composition/callback shell.
|
||
|
||
## Outcome
|
||
|
||
Close the remaining retail interaction surfaces before the larger M4
|
||
quest/emote/character-creation bodies begin:
|
||
|
||
1. Favorite spell bars expose their DAT-authored overflow arrows and scroll
|
||
through every server-persisted favorite.
|
||
2. The status bar's hand and magnifying-glass controls invoke the same Use and
|
||
Assess commands as their keyboard paths.
|
||
3. Assessing a creature, player, NPC, or object opens its retail information in
|
||
the independent movable/resizable retail floaty examination window.
|
||
4. World picking can resolve visible equipped children, such as a character's
|
||
wielded weapon, while selection markers remain anchored to the picked child.
|
||
5. Vendor use opens the authored vendor surface, publishes its inventory, and
|
||
supports retail selection/browsing.
|
||
6. Vendor buy/sell transactions, quantities, pending-state ownership, and
|
||
authoritative inventory reconciliation complete the loop.
|
||
|
||
The program reuses the existing retained-window host, `SelectionState`,
|
||
`ClientObjectTable`, interaction transaction owner, and server-authoritative
|
||
inventory updates. It does not create parallel panel positions, item tables,
|
||
selection state, or optimistic inventory outcomes.
|
||
|
||
## Ordered slices
|
||
|
||
| Slice | Deliverable | Principal owner |
|
||
|---|---|---|
|
||
| 1 | Spell-bar overflow arrows | `SpellcastingUiController` + generic retained scrollbar/list |
|
||
| 2 | Status Use/Assess commands | focused status-bar controller binding to the existing action router |
|
||
| 3 | Assessment information panel | retained controller in an independent floaty examination window |
|
||
| 4 | Equipped-child world picking | pure world-query/picking policy plus presentation anchor |
|
||
| 5 | Vendor browse lifecycle | vendor session/controller plus authored panel |
|
||
| 6 | Vendor transactions | server-authoritative buy/sell command and reconciliation owner |
|
||
|
||
Each slice begins with named-retail research, produces pseudocode and
|
||
conformance tests, updates the divergence register if required, and lands as a
|
||
separate bisectable commit. A visual gate follows each UI-bearing slice.
|
||
|
||
Slice 1 now imports both 23-pixel arrow buttons, their rollover/pressed media,
|
||
and HideDisabled from the real combat fixture; places their artwork by the
|
||
authored leading/trailing positions; shares the list's single pixel-scroll
|
||
model; advances one 32-pixel cell per press; and exposes only actively selected
|
||
spells. Passive object/endowment refresh preserves a manual offset. The mixed
|
||
610/800-pixel DAT anchor chain is solved to a fixed 747-pixel combat root,
|
||
producing exactly 18 visible 32-pixel favorite cells (nine numbered plus nine
|
||
unnumbered) without consuming the overflow. The initial 57-test/full-suite gate passed;
|
||
the corrective 104-test focus set, 3,472 App tests / 3 skips, Release solution
|
||
build, and 7,845 complete-solution tests / 5 skips pass. The corrected
|
||
18-cell bar and both overflow directions passed the connected gate.
|
||
|
||
Slice 2 now drives the toolbar hand from canonical `SelectionState` changes and
|
||
live `ClientObjectTable` updates. The pure Core predicate ports
|
||
`gmToolbarUI::HandleSelectionChanged`: combat-use items, armor/clothing/jewelry,
|
||
and `ItemUses` values without `USEABLE_NO` remain active. Clicking still enters
|
||
the existing `ItemInteractionController` command path, so weapons use the
|
||
server-confirmed AutoWield transaction and targeted tools enter the existing
|
||
use-on-target cursor. Empty selection and explicitly unusable spell components
|
||
are ghosted and ignore clicks. Empty-selection ghosting is the user's explicit
|
||
choice over retail's generic `TARGET_MODE_USE` entry and is registered as
|
||
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 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
|
||
Use event immediately for both owned and world objects; acdream had
|
||
incorrectly routed the packet through a local approach lookup, where
|
||
Blackmoor's Favor has no spatial entity and was silently cancelled. Every
|
||
ordinary Use now sends once and ACE owns any authoritative MoveTo chain.
|
||
AD-27 remains only for client-side pickup completion. 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
|
||
`You must remove your <item> to wear that`. Research:
|
||
[`../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. 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.
|
||
The user confirmed Blackmoor's Favor now activates correctly.
|
||
|
||
Before Assess, the user requested the adjacent retail item-cooldown
|
||
presentation. `PublicWeenieDesc` now preserves the optional shared cooldown id
|
||
and duration, assessed property updates reach the same object fields, and
|
||
`CEnchantmentRegistry::OnCooldown @ 0x005943C0` plus
|
||
`UIElement_UIItem::UpdateCooldownDisplay @ 0x004E1E20` are ported through the
|
||
canonical Core registry and one pure display projection. Every retained item
|
||
list shares one controller and the exact
|
||
ten DAT-authored radial sprites `0x060067CF..0x060067D8`; items with the same
|
||
group display the same server-authored cooldown. Research:
|
||
[`../research/2026-07-23-retail-item-cooldown-pseudocode.md`](../research/2026-07-23-retail-item-cooldown-pseudocode.md).
|
||
The focused parser/Core/UI/production-DAT tests, warning-free Release solution
|
||
build, 3,482 App tests / 3 skips, and 7,875 complete-solution tests / 5 skips
|
||
pass. AP-123 records only the retained toolkit's procedural-child adaptation;
|
||
the visible assets, ordering, timing, and shared-group behavior are exact.
|
||
The user accepted the live cooldown presentation 2026-07-23.
|
||
|
||
Slice 3 preserves the existing toolbar/keyboard Assess command and ports the
|
||
retail response owner around it. One shared UI-busy reference covers the
|
||
latest pending GUID, stale replies are rejected, the accepted reply becomes
|
||
the current appraisal, and combat-time creature/player refreshes occur every
|
||
0.75 seconds only while the examination window is visible. Complete
|
||
`IdentifyObjectResponse` parsing now uses ACE/retail's exact flag values and
|
||
positional order, includes the three-word HookProfile, and rejects truncated
|
||
gated payloads rather than continuing from a corrupt cursor. The first live
|
||
monster gate found that `0x0100` had been mislabeled WeaponProfile; the parser
|
||
dropped every creature response before response acceptance, leaving the one
|
||
busy reference held. A literal ACE `0x0100` creature fixture now protects that
|
||
packet-to-controller path.
|
||
|
||
LayoutDesc `0x2100006B`, root `0x100005F2`, supplies the complete 310 x 400
|
||
floaty chrome, title, item/creature/character subviews, close control, and
|
||
authored scrollbars. `gmFloatyExaminationUI` is an independent top-level
|
||
window, so Inventory/Skills/Spellbook no longer replace it or inherit its
|
||
geometry. The imported multiline inscription field now ports
|
||
`ItemExamineUI`'s public/hook inscribability, inventory ownership and
|
||
same-scribe permission rules, placeholder/signature presentation, exact
|
||
failure notices, focus commit behavior, and CP-1252 `SetInscription (0x00BF)`
|
||
transaction. Basic item and player reports are live. The creature page now
|
||
resolves its type through retail EnumMapper `0x2200000E`, preserves the
|
||
authored Character/Level header, creates the exact nine stat rows from
|
||
template `0x10000166`, and renders a fixed-heading animated clone through a
|
||
private viewport using retail's bounding-box camera and distant light.
|
||
The visual-gate correction also ports the separate `0x10000335`
|
||
damage/critical/resistance rating list, places authored row chrome behind the
|
||
animated preview and text in front, adds the balanced row inset, and follows
|
||
the current selection automatically while the examination window is visible.
|
||
Item-object preview, specialized item/character detail regions, and exact
|
||
creature appraisal font-state selection remain the narrowed AP-110 residual.
|
||
The item report now retains `PublicWeenieDesc` hook identity, applies
|
||
appraisal-only Value/Burden unknowns, suppresses mounted-hook sentinel
|
||
capacities, preserves retail line/paragraph boundaries, and selects the
|
||
authored white/green/red item font-color entries. Research:
|
||
[`../research/2026-07-23-retail-appraisal-ui-pseudocode.md`](../research/2026-07-23-retail-appraisal-ui-pseudocode.md).
|
||
The focused parser/router/request/controller/fixture tests pass, as do the
|
||
Release solution build with 17 pre-existing tracked test warnings, 3,496 App
|
||
tests / 3 skips, and 7,913 complete-solution tests / 5 skips.
|
||
|
||
The creature presentation follow-up adds real-DAT row-template and EnumMapper
|
||
fixtures plus pure conformance coverage for row order/formatting, enchantment
|
||
semantics, failed assessments, stable clone identity, live animated mesh
|
||
updates, hydrated mesh bounds, and retail camera fitting. The warning-free App code,
|
||
Release solution build, 3,506 App tests / 3 skips, and 7,923 complete-solution
|
||
tests / 5 skips pass. The connected visual result remains the closeout gate.
|
||
|
||
The rating/layering/selection-follow correction adds 18 focused green tests,
|
||
passes the Release solution build with the 17 warnings already tracked by
|
||
#228, 3,510 App tests / 3 skips, and 7,927 complete-solution tests / 5 skips.
|
||
Its corrected connected visual result remains the closeout gate.
|
||
|
||
The item-report/layout correction restores the authored 310 x 400 examination
|
||
size for the connected profile, leaves retail's ordinary resize range intact,
|
||
keeps row chrome at its LayoutDesc origin while insetting only foreground
|
||
creature text, and starts generated item prose at the top of its authored
|
||
scroll surface. `ItemAppraisalTextFormatter` now owns the decomp-ordered item
|
||
projection outside `GameWindow` and outside the examination controller. It
|
||
ports common weapon damage ranges/speed/range/ammunition, armor protection
|
||
bands, defense/caster modifiers, workmanship, ratings, wield/use/activation
|
||
requirements, item XP/capacity/lock/mana/uses/creator/rare data, cooldown and
|
||
imbued special properties, and both short spell lists and full DAT spell
|
||
descriptions. The conformance follow-up replaces public-value fallbacks with
|
||
retail's appraisal presence semantics, restores hook/capacity/lock behavior,
|
||
and carries each `AddItemInfo` fragment's paragraph and font-color index into
|
||
the retained text shaper. AP-110 now names only the remaining specialized,
|
||
player-dependent, DAT-display-name, creature-font-state, and object-preview gaps.
|
||
Focused conformance fixtures cover the geometry layering plus melee, launcher,
|
||
armor, spell, and special-property reports. The Release solution build passes
|
||
with the 17 warnings already tracked by #228, 3,514 App tests / 3 skips, and
|
||
7,931 complete-solution tests / 5 skips. The connected visual result remains
|
||
the historical pre-acceptance checkpoint; the final gate passed 2026-07-24.
|
||
|
||
The item-format conformance correction adds literal hook-tail cursor fixtures,
|
||
wire/session/object-table propagation tests, the exact Black Phyntos Hive
|
||
report, capacity/page/lock presence cases, structured paragraph checks, and
|
||
LayoutDesc color-palette/enchantment-style coverage. The isolated Release
|
||
solution build passes with the same 17 tracked warnings; 3,522 App tests / 3
|
||
skips and 7,942 complete-solution tests / 5 skips pass while the previously
|
||
launched client retains the normal Release output lock. The corrected
|
||
connected item visual was accepted 2026-07-24.
|
||
|
||
The exhaustive item-report correction replaces the remaining generic/numeric
|
||
approximations with the complete named-retail dispatch. Equipment sets use the
|
||
literal EoR table; ratings, tinkering/salvage averages, coverage, failed weapon
|
||
unknowns, level restrictions, all three item-XP curves, activation heritage,
|
||
healer/ordinary boost behavior, rare timers, and magic `~ Name: Description`
|
||
rows now preserve retail wording and ordering. Description construction ports
|
||
lifespan prose, workmanship/material/gem decoration and portal/PK restriction
|
||
bits. `RetailAppraisalNameResolver` follows retail's master
|
||
`EnumIDMap -> sub-enum 1 DualEnumIDMap` material chain and shares the installed
|
||
creature mapper for slayers and wield requirements; a production-DAT test pins
|
||
Ruby and Ghost. AP-110 is narrowed to item preview and the projections that
|
||
need live player/localization state (effective shield, cooldown remaining, and
|
||
augmentation-cost `StringInfo`), plus character and creature-font residuals.
|
||
The focused item formatter suite passes 21/21, App Release passes 3,531 tests /
|
||
3 skips, and the complete Release solution passes 7,952 tests / 5 skips. The
|
||
connected item-report comparison passed on 2026-07-24.
|
||
|
||
The material-title and section-boundary correction carries
|
||
`PublicWeenieDesc.MaterialType` from CreateObject through the canonical object
|
||
table and resolves `ACCWeenieObject::GetObjectName(NAME_APPROPRIATE)` through
|
||
the installed DAT material map. Examination titles now produce names such as
|
||
`Reed Shark Hide Steel Toed Boots` without duplicating a material already
|
||
present in the authored base name. Empty `AddItemInfo` calls are retained as
|
||
real report fragments, restoring retail's intentional blank rows after
|
||
workmanship, before armor level, around rating/special-property blocks, and at
|
||
the later use/item-level boundaries. Focused wire, projection, object-table,
|
||
title, and boots-layout fixtures protect the full path. With the subsequent
|
||
right-click and press-time retained-item input ports, the Release solution
|
||
build and 3,548 App tests / 3 skips plus 7,979 complete-solution tests /
|
||
5 skips pass.
|
||
|
||
The world right-click follow-up ports
|
||
`UIElement_SmartBoxWrapper::MouseUp @ 0x004E5820` and the
|
||
`sr_Examine` branch of `RecvNotice_SmartBoxObjectFound @ 0x004E5AD0`.
|
||
The configurable `SelectRight` binding now completes on release, cancels when
|
||
pointer travel crosses the retail-observed three-pixel drag threshold, and
|
||
routes through the existing world picker, lighting pulse, canonical
|
||
`SelectionState`, and appraisal request owner. Empty world space remains a
|
||
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.
|
||
The input-latency follow-up ports `UIElement_ListBox::MouseDown @ 0x0046E3A0`
|
||
and the physical left-click branch of
|
||
`UIElement_ItemList::ListenToElementMessage @ 0x004E4D50`. Canonical selection
|
||
and the retained green frame update during left-button down, before the
|
||
three-pixel drag threshold. Target mode is offered first. Opening, using,
|
||
equipping, looting, and shortcut activation remain completed-click or
|
||
double-click actions and are suppressed when target mode consumed the press.
|
||
Favorite spells now use the parallel non-weenie path:
|
||
`gmSpellcastingUI::ListenToElementMessage @ 0x004C7AB0` selects the favorite
|
||
on left press, while the spell branch of
|
||
`UIElement_ItemList::ListenToElementMessage @ 0x004E4D50` opens the authored
|
||
SpellPanel locally on right-click. Spell IDs never enter `SelectionState` or
|
||
the status-bar magnifier path, and no Appraise/busy transaction is invented.
|
||
The view projects exact spell fields and the current appropriate formula
|
||
through authored component template `0x1000032E`. Its corrective pass uses
|
||
each component descriptor's icon DID rather than its inventory WCID, applies
|
||
the modern scarab/prismatic formula when ACE disables component enforcement
|
||
(IA-21), and introduces per-window authored-geometry revisions so an obsolete
|
||
saved examination height resets once without discarding position or future
|
||
user resizing. The corrected formula/icons/foreground stack and authored
|
||
extent were accepted on 2026-07-24. The 61-test focused App
|
||
gate passed before this correction. The corrected focused tests, Release build
|
||
with #228's 17 tracked test warnings, 3,555 App tests / 3 skips, and 7,986
|
||
complete-solution tests / 5 skips pass.
|
||
|
||
## Slice 1 — spell-bar overflow arrows
|
||
|
||
### Retail oracle
|
||
|
||
The authored Magic combat layout contains, inside every favorite-tab group:
|
||
|
||
- horizontal scrollbar `0x100000B5`, 685×36 at the 800-pixel design width;
|
||
- decrement button `0x10000071`, 23×36;
|
||
- increment button `0x10000072`, 23×36;
|
||
- item list `0x100000B6`, inset by 23 pixels on both sides and 32 pixels high.
|
||
|
||
The list references the scrollbar through `UIElement_Scrollable` property
|
||
`0x71`. The scrollbar references increment/decrement buttons through
|
||
properties `0x77`/`0x78`, and property `0x79` enables HideDisabled. There is no
|
||
track or thumb in this specific control: the two authored arrows are the whole
|
||
visible scrollbar.
|
||
|
||
Named retail references and executable pseudocode are recorded in
|
||
[`../research/2026-07-23-retail-spellbar-overflow-pseudocode.md`](../research/2026-07-23-retail-spellbar-overflow-pseudocode.md).
|
||
|
||
### Implementation plan
|
||
|
||
1. Generalize `DatWidgetFactory.BuildScrollbar` so horizontal and vertical
|
||
scrollbars both import the referenced decrement/increment children,
|
||
including their authored positions, dimensions, Normal/rollover/pressed
|
||
media, and HideDisabled property.
|
||
2. Generalize `UiScrollbar` to use distinct authored decrement/increment
|
||
extents for rendering, hit-testing, track geometry, and dragging. Preserve
|
||
the existing 16-pixel default for layouts whose button children are absent.
|
||
3. Reproduce retail disabled presentation: a model without overflow rejects
|
||
pointer input; when HideDisabled is authored, the scrollbar also draws
|
||
nothing and does not claim hit tests.
|
||
4. In `SpellcastingUiController`, bind each group's scrollbar to its
|
||
`UiItemList.Scroll`, enable horizontal scrolling, and retain one independent
|
||
pixel offset per favorite tab.
|
||
5. Match `SpellCastSubMenu::SetSelected`: selecting a spell through keyboard,
|
||
shortcut, or code scrolls that item into view; passive state refresh does
|
||
not re-expose it.
|
||
6. Carry the combat root's mixed-parent raw-edge policies through the complete
|
||
imported tree and solve for the retail 18-cell favorite viewport; keep that
|
||
HUD capacity fixed across desktop resizes.
|
||
7. Pin the importer, arrow hit extents, 32-pixel step, no-overflow behavior,
|
||
controller binding, and selection exposure with focused App tests.
|
||
8. Run the App Release suite, solution Release build, and complete Release
|
||
suite. Then update the roadmap/memory and request the connected visual gate:
|
||
place more favorites than fit, scroll both directions, change tabs, and
|
||
verify arrows disappear on a non-overflowing tab.
|
||
|
||
### Invariants
|
||
|
||
- DAT supplies the controls and their artwork; no new spell-bar texture or
|
||
overlay is invented.
|
||
- The scrollbar and list share one `UiScrollable`; there is no second offset.
|
||
- One arrow press moves one 32-pixel favorite cell, matching
|
||
`UIElement_ListBox::InqScrollDelta`.
|
||
- Hidden disabled arrows cannot intercept combat-page dragging or clicks.
|
||
- Existing stack, combat-power, chat, inventory, spellbook, and external
|
||
container scrollbars retain their current behavior.
|
||
- No substantial feature body enters `GameWindow`.
|
||
|
||
## Slice 2 — status Use/Assess commands
|
||
|
||
### Use-hand implementation
|
||
|
||
1. `ItemInteractionPolicy.IsToolbarUseEnabled` is the pure named-retail
|
||
selection predicate.
|
||
2. `ItemInteractionController.IsToolbarUseEnabled` adapts the selected live
|
||
object without triggering a request or consuming the use throttle.
|
||
3. `ToolbarController` subscribes to canonical `SelectionState.Changed` and
|
||
selected-object add/update/remove notices, then sets the imported button's
|
||
normal or ghosted state through `UiButton.Enabled`.
|
||
4. Enabled clicks retain the one existing activation path:
|
||
equipment enters `AutoWieldController`, ordinary use enters the normal Use
|
||
request owner, and targeted items enter `UseItemOnTarget`, which already
|
||
owns the retail target cursor.
|
||
|
||
### Slice 2 closeout
|
||
|
||
- The shared item-cooldown visual gate passed.
|
||
- `gmToolbarUI::ListenToElementMessage @ 0x004BEE90` confirms the existing
|
||
magnifying-glass path was already correct: assess the selected GUID
|
||
immediately, otherwise enter one-shot Examine target mode.
|
||
- Slice 3 owns the response lifetime and retained examination presentation;
|
||
Slice 2 adds no duplicate command path.
|
||
|
||
## Slice 3 — assessment information panel
|
||
|
||
### Implemented ownership
|
||
|
||
1. `AppraiseInfoParser` owns the complete positional response payload,
|
||
including HookProfile and strict truncation failure.
|
||
2. `ItemInteractionController` owns the pending/current GUID pair and the
|
||
balanced shared busy reference.
|
||
3. `AppraisalUiController` owns response acceptance, item/creature/character
|
||
subview selection, report projection, scroll preservation, first-response
|
||
visibility, and visible-combat refresh.
|
||
4. `RetailUiRuntime` imports and registers retail's
|
||
`gmFloatyExaminationUI` as its own top-level window. It deliberately does
|
||
not enter `RetailPanelUiController`; Inventory, Skills, Spellbook, and
|
||
effects keep their shared main-panel geometry while Examination coexists.
|
||
5. Network workers deliver immutable parsed data through the existing
|
||
session router; retained state changes remain on the update thread.
|
||
6. `AppraisalUiController` owns inscription presentation and optimistic field
|
||
state; `WorldSession` owns the exact `0x00BF` GameAction send. Authoritative
|
||
inventory/appraisal data remains the source on the next response.
|
||
|
||
### Connected gate
|
||
|
||
- Select an item and click the magnifying glass (or press Assess): a separate
|
||
floaty window should show its name, available properties, descriptions,
|
||
inscription/signature, retail chrome, and working scrollbars.
|
||
- Assess a monster, NPC, and player: the correct creature/character page
|
||
should appear and the busy cursor should clear.
|
||
- On a monster, verify the animated creature is centered behind the exact
|
||
Strength/Endurance/Coordination/Quickness/Focus/Self/Health/Stamina/Mana
|
||
order, with its creature type and level in the authored header. Assess
|
||
differently sized monsters to verify the retail bounding-box fit.
|
||
- In combat, leave a creature assessment open long enough to observe a health
|
||
refresh; closing the examination window must stop refreshes.
|
||
- Keep Examination open while opening/moving Inventory or Skills and confirm
|
||
both windows coexist with independent rectangles.
|
||
- On the combat spell bar, press and hold a different favorite: its selection
|
||
and name should update before release. Right-click a favorite: the same
|
||
examination floaty should show the SpellPanel with name, school, mana,
|
||
duration, range, description, and component formula. The toolbar magnifier
|
||
must not treat the selected spell as an object.
|
||
- Assess an owned inscribable weapon. Edit and clear its inscription by
|
||
clicking elsewhere, then reassess it. An item authored by another player is
|
||
read-only and reports the exact retail permission line when clicked.
|
||
|
||
## Slice 4 — equipped-child world picking
|
||
|
||
**Status:** implemented 2026-07-29, pending the two-client visual gate. Owner
|
||
shape per the program table held: pure world-query/picking policy plus
|
||
presentation anchor. No wire, physics, renderer, or
|
||
`EquippedChildRenderController` changes. `LiveEntityRuntime` gained scoped
|
||
`TryGetAttachedProjectedRecord` / `TryGetPickEligibleRecord` predicates;
|
||
`TryGetInteractionEligibleRecord` and the `_visible` set are untouched, so
|
||
radar, auto-target, and `CombatAttackTargetSource` remain wielded-item free
|
||
(regression-asserted). `WorldSelectionQuery` takes the composed child root as
|
||
an injected `Func<uint, Matrix4x4?>` beside the selection-sphere hook, wired in
|
||
`LivePresentationComposition` from `EntityEffectPoseRegistry.TryGetRootPose`.
|
||
The own-wielded `sr_Use` gate (`0x004E5BE9`) ships through the new
|
||
`IWorldSelectionQuery.IsWieldedByPlayer`. Gates: App tests 3,951/3 skips,
|
||
complete Release solution 9,783/5 skips, connected world-lifecycle gate
|
||
`RESULT=PASS`.
|
||
|
||
**Correction 2026-07-29 (Opus review finding F1, HIGH).** Making a remote's
|
||
wielded weapon selectable made the pickup chain reachable end to end for the
|
||
first time, and acdream had never ported
|
||
`ItemHolder::AttemptToPlaceInContainer_IsItemLegal @ 0x005870C0`'s arm at
|
||
`0x005872B7` — `!ACCWeenieObject::IsOwnedByPlayer(item) && item->pwd._location
|
||
!= 0` — so `SelectionPickUp` on another character's weapon installed a real
|
||
approach and a wire request the server rejects. That arm now ships, with
|
||
retail's own notice (`0x007e2228`) and retail's placement ahead of every other
|
||
`AttemptToPlaceInContainer @ 0x00588140` stage: one local message, no movement,
|
||
no request. The player's own wielded item is `IsOwnedByPlayer`, so it passes
|
||
the arm and takes retail's `PositionState.WIELDED` route —
|
||
`ACCWeenieObject::UIAttemptPutInContainer @ 0x0058D680` records
|
||
`IR_PUT_IN_CONTAINER`, not `IR_PICK_UP` — dispatching its container transfer
|
||
immediately with no approach. `TryGetApproach` now refuses attached children
|
||
outright for the same reason, so no approach can anchor on a wielder's root.
|
||
Picking, selection, examination, and the marker anchor are unchanged. The
|
||
slice therefore introduces no divergence, contrary to what `f6db964f`'s message
|
||
claimed; no register row is owed. Gates: App tests 3,960/3 skips, complete
|
||
Release solution 9,792/5 skips, connected world-lifecycle gate `RESULT=PASS`.
|
||
|
||
### The retail mechanism
|
||
|
||
Retail picking is render-coupled, not a scene-graph ray walk. A click arms a
|
||
per-frame selection cursor (`UIElement_SmartBoxWrapper::MouseDown @
|
||
0x004E5700` sets a `SearchReason` — sr_Select/sr_Examine/sr_Use/sr_TargetedUse,
|
||
acclient.h:6789 — then `SmartBox::find_object @ 0x00451C60` sets
|
||
`Render::set_selection_cursor @ 0x0054B750`). During the frame,
|
||
`Render::update_viewpoint @ 0x0054CDD0` builds `selection_ray` via
|
||
`Render::pick_ray @ 0x0054B610`, and EVERY drawn part accumulates hits in
|
||
`Render::GfxObjUnderSelectionRay @ 0x0054C740`: drawing-sphere test, then
|
||
per-polygon tests when enabled, keeping the closest — with the polygon winner
|
||
outranking a sphere-only winner (`GetMouseSelectionObjectID @ 0x0054C950`,
|
||
read at `SmartBox::DrawNoBlit @ 0x00454C20`).
|
||
|
||
**The child-vs-parent answer:** each hit records `part->physobj->id`
|
||
(`CPhysicsPart::get_physobj_id @ 0x0050D490`), and a part is only a candidate
|
||
when `part->physobj->id != 0` (`CPhysicsPart::Draw @ 0x0050D7A0`). Equipped
|
||
children are first-class `CPhysicsObj`s with their own ids and part arrays
|
||
(`CPhysicsObj::add_child @ 0x0050F870` via `CSetup::GetHoldingLocation @
|
||
0x005213F0`; `CPhysicsObj::UpdateChild @ 0x00512D50` composes
|
||
`Frame::combine(parent_part_frame, hold_frame)` into the child's own
|
||
`m_position` every frame). So a click on a wielded weapon returns THE WEAPON'S
|
||
GUID — there is no parent redirection in the path, and no ethereal or
|
||
wielded-specific gate: the only candidacy rule is "drawn part with a nonzero
|
||
physobj id".
|
||
|
||
Post-pick (`RecvNotice_SmartBoxObjectFound @ 0x004E5AD0`): the id must exist in
|
||
the weenie table; selection is set to the picked id itself
|
||
(`ACCWeenieObject::SetSelectedObject @ 0x0058C2E0`); the click flash
|
||
(`CPhysicsObj::SetLighting @ 0x00511A80`) is non-recursive — it lights that
|
||
object's own part array ONLY, so clicking a weapon flashes the weapon and
|
||
clicking a creature does not flash its weapon; the vivid brackets
|
||
(`VividTargetIndicator @ 0x004F5CE0`) derive from the selected object's own
|
||
selection sphere at its own position, which for a child IS the hand frame.
|
||
`sr_Use` on an object whose `_wielderID == player_id` is suppressed
|
||
(0x004E5BE9) while selection still happens; `sr_Examine` examines the child id
|
||
directly. `PositionState.WIELDED` is distinct from `IN_CONTAINER`
|
||
(acclient.h:6802), so container suppression never hides a wielded selection.
|
||
|
||
### The gap in acdream (the picker is already right)
|
||
|
||
Equipped children are already live entities with their own `ServerGuid`
|
||
(`EquippedChildRenderController.TryRealize`, :448-617) and every draw path
|
||
already publishes their selection parts under that guid
|
||
(`RetailSelectionScene.AddVisiblePart`, which only skips `serverGuid == 0`).
|
||
`RetailWorldPicker.Pick` therefore already returns the weapon as the polygon
|
||
winner. The failure is entirely downstream: `WorldSelectionQuery.PickAt`
|
||
(:137-154) requires `TryGetInteractionEligibleRecord`, whose `_visible` set
|
||
admits `LiveEntityProjectionKind.World` only (`LiveEntityRuntime.cs:1183`
|
||
excludes Pending/Attached/Hidden by design), so the winning hit is discarded
|
||
and the click reports nothing. Retail would have succeeded. Marker anchoring
|
||
has the twin problem: `ResolveVividTargetInfo` (:262-286) gates on the
|
||
World-only `TryGetSpatiallyProjectedRecord`, and `TryGetSelectionSphere`
|
||
(:293-318) anchors at `entity.Position/Rotation`, which for an attached child
|
||
is deliberately the PARENT's root pose (`ApplyParentWorldPose`, :651-658) —
|
||
brackets at the wielder's feet. The child's true composed root
|
||
(`pose.RootLocal * parentWorld`, the exact `Frame::combine` equivalent) is
|
||
already published per frame to `EntityEffectPoseRegistry` (`PublishChildPose`,
|
||
:632-644; `TryGetRootPose` :195) and is what the vfx anchors already use.
|
||
|
||
### Slice plan
|
||
|
||
1. **Pick eligibility for attached projections.** Add a scoped
|
||
`LiveEntityRuntime.TryGetPickEligibleRecord(serverGuid, localEntityId)`
|
||
accepting `World` (today's semantics) OR `Attached` (with the same
|
||
`IsSpatiallyProjected` + `WorldEntity.Id == localEntityId` staleness
|
||
recheck). Consume it in `WorldSelectionQuery.PickAt`, the lighting-pulse
|
||
identity paths, and `TryGetInteractionTarget`. **Do NOT widen
|
||
`TryGetInteractionEligibleRecord`/`_visible`** — it feeds radar,
|
||
auto-target, sticky/MoveTo establishment and `CombatAttackTargetSource`,
|
||
none of which retail lets wielded items enter (retail's radar has no
|
||
wielded blips).
|
||
2. **Marker + sphere anchor.** Branch `ResolveVividTargetInfo` onto the new
|
||
predicate, and for Attached records transform the Setup selection sphere by
|
||
`EntityEffectPoseRegistry.TryGetRootPose(localId)` (injected as a
|
||
`Func<uint, Matrix4x4?>` beside the existing selection-sphere hook) instead
|
||
of the parent-derived `entity.Position/Rotation`.
|
||
3. **Own-wielded Use gate.** In the use-immediately path, skip the Activate
|
||
enqueue when the picked object's `WielderId == playerGuid` (selection and
|
||
flash still occur) — the 0x004E5BE9 parity. If deferred, it owes an AP row.
|
||
4. **Files:** `LiveEntityRuntime.cs` (predicate), `WorldSelectionQuery.cs`,
|
||
`SelectionInteractionController.cs`, plus composition wiring for the
|
||
root-pose hook. Untouched: `RetailWorldPicker`, `RetailSelectionScene`,
|
||
`WbDrawDispatcher`, `EquippedChildRenderController`.
|
||
5. **Conformance tests** (harnesses exist in
|
||
`tests/AcDream.App.Tests/Interaction/WorldSelectionQueryTests.cs` and
|
||
`Rendering/RetailSelectionSceneTests.cs`): child part closest → resolves to
|
||
the CHILD guid; stale/withdrawn child record → null, never the parent;
|
||
marker uses the pose-registry root, not the parent root; marker suppressed
|
||
for the player's own wielded child, shown for a remote's; lighting pulse
|
||
lights the child identity only; double-click Use suppressed on own wielded.
|
||
6. **Visual gate (user, two-client):** click a remote character's wielded
|
||
weapon — selection names the weapon, the flash lights only the weapon, the
|
||
vivid brackets track the weapon through the wielder's animation (hand, not
|
||
feet), right-click opens Slice 3's examination window on the weapon, radar
|
||
shows no weapon blip, and double-clicking your OWN weapon does not fire a
|
||
Use.
|
||
|
||
### Notes
|
||
|
||
- Slice 3 dependency verified: `SelectionState.Select` stores any nonzero guid
|
||
and `RequestAppraisal` has no eligibility gate, so the examination window
|
||
works on a picked child unmodified once the pick resolves.
|
||
- Divergence register: this slice ADDS no row — it removes an undocumented
|
||
deviation (Attached exclusion from pick eligibility versus retail's
|
||
part-id pick).
|
||
- Existing architectural divergence, unchanged by this slice: retail re-arms
|
||
the pick every frame for hover/tooltips (`sr_MouseOver`); acdream picks on
|
||
demand per click against the last published frame, with an identity recheck.
|