# Retail gameplay indicator bar pseudocode (2026-07-17) ## Scope and oracle This note covers retail LayoutDesc `0x21000071` (`gmIndicatorsUI`) and its seven 20x20 children. The behavior oracle is the named Sept-2013 retail decomp. The retained port continues to use the authored LayoutDesc states and sprites; code only chooses a state and dispatches the authored input action. Named retail functions: - `gmIndicatorsUI::ListenToElementMessage @ 0x004BFA90` - `gmUIElement_VitaeIndicator::PostInit @ 0x004E5EA0` - `gmUIElement_VitaeIndicator::Update @ 0x004E5FE0` - `gmUIElement_MiniGameIndicator::PostInit @ 0x004E6390` - `gmUIElement_MiniGameIndicator::RecvNotice_BeginGame @ 0x004E6450` - `gmUIElement_MiniGameIndicator::RecvNotice_EndGame @ 0x004E6470` - `gmUIElement_LinkStatusIndicator::SetLinkState @ 0x004E65E0` - `gmUIElement_LinkStatusIndicator::PostInit @ 0x004E66D0` - `gmUIElement_LinkStatusIndicator::UpdateLinkState @ 0x004E6730` - `gmUIElement_LinkStatusIndicator::UseTime @ 0x004E67C0` - `gmUIElement_EffectsIndicator::PostInit @ 0x004E6930` - `gmUIElement_EffectsIndicator::Update @ 0x004E6A50` - `gmUIElement_BurdenIndicator::PostInit @ 0x004E6B80` - `gmUIElement_BurdenIndicator::Update @ 0x004E6CE0` - `LinkStatusHolder::GetConnectionStatus @ 0x00411380` - `LinkStatusHolder::OnHeartbeat @ 0x004113D0` - `gmEffectsUI::SpellEffectMatchesUIType @ 0x004B76C0` - `gmGamePlayUI::RecvNotice_EndCharacterSession @ 0x004EBEA0` The installed retail LayoutDesc establishes the fixed child order and art: | X | Element | Retail class | Meaning / input action | |---:|---:|---:|---| | 5 | `0x100000F8` | `0x10000003` | link status / `LinkStatusPanel` | | 25 | `0x100000F5` | `0x10000002` | beneficial enchantments / `HelpfulSpellsPanel` | | 45 | `0x100000F6` | `0x10000002` | harmful enchantments / `HarmfulSpellsPanel` | | 65 | `0x100000F4` | `0x10000006` | vitae / `VitaePanel` | | 85 | `0x100000F7` | `0x10000001` | burden / `CharacterInformationPanel` | | 105 | `0x100000F3` | `0x10000004` | mini-game | | 125 | `0x100000FA` | ordinary button | end character session | ## Pseudocode ### Link indicator ```text PostInit: base.PostInit() listen to global time notices semanticLinkState = Good SetState(Connection_good = 17) On each global-time notice: if now - lastUpdate >= 4.0 seconds: (secondsSinceServerPacket, connected) = GetConnectionStatus() if !connected or secondsSinceServerPacket >= 40: Disconnected else if secondsSinceServerPacket >= 20: Bad else if secondsSinceServerPacket >= 5: Uncertain else: Good SetLinkState(result) lastUpdate = now if semanticLinkState == Bad and now - lastFlash >= 0.75 seconds: toggle visual state between Connection_uncertain (18) and Connection_bad (19) lastFlash = now SetLinkState(newState): if newState == semanticLinkState: return semanticLinkState = newState set authored state Good=17, Uncertain=18, Bad=19, Disconnected=20 when entering Bad: lastFlash = now OnHeartbeat: lastHeardFromCurrentServer = now packetLoss = linkAverages.averagePacketLoss ``` acdream records the last successfully decoded server datagram in `WorldSession`; the retained controller reads an immutable link snapshot. This keeps socket/session facts out of the UI while preserving retail's thresholds and cadence. ### Helpful and harmful effects ```text On PlayerDesc or enchantment change: helpfulCount = 0 harmfulCount = 0 for each raw multiplicative/additive enchantment node: spell = lookup spell metadata if spell flags contain Beneficial: helpfulCount++ else: harmfulCount++ Helpful button = Normal when helpfulCount > 0, else Ghosted Harmful button = Normal when harmfulCount > 0, else Ghosted ``` The indicator counts raw bucket-1/bucket-2 nodes. It does not use the effects panel's later spell-category duel projection. ### Vitae ```text On PlayerDesc or VitaeChanged: if enchantment registry has Vitae and vitae.modifier < 1.0: SetState(Normal = 1) else: SetState(Ghosted = 13) ``` ### Burden / character information ```text On PlayerDesc or LoadChanged: if InqLoad fails: load = absent if load is absent or load < 1.0: SetState(Unencumbered = 14) else if load < 2.0: SetState(Encumbered = 15) else: SetState(Heavily_encumbered = 16) On click: dispatch CharacterInformationPanel ``` `InqLoad` is the existing retail burden ratio: `EncumbranceVal / EncumbranceCapacity(Strength, augmentation)`. ### Mini-game ```text On BeginGame notice: SetState(Normal = 1) On EndGame notice: SetState(Ghosted = 13) ``` The authored ghosted state is rendered now. The notices remain owned by the still-unported mini-game subsystem (existing divergence AP-110), so this task does not manufacture a game-active state from unrelated packets. ### End character session ```text When child 0x100000FA sends Click: SendNotice_EndCharacterSession(1) RecvNotice_EndCharacterSession(1): build the shared logout confirmation dialog using ID_Client_EndCharacterSessionConfirm on acceptance, end the character session ``` The acdream port reuses `RetailDialogFactory`; acceptance closes the current client, whose existing `WorldSession.Dispose` sends retail's graceful CharacterLogOff and transport Disconnect packets. ## Cross-checks and retained boundaries - The already-ported `BurdenMath` matches retail `EncumbranceSystem::EncumbranceCapacity @ 0x004FCC00` and `EncumbranceSystem::Load @ 0x004FCC40`. - The existing enchantment parser preserves the separate Mult, Add, Vitae, and Cooldown registry buckets used by the retail indicator queries. - ACE/reference source is not present in this worktree. No server behavior is inferred for the strip: state is derived only from packets already parsed by the client, and the named retail client remains the behavior oracle. - Full Link Status, Vitae, and mini-game panels remain tracked by AP-110. This port does not replace them with an ad-hoc panel.