acdream/docs/research/2026-07-13-retail-paperdoll-slot-backgrounds-pseudocode.md
Erik 27619b2328 docs(ui): record Aetheria slot visual gate
Record the connected 2026-07-13 confirmation that ACE Aetheria states 0/1/3/7 progressively reveal none, blue, blue+yellow, and all slots with the authored backgrounds and no relog.

Co-Authored-By: Codex <codex@openai.com>
2026-07-13 10:00:46 +02:00

121 lines
5.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Retail paperdoll slot backgrounds — pseudocode
Date: 2026-07-13
Scope: the empty-cell art for all 24 equipment lists in `gmPaperDollUI`, including
the three quest-unlocked Aetheria sigil locations. This corrects the earlier visual inference that the slots had no
per-location art. The paperdoll layout elements themselves inherit the same ItemList
base, but retail creates their cells from distinct `UIElement_UIItem` prototypes in
the shared catalog `0x21000037`.
## Retail anchors
- `gmPaperDollUI::GetLocationInfoFromElementID` @ `0x004A37F0`
(`named-retail/acclient_2013_pseudo_c.txt:173620`) — exact paperdoll element-id to
`INVENTORY_LOC`/`EquipMask` mapping.
- `gmPaperDollUI::PostInit` @ `0x004A5360`
(`named-retail/acclient_2013_pseudo_c.txt:175232`) — binds each element as a
single-cell `UIElement_ItemList`; hides only the nine armor lists in doll view.
- `UIElement_ItemList::InternalCreateItem` @ `0x004E3570`
(`named-retail/acclient_2013_pseudo_c.txt:231486`) — resolves the UIItem catalog
through enum-map `0x10000038`, category `5`, key `0x23`, then clones the selected
`UIElement_UIItem` prototype.
- `UIElement_ItemList::ItemList_AddItem` @ `0x004E3F50` and
`gmPaperDollUI::SetUIItemIntoLocation` @ `0x004A5F90` — even an empty location is
represented by a real UIItem cell in its list.
- Live DAT `LayoutDesc 0x21000037` — each prototype's child `0x1000033B`, state
`ItemSlot_Empty`, supplies the exact 32×32 RenderSurface below.
The retained `references/WorldBuilder` tree has no UI implementation. ACE/ACViewer
are interpretation aids for `EquipMask` and surface decoding; neither owns the retail
paperdoll widget behavior. The named client plus live DAT are therefore the oracle.
## Literal pseudocode
```text
PaperDoll.PostInit:
for every authored equipment-list element:
list = GetChildRecursive(elementID).DynamicCast(UIElement_ItemList)
RegisterItemListDragHandler(list, paperDoll)
UpdateItemSlotTooltip(list, empty)
hide the nine armor-location lists in doll view
ItemList.InternalCreateItem:
prototypeID = GetAttribute_Enum(ItemListCellTemplate)
catalog = GetByEnum(UIElementCatalog, LayoutDesc, UIItemCatalog)
return CreateChildElement(list, catalog, prototypeID).DynamicCast(UIElement_UIItem)
PaperDoll.SetUIItemIntoLocation(itemID, locationMask):
for each paperdoll slot whose location bit intersects locationMask:
flush that slot's one-cell item list
add itemID to that list
update its tooltip
UIItem empty draw:
draw prototype.child(0x1000033B).state(ItemSlot_Empty).image
```
## Authored slot-template table
| Paperdoll element | Equip location | UIItem prototype | Empty RenderSurface |
|---|---|---|---|
| `0x100001DA` | NeckWear | `0x10000446` | `0x06000F68` |
| `0x100001DB` | WristWearLeft | `0x10000447` | `0x06000F5D` |
| `0x100001DC` | FingerWearLeft | `0x10000448` | `0x06000F5A` |
| `0x100001DD` | WristWearRight | `0x10000449` | `0x06000F6A` |
| `0x100001DE` | FingerWearRight | `0x1000044A` | `0x06000F6B` |
| `0x100001DF` | weapon composite | `0x1000044B` | `0x06000F66` |
| `0x100001E0` | MissileAmmo | `0x1000044C` | `0x06000F5E` |
| `0x100001E1` | Shield | `0x1000044D` | `0x06000F6C` |
| `0x100001E2` | ChestWear | `0x1000044E` | `0x060032C5` |
| `0x100001E3` | UpperLegWear | `0x1000044F` | `0x060032C4` |
| `0x1000058E` | TrinketOne | `0x1000058F` | `0x06006A6C` |
| `0x100005E9` | Cloak | `0x100005EA` | `0x0600708F` |
| `0x10000595` | SigilOne / blue | `0x10000592` | `0x06006BEF` |
| `0x10000596` | SigilTwo / yellow | `0x10000593` | `0x06006BF0` |
| `0x10000597` | SigilThree / red | `0x10000594` | `0x06006BF1` |
| `0x100005AB` | HeadWear | `0x100005B4` | `0x06006D7F` |
| `0x100005AC` | ChestArmor | `0x100005B5` | `0x06006D7B` |
| `0x100005AD` | AbdomenArmor | `0x100005B6` | `0x06006D79` |
| `0x100005AE` | UpperArmArmor | `0x100005B7` | `0x06006D87` |
| `0x100005AF` | LowerArmArmor | `0x100005B8` | `0x06006D81` |
| `0x100005B0` | HandWear | `0x100005B9` | `0x06006D7D` |
| `0x100005B1` | UpperLegArmor | `0x100005BA` | `0x06006D89` |
| `0x100005B2` | LowerLegArmor | `0x100005BB` | `0x06006D83` |
| `0x100005B3` | FootWear | `0x100005BD` | `0x06006D85` |
## Aetheria unlock visibility
`gmPaperDollUI::UpdateAetheria @ 0x004A3E50` reads player int quality `0x142`
(`PropertyInt.AetheriaBitfield`, 322). `RecvNotice_PlayerDescReceived @ 0x004A43E0`
applies the login value, and `OnQualityChanged @ 0x004A4490` reapplies it after a
live quest-completion update:
```text
bits = player.intQuality(0x142, default=0)
blue.visible = (bits & 1) != 0
yellow.visible = (bits & 2) != 0
red.visible = (bits & 4) != 0
```
ACE independently confirms the property is `[SendOnLogin] AetheriaBitfield = 322`
and defines the same Blue=1, Yellow=2, Red=4 flags. The existing generic private
PropertyInt wire path (`0x02CD`) already stores the live value on the player object.
Live gate passed 2026-07-13 (`edc9be30`): ACE `/enable-aetheria 0/1/3/7`
progressively revealed none/blue/blue+yellow/all three immediately, with the correct
authored background for each color and no relog.
## Port mapping
- `PaperdollSlotBackgrounds` is the single table for element-id, `EquipMask`, and
UIItem prototype. `PaperdollController` consumes the same definitions for behavior,
preventing the visual and equip-location maps from drifting apart.
- `ItemListCellTemplate.ResolvePrototypeEmptySprite` reuses the already-shipped
inheritance-aware `ItemSlot_Empty` resolver instead of hardcoding surface IDs in
the controller.
- `RetailUiRuntime` and UI Studio resolve the 24 surfaces from the live DAT once at
bind time and inject them into the controller. A missing authored surface is a startup
error rather than a silently generic slot; the old inventory-grid sprite remains only
for isolated controller bindings that do not supply the paperdoll catalog map.