fix(ui): match retail item appraisal semantics

Preserve PublicWeenieDesc hook identity from CreateObject through the item model so hook appraisals suppress sentinel capacities exactly. Use appraisal-only Value and Burden presence, retain AddItemInfo paragraph and authored font-color selection, and port retail lock, page, enchantment, and spell-block formatting.

Co-authored-by: Codex <codex@openai.com>
This commit is contained in:
Erik 2026-07-23 18:04:19 +02:00
parent bc47bc4917
commit d3c5e06fdd
21 changed files with 982 additions and 145 deletions

View file

@ -107,7 +107,21 @@ text out from the top, and replaces the sparse property dump with retail's
ordered item-report branches for common weapons, armor, magic and DAT spell
descriptions, requirements, capacities, cooldown/special properties, uses,
crafting, ratings, rare state, and prose. AP-110 records the remaining
specialized/player-dependent/DAT-name/font/object-preview branches explicitly.
specialized/player-dependent/DAT-name/creature-font/object-preview branches
explicitly.
The item-format conformance correction then fixed the live Black Phyntos Hive
counterexample at its source. CreateObject had walked past
`PublicWeenieDesc.HookItemTypes` and `HookType` without retaining them, so the
item formatter could not execute retail `ACCWeenieObject::IsHook`. It also
fell back from a deliberately incomplete appraisal to public Value/Burden and
printed the hook's 255/255 sentinel capacities as ordinary storage. Hook
identity now survives the wire/session/object-table path; Value/Burden use
only the appraisal profile and display `???`/`Unknown` when absent;
`HasHookedItem` suppresses the hook capacity; lock wording and page-count
property order match retail; and each report fragment preserves
`AddItemInfo`'s line/paragraph flag plus LayoutDesc `0x1B`'s white/green/red
font-color indices.
**Files:** `src/AcDream.Core.Net/Messages/AppraiseInfoParser.cs`;
`src/AcDream.App/UI/RetailUiRuntime.cs`;
@ -129,6 +143,9 @@ animated target remains above row chrome but below text; changing selection
updates the open window without another magnifier click. Item text begins at
the top and assessed melee/missile/armor/magic items show their retail-ordered
stats and full DAT spell descriptions.
The Black Phyntos Hive specifically reads `Value: ???`, `Burden: Unknown`,
then its description after one retail paragraph break, without the bogus
255-item/255-container line.
---

View file

@ -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/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 has its independent authored floaty layout, inscription transaction, retail creature stat/rating/animated-preview presentation, default selection-follow, and retail-ordered item reports covering weapon/armor/caster modifiers, spell names plus DAT descriptions, requirements, capacities, cooldown/special properties, uses, crafting, rare, and description data. It still lacks item-object preview, player-dependent effective shield projection, DAT display-name resolution for equipment sets and slayer creature types, some expiry/material/portal/PK-specialized description branches, exhaustive character detail regions, and exact appraisal FontInfo-list selection. | `src/AcDream.App/UI/RetailUiRuntime.cs`; `src/AcDream.App/UI/Layout/AppraisalUiController.cs`; `src/AcDream.App/UI/Layout/ItemAppraisalTextFormatter.cs`; `src/AcDream.App/UI/Layout/CreatureAppraisalRows.cs`; `src/AcDream.App/Rendering/CreatureAppraisalPresentation.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/inscription/creature-preview/item-report loop cover the active loops; the remaining examination mechanisms require player-state or DAT display-name resolvers and object-preview ownership rather than fabricated content | Uncommon item/character assessments may omit the listed specialized branches and item-object presentation; unresolved equipment-set/slayer values use their numeric identity; enchanted/incomplete appraisal rows use the normal authored font until the exact FontInfo list is bound; other absent panels remain unavailable; real packet loss is displayed as 0.00% instead of retail's moving average | `BasicCreatureExamineUI::Init @ 0x004AB9C0`; `CreatureExamineUI::SetAppraiseInfo @ 0x004B3FF0`; `gmExaminationUI::RecvNotice_SelectionChanged @ 0x004AB3D0`; `AttributeInfoRegion::Update @ 0x004F1D90`; `gmExaminationUI::SetAppraiseInfo @ 0x004ADAE0`; `ItemExamineUI::SetAppraiseInfo @ 0x004B72B0`; `ItemExamineUI::Appraisal_ShowSpecialProperties @ 0x004B0140`; `ItemExamineUI::Appraisal_ShowWeaponAndArmorData @ 0x004B10E0`; `ItemExamineUI::Appraisal_ShowMagicInfo @ 0x004B2E10`; `ItemExamineUI::SetInscription @ 0x004AE2F0`; `CM_Writing::Event_SetInscription @ 0x006A98B0`; `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 has its independent authored floaty layout, inscription transaction, retail creature stat/rating/animated-preview presentation, default selection-follow, and retail-ordered item reports covering weapon/armor/caster modifiers, spell names plus DAT descriptions, requirements, capacities, cooldown/special properties, uses, crafting, rare, and description data. Item reports also preserve appraisal-only unknowns, hook/capacity/lock presence rules, paragraph boundaries, and the authored normal/beneficial/detrimental color list. It still lacks item-object preview, player-dependent effective shield projection, DAT display-name resolution for equipment sets and slayer creature types, some expiry/material/portal/PK-specialized description branches, exhaustive character detail regions, and exact creature appraisal FontInfo-list selection. | `src/AcDream.App/UI/RetailUiRuntime.cs`; `src/AcDream.App/UI/Layout/AppraisalUiController.cs`; `src/AcDream.App/UI/Layout/ItemAppraisalTextFormatter.cs`; `src/AcDream.App/UI/Layout/CreatureAppraisalRows.cs`; `src/AcDream.App/Rendering/CreatureAppraisalPresentation.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/inscription/creature-preview/item-report loop cover the active loops; the remaining examination mechanisms require player-state or DAT display-name resolvers and object-preview ownership rather than fabricated content | Uncommon item/character assessments may omit the listed specialized branches and item-object presentation; unresolved equipment-set/slayer values use their numeric identity; enchanted/incomplete creature appraisal rows use the normal authored font until the exact FontInfo list is bound; other absent panels remain unavailable; real packet loss is displayed as 0.00% instead of retail's moving average | `BasicCreatureExamineUI::Init @ 0x004AB9C0`; `CreatureExamineUI::SetAppraiseInfo @ 0x004B3FF0`; `gmExaminationUI::RecvNotice_SelectionChanged @ 0x004AB3D0`; `AttributeInfoRegion::Update @ 0x004F1D90`; `gmExaminationUI::SetAppraiseInfo @ 0x004ADAE0`; `ItemExamineUI::SetAppraiseInfo @ 0x004B72B0`; `ItemExamineUI::AddItemInfo @ 0x004AC050`; `ItemExamineUI::Appraisal_ShowCapacity @ 0x004B2680`; `ItemExamineUI::Appraisal_ShowSpecialProperties @ 0x004B0140`; `ItemExamineUI::Appraisal_ShowWeaponAndArmorData @ 0x004B10E0`; `ItemExamineUI::Appraisal_ShowMagicInfo @ 0x004B2E10`; `ItemExamineUI::SetInscription @ 0x004AE2F0`; `CM_Writing::Event_SetInscription @ 0x006A98B0`; `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` |

View file

@ -144,7 +144,11 @@ damage/critical/resistance rating list, places authored row chrome behind the
animated preview and text in front, adds the balanced row inset, and follows
the current selection automatically while the examination window is visible.
Item-object preview, specialized item/character detail regions, and exact
appraisal font-state selection remain the narrowed AP-110 residual. Research:
creature appraisal font-state selection remain the narrowed AP-110 residual.
The item report now retains `PublicWeenieDesc` hook identity, applies
appraisal-only Value/Burden unknowns, suppresses mounted-hook sentinel
capacities, preserves retail line/paragraph boundaries, and selects the
authored white/green/red item font-color entries. 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,496 App
@ -172,14 +176,26 @@ ports common weapon damage ranges/speed/range/ammunition, armor protection
bands, defense/caster modifiers, workmanship, ratings, wield/use/activation
requirements, item XP/capacity/lock/mana/uses/creator/rare data, cooldown and
imbued special properties, and both short spell lists and full DAT spell
descriptions. AP-110 now names only the remaining specialized,
player-dependent, DAT-display-name, font-state, and object-preview gaps.
descriptions. The conformance follow-up replaces public-value fallbacks with
retail's appraisal presence semantics, restores hook/capacity/lock behavior,
and carries each `AddItemInfo` fragment's paragraph and font-color index into
the retained text shaper. AP-110 now names only the remaining specialized,
player-dependent, DAT-display-name, creature-font-state, and object-preview gaps.
Focused conformance fixtures cover the geometry layering plus melee, launcher,
armor, spell, and special-property reports. The Release solution build passes
with the 17 warnings already tracked by #228, 3,514 App tests / 3 skips, and
7,931 complete-solution tests / 5 skips. The connected visual result remains
the closeout gate.
The item-format conformance correction adds literal hook-tail cursor fixtures,
wire/session/object-table propagation tests, the exact Black Phyntos Hive
report, capacity/page/lock presence cases, structured paragraph checks, and
LayoutDesc color-palette/enchantment-style coverage. The isolated Release
solution build passes with the same 17 tracked warnings; 3,522 App tests / 3
skips and 7,942 complete-solution tests / 5 skips pass while the previously
launched client retains the normal Release output lock. The corrected
connected item visual remains the closeout gate.
## Slice 1 — spell-bar overflow arrows
### Retail oracle

View file

@ -23,6 +23,16 @@ retained examination window.
- `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`
@ -333,6 +343,57 @@ 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
ShowValueAndBurden(profile):
if profile contains int 19:
append "Value: <grouped value>"
else:
append "Value: ???"
if profile contains int 5:
append "Burden: <grouped burden>"
else:
append "Burden: Unknown"
IsHook(object):
return PublicWeenieDesc.HookType != 0
and PublicWeenieDesc.HookItemTypes != TYPE_UNDEF
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 "<used> of <count> pages full." as a new paragraph
# A hook with a HookProfile describes its mounted item. Its sentinel
# 255/255 PublicWeenieDesc capacities are not player-facing capacity.
ShowLock(object, profile):
if object is a hook:
return
if Locked bool is absent:
append nonzero LockpickResistance as
"Bonus to Lockpick Skill: <signed value>"
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 <grade> to pick (Resistance <value>)."
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:
@ -470,6 +531,10 @@ 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,
@ -603,7 +668,10 @@ 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, and
combat refresh are in scope. Retail's item-object preview, exhaustive
specialized `ItemExamineUI` detail branches, exact appraisal FontInfo-list
combat refresh are in scope. Common item report rows now preserve retail's
appraisal-only unknown values, hook/capacity/lock presence rules, exact
paragraph boundaries, and authored normal/beneficial/detrimental font-color
selection. Retail's item-object preview, exhaustive specialized
`ItemExamineUI` detail branches, creature incomplete/high/low FontInfo
selection, and PSR-only scribe-account/override behavior remain explicit
AP-110 work.

View file

@ -437,7 +437,7 @@ Per-flag (in exact order):
0x00400000 SpellId : uint16
0x02000000 OwnerId : uint32
0x04000000 Restrictions : RestrictionDB (house perms)
0x20000000 HookItemTypes : uint16 (HookType)
0x20000000 HookItemTypes : uint32 (ITEM_TYPE mask)
0x00000040 MonarchId : uint32
0x10000000 HookType : uint16
0x40000000 IconOverlay : packedDWORD (minus 0x06000000)