feat(ui): port retail appraisal panel
Preserve retail's one-pending-appraisal busy lifetime, parse the complete gated response, and mount the authored examination layout in the shared main-panel host. Keep known 3D preview and inscription-write gaps explicit in AP-110.
This commit is contained in:
parent
6b1ae4fb76
commit
643cdfe66e
24 changed files with 17132 additions and 40 deletions
|
|
@ -205,7 +205,7 @@ AP-94..AP-112 for the confirmed retail-UI completion gaps.
|
|||
| ~~AP-107~~ | **RETIRED 2026-07-11 (Wave 3.3 / #197)** — typed `OfferPrimaryClick` returns `NotActive`, `ConsumedSuccess`, or `ConsumedRejected`; every retained item surface plus radar/world offers active target mode before local selection/open/use fallback. Rejections are consumed and cannot drift selection. | `src/AcDream.App/UI/ItemInteractionController.cs`; inventory/paperdoll/toolbar/radar/world call sites | — | — | `UIElement_ItemList::HandleTargetedUseLeftClick @ 0x004E24D0` |
|
||||
| AP-108 | Paperdoll/AutoWield still omit the remaining missile/held restrictions and corrupt-mask branch of full `AutoWieldIsLegal`, dual-wield/off-hand rules, double-click examine/drag from the doll, body-part selection lighting, and retail's synchronous `" - cannot unwield the %s"` failure suffix (the current send seam reports rejection asynchronously). **AutoWear legality retired from this row 2026-07-23:** inventory activation and paperdoll drops now apply the retail clothing-priority/location blocker lookup and exact `"You must remove your %s to wear that"` system notice. **Primary replacement retired 2026-07-14; Aetheria retired 2026-07-13.** | `src/AcDream.App/UI/Layout/PaperdollController.cs`; `src/AcDream.App/UI/AutoWieldController.cs` | Basic equip slots, Aetheria, live doll, AutoWear conflict reporting, and primary weapon/incompatible shield/mismatched ammo blocker sequencing work in peace and war | Remaining illegal/off-hand cases, asynchronous dequip rejection wording, doll examine/drag, and selection lighting still differ functionally | `CPlayerSystem::AutoWieldIsLegal @ 0x0055ED60`; `CPlayerSystem::AutoWearIsLegal @ 0x0055EF40`; `CPlayerSystem::AutoWield @ 0x00560A60`; `gmPaperDollUI @ 0x004A3590..0x004A5F90` |
|
||||
| AP-109 | Character Titles page is inert and live displayed-title/luminance state is absent | `src/AcDream.App/UI/Layout/CharacterStatController.cs`; `CharacterSheetProvider.cs` | Attributes/skills core output is user-accepted | Titles cannot be selected/displayed and level-200 luminance fields are missing | `gmCharacterTitleUI @ 0x0049A610`; `gmStatManagementUI::UpdateExperience @ 0x004F0A70` |
|
||||
| AP-110 | Remaining retained gameplay panels and world HUD are absent: advanced-combat powerbar, residual social/examine/floating chat, quests/map/options/smartbox, vendor/trade/salvage/tinkering, mini-game gameplay, Link Status NAK/retransmission packet-loss averaging, and D.6 nameplates/floaters | `src/AcDream.App/UI/RetailUiRuntime.cs`; `src/AcDream.Core.Net/LinkStatusSnapshot.cs`; D.5/D.6 roadmap | Basic combat plus M3 spellbook/component/effects/favorite-spell/Link/Vitae surfaces now cover the active melee/missile/magic loops; the Link page has exact ping RTT but reports the current transport's zero default until its missing reliability statistics land | Large portions of retail gameplay still have no production UI; real packet loss is displayed as 0.00% instead of retail's moving average | Named `gm*UI::PostInit` methods; `CLinkStatusAverages::GetAveragePacketLoss @ 0x00546610`; LayoutDesc catalog |
|
||||
| AP-110 | Remaining retained gameplay panels and world HUD are absent: advanced-combat powerbar, residual social/floating chat, quests/map/options/smartbox, vendor/trade/salvage/tinkering, mini-game gameplay, Link Status NAK/retransmission packet-loss averaging, and D.6 nameplates/floaters. Examination now has its authored outer layout and basic item/creature/character reports, but still lacks the exact 3D preview and exhaustive specialized detail regions, conditional inscription-write transaction, and selection-follow preference. | `src/AcDream.App/UI/RetailUiRuntime.cs`; `src/AcDream.App/UI/Layout/AppraisalUiController.cs`; `src/AcDream.Core.Net/LinkStatusSnapshot.cs`; D.5/D.6 roadmap | Basic combat, M3 magic/Link/Vitae surfaces, and the core examination request/presentation loop cover the active loops; the remaining examination mechanisms require additional render and item-write owners rather than fabricated content | Specialized assessments omit some retail fields/3D preview and inscriptions are display-only; other absent panels remain unavailable; real packet loss is displayed as 0.00% instead of retail's moving average | `gmExaminationUI::SetAppraiseInfo @ 0x004ADAE0`; `ItemExamineUI::SetAppraiseInfo @ 0x004B72B0`; `CLinkStatusAverages::GetAveragePacketLoss @ 0x00546610`; LayoutDesc catalog |
|
||||
| ~~AP-111~~ | **RETIRED 2026-07-11 (M2 held-object parenting)** — equipped hand items are no longer omitted from the render world. CreateObject now preserves Placement/Parent/position timestamp bootstrap; live `0xF749` ParentEvent is parsed with retail sequence freshness; a focused render controller resolves `Setup.HoldingLocations`, applies the child's placement frame, and recomposes the separate child entity after every parent animation tick. Pickup retains the weenie's visual metadata for a later wield. | `src/AcDream.Core.Net/Messages/{CreateObject,ParentEvent}.cs`; `src/AcDream.Core/Meshing/EquippedChildAttachment.cs`; `src/AcDream.App/Rendering/EquippedChildRenderController.cs` | — | — | `ClientCombatSystem::GetDefaultCombatMode @ 0x0056B310`; `SmartBox::HandleParentEvent @ 0x004535D0`; `CPhysicsObj::set_parent @ 0x00515A90`; `CPhysicsObj::UpdateChild @ 0x00512D50` |
|
||||
| AP-112 | The basic combat bar ports visibility, height selection, desired-power slider, exact 1.0/0.8-second charge, ready-stance gating, request/release, `MaybeStopCompletely`, server-response queueing, and auto-repeat, but still omits `StartAttackRequest`'s `FinishJump` call and exact trained-Recklessness visibility semantics (IA-20 keeps the dark range as the accepted baseline) | `src/AcDream.App/Combat/CombatAttackController.cs`; `src/AcDream.App/UI/Layout/CombatUiController.cs` | The shared player movement owner now performs retail's server-control-gated full stop and movement report before an attack build; the remaining seams require the jump owner and a distinct Recklessness treatment | Starting an attack while charging a jump may not finish that jump exactly when retail does; trained/untrained Recklessness presentation is identical | `ClientCombatSystem::StartAttackRequest @ 0x0056C040`; `CommandInterpreter::MaybeStopCompletely @ 0x006B3B90`; `gmCombatUI::ListenToElementMessage @ 0x004CC430` |
|
||||
| AP-113 | Invalid lifestone-command arguments display the local text `Usage: /lifestone`; retail definitely emits a local usage/error line but Binary Ninja misidentifies the referenced wide-string address, so its exact wording is not yet recovered | `src/AcDream.UI.Abstractions/Panels/Chat/ChatCommandRouter.cs`; `RetailClientCommandCatalog.cs` | The behavior boundary is exact (handled locally, no chat and no game action); only a low-impact diagnostic sentence differs | `/ls now` can show different wording/color from retail while still refusing the invalid request correctly | `ClientCommunicationSystem::DoLifestone @ 0x0056FC70` |
|
||||
|
|
|
|||
|
|
@ -29,8 +29,13 @@ That correction is user-confirmed. The requested adjacent cooldown follow-up
|
|||
ports the optional public shared-cooldown metadata, the authoritative
|
||||
cooldown-enchantment lookup, the exact ten-step bucket formula, and DAT
|
||||
sprites `0x060067CF..0x060067D8` through the shared retained `UiItemSlot`
|
||||
presentation. Its visual gate comes next; the magnifying-glass Assess command
|
||||
then follows in the same slice.
|
||||
presentation; its connected gate passed. The existing magnifying-glass Assess
|
||||
command was already retail-faithful. Slice 3 is now code-complete: the complete
|
||||
appraisal response reaches a balanced pending/current request owner and the
|
||||
imported 310 x 400 examination layout through the canonical shared main-panel
|
||||
host. Item, creature, and character views, authored scrollbars, first-response
|
||||
visibility, and visible-combat refresh are pinned; the connected examination
|
||||
gate is next.
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -96,8 +96,12 @@ The follow-up retail correction for zero-valued direct-use items, named
|
|||
AutoWear blockers, and carried-use delivery is user-confirmed. The requested
|
||||
adjacent cooldown follow-up now preserves the item's shared group/duration,
|
||||
projects the authoritative cooldown-enchantment bucket, and draws retail's ten
|
||||
DAT-authored radial steps across all item-slot surfaces. Its visual gate is
|
||||
next; the Assess half then continues.
|
||||
DAT-authored radial steps across all item-slot surfaces; its connected gate
|
||||
passed. The pre-existing Assess command has been reconciled against retail and
|
||||
Slice 3 is code-complete: strict full appraisal parsing, balanced
|
||||
pending/current request ownership, and LayoutDesc `0x2100006B` item,
|
||||
creature, and character views now live in the canonical shared main-panel
|
||||
host. Its connected examination gate is next.
|
||||
|
||||
Carried:
|
||||
#153 far-teleport residual, #116 slide-response, the remaining R6 ownership cleanup plus
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
# World interaction completion — pre-M4 program
|
||||
|
||||
**Status:** Slice 1 user-accepted 2026-07-23. Slice 2 Use-hand selection,
|
||||
zero-useability, carried direct-use, and AutoWear correction passed their
|
||||
connected gates. The requested shared item-cooldown follow-up is code-complete
|
||||
and awaits its visual gate before Assess continues.
|
||||
zero-useability, carried direct-use, AutoWear correction, and the requested
|
||||
shared item-cooldown follow-up passed their connected gates. The existing
|
||||
retail Assess command has been reconciled and Slice 3's examination panel is
|
||||
code-complete; its connected visual gate is next.
|
||||
**Milestone:** M4 prerequisite/preamble.
|
||||
**Architecture:** retained gameplay UI over shared selection, object, and
|
||||
interaction state. `GameWindow` remains a composition/callback shell.
|
||||
|
|
@ -106,6 +107,26 @@ The focused parser/Core/UI/production-DAT tests, warning-free Release solution
|
|||
build, 3,482 App tests / 3 skips, and 7,875 complete-solution tests / 5 skips
|
||||
pass. AP-123 records only the retained toolkit's procedural-child adaptation;
|
||||
the visible assets, ordering, timing, and shared-group behavior are exact.
|
||||
The user accepted the live cooldown presentation 2026-07-23.
|
||||
|
||||
Slice 3 preserves the existing toolbar/keyboard Assess command and ports the
|
||||
retail response owner around it. One shared UI-busy reference covers the
|
||||
latest pending GUID, stale replies are rejected, the accepted reply becomes
|
||||
the current appraisal, and combat-time creature/player refreshes occur every
|
||||
0.75 seconds only while the examination window is visible. Complete
|
||||
`IdentifyObjectResponse` parsing now includes the three-word HookProfile and
|
||||
rejects truncated gated payloads rather than continuing from a corrupt cursor.
|
||||
LayoutDesc `0x2100006B`, root `0x100005F2`, supplies the complete 310 x 400
|
||||
floaty chrome, title, item/creature/character subviews, close control, and
|
||||
authored scrollbars. The panel participates in the canonical one-active
|
||||
main-panel geometry/lifecycle rather than owning a parallel location. Basic
|
||||
item, creature, and player reports are live; exact 3D preview/specialized
|
||||
detail regions, conditional inscription editing, and selection-follow remain
|
||||
the narrowed AP-110 residual. Research:
|
||||
[`../research/2026-07-23-retail-appraisal-ui-pseudocode.md`](../research/2026-07-23-retail-appraisal-ui-pseudocode.md).
|
||||
The focused parser/router/request/controller/fixture tests pass, as do the
|
||||
Release solution build with 17 pre-existing tracked test warnings, 3,488 App
|
||||
tests / 3 skips, and 7,885 complete-solution tests / 5 skips.
|
||||
|
||||
## Slice 1 — spell-bar overflow arrows
|
||||
|
||||
|
|
@ -183,11 +204,40 @@ Named retail references and executable pseudocode are recorded in
|
|||
request owner, and targeted items enter `UseItemOnTarget`, which already
|
||||
owns the retail target cursor.
|
||||
|
||||
### Remaining Slice 2 work
|
||||
### Slice 2 closeout
|
||||
|
||||
- Run the connected visual gate for the shared item cooldown: use Blackmoor's
|
||||
Favor or a summon/stone and verify its inventory and shortcut aliases count
|
||||
down together, then clear.
|
||||
- Port the magnifying-glass Assess command state and activation.
|
||||
- Continue directly into the Assess information slice after the combined
|
||||
status-control gate.
|
||||
- The shared item-cooldown visual gate passed.
|
||||
- `gmToolbarUI::ListenToElementMessage @ 0x004BEE90` confirms the existing
|
||||
magnifying-glass path was already correct: assess the selected GUID
|
||||
immediately, otherwise enter one-shot Examine target mode.
|
||||
- Slice 3 owns the response lifetime and retained examination presentation;
|
||||
Slice 2 adds no duplicate command path.
|
||||
|
||||
## Slice 3 — assessment information panel
|
||||
|
||||
### Implemented ownership
|
||||
|
||||
1. `AppraiseInfoParser` owns the complete positional response payload,
|
||||
including HookProfile and strict truncation failure.
|
||||
2. `ItemInteractionController` owns the pending/current GUID pair and the
|
||||
balanced shared busy reference.
|
||||
3. `AppraisalUiController` owns response acceptance, item/creature/character
|
||||
subview selection, report projection, scroll preservation, first-response
|
||||
visibility, and visible-combat refresh.
|
||||
4. `RetailUiRuntime` imports the retail layout and registers it with
|
||||
`RetailPanelUiController`, so Inventory, Skills, Spellbook, effects, and
|
||||
Examination remain views of the same movable/resizable main panel.
|
||||
5. Network workers deliver immutable parsed data through the existing
|
||||
session router; retained state changes remain on the update thread.
|
||||
|
||||
### Connected gate
|
||||
|
||||
- Select an item and click the magnifying glass (or press Assess): the shared
|
||||
panel should show its name, available properties, descriptions,
|
||||
inscription/signature, retail chrome, and working scrollbar.
|
||||
- Assess a monster, NPC, and player: the correct creature/character page
|
||||
should appear and the busy cursor should clear.
|
||||
- In combat, leave a creature assessment open long enough to observe a health
|
||||
refresh; closing it or opening another shared panel must stop refreshes.
|
||||
- Move/resize Examination, switch to Inventory or Skills, and confirm the
|
||||
shared rectangle is retained.
|
||||
|
|
|
|||
193
docs/research/2026-07-23-retail-appraisal-ui-pseudocode.md
Normal file
193
docs/research/2026-07-23-retail-appraisal-ui-pseudocode.md
Normal file
|
|
@ -0,0 +1,193 @@
|
|||
# Retail appraisal request and examination UI pseudocode
|
||||
|
||||
**Date:** 2026-07-23
|
||||
**Scope:** status-bar Assess request, IdentifyObjectResponse parsing, and the
|
||||
retained examination window.
|
||||
|
||||
## Sources
|
||||
|
||||
- Named retail:
|
||||
- `gmToolbarUI::ListenToElementMessage @ 0x004BEE90`
|
||||
- `gmExaminationUI::RecvNotice_ExamineObject @ 0x004AB7B0`
|
||||
- `gmExaminationUI::RecvNotice_SelectionChanged @ 0x004AB3D0`
|
||||
- `gmExaminationUI::SetActiveExamineUI @ 0x004AB420`
|
||||
- `gmExaminationUI::UseTime @ 0x004AB530`
|
||||
- `gmExaminationUI::SetAppraiseInfo @ 0x004ADAE0`
|
||||
- `BasicCreatureExamineUI::SetAppraiseInfo @ 0x004B3F70`
|
||||
- `CreatureExamineUI::SetAppraiseInfo @ 0x004B3FF0`
|
||||
- `CharExamineUI::SetAppraiseInfo @ 0x004B45F0`
|
||||
- `ItemExamineUI::SetAppraiseInfo @ 0x004B72B0`
|
||||
- `gmFloatyExaminationUI::ResizeTo @ 0x004D4470`
|
||||
- Retail LayoutDesc `0x2100006B`, root `0x100005F2`
|
||||
(`gmFloatyExaminationUI`, authored size 310 x 400 pixels).
|
||||
- ACE:
|
||||
- `GameEventIdentifyObjectResponse`
|
||||
- `AppraiseInfo.Write`
|
||||
- `HookProfile.Write`
|
||||
- ACViewer's `HookAppraisalProfile` representation, used as a second
|
||||
interpretation check for the three-field hook profile.
|
||||
|
||||
The named retail client is the behavior oracle. ACE and ACViewer only clarify
|
||||
the server packet and structure interpretation.
|
||||
|
||||
## Toolbar request
|
||||
|
||||
```text
|
||||
on Examine button:
|
||||
if selected object id != 0:
|
||||
ClientUISystem.ExamineObject(selected id)
|
||||
else:
|
||||
enter TARGET_MODE_EXAMINE
|
||||
```
|
||||
|
||||
This was already present in acdream before this slice and does not need a
|
||||
second command path.
|
||||
|
||||
## Examination request lifetime
|
||||
|
||||
```text
|
||||
on ExamineObject(guid):
|
||||
if guid == 0:
|
||||
return
|
||||
|
||||
if awaitingAppraisalId == 0:
|
||||
ClientUISystem.IncrementBusyCount()
|
||||
|
||||
awaitingAppraisalId = guid
|
||||
send CM_Item.Event_Appraise(guid)
|
||||
clear pending profile pointer
|
||||
```
|
||||
|
||||
Replacing one pending GUID does not add a second busy reference. The response
|
||||
for the current pending GUID releases that one reference.
|
||||
|
||||
## IdentifyObjectResponse packet order
|
||||
|
||||
```text
|
||||
read guid
|
||||
read flags
|
||||
read success
|
||||
|
||||
if IntStatsTable: read packed int table
|
||||
if Int64StatsTable: read packed int64 table
|
||||
if BoolStatsTable: read packed bool table
|
||||
if FloatStatsTable: read packed double table
|
||||
if StringStatsTable: read packed string table
|
||||
if DidStatsTable: read packed DID table
|
||||
if SpellBook: read spell list
|
||||
if ArmorProfile: read armor profile
|
||||
if CreatureProfile: read creature profile
|
||||
if WeaponProfile: read weapon profile
|
||||
if HookProfile: read flags, validLocations, ammoType (three u32)
|
||||
if ArmorEnchantmentBitfield: read highlight/color (two u16)
|
||||
if WeaponEnchantmentBitfield: read highlight/color (two u16)
|
||||
if ResistEnchantmentBitfield: read highlight/color (two u16)
|
||||
if ArmorLevels: read nine u32 armor levels
|
||||
```
|
||||
|
||||
Every gated field is positional. A truncated gated field rejects the packet;
|
||||
returning a partial profile would let later fields masquerade as earlier ones.
|
||||
|
||||
## Applying an appraisal
|
||||
|
||||
```text
|
||||
SetAppraiseInfo(guid, profile):
|
||||
if guid == 0:
|
||||
return
|
||||
if guid != awaitingAppraisalId and guid != currentAppraisalId:
|
||||
return
|
||||
|
||||
firstResponse = false
|
||||
if guid == awaitingAppraisalId:
|
||||
awaitingAppraisalId = 0
|
||||
ClientUISystem.DecrementBusyCount()
|
||||
currentAppraisalId = guid
|
||||
firstResponse = true
|
||||
|
||||
if profile has no CreatureProfile:
|
||||
subview = item
|
||||
else if profile has string property Template (5)
|
||||
or int property EquipmentSetId (0x105):
|
||||
subview = character/player
|
||||
else:
|
||||
subview = creature
|
||||
|
||||
object = ClientObjMaintSystem.GetWeenieObject(guid)
|
||||
if object is absent:
|
||||
return
|
||||
|
||||
titleName = profile string 0x34 if present
|
||||
else object.GetObjectName(NAME_APPROPRIATE)
|
||||
title = titleName with stack count
|
||||
|
||||
newlySelectedInSubview = guid != subview.currentObjectId
|
||||
subview.Init(guid, object)
|
||||
subview.SetAppraiseInfo(profile, newlySelectedInSubview)
|
||||
SetActiveExamineUI(subview)
|
||||
|
||||
if firstResponse:
|
||||
show examination window
|
||||
```
|
||||
|
||||
Only the first response to an explicit request forces the window visible.
|
||||
Background refreshes update the current object without reopening a closed
|
||||
window.
|
||||
|
||||
## Subview and refresh behavior
|
||||
|
||||
```text
|
||||
SetActiveExamineUI(selected):
|
||||
hide item, creature, character, and spell subviews
|
||||
show selected subview only
|
||||
|
||||
on time pulse:
|
||||
if examination window is visible
|
||||
and combat mode is not peace
|
||||
and active subview is creature or character
|
||||
and currentAppraisalId != 0
|
||||
and 0.75 seconds elapsed:
|
||||
send another Event_Appraise(currentAppraisalId)
|
||||
```
|
||||
|
||||
When the examination window's selection-follow option is active, a selection
|
||||
change appraises the new selected GUID; clearing selection hides the window.
|
||||
That optional retail preference remains outside this slice.
|
||||
|
||||
## Item presentation order
|
||||
|
||||
`ItemExamineUI::SetAppraiseInfo` clears and rebuilds the authored item text in
|
||||
this order:
|
||||
|
||||
1. icon;
|
||||
2. value and burden (the EoR layout has no separate value/burden controls, so
|
||||
these enter the main text);
|
||||
3. inscription/signature;
|
||||
4. tinkering/workmanship;
|
||||
5. set and rating information;
|
||||
6. weapon or armor profile;
|
||||
7. defense and armor modifiers;
|
||||
8. magic, special properties, usage and wield requirements;
|
||||
9. caster/boost/healer/capacity/lock/mana/uses/craftsman details;
|
||||
10. sale restriction, rare status, full magic, and description.
|
||||
|
||||
The main text and inscription areas use their authored scrollbars. A newly
|
||||
selected object resets those scroll positions to the top; a refresh of the
|
||||
same object preserves the user's position.
|
||||
|
||||
## Window behavior
|
||||
|
||||
The LayoutDesc root already contains retail's complete floaty window chrome,
|
||||
close control, item/creature/character/spell subviews, and scrollbars.
|
||||
`gmFloatyExaminationUI::ResizeTo` delegates to ordinary `UIElement::ResizeTo`
|
||||
and persists width/height; it adds no special resize algorithm.
|
||||
|
||||
## Slice boundary
|
||||
|
||||
This slice ports the request lifetime, packet integrity, subview choice,
|
||||
authored outer layout, basic report content, scrollbar ownership, and combat
|
||||
refresh. Retail's exact 3D object/creature preview, all specialized
|
||||
`ItemExamineUI` detail branches, selection-follow preference, and
|
||||
`SetInscriptionEditableState` plus inscription submission remain explicit
|
||||
AP-110 work. Until that permission/write transaction exists, the imported
|
||||
inscription field is display-only rather than accepting edits that cannot be
|
||||
sent.
|
||||
Loading…
Add table
Add a link
Reference in a new issue