# 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` - `ItemExamineUI::HandleInscriptionGainingFocus @ 0x004AC0E0` - `ItemExamineUI::HandleInscriptionMousePresses @ 0x004AC230` - `ItemExamineUI::SetInscriptionEditableState @ 0x004AC720` - `ItemExamineUI::SetInscription @ 0x004AE2F0` - `ItemExamineUI::HandleInscriptionLosingFocus @ 0x004AE620` - `gmFloatyExaminationUI::ResizeTo @ 0x004D4470` - `AppraisalProfile::IsHookedItemInscribable @ 0x005B2F90` - `HookAppraisalProfile::IsInscribable @ 0x005B64C0` - `CM_Writing::Event_SetInscription @ 0x006A98B0` - `PublicWeenieDesc::BF_INSCRIBABLE` in `acclient.h:6434` - Retail LayoutDesc `0x2100006B`, root `0x100005F2` (`gmFloatyExaminationUI`, authored size 310 x 400 pixels). - ACE: - `IdentifyResponseFlags` - `GameEventIdentifyObjectResponse` - `AppraiseInfo.Write` - `HookProfile.Write` - `GameActionSetInscription` - `Player.HandleActionSetInscription` - 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 The flag values are part of the wire contract and are not the field-order indices: ```text IntStatsTable 0x0001 BoolStatsTable 0x0002 FloatStatsTable 0x0004 StringStatsTable 0x0008 SpellBook 0x0010 WeaponProfile 0x0020 HookProfile 0x0040 ArmorProfile 0x0080 CreatureProfile 0x0100 ArmorEnchantmentBitfield 0x0200 ResistEnchantmentBitfield 0x0400 WeaponEnchantmentBitfield 0x0800 DidStatsTable 0x1000 Int64StatsTable 0x2000 ArmorLevels 0x4000 ``` ACE writes the gated bodies in the order below, independent of the numerical position of each flag. ```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. The root's concrete retail type is `gmFloatyExaminationUI`, not a child of `gmPanelUI`. It is mounted and persisted as its own top-level retained window. Showing it must not hide, replace, resize, or reposition the shared Inventory/Skills/Spellbook panel. `gmFloatyExaminationUI::ResizeTo` delegates to ordinary `UIElement::ResizeTo` and persists width/height; it adds no special resize algorithm. ## Inscription presentation and eligibility ```text SetInscription(profile): reset inscription field to its unfocused state clear displayed text, signature, old inscription, scribe name/account inscribable = object.PublicWeenieDesc has BF_INSCRIBABLE if object is a hook: inscribable = profile.HookProfile exists and (HookProfile.Flags & 1) != 0 if not inscribable: hide inscription field and signature return scribe = profile string property 8, or empty oldInscription = profile string property 7, or empty if scribe is empty: field = "" signature = "" else: field = oldInscription signature = "--" + scribe if local player is a PSR and scribe-account property 0x17 exists: signature += " <" + scribeAccount + ">" show field and signature SetInscriptionEditableState() SetInscriptionEditableState: editable = object exists and object has BF_INSCRIBABLE and object is owned by the local player and (scribe is empty or case-insensitive scribe == local player name) PSR may override the ownership/scribe restriction field.Editable = editable field.Selectable = editable inscription background receives mouse presses only when not editable ``` The ordinary-player port does not fabricate PSR status. It applies the exact ownership/name rule; an eventual PSR player-description capability can add the retail override without changing this transaction. For hooks, the appraisal HookProfile is the source of truth for presentation: `AppraisalProfile::IsHookedItemInscribable` returns false when it is absent and otherwise returns bit 0. The live public description remains the source used by `SetInscriptionEditableState`, matching the two retail functions rather than merging them into one guessed rule. Noneditable mouse presses report retail's exact reason: ```text if scribe is nonempty and differs from local player: "Only can change the inscription" else if object is absent or not owned: "Item must be in your inventory to inscribe." else if BF_INSCRIBABLE is absent: "This item is not inscribable." ``` ## Inscription edit transaction ```text on inscription focus gained: if scribe is empty: switch to the normal editing state clear "" signature = "--" + local player name on inscription focus lost: if current object id == 0: return text = current field text skipSend = text is empty and original scribe is empty if not skipSend and text != oldInscription: send CM_Writing.Event_SetInscription(object id, text) if text is empty: restore unfocused state field = "" signature = "" scribe name/account = "" else: scribe name = local player name signature = "--" + local player name oldInscription = text ``` `CM_Writing::Event_SetInscription` sends a normal UI-counter GameAction: ```text u32 0xF7B1 GameAction envelope u32 uiCounter u32 0x00BF SetInscription u32 objectGuid String16L text CP-1252, padded to a four-byte boundary ``` There is no successful server reply in retail captures. The client therefore updates its field/signature optimistically after sending; ACE deliberately implements the same behavior. ## Remaining slice boundary The request lifetime, exact packet flags, independent floaty window, inscription permission/edit/write transaction, subview choice, authored outer layout, basic report content, scrollbar ownership, and combat refresh are in scope. Retail's exact 3D object/creature preview, exhaustive specialized `ItemExamineUI` detail branches, selection-follow preference, and PSR-only scribe-account/override behavior remain explicit AP-110 work.