fix(client): restore retail interaction parity
Harden keyboard and camera routing, inventory and vendor interactions, chat/emotes, relog portal flow, and paperdoll rendering. Add retail research, connected gate coverage, and release-gate validation.
This commit is contained in:
parent
0c699240e0
commit
f6fe0f2a4f
151 changed files with 10162 additions and 1211 deletions
283
docs/ISSUES.md
283
docs/ISSUES.md
|
|
@ -24,9 +24,251 @@ What does NOT go here:
|
|||
- Every session: scan OPEN issues at start; promote/close anything we touched during the session before ending.
|
||||
- Promoting to a Phase: mark as `DONE (promoted to Phase X)` + commit SHA where the Phase entry landed.
|
||||
|
||||
## #450 — Fast character re-entry after logout can remain in portal space at `lb 0/0`
|
||||
|
||||
**Status:** DONE — OWNER-ACCEPTED 2026-08-26 in the combined client-parity gate.
|
||||
**Component:** session reset / streaming-origin retirement / login reveal.
|
||||
|
||||
After Shift+Escape logout to character selection, immediately entering the
|
||||
character again could leave the client indefinitely in portal space with no
|
||||
landblocks admitted. The server accepted the second entry; the client title
|
||||
remained at `lb 0/0`.
|
||||
|
||||
**Root cause/fix:** confirmed logout starts a frame-budgeted retirement of the
|
||||
old streaming window, but the synchronous session reset ignored its incomplete
|
||||
result and exposed the fresh Runtime generation. The new world then inherited
|
||||
the old origin-recenter admission gate. The confirmed-logoff pump now holds the
|
||||
authored tunnel until old-window retirement converges, then transfers that
|
||||
completed barrier through the reset callback exactly once. A deterministic
|
||||
regression proves the character-select handoff cannot execute while retirement
|
||||
is incomplete and cannot begin a duplicate retirement during reset.
|
||||
|
||||
**Acceptance:** Shift+Escape to character selection, immediately re-enter, and
|
||||
confirm the destination begins admitting landblocks and exits portal space.
|
||||
Repeat twice in one process.
|
||||
|
||||
## #449 — Main backpack remains falsely full after an item slot is freed
|
||||
|
||||
**Status:** DONE — OWNER-ACCEPTED 2026-08-26 in the combined client-parity gate.
|
||||
**Component:** inventory drag acceptance / main-pack capacity.
|
||||
|
||||
With a full backpack, a move into it correctly shows the red reject cursor.
|
||||
After dropping an item to free a slot, later moves from another pack could
|
||||
remain rejected.
|
||||
|
||||
**Root cause/fix:** main-pack fullness counted every child of the player,
|
||||
including side bags, even though retail places side bags in a separate
|
||||
container-selector list governed by `ContainersCapacity`. Capacity fill,
|
||||
append placement, and drag acceptance now count only visible loose contents.
|
||||
A regression starts with two loose items plus a side bag at capacity two,
|
||||
removes one loose item, and proves the next drag changes from Reject to Accept
|
||||
with a 50% capacity meter.
|
||||
|
||||
**Acceptance:** fill the main pack, observe one rejected move, drop one loose
|
||||
item, then move an item from a side pack into the freed main-pack slot. It must
|
||||
accept immediately without reopening the inventory window. Run with the
|
||||
combined gate in `docs/research/2026-08-26-combined-client-parity-gate.md`.
|
||||
|
||||
## #448 — Outgoing melee hit messages expose a percentage that retail does not print
|
||||
|
||||
**Status:** DONE — OWNER-ACCEPTED 2026-08-26 in the combined client-parity gate.
|
||||
**Component:** combat chat / AttackerNotification presentation.
|
||||
|
||||
Successful outgoing melee hits currently print a percentage in chat, for
|
||||
example `You hit ... for ... damage (54.0%).` The owner reports that this is
|
||||
not retail behavior and that the percentage should not be shown.
|
||||
|
||||
**Likely seam:** `CombatChatTranslator.HandleDamageDealt` unconditionally
|
||||
appends `DamageDealt.DamagePercent`; its tests explicitly pin a template taken
|
||||
from holtburger rather than the named retail client. Recover the exact
|
||||
AttackerNotification presentation from the named retail decomp/string tables,
|
||||
then replace the formatter and its tests. Preserve the wire value in combat
|
||||
state if it has another legitimate consumer; this issue concerns chat output.
|
||||
|
||||
**Acceptance:** ordinary and critical outgoing melee hit lines match retail
|
||||
wording and punctuation exactly and contain no acdream-added percentage.
|
||||
|
||||
## #447 — `@acecommands` produces blank lines in the chat window
|
||||
|
||||
**Status:** DONE — OWNER-ACCEPTED 2026-08-26 in the combined client-parity gate.
|
||||
**Component:** ACE server-command responses / chat presentation.
|
||||
|
||||
Running `@acecommands` against the test server produces a series of blank
|
||||
chat lines instead of the command names and descriptions. The command reaches
|
||||
ACE, but its multiline response loses its visible text before presentation.
|
||||
|
||||
**Investigation seam:** capture the authoritative response message type and
|
||||
raw payload, then trace it through the server-command/interface-text parser,
|
||||
`RuntimeCommunicationState`, and retained chat markup rendering. Do not work
|
||||
around the defect by printing the static `docs/reference/ace-commands.md`
|
||||
copy; the live server response must render correctly.
|
||||
|
||||
**Acceptance:** `@acecommands` displays every non-empty server response line
|
||||
inside retail's retained transcript window with its text intact (newest
|
||||
complete-line tail when the response itself exceeds the cap), produces no
|
||||
blank-line spam, and does not regress normal chat or other ACE commands.
|
||||
|
||||
**2026-08-26 fix:** ACE sends the complete command listing as one `0xF7E0`
|
||||
`ServerMessage` containing embedded newlines. The parser and runtime route
|
||||
already preserved that payload. The retained transcript budget treated the
|
||||
whole message as one indivisible log entry, however, so an admin-sized reply
|
||||
larger than retail's `0x2710`-character cap advanced past the only entry and
|
||||
rendered nothing. `ChatTranscriptRenderer` now clips an oversized boundary
|
||||
entry at a newline and keeps its newest complete lines, matching retail's
|
||||
front-truncation behavior. Ordinary multiline replies below the cap render
|
||||
every authored line. Parser round-trip, normal multiline, oversized response,
|
||||
filter, tagged-run, and existing chat regression tests pass. Owner check is in
|
||||
`docs/research/2026-08-26-combined-client-parity-gate.md`.
|
||||
|
||||
## #446 — Configure Keyboard bindings need an end-to-end retail-parity pass
|
||||
|
||||
**Status:** DONE — OWNER-ACCEPTED 2026-08-26 in the combined client-parity gate,
|
||||
including connected behavior and persistence.
|
||||
**Component:** input / Configure Keyboard / binding persistence.
|
||||
|
||||
The owner reports that keyboard binding still does not work reliably or match
|
||||
retail. Treat this as an end-to-end product gate rather than another isolated
|
||||
layout fix: display the authored mappings, capture a replacement key or mouse
|
||||
button, apply the correct retail conflict rules, make the new action fire,
|
||||
and preserve it across restart. Escape cancellation, Reset/Defaults, scoped
|
||||
combat bindings, modifier chords, and mouse bindings must also match retail.
|
||||
|
||||
Existing issue #373 is one known concrete defect in this flow: acdream ignores
|
||||
the DAT `ActionMap.ConflictingMaps` table and can erase valid shared combat
|
||||
bindings. The fixes recorded under #394-#396 remain pending a complete owner
|
||||
re-gate and do not establish that binding works end to end.
|
||||
|
||||
**2026-08-26 implementation:** deep audit at
|
||||
`docs/research/2026-08-26-retail-keyboard-routing-audit.md`. Bare Escape no
|
||||
longer exits player mode or exposes the orbit/developer bird's-eye camera. It
|
||||
now follows the complete proven retail ladder: finish jump charge, release
|
||||
focused UI, stop movement/repeat attack, cancel target mode, clear selection,
|
||||
then toggle the authored Gameplay Options page. Shift+Escape reaches the real
|
||||
logout gate.
|
||||
|
||||
All 306 installed ActionMap rows now have distinct live identities and enabled
|
||||
Configure Keyboard rows. Exact defaults, contexts, activation, DAT conflict
|
||||
policy, modifier-only and mouse capture, duplicate-chord multicast, explicit
|
||||
unbinding, dense two-slot insertion, same-row no-op, unsupported-input retry,
|
||||
priority conflict/non-bindable dialogs with exact DAT text, dirty-only Revert,
|
||||
Apply/Defaults/OK/Cancel, schema migration, and persistence are implemented.
|
||||
The complete camera, selection, missile, magic, 87-emote, screenshot/help/
|
||||
plugin, quickslot 1–18, panel/chat, and 48 CharacterSettings families reach
|
||||
concrete consumers. Selection includes retail radar/combat/fellow/vendor/
|
||||
environment and session opened-corpse rules. The approved 40 m mouse-wheel
|
||||
chase zoom remains unchanged and regression-pinned.
|
||||
|
||||
Retail's Load File / Save As path is now live as well: the client parses and
|
||||
writes the Sept-2013 PFile `.keymap` grammar under
|
||||
`Documents\Asheron's Call`, remembers the selected profile, presents the
|
||||
authored type-7 file menu and type-5 filename/overwrite dialogs, loads it on
|
||||
startup, and rewrites it on graceful shutdown like retail. `keybinds.json`
|
||||
remains a compatibility mirror for acdream-only commands. AP-202 is retired.
|
||||
|
||||
Automated keyboard-impact evidence is green: App 6,413/6,413, Core
|
||||
4,713/4,713, Runtime 1,849/1,849, and UI.Abstractions 879/879 (13,854
|
||||
tests total). Installed-DAT conformance pins all 306 identities, defaults, the
|
||||
authored Configure Keyboard mount, and active Load/Save controls. Only the
|
||||
connected gate below remains.
|
||||
|
||||
**First owner-round findings fixed 2026-08-26:** modifier-only capture now
|
||||
normalizes LeftShift and consistently raises the retail overwrite prompt when
|
||||
Move Forward conflicts with Toggle Walk/Run. Regular Enter enters chat without
|
||||
its raw event immediately submitting the new field; keypad Enter no longer
|
||||
falls through to the raw chat-focus shortcut. Melee height keys now preserve
|
||||
the Press→held charge→Release transaction instead of treating the first Hold
|
||||
tick as release. Map mode transforms both retail's target direction and viewer
|
||||
offset through the target frame, placing the eye high overhead rather than low
|
||||
behind the character; the approved mouse-wheel zoom range is unchanged.
|
||||
Shift+Escape's same-process relog portal stall is tracked and fixed as #450.
|
||||
Focused App coverage plus the standard Release lane pass.
|
||||
|
||||
**Acceptance:** a connected retail side-by-side covers representative movement,
|
||||
combat, panel, modifier, and mouse mappings; every rebound action executes,
|
||||
conflicts match retail, cancellation changes nothing, and applied bindings
|
||||
survive a fresh client launch.
|
||||
|
||||
## #445 — Stack split errors in inventory; vendor drag ignores selected quantity
|
||||
|
||||
**Status:** DONE — OWNER-ACCEPTED 2026-08-26 in the combined client-parity gate.
|
||||
**Component:** inventory stack splitting / vendor sell staging / shared split
|
||||
quantity.
|
||||
|
||||
Two live paths fail after selecting a partial quantity with the stack slider:
|
||||
|
||||
1. Splitting a stack within the inventory produces an error instead of moving
|
||||
the selected quantity into the destination slot.
|
||||
2. With a stack of 10 and the slider set to 2, dragging the stack into the
|
||||
vendor window stages all 10 rather than the selected 2.
|
||||
|
||||
**Expected:** the selected quantity is the single shared value consumed by
|
||||
inventory split operations and by the vendor drop path; the source retains
|
||||
the remainder. Capture the exact inventory error text/code during the fix
|
||||
gate.
|
||||
|
||||
**Investigation seam:** trace `StackSplitQuantityState` from selection/slider
|
||||
changes through the inventory `SendStackableSplitToContainer` request. The
|
||||
vendor path currently documents and implements full-stack sell staging in
|
||||
`VendorUiController.EvaluateSellAcceptability`; compare that claim against
|
||||
named retail and a retail client gate before changing it, then make the
|
||||
observed behavior and documentation agree. This is distinct from #313, which
|
||||
only tracks selection transfer to the newly created split result.
|
||||
|
||||
**2026-08-26 fix:** named retail's enclosing
|
||||
`VendorSellUI::AcceptDragObject @ 0x004C4F00` disproved the old full-stack-only
|
||||
comment. A partial vendor drop now sends the exact slider quantity through the
|
||||
canonical inventory transaction owner, stages the source as retail's temporary
|
||||
row, and replaces that row in place when the server-created stack with matching
|
||||
WCID/quantity arrives. A matching failure removes the placeholder. Ordinary
|
||||
inventory splitting now uses that same owner and computes empty main-pack
|
||||
placement from visible loose items, excluding side bags that live in retail's
|
||||
separate selector list. Exact quantity, request lifetime, replacement order,
|
||||
and side-bag placement are regression-tested. Owner check is in
|
||||
`docs/research/2026-08-26-combined-client-parity-gate.md`.
|
||||
|
||||
## #444 — Vendor alternate-currency balance stays stale after a successful purchase
|
||||
|
||||
**Status:** DONE — OWNER-ACCEPTED 2026-08-26 in the combined client-parity gate.
|
||||
**Component:** vendor UI / alternate-currency purchase refresh.
|
||||
|
||||
At a vendor that accepts an alternate currency (observed with Colosseum
|
||||
Coins), the purchase succeeds and the server removes the currency, but the
|
||||
vendor window continues to show the pre-purchase holding. Example: the purse
|
||||
line says "You have 10 Colosseum Coins" before the purchase and still says 10
|
||||
afterward. The displayed holding should update immediately after the
|
||||
authoritative purchase/inventory update.
|
||||
|
||||
**Likely seam:** `VendorUiController.BuildPurseText` and `BuildCostText` read
|
||||
the vendor-open snapshot `VendorShopProfile.AlternateCurrencyAmount`
|
||||
directly. `OnObjectMoneyChanged` repaints the text, but the repainted value is
|
||||
still that latched profile amount rather than the live alternate-currency
|
||||
holding (or retail's `trade_num - m_last_sale` equivalent). Add a connected
|
||||
regression for purchase success followed by the refreshed purse and item-cost
|
||||
text; cover both the Buying tab and Items tab.
|
||||
|
||||
**2026-08-26 fix:** alternate-currency displays and Buy All affordability now
|
||||
prefer the authoritative sum of player-owned currency stacks. On a successful
|
||||
Buy/Buy All dispatch, retail's `m_last_sale` subtraction updates the Items and
|
||||
Buying/Selling purse text immediately; the next matching currency add/update/
|
||||
move/remove clears that optimistic subtraction and repaints from canonical
|
||||
inventory. The vendor snapshot remains only the pre-observation fallback.
|
||||
Automated coverage pins the immediate 10→8 display and the subsequent
|
||||
authoritative 8→8 reconciliation. Owner check is in
|
||||
`docs/research/2026-08-26-combined-client-parity-gate.md`.
|
||||
|
||||
## #443 — Examination/paperdoll private viewport: doll appears only after a delay on first open (was: "renders nothing")
|
||||
|
||||
**Status:** FIXED / OWNER-ACCEPTED 2026-08-25.
|
||||
**Status:** FIXED / CONNECTED LIVE RE-GATE PASSED 2026-08-26 — awaiting owner
|
||||
acceptance. Reopened after the owner again observed a missing paperdoll that
|
||||
appeared only after waiting. Previously marked FIXED / OWNER-ACCEPTED
|
||||
2026-08-25. The recurrence exposed two remaining gaps: palette/clothing texture
|
||||
composites could still be pending when the private pass cleared and published
|
||||
its target, and Vulkan's two concurrently recorded frames reused that same
|
||||
offscreen image as both a color attachment and a retained-UI sampled texture.
|
||||
The 2026-08-26 combined client-parity gate passed every #444–#450 row on the
|
||||
same Release binary while the paperdoll remained missing, confirming #443 is
|
||||
an isolated private-viewport defect rather than an inventory transaction,
|
||||
input, relog, chat, combat-text, or vendor failure.
|
||||
**Component:** private entity viewports (examination clone, inventory
|
||||
paperdoll — shared `PrivateEntityViewportRenderer`).
|
||||
**Filed:** 2026-08-25, AS-GF1 gate-fix session. **Narrowed same day at the
|
||||
|
|
@ -59,6 +301,25 @@ and new residency tests pass 30/30; the App hermetic lane passes 6,358/6,358.
|
|||
The owner then live-verified repeated inventory and monster/player assessment
|
||||
opens against the local ACE test server: "Good. works."
|
||||
|
||||
**2026-08-26 recurrence fix:** the shared renderer now advances and gates the
|
||||
complete private-entity resource set — mesh upload plus original, palette and
|
||||
clothing-composite textures — before allocating, clearing, or publishing a
|
||||
new viewport target. It therefore keeps the previous completed image (or the
|
||||
authored panel art on first use) until the new doll is actually drawable.
|
||||
`PaperdollFramePresenter` also builds, redresses and prewarms the inventory
|
||||
doll while its tab is hidden, so opening the tab no longer starts residency
|
||||
work from zero. The decisive intermittent fault was the shared render target:
|
||||
one Vulkan flight slot could clear/write it while the other still sampled it.
|
||||
`PrivateEntityViewportRenderer` now owns a bounded target, sampler and texture
|
||||
slot per encountered GPU flight slot, and publishes the current frame's exact
|
||||
handle. The same correction covers inventory paperdoll, creature appraisal and
|
||||
character-creation preview viewports. Temporary flight-slot colors proved both
|
||||
slots render the complete textured doll; all probes were then removed. The
|
||||
clean Release client passed first open plus two repeated close/reopen cycles on
|
||||
the local ACE server with no missing frame and no runtime error. Focused App,
|
||||
Runtime and input tests pass 384/384, including the byte-exact production
|
||||
SPIR-V oracle; the Release solution builds with zero warnings/errors.
|
||||
|
||||
Owner report at the Campaign AS connected gate: the animated 3-D paperdoll
|
||||
in the examination window (LayoutDesc `0x2100006B` element `0x10000148`)
|
||||
worked correctly at baseline `974fe88a` (praised the same session) and was
|
||||
|
|
@ -4004,8 +4265,7 @@ switching stays #376.
|
|||
|
||||
## #373 — Configure Keyboard: DAT `ActionMap.ConflictingMaps` not consulted — the combat cluster raises false conflict prompts
|
||||
|
||||
**Status:** OPEN — filed 2026-08-11 at Campaign OP slice OP8's re-review
|
||||
round 2 (R1's scope boundary).
|
||||
**Status:** DONE 2026-08-26 — fixed as the first #446 keyboard-parity slice.
|
||||
|
||||
The DAT ActionMap (DID `0x26000000`) carries a `ConflictingMaps` table
|
||||
retail's `UIOption_ActionKeyMap` consults when deciding whether two rows
|
||||
|
|
@ -4022,17 +4282,22 @@ new action to one) prompts "overwrite N bindings?" where retail prompts
|
|||
for fewer or none. Accepting the prompt then strips retail-default
|
||||
bindings that should have survived.
|
||||
|
||||
The OP8 round-2 fix already excluded store-only rows (`MappedAction is
|
||||
null`) from the conflict universe — those cannot collide because they
|
||||
never reach the InputDispatcher — but retail-mapped cross-context
|
||||
sharing needs the real table. **Fix:** parse `ConflictingMaps` in
|
||||
The OP8 round-2 fix originally excluded store-only rows (`MappedAction is
|
||||
null`) from the conflict universe. Campaign KB later mapped and enabled every
|
||||
one of the 306 installed rows, eliminating that tier; retail cross-context
|
||||
sharing still needs the real table. **Fix:** parse `ConflictingMaps` in
|
||||
`RetailActionMap` (the reader already round-trips the field —
|
||||
`RetailActionMapReaderTests` constructs it), and make `FindConflicts`
|
||||
consult it: two rows sharing a chord conflict only if their contexts'
|
||||
ConflictingMaps entries say so. Conformance-test against the combat
|
||||
cluster's authored defaults (five keys, multi-row each, zero prompts on
|
||||
a no-op rebind). The gate script's §OP8 warns the user off treating the
|
||||
false prompts as new breakage until this lands.
|
||||
a no-op rebind).
|
||||
|
||||
**Fix landed:** `RetailActionMapSnapshot` now owns the copied DAT conflict
|
||||
sets and `KeyboardConfigController.FindConflicts` consults them before
|
||||
offering reassignment. Hermetic tests pin permitted cross-combat sharing and
|
||||
declared cross-map conflicts; an installed-DAT test pins that melee, missile,
|
||||
and magic are pairwise non-conflicting. Same-context conflicts remain active.
|
||||
|
||||
## #372 — Options panel: Character/Chat/Config tabs render BLANK on screen and most Gameplay buttons do nothing (connected-gate failure)
|
||||
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -94,8 +94,9 @@ DAT-authored values.
|
|||
register row for the ACE-sourced 2013-unverifiable mapping.
|
||||
- **D4 — Configure Keyboard is the campaign's rebind screen** (it is the
|
||||
ONLY rebind screen — D1). Port `gmKeyboardUI`'s shape and DAT ActionMap
|
||||
data (lane D Option C) but persist to `keybinds.json`; retail `.keymap`
|
||||
file interchange is a register-row deferral.
|
||||
data (lane D Option C). **Superseded 2026-08-26 by #446:** named retail
|
||||
`.keymap` Load File / Save As/startup/shutdown persistence now ships;
|
||||
`keybinds.json` remains only the host-command compatibility mirror.
|
||||
- **D5 — dead-endpoint buttons short-circuit to their own retail failure
|
||||
strings.** Urgent Assistance / Report Abuse open a defunct
|
||||
`support.turbine.com` URL in retail; acdream skips the browser launch and
|
||||
|
|
@ -360,8 +361,8 @@ modal capture; right-click erases; N-way cross-map conflicts + the
|
|||
non-user-bindable refusal per lane D §5; Save/Cancel; Reset-to-defaults
|
||||
reloads the DAT maps. Persistence: `keybinds.json` (D4).
|
||||
|
||||
**Register rows:** `.keymap` file interchange not implemented (D4); any
|
||||
retail column/behaviour consciously narrowed.
|
||||
**Register rows:** any retail column/behaviour consciously narrowed. The
|
||||
former D4 `.keymap` deferral was retired by #446 on 2026-08-26.
|
||||
|
||||
**Gate:** connected — rebind a movement key, conflict prompt on a taken
|
||||
chord, persistence across relaunch, reset restores retail defaults.
|
||||
|
|
@ -394,7 +395,8 @@ chord, persistence across relaunch, reset restores retail defaults.
|
|||
`2026-08-09-chat-retail-window-shell.md` §6.3's register row.
|
||||
- A pre-world character-select flow (D6 adapts; its register row carries
|
||||
the future work).
|
||||
- Retail `.keymap` file read/write (D4 register row).
|
||||
- None for retail `.keymap` file read/write; #446 implemented it on
|
||||
2026-08-26 and retired AP-202.
|
||||
- The `0x21000017` docked `gmPanelUI` host variant — acdream ships the
|
||||
floating host only (register row in OP3 if the review deems it a
|
||||
divergence; retail exposes both).
|
||||
|
|
|
|||
|
|
@ -337,44 +337,18 @@ nothing wrong" surprise, matching the register's existing framing of the
|
|||
|
||||
### B.2 — Double-click
|
||||
|
||||
**No dedicated double-click-to-buy mechanism was found for vendor shop
|
||||
items.** Evidence, not absence-of-search:
|
||||
**Corrected 2026-08-26:** the original symbol-name search missed the real
|
||||
mechanism. Retail handles this inside the general
|
||||
`gmVendorUI::HandleMousePresses @ 0x004C40D0`; it does not require a separately
|
||||
named `CheckForDoubleClick` function. In the Items-list branch, the retail
|
||||
double-click condition directly calls `gmVendorUI::BuySingleItem` for the
|
||||
clicked row. The same function also owns staged Buying/Selling removal and
|
||||
their `ClientLocal` feedback.
|
||||
|
||||
- `gmVendorUI::ListenToElementMessage` (`pc:204260-204309`, full function
|
||||
read) dispatches on message id 1 (button click →
|
||||
`HandleButtonClicks`), 7 (dropdown selection change), `0x2c` (page
|
||||
change), `0x15` (drop release), and `0x1c` (routes to
|
||||
`HandleMousePresses` only when `m_itemsUI != 0`) — there is no distinct
|
||||
"double-click" message id handled at the panel level.
|
||||
- The base list class `UIElement_ItemList` (every method enumerated via
|
||||
`docs/research/named-retail/symbols.json`, ~50 symbols) has
|
||||
`HandleSingleSelection`, `HandleTargetedUseLeftClick`,
|
||||
`ItemList_SetSelectedItem`, `ItemList_OpenContainer` (for double-clicking
|
||||
a CONTAINER item specifically — opening it, not buying), but **no
|
||||
generic double-click handler** and no vendor-specific one either.
|
||||
- Other retail panels DO have an explicit, separately-named double-click
|
||||
handler when the mechanism exists — e.g. `gmContractsUI::CheckForDoubleClick`
|
||||
(`0x00497A10`), `gmPageListUI::CheckForDoubleClick` (`0x00493140`). No
|
||||
`gmVendorUI::CheckForDoubleClick` or `VendorItemsUI::CheckForDoubleClick`
|
||||
symbol exists in the 18,366-function named table.
|
||||
|
||||
**Conclusion:** retail's confirmed vendor-item interaction model is
|
||||
single-click-to-select (→ drives the global `ACCWeenieObject::selectedID`,
|
||||
B.3 below) plus an explicit Buy/Add button press. There is no evidence
|
||||
retail supports double-click-to-buy on the shop list. The user's
|
||||
expectation likely carries over from inventory-panel muscle memory
|
||||
(double-click = use/equip elsewhere in retail) — but the vendor "Items"
|
||||
list is not that panel. **This is flagged as an open question for the
|
||||
contract, not resolved unilaterally**: per the project's
|
||||
no-invented-mechanisms discipline, do not silently add a double-click-buy
|
||||
shortcut and call it retail-faithful. The retail-faithful, fully-evidenced
|
||||
fix for "double-click does nothing" is: (a) make single-click meaningfully
|
||||
select (today it only sets a private field with no visible effect — see
|
||||
B.3), and (b) make the Buy button actually work. If the user still wants a
|
||||
double-click shortcut after seeing single-click+Buy work, that is a
|
||||
deliberate, flagged acdream UX addition on top of retail, not a retail port
|
||||
— call it out explicitly in the commit/register the way AP-116
|
||||
(Particle Range) or similar user-directed deviations are recorded.
|
||||
**Conclusion:** browse-row double-click-to-buy is verbatim retail behavior.
|
||||
The acdream binding is a port, not an optional modernization. The previous
|
||||
absence-of-symbol inference was false and is superseded by the direct function
|
||||
body.
|
||||
|
||||
### B.3 — The quantity slider
|
||||
|
||||
|
|
@ -735,10 +709,9 @@ concretely unblocked by the one before it; skipping ahead reproduces the
|
|||
polish, not correctness — the server is authoritative either way) and
|
||||
file it as a fast follow-up if the user notices the round-trip lag on a
|
||||
refused purchase.
|
||||
2. **Double-click** — no retail mechanism found (B.2). Ask the user
|
||||
directly whether they want a deliberate acdream-only double-click
|
||||
shortcut once single-click-select + Buy-button-works is verified live,
|
||||
rather than assuming yes and inventing behavior.
|
||||
2. **Double-click — RESOLVED 2026-08-26.** Retail's
|
||||
`gmVendorUI::HandleMousePresses @ 0x004C40D0` directly buys a browse row on
|
||||
double-click. Keep this behavior and its staged-row siblings.
|
||||
3. **Where does the vendor-owned split-exempt-mask predicate live** — C.1's
|
||||
design question: fold into `SelectedObjectController` directly (it
|
||||
already owns the seeding logic, would need a `Func<uint,bool>
|
||||
|
|
|
|||
|
|
@ -5,6 +5,13 @@ Research lane D of the settings-track campaign
|
|||
questions **Q5** (Configure Keyboard: retail's keymap UI + storage) and
|
||||
**Q6** (what every Gameplay Options tab button does).
|
||||
|
||||
> **2026-08-26 implementation addendum:** the report below describes the
|
||||
> pre-OP8 state and its design choices at that date. acdream has now shipped
|
||||
> Option C end to end: all 306 installed-DAT rows, retail conflicts/capture,
|
||||
> and real named `.keymap` Load File / Save As/startup/shutdown persistence.
|
||||
> See `docs/research/2026-08-26-retail-keyboard-routing-audit.md`; AP-202 is
|
||||
> retired.
|
||||
|
||||
**Report only.** No repo code was changed. Every retail claim below carries
|
||||
a named symbol + address from the Sept 2013 EoR PDB-paired build. The
|
||||
PDB/binary pairing was verified first:
|
||||
|
|
|
|||
131
docs/research/2026-08-26-combined-client-parity-gate.md
Normal file
131
docs/research/2026-08-26-combined-client-parity-gate.md
Normal file
|
|
@ -0,0 +1,131 @@
|
|||
# Combined client parity owner gate
|
||||
|
||||
**Date prepared:** 2026-08-26
|
||||
|
||||
**Run:** OWNER-COMPLETE 2026-08-26 on one exact Release binary.
|
||||
|
||||
**Scope:** #443–#450 plus the complete inventory/vendor interaction audit.
|
||||
|
||||
**Outcome:** Sections A–C and E–F passed, including the mid-drag cursor-icon
|
||||
re-test added during the round. Issues #444–#450 are owner-accepted. Section D
|
||||
failed: the private paperdoll remained missing, so #443 stays open as the only
|
||||
surviving defect. Tested executable SHA-256:
|
||||
`173989F3C85C05C0746D628CDF9C6194F6A5E3EFD4597806BF83417483C37B42`.
|
||||
|
||||
Keep the client log for the whole run and take a screenshot for any visual or
|
||||
text mismatch. For every refused item action, record the cursor color, exact
|
||||
SpewBox line, and whether the item visibly moved before the refusal.
|
||||
|
||||
## A. Keyboard, camera, combat input, and relog — #446/#450
|
||||
|
||||
1. In Configure Keyboard, bind Move Forward to bare Shift while Toggle
|
||||
Walk/Run already owns it. Verify the retail conflict dialog appears and the
|
||||
chosen resolution is honored. Repeat with a known allowed shared chord.
|
||||
2. Apply a changed binding, close/reopen Options, then restart the client.
|
||||
Verify it survives. Test Revert, Defaults, Cancel, and Load/Save `.keymap`.
|
||||
3. Press regular Enter: chat input must focus. Press keypad Enter outside chat:
|
||||
it must perform only its configured camera action and must not focus chat.
|
||||
4. Press bare Escape through the retail ladder: cancel target/focus/selection
|
||||
first, then toggle Gameplay Options. It must never enter a developer orbit
|
||||
or bird's-eye mode. Preserve the approved mouse-wheel zoom range.
|
||||
5. Hold End, Page Down, or Delete in melee mode. The bar must charge while the
|
||||
key is held and attack only on release, using the selected height.
|
||||
6. Shift+Escape to character selection, immediately re-enter, and verify the
|
||||
destination loads and exits portal space. Repeat twice in one process.
|
||||
|
||||
## B. Chat and combat text — #447/#448
|
||||
|
||||
1. Run `@acecommands`. Every non-empty server line must be visible; an
|
||||
oversized response must retain the newest complete lines, not blank the
|
||||
transcript.
|
||||
2. Run `@acehelp acecommands` and send ordinary chat afterward. Verify normal
|
||||
text, filtering, and scrolling remain intact.
|
||||
3. Land ordinary and critical melee hits. Outgoing lines must match retail
|
||||
wording and punctuation and contain no acdream-added percentage.
|
||||
|
||||
## C. Selection, use, containers, movement, and splitting — #445/#449
|
||||
|
||||
Use a normal item, unusable item, wearable, weapon, two mergeable stacks, a
|
||||
Pyreal stack, one side pack, a full main pack, a full side pack, an open
|
||||
external container, and a creature/player target.
|
||||
|
||||
1. Single-click and right-click inventory, side-pack, external-container, and
|
||||
paperdoll items. Verify one global selection, stable highlight, status text,
|
||||
and right-click examination.
|
||||
2. Select an owned Pyreal stack. The toolbar must read
|
||||
`<stack> <appropriate name> (of <total carried Pyreals>)`, with no comma
|
||||
insertion added by acdream.
|
||||
3. Single- and double-click a carried side pack. It must open on press, issue
|
||||
no generic item-use request, and remain stably selected.
|
||||
4. Double-click usable, unusable, wearable, and wieldable items. Verify one
|
||||
action. Any local refusal must appear once in SpewBox, not in normal chat.
|
||||
5. Move a full item between main pack and side pack. Before acknowledgement,
|
||||
the source must remain canonical with retail's waiting/ghost presentation;
|
||||
after success it appears only at the destination. Force one rejection and
|
||||
verify no duplicate, disappearance, or speculative capacity change.
|
||||
While holding the item under the cursor, move across the inventory and wait
|
||||
through several ordinary object updates: the cursor icon must remain visible
|
||||
until release. This re-gates the mid-drag procedural-refresh fix found during
|
||||
the first 2026-08-26 owner pass.
|
||||
6. Fill the main pack, observe a rejected move, drop one loose item, then move
|
||||
an item from a side pack into the freed slot immediately. It must accept
|
||||
without reopening the inventory window (#449).
|
||||
7. From a stack of 10, select 2 and split into an empty main-pack slot, an open
|
||||
side pack, and an external container. Each successful result must be 8+2.
|
||||
8. Merge full and partial stacks. Verify selected quantity, target selection,
|
||||
target-cap clamp, source remainder, and exact refusal text for a full target.
|
||||
9. Drop full and partial stacks to the world, then pick them up. Verify pending
|
||||
visuals, authoritative commit, failure cleanup, and no duplicate object.
|
||||
10. Give a full and partial stack to a creature; drag onto another player and
|
||||
verify secure-trade routing. Hover rejection must stay silent; release
|
||||
rejection must print the exact ClientLocal reason.
|
||||
11. Equip once by double-click and once by paperdoll drag. Test a clothing
|
||||
conflict and weapon replacement. Canonical inventory/paperdoll ownership
|
||||
must not change before the authoritative response.
|
||||
|
||||
## D. Private paperdoll viewport — #443
|
||||
|
||||
1. From a fresh process, open inventory and assess one monster and one player.
|
||||
2. The animated doll must be present on first open, not appear only after a
|
||||
delay. Close/reopen each view several times and change equipment once.
|
||||
3. Record #443 independently if the viewport is late or missing even when the
|
||||
underlying equip/inventory transaction is correct.
|
||||
|
||||
## E. Vendor parity and alternate currency — #444/#445
|
||||
|
||||
1. On the browse list, single-click selects, right-click examines, and
|
||||
double-click buys exactly one/current-slider unit through the normal retail
|
||||
purchase path.
|
||||
2. Add a stack quantity greater than one to Buying. Double-click its staged
|
||||
row: remove exactly one unit and print
|
||||
`Removing <name> from shopping list` once in SpewBox.
|
||||
3. Stage an owned item in Selling. Right-click examines it; double-click
|
||||
removes the whole staged entry and prints the same removal form.
|
||||
4. Drag a staged Selling row: it must unstage. Repeat after selecting only part
|
||||
of its stack: SpewBox must print
|
||||
`You cannot split items from this panel` and the slider must reset to max.
|
||||
5. From an owned stack of 10, select 2 and drag into Selling. Verify the
|
||||
temporary row resolves to the new authoritative stack of 2, Sell All sells
|
||||
exactly 2, and 8 remain (#445).
|
||||
6. At an alternate-currency vendor, note holdings in the Items cost sentence
|
||||
and Buying/Selling purse lines. Buy once: every visible holding must decrease
|
||||
immediately and remain correct after authoritative inventory refresh,
|
||||
tab changes, and vendor reopen (#444).
|
||||
7. Buy All once with enough currency and once without enough. The first uses
|
||||
the refreshed balance; the second prints retail's insufficient-funds notice
|
||||
and sends no purchase.
|
||||
|
||||
## F. Re-entrant and lifecycle stress
|
||||
|
||||
1. Change selection during a pending split, close an external container during
|
||||
a pending move, and retry immediately after a refusal.
|
||||
2. Attempt a second inventory operation while one request is pending. Verify a
|
||||
clean retail refusal/no-op, never duplicated wire action or stuck busy state.
|
||||
3. Log out or portal with a recently completed interaction, re-enter, and
|
||||
verify pending projections and the request ledger converge to zero.
|
||||
|
||||
## Pass rule
|
||||
|
||||
The pass rule was satisfied for #444–#450 on the exact binary recorded above.
|
||||
#443 remains open by itself because the transaction rows passed and the failure
|
||||
was confined to private viewport residency.
|
||||
43
docs/research/2026-08-26-issues-444-445-447-test-script.md
Normal file
43
docs/research/2026-08-26-issues-444-445-447-test-script.md
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
# Issues #444, #445, #447 — consolidated owner gate
|
||||
|
||||
Run these checks together on the next connected test build. They deliberately
|
||||
require no special probes; record the client log and one screenshot per
|
||||
section. If a split fails, also record the exact visible error text.
|
||||
|
||||
## #444 — alternate-currency vendor balance
|
||||
|
||||
1. Open a vendor that accepts an alternate currency and note the amount shown
|
||||
in both the Items cost sentence and the Buying-tab purse line.
|
||||
2. Buy one item.
|
||||
3. Confirm both visible amounts decrease immediately and remain correct after
|
||||
the server refresh. Close/reopen the tab and vendor and confirm the amount
|
||||
does not bounce back to the old snapshot.
|
||||
4. Buy All once with enough currency, then once without enough. Confirm the
|
||||
first uses the refreshed holding and the second shows retail's insufficient-
|
||||
money notice without sending a purchase.
|
||||
|
||||
## #445 — inventory and vendor partial stack splits
|
||||
|
||||
1. In the main pack, select a stack of 10, set the slider to 2, and drag it to
|
||||
an empty main-pack slot while at least one side bag is equipped.
|
||||
2. Confirm the source becomes 8 and a new stack of exactly 2 appears at the
|
||||
chosen loose-item position; no error should appear.
|
||||
3. Repeat into an open side bag and confirm the same 8+2 result.
|
||||
4. Reset to a stack of 10, select 2, and drag it onto the vendor Selling list.
|
||||
Confirm the client prints `Splitting the <name> before selling them`, then
|
||||
the staged row resolves to the new stack of 2 rather than the source stack.
|
||||
5. Press Sell All and confirm exactly 2 are sold and 8 remain.
|
||||
|
||||
## #447 — `@acecommands` multiline response
|
||||
|
||||
1. Run `@acecommands` on the test account.
|
||||
2. Confirm command text is visible, consecutive server lines are readable,
|
||||
and there is no screen of blank chat rows.
|
||||
3. Scroll through the retained result. A response beyond retail's 10,000-
|
||||
character transcript cap should retain the newest complete command lines
|
||||
instead of blanking the entire response.
|
||||
4. Run `@acehelp acecommands` and one ordinary chat command afterward; confirm
|
||||
their text and normal chat presentation remain intact.
|
||||
|
||||
Pass all three sections on one exact binary, then mark #444/#445/#447
|
||||
owner-accepted together.
|
||||
745
docs/research/2026-08-26-retail-inventory-interaction-audit.md
Normal file
745
docs/research/2026-08-26-retail-inventory-interaction-audit.md
Normal file
|
|
@ -0,0 +1,745 @@
|
|||
# Retail inventory interaction audit
|
||||
|
||||
**Date:** 2026-08-26
|
||||
|
||||
**Scope:** Selection, status text, single/double/right click, drag/drop,
|
||||
container movement, ground pickup/drop, equipping, stack splitting, vendor
|
||||
staging, failure feedback, and SpewBox routing.
|
||||
|
||||
**Change policy:** Audit followed by implementation in the same worktree.
|
||||
|
||||
**Source snapshot:** `0c699240`, plus the already-present working-tree fixes for
|
||||
#444, #445, #446, #447, and #449. Those fixes are assessed as found; this
|
||||
report does not claim that they have been committed or user-accepted.
|
||||
|
||||
## Implementation closeout — 2026-08-26
|
||||
|
||||
Slices 1–4 below are implemented and automated-test covered. Gameplay
|
||||
refusals now use the `ClientLocal` SpewBox route; move/wield failure kinds are
|
||||
complete; full move/drop/wield are request-first with pending projections;
|
||||
owned-container and vendor-row mouse behavior follows the named retail
|
||||
handlers; hover and release share one side-effect-free legality policy; and
|
||||
local item-policy wording is composed from retail's exact literals.
|
||||
|
||||
The last toolbar uncertainty is also resolved. Raw retail bytes at
|
||||
`gmToolbarUI::HandleSelectionChanged @ 0x004BF4EF` push format literal
|
||||
`0x007B4748`, which decodes to `%d %hs (of %d)`. The owned Pyreal-stack branch
|
||||
now renders that exact stack/name/total shape. The final AutoWield fallback was
|
||||
also corrected: retail does not print the invented “That slot is already in
|
||||
use”; with automatic unblocking enabled it moves the preferred occupied-slot
|
||||
item to the backpack, waits for the authoritative move, then retries the
|
||||
wield. Slice 5 remains deliberately deferred as the single combined connected
|
||||
owner gate.
|
||||
|
||||
## Audited root causes (now fixed)
|
||||
|
||||
The inventory implementation was not missing one isolated rule. Most individual
|
||||
operations existed and used the correct wire messages, but three seams made the
|
||||
whole experience feel intermittent:
|
||||
|
||||
1. **Some retail-local refusal text was routed to a dead production callback.**
|
||||
`ItemInteractionController` and `AutoWieldController` used a `toast` callback
|
||||
for a substantial class of local rejections while `GameWindow` supplied
|
||||
`null`. Retail sends these messages to the `ClientLocal` channel, which is
|
||||
the SpewBox in acdream. The result was a real silent-failure class, not merely
|
||||
different wording.
|
||||
2. **Full moves, world drops, and wield operations mutated canonical inventory
|
||||
state before the server accepted them.** Retail normally leaves the source
|
||||
canonical object in place, adds a waiting/ghost projection at the intended
|
||||
destination, and commits only after the authoritative object update. The
|
||||
old optimistic mutation was reversible, but selection, capacity,
|
||||
paperdoll, vendor, and other observers could see a transient state that never
|
||||
existed on the server. This was the largest structural flakiness risk.
|
||||
3. **Several list-specific mouse behaviors did not match retail.** In
|
||||
particular, staged vendor rows could not be double-clicked or dragged to
|
||||
remove them, staged rows lacked right-click examine, and owned side-pack
|
||||
double-click/open ordering differed from retail.
|
||||
|
||||
The wire builders, global selection/split model, merge-first rule, request gate,
|
||||
most right-click examine paths, normal item double-click use/equip, external
|
||||
container pickup, paperdoll placement validation, and the newly repaired
|
||||
vendor-split/main-pack-capacity paths are broadly aligned with retail.
|
||||
|
||||
The implementation was executed in this order:
|
||||
|
||||
1. Route every local item refusal through `ClientLocal`/SpewBox.
|
||||
2. Replace canonical optimistic movement with retail-style pending projections.
|
||||
3. Close the vendor staged-row and owned-container input differences.
|
||||
4. Deepen hover/drop legality and finish exact status/failure text parity.
|
||||
5. Run one connected interaction matrix across inventory, paperdoll, ground,
|
||||
external containers, and vendors.
|
||||
|
||||
## Method and evidence standard
|
||||
|
||||
This audit used four evidence layers:
|
||||
|
||||
- The September 2013 named retail pseudo-C under
|
||||
`docs/research/named-retail/acclient_2013_pseudo_c.txt`, searched by named
|
||||
class and method before relying on older address-only material.
|
||||
- Existing focused retail notes under `docs/research/`, especially the item,
|
||||
drag, give, world-drop, use/autowear, and vendor investigations.
|
||||
- The current production controllers, Runtime owners, UI input dispatch, wire
|
||||
request builders, and communication routing.
|
||||
- Existing focused tests, used to distinguish implemented intent from behavior
|
||||
that is not currently protected.
|
||||
|
||||
Verdicts in this report mean:
|
||||
|
||||
- **Match:** the important retail behavior and ownership rule are present.
|
||||
- **Partial:** the common path matches, but a retail branch, presentation rule,
|
||||
or failure path is absent.
|
||||
- **Mismatch:** direct retail evidence contradicts the current behavior.
|
||||
- **Risk:** the mechanism differs in a way likely to produce transient or race
|
||||
defects, but this audit does not assert a particular live symptom without a
|
||||
connected reproduction.
|
||||
- **Gate pending:** a code fix exists in the working tree and has automated
|
||||
coverage, but the owner has not yet accepted the live behavior.
|
||||
|
||||
## Retail reference model
|
||||
|
||||
### One selected object and one split quantity
|
||||
|
||||
Retail has a client-global selected object. Clicking an item selects it;
|
||||
right-click first selects it and then examines it; beginning a drag selects it
|
||||
if it was not already selected. The toolbar observes that global selection and
|
||||
shows the name, stack quantity, and split controls.
|
||||
|
||||
The split quantity is also global and applies only when the dragged/requested
|
||||
object is the selected object. An unselected stack always means the full stack.
|
||||
Changing selection resets/reseeds the split amount. Vendor-owned selected
|
||||
stacks use a different initial amount from normal owned stacks.
|
||||
|
||||
Primary anchors:
|
||||
|
||||
- `UIElement_ItemList::ListenToElementMessage` at `0x004E4D50`
|
||||
- `UIElement_ItemList::BeginDrag` at `0x004E32D0`
|
||||
- `gmToolbarUI::HandleSelectionChanged` at `0x004BF380`
|
||||
- `ItemHolder::GetObjectSplitSize` in the named retail pseudo-C
|
||||
|
||||
### Mouse-down establishes intent; click completion performs list action
|
||||
|
||||
For a retail item-list entry, left press first gives target mode a chance to
|
||||
consume the object. Otherwise it selects the object. A container-list entry
|
||||
also opens that child container and updates its open indicator in this same
|
||||
item-list message path.
|
||||
|
||||
Right press selects and examines. Double-click invokes generic `UseObject` for
|
||||
ordinary list items, but the generic double-use path is suppressed for an
|
||||
owned `containerList` entry. The ground/external root is explicitly allowed.
|
||||
|
||||
This distinction matters: a side pack is opened as a container, not opened and
|
||||
then generically used as an ordinary item on the second click.
|
||||
|
||||
### Dragging is a request with pending presentation
|
||||
|
||||
Beginning a physical-item drag produces a source waiting/ghost state. Vendor,
|
||||
salvage, and shortcut lists are special list types and do not use the same
|
||||
physical-source waiting ghost.
|
||||
|
||||
Hover is advisory and silent. Release reruns legality with feedback enabled.
|
||||
For a normal container move, retail retains the canonical source ownership and
|
||||
adds a pending destination projection. The server's authoritative object update
|
||||
commits the move. Rejection removes the pending projection and prints the local
|
||||
failure. This same general principle appears in world placement and split-to-
|
||||
world handling.
|
||||
|
||||
Primary anchors:
|
||||
|
||||
- `UIElement_ItemList::BeginDrag` at `0x004E32D0`
|
||||
- `UIElement_ItemList::DragOver` at `0x004E3400`
|
||||
- `UIElement_ItemList::AcceptDragObject` at `0x004E4250`
|
||||
- `UIElement_ItemList::HandleDropRelease` at `0x004E4790`
|
||||
- `ItemHolder::AttemptToPlaceInContainer_IsItemLegal` at `0x005870C0`
|
||||
- `ItemHolder::AttemptToPlaceInContainer_IsContainerLegal` at `0x005879B0`
|
||||
- `ItemHolder::WillItemFitInContainer` at `0x00587D60`
|
||||
- `ItemHolder::IsDragIntoContainerAttemptLegal` at `0x00587E90`
|
||||
|
||||
### Drop target dispatch is ordered
|
||||
|
||||
Retail's three-dimensional drop/give dispatcher follows this practical order:
|
||||
|
||||
1. Require an owned, movable source that is not currently in trade.
|
||||
2. Dropping on self means the main backpack.
|
||||
3. Target zero means ground placement or split-to-world.
|
||||
4. Try stack merge before treating the target as a container.
|
||||
5. A player target opens/routes through secure trade.
|
||||
6. A creature target uses give-item behavior.
|
||||
7. A container target must be open, unlocked, and legal.
|
||||
8. Vendor lists use their own staging rules.
|
||||
9. Otherwise resolve as a ground placement or refuse it.
|
||||
|
||||
`AttemptMerge` uses the selected split amount, clamps to target capacity, sends
|
||||
the merge request, and selects the target stack. Give-item is request-only; it
|
||||
does not optimistically remove the source from canonical inventory.
|
||||
|
||||
Primary anchors:
|
||||
|
||||
- `ItemHolder::AttemptMerge` at `0x005878F0`
|
||||
- `ItemHolder::AttemptPlaceIn3D` at `0x00588600`
|
||||
- `docs/research/2026-07-13-retail-give-item-pseudocode.md`
|
||||
- `docs/research/2026-07-26-retail-inventory-placement-and-world-drop-pseudocode.md`
|
||||
|
||||
### Use and equip
|
||||
|
||||
Generic double-click use passes through `ItemHolder::DetermineUseResult` and
|
||||
`ItemHolder::UseObject`, with a short use throttle. The item is classified as
|
||||
direct-use, targeted-use, pickup, equip/autowear, trade, salvage, or game use.
|
||||
Retail locally refuses invalid states and prints a `ClientLocal` message.
|
||||
|
||||
Paperdoll 3D clicks and discrete equipment-slot lists share the same global
|
||||
selection/examine model. Dropping on a paperdoll location validates the exact
|
||||
location, then chooses auto-wear or auto-wield behavior. Clothing overlap can
|
||||
be rejected locally; weapon replacement has different rules.
|
||||
|
||||
Primary anchors:
|
||||
|
||||
- `ItemHolder::DetermineUseResult` at `0x00588460`
|
||||
- `ItemHolder::UseObject` at `0x00588A80`
|
||||
- `CPlayerSystem::UsingItem` at `0x00562F70`
|
||||
- `gmPaperDollUI::ListenToElementMessage` at `0x004A5C30`
|
||||
- `gmPaperDollUI::AcceptDragObject` at `0x004A3B10`
|
||||
- `gmPaperDollUI::AcceptPaperDollDragObject` at `0x004A4A70`
|
||||
- `docs/research/2026-07-23-retail-item-use-and-autowear-pseudocode.md`
|
||||
|
||||
### Vendor rows are active item lists
|
||||
|
||||
Direct named-retail evidence establishes these behaviors:
|
||||
|
||||
- Double-clicking a vendor browse row buys one item.
|
||||
- Double-clicking a staged buying row removes it and prints
|
||||
“Removing %s from shopping list” through `ClientLocal`.
|
||||
- Double-clicking a staged selling row removes it, clears its sell state, and
|
||||
prints the same form of message.
|
||||
- Dragging an already-staged selling row removes it from the staged list.
|
||||
- If a partial split is selected while dragging a staged selling row, retail
|
||||
refuses to split that row, prints “You cannot split items from this panel”,
|
||||
and resets the split control to the stack maximum.
|
||||
- A new partial-stack drag into the Selling list sends a split request, creates
|
||||
a temporary staged row, and replaces that row when the new matching object
|
||||
arrives.
|
||||
- Hover rejection is silent; release rejection prints to `ClientLocal`.
|
||||
|
||||
Primary anchors:
|
||||
|
||||
- `gmVendorUI::HandleMousePresses` at `0x004C40D0`
|
||||
- `gmVendorUI::RecvNotice_ItemListBeginDrag` at `0x004C4380`
|
||||
- `VendorSellUI::DragItemAcceptable` at `0x004C20C0`
|
||||
- `VendorSellUI::AcceptDragObject` at `0x004C4F00`
|
||||
- `VendorSellUI::ItemAttributesChanged` at `0x004C3FD0`
|
||||
|
||||
This corrects an older project research conclusion: browse-row double-click
|
||||
buy is retail behavior. It is not an acdream modernization.
|
||||
|
||||
### Feedback destination
|
||||
|
||||
Retail item-policy and request-failure messages are sent on the local client
|
||||
text channel. In acdream, `RuntimeCommunicationState.AddText` maps
|
||||
`ClientLocal` (`0x1A`) to the SpewBox only: it does not add the line to the
|
||||
chat transcript and does not apply a chat timestamp.
|
||||
|
||||
Hover failures are normally silent. Release/action failures are not. Server
|
||||
request failures are composed by `ACCWeenieObject::ServerSaysAttemptFailed`
|
||||
at `0x0058EAE0`, including move and wield failures.
|
||||
|
||||
## Current acdream ownership and routing
|
||||
|
||||
The relevant production flow is:
|
||||
|
||||
```text
|
||||
UiRoot / UiItemSlot
|
||||
-> InventoryController | ExternalContainerController | PaperdollController
|
||||
| VendorUiController | SelectedObjectController
|
||||
-> ItemInteractionController / AutoWieldController
|
||||
-> RuntimeInventoryState + RuntimeActionState transactions
|
||||
-> ClientObjectTable (canonical object ownership)
|
||||
-> outbound request builder
|
||||
-> authoritative object update / request failure
|
||||
-> RuntimeCommunicationState.ClientLocal -> SpewBox
|
||||
```
|
||||
|
||||
Important owners:
|
||||
|
||||
- `SelectionState` is the sole selected-object owner shared by inventory,
|
||||
paperdoll, vendor, world selection, and toolbar status.
|
||||
- `RuntimeInventoryState` owns external-container state, item-use transaction
|
||||
state, shared busy/request state, split/pending placement state, and borrows
|
||||
the canonical `ClientObjectTable`.
|
||||
- `SelectedObjectController` projects selection into the authored toolbar and
|
||||
owns the split-slider presentation.
|
||||
- `ItemInteractionController` classifies use/drop/give/move operations and
|
||||
sends requests.
|
||||
- `InventoryController`, `ExternalContainerController`, `PaperdollController`,
|
||||
and `VendorUiController` own their list-specific input and projections.
|
||||
|
||||
This ownership shape aligns with the architecture document. The central issue
|
||||
is not duplicate state; it is which state is mutated before acknowledgement.
|
||||
|
||||
## Behavior matrix
|
||||
|
||||
| Surface/action | Retail | Current acdream | Verdict |
|
||||
|---|---|---|---|
|
||||
| Inventory left press | Target-mode consume, otherwise select | `PrimaryItemPressed` does the same | Match |
|
||||
| Ordinary item single click | Select; no generic use | Mouse-down selects | Match |
|
||||
| Ordinary item double-click | Generic use/equip | `DoubleClicked = ActivateItem` | Match |
|
||||
| Owned side-pack single press | Select and open in the item-list handler | Selects and opens on mouse-down | Match, implemented |
|
||||
| Owned side-pack double-click | Open behavior; generic item double-use suppressed | Opens once; generic activation is suppressed | Match, implemented |
|
||||
| Inventory right-click | Select, then examine | Select and examine | Match |
|
||||
| Drag lift | Select if needed; source ghost | Selects and ghosts | Match |
|
||||
| Drag hover | Silent, legality-aware green/red | Silent and shares the release legality decision | Match, implemented |
|
||||
| Full internal move | Request plus pending destination projection; canonical source waits for server | Request-first pending projection; authoritative update commits | Match, implemented |
|
||||
| Merge stacks | Merge before container placement; selected split amount; select target | Same broad behavior | Match |
|
||||
| Partial move to container | Split request; wait for authoritative object | Request-only | Match |
|
||||
| Drop to ground | Request/pending presentation; source remains canonical until response | Request-first; canonical source waits for response | Match, implemented |
|
||||
| Split to ground | Global pending split; select arriving matching object; timeout | Request/pending path exists | Broad match |
|
||||
| Pick up from ground | Pending destination projection; authoritative commit | Pending destination path | Match |
|
||||
| Open external container | Root/nested list-specific behavior | Root double-click, nested open behavior | Broad match |
|
||||
| Move to external container | Request-only, open/unlocked legality, server commit | Request-only with shared hover/release legality | Match, implemented |
|
||||
| Give to creature | Request-only; selected split amount | Request-only | Match |
|
||||
| Give/drop to player | Secure-trade routing | Secure-trade routing exists | Broad match |
|
||||
| Paperdoll click/right-click | Global select/examine | Global select/examine | Match |
|
||||
| Paperdoll drag equip | Exact location validation; auto-wear/wield | Same broad split | Broad match |
|
||||
| Full wield | Authoritative request model | Request-first; canonical ownership waits for response | Match, implemented |
|
||||
| Invalid item use/equip | ClientLocal text in SpewBox | Shared `ReportClientLocal` route | Match, implemented |
|
||||
| Selected status | Normal name or `{quantity} name`; owned coin is `%d %hs (of %d)` | Both branches implemented | Match, implemented |
|
||||
| Split applicability | Only selected stack uses global quantity | Same | Match |
|
||||
| Vendor browse single/right | Select; right-click examine | Select and right-click examine | Match |
|
||||
| Vendor browse double | Buy one | Buy one | Match |
|
||||
| Drag inventory to Selling | Stage full or selected partial quantity | Present; partial temp-row replacement present | Match, #445 gate pending |
|
||||
| Vendor hover refusal | Silent | Silent | Match |
|
||||
| Vendor release refusal | ClientLocal/SpewBox | System message/SpewBox path | Match |
|
||||
| Staged Buying double-click | Remove one + SpewBox line | Same | Match, implemented |
|
||||
| Staged Selling double-click | Remove row, clear state + SpewBox line | Same | Match, implemented |
|
||||
| Staged Selling drag | Remove row; partial selection warns and resets split | Same, exact refusal + reset | Match, implemented |
|
||||
| Staged row right-click | Generic select/examine item-list behavior | Select and examine on every vendor list role | Match, implemented |
|
||||
| Main-pack capacity | Items and carried containers counted separately | Separate loose-item count now present | Match, #449 gate pending |
|
||||
| Server move/wield failure text | Exact ClientLocal move/wield compositions | Both request kinds and compositions present | Match, implemented |
|
||||
|
||||
## Findings
|
||||
|
||||
### F1 — local inventory refusals can be completely silent
|
||||
|
||||
**Resolution:** CLOSED IN CODE — one `ReportClientLocal` route now selects
|
||||
interface text, system text, or the test fallback in that order.
|
||||
|
||||
**Priority:** P0
|
||||
|
||||
**Confidence:** Confirmed by production composition
|
||||
|
||||
`ItemInteractionController` uses two different presentation routes:
|
||||
|
||||
- `_systemMessage` / `_interfaceText`, which are wired to
|
||||
`RuntimeCommunicationState.AddText(..., ClientLocal)` and reach SpewBox.
|
||||
- `_toast`, used by many local policy refusals.
|
||||
|
||||
`InteractionRetainedUiComposition` forwards its `toast` dependency, but
|
||||
`GameWindow` currently sets the production composition toast to `null` after
|
||||
the developer-toast surface was removed. Consequently, the local rejection
|
||||
still aborts the action, but the user receives no explanation.
|
||||
|
||||
Affected classes include invalid item use, missing use target, trade/wield
|
||||
requirements, locked or unsuitable targets, invalid move/give/drop states,
|
||||
midair/drop refusal, and paperdoll slot-in-use refusal. Exact membership should
|
||||
be frozen in a focused message-routing test before changing it.
|
||||
|
||||
Retail evidence is unambiguous: these are local client text messages and belong
|
||||
in SpewBox, not a transient developer toast.
|
||||
|
||||
**Future fix:** remove the semantic split for gameplay failure text. Give item
|
||||
controllers one `ClientLocal` sink and reserve any visual toast mechanism for
|
||||
non-retail developer/launcher notifications.
|
||||
|
||||
### F2 — optimistic canonical moves expose impossible intermediate state
|
||||
|
||||
**Resolution:** CLOSED IN CODE — full move, world drop, and wield dispatch
|
||||
requests without mutating canonical ownership; pending source/destination
|
||||
presentation converges on confirmation, failure, and reset.
|
||||
|
||||
**Priority:** P0 architectural correction
|
||||
|
||||
**Confidence:** Confirmed mechanism divergence; symptom linkage requires gates
|
||||
|
||||
The full-stack internal move and world-drop paths use optimistic operations
|
||||
against the canonical object table. Full wield uses the same pattern. Failure
|
||||
rollback exists, but all borrowers can observe the speculative state:
|
||||
|
||||
- selection and toolbar status;
|
||||
- loose-item and carried-container capacity;
|
||||
- paperdoll slots;
|
||||
- vendor sell eligibility/staging;
|
||||
- external-container views;
|
||||
- plugins and Runtime views.
|
||||
|
||||
Retail instead keeps source canonical ownership stable and uses waiting/ghost
|
||||
presentation at the intended destination until the server update arrives.
|
||||
|
||||
This does not prove that every reported intermittent inventory symptom comes
|
||||
from this seam. It does explain why otherwise-correct controllers can disagree
|
||||
briefly and why a rejection/late response/re-entrant action can make the UI feel
|
||||
flaky.
|
||||
|
||||
**Future fix:** model full move/drop/wield like the existing request-only split,
|
||||
give, ground-pickup, and external-container paths. Store a generation-scoped
|
||||
pending placement intent and presentation ghost, send the request, and let the
|
||||
authoritative update commit canonical ownership. On failure/timeout/reset,
|
||||
remove only the pending presentation.
|
||||
|
||||
### F3 — vendor staged-row removal behavior is missing
|
||||
|
||||
**Resolution:** CLOSED IN CODE — staged rows implement the retail
|
||||
double-click, right-click, drag-lift, message, and split-reset branches.
|
||||
|
||||
**Priority:** P1
|
||||
|
||||
**Confidence:** Confirmed by direct named-retail functions
|
||||
|
||||
Current staged Buying and Selling rows only bind selection. They have no
|
||||
double-click removal. Selling rows also disable drag source behavior.
|
||||
|
||||
Retail supports:
|
||||
|
||||
- double-click staged Buying to remove;
|
||||
- double-click staged Selling to remove and clear sell state;
|
||||
- drag staged Selling to remove;
|
||||
- a precise ClientLocal removal line;
|
||||
- a partial-split refusal/reset when dragging from the staged Selling list.
|
||||
|
||||
**Future fix:** add list-role-specific actions rather than routing these rows
|
||||
through generic item activation. Protect each action with unit tests that also
|
||||
assert selection, sell-state cleanup, totals, and exact SpewBox routing.
|
||||
|
||||
### F4 — owned side-pack click/double-click sequencing differs
|
||||
|
||||
**Resolution:** CLOSED IN CODE — carried containers open on press and the
|
||||
generic double-use route is suppressed for that list role.
|
||||
|
||||
**Priority:** P1
|
||||
|
||||
**Confidence:** Confirmed structural mismatch
|
||||
|
||||
Retail opens a carried child container in the item-list press handler and
|
||||
suppresses generic double-click use for a `containerList` item. acdream selects
|
||||
on mouse-down, opens on completed click, and binds generic activation to double
|
||||
click for every inventory cell. `UiRoot` emits the second click before the
|
||||
double-click event, so a double-click can both open and activate the pack.
|
||||
|
||||
This is a plausible source of redundant requests and awkward drag/open
|
||||
interactions. It should be fixed by explicit item-list role, not by a global
|
||||
double-click timing change, because ordinary items and the external-container
|
||||
root intentionally retain double-click use/open behavior.
|
||||
|
||||
### F5 — hover acceptance is less strict than release/server legality
|
||||
|
||||
**Resolution:** CLOSED IN CODE — `InventoryContainerPlacementPolicy` is the
|
||||
shared silent-hover/speaking-release decision for owned and external lists.
|
||||
|
||||
**Priority:** P1/P2
|
||||
|
||||
**Confidence:** Confirmed code difference
|
||||
|
||||
Inventory-grid hover mostly checks list role, basic object class, and capacity.
|
||||
External-container hover is broader still. Retail's predicates incorporate
|
||||
ownership, trade state, source/destination identity, real carrying-container
|
||||
restrictions, open/locked state, destination capacity type, and other legal
|
||||
conditions.
|
||||
|
||||
The practical symptom is a green cursor followed by a refusal or apparent
|
||||
no-op on release. Hover must remain silent, but its boolean should be produced
|
||||
from the same pure legality decision used at release.
|
||||
|
||||
**Future fix:** extract one side-effect-free placement decision that returns a
|
||||
reason code. Hover consumes only allowed/denied; release converts the same
|
||||
reason to exact ClientLocal text.
|
||||
|
||||
### F6 — selected status lacks retail's owned-coin special case
|
||||
|
||||
**Resolution:** CLOSED IN CODE — the PDB-matched retail executable resolves
|
||||
the literal at `0x007B4748` to `%d %hs (of %d)`; the controller now reads the
|
||||
player's `CoinValue` and uses that exact branch for owned WCID 273 stacks.
|
||||
|
||||
**Priority:** P2
|
||||
|
||||
**Confidence:** Byte-resolved from the PDB-matched retail executable
|
||||
|
||||
Normal current text—name for a singleton and `{stackSize} {name}` for a
|
||||
stack—matches the main retail branch. Retail has an additional owned-coinstack
|
||||
formatting branch that derives a total/value-aware display and name. The
|
||||
current controller always uses the generic stack prefix.
|
||||
|
||||
Binary inspection resolves the apparent vtable-symbol artifact: the raw call
|
||||
site pushes `0x007B4748`, `%d %hs (of %d)`, with stack size, appropriate name,
|
||||
and the player's integer `CoinValue` as its three arguments.
|
||||
|
||||
### F7 — request failure coverage omits move and wield kinds
|
||||
|
||||
**Resolution:** CLOSED IN CODE — both request kinds are represented and route
|
||||
through the item-aware retail failure composer.
|
||||
|
||||
**Priority:** P2
|
||||
|
||||
**Confidence:** Confirmed enum/composer gap
|
||||
|
||||
Retail's `ServerSaysAttemptFailed` includes move and wield result families.
|
||||
The current request-failure model and `InventoryFailureMessages` cover merge,
|
||||
split, pickup, put, drop, and give, but do not represent the retail move/wield
|
||||
families. A server-side failure in those operations therefore cannot produce
|
||||
the exact item-aware retail sentence through the common composer.
|
||||
|
||||
### F8 — current retail-divergence documentation is wrong about vendor double-click
|
||||
|
||||
**Resolution:** CLOSED — the older research is corrected and AP-171 retired.
|
||||
|
||||
**Priority:** Documentation correction before implementation
|
||||
|
||||
**Confidence:** Confirmed by direct named-retail evidence
|
||||
|
||||
Older vendor research and AP-171 characterize double-click browse-row purchase
|
||||
as an acdream enhancement. `gmVendorUI::HandleMousePresses` directly calls
|
||||
`BuySingleItem` on the retail Items-list double-click. Current browse behavior
|
||||
is correct; the documentation is not. Leaving this claim in the register risks
|
||||
a future parity cleanup deleting a retail feature.
|
||||
|
||||
### F9 — #445 and #449 need connected acceptance, not more inference
|
||||
|
||||
**Priority:** Gate now
|
||||
|
||||
**Confidence:** Automated fixes present
|
||||
|
||||
- #445 now uses the selected split quantity for vendor selling, creates a
|
||||
temporary staged row, and replaces it when the authoritative split object
|
||||
arrives.
|
||||
- #449 now counts loose items separately from carried container objects when
|
||||
deciding whether the main backpack is full.
|
||||
|
||||
Both have focused tests in the current working tree. Neither should be marked
|
||||
closed until a live server gate covers success, refusal, repeated action, and
|
||||
selection changes.
|
||||
|
||||
### F10 — paperdoll disappearance is a separate rendering/residency defect
|
||||
|
||||
**Priority:** Keep separate from transaction fixes
|
||||
|
||||
**Confidence:** Existing issue #443
|
||||
|
||||
The intermittent missing paperdoll that heals after a delay is tracked as
|
||||
paperdoll first-open/residency behavior. It can make a correct equip transaction
|
||||
look broken, so it belongs in the combined user gate, but it should not be
|
||||
folded into inventory ownership or input logic without evidence.
|
||||
|
||||
## SpewBox contract
|
||||
|
||||
The following should appear in the SpewBox through `ClientLocal` when the user
|
||||
commits the action and it is refused or changed:
|
||||
|
||||
- invalid use/equip/wield state;
|
||||
- “choose a target” or invalid target;
|
||||
- cannot move/drop/give an item;
|
||||
- locked, closed, full, or otherwise illegal destination;
|
||||
- merge/split/pickup/put/drop/give/move/wield request failure;
|
||||
- vendor item cannot be sold or split in that list;
|
||||
- removal from a vendor shopping/selling list;
|
||||
- automatic removal of conflicting wear items where retail reports it;
|
||||
- midair or other locally cancelled placement when retail reports it.
|
||||
|
||||
The following should be silent:
|
||||
|
||||
- merely hovering a rejected drop target;
|
||||
- moving the pointer away without releasing;
|
||||
- ordinary selection changes;
|
||||
- beginning a legal drag.
|
||||
|
||||
These messages should not be duplicated into the normal chat log and should
|
||||
not gain chat timestamps. That is already how `ClientLocal` behaves in the
|
||||
communication owner.
|
||||
|
||||
## Existing automated coverage
|
||||
|
||||
The repository already has strong narrow coverage in:
|
||||
|
||||
- `InventoryControllerTests`: population, selection, open/right-click,
|
||||
drag/ghost, pending pickup, split, merge, capacity, rollback, and #449.
|
||||
- `ExternalContainerControllerTests`: root/nested behavior, selection,
|
||||
right-click, partial split, and pending gates.
|
||||
- `PaperdollControllerTests`: selection, examine, drag, and wield placement.
|
||||
- `SelectedObjectControllerTests`: name, stack status, slider, and vendor split
|
||||
initialization.
|
||||
- `VendorUiControllerTests`: browse, buy quantities, selection/examine,
|
||||
staging, partial vendor split/failure, rejection feedback, and alternate
|
||||
currency.
|
||||
- `ItemInteractionControllerTests`: use/equip, world drop, give, partial-stack
|
||||
behavior, failures, and transaction lifecycle.
|
||||
- Runtime inventory tests: request ownership, reset, and lifecycle behavior.
|
||||
|
||||
The pre-implementation test suite was strongest at proving controller-local
|
||||
intent. The implementation program below adds the missing transaction and
|
||||
cross-controller coverage.
|
||||
|
||||
## Automated gates added by the implementation
|
||||
|
||||
The implementation adds or updates coverage for the following:
|
||||
|
||||
1. A production-composition test proving every local policy rejection reaches
|
||||
`ClientLocal`/SpewBox and no gameplay failure depends on a toast callback.
|
||||
2. Owned side-pack single/double-click tests proving one open action and no
|
||||
generic use request, including the second-click event order.
|
||||
3. Vendor staged Buying and Selling double-click removal tests with exact
|
||||
selection, totals, state cleanup, and message assertions.
|
||||
4. Vendor staged Selling drag-to-remove and selected-partial split-reset tests.
|
||||
5. Staged vendor-row right-click select/examine tests.
|
||||
6. A table-driven pure legality test shared by hover and release for inventory,
|
||||
external container, ground, player, creature, vendor, self, locked container,
|
||||
full item slots, and full container slots.
|
||||
7. Owned coinstack toolbar-status parity using the byte-resolved exact format.
|
||||
8. Move and wield authoritative failure-composition tests.
|
||||
9. Transaction-observer tests proving canonical ownership does not change
|
||||
before acknowledgement while selection, capacity, vendor, and paperdoll
|
||||
borrow the same state.
|
||||
10. Re-entrant sequences: drag while a request is pending, selection change
|
||||
during split, rejection after container close, late response after session
|
||||
reset, and repeated action after rollback.
|
||||
|
||||
## Executed implementation program
|
||||
|
||||
### Slice 1 — feedback integrity — COMPLETE
|
||||
|
||||
- Replace gameplay `toast` refusal calls with the shared ClientLocal sink.
|
||||
- Add the missing move/wield failure kinds and exact item-aware compositions.
|
||||
- Freeze hover-silent versus release-speaks behavior.
|
||||
- Correct the vendor double-click documentation claim.
|
||||
|
||||
This is small, high-confidence, and immediately turns “nothing happened” into
|
||||
an actionable player explanation.
|
||||
|
||||
### Slice 2 — authoritative placement ownership — COMPLETE
|
||||
|
||||
- Introduce one generation-scoped pending placement record for full move,
|
||||
world drop, and wield.
|
||||
- Preserve canonical source ownership until the authoritative object update.
|
||||
- Project source waiting/ghost and destination pending visuals separately.
|
||||
- Converge success, refusal, timeout, disconnect, and late-response cleanup.
|
||||
- Prove all borrowed observers see either pre-commit or committed state, never
|
||||
a speculative canonical move.
|
||||
|
||||
This is the most important solidity work and should receive dual review because
|
||||
it crosses Runtime ownership and retained presentation.
|
||||
|
||||
### Slice 3 — item-list mouse parity — COMPLETE
|
||||
|
||||
- Make carried-container press/open and double-click suppression explicit.
|
||||
- Add staged vendor double-click removal.
|
||||
- Add staged Selling drag-to-remove and split reset/refusal.
|
||||
- Restore right-click select/examine consistently across vendor list roles.
|
||||
|
||||
### Slice 4 — shared legality and exact presentation — COMPLETE
|
||||
|
||||
- Unify hover/release placement decisions with reason codes.
|
||||
- Add the owned-coinstack toolbar branch after capturing exact retail text.
|
||||
- Reconcile hard-coded local item wording with DAT-backed retail strings.
|
||||
|
||||
### Automated verification — COMPLETE
|
||||
|
||||
- Focused inventory/external-container/paperdoll/vendor/selection/item-use
|
||||
matrix: 328 passed, 0 failed.
|
||||
- Cross-controller retained-UI interaction flow: 10 passed, 0 failed.
|
||||
- Complete Release build: 0 warnings, 0 errors.
|
||||
- Repository hermetic lane (the exact release filter, serial execution):
|
||||
15,755 passed, 0 skipped, 0 failed across 14 test assemblies.
|
||||
|
||||
The repository wrapper's project-consistency preflight explicitly excludes the
|
||||
tracked deployment-only ACE comparison mods under `tools/ace-mods/`. They
|
||||
compile against a separately installed ACE server and intentionally remain
|
||||
outside `AcDream.slnx`; the portable product graph still owns every other
|
||||
project under `src/`, `tests/`, and `tools/`.
|
||||
|
||||
### Slice 5 — connected closure — DEFERRED OWNER GATE
|
||||
|
||||
Run the manual matrix below against ACE using an exact built binary and retain
|
||||
logs/screenshots for failures. Close #445 and #449 only after their rows pass.
|
||||
Keep #443 independent unless the evidence links paperdoll rendering to an
|
||||
inventory acknowledgement.
|
||||
|
||||
## Connected manual matrix
|
||||
|
||||
Use one normal item, one wearable item, one wieldable item, two mergeable
|
||||
stacks, one side pack, a full main backpack, a full side pack, an open chest,
|
||||
a locked/closed container if available, a creature/player target, and a vendor
|
||||
with normal and alternate currency.
|
||||
|
||||
1. Single-click each item/list type; verify selection border and exact status.
|
||||
2. Right-click inventory, side-pack, external-container, paperdoll, browse,
|
||||
Buying, and Selling rows; verify selection and examine.
|
||||
3. Double-click ordinary usable, wearable, wieldable, and unusable items;
|
||||
verify one request and correct SpewBox refusal where applicable.
|
||||
4. Single- and double-click a carried side pack; verify one open action, no
|
||||
redundant generic use, and stable selection.
|
||||
5. Drag a full item between main pack and side pack; observe source/destination
|
||||
before response, after success, and after forced rejection.
|
||||
6. Fill a side pack, reject a move, free one slot, and retry immediately.
|
||||
7. Fill the main pack with loose items while carrying side packs; verify item
|
||||
and container capacities independently (#449).
|
||||
8. Merge full and partial stacks; verify selected split amount, target
|
||||
selection, source remainder, and full-target refusal text.
|
||||
9. Split to an inventory container, external container, creature, ground, and
|
||||
vendor; change selection while the request is pending.
|
||||
10. Drop full and partial stacks to ground; verify ghost/pending behavior,
|
||||
selected arriving object, rejection cleanup, and no duplicate item.
|
||||
11. Pick up from ground into a nearly full destination, then retry after
|
||||
freeing capacity.
|
||||
12. Equip by double-click and by paperdoll drag; test clothing conflict and
|
||||
weapon replacement. Verify source/paperdoll state before acknowledgement.
|
||||
13. Drag full and partial stacks to vendor Selling; verify exact quantities,
|
||||
temp-row replacement, totals, and #445 behavior.
|
||||
14. Double-click staged Buying and Selling rows to remove them; verify SpewBox
|
||||
text and state cleanup.
|
||||
15. Drag a staged Selling row to remove it; repeat with a partial split selected
|
||||
and verify refusal plus slider reset.
|
||||
16. Complete/cancel transactions in normal and alternate currency; verify
|
||||
currency balance refresh (#444) and selection/status stability.
|
||||
17. Repeat representative actions while another inventory request is pending,
|
||||
immediately after rejection, and immediately after reopening a container.
|
||||
18. Log out/portal/re-enter with a pending or recently completed interaction;
|
||||
verify the request ledger and pending projections converge to zero.
|
||||
|
||||
For every refused release/action, record whether the cursor was green/red,
|
||||
whether a SpewBox line appeared, the exact line, and whether canonical item
|
||||
ownership changed before the server response.
|
||||
|
||||
## Evidence index
|
||||
|
||||
Retail research already in the tree:
|
||||
|
||||
- `docs/research/deepdives/r06-items-inventory.md`
|
||||
- `docs/research/2026-07-13-retail-item-drag-visuals-pseudocode.md`
|
||||
- `docs/research/2026-07-13-retail-give-item-pseudocode.md`
|
||||
- `docs/research/2026-07-23-retail-item-use-and-autowear-pseudocode.md`
|
||||
- `docs/research/2026-07-26-retail-inventory-placement-and-world-drop-pseudocode.md`
|
||||
- `docs/research/2026-08-08-slice6-vendor-transactions-research.md`
|
||||
- `docs/research/named-retail/acclient_2013_pseudo_c.txt`
|
||||
|
||||
Primary current implementation surfaces:
|
||||
|
||||
- `src/AcDream.App/UI/UiRoot.cs`
|
||||
- `src/AcDream.App/UI/UiItemSlot.cs`
|
||||
- `src/AcDream.App/UI/ItemInteractionController.cs`
|
||||
- `src/AcDream.App/UI/Layout/InventoryController.cs`
|
||||
- `src/AcDream.App/UI/Layout/ExternalContainerController.cs`
|
||||
- `src/AcDream.App/UI/Layout/PaperdollController.cs`
|
||||
- `src/AcDream.App/UI/Layout/SelectedObjectController.cs`
|
||||
- `src/AcDream.App/UI/Layout/VendorUiController.cs`
|
||||
- `src/AcDream.App/UI/AutoWieldController.cs`
|
||||
- `src/AcDream.App/Composition/InteractionRetainedUiComposition.cs`
|
||||
- `src/AcDream.App/Rendering/GameWindow.cs`
|
||||
- `src/AcDream.Core/Items/ItemInteractionPolicy.cs`
|
||||
- `src/AcDream.Runtime/Gameplay/RuntimeInventoryState.cs`
|
||||
- `src/AcDream.Runtime/Gameplay/RuntimeActionState.cs`
|
||||
|
||||
## Closure statement
|
||||
|
||||
The retail-backed work order is implemented through Slice 4. The code now has
|
||||
one ClientLocal feedback route, request-first authoritative placement,
|
||||
list-role-specific retail mouse behavior, shared placement legality, complete
|
||||
move/wield failure composition, exact local-policy literals, and the exact
|
||||
owned-coinstack status format. Occupied-slot AutoWield now also follows retail's
|
||||
move-confirm-retry transaction instead of emitting an invented refusal. The
|
||||
complete hermetic automated lane is green. No connected acceptance is claimed
|
||||
here; the combined owner gate remains the final closure step, and #443 remains
|
||||
an independent private-viewport residency issue.
|
||||
115
docs/research/2026-08-26-retail-keyboard-routing-audit.md
Normal file
115
docs/research/2026-08-26-retail-keyboard-routing-audit.md
Normal file
|
|
@ -0,0 +1,115 @@
|
|||
# Retail keyboard defaults and routing audit — 2026-08-26
|
||||
|
||||
## Verdict
|
||||
|
||||
The code gate for #446 now covers all 306 user-bindable rows in the installed
|
||||
Sept-2013 EoR ActionMap. Each row has a distinct `InputAction`, appears enabled
|
||||
in Configure Keyboard, persists through retail-compatible named `.keymap`
|
||||
profiles, and reaches
|
||||
a concrete subsystem consumer. The exact installed-DAT default chord set has
|
||||
zero exceptions. The remaining gate is a connected visual/behavior pass and a
|
||||
fresh-process persistence check.
|
||||
|
||||
The approved acdream extension is deliberately retained: mouse-wheel chase
|
||||
zoom may pull back to 40 m. It does not change the retail keyboard defaults or
|
||||
the keypad camera actions.
|
||||
|
||||
## Oracles
|
||||
|
||||
- `docs/research/named-retail/retail-default.keymap.txt` and installed
|
||||
`client_portal.dat` ActionMap DID `0x26000000`: the 306 rows, default chords,
|
||||
activation types, input contexts, and `ConflictingMaps` relationships.
|
||||
- Installed MasterInputMaps `0x14000000` and `0x14000002`: non-bindable system
|
||||
and mouse commands.
|
||||
- `ClientUISystem::OnAction @0x00564B90`: Escape priority.
|
||||
- `CInputManager_WIN32::GetNameFromKey_Internal @0x00687800` and
|
||||
`ControlNameMapper::LoadSemantics`: displayed keyboard/mouse names.
|
||||
- `ACCmdInterp::InitializeEmoteInputActionHash @0x0058B510`: all 87 emote
|
||||
action-to-motion mappings.
|
||||
- `CPlayerSystem::SelectNext @0x0055F9A0`: selection-cycle filtering and
|
||||
opened-corpse behavior.
|
||||
|
||||
## Exact ActionMap coverage
|
||||
|
||||
| Retail input map | Rows | Consumer |
|
||||
|---|---:|---|
|
||||
| Movement | 14 | Runtime movement owner, including four postures |
|
||||
| Camera + alternate camera | 22 | Held camera input, presets, alternate-scope modifier, instant mouse look |
|
||||
| Combat + melee + missile + magic | 32 | Runtime combat attack owner and spellcasting controller, including spell slots 1–12 |
|
||||
| Emotes | 87 | Exact retail raw-motion table and Runtime `ExecuteMotion` |
|
||||
| Item selection | 26 | Selection controller/query and canonical inventory interaction state |
|
||||
| UI | 42 | Retained panels, screenshot, help/plugin result, logout, and selection commands |
|
||||
| Chat + chat-entry toggle | 7 | Retained chat entry/reply/command routes |
|
||||
| Quickslots | 28 | Toolbar use/select/create routes, including slots 10–18 |
|
||||
| Character settings | 48 | Exact `CharacterOptionId` bit toggle through Runtime |
|
||||
| **Total** | **306** | **306 distinct live identities** |
|
||||
|
||||
The low MasterInputMap entries such as bare Escape and raw mouse event
|
||||
commands are intentionally not Configure Keyboard rows in retail and are not
|
||||
counted among the 306. Unknown rows from a future DAT can still round-trip in
|
||||
the compatibility sibling store, but the installed EoR DAT has no such row and
|
||||
shows no dimmed/store-only keyboard entry.
|
||||
|
||||
## Behavior completed
|
||||
|
||||
- Defaults are an exact installed-DAT transcription, including bare
|
||||
`LeftShift`; device, modifier, activation, and scope all match.
|
||||
- Primary and alternate camera maps remain distinct rebind targets even where
|
||||
retail reuses an action id. The alternate modifier changes the active camera
|
||||
scope without aliasing saved bindings.
|
||||
- Same physical chord may fire each distinct retail action allowed by the
|
||||
ActionMap. In particular, the authored Alt+1..4 chat/UI and quickslot rows
|
||||
multicast instead of one silently replacing the other.
|
||||
- Rebind conflicts use the DAT `ConflictingMaps` table. The shared melee,
|
||||
missile, and magic key cluster remains legal; true conflicts still prompt.
|
||||
- Capture accepts keyboard keys, modifier-only bindings, and mouse buttons.
|
||||
Physical modifier self-bits are normalized, so binding LeftShift does not
|
||||
accidentally become Shift+LeftShift. Mouse button names use retail's
|
||||
`DIMOFS_BUTTON0..7` semantics table. Unsupported joystick and left/right
|
||||
mouse inputs keep the instruction dialog open and re-arm capture.
|
||||
- Setting the chord already present on the same row is a no-op. New chords use
|
||||
retail's dense two-slot insertion rule, and conflicts use priority dialogs
|
||||
with the exact installed-DAT singular/plural and non-bindable text.
|
||||
- Apply/OK, Revert, Defaults, Cancel, explicit unbinding, schema migration,
|
||||
and startup persistence are covered. Revert is enabled only while dirty;
|
||||
OK avoids rewriting an unchanged file.
|
||||
- Load File and Save As use retail's type-7 menu/type-5 text-entry dialogs,
|
||||
PFile bracket-text grammar, filename normalization, overwrite/read-only
|
||||
handling, `Documents\Asheron's Call\*.keymap` directory, selected-profile
|
||||
preference, startup load, and graceful-shutdown rewrite. The portable JSON
|
||||
file remains only as an acdream-host-command compatibility mirror.
|
||||
- Escape follows retail's priority: finish jump charge, release focused UI,
|
||||
stop movement/repeat attack, cancel target mode, clear selection, then
|
||||
toggle the authored Gameplay Options page. It never exits player mode or
|
||||
exposes the orbit/developer camera. Shift+Escape reaches the normal logout
|
||||
gate.
|
||||
- Selection cycling applies retail's containment, cloaking, radar, attackable,
|
||||
fellow, vendor, environment, combat-mode, and opened-corpse rules.
|
||||
Opened-corpse history lives for the session and retires on object deletion.
|
||||
- Screenshot, help, and plugin actions are consumed. Missing separately
|
||||
shipped retail help/plugin surfaces report an honest chat/system result
|
||||
rather than doing nothing.
|
||||
|
||||
## Automated verification
|
||||
|
||||
- App: 6,413/6,413 passed.
|
||||
- Core: 4,713/4,713 passed.
|
||||
- Runtime: 1,849/1,849 passed.
|
||||
- UI.Abstractions: 879/879 passed.
|
||||
- Installed-DAT identity/default conformance and the authored Configure
|
||||
Keyboard mount pin all 306 rows.
|
||||
|
||||
The `.keymap` codec parses the committed real retail file and round-trips all
|
||||
306 user-bindable identities, including low-bit Shift/Ctrl/Alt/Win modifiers,
|
||||
DirectInput controls, fixed Escape/system/edit/pointer maps, and the 48
|
||||
CharacterOption action names. AP-202 is retired. The connected gate could not
|
||||
be run because no local ACE endpoint was listening on UDP port 9000.
|
||||
|
||||
## Connected acceptance gate
|
||||
|
||||
Use the installed EoR DATs and the normal owner-gate pak. In Configure
|
||||
Keyboard, verify that all rows are enabled and that a key, modifier-only chord,
|
||||
and mouse button can each be rebound. Exercise representative movement,
|
||||
camera, melee/missile/magic, emote, selection, panel, chat, quickslot, and
|
||||
character-option actions. Verify conflict prompt, Cancel, Revert, Defaults,
|
||||
Apply, and OK, then restart the process and confirm the applied bindings remain.
|
||||
Loading…
Add table
Add a link
Reference in a new issue