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)
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue