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

5.8 KiB
Raw Blame History

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

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:

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.