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>
81 lines
3.4 KiB
Markdown
81 lines
3.4 KiB
Markdown
# 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<Int64_QualityType, int64_t>` 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<false>`; 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.
|