# 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::Init @ 0x004AB9C0` - `BasicCreatureExamineUI::BasicCreatureExamineUI @ 0x004ACD30` - `BasicCreatureExamineUI::SetLevelValueText @ 0x004B3E70` - `BasicCreatureExamineUI::SetAppraiseInfo @ 0x004B3F70` - `CreatureExamineUI::SetAppraiseInfo @ 0x004B3FF0` - `CharExamineUI::SetAppraiseInfo @ 0x004B45F0` - `AttributeInfoRegion::Update @ 0x004F1D90` - `Attribute2ndInfoRegion::Update @ 0x004F1E80` - `AppraisalSystem::InqCreatureDisplayName @ 0x005B59E0` - `ItemExamineUI::SetAppraiseInfo @ 0x004B72B0` - `ItemExamineUI::AddItemInfo @ 0x004AC050/0x004ADCA0` - `ItemExamineUI::Appraisal_ShowValueInfo @ 0x004ADEA0` - `ItemExamineUI::Appraisal_ShowBurdenInfo @ 0x004AE180` - `ItemExamineUI::Appraisal_ShowCapacity @ 0x004B2680` - `ItemExamineUI::Appraisal_ShowLockAppraiseInfo @ 0x004B2790` - `AppraisalProfile::HasHookedItem @ 0x00525C70` - `AppraisalProfile::InqIntEnchantmentMod @ 0x005B2C10` - `AppraisalProfile::InqFloatEnchantmentMod @ 0x005B2C70` - `AppraisalSystem::LockpickSuccessPercentToString @ 0x005B4600` - `ACCWeenieObject::IsHook @ 0x0058C660` - `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 ``` `ACCWeenieObject::GetObjectName @ 0x0058E6E0` does more than pluralize: ```text baseName = singular or plural name selected for the live stack size if PublicWeenieDesc.MaterialType > 0: material = AppraisalSystem.InqMaterialName(MaterialType) remove material from baseName when it is already authored there trim the remaining baseName baseName = material + " " + remaining baseName return baseName ``` The title therefore uses the live CreateObject material field. For example, an authored `Steel Toed Boots` object whose material is `Reed Shark Hide` is titled `Reed Shark Hide Steel Toed Boots`; appraisal property 131 is for description decoration and is not a substitute for this live name field. 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) ``` `gmExaminationUI::gmExaminationUI @ 0x004AB2B0` initializes `m_examineNewlySelectedItem` to one. Selection-follow is therefore retail's default examination behavior, not an optional preference: ```text on SelectionChanged: if examination window is not visible: return if selected object id != 0: ClientUISystem.ExamineObject(selected id) else: hide examination window ``` ## Creature presentation `BasicCreatureExamineUI` binds viewport `0x10000148`, base-stat list box `0x10000149`, miscellaneous-rating list box `0x10000335`, level value `0x1000014C`, and creature display name `0x1000014E`. Its rows are instances of LayoutDesc `0x2100006B` template `0x10000166`, whose label and value fields are `0x1000012A` and `0x1000012B`. ```text on creature appraisal: level = int property 25 level text = decimal level when positive, otherwise "???" creatureType = int property 2 display name = EnumMapper 0x2200000E[creatureType] replace underscores in display name with spaces clear stat list append rows in this exact order: Strength primary attribute 1 Endurance primary attribute 2 Coordination primary attribute 4 Quickness primary attribute 3 Focus primary attribute 5 Self primary attribute 6 Health current secondary 2 / maximum secondary 1, show percent Stamina current secondary 4 / maximum secondary 3 Mana current secondary 6 / maximum secondary 5 clear miscellaneous list damageRating = int property 0x133 damageResistRating = int property 0x134 critRating = int property 0x139 critDamageRating = int property 0x13A critResistRating = int property 0x13B critDamageResist = int property 0x13C healingBoostRating = int property 0x143 dotResistRating = int property 0x15E lifeResistRating = int property 0x15F if any displayed rating group is nonzero: append blank row if damageRating or critRating or critDamageRating is positive: append "Dmg/CritDmg" = "Rating: damageRating/critDamageRating" if damageResistRating or critResistRating or critDamageResist is positive: append "Dmg/CritDmg" = "Resist: damageResistRating/critDamageResist" if dotResistRating or lifeResistRating is positive: append "DoT/Life:" = "Resist: dotResistRating/lifeResistRating" append blank row ``` Successful primary values are decimal integers. Successful Health is `current/max (percent %)`; Stamina and Mana are `current/max`. On a failed assessment, Health still shows the percentage when both values are known, while every other row is `???`. The profile highlight/color bitfields select one of retail's four authored FontInfo entries (normal, beneficial, detrimental, or incomplete); exact FontInfo-list binding remains AP-110. The 2013 client reads HealingBoost but does not append it to this creature rating list. CritRating and CritResistRating determine whether their paired row exists, while the displayed right-hand value is CritDamageRating or CritDamageResistRating exactly as retail formats it. The authored viewport and list overlap. Retail draws the creature between the row chrome and the row text. The modern retained RTT seam reproduces that ordering with two synchronized copies of the same DAT row template: media-only chrome below the viewport and media-free label/value descendants above it. Both copies share one pixel scroll model. The 292-pixel authored row is inset four pixels within the 300-pixel list surface, giving the text a balanced left/right margin without changing template geometry. The animated preview is a private `CreatureMode`, not the live world object: ```text clear the previous private creature clone selected CPhysicsObj with makeObject set clone heading to 191.367905 degrees query the clone bounding box width = bbox.max.x - bbox.min.x depth = bbox.max.y - bbox.min.y height = bbox.max.z - bbox.min.z viewportHeightOverWidth = viewport.height / viewport.width fittedVerticalSpan = max(height, width * viewportHeightOverWidth) center = (bbox.min + bbox.max) / 2 camera.position = ( center.x, center.y - (fittedVerticalSpan * 1.20710683 + depth / 2), center.z) camera.direction = identity (+Y) viewport light: type = DISTANT_LIGHT intensity = 2 direction = (0.3, 1.9, 0.65) insert clone into the private CreatureMode ``` ACDream's modern rendering equivalent uses the same isolated render-to-texture owner as the inventory paperdoll. The examination clone has its own stable render identity, fixed heading and retail camera/light, while its mesh-part transforms are refreshed from the live target each frame so its current animation is visible without registering a second gameplay entity. Retail's clone instead advances the motion state copied by `CPhysicsObj::MorphToExistingObject`; that narrow sequencer-ownership adaptation is recorded under AP-92. ## 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 individual branches are not a generic property dump. They project the wire profile into retail prose: ```text AddItemInfo(text, fontColorIndex, sameParagraph): if the report already contains text: append sameParagraph ? "\n" : "\n\n" append text with font-list index 0 and the requested color-list index # Empty text is meaningful. The separator is still appended, so # AddItemInfo("", ..., true) preserves an intentional blank row. ShowValueAndBurden(profile): if profile contains int 19: append "Value: " else: append "Value: ???" if profile contains int 5: append "Burden: " else: append "Burden: Unknown" IsHook(object): return PublicWeenieDesc.HookType != 0 and PublicWeenieDesc.HookItemTypes != TYPE_UNDEF UnpackCapacityByte(wireByte): # PublicWeenieDesc::UnPack uses MOVSX at 0x005AD530 and 0x005AD545. # The generated pseudocode's uint8_t temporary is misleading. return sign_extend_int8_to_int32(wireByte) ShowCapacity(object, profile): if object is not a hook or profile does not contain a HookProfile: append authored item/container capacity as a new paragraph if profile contains PageCount (175) and PageUsed (174): append " of pages full." as a new paragraph # A hook with a HookProfile describes its mounted item, so the hook's own # capacity is not player-facing capacity. # Ordinary creature/NPC-lookalike templates can carry -1/-1 capacity # sentinels. They travel as FF/FF but UnpackCapacityByte restores -1/-1, # so the routine's > 0 tests emit no capacity prose. Black Phyntos Hive # (WCID 28249) is this case: Creature, not Hook. ShowLock(object, profile): if object is a hook: return if Locked bool is absent: append nonzero LockpickResistance as "Bonus to Lockpick Skill: " return append "Unlocked" and return when Locked is false append "Locked" if LockpickResistance is absent: append "You can't tell how hard the lock is to pick." else if LockpickSuccessPercent is present: map it through LockpickSuccessPercentToString append "The lock looks to pick (Resistance )." FontColor(profileBitfield, lowBit): if lowBit is absent: return normal color index 0 if the corresponding bit 16 positions higher is present: return beneficial color index 1 return detrimental color index 2 ShowWeaponAndArmor(validLocations, profile): ammoType = live PublicWeenieDesc ammo type if object is a hook: ammoType = HookProfile ammo type if validLocations contains Shield: append "Base Shield Level: " append the player-dependent effective Shield level when available if validLocations contains a weapon location: weapon = profile.WeaponProfile append "Skill: " if WeaponType exists: append the parenthesized weapon subtype launcher = validLocations contains MissileWeapon and ammoType != None append launcher ? "Damage Bonus: " : "Damage: " minimum = (1 - DamageVariance) * Damage if Damage - minimum > 0.0002: append " - " else: append "" append damage type for non-launchers append Elemental Damage Bonus when present append launcher Damage Modifier when present if validLocations contains MeleeWeapon, MissileWeapon, or TwoHanded: append "Speed: ()" categories: raw < 11 -> Very Fast raw < 31 -> Fast raw < 50 -> Average raw < 80 -> Slow otherwise -> Very Slow if launcher: rangeYards = min( 85, 2 * MaxVelocity^2 * (1 / 9.8) * 1.094) if rangeYards < 10: displayedRange = ceil(rangeYards) else: displayedRange = truncate down to a multiple of 5 append "Range: yds." if MaxVelocityEstimated: append " (based on STRENGTH 100)" if WeaponOffense != 1 and this is not launcher damage: append "Bonus to Attack Skill: ." append the retail ammunition sentence: ammunition -> "Used as ammunition by bows/crossbows/atlatls." launcher -> "Uses arrows/quarrels/atlatl darts as ammunition." ShowArmor(profile): append "Armor Level: " for Slashing, Piercing, Bludgeoning, Fire, Cold, Acid, Electric, Nether: adjective from DamageResistanceToString: 0.0 -> None (0.0, 0.4) -> Poor [0.4, 0.8) -> Below Average [0.8, 1.2) -> Average [1.2, 1.6) -> Above Average [1.6, 2.0) -> Excellent >= 2.0 -> Unparalleled append ": ()" ShowMagic(profile): if the spellbook exists and appraisal failed: append "Spells: unknown." return split spell ids by the high enchantment bit resolve every id with that bit masked through ClientMagicSystem for ordinary spells: append Spellcraft, Mana, and Mana Cost when present append "Spell Descriptions:" append each spell name and its DAT description for high-bit enchantments: append "Enchantments:" append each spell name and its DAT description ShowSpecialProperties(profile): append carry limit and authored cooldown duration when present append cleave count build one "Properties: " line from: creature slayer, Multi-Strike, imbued-effect flags, Magic Absorbing, Unenchantable, Attuned/Bonded/death behavior, Retained, Crushing Blow, Biting Strike, Armor/Resistance Cleaving, Cast on Strike, Ivoryable, and Dyeable if an imbued-effect flag exists: append "This item cannot be further imbued." if AutowieldLeft: append the left-hand tether sentence ShowRemainingItemFields(profile): append, in SetAppraiseInfo order: usage text level, direct heritage restriction, and wield requirements (up to four requirement triplets) use restrictions item level and item XP activation requirements caster, boost, healing, capacity, lock, and mana-stone data remaining key/charge counts creator, last tinkerer, and imbuer cannot-be-sold and rare notices short and long descriptions ``` ### Exhaustive EoR item-report branches The common projection above was re-audited against every helper called by `ItemExamineUI::SetAppraiseInfo @ 0x004B72B0`. The remaining item branches are not generic property labels; retail applies the following exact rules. ```text ShowTinkering: if NumTimesTinkered (171): "This item has been tinkered %d time[s]." if TinkerName (39): "Last tinkered by %s." if ImbuerName (40): "Imbued by %s." if Workmanship (105): if NumItemsInMaterial (170) is absent: "Workmanship: ()" else: average = workmanship / NumItemsInMaterial "Workmanship: ()" blank line "Salvaged from %d items." append an empty same-paragraph entry unconditionally ShowSet: EquipmentSetId (265) selects the literal EoR set-name table. Unknown and explicitly unused entries produce no row. Known entries produce "Set: ". ShowRatings: append only positive ratings, in this order: Dam, Dam Resist, Crit, Crit Dam, Crit Resist, Crit Dam Resist, Heal Boost, Nether Resist, Life Resist render as "Ratings: Dam 3, Crit 2" (there is no plus sign) positive Vitality (379) is a separate "This item adds %d Vitality." if any rating or Vitality was emitted: append an empty same-paragraph entry SetAppraiseInfo appends one more empty entry when Set or Ratings emitted ShowWeaponAndArmorData: only interpret WeaponProfile inside weapon/shield valid-location bits preserve retail Unknown sentinels for damage, speed, and range for ordinary clothing append: "Covers Head, Chest, Abdomen, Upper Arms, Lower Arms, Hands, Upper Legs, Lower Legs, Feet" selecting the names from ClothingPriority bits in that exact order ShowArmorModsData: the first "Armor Level" string itself begins with "\n" AddItemInfo also contributes its ordinary separator, leaving one blank row between clothing coverage and Armor Level ShowSpecialProperties: begin with an empty same-paragraph entry carry-limit text has another empty entry before it shared cooldown metadata and cleave have their retail trailing empty entry ShowShortMagic: list only ordinary spell ids (high bit clear) a failed appraisal prints "Spells: unknown." ShowMagic: a failed appraisal also prints "Spells: unknown." ordinary spell rows are: "Spell Descriptions:\n~ : " enchantment rows are: "Enchantments:\n~ : " ShowLevelLimits: min == max: "Restricted to characters of Level %d." min + max: "Restricted to characters of Levels %d to %d." min only: "Restricted to characters of Level %d or greater." max only: "Restricted to characters of Level %d or below." ShowItemLevel: require ItemBaseXp (int64 5), ItemMaxLevel (319), and ItemXpStyle (320) currentLevel = ItemTotalXPToLevel(ItemTotalXp, baseXp, maxLevel, style) displayedLevel = min(currentLevel + 1, maxLevel) nextThreshold = ItemLevelToTotalXP(min(currentLevel + 1, maxLevel), ...) "Item Level: / " "Item XP: / " append an empty same-paragraph entry after Item XP ItemLevelToTotalXP(level, base, max, style): clamp level to [0, max] style 1: base * level style 2: base * (2^level - 1) style 3: base * level * (level + 1) / 2 ShowActivationRequirements: successful appraisals only append Arcane Lore, Allegiance Rank, Heritage, skill, primary-attribute, and secondary-attribute requirements to one "Activation requires " row in that order ShowBoostValue / ShowHealKitValues: ordinary consumables render Health/Mana "... when used." Stamina is "... when consumed." healer and hooked-healer profiles instead interpret BoostValue as "Bonus to Healing Skill" and float 100 as "Restoration Bonus" ShowRareInfo: RareUsesTimer (bool 108): "This rare item has a timer restriction of 3 minutes. You will not be able to use another rare item with a timer within 3 minutes of using this one." RareId (17): "Rare #%d" ShowDescription: if lifespan, creation timestamp, and remaining lifespan are present: remaining < 0: "This item is in the act of disintegrating." otherwise: "This item expires in " LongDesc replaces ShortDesc when present; both are never printed GearPlatingName (52) replaces LongDesc before decoration AppraisalItemSkill (172) decorates that description: bit 1 prepends workmanship MaterialType (131) prepends the DAT material display name and removes its first occurrence from the authored description bit 4 appends ", set with " PortalBitmask (111) appends, in bit order: Player Killers may not use this portal. Lite Player Killers may not use this portal. Non-Player Killers may not use this portal. This portal cannot be recalled nor linked to. This portal cannot be summoned. ``` The material name is not a locally invented table. `MaterialTypeEnumMapper::MaterialTypeToString @ 0x005CD500` performs `DBObj::GetByEnum(DBObjType=0x28, clientEnum=0x10000001, subEnum=1)`: the master map resolves the root `EnumIDMap`, sub-enum 1 resolves the material `DualEnumIDMap`, and its client name is read with underscores replaced by spaces. Creature/slayer names use `EnumMapper::GetString(0x10000005, value)`; heritage restrictions use `EnumMapper::GetString(0x10000002, value)` with retail's explicit Gharu'ndim/Umbraen/Olthoi spellings. Additional named sources for these branches: - `ItemExamineUI::Appraisal_ShowSet @ 0x004AE880` - `ItemExamineUI::Appraisal_ShowRatings @ 0x004AF080` - `ItemExamineUI::Appraisal_ShowTinkeringInfo @ 0x004B0E70` - `ItemExamineUI::Appraisal_ShowLevelLimitInfo @ 0x004B2920` - `ItemExamineUI::Appraisal_ShowItemLevelInfo @ 0x004B3C60` - `ItemExamineUI::Appraisal_ShowDescription @ 0x004B6990` - `ExperienceSystem::ItemLevelToTotalXP @ 0x005C8100` - `ExperienceSystem::ItemTotalXPToLevel @ 0x005C81B0` - `MaterialTypeEnumMapper::MaterialTypeToString @ 0x005CD500` Sources: - `ItemExamineUI::SetAppraiseInfo @ 0x004B72B0` - `ItemExamineUI::Appraisal_ShowWieldRequirements @ 0x004AF9A0` - `ItemExamineUI::Appraisal_ShowSpecialProperties @ 0x004B0140` - `ItemExamineUI::Appraisal_ShowWeaponAndArmorData @ 0x004B10E0` - `ItemExamineUI::Appraisal_ShowArmorModsData @ 0x004B1F60` - `ItemExamineUI::Appraisal_ShowMagicInfo @ 0x004B2E10` - `ItemExamineUI::Appraisal_ShowActivationRequirements @ 0x004B3270` - `AppraisalSystem::WeaponTimeToString @ 0x005B4970` - `AppraisalSystem::DamageResistanceToString @ 0x005B5490` - `AppraisalSystem::InqWorkmanshipAdjective @ 0x005B48E0` - `ClientUISystem::DeltaTimeToString @ 0x00565E10` ACE's `AppraiseInfo` serializer and property enums were used to pin the typed-table IDs carried by the network profile. ACViewer's portal.dat SpellTable path was used as the second reference check that item spell names and descriptions come from DAT spell metadata rather than a hand-maintained client list. Retail's named functions remain the behavioral oracle. 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. LayoutDesc `0x2100006B` element `0x1000013C` authors the item report's font-color list as white, green, and red. Those are the concrete colors selected by the `AddItemInfo` indices above; they are not hard-coded by appraisal logic. ## 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. The authored root is exactly 310 by 400 pixels and has 310-by-400 minimum constraints. Its maximum constraints are 2000 by 2000, so 310 by 400 is the retail opening/default size, not a fixed-size panel. The main item report control is authored with bottom vertical justification, but `ItemExamineUI::AddItemInfo` appends rows from the start of the cleared text surface. The retained projection must therefore lay out that generated report from the top while leaving the separate authored inscription behavior intact. ## 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, ordered creature stats and ratings, animated creature preview, viewport/list/text compositing, selection-follow, scrollbar ownership, combat refresh, and the complete EoR item-report dispatch are in scope. Item rows now preserve retail's appraisal-only unknown values, hook/capacity/lock presence rules, exact paragraph boundaries, authored normal/beneficial/detrimental font-color selection, equipment-set/ratings/tinkering/weapon/armor/magic/XP/ activation/healer/rare/description ordering, and installed-DAT material and creature names. Retail's item-object preview, live player-dependent effective shield/cooldown projections, localized augmentation-cost `StringInfo`, creature incomplete/high/low FontInfo selection, complete character detail, and PSR-only scribe-account/override behavior remain explicit AP-110 work.