feat(items): port retail shared cooldown overlays
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>
This commit is contained in:
parent
19e8863f4e
commit
6b1ae4fb76
27 changed files with 875 additions and 20 deletions
|
|
@ -107,7 +107,7 @@ accepted-divergence entries (#96, #49, #50).
|
|||
|
||||
---
|
||||
|
||||
## 3. Documented approximation (AP) — 89 active rows
|
||||
## 3. Documented approximation (AP) — 90 active rows
|
||||
|
||||
Wave-0 UI ledger repair (2026-07-10) retired stale AP-38, resolved the AP-84
|
||||
collision, restored overwritten paperdoll rows as AP-92/AP-93, and registered
|
||||
|
|
@ -219,6 +219,7 @@ AP-94..AP-112 for the confirmed retail-UI completion gaps.
|
|||
| AP-120 | `ObjectTableWiring.ApplyEntitySpawn` publishes the CreateObject's WeenieDesc/item state before the same-generation physics update tail. Retail applies WeenieDesc after ObjDesc, Parent-or-Position/Pickup, Movement, State, and Vector. | `src/AcDream.Core.Net/ObjectTableWiring.cs` (`ApplyEntitySpawn`); `src/AcDream.App/World/LiveEntitySameGenerationUpdateRouter.cs` | Core.Net owns item-table ingestion before App callbacks and the current single-thread FIFO prevents a second network packet from interleaving; changing publication order crosses the Core.Net/App ownership boundary and requires a separately tested event transaction. | A synchronous item-table observer can see the refreshed WeenieDesc while the same object's physics/parent/state still reflects the prior snapshot; retail observers see the completed physics tail first. | `SmartBox::HandleCreateObject @ 0x00454C80`; `ACCObjectMaint::CreateObject @ 0x00558870` |
|
||||
| AP-121 | The `/framerate` command persists acdream's live ShowFps value in the modern `settings.json` Display bag after performing retail's live toggle/notice behavior. The named retail `DoFrameRate` body itself only flips `fShowFramerate` and sends `SetFramerateDisplay`; no equivalent persistence write occurs in that command body. | `src/AcDream.App/Settings/RuntimeSettingsController.cs` (`ToggleFrameRate`) | This preserves acdream's shipped L.0 cross-launch display preference while keeping the live retail mechanism and SmartBox visibility result exact; it changes no gameplay or wire state | A relaunch can retain FPS visibility where the named retail command body alone would not; a future port of retail's wider preference owner must avoid double-persisting or applying conflicting startup state | `ClientCommunicationSystem::DoFrameRate @ 0x005707D0`; `CM_UI::SendNotice_SetFramerateDisplay @ 0x0047A050`; `gmSmartBoxUI::RecvNotice_SetFramerateDisplay @ 0x004D65E0` |
|
||||
| AP-122 | The toolbar Use hand is Ghosted when selection is empty. Retail sets state 1 (Normal) and an empty-selection click enters generic `TARGET_MODE_USE`; selected-object enablement and activation remain retail-faithful. | `src/AcDream.App/UI/Layout/ToolbarController.cs` (`RefreshUseButton`) | User explicitly requires no selection to present as unavailable. The disabled retained button consumes the click without opening a cursor; every nonempty selection still uses the exact retail CombatUse/type/ItemUses predicate and the shared activation owner. | Generic pick-then-use mode cannot be entered from the empty toolbar hand; an object must first be selected, after which the same Use command is available. | `gmToolbarUI::HandleSelectionChanged @ 0x004BF380`; `gmToolbarUI::ListenToElementMessage @ 0x004BEE90` |
|
||||
| AP-123 | Item cooldowns use the retained toolkit's existing procedural `UiItemSlot` leaf rather than materializing retail's ten `m_elem_Icon_Cooldown_*` child elements. The group lookup, remaining-time formula, exact DAT sprites, step choice, and topmost ReadOrder-8 outcome are faithful. | `src/AcDream.App/UI/Layout/ItemCooldownUiController.cs`; `src/AcDream.App/UI/UiItemSlot.cs` | `UiItemSlot` already consumes/reproduces UIItem children procedurally under the IA-15 retained-toolkit architecture. Selecting one imported sprite at draw time gives every inventory/equipment/shortcut alias the same output without a parallel widget tree or per-cell timers. | A future feature that observes the individual cooldown child visibility/state rather than the rendered UIItem could see no child elements even though the cell looks and advances correctly. | `CEnchantmentRegistry::OnCooldown @ 0x005943C0`; `UIElement_UIItem::UpdateCooldownDisplay @ 0x004E1E20`; common UIItem prototype `0x1000033E` |
|
||||
|
||||
## 4. Temporary stopgap (TS) — 38 active rows + TS-37 historical note (TS-20 retired 2026-07-16 — the later named-retail audit disproved the proposed DrawingBSP polygon filter; TS-37 is a retired-row historical note, not an active count; TS-39 retired R5-V3 — sticky seams bound to the ported PositionManager/StickyManager, radii threaded; TS-45 retired 2026-07-07 — hand-rolled `SphereCollision` replaced by the faithful CSphere family port, fixing the player-vs-monster crowd wedge; TS-3 retired 2026-07-07 — `frames_stationary_fall` accounting ported in the #182 verbatim UpdateObjectInternal rebuild, fixing the airborne falling-animation wedge; TS-41 retired 2026-07-07 — SERVERVEL synth-velocity remote body-drive replaced by the retail interp catch-up + unconditional MovementManager::UseTime, the remote-creature de-overlap #184; TS-42 retired 2026-07-19 — semantic animation completion now precedes the ordered Target/Movement/PartArray/Position tail; TS-44 narrowed again 2026-07-19 — complete orientation joined interpolation, only during-stick enqueue suppression remains)
|
||||
|
||||
|
|
|
|||
|
|
@ -25,8 +25,12 @@ through the world-object approach query and silently cancelled because
|
|||
inventory objects have no spatial entity. Owned direct-use now sends
|
||||
immediately, while the registered AD-27 approach seam remains world-only;
|
||||
shared AutoWear reports the exact overlapping worn item that must be removed.
|
||||
That correction awaits its connected gate; the magnifying-glass Assess command
|
||||
follows in the same slice.
|
||||
That correction is user-confirmed. The requested adjacent cooldown follow-up
|
||||
ports the optional public shared-cooldown metadata, the authoritative
|
||||
cooldown-enchantment lookup, the exact ten-step bucket formula, and DAT
|
||||
sprites `0x060067CF..0x060067D8` through the shared retained `UiItemSlot`
|
||||
presentation. Its visual gate comes next; the magnifying-glass Assess command
|
||||
then follows in the same slice.
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -92,13 +92,12 @@ broaden the feature surface.
|
|||
Slice 1's 18-cell favorite-spell overflow bar is user-accepted. Slice 2 is
|
||||
active: the status hand's selected-object availability and existing
|
||||
Use/AutoWield/use-on-target command routing passed their connected gate.
|
||||
The follow-up retail correction for zero-valued direct-use items and named
|
||||
AutoWear blockers is code-complete. Its first connected gate exposed and
|
||||
corrected the lower delivery boundary: carried direct-use had been sent through
|
||||
the world approach query even though inventory objects have no spatial entity.
|
||||
Owned Use now sends immediately; world Use alone retains the registered AD-27
|
||||
approach seam. The combined correction awaits its connected gate before the
|
||||
Assess half continues.
|
||||
The follow-up retail correction for zero-valued direct-use items, named
|
||||
AutoWear blockers, and carried-use delivery is user-confirmed. The requested
|
||||
adjacent cooldown follow-up now preserves the item's shared group/duration,
|
||||
projects the authoritative cooldown-enchantment bucket, and draws retail's ten
|
||||
DAT-authored radial steps across all item-slot surfaces. Its visual gate is
|
||||
next; the Assess half then continues.
|
||||
|
||||
Carried:
|
||||
#153 far-teleport residual, #116 slide-response, the remaining R6 ownership cleanup plus
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
# World interaction completion — pre-M4 program
|
||||
|
||||
**Status:** Slice 1 user-accepted 2026-07-23. Slice 2 Use-hand selection state
|
||||
passed its connected gate. The follow-up zero-useability/AutoWear correction is
|
||||
code-complete and awaits its connected gate before Assess continues.
|
||||
**Status:** Slice 1 user-accepted 2026-07-23. Slice 2 Use-hand selection,
|
||||
zero-useability, carried direct-use, and AutoWear correction passed their
|
||||
connected gates. The requested shared item-cooldown follow-up is code-complete
|
||||
and awaits its visual gate before Assess continues.
|
||||
**Milestone:** M4 prerequisite/preamble.
|
||||
**Architecture:** retained gameplay UI over shared selection, object, and
|
||||
interaction state. `GameWindow` remains a composition/callback shell.
|
||||
|
|
@ -89,6 +90,22 @@ tests / 5 skips. The carried-use delivery correction adds an end-to-end App
|
|||
pin from Favor activation through wire dispatch and authoritative UseDone busy
|
||||
release. The Release build retains the 17 tracked test-project warnings;
|
||||
3,477 App tests / 3 skips and 7,858 complete-solution tests / 5 skips pass.
|
||||
The user confirmed Blackmoor's Favor now activates correctly.
|
||||
|
||||
Before Assess, the user requested the adjacent retail item-cooldown
|
||||
presentation. `PublicWeenieDesc` now preserves the optional shared cooldown id
|
||||
and duration, assessed property updates reach the same object fields, and
|
||||
`CEnchantmentRegistry::OnCooldown @ 0x005943C0` plus
|
||||
`UIElement_UIItem::UpdateCooldownDisplay @ 0x004E1E20` are ported through the
|
||||
canonical Core registry and one pure display projection. Every retained item
|
||||
list shares one controller and the exact
|
||||
ten DAT-authored radial sprites `0x060067CF..0x060067D8`; items with the same
|
||||
group display the same server-authored cooldown. Research:
|
||||
[`../research/2026-07-23-retail-item-cooldown-pseudocode.md`](../research/2026-07-23-retail-item-cooldown-pseudocode.md).
|
||||
The focused parser/Core/UI/production-DAT tests, warning-free Release solution
|
||||
build, 3,482 App tests / 3 skips, and 7,875 complete-solution tests / 5 skips
|
||||
pass. AP-123 records only the retained toolkit's procedural-child adaptation;
|
||||
the visible assets, ordering, timing, and shared-group behavior are exact.
|
||||
|
||||
## Slice 1 — spell-bar overflow arrows
|
||||
|
||||
|
|
@ -168,9 +185,9 @@ Named retail references and executable pseudocode are recorded in
|
|||
|
||||
### Remaining Slice 2 work
|
||||
|
||||
- Run the connected visual gate for the shared item cooldown: use Blackmoor's
|
||||
Favor or a summon/stone and verify its inventory and shortcut aliases count
|
||||
down together, then clear.
|
||||
- Port the magnifying-glass Assess command state and activation.
|
||||
- Run the connected correction gate: activate Blackmoor's Favor, then try to
|
||||
equip armor blocked by an overlapping worn item and verify the named system
|
||||
message.
|
||||
- Continue directly into the Assess information slice after the combined
|
||||
status-control gate.
|
||||
|
|
|
|||
|
|
@ -128,7 +128,7 @@ gives the per-state sprite ids (column 3 below).
|
|||
| `m_elem_Icon_OpenContainer` | `0x10000450` | 3 | "this container is open" frame | `DirectState 0x06005D9C Alphablend` (dump:2232) |
|
||||
| `m_elem_Icon_DragAccept` | `0x1000045A` | 3 | drag-rollover accept/reject frame | `ItemSlot_DragOver_Accept → 0x060011F9`, `_Reject → 0x060011F8`, `_DropIn → 0x060011F7` (dump:1174-1175,1258-1260) |
|
||||
| `m_elem_Icon_Quantity` | `0x100004F5` | 12 (Text) | the stack-count number | — |
|
||||
| `m_elem_Icon_Cooldown_10..100` | `0x1000054F..0x10000558` | 3 | 10-step radial cooldown ring | `DirectState 0x0600109D / 0x060012D9 / 0x06001DAE / 0x060067CF..D1 …` (dump:778-863) |
|
||||
| `m_elem_Icon_Cooldown_10..100` | `0x1000054F..0x10000558` | 3 | 10-step radial cooldown ring | Common physical UIItem prototype `0x1000033E`: exact ordered `0x060067CF..0x060067D8`; ReadOrder 8, above every other cell layer. See `2026-07-23-retail-item-cooldown-pseudocode.md`. |
|
||||
| `m_dragIcon` | `0x10000345` (created) | — | translucent drag-ghost | created via `CreateChildElement(this, dbobj, 0x10000345)`, `SetVisible(0)` (229738-229740) |
|
||||
|
||||
**The four named LayoutDesc states** that drive `m_elem_Icon` / `m_elem_Icon_DragAccept`
|
||||
|
|
|
|||
174
docs/research/2026-07-23-retail-item-cooldown-pseudocode.md
Normal file
174
docs/research/2026-07-23-retail-item-cooldown-pseudocode.md
Normal file
|
|
@ -0,0 +1,174 @@
|
|||
# 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.
|
||||
Loading…
Add table
Add a link
Reference in a new issue