# 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: ```text 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: ```text PWD2_Packed_CooldownID = 0x00000002 PWD2_Packed_CooldownDuration = 0x00000004 ``` They occur after IconUnderlay and MaterialType, and before PetOwner: ```text 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: ```text 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 ```text 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`) and `CooldownDuration` (`PropertyFloat 167`) in the same optional public description order. Cooldown enchantments are placed in the registry's cooldown bucket. - Holtburger parses the same optional `u32` id and `f64` duration, and keeps enchantment spell/layer identifiers plus `f64` start/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 - `CreateObject` preserves optional cooldown metadata through `EntitySpawn`, `WeenieData`, and `ClientObject`. - `Spellbook.OnCooldown` owns the ordered group lookup, remaining-time calculation, and exact expired-node removal. - `ItemCooldownDisplay` owns the pure ten-step display calculation. - `ItemCooldownUiController` binds every current and future `UiItemList` cell and evaluates represented items once in its pre-draw heartbeat. - `ItemCooldownAssets` imports all ten sprites from DAT. Missing assets fail visibly in diagnostics; no fabricated fallback ring is used. - `UiItemSlot` draws 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 `0x8000` shared-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.