fix(ui): port live experience quality updates (#217)

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>
This commit is contained in:
Erik 2026-07-13 20:43:48 +02:00
parent 30d294506c
commit 84b7d2d7cd
15 changed files with 391 additions and 29 deletions

View file

@ -44,38 +44,40 @@ Copy this block when adding a new issue:
---
## #216 — Inventory drags into the 3-D world could only drop items
## #217 — Character windows did not receive live 64-bit experience updates
**Status:** IN-PROGRESS
**Status:** IN-PROGRESS — implementation complete; connected visual gate pending
**Severity:** HIGH
**Filed:** 2026-07-13
**Component:** retained UI / inventory / interaction / net
**Component:** retained UI / character sheet / net / player state
**Description:** Releasing a physical inventory item over an NPC always sent
the ground-drop path. A starter-dungeon character therefore could not hand the
exit token to the exit NPC and could not complete the dungeon.
**Description:** The Attributes window always displayed Total Experience as 0
and an empty level-progress meter after the player earned XP. The Skills footer
also displayed Unassigned Experience as 0, preventing an accurate raise-cost
decision. The Total Experience number was centered instead of right-aligned.
**Root cause / status:** Implementation complete; connected live gate pending.
The exact `AttemptPlaceIn3D` policy and the `0x00CD`
packet builder already existed, but `GameWindow.OnUiDragReleasedOutside`
discarded the release coordinates and always called `DropToWorld`, hard-coding
`TargetId=0`. The release path now world-picks at the actual release point,
the controller executes its existing `GiveToTarget` action without optimistic
mutation, `WorldSession` sends the exact request, and PlayerDescription drives
the retail player secure-trade preference.
**Root cause / status:** acdream parsed the neighboring private/public Int32
quality messages (`0x02CD`/`0x02CE`) but omitted retail
`PrivateUpdatePropertyInt64 (0x02CF)`. Both TotalExperience (quality 1) and
AvailableExperience (quality 2) use that message. The exact parser/event/state
path is now ported, both local-player projections are synchronized in one
wiring owner, the existing retail XP-band formula receives live data, and only
the value element `0x10000235` is explicitly right-aligned.
**Files:** `src/AcDream.App/UI/ItemInteractionController.cs`;
`src/AcDream.App/Rendering/GameWindow.cs`;
`src/AcDream.Core.Net/WorldSession.cs`.
**Files:** `src/AcDream.Core.Net/Messages/PrivateUpdatePropertyInt64.cs`;
`src/AcDream.Core.Net/WorldSession.cs`;
`src/AcDream.Core.Net/ObjectTableWiring.cs`;
`src/AcDream.Core/Player/LocalPlayerState.cs`;
`src/AcDream.App/UI/Layout/CharacterStatController.cs`.
**Research:** `docs/research/2026-07-13-retail-give-item-pseudocode.md`;
`ItemHolder::AttemptPlaceIn3D @ 0x00588600`;
`ACCWeenieObject::UIAttemptGive @ 0x0058D620`;
`CM_Inventory::Event_GiveObjectRequest @ 0x006ABB00`.
**Research:** `docs/research/2026-07-13-retail-experience-update-pseudocode.md`;
`CM_Qualities::DispatchUI_PrivateUpdateInt64 @ 0x006AEAD0`;
`ClientObjMaintSystem::Handle_Qualities__PrivateUpdateInt64 @ 0x00559000`;
`gmStatManagementUI::UpdateExperience @ 0x004F0A70`.
**Acceptance:** Drag the starter-dungeon exit token from the backpack onto the
exit NPC. ACE accepts the token, the inventory removes/decrements it only from
the authoritative server response, and the quest teleport exits the dungeon.
**Acceptance:** Earn XP while logged in. Attributes immediately shows the new
right-aligned total and red progress fill; Skills immediately shows the same
server-authoritative unassigned XP available for raises.
---
@ -8225,6 +8227,18 @@ Fixed by the A7 lighting rework, specifically A7 Fix D (`0980bea4`, `cf627933`,
**Status:** DONE (2026-07-09, investigated this session).
The described mechanism (live entities collapsing to one root-centered cylinder) no longer exists. `RegisterLiveEntityCollision` (`GameWindow.cs:4244`) now builds a multi-shape list via `ShadowShapeBuilder.FromSetup` — one shape per CylSphere, Sphere, and BSP-bearing Part — and registers via `RegisterMultiPart` (A6.P4 Task 7, `ca9341c2`). The retail `CSphere` and `CCylSphere` collision families were ported verbatim in `78e57581` (sphere primitive), `6ab26989` (fix #172, CCylSphere family), and `96ae2740` (fix #182, CSphere family, retires TS-45). The remaining `setup.Radius` single-cylinder path is now a deliberately-audited fallback used only when no other shape data exists. `tests/AcDream.Core.Tests/Physics/ShadowShapeBuilderShapeSourceTests.cs` covers both a multi-shape object and a live-creature case.
---
## #216 — Inventory drags into the 3-D world could only drop items
**Status:** DONE (2026-07-13, `30d29450`, user-confirmed connected gate).
Dragging the starter-dungeon exit token onto its NPC now world-picks at the
actual release point, executes the retail `GiveToTarget` path without optimistic
inventory mutation, receives ACE's authoritative removal, and completes the
quest teleport out of the dungeon.
---
<!--
Example: