acdream/docs/research/2026-07-17-retail-indicator-bar-pseudocode.md
Erik 52c529be86 feat(ui): port retail gameplay indicator bar
Promote all seven LayoutDesc 0x21000071 controls to retained buttons, drive link quality, effects, Vitae, and burden from live state, and route Character Information plus end-session confirmation through the shared UI owners. Keep network timing in WorldSession and pin retail thresholds, flash cadence, authored states, and action routing with focused conformance tests.

Release build and all 5,807 tests pass with five intentional skips. Connected visual gate pending.

Co-authored-by: OpenAI Codex <codex@openai.com>
2026-07-17 10:02:44 +02:00

6 KiB

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

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

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

On PlayerDesc or VitaeChanged:
    if enchantment registry has Vitae and vitae.modifier < 1.0:
        SetState(Normal = 1)
    else:
        SetState(Ghosted = 13)

Burden / character information

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

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

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.