Preserve public shared-cooldown metadata, resolve the authoritative cooldown enchantment with retail expiry semantics, and project the exact ten DAT-authored radial steps through the shared retained item-slot architecture. Co-authored-by: Codex <codex@openai.com>
5.7 KiB
Retail item cooldown presentation
Date: 2026-07-23
Scope: the shared radial cooldown overlay drawn on inventory, equipment,
container, and shortcut UIElement_UIItem cells.
Retail oracle
Named-retail symbols:
PublicWeenieDesc::UnPack— public object metadata, including the optional shared cooldown id and duration.CEnchantmentRegistry::OnCooldown @ 0x005943C0— resolves the live cooldown enchantment and its remaining duration.UIElement_UIItem::PostInit @ 0x004E1870— binds the ten authored cooldown children.UIElement_UIItem::UpdateCooldownDisplay @ 0x004E1E20— selects the visible radial step.UIElement_UIItem::DoHeartbeat @ 0x004E2070— refreshes the display.
The named pseudo-C loses the x87 expression in
UpdateCooldownDisplay. Disassembly of the matching September 2013 client
recovers the exact bucket expression:
truncate((remaining / itemCooldownDuration) * 10 + 1)
The result 1..9 selects the 10%..90% child. A result at or above 10 selects
the 100% child. No child is visible when the cooldown is inactive or the item
duration is non-positive.
Wire metadata
PublicWeenieDesc second-header flags:
PWD2_Packed_CooldownID = 0x00000002
PWD2_Packed_CooldownDuration = 0x00000004
They occur after IconUnderlay and MaterialType, and before PetOwner:
if secondHeader & 0x00000002:
cooldownId = read_u32()
if secondHeader & 0x00000004:
cooldownDuration = read_f64() // seconds
Absence remains distinct from a present zero. Assessment properties use the
same semantic values: SharedCooldown PropertyInt 280 and CooldownDuration
PropertyFloat 167.
Registry lookup
Retail stores cooldowns in the enchantment-registry cooldown list. The
requested enchantment id is the item's shared cooldown group plus 0x8000.
The list is examined in stored order and the first matching record decides
the result:
OnCooldown(itemCooldownId, now):
if itemCooldownId == 0:
return inactive
requestedSpellId = itemCooldownId + 0x8000
for record in cooldownList, head to tail:
if low16(record.spellId) != requestedSpellId:
continue
remaining = record.duration + record.startTime - now
if remaining > 0:
return active, remaining
removeExpiredRecord(record)
return inactive
return inactive
The server remains authoritative for adding, updating, and removing the cooldown enchantment. Items that share a cooldown id therefore display the same live lockout without per-item client timers.
UI projection
UpdateCooldownDisplay(item, cooldownRegistry, now):
hide cooldown children 10..100
if item.cooldownDuration <= 0:
return
active, remaining =
cooldownRegistry.OnCooldown(item.cooldownId + 0x8000, now)
if not active:
return
step = truncate((remaining / item.cooldownDuration) * 10 + 1)
if step >= 10:
show cooldown_100
else if step >= 1:
show cooldown_[step * 10]
UIElement_UIItem::DoHeartbeat repeats this projection, so the artwork
advances while no object packet changes.
DAT-authored presentation
The common physical UIItem prototype is element 0x1000033E in catalog
LayoutDesc 0x21000037. Physical-item, container, and shortcut prototypes
inherit the same children:
| Step | Element | RenderSurface |
|---|---|---|
| 10% | 0x1000054F |
0x060067CF |
| 20% | 0x10000550 |
0x060067D0 |
| 30% | 0x10000551 |
0x060067D1 |
| 40% | 0x10000552 |
0x060067D2 |
| 50% | 0x10000553 |
0x060067D3 |
| 60% | 0x10000554 |
0x060067D4 |
| 70% | 0x10000555 |
0x060067D5 |
| 80% | 0x10000556 |
0x060067D6 |
| 90% | 0x10000557 |
0x060067D7 |
| 100% | 0x10000558 |
0x060067D8 |
The empty-slot-only prototype 0x10000341 has no cooldown children.
Cooldown children use ReadOrder 8, above icon/quantity/capacity (1),
waiting/drag-accept (3), selection (4), shortcut digits (5), sell (6), and
trade (7). The active cooldown sprite is therefore the last UIItem layer
drawn.
Reference cross-checks
- ACE serializes
SharedCooldown(PropertyInt 280) andCooldownDuration(PropertyFloat 167) in the same optional public description order. Cooldown enchantments are placed in the registry's cooldown bucket. - Holtburger parses the same optional
u32id andf64duration, and keeps enchantment spell/layer identifiers plusf64start/duration values.
These references confirm the packet and registry interpretation. The named
retail client and matching-binary disassembly remain authoritative for lookup
order, the 0x8000 offset, bucket selection, and draw ordering.
acdream architecture
CreateObjectpreserves optional cooldown metadata throughEntitySpawn,WeenieData, andClientObject.Spellbook.OnCooldownowns the ordered group lookup, remaining-time calculation, and exact expired-node removal.ItemCooldownDisplayowns the pure ten-step display calculation.ItemCooldownUiControllerbinds every current and futureUiItemListcell and evaluates represented items once in its pre-draw heartbeat.ItemCooldownAssetsimports all ten sprites from DAT. Missing assets fail visibly in diagnostics; no fabricated fallback ring is used.UiItemSlotdraws the selected sprite last, so inventory, paperdoll, external-container, and shortcut aliases share one mechanism.
Conformance cases
- optional metadata present, absent, and present-zero;
- exact
0x8000shared-group mapping; - cooldown-bucket filtering and stored-order first-match behavior;
- expired, zero-duration, and active records;
- exact bucket boundaries and 100% clamping;
- assessed property updates;
- propagation to existing and future item-list cells;
- exact production-DAT sprite sequence.