Parse retail PrivateUpdatePropertyInt64 and route authoritative Total/Available Experience through both local-player projections so Attributes, the level meter, and Skills refresh together. Preserve the existing retail XP curve and right-align the Total XP value. Close the user-confirmed item-give gate for #216 and record the named-retail/ACE/holtburger conformance evidence. Co-Authored-By: Codex <noreply@openai.com>
3.4 KiB
3.4 KiB
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 @ 0x006AEAD0validates opcode0x02CF, then readssequence:u8,property:u32, andvalue:i64from offsets 4, 5, and 9.ClientObjMaintSystem::Handle_Qualities__PrivateUpdateInt64 @ 0x00559000applies the property to the local player's qualities through the commonUpdateStat<Int64_QualityType, int64_t>path.gmStatManagementUI::PostInit @ 0x004EFD90registers the panel for playerStatType::Int64qualities 1 (TotalExperience), 2 (AvailableExperience), 6 (AvailableLuminance), and 7 (MaximumLuminance).gmStatManagementUI::Update @ 0x004F0020callsUpdateExperiencewhenever the registered player qualities refresh.gmStatManagementUI::UpdateExperience @ 0x004F0A70reads Int64 quality 1, reads Int quality0x19(level), derives the current and next cumulative-XP thresholds, writes the total-XP and XP-to-next text, and sets meter attribute0x69to(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
GameMessagePrivateUpdatePropertyInt64writes the same 17-byte body: opcode, one-byte sequence, property id, signed 64-bit value. - ACE marks
PropertyInt64.TotalExperience(1) andPropertyInt64.AvailableExperience(2)SendOnLogin, andGameEventPlayerDescriptionincludes both in the login Int64 table. - holtburger
crates/holtburger-protocol/src/messages/object/messages/properties.rsdefinesPrivateUpdatePropertyInt64Data = UpdatePropertyInt64<false>; its generic private-property unpacker readsu8 sequence, no guid,u32 property, theni64 value.
Faithful pseudocode
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
- Parse
0x02CFas an exact typed top-level message. - Publish a typed
PlayerInt64PropertyUpdatedevent fromWorldSession. - Apply the update to both existing local-player projections
(
ClientObjectTableandLocalPlayerState) so every retained UI consumer observes the same authoritative server value. - Keep the existing ExperienceTable calculation; it already matches
UpdateExperienceand was only starved of live data. - Preserve the imported retail layout and explicitly right-align only the
Total Experience value element (
0x10000235); its caption remains left aligned.