# Retail experience-update pipeline pseudocode **Date:** 2026-07-13 **Scope:** live Total Experience, Unassigned Experience, and character-level XP meter ## Retail oracle - `CM_Qualities::DispatchUI_PrivateUpdateInt64 @ 0x006AEAD0` validates opcode `0x02CF`, then reads `sequence:u8`, `property:u32`, and `value:i64` from offsets 4, 5, and 9. - `ClientObjMaintSystem::Handle_Qualities__PrivateUpdateInt64 @ 0x00559000` applies the property to the local player's qualities through the common `UpdateStat` path. - `gmStatManagementUI::PostInit @ 0x004EFD90` registers the panel for player `StatType::Int64` qualities 1 (TotalExperience), 2 (AvailableExperience), 6 (AvailableLuminance), and 7 (MaximumLuminance). - `gmStatManagementUI::Update @ 0x004F0020` calls `UpdateExperience` whenever the registered player qualities refresh. - `gmStatManagementUI::UpdateExperience @ 0x004F0A70` reads Int64 quality 1, reads Int quality `0x19` (level), derives the current and next cumulative-XP thresholds, writes the total-XP and XP-to-next text, and sets meter attribute `0x69` to `(totalXp - currentThreshold) / (nextThreshold - currentThreshold)`. The named top-level dispatch table routes `0x02CD` to private Int32, `0x02CE` to public Int32, `0x02CF` to private Int64, and `0x02D0` to public Int64. acdream already handled the first two and was missing `0x02CF`. ## Cross-reference - ACE `GameMessagePrivateUpdatePropertyInt64` writes the same 17-byte body: opcode, one-byte sequence, property id, signed 64-bit value. - ACE marks `PropertyInt64.TotalExperience` (1) and `PropertyInt64.AvailableExperience` (2) `SendOnLogin`, and `GameEventPlayerDescription` includes both in the login Int64 table. - holtburger `crates/holtburger-protocol/src/messages/object/messages/properties.rs` defines `PrivateUpdatePropertyInt64Data = UpdatePropertyInt64`; its generic private-property unpacker reads `u8 sequence`, no guid, `u32 property`, then `i64 value`. ## Faithful pseudocode ```text on top-level game message(body): if body.opcode == 0x02CF: require body.length >= 17 sequence = body.u8_at(4) // transport ordering byte; latest value wins property = body.u32_at(5) value = body.i64_at(9) player.qualities.int64[property] = value notify registered quality observers on character-stat panel update(player.qualities): totalXp = qualities.int64[1] level = qualities.int32[0x19] current = ExperienceToLevel(level) next = ExperienceToLevel(level + 1) totalXpText = format(totalXp) xpToNextText = format(max(0, next - totalXp)) xpMeter = next > current ? clamp((totalXp - current) / (next - current), 0, 1) : 0 on skill panel update(player.qualities): unassignedExperienceText = format(qualities.int64[2]) ``` ## Port shape 1. Parse `0x02CF` as an exact typed top-level message. 2. Publish a typed `PlayerInt64PropertyUpdated` event from `WorldSession`. 3. Apply the update to both existing local-player projections (`ClientObjectTable` and `LocalPlayerState`) so every retained UI consumer observes the same authoritative server value. 4. Keep the existing ExperienceTable calculation; it already matches `UpdateExperience` and was only starved of live data. 5. Preserve the imported retail layout and explicitly right-align only the Total Experience value element (`0x10000235`); its caption remains left aligned.