acdream/docs/research/2026-07-11-retail-selected-health-policy-pseudocode.md
Erik 2644d1d527 fix(ui): port exact selected health policy
Replace the toolbar's PWD-bit approximation with retail ObjectIsAttackable composed with the exact player and pet short-circuits. Carry second-header PetOwner through CreateObject, session, and ClientObject state so self, pet, and Free-PK cases match retail while friendly NPCs and attackable non-creatures remain name-only.

Co-Authored-By: Codex <codex@openai.com>
2026-07-11 11:42:04 +02:00

105 lines
3.5 KiB
Markdown

# Retail selected-object health policy pseudocode — 2026-07-11
## Oracle
- `gmToolbarUI::HandleSelectionChanged @ 0x004BF380`, health branch
`0x004BF725..0x004BF76C`.
- `ClientCombatSystem::ObjectIsAttackable @ 0x0056A600`.
- `ACCWeenieObject::IsPlayer/IsPK/IsPKLite @ 0x0058C890/0x0058C8B0/0x0058C8A0`.
- `PublicWeenieDesc::UnPack @ 0x005AD470`, second-header PetOwner read
`0x005AD7EC..0x005AD7F8`.
- Struct/flag definitions: `acclient.h` `PublicWeenieDesc`,
`PublicWeenieDescPackHeader2::PWD2_Packed_PetOwner = 0x8`.
ACE cross-check: `WorldObject_Networking.SerializeCreateObject` writes the second
header after the ordinary optional tail in this order: IconUnderlay, MaterialType,
CooldownId, CooldownDuration (`double`), PetOwner (`u32`), then alignment. Its
`UpdateObjectDescriptionFlags` maps the same PWD player/attackable/PK/Free-PK/PKLite
bits read by retail.
Chorizite/holtburger cross-check: the independently generated
`Chorizite.ACProtocol/Types/PublicWeenieDesc` schema and holtburger inventory client
inventory are catalogued in `deepdives/r06-items-inventory.md`; that schema pins the
same Header2 `0x01/0x02/0x04/0x08` order and field widths. Neither interpretation
aid changes the client behavior above; the named retail branch order remains ground truth.
## Constants
```text
ITEM_TYPE_CREATURE = 0x10
BF_PLAYER = 0x00000008
BF_ATTACKABLE = 0x00000010
BF_PLAYER_KILLER = 0x00000020
BF_FREE_PKSTATUS = 0x00200000
BF_PKLITE_PKSTATUS = 0x02000000
PWD2_PET_OWNER = 0x00000008
```
## Exact pseudocode
```text
ToolbarShouldQueryHealth(selectedId):
selected = lookup selectedId
if selected missing:
return false
if selected.stackSize > 1:
return false // stack UI is mutually exclusive
isPlayer = (selected.pwd.bitfield & BF_PLAYER) != 0
isPet = selected.pwd.petOwner != 0
if isPlayer OR isPet OR ObjectIsAttackable(selectedId):
QueryHealth(selectedId)
return true
return false
ObjectIsAttackable(targetId):
if targetId == 0 OR targetId == localPlayerId:
return true
target = lookup targetId
if target missing OR (target.type & ITEM_TYPE_CREATURE) == 0:
return false
if (target.pwd.bitfield & BF_FREE_PKSTATUS) != 0:
return true
player = lookup localPlayerId
if player missing:
return false
if (player.pwd.bitfield & BF_FREE_PKSTATUS) != 0:
return true
if target.IsPlayer():
if target.IsPK() AND player.IsPK():
return true
if target.IsPKLite() AND player.IsPKLite():
return true
return false
if target.pwd.petOwner != 0:
return false
return (target.pwd.bitfield & BF_ATTACKABLE) != 0
```
## Toolbar consequence
The toolbar's outer `IsPlayer || petOwner` test runs before
`ObjectIsAttackable`. Therefore every selected player and every selected pet gets
a health query regardless of PK pairing. The full PK/PKLite branches remain part
of the faithful reusable `ObjectIsAttackable` port, but they do not decide toolbar
player visibility. Relative to acdream's former PWD-only approximation, the
observable missing toolbar cases are:
1. the local player/self (formerly rejected explicitly),
2. creatures with `PetOwner != 0`,
3. Free-PK creatures, or ordinary creatures while the local player is Free-PK.
Friendly non-pet NPCs remain name-only. Non-creature objects remain name-only even
when they carry `BF_ATTACKABLE` (the existing attackable-door regression guard).