# 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.